基本语言(一)

1 说一下static关键字的作用

静态变量:编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。另外,如果没有显式地初始化静态变量,编译器将把它设置为0.这种变量被称为零初始化的(zero-initialized)。

(1)全局静态变量

在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.

静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

(2)局部静态变量

在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

内存中的位置:静态存储区

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

5种变量存储方式

这里指的是声明为static的变量对外的访问性,默认的全局变量是extern的。

存储描述 持续性 作用域 链接性 如何声明
自动 自动 代码块 在代码块中
寄存器 自动 代码块 在代码块中,使用关键字register
静态,无链接性 静态 代码块 在代码块中,使用关键字static
静态,外部链接性 静态 文件 外部 不再任何函数内
静态,内部链接性 静态 文件 内部 不再任何函数内,使用关键字static

补充关键字:静态持续性、外部链接性

(3)静态函数

在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;

warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;

(4)类的静态成员

详细请阅读C++ Primer Plus第六版 P428相关文章

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。

一个不恰当的比喻就是:静态成员就好比公用电话,所有对象共同使用;非静态成员就好比手机,每个对象使用自己的

注意:不能再类声明中初始化静态成员变量。这是因为声明描述了如何分配内存,但并不分配内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。初始化语句指出了类型,并使用了作用域运算符,但没有使用关键字static。

<类型> <类名>::<变量名> = <值>

如果静态类成员是私有,则不能通过类名+变量名访问,会报如下错误!

JBZHD1

是否可以继承

子类可以访问弗雷德static变量,但受访问控制(若父类中的static是private就无法访问)

子类和父类的static变量是同一变量,共享同一存储空间。

而继承关系,子类和父类是分别有自己的存储空间。

(5)类的静态函数

静态成员函数和静态数据成员一样,他们都属于类的静态成员,他们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。

调用格式:<类名>::<静态成员函数>(<参数表>)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/*
myString.h
(4)(5)说明
*/
#pragma once
#include <iostream>
using namespace std;
class myString {
private:
char * str;
int len;
// static int numStrings;
public:
static int numStrings; // 如果是私有的,则无法通过<类名>::<变量名>访问
myString(const char * s);
myString();
~myString();
static void getdata(myString s);
//friend function
friend ostream & operator<<(ostream & os,
const myString & st);
};
/*
myString.cpp
(4)(5)说明
*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "myString.h"
#include <stdlib.h>
#include <cstring>
#include <string.h>
using namespace std;
int myString::numStrings = 0;
myString::myString(const char * s)
{
len = strlen(s);
str = new char[len + 1];
strcpy(str,s);
numStrings++;
cout << numStrings << ": \"" << str
<< "\" object created\n";
}
myString::myString()
{
len = 4;
str = new char[4];
strcpy(str, "C++");
numStrings++;
cout << numStrings << ":\"" << str
<< "\"object created\n";
}
myString::~myString()
{
cout << "\""<<str<<"\" object deleted,";
--numStrings;
cout << numStrings << "left \n";
delete[] str;
}
ostream & operator<<(ostream & os,
const myString & st)
{
os << st.str;
return os;
}
void myString::getdata(myString s)
{
cout << "numStrings:" << numStrings << endl; //numStrings是静态成员,可以在静态成员中访问
cout << "当前传入字符为:" << s.str << endl;//在静态成员函数中,只能通过对象访问类的非静态成员
cout << "当前传入字符长度为:" << s.len << endl;
}
/*
main.h
(4)(5)说明
*/
#include <iostream>
#include "myString.h"
using namespace std;
int main()
{
cout << "测试开始" << endl;
myString headline1("足球");
myString headline2("羽毛球");
//静态类成员函数调用直接通过类名
cout << "执行静态类成员函数:\n" << "--------------\n";
myString::getdata(headline2);
//headline1.getdata(headline1); 通过对象名也可以
cout << "--------------\n";
cout << "测试结束" << endl;
system("pause");
return 0;
}

2 C++和C的区别

设计思想上:

C++是面向对象的语言,而C是面向过程的结构化编程语言

语法上:

C++具有封装、继承和多态三种特性

C++相比C,增加多许多类型安全的功能,比如强制类型转换

C++支持范式编程,比如模板类、函数模板

3 C++中的static关键字的作用

  • 对于函数定义和代码块之外的变量声明,static修改标识符的链接属性,由默认的external变为internal,作用域和存储类型不改变,这些符号只能在声明它们的源文件中访问。

  • 对于代码块内部的变量声明,static修改标识符的存储类型,由自动变量改为静态变量,作用域和链接属性不变。这种变量在程序执行之前就创建,在程序执行的整个周期都存在。

  • 对于被static修饰的普通函数,其只能在定义它的源文件中使用,不能在其他源文件中被引用
  • 对于被static修饰的类成员变量和成员函数,它们是属于类的,而不是某个对象,所有对象共享一个静态成员。静态成员通过<类名>::<静态成员>来使用。

4 C++中显式类型转换和隐式类型转换

显式类型转换

参考回答:

1.const_cast

用于将const变量转为非const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
int age = 40;
const int agep = age;
cout << "const of age:" << agep << endl;
int &nConstAge = const_cast<int&>(agep);
// 修改nConstAge的值看看是否能成功修改age的值
nConstAge = 18;
cout << "new const of age:" << agep << endl;

