C++11特性及其它常用特性

C++11特性及其它常用特性

1. explicit 关键字

  • explicit /ɪkˈsplɪsɪt/ 明确的;清楚的;直率的;详述的

  • 作用是表明该构造函数是显示的, 而非隐式的.不能进行隐式转换! 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).

  • 例如:

    Student xiaoM(18); //显示构造

    Student xiaoW = 18; //隐式构造 有可能会照成歧义,以为是赋值操作代码:

  • 代码

#include <iostream>
#include<string>
using namespace std;

class Student
{
public:
	Student(const int age)
	{
		this->age = age;
	}
	//加上explicit后下方构造函数就不可以发生隐式类型转换了
	explicit Student(const string name)
	{
		this->name = name;
	}

private:
	int age;
	string name;
};
int main(void) {

	Student stu1(12);//显示类型
	Student stu2 = 22;//隐式类型

	//加了explicit的构造函数
	Student stu3("李华");
	//Student stu4 = "小明"; 不可以发生隐式类型

	system("pause");
	return 0;
}

2. 左值和右值的概念

存储的层次结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0JqY5jn-1665126094706)(D:\冲击offer\博客\C++基础\assets\wps1-16649534308481.jpg)]

  • 按字面意思,通俗地说。以赋值符号 = 为界,= 左边的就是左值(lvalue),= 右边就是右值(rvalue)。
int   a  =  666;		//左值   右值

int   b  =  888;		//左值   右值

int   c  =  a   +  b;	//左值   右值   右值

左值 - 代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。
右值 - 通过排他性来定义,每个表达式不是左值就是右值。

左值的定义,右值是不在内存中占有确定位置的表达式,而是存在cpu的寄存器中。

所有的左值(无论是数组,函数或不完全类型)都可以转换成右值

3. 函数返回值当引用

C++引用使用时的难点:

  1. 当函数返回值为引用时

    若返回局部变量,不能成为其它引用的初始值,不能作为左值使用

  2. 若返回静态变量或全局变量

    可以成为其他引用的初始值 即可作为右值使用,也可作为左值使用

  3. 返回形参当引用

​ (注:C++链式编程中,经常用到引用,运算符重载专题)

  • 测试代码
#include <iostream>
#include <stdlib.h>
#include <stdio.h>

using namespace std;


int demo1() {
	int i = 0;
	//printf("i 的地址: %p, i=%d\n", &i, i);

	return i;
}

int &demo(int **addr) {
	int i = 666;
	*addr = &i;
	printf("i 的地址: %p, i=%d\n", &i, i);

	return i;
}

int &demo_static(int **addr) {
	static int i = 666;
	*addr = &i;
	printf("demo_static: i 的地址: %p, i=%d\n", &i, i);

	return i;
}

//3. 函数返回形参(普通参数)当引用
int &demo3(int var) {
	var = 666;
	return var;
}

//4. 函数返回形参(引用)当引用
int &demo4(int &var) {
	var = 666;
	return var;
}

int main(void) {
	int *addr = NULL;
	int ret = demo(&addr);

	//第一种情况  函数返回局部变量引用不能成为其它引用的初始值
	//int &i1 = demo(&addr);  
	//i1 = 888;
	//printf("addr: %p i1=%d\n", addr, i1);

	//demo(&addr);
	//demo1();
	//printf("addr: %p i1=%d\n", addr, i1);

	//第二种情况 函数返回局部变量不能做为左值
	/*demo(&addr) = 888;
	printf("1. addr: %p  value: %d\n", addr, *addr);
	demo1();
	printf("2. addr: %p  value: %d\n", addr, *addr);
	*/

	//第三种情况  返回静态变量或全局变量可以成为左值或是其它引用的初始值
	//demo_static(&addr) = 888;
	int &i1 = demo_static(&addr);
	i1 = 888;
	printf("1. addr: %p  value: %d\n", addr, *addr);
	demo1();
	printf("2. addr: %p  value: %d\n", addr, *addr);
	demo_static(&addr);
	  
	system("pause");
	return 0;
}

4. C++11 新增容器 - array

