指针
本质上说,指针和普通变量没有区别。
指针都是存储在栈上或堆上,不管在栈上还是堆上,都一定有一个地址。
因为不同的变量类型占用不同的存储空间, 所以声明指针变量必须指定指针所指向变量的数据类型
在32位系统中,int 变量和指针都是32位。指针必须和&,*这两个符号一起使用才有意义。
&a | a | *a |
|---|---|---|
a这个变量的地址 | a对应地址存储的值 | 对应地址存储的值作为地址对应的值 |
NULL和nullptr
类型安全: nullptr 是C++11引入的关键字,它是一个指针类型,明确表示空指针。 NULL 是一个宏定义,通常被定义为整数0或者(void*)0。在C++中,NULL 可能会被解释为整数0,这可能导致一些类型安全问题。 代码可读性:
使用 nullptr 可以更清晰地表达你的意图,表明你正在使用一个空指针。 使用 NULL 时,可能会让人误以为是一个整数0,降低代码的可读性。 函数重载: 在函数重载时,nullptr 可以更好地匹配指针类型的参数,避免歧义。 NULL 可能会被解释为整数0,导致重载决策错误。
总结:
- 在C++中,推荐使用
nullptr来表示空指针,因为它更安全、更具可读性,并且可以避免函数重载时的歧义。 - 尽量避免使用
NULL,特别是在C++代码中。
数组
数组名是该数组首元素的地址 作为实际参数的数组名要求形式参数是一个与之匹配的指针
只有在这种情况下 c才会把int ar[]和int* ar解释成一样 也就是说 ar是指向int的指针 
初始化
字符数组所有未被使用的元素都被自动初始化为'\0'
//m1和m2不一样 因为m2 最后缺少空字符 \0
const char m1[40] = "helloworld";
const char m2[40] = "'H','e','l'....";
//让编译器确定初始化字符数组的大小很合理
//如果创建一个稍后再确定的数组就必须在声明时指定大小
const char m3[] = "if you";储存位置
- 通常字符串都作为可执行文件的一部分 储存在数据段中
- 当把程序载入内存时 也载入了程序中的字符串(储存在静态储存区)。
- 程序运行时 才会为该数组分配内存 此时将字符串拷贝到数组中。
- 此时字符串副本有2个 一个是静态内存区 一个是array中的。
定义数组
*p允许++p,p++这样的操作。array[]不允许*p通常只能用于变量前,不能用于常量。array[]是地址常量*p该变量是最初指向该字符串的首字符- 如果打算修改字符串 就不要用指针指向字符串字面量
智能指针
直接使用new和delete运算符极其容易导致内存泄露,而且非常难以避免。
于是人们发明了智能指针这种可以自动回收内存的工具。
智能指针和裸指针不要混用,这一点非常重要
共享型智能指针 (shared_ptr)
普通的指针可以单独一个指针占用一块内存,也可以多个指针共享一块内存。同一块堆内存可以被多个shared_ptr共享。
在现代程序中,当想要共享一块堆内存时,优先使用shared_ptr,可以极大的减少内存泄露的问题。
工作原理
我们在动态分配内存时,堆上的内存必须通过栈上的内存来寻址。也就是说栈上的指针(堆上的指针也可以指向堆内存,但终究是要通过栈来寻址的)是寻找堆内存的唯一方式。
所以我们可以给堆内存添加一个引用计数,有几个指针指向它,它的引用计数就是几,当引用计数为0是,操作系统会自动释放这块堆内存。
常用操作
智能指针可以像普通指针那样使用,shared_ptr早已对各种操作进行了重载,就当它是普通指针就可以了。
初始化
一般来说不推荐使用new进行初始化,因为C++标准提供了专门创建shared_ptr的函数make_shared,该函数是经过优化的,效率更高。
std::shared_ptr<int> sharedI(new int(100));使用make_shared函数进行初始化:
std::shared_ptr<int> sharedI = std::make_shared<int>(100);注意:千万不要用裸指针初始化shared_ptr,容易出现内存泄露的问题。
//虽然可以这样做 但是极容易导致内存泄漏问题
int* pi = new int(100);
std::shared_ptr<int> sharedI(pi);当然使用复制构造函数初始化也是没有问题的。
//如果不能用 那么共享型的共享二字还有什么意义呢 所以这是天经地义的
std::shared_ptr<int> sharedI1 = std::make_shared<int>(100);
std::shared_ptr<int> sharedI2(sharedI1);引用计数
智能指针就是通过引用计数来判断释放堆内存时机的。use_count()函数可以得到shared_ptr对象的引用计数。
int main() {
std::shared_ptr<int> sharedI = std::make_shared<int>(100);
std::cout << sharedI.use_count() <<std::endl;
std::shared_ptr<int> sharedI2(sharedI);
std::cout << sharedI.use_count() <<std::endl;
shared2.reset();
std::cout << sharedI.use_count() << std::endl;
}常用函数
unique函数
判断该shared_ptr对象是否独占若独占,返回true。否则返回false。
reset函数
- 当
reset函数有参数时,改变此shared_ptr对象指向的内存。 - 当
reset函数无参数时,将此shared_ptr对象置空,也就是将对象内存的指针设置为nullptr。
get函数
- 智能指针就是对裸指针的封装。这个函数就是获取裸指针。
- 强烈不推荐使用。因为太不推荐使用, 所以
visual studio的列表中都没有。 - 如果一定要使用,那么一定不能
delete返回的指针。
swap函数
- 交换两个智能指针所指向的内存
- std命名空间中全局的swap函数
shared_ptr类提供的swap函数
注意事项
创建数组
int main() {
//有这个语法 但是c++20后才可以放心这么做
std::shared_ptr<int> sharedI(new int[100]());
std::cout << sharedI.get()[10] << std::endl;
}参数传递
用智能指针作为参数传递时直接值传递就可以了。
shared_ptr的大小为固定的8或16字节- 也就是两倍指针的的大小,32位系统指针为4个字节,64位系统指针为8个字节,
shared_ptr中就两个指针 shared_ptr就是封装了一个指针,一个计数器。真正的内容都在堆空间中。所以直接值传递就可以了。
void myFunc(std::shared_ptr<int> sharedI) {}弱引用智能指针 (weak_ptr)
也是一种共享型智能指针,可以视为对共享型智能指针的一种补充
这个智能指针是在C++11的时候引入的标准库,它的出现完全是为了弥补shared_ptr天生有缺陷的问题
其实shared_ptr可以说近乎完美。只是通过引用计数实现的方式也引来了引用成环的问题,这种问题靠它自己是没办法解决的。
所以在C++11的时候将shared_ptr和weak_ptr一起引入了标准库,用来解决循环引用的问题。
以下这段代码出现了循环引用的问题
class B;
class A{
public:
std::shared_ptr<B> sharedB;
};
class B{
public:
std::shared_ptr<A> sharedA;
};
int main() {
std::shared_ptr<A> sharedA = std::make_shared<A>();
std::shared_ptr<B> sharedB = std::make_shared<B>();
//2个堆内存 你指我 我指你 都在等对方相互释放 就内存泄漏了
shardA->shardB = shardB;
shardB->shardA = shardA;
}我们可以将其中一个改成weak_ptr来解决循环引用的问题
class B;
class A{
public:
std::weak_ptr<B> weakB;
};
class B{
public:
std::shared_ptr<A> sharedA;
};
int main() {
std::shared_ptr<A> sharedA = std::make_shared<A>();
std::shared_ptr<B> sharedB = std::make_shared<B>();
shardA->weakB = shardB;
shardB->shardA = shardA;
}作用原理:
weak_ptr的对象需要绑定到shared_ptr对象上,作用原理是weak_ptr不会改变shared_ptr对象的引用计数。
只要shared_ptr对象的引用计数为0,就会释放内存,weak_ptr的对象不会影响释放内存的过程。
weak_ptr使用较少,就是为了处理shared_ptr循环引用问题而设计的。
独享型智能指针 (unique_ptr)
- 同一块堆内存只能被一个
unique_ptr拥有。 - 在使用智能指针时,我们一般优先考虑独占式智能指针,因为消耗更小。
- 如果发现内存需要共享,那么再去使用
shared_ptr。
初始化
使用new运算符进行初始化
std::unique_ptr<int> uniqueI(new int(100));使用make_unique函数进行初始化
std::unique_ptr<int> uniqueI = std::make_unique<int>(100);常用操作
unque_ptr禁止复制构造函数,也禁止赋值运算符的重载。否则独占便毫无意义。unqiue_ptr允许移动构造,移动赋值。移动语义代表之前的对象已经失去了意义,移动操作自然不影响独占的特性。
std::unique_ptr<int> uniqueI = std::make_unique<int>(100);
std::unique_ptr<int> uniqueI2(std::move(uniqueI));std::unique_ptr<int> uniqueI = std::make_unique<int>(100);
std::unique_ptr<int> uniqueI2 = std::make_unique<int>(200);
uniqueI2 = std::move(uniqueI);- 当
unique_ptr的对象为一个右值时,就可以将该对象转化为shared_ptr的对象。
void myFunc(std::unique_ptr<int> uniquePtr) {
//但是要保证uniquePtr不能再单独使用了
std::shared_ptr<int> sharedI(std::move(uniquePtr));
}这个使用的并不多,需要将独占式指针转化为共享式指针常常是因为先前设计失误。注意:shared_ptr对象无法转化为unique_ptr对象。
常用函数
reset函数
- 不带参数的情况下:释放智能指针的对象,并将智能指针置空。
- 带参数的情况下:释放智能指针的对象,并将智能指针指向新的对象。
使用范围
能使用智能指针就尽量使用智能指针,那么哪些情况属于不能使用智能指针的情况呢?
有些函数必须使用C语言的指针,这些函数又没有替代,这种情况下,才使用普通的指针。
必须使用C语言指针的情况包括:
- 网络传输函数,比如windows下的send,recv函数,只能使用c语言指针,无法替代.
- c语言的文件操作部分。这方面
C++已经有了替代品,C++的文件操作完全支持智能指针,所以在做大型项目时,推荐使用C++的文件操作功能(IO库详细讲解)。
除了以上两种情况,剩下的均推荐使用智能指针。
我们应该使用哪个智能指针呢?
- 优先使用
unique_ptr,内存需要共享时再使用shared_ptr。 - 当使用
shared_ptr时,如果出现了循环引用的情况,再去考虑使用weak_ptr。
