一、概念
堆内存对象需要手动使用delete销毁,如果没有使用delete销毁就会造成内存泄漏。
所以C++在ISO98标准中引入了智能指针的概念,并在ISO11中趋于完善。
使用智能指针可以让堆内存对象具有栈内存对象的特点,原理是给需要手动回收的内内存对象套上一个栈内存的模板类对象的即可。
使用智能指针需要引入头文件#include<memory>
C++有四种智能指针
● auto_ptr(自动指针)(C++ISO98 ,已废弃)
● unique_ptr(唯一指针)(C++ISO11)
● shared_ptr(共享指针)(C++ISO11)
● weak_ptr(虚指针)(C++ISO11)
二、auto_ptr
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
string s;
public:
Test(string s):s(s)
{
cout<<s<<"构造函数"<<endl;
}
~Test()
{
cout<<s<<"析构函数"<<endl;
}
void show()
{
cout<<s<<"对象执行"<<endl;
}
};
int main()
{
{
//创建一个Test对象,t1指针指向对象
Test *t1=new Test("A");
//将对象t1交给ap1管理
auto_ptr<Test>ap1(t1);
//访问对象的成员函数
ap1.get()->show(); //ap1.get() <=等价=> t1
//释放智能指针ap1对对象t1的管理权,但不销毁对象
// ap1.release();
//释放智能指针ap1对对象t1的管理权,且销毁对象
// ap1.reset();
//创建新的对象t2,销毁原t1对象,并将ap1管理的对象换为t2
Test *t2=new Test("B");
ap1.reset(t2);
// //以上两句代码可简写为
// ap1.reset(new Test("B"));
ap1.get()->show();
cout<<"局部代码块执行结束"<<endl;
}
cout<<"程序运行结束"<<endl;
return 0;
}
/*
A构造函数
A对象执行
B构造函数
A析构函数
B对象执行
局部代码块执行结束
B析构函数
程序运行结束
*/
由于成员变量存在指针类型,其拷贝构造函数和赋值运算符重载函数的使用会出现问题,与浅拷贝不同,auto_ptr的复制语义(拷贝构造函数、赋值运算符重载函数)会导致智能指针对对象控制权的转移(非程序员本意),这也是他被废弃的原因
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
string s;
public:
Test(string s):s(s)
{
cout<<s<<"构造函数"<<endl;
}
~Test()
{
cout<<s<<"析构函数"<<endl;
}
void show()
{
cout<<s<<"对象执行"<<endl;
}
};
int main()
{
{
//创建一个Test对象,t1指针指向对象
Test *t1=new Test("A");
//将对象t1交给ap1管理
auto_ptr<Test>ap1(t1);
cout<<ap1.get()<<endl;
//显示调用拷贝构造函数
auto_ptr<Test>ap2(ap1);//对t1的管理权从ap1到了ap2
cout<<ap1.get()<<" "<<ap2.get()<<endl;
//隐式调用拷贝构造函数
auto_ptr<Test>ap3=ap2;//对t1的管理权从ap2到了ap3
cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<endl;
//使用赋值运算符
auto_ptr<Test>ap4;
ap4=ap3; //对t1的管理权从ap3到了ap4
cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<" "<<ap4.get()<<endl;
cout<<"局部代码块执行结束"<<endl;
}
cout<<"程序运行结束"<<endl;
return 0;
}
/*
A构造函数
0x1162670
0 0x1162670
0 0 0x1162670
0 0 0 0x1162670
局部代码块执行结束
A析构函数
程序运行结束
*/
三、unique_ptr
作为auto_ptr的改进,unique_ptr对其他持有的资源对象具有唯一控制权,即不可以通过常规的复制语法转移或者拷贝资源对象的控制权。
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
string s;
public:
Test(string s):s(s)
{
cout<<s<<"构造函数"<<endl;
}
~Test()
{
cout<<s<<"析构函数"<<endl;
}
void show()
{
cout<<s<<"对象执行"<<endl;
}
};
int main()
{
{
//创建一个Test对象,t1指针指向对象
Test *t1=new Test("A");
//将对象t1交给up1管理
unique_ptr<Test>up1(t1);
cout<<up1.get()<<endl;
//显示调用拷贝构造函数
unique_ptr<Test>up2(move(up1));
cout<<up1.get()<<" "<<up2.get()<<endl;
//隐式调用拷贝构造函数
unique_ptr<Test>up3=move(up2);
cout<<up1.get()<<" "<<up2.get()<<" "<<up3.get()<<endl;
//使用赋值运算符
unique_ptr<Test>up4;
up4=move(up3);
cout<<up1.get()<<" "<<up2.get()<<" "<<up3.get()<<" "<<up4.get()<<endl;
cout<<"局部代码块执行结束"<<endl;
}
cout<<"程序运行结束"<<endl;
return 0;
}
/*
A构造函数
0xed2670
0 0xed2670
0 0 0xed2670
0 0 0 0xed2670
局部代码块执行结束
A析构函数
程序运行结束
*/
四、shared_ptr
每次多一个shared_ptr对资源进行管理,引用计数将+1,每个指向该对象的shared_ptr对象销毁时,引用计数-1,最后一个shared_ptr对象销毁时,计数清零,资源对象销毁。(即对于所有管理某对象A的 shared_ptr,这些 shared_ptr共享的资源有:引用计数、对象资源)
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
string s;
public:
Test(string s):s(s)
{
cout<<s<<"构造函数"<<endl;
}
~Test()
{
cout<<s<<"析构函数"<<endl;
}
void show()
{
cout<<s<<"对象执行"<<endl;
}
};
int main()
{
shared_ptr<Test>sp3;
{
shared_ptr<Test>sp1=make_shared<Test>("A");
cout<<"引用计数:"<<sp1.use_count()<<endl;
cout<<"-------------"<<endl;
shared_ptr<Test>sp2(sp1);
cout<<"引用计数:"<<sp2.use_count()<<endl;
cout<<"引用计数:"<<sp1.use_count()<<endl;
cout<<"-------------"<<endl;
sp3=sp2;
cout<<"引用计数:"<<sp3.use_count()<<endl;
cout<<"局部代码块执行结束"<<endl;
}
//此时sp1、sp2都已销毁,所以引用计数减2
cout<<"引用计数:"<<sp3.use_count()<<endl;
cout<<"程序运行结束"<<endl;
return 0;
}
/*
A构造函数
引用计数:1
-------------
引用计数:2
引用计数:2
-------------
引用计数:3
局部代码块执行结束
引用计数:1
程序运行结束
A析构函数
*/
五、weak_ptr
weak_ptr是一个不控制资源对象的智能指针,不会影响资源的引用计数,其主要的目的是协助shared_ptr工作。通过weak_ptr的构造函数,参数传入一个持有资源对象的shared_ptr或者weak_ptr对象即可创建。weak_ptr与资源对象呈弱相关性,因此不支持get等函数直接操作资源对象.
建议weak_ptr调用lock函数之前,先检测引用计数是否大于0,或使用expired()函数检测是否可转换为shared_ptr.
- 因为不会增加引用计数,所以作为观察者,观察对象是否被释放
- 若shared_ptr失效,weak_ptr可以通过lock函数“转正”
- shared_ptr在特殊场景下也会造成内存泄漏(循环引用导致智能指针内存泄漏的问题),可以使用weak_ptr来解决
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
string s;
public:
Test(string s):s(s)
{
cout<<s<<"构造函数"<<endl;
}
~Test()
{
cout<<s<<"析构函数"<<endl;
}
void show()
{
cout<<s<<"对象执行"<<endl;
}
};
int main()
{
weak_ptr<Test>wp3;
{
shared_ptr<Test>sp1=make_shared<Test>("A");
weak_ptr<Test>wp1=sp1;
weak_ptr<Test>wp2(wp1);
cout<<sp1.use_count()<<endl;
cout<<wp1.use_count()<<endl;
cout<<wp2.use_count()<<endl;
cout<<"-----------"<<endl;
//weak_ptr可以通过lock函数“转正”
shared_ptr<Test>sp2=wp1.lock();
wp3=wp2;
cout<<wp3.use_count()<<endl;
cout<<"局部代码块执行结束"<<endl;
}
//判断能否转正
cout<<wp3.use_count()<<endl;
if(wp3.expired())
{
cout<<"weak_ptr无法转正"<<endl;
}
cout<<"程序运行结束"<<endl;
return 0;
}
/*
A构造函数
1
1
1
-----------
2
局部代码块执行结束
A析构函数
0
weak_ptr无法转正
程序运行结束
*/
六、其他
1.nullptr
在C++11中使用nullptr代替NULL,作为空指针的表示方式,在C++中,可以用作空指针常量,表示指针不指向任何有效的内存地址。
2.类型推导
2.1 auto
使用auto关键字可以推导类型,C++11引入的。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
auto i1=10;//typeof(i1)-->整型
auto i2=1.5;//typeof(i2)-->浮点型
auto i3=new auto(10); //typeof(i3)-->int *
//int* i3=new int(10);
cout<<*i3<<" "<<i3<<endl;
auto i4='a';//typeof(i4)-->char
return 0;
}
2.2 decltypt
decltypt可以推导表达式的类型,需要注意,decltypt只会分析表达式的类型,不会具体计算表达式的值。
3.初始化列表
#include <iostream>
#include <array>
#include <vector>
using namespace std;
class Student
{
private:
string name;
int age;
public:
Student(string name,int age):name(name),age(age)
{
}
void show()
{
cout << name << " " << age << endl;
}
};
int main()
{
Student s = {"张胜男",18}; // 列表初始化
s.show();
array<int,5>arr1 = {1,3,4,5,6};
vector<int> vec1 ={1,2,3};
int arr2[] = {2,3,45,6};
int a{};
cout << a << endl; // 0
return 0;
}
4.面试题
【面试题】:C++11学过那些特性?
- auto(类型推导)
- 智能指针(unique_ptr、 shared_ptr、weak_ptr)
- 初始化列表
- for-each
- 类型转换函数
- 继承构造
- array
- override
5. 进制输出
#include <iostream>
using namespace std;
int main()
{
// 为了区分不同进制,可以增加进制显式的功能,此功能设定持久
cout << showbase;
cout << dec << 1234 << endl; // 1234
cout << oct << 1234 << endl; // 02322
// 输出进制是持久的
cout << 9 << endl; // 011
cout << hex << 256 << endl; // 0x100
// 取消进制显式的功能
cout << noshowbase;
cout << 16 << endl; // 10
return 0;
}
6. 设定输出域宽度
可以使用setw()来制定一个整数或者一个字符串输出占用的与宽度。● 当设定域宽度小于数据本身时,仍然会显示为数据本身的宽度。
● 当设定域宽度大于数据本身时,会显式未设定的宽度。
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
// 仍然会按照实际的宽度输出
cout << setw(5) << 123456 << setw(5) << 123456 << endl;
cout << setw(10) << 123456 << setw(10) << 123456 << endl;
// 域宽度 只能作用于下一行
cout << setw(10);
cout << 123456 << endl;
cout << 123456 << endl;
return 0;
}
拓展学习:
C++如何实现多线程、多进程。(案例:线程池)
文件处理。(案例:日志系统)
客户端、服务器、高并发
Lambda表达式