array容器概念

  • array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。

  • array是将元素置于一个固定数组中加以管理的容器。

  • array可以随机存取元素,支持索引值直接存取 , 用[]操作符或at()方法对元素进行操作,也可以使用迭代器访问

  • array可以完全替代C语言中的数组,使操作数组元素更加安全!

  • #include

array特点

array 容器的大小是固定的,无法像vector那样动态的扩展或收缩,这也就意味着,在使用该容器的过程无法增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。

STL 还提供有可动态扩展或收缩存储空间的 vector 容器

array对象的构造

  • array采用模板类实现,array对象的默认构造形式

  • array<T,int> arrayT; //T为存储的类型, 为数值型模板参数

//构造函数

array<int, 5> a1;   //一个存放5个int的array容器

array<float, 6> a2;  //一个存放6个float的array容器

array<student, 7> a3; //一个存放7个student的array容器

array的赋值

a1.fill(666); 					//玩法一 用特定值填充array中所有元素

array<int, 4> test={1, 2, 3, 4};// 玩法二 定义时使用初始化列表

array<int, 4> test;
test={1,2,3,4};  				//玩法三 定义后使用列表重新赋值

array<int, 4> a1,a2;
a1={1,2,3,4};					//玩法四

a2 = a1;						//玩法五,赋值运算需要类型和数量都一样

a1.swap(a2); 					//玩法六  和其它array进行交换 

array的大小

array.size();	  //返回容器中元素的个数

array.max_size();  //返回容器中最大元素的个数,同size()。

array.empty();	  //判断容器是否为空

array的数据存取

第一 使用下标操作 a1[0] = 100;

第二 使用at 方法 如: a1.at(2) = 100;

第三 接口返回的引用 a1.front() 和 a1.back()

注意: 第一和第二种方式必须注意越界

array 迭代器访问

array.begin();     //返回容器中第一个数据的迭代器。

array.end();       //返回容器中最后一个数据之后的迭代器。

array.rbegin();    //返回容器中倒数第一个元素的迭代器。

array.rend();      //返回容器中倒数最后一个元素的后面的迭代器。

array.cbegin();    //返回容器中第一个数据的常量迭代器。

array.cend();      //返回容器中最后一个数据之后的常量迭代器。

array.crbegin();   //返回容器中倒数第一个元素的常量迭代器。

array.crend();     //返回容器中倒数最后一个元素的后面的常量迭代器。

5. C++的类型转换

概述:

旧式转型C风格的强制类型:

TYPE b = (TYPE) a

例如:

int i = 48;

char c = (char) i;

新式转型C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。

格式:

TYPE b = 类型操作符 ( a )

类型操作符 = static_cast | reinterpreter_cast | dynamic_cast | const_cast

(1)static_cast

静态类型转换(斯文的劝导,温柔的转换)。如int转换成char

主要用法:

  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。上行指针或引用(子类到父类)转换安全,下行不安全

  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

  • 把空指针转换成目标类型的空指针。

  • 把任何类型的表达式转换成void类型。

测试代码:

#include <iostream>
using namespace std;
class Father
{
    virtual void play() = 0;
};
class Son : public Father
{
public:
	void play()
	{
		cout << "我要打游戏" << endl;
	}
};
int main(void) {
	//1,父子之间转换
	Father * f1 = static_cast<Father *>(new Son());//安全
	Son * s = static_cast<Son *>(new Father());//不安全

	//2,基本数据类型之间的转换
	int num = static_cast<int>('a');

	//3,把空指针转换成目标类型的空指针
	int * p = static_cast<int *>(NULL);

	//4,把任何类型的表达式转换成void类型。
	int * arr = new int[10];
	void * varr = static_cast<void *>(arr);

	system("pause");
	return 0;
}

很多情况下,static_cast可以转换的基本上不加static_cast也可以转换,但是加上后会让别的程序员清晰的知道这是强制类型转换。但是往往会省略不写。

(2)reinterpret_cast

  • 重新解释类型(挂羊头,卖狗肉) 不同类型间的互转,数值与指针间的互转

  • 用法: TYPE b = reinterpret_cast ( a )

  • TYPE必须是一个指针、引用、算术类型、函数指针.

