c++(8·斜杠、嵌套类、指针阅读、联合体、智能指针)
正、反、双反 斜杠
正斜杠(/): 支持跨平台
反斜杠(\):Windows原生路径分隔符, \也是转义前导字符,用于和后面跟随的字母构成转义字符,因此通常不会直接使用它,而是会使用\ \
双反斜杠(\ \):一个\后面是\,那么第一个\会起到避免第二个\转义的作用
嵌套类
嵌套类:将一个类的定义放到另一个类中,目的在于隐藏类名(限制用户使用该类建立对象),减少全局的标识符,强调主从关系(必须创建A才会有B)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A
{
public:
class B
{
};
}
class A
{
};
class B
{
};
展开
- 嵌套类与外围类的对象成员具有相同的访问权限规则
- 嵌套类不属于外围类,它们之间不可以直接访问对方的成员,常分解看作非嵌套类来处理
指针阅读
右左法则:从标识符开始,首先向右看,直到遇括号或者结束, 然后向左看,直到遇到括号或者结束,跳出括号,再向右看然后向左看,直到全部阅读完为止
1
2
3
4
5
6
7
8
9
int i;//从i开始,右侧没有看左侧,则i是int类型
int *a[3];//从a开始,右侧说明a是包含3个元素的数组,左侧说明每个元素是int*类型
int (*a)[3];//从a开始,右侧是括号,左侧说明a是指针,左侧遇到括号,右侧说明a指向包含3个元素的数组,左侧表明每个元素是int类型
int **p;//p是指针,指向的对象类型也是指针(二级指针),此指针指向的类型是int
int *foo();//从foo开始,右侧说明foo是函数,左侧说明返回类型是int*
int (*foo)();//从foo开始,右侧是括号,左侧说明foo是指针,左侧遇到括号,右侧说明foo指向函数(函数指针),左侧说明返回类型是int
int (*(*vtable)[])();//vtable是指针,指向数组,每个元素是指针类型,指向函数(函数指针),左侧说明此元素返回类型是int
int *(*p(int))[3];//p是包含int类型参数的函数,返回类型为指针,此指针指向包含3个元素的数组,每个元素类型是指针,指向int类型对象
int (*(*p)(int))(int);//p是指针,指向包含int类型参数的函数,函数返回类型为指针,指针指向包含int类型参数的函数,函数返回类型为int
展开
union
1
2
3
4
5
6
7
8
9
union name(可选){ // 无名联合体可直接使用变量名,有名的联合体需要创建name类型的对象
int i;
float f;
};//联合体大小 == 4
union U1 {
int n;
char s[11];
double d;
};//联合体大小 == 16
展开
联合体/共用体,它类似于结构体,都可以存储不同数据类型的对象,并且它默认的成员访问修饰符是public的,
但是不同于结构体的内存分配机制,允许在 同一块内存空间 存储不同的数据类型,所有成员共享内存,它所有成员的偏移量都是0,内存地址都相同,union的内存容量取决于最大的成员,内存对齐要考虑所有的成员,
修改一个成员会影响其他成员的值,同一时间只能使用其中一个成员,因为它是最安全的,如果使用非赋值的成员,可能得到垃圾值或程序崩溃
当多个数据每次只取其一时,可以利用联合体,它可以节省内存
智能指针
智能指针帮助我们自动释放动态分配的内存
auto_ptr(C++11中已弃用)
它的原理是将指向动态内存的指针,被有生命周期的对象托管,那么在对象生命周期结束时,它的析构函数中会调用delete,释放托管的指针指向的动态内存
1
2
3
4
5
6
7
8
9
10
11
12
#include < memory >
auto_ptr<type> test(new type);//auto_ptr是类模板
Test *tmp = test.get();
Test *tmp2 = test.release();
delete tmp2;
test.reset();// 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之
auto_ptr<int[]> array(new int[5]);//❌:不支持管理数组
展开
- get()获取托管的指针
- release() 取消托管, 将内部指针置为nullptr,因此我们需要手动delete释放内存
- reset() 重置托管的指针,
- 如果不传入参数,释放托管的指针,
- 如果有参数,如果它和原指针指向的地址不一致,原来的会被delete掉, 托管更新为这个指针
- *和->,auto_ptr类内重载了这两个运算符,会对内部指针进行操作
auto_ptr的问题,在unique_ptr可以解决:
- 赋值会改变资源的所有权,因此很有可能导致意外的行为
- 比如p1 = p2, p1托管p2托管的指针,p2的托管指针置为nullptr
- 比如在STL容器中很有可能访问越界,因为一旦元素间赋值,右侧的元素将置为nullptr,再访问它将程序崩溃,我们不可以访问无效的内存地址
unique_ptr
1
2
3
4
5
6
7
8
unique_ptr<string> t1;//创建空对象
unique_ptr<string> p1(new string("hello"));//类模板
p1 = p2; // ❌:禁止左值拷贝赋值
unique_ptr<string> p3(p2); // ❌:禁止左值拷贝构造
p1 = std::move(p2); //✅:
unique_ptr<string> p3(std::move(p1)); //✅:
unique_ptr<int[]> array(new int[5]);//✅:支持管理数组
t9 = nullptr;//释放对象
展开
和auto_ptr很相似
- 离开作用域调用析构会释放内存
- 函数的使用和作用几乎一致
- 多个指针不能指向同一个资源(auto_ptr和unique_ptr都会移动语义:转移所有权)
- 但不允许左值拷贝构造/赋值,允许右值拷贝构造/赋值,那么p1 = p2,p1托管p2的指针, p2的托管指针置为nullptr
shared_ptr
1
2
3
4
5
6
7
shared_ptr<string> sp1;
shared_ptr<string> sp2(new string("hello"));
sp1 = sp2;
shared_ptr<string> sp3(sp1);
shared_ptr<string[]> sp5(new string[5] { 3, 4, 5, 6, 7 });
shared_ptr<int> up3 = make_shared<int>(2);
up1 = nullptr ;//释放对象
展开
- shared_ptr允许多个指针指向同一个资源,它采用引用计数的方式,当拷贝构造/拷贝赋值时,引用计数加1,当析构/重置/被赋值时,引用计数减1,如果计数为零,就释放动态内存
- 允许拷贝构造/赋值
- use_count()可以获取引用计数个数
- 使用make_shared 初始化对象,分配内存效率更高(常用初始化方式)
循环引用问题
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
class A{
void Set(shared_ptr<B> _b) {
this->b = _b;
}
private:
shared_ptr<B> b;
};
class B{
void Set(shared_ptr<A> _a) {
this->a = _a;
}
private:
shared_ptr<A> a;
};
void fun(){
shared_ptr<A> a(new A());//引用计数1
shared_ptr<B> b(new B());//引用计数1
a->Set(b);//引用计数2
b->Set(a);//引用计数2
}
int main(void) {
fun();
//引用计数1
//引用计数1
return 0;
}
展开
当调用fun()结束时不会释放内存,
- 创建a和b智能指针对象,指向A和B内存引用计数++
- 调用Set(),类内的成员智能指针指向对方对象指向的内存,指向A和B内存引用计数++
- 当fun结束a和b智能指针对象销毁,指向A和B内存引用计数–,A和B内存仍存在,因为各自仍有一个引用计数
有一种解决方式,将任意一个Set注释掉,假如注释掉b->Set,
- 创建a和b智能指针对象,指向A和B内存引用计数++
- 调用Set(),指向B内存引用计数++
- 当fun结束a和b智能指针对象销毁,指向A和B内存引用计数–,由于指向A的引用计数为0被释放内存,A内存中的b成员生命周期结束被消耗,指向B内存引用计数–,b对象销毁,指向B内存引用计数–
- 这样A和B内存都会被释放
weak_ptr
弱指针 设计目的是为了协助 shared_ptr 工作, 相当于托管了shared_ptr(就像shared_ptr托管动态内存一样)
1
2
3
4
5
6
shared_ptr<A> a(new A());
shared_ptr<B> b(new B());
weak_ptr<A> w(a); // 使用共享指针构造
weak_ptr<B> w1; // 使用共享指针构造
w1 = b; // 使用共享指针赋值
shared_ptr<B> b1= w1.lock();
展开
- 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造,
- 它的构造和析构不会引起引用记数的增加或减少
- 没有重载*和->
- lock()获得一个可用的 shared_ptr 对象
- expired()是否过期,有托管则返回false,无托管则返回true
1
2
3
4
5
6
class A{
……
private:
weak_ptr<B> b;//💡
};
……
展开
- 可以解决shared_ptr的循环引用问题,我们将A的成员变量改变为weak_ptr,它的原理和上述将任意一个Set注释掉是一样的