指针
Note
that the *
sign can be confusing here, as it does two different things in our code:
- When used in declaration (
string* ptr
), it creates a pointer variable. - When not used in declaration, it act as a dereference operator.
string food = "Pizza";
string* ptr = &food;
// Output the value of food (Pizza)
cout << food << "\n";
// Output the memory address of food (0x6dfed4)
cout << &food << "\n";
// Access the memory address of food and output its value (Pizza)
cout << *ptr << "\n";
// Change the value of the pointer
*ptr = "Hamburger";
// Output the new value of the pointer (Hamburger)
cout << *ptr << "\n";
// Output the new value of the food variable (Hamburger)
cout << food << "\n";
智能指针
将原生指针封装成对象,来解决堆上内存泄漏的问题。
为什么要使用智能指针:
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
当栈对象生存周期结束,智能指针会调用析构函数释放之前申请的内存,从而避免内存泄漏。
智能指针是类,一定要有operator*
和operator->();
*
传回所指的对象,->
调用对象的成员。
- shared_ptr:拥有共享对象所有权语义的智能指针;
- weak_ptr:到 shared_ptr 所管理对象的弱引用;
- unique_ptr:拥有独有对象所有权语义的智能指针。
C++11常用的智能指针有shared_ptr,它采用计数的方法,记录当前内存被几个智能指针引用。计数内存在堆上分配。 当新增一个引用时,计数+1;失去一个引用,计数-1.当引用为0,智能指针自动释放申请的内存资源。
初始化shared_ptr可以通过make_shared函数,或者,通过构造函数传入普通指针,并通过get获得指针。
智能指针有内存泄露的情况
当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
例如:parent有一个shared_ptr类型的成员指向孩子,而child也有一个shared_ptr类型的成员指向父亲。然后在创建孩子和父亲对象时也使用了智能指针c和p,随后将c和p分别又赋值给child的智能指针成员parent和parent的智能指针成员child。从而形成了一个循环引用。
为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。
shared_ptr多个指针指向相同的对象,也叫共享指针。shared_ptr采用了引用计数的方式,更好地解决了赋值与拷贝的问题,每一个shared_ptr的拷贝都指向相同的内存,每拷贝一次内部的引用计数加1,每析构一次内部的引用计数减1,为0时自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取时需要加锁。
成员函数:
(1) get: 获得内部对象的指针;
(2) swap:交换所管理的对象;
(3) reset:替换所管理的对象;
(4) use_count:返回shared_ptr所指对象的引用计数;
(5) operator*和operator->:解引用存储的对象指针;
(6) operator=:对shared_ptr赋值;
(7) operator bool:检查是否有关联的管理对象;
(8) owner_before:提供基于拥有者的共享指针排序。
交换: std::swap(std::shared_ptr) 特化的swap算法用于交换两个智能指针。
初始化:通过构造函数传入指针初始化,也可以使用std::make_shared 或 std::allocate_shared 函数初始化。
注意事项:
(1) 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。不能使用类似这样的形式 shared_ptr<int> p = new int
;
(2) 避免循环引用,这是shared_ptr的一个最大陷阱,导致内存泄漏,这一点在weak_ptr中将得到完善;
(3) 管理数组指针时,需要制定Deleter以使用delete[]操作符销毁内存,shared_ptr并没有针对数组的特化版本;
(4) 不能把一个原生指针交给两个智能指针对象管理,对其它智能指针也是如此。
当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该考虑
- 当函数的参数为对象时,实参传递给形参的实际上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;
- 当函数的返回值为一个对象时,该对象实际上是函数内对象的一个拷贝,用于返回函数调用处。
- 浅拷贝带来内存泄漏的问题,本质在于析构函数释放多次堆内存,使用std::shared_ptr,可以完美解决这个问题。
简述 C++ 中智能指针的特点
shared_ptr, 关于shared_ptr使用需要记住什么?
- 尽量避免使用raw pointer构建shared_ptr,至于原因此处不便于多讲,后续还有讲解
- shared_ptr使得依据共享生命周期而经行地资源管理进行垃圾回收更为方便
- shared_ptr对象的大小通常是unique_ptr的两倍,这个差异是由于Control Block导致的,并且shared_ptr的引用计数的操作是原子的,这里的分析也会在后续看到
- 默认的资源销毁是采用delete,但是shared_ptr也支持用户提供deleter,与unique_ptr不同,不同类型的deleter对shared_ptr的类型没有影响。
C++程序设计中使用堆内存是非常频繁的操作。C++11中引入了智能指针
的概念,方便管理堆内存
。 使用普通指针
,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。 从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。 智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。 另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。 智能指针还有一个作用是把值语义转换成引用语义
C++ 中智能指针和指针的区别是什么?
智能指针是指在C++中,用于管理堆内存资源的特殊类型的指针。智能指针与普通指针的区别在于,智能指针会自动管理内存资源的分配和释放,以避免内存泄漏和空指针异常。
普通指针是指在C++中,指向内存中的某个地址的变量。普通指针可以指向任意类型的内存资源,并通过解引用运算符*访问指针指向的内存资源。但是,普通指针不会自动管理内存资源,程序员需要自己手动分配和释放内存,否则可能会导致内存泄漏和空指针异常。
例如,下面是一个使用普通指针的示例程序:
#include <iostream>
int main() {
int* p = new int(5);
std::cout << *p << std::endl;
delete p;
return 0;
}
在这个程序中,我们定义了一个指针p
,并使用动态内存分配运算符new
为它分配了一个新的内存空间,并将值5
赋值给它。然后我们通过解引用运算符*
访问指针指向的内存空间,并将它的值输出到标准输出流。最后,我们使用delete
运算符释放指针所指向的内存空间,避免内存泄漏。
这个程序的运行结果是输出数字5
,然后正常退出。但是,如果我们没有正确释放指针所指向的内存空间,就会导致内存泄漏,从而影响程序的性能和稳定性。
为了避免这种情况的发生,我们可以使用智能指针。智能指针是指在C++中,用于管理堆内存资源的特殊类型的指针。智能指针会自动管理内存资源的分配和释放,以避免内存泄漏和空指针异常。
例如,下面是一个使用智能指针的示例程序:
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> p(new int(5));
std::cout << *p << std::endl;
return 0;
}
在这个程序中,我们使用了C++标准库中的std::unique_ptr智能指针类型。我们使用这个类型的构造函数,创建了一个新的智能指针,并为它分配了一个新的内存空间,并将值5赋值给它。然后我们通过解引用运算符*访问指针指向的内存空间,并将它的值输出到标准输出流。
在这个程序中,我们没有手动释放指针所指向的内存空间,但是由于我们使用了智能指针,它会在智能指针被销毁时,自动释放指针所指向的内存空间,因此,我们不必担心内存泄漏的问题。
总之,智能指针与普通指针的区别在于,智能指针会自动管理内存资源的分配和释放,以避免内存泄漏和空指针异常。智能指针可以方便程序员管理堆内存资源,提高程序的性能和稳定性。
指针和引用的区别是什么?
本质:指针是一个变量,存储内容是一个地址,指向内存的一个存储单元。而引用是原变量的一个别名,实质上和原变量是一个东西,是某块内存的别名。
指针的值可以为空,且非const指针可以被重新赋值以指向另一个不同的对象。而引用的值不能为空,并且引用在定义的时候必须初始化,一旦初始化,就和原变量“绑定”,不能更改这个绑定关系。
- 引用必须被初始化,指针不必。
- 引用初始化以后不能被改变,指针可以改变所指的对象。
- 不存在指向空值的引用,但是存在指向空值的指针。
简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
修改内容上的差别
cppchar a[] = "hello"; a[0] = 'X'; char *p = "world"; // 注意p 指向常量字符串 p[0] = 'X'; // 编译器不能发现该错误,运行时错误
用运算符
sizeof
可以计算出数组的容量(字节数)。sizeof(p)
p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。cppchar a[] = "hello world"; char *p = a; cout << sizeof(a) << endl; // 12 字节 cout << sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
cppvoid Func(char a[100]) { cout << sizeof(a) << endl; // 4 字节而不是100 字节 }
指针引起的崩溃 常见原因
- 指针未判空
- 野指针
- 其他地方改写了指针