忠告:滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。

#include <iostream>

using namespace std;

class Animal {
public:
	void cry() {
		cout << "动物叫" << endl;
	}
};

class Cat :public Animal
{
public:
	void cry()
	{
		cout << "喵喵瞄" << endl;
	}

};

class Dog :public Animal
{
public:
	void cry()
	{
		cout << "汪汪汪" << endl;
	}

};

int main02(void) {

	//用法一   数值与指针之间的转换
	int* p = reinterpret_cast<int*>(0x99999);
	int val = reinterpret_cast<int>(p);

	//用法二  不同类型指针和引用之间的转换
	Dog  dog1;
	Animal* a1 = &dog1;		//父类指针指向子类对象

	Dog* dog1_p = reinterpret_cast<Dog*>(a1);
	Dog* dog2_p = static_cast<Dog*>(a1);   //如果能用static_cast ,static_cast 优先

	Cat* cat2_p = reinterpret_cast<Cat*>(dog1_p);//滥用:狗强转为猫


	system("pause");
	return 0;
}

(3)dynamic_cast

  • 动态类型转换:在程序运行的时候,可以判断对象是什么类别

  • 将一个基类对象指针cast到继承类指针,dynamic_cast 会根据基类指针是否真正指向继承类指针来做相应处理。失败返回null,成功返回正常cast后的对象指针;

  • 将一个基类对象引用cast 继承类对象,dynamic_cast 会根据基类对象是否真正属于继承类来做相应处理。失败抛出异常bad_cast

注意:dynamic_cast在将父类cast到子类时,父类必须要有虚函数一起玩。

#include <iostream>

using namespace std;

class Animal {
public:
	virtual void cry() = 0;
};

class Cat :public Animal
{
public:
	void cry()
	{
		cout << "喵喵瞄" << endl;
	}

	void play()
	{
		cout << "爬爬树"<<endl;
	}

};

class Dog :public Animal
{
public:
	void cry()
	{
		cout << "汪汪汪" << endl;
	}

	void play()
	{
		cout << "溜达溜达" << endl;
	}

};


void animalPlay(Animal* animal) {
	animal->cry();
    
	//父类指针如果指向的是Dog对象,那就转换成功,否则转换失败 pDoy = NULL;
	Dog* pDog = dynamic_cast<Dog*>(animal);
	if (pDog) {
		pDog->play();
	}
	else {//pDog == NULL
		cout << "不是狗,别骗我!" << endl;
	}


	Cat* pCat = dynamic_cast<Cat*>(animal);
	if (pCat) {
		pCat->play();
	}
	else {//pDog == NULL
		cout << "不是猫,别骗我!" << endl;
	}
}


int main(void) {

	Dog* dog1 = new Dog();
	Animal* a1 = dog1;

	animalPlay(a1);

	Cat* cat1 = new Cat();
	Animal* a2 = cat1;

	system("pause");
	return 0;
}

(4)const_cast

去const属性。(仅针对于指针和引用)

#include <iostream>

using namespace std;

void demo(const char* p)
{
	//对指针去掉cost 重新赋值
	//char* p1 = const_cast<char *>(p);
	//p1[0] = 'A';

	//直接去掉const修改
	const_cast<char*>(p)[0] = 'A';

	
	cout << p << endl;

}

void demo(const int p)
{
	//const_cast<int>(p) = 888;// NO ! 不能对非指针和引用进行const 转换
}
int main(void)
{
	//字符串数组
	char p[] = "12345678";
	demo(p);  //合情合理

	//常量字符串不能去掉const 修改
	//警告: 在去掉常量限定符之前,保证指针所指向的内存能够修改,不能修改则会引起异常。
	const char* cp = "987654321";//放在常量区
	demo(cp);

	system("pause");
	return 0;
}

(5)类型转换使用建议

​ 1)static_cast<>()静态类型转换,编译的时c++编译器会做编译时的类型检查;隐式转换;基本类型转换,父子类之间合理转换

​ 2)reinterpret_cast<>()若不同类型之间,进行强制类型转换,用reinterpret_cast<>() 进行重新解释