system("pause");
return 0;
}


在这里插入图片描述

2.static_cast

用于将const变量转为非const

3.dynamic_cast

用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化是,如果是非法的对于指针返回NULL,对于引用跑一场。要深入了解内部转换的原理。

  • 向上转换:指的是子类向基类的转换
  • 向下转换:指的是基类想子类的转换

她通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够向下转换。

4.reinterpret_cast

几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

隐式类型转换

算术转换

如果是不同类型的变量,计算+-*/的时候,会出现类型转换,最后会转换成只有一种类型的式子进行运算。

在进行运算时,以表达式中最长类型为主,将其他类型位据均转换成该类型,如:

(1)若运算数中有double型或float型,则其他类型数据均转换成double类型进行运算。

(2)若运算数中最长的类型为long型.则其他类型数均转换成long型数。

(3)若运算数中最长类型为int型,则char型也转换成int型进行运算。算术转换是在运算过程中自动完成的。

大致就是如下所示,算式中的变量会转换为长度更长的更高的类型。此种情况的的还有bool类型的变量,除0之外,其他变量都会被bool类型转换为true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
int a = 2;
double b = 3.1415;
char c = 'a';
cout << typeid(a*b-c).name() << endl;
bool flag = 0.01;
cout << flag << endl;
system("pause");
return 0;
}

image-20210111125320791

赋值转换

进行赋值操作时,赋值运算符右边的数据类型必须转换成赋值号左边的类型,若右边的数据类型的长度大于左边,则要进行截断或舍入操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
int a = 2;
double b = 3.1415;
char c = 'a';
/*
1. a-->double
2. c-->int 原式就变成了 double-int
3. int-->double
原始式子就是变成了 int = double
最后等号部分,double-->int
*/
int result = a*b-a*c;
system("pause");
return 0;
}

image-20210111131113541

输出转换

输出转换就是在程序中用printf()函数以指定格式输出,当要输出的数据类型与输出格式不符合时,会转化为以输出格式的输出形式。

1
2
char a = 'a';
printf("输出a的ASCII值:%d", a); //输出“输出a的ASCII值:97”

5. 为何说呢么不使用c的强制转换?

C的强制转换表面上看起来功能很强发什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

5 请说一下C/C++中指针和引用的区别

  1. 指针有自己的一块空间,而引用只是一个别名;

  2. 使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

  3. 指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用;

  4. 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;

  5. 可以有const指针,但是没有const引用;

  6. 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;

  7. 指针可以有多级指针(**p),而引用止于一级;

  8. 指针和引用使用++运算符的意义不一样;

  9. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

6 C++中的smart pointer 四个只能指针:

shared_ptr,unique_ptr,weak_ptr,auto_ptr

参考回答:

C++里面的三个智能指针:auto_ptr,shared_ptr,weak_ptr,unique_ptr其中后三个是C++11支持,并且第一个已经被11弃用。

为什么要使用智能指针:

智能指针的作用是管理一个指针,因为存在一下这种情况:申请的空间在函数结束时忘记释放,造成内存泄露。使用智能指针可以很大程度上的避免这个问题,与因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是函数结束时自动释放内存空间,不需要手动释放内存空间。

1、auto_ptrC++98的方案,C++11已经抛弃

采用所有权模式。

1
2
3
auoto_ptr<string>  p1 (new string ("I reigned lonely as a cloud."));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时,访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

2、unique_ptr替换auto_ptr

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个只能指针可以指向该对象。它对于避免资源泄露(例如:以new创建对象后因为发生异常而忘记调用delete,特别有用。

采用所有权模式,还是上面那个例子

1
2
3
unique_ptr<string> p3 (new string ("auto"));
unique_ptr<string> p4;
p4 = p3; // 此时会报错~

编译器认为p4=p3非法,避免了p3不再设置项有效数据的问题。因此,unique_ptr比auto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序视图将一个unique_ptr赋值给另一个时,如果源unique_ptr是个临时右值,编译器允许这么做;如果源unique_ptr将存在一段时间,编译器将禁止这么做,比如:

1
2
3
4
5
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; //#1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); //#2 allowed

其中#1留下的悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象再起所有权让给pu3后就会被销毁。这种情况而已的行为表明,unique_ptr由于允许两种赋值的auto_ptr。

:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

1
2
3
4
5
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

3、shared_ptr

shared_ptr实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,他使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造。还可以通过传入auto_ptr、unique_ptr、weak_ptr来构造。当我们使用release()时,当前指针会释放资源苏有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数 说明
use_count 返回引用计数的个数
unique 返回是否是独占所有权
swap 交换两个shared_ptr对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少
get 返回内部对象(指针),由于已经重载了()方法,因此和直接使用对象是一样的。
shared_ptr<int> sp(new int (1)); spsp.get()是等价的

4、weak_ptr

weak_ptr是一种不控制对象生命周期的只能指针,它指向一个shared_ptr管理的对象。进行该对象的内训管理的是那个强引用的shared_ptr。weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用技术的增加或见啥,weak_ptr是用来解决shatred_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数不可能下降为0,资源永远不会释放。它是对对象的一种弱引用不会增加对象的引用计数,和share_ptr之间可以相互转化,shared_ptr可以直赋值给它,它可以通过lock()函数来获得shared_ptr。

这篇文章还在编辑中······