建 议:

C语言中 能隐式类型转换的,在c++中可用 static_cast<>()进行类型转换。因C++编译器在编译检查一般都能通过;C语言中不能隐式类型转换的,在c++中可以用 reinterpret_cast<>() 进行强制类型解释。

总结:static_cast<>()和reinterpret_cast<>() 基本上把C语言中的 强制类型转换给覆盖,注意reinterpret_cast<>()很难保证移植性。

​ 3)dynamic_cast<>(),动态类型转换,安全的虚基类和子类之间转换;运行时类型检查

​ 4)const_cast<>(),去除变量的只读属性

  • 最后的忠告:程序员必须清楚的知道: 要转的变量,类型转换前是什么类型,类型转换后是什么类型,转换后有什么后果。

  • C++大牛建议:一般情况下,不建议进行类型转换;避免进行类型转换。

4. 智能指针

(1)auto_ptr 使用详解 (C++98)

  • auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!

用法:

  • 头文件: #include

  • 用 法: auto_ptr<类型> 变量名(new 类型)

例 如:

 auto_ptr<string> str(new string("我要成为大牛~ 变得很牛逼!"));

 auto_ptr<vector<int>> av(new vector<int>(10));

示例代码:

#include <iostream>
#include <memory>
using namespace std;
class Student
{
public:
	Student()
	{
		cout << "构造函数调用" << endl;
		age = 20;
	}
	~Student()
	{
		cout << "析构函数调用" << endl;
	}
	int getValue()
	{
		return age;
	}

private:
	int age;
};

void text()
{
	//创建智能指针和智能指针的使用
	auto_ptr<Student> s(new Student());
	cout << "sage = " << s->getValue() << endl;

	//智能指针的API
	if (0)
	{
	//返回创建动态分配内存的指针
		Student * ss = s.get();
		cout << "ssage = " << ss->getValue() << endl;

		//reset 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉,指向新开辟的内存
		s.reset(new Student());

		//release 取消指针指针对动态内存的托管,之前分配的内存必须手动释放
		Student *  tmp = s.release();
		delete tmp;
	}
}
int main(void) {

	text();
	system("pause");
	return 0;
}

注意事项:

//忠告1: 智能指针不要定义为全局变量,因为全局变量作用的范围是整个程序,
//所以只有在程序结束的时候,才会释放内存
auto_ptr< Test> t(new Test());  

//忠告2: 不要定义指向智能指针对象的指针变量
auto_ptr<Test>* tp = new auto_ptr<Test>(new Test()); 

//忠告3: 除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个智能指针
auto_ptr< Test> t(new Test());
auto_ptr< Test> t1;
t1 = t;

使用建议:

1.尽可能不要将auto_ptr 变量定义为全局变量或指针

2.除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个 智能指针

3.C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!

(2)unique_ptr 使用详解 (C++11)

auto_ptr是用于C++11之前的智能指针。由于 auto_ptr 基于排他所有权模式:两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。

  • 弊端 : 此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题。
auto_ptr<string> p1(new string("I reigned loney as a cloud."));
auto_ptr<string> p2;
p2=p1; //auto_ptr不会报错,

cout << *p2 << endl;
//cout << *p1 << endl;当再次访问p1的时候,会发生越界
  • unique_ptr聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1;                                      // 报错
  • 如果确实想执行类似与赋值的操作,C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。
unique_ptr<string> p1(new string("I 'm martin."));
unique_ptr<string> p2(new string("I 'm rock."));

//如果一定要转移,使用move 把左值转成右值
p1 = std::move(p2);

cout << *p2 << endl;
//cout << *p1 << endl;当再次访问p1的时候,会发生越界

所以,C++11用更严谨的unique_ptr 取代了auto_ptr!

unique_ptr 常用API

构造函数

unique_ptr<T> up ; 		//空的unique_ptr,可以指向类型为T的对象

unique_ptr<T> up1(new T()) ;	//定义unique_ptr,同时指向类型为T的对象

unique_ptr<T[]> up ; 	//空的unique_ptr,可以指向类型为T[的数组对象

unique_ptr<T[]> up1(new T[]) ;	//定义unique_ptr,同时指向类型为T的数组对象

unique_ptr<T,D> up(); 	//空的unique_ptr,接受一个D类型的删除器d,使用d释放内存

unique_ptr<T,D> up(new T()); //定义unique_ptr,同时指向类型为T的对象,接受一个D类型的删除器d,使用删除器d来释放内存赋值

unique_ptr<int> up1(new int(10));

unique_ptr<int> up2(new int(11));

up1 = std::move(up2);//必须使用移动语义,结果,up1 内存释放, up2 交由up1 管理

主动释放对象会释放内存

up = nullptr ;	   //释放up指向的对象,将up置为空

up = NULL; 	//作用相同 

放弃对象控制权

up.release();   //放弃对象的控制权,返回指针,将up置为空,不会释放内存

重置

up.reset(…) ; //参数可以为 空、内置指针,先将up所指对象释放,然后重置up的值

交换

up.swap(up1);  //将智能指针up 和up1管控的对象进行交换

测试代码:

#include <stdio.h>
#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

int main() {

	//弊端1. auto_ptr 被C++11 抛弃的主要理由 p1= p2 ,复制或赋值都会改变资源的所有权
	//unique_ptr 如何解决这个问题? 不允许显示的右值赋值和构造
	unique_ptr<string> p1(new string("I 'm martin."));
	unique_ptr<string> p2(new string("I 'm rock."));
	printf("p1: %p\n", p1.get());
	printf("p2: %p\n", p2.get());

	//如果一定要转移,使用move 把左值转成右值
	p1 = std::move(p2);
	printf("p1: %p\n", p1.get());
	printf("p2: %p\n", p2.get());

	//p1 = p2;  //左值赋值禁止
	unique_ptr<string> p3(new string("I 'm p3."));
	unique_ptr<string> p4(std::move(p3));  //左值拷贝构造也不行,必须转成右值

	//弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
	vector<unique_ptr<string>> vu;
	unique_ptr<string> p5(new string("I 'm p5."));
	unique_ptr<string> p6(new string("I 'm p6."));

	vu.push_back(std::move(p3));
	vu.push_back(std::move(p4));

	cout << "va[0]: " << *vu[0] << endl;
	cout << "va[1]: " << *vu[1] << endl;

	//vu[0] = vu[1];  //unique_ptr不支持直接赋值,没有风险

	//弊端3. auto_ptr不支持对象数组的内存管理,unique_ptr 支持
//但是unique_ptr 支持对象数组的管理
	//auto_ptr<int[]> ai(new int[5]);  //不能这样定义
	unique_ptr<int[]> ui(new int[5]);  //自动会调用 delete []函数去释放
	system("pause");
	return 0;
}

(3)shared_ptr 使用详解 (C++11)

  • 熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?

  • shared_ptr : 可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!

构造函数

shared_ptr<T> sp ; //空的shared_ptr,可以指向类型为T的对象

shared_ptr<T> sp1(new T()) ;//定义shared_ptr,同时指向类型为T的对象

shared_ptr<T[]> sp2 ; //空的shared_ptr,可以指向类型为T[的数组对象C++17后支持

shared_ptr<T[]> sp3(new T[]{...}) ;//指向类型为T的数组对象C++17后支持

shared_ptr<T> sp4(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D释放内存

shared_ptr<T> sp5(new T(), D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存

初始化

方式一 构造函数

shared_ptr<int> up1(new int(10));  //int(10) 的引用计数为1

shared_ptrr<int> up2(up1);  //使用智能指针up1构造up2, 此时int(10) 引用计数为2

方式二 使用make_shared 初始化对象,分配内存效率更高 (强烈推荐)

//make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr; 用法:

//make_shared<类型>(构造类型对象需要的参数列表); 

shared_ptr<int> p4 = make_shared<int>(2); //多个参数以逗号','隔开,最多接受十个

shared_ptr<string> p4 = make_shared<string>("字符串");

赋值

shared_ptr<int> up1(new int(10));  //int(10) 的引用计数为1

shared_ptr<int> up2(new int(11));  //int(11) 的引用计数为1

up1 = up2;//int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2

主动释放对象

shared_ptr<int> up1(new int(10));

up1 = nullptr ;//int(10) 的引用计数减1,计数归零内存释放
up1 = NULL; //作用同上

重置

up.reset() ;   //将p重置为空指针,所管理对象引用计数 减1

up.reset(p1);  //将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控

up.reset(p1,d); //将p重置为p(的值),p 管控的对象计数减1并使用d作为删除器

交换

std::swap(p1,p2); //交换p1 和p2 管理的对象,原对象的引用计数不变

p1.swap(p2);   	  //同上
使用陷阱

shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

#include<iostream>
using namespace std;
#include<memory>

class Gial;
class Boy
{
public:
	Boy()
	{
		cout << "构造函数的调用" << endl;
	}
	~Boy()
	{
		cout << "析构函数的调用" << endl;
	}
	void addGialFriead(shared_ptr<Gial> & gial)
	{
		this->gial = gial;
	}

private:
	shared_ptr<Gial> gial;
};

class Gial
{
public:
	Gial()
	{
		cout << "构造函数的调用" << endl;
	}
	~Gial()
	{
		cout << "析构函数的调用" << endl;
	}
	void addBoyFriead(shared_ptr<Boy> & boy)
	{
		this->boy = boy;
	}
private:
	shared_ptr<Boy> boy;
};

void text()
{
	shared_ptr<Boy> b(new Boy());
	shared_ptr<Gial> g(new Gial());
	b->addGialFriead(g);
	g->addBoyFriead(b);
}

int main()
{

	text();

	system("pause");
	return 0;
}

(4)weak_ptr 使用详解 (自从C++11)

  • weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作,
  • 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少
  • 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

使用weak_ptr解决上述代码问题:

#include<iostream>
using namespace std;
#include<memory>
class Gial;
class Boy
{
public:
	Boy()
	{
		cout << "构造函数的调用" << endl;
	}
	~Boy()
	{
		cout << "析构函数的调用" << endl;
	}
	void addGialFriead(shared_ptr<Gial> & gial)
	{
		this->gial = gial;
	}
private:
	weak_ptr<Gial> gial;
};

class Gial
{
public:
	Gial()
	{
		cout << "构造函数的调用" << endl;
	}
	~Gial()
	{
		cout << "析构函数的调用" << endl;
	}
	void addBoyFriead(shared_ptr<Boy> & boy)
	{
		this->boy = boy;
	}
private:
	weak_ptr<Boy> boy;
};

void text()
{
	shared_ptr<Boy> b(new Boy());
	shared_ptr<Gial> g(new Gial());
	b->addGialFriead(g);
	g->addBoyFriead(b);
}

int main()
{
	text();
	

	system("pause");
	return 0;
}

输出:

weak_ptr基本使用实例:

#include<iostream>
using namespace std;
#include<memory>

class Person
{
public:
	Person(int age) {
		this->age = age;
	}
	int getAge(){
		return age;
	}
private:
	int age;
};

int main()
{
	shared_ptr<Person> p(new Person(12));
	cout << p.use_count() << endl;//1

	//弱指针的使用
	weak_ptr<Person> wp = p;
	//wp->getAge();弱指针不能调用成员函数
	cout << p.use_count() << endl;//1 弱指针不影响引用计数

	//弱指针转化为共享指针,就可以正常使用
	shared_ptr<Person> pp = wp.lock();
	cout << p.use_count() << endl;//2
	cout << pp->getAge() << endl;


	system("pause");
	return 0;
}

(5)智能指针的使用陷阱

  1. 不要把一个原生指针给多个智能指针管理
int *x = new int(10);
unique_ptr<int> up1(x);
unique_ptr<int> up2(x);
//警告! 以上代码使up1 up2指向同一个内存,非常危险或以下形式:up1.reset(x);up2.reset(x);
  1. 记得使用u.release()的返回值

在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了

  1. 禁止delete 智能指针get 函数返回的指针

如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!

  1. 禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
shared_ptr<int> sp1(new int(10));

//一个典型的错误用法 shared_ptr<int> sp4(sp1.get());

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值