C++学习笔记(12)

194、智能指针 shared_ptr

shared_ptr 共享它指向的对象,多个 shared_ptr 可以指向(关联)相同的对象,在内部采用计数机

制来实现。

当新的 shared_ptr 与对象关联时,引用计数增加 1。

当 shared_ptr 超出作用域时,引用计数减 1。当引用计数变为 0 时,则表示没有任何 shared_ptr

与对象关联,则释放该对象。

一、基本用法

shared_ptr 的构造函数也是 explicit,但是,没有删除拷贝构造函数和赋值函数。

1)初始化

方法一:

shared_ptr<AA> p0(new AA("西施")); // 分配内存并初始化。

方法二:

shared_ptr<AA> p0 = make_shared<AA>("西施"); // C++11 标准,效率更高。

shared_ptr<int> pp1=make_shared<int>(); // 数据类型为 int。

shared_ptr<AA> pp2 = make_shared<AA>(); // 数据类型为 AA,默认构造函数。

shared_ptr<AA> pp3 = make_shared<AA>("西施"); // 数据类型为 AA,一个参数的构造函数。

shared_ptr<AA> pp4 = make_shared<AA>("西施",8); // 数据类型为 AA,两个参数的构造函数。

方法三:

AA* p = new AA("西施");

shared_ptr<AA> p0(p); // 用已存在的地址初始化。

方法四:

shared_ptr<AA> p0(new AA("西施"));

shared_ptr<AA> p1(p0); // 用已存在的 shared_ptr 初始化,计数加 1。

shared_ptr<AA> p1=p0; // 用已存在的 shared_ptr 初始化,计数加 1。

2)使用方法

 智能指针重载了*和->操作符,可以像使用指针一样使用 shared_ptr。

 use_count()方法返回引用计数器的值。

 unique()方法,如果 use_count()为 1,返回 true,否则返回 false。

 shared_ptr 支持赋值,左值的 shared_ptr 的计数器将减 1,右值 shared_ptr 的计算器将加 1。

 get()方法返回裸指针。

 不要用同一个裸指针初始化多个 shared_ptr。

 不要用 shared_ptr 管理不是 new 分配的内存。

3)用于函数的参数

与 unique_ptr 的原理相同。

4)不支持指针的运算(+、-、++、--)

二、更多细节

1)将一个 unique_ptr 赋给另一个时,如果源 unique_ptr 是一个临时右值,编译器允许这样做;如

果源 unique_ptr 将存在一段时间,编译器禁止这样做。一般用于函数的返回值。

2)用nullptr给shared_ptr赋值将把计数减1,如果计数为0,将释放对象,空的shared_ptr==nullptr。

3)release()释放对原始指针的控制权,将 unique_ptr 置为空,返回裸指针。

4)std::move()可以转移对原始指针的控制权。还可以将 unique_ptr 转移成 shared_ptr。

5)reset()改变与资源的关联关系。

pp.reset(); // 解除与资源的关系,资源的引用计数减 1。

pp. reset(new AA("bbb")); // 解除与资源的关系,资源的引用计数减 1。关联新资源。

6)swap()交换两个 shared_ptr 的控制权。

void swap(shared_ptr<T> &_Right);

7)shared_ptr 也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同

使用裸指针管理基类对象和派生类对象那样。

8)shared_ptr 不是绝对安全,如果程序中调用 exit()退出,全局的 shared_ptr 可以自动释放,但局

部的 shared_ptr 无法释放。

9)shared_ptr 提供了支持数组的具体化版本。

数组版本的 shared_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。

10)shared_ptr 的线程安全性:

 shared_ptr 的引用计数本身是线程安全(引用计数是原子操作)。

 多个线程同时读同一个 shared_ptr 对象是线程安全的。

 如果是多个线程对同一个 shared_ptr 对象进行读和写,则需要加锁。

 多线程读写 shared_ptr 所指向的同一个对象,不管是相同的 shared_ptr 对象,还是不同的

shared_ptr 对象,也需要加锁保护。

11)如果 unique_ptr 能解决问题,就不要用 shared_ptr。unique_ptr 的效率更高,占用的资源更

少。

示例 1:

#include <iostream>

#include <memory>

using namespace std;

class AA

{

public:

string m_name;

AA() { cout << m_name << "调用构造函数 AA()。\n"; }

AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<

")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }

};

int main()

{

shared_ptr<AA> pa0(new AA("西施 a")); // 初始化资源西施 a。

shared_ptr<AA> pa1 = pa0; // 用已存在的 shared_ptr 拷贝构造,

计数加 1。

shared_ptr<AA> pa2 = pa0; // 用已存在的 shared_ptr 拷贝构造,

计数加 1。

cout << "pa0.use_count()=" << pa0.use_count() << endl; // 值为 3。

shared_ptr<AA> pb0(new AA("西施 b")); // 初始化资源西施 b。

shared_ptr<AA> pb1 = pb0; // 用已存在的 shared_ptr 拷贝构造,

计数加 1。

cout << "pb0.use_count()=" << pb0.use_count() << endl; // 值为 2。

pb1 = pa1; // 资源西施 a 的引用加 1,资源西施 b 的引用减 1。

pb0 = pa1; // 资源西施 a 的引用加 1,资源西施 b 的引用成了 0,将被释放。

cout << "pa0.use_count()=" << pa0.use_count() << endl; // 值为 5。

cout << "pb0.use_count()=" << pb0.use_count() << endl; // 值为 5。

}

195、智能指针的删除器

在默认情况下,智能指针过期的时候,用 delete 原始指针; 释放它管理的资源。

程序员可以自定义删除器,改变智能指针释放资源的行为。

删除器可以是全局函数、仿函数和 Lambda 表达式,形参为原始指针。

示例:

#include <iostream>

#include <memory>

using namespace std;

class AA

{

public:

string m_name;

AA() { cout << m_name << "调用构造函数 AA()。\n"; }

AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<

")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }

};

void deletefunc(AA* a) { // 删除器,普通函数。

cout << "自定义删除器(全局函数)。\n";

delete a;

}

struct deleteclass // 删除器,仿函数。

{

void operator()(AA* a) {

cout << "自定义删除器(仿函数)。\n";

delete a;

}

};

auto deleterlamb = [](AA* a) { // 删除器,Lambda 表达式。

cout << "自定义删除器(Lambda)。\n";

delete a;

};

int main()

{

shared_ptr<AA> pa1(new AA("西施 a"), deletefunc);

//shared_ptr<AA> pa2(new AA("西施 b"), deleteclass());

//shared_ptr<AA> pa3(new AA("西施 c"), deleterlamb);

//unique_ptr<AA,decltype(deletefunc)*> pu1(new AA("西施 1"), deletefunc);

// unique_ptr<AA, void (*)(AA*)> pu0(new AA("西施 1"), deletefunc);

//unique_ptr<AA, deleteclass> pu2(new AA("西施 2"), deleteclass());

//unique_ptr<AA, decltype(deleterlamb)> pu3(new AA("西施 3"), deleterlamb);

}

196、智能指针 weak_ptr 一、shared_ptr 存在的问题

shared_ptr 内部维护了一个共享的引用计数器,多个 shared_ptr 可以指向同一个资源。

如果出现了循环引用的情况,引用计数永远无法归 0,资源不会被释放。

示例:

#include <iostream>

#include <memory>

using namespace std;

class BB;

class AA

{

public:

string m_name;

AA() { cout << m_name << "调用构造函数 AA()。\n"; }

AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<

")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }

shared_ptr<BB> m_p;

};

class BB

{

public:

string m_name;

BB() { cout << m_name << "调用构造函数 BB()。\n"; }

BB(const string& name) : m_name(name) { cout << "调用构造函数 BB(" << m_name << ")。

\n"; } ~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }

shared_ptr<AA> m_p;

};

int main()

{

shared_ptr<AA> pa = make_shared<AA>("西施 a");

shared_ptr<BB> pb = make_shared<BB>("西施 b");

pa-> m_p = pb;

pb->m_p = pa;

}

二、weak_ptr 是什么

weak_ptr 是为了配合 shared_ptr 而引入的,它指向一个由 shared_ptr 管理的资源但不影响资源的

生命周期。也就是说,将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。

不论是否有 weak_ptr 指向,如果最后一个指向资源的 shared_ptr 被销毁,资源就会被释放。

weak_ptr 更像是 shared_ptr 的助手而不是智能指针。

示例:

#include <iostream>

#include <memory>

using namespace std;

class BB;

class AA

{

public:

string m_name;

AA() { cout << m_name << "调用构造函数 AA()。\n"; }

AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<

")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }

weak_ptr<BB> m_p;

};

class BB

{

public:

string m_name;

BB() { cout << m_name << "调用构造函数 BB()。\n"; }

BB(const string& name) : m_name(name) { cout << "调用构造函数 BB(" << m_name << ")。

\n"; } ~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }

weak_ptr<AA> m_p;

};

int main()

{

shared_ptr<AA> pa = make_shared<AA>("西施 a");

shared_ptr<BB> pb = make_shared<BB>("西施 b");

cout << "pa.use_count()=" << pa.use_count() << endl;

cout << "pb.use_count()=" << pb.use_count() << endl;

pa->m_p = pb;

pb->m_p = pa;

cout << "pa.use_count()=" << pa.use_count() << endl;

cout << "pb.use_count()=" << pb.use_count() << endl;

}

三、如何使用 weak_ptr

weak_ptr 没有重载 ->和 *操作符,不能直接访问资源。

有以下成员函数:

1)operator=(); // 把 shared_ptr 或 weak_ptr 赋值给 weak_ptr。

2)expired(); // 判断它指资源是否已过期(已经被销毁)。

3)lock(); // 返回 shared_ptr,如果资源已过期,返回空的 shared_ptr。

4)reset(); // 将当前 weak_ptr 指针置为空。

5)swap(); // 交换。

weak_ptr 不控制对象的生命周期,但是,它知道对象是否还活着。

用 lock()函数把它可以提升为 shared_ptr,如果对象还活着,返回有效的 shared_ptr,如果对象

已经死了,提升会失败,返回一个空的 shared_ptr。

提升的行为(lock())是线程安全的。

示例:

#include <iostream>

#include <memory>

using namespace std;

class BB;

class AA

{

public:

string m_name;

AA() { cout << m_name << "调用构造函数 AA()。\n"; }

AA(const string& name) : m_name(name) { cout << "调用构造函数 AA(" << m_name <<

")。\n"; }

~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }

weak_ptr<BB> m_p;

};

class BB

{

public:

string m_name;

BB() { cout << m_name << "调用构造函数 BB()。\n"; }

BB(const string& name) : m_name(name) { cout << "调用构造函数 BB(" << m_name << ")。

\n"; } ~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }

weak_ptr<AA> m_p;

};

int main()

{

shared_ptr<AA> pa = make_shared<AA>("西施 a");

{

shared_ptr<BB> pb = make_shared<BB>("西施 b");

pa->m_p = pb;

pb->m_p = pa;

shared_ptr<BB> pp = pa->m_p.lock(); // 把 weak_ptr 提升为 shared_ptr。

if (pp == nullptr)

cout << "语句块内部:pa->m_p 已过期。\n";

else

cout << "语句块内部:pp->m_name=" << pp->m_name << endl;

}

shared_ptr<BB> pp = pa->m_p.lock(); // 把 weak_ptr 提升为 shared_ptr。

if (pp == nullptr)

cout << "语句块外部:pa->m_p 已过期。\n";

else

cout << "语句块外部:pp->m_name=" << pp->m_name << endl;

}

201、文件操作-写入文本文件

文本文件一般以行的形式组织数据。

包含头文件:#include <fstream>

类:ofstream(output file stream)

ofstream 打开文件的模式(方式):

对于 ofstream,不管用哪种模式打开文件,如果文件不存在,都会创建文件。

ios::out 缺省值:会截断文件内容。

ios::trunc 截断文件内容。(truncate)

ios::app 不截断文件内容,只在文件未尾追加文件。(append)

示例:

#include <iostream>

#include <fstream> // ofstream 类需要包含的头文件。

using namespace std;

int main()

{

// 文件名一般用全路径,书写的方法如下:

// 1)"D:\data\txt\test.txt" // 错误。

// 2)R"(D:\data\txt\test.txt)" // 原始字面量,C++11 标准。

// 3)"D:\\data\\txt\\test.txt" // 转义字符。

// 4)"D:/tata/txt/test.txt" // 把斜线反着写。

// 5)"/data/txt/test.txt" // Linux 系统采用的方法。

string filename = R"(D:\data\txt\test.txt)";

//char filename[] = R"(D:\data\txt\test.txt)";

// 创建文件输出流对象,打开文件,如果文件不存在,则创建它。

// ios::out 缺省值:会截断文件内容。

// ios::trunc 截断文件内容。(truncate)

// ios::app 不截断文件内容,只在文件未尾追加文件。(append)

//ofstream fout(filename);

//ofstream fout(filename, ios::out);

//ofstream fout(filename, ios::trunc);

//ofstream fout(filename, ios::app);

ofstream fout;

fout.open(filename,ios::app);

// 判断打开文件是否成功。

// 失败的原因主要有:1)目录不存在;2)磁盘空间已满;3)没有权限,Linux 平台下很常见。

if (fout.is_open() == false)

{

cout << "打开文件" << filename << "失败。\n"; return 0;

}

// 向文件中写入数据。

fout << "西施|19|极漂亮\n";

fout << "冰冰|22|漂亮\n";

fout << "幂幂|25|一般\n";

fout.close(); // 关闭文件,fout 对象失效前会自动调用 close()。

cout << "操作文件完成。\n";

}

202、文件操作-读取文本文件

包含头文件:#include <fstream>

类:ifstream

ifstream 打开文件的模式(方式):

对于 ifstream,如果文件不存在,则打开文件失败。

ios::in 缺省值。

示例:

#include <iostream>

#include <fstream> // ifstream 类需要包含的头文件。

#include <string> // getline()函数需要包含的头文件。

using namespace std;

int main()

{

// 文件名一般用全路径,书写的方法如下:

// 1)"D:\data\txt\test.txt" // 错误。

// 2)R"(D:\data\txt\test.txt)" // 原始字面量,C++11 标准。

// 3)"D:\\data\\txt\\test.txt" // 转义字符。

// 4)"D:/tata/txt/test.txt" // 把斜线反着写。

// 5)"/data/txt/test.txt" // Linux 系统采用的方法。

string filename = R"(D:\data\txt\test.txt)";

//char filename[] = R"(D:\data\txt\test.txt)";

// 创建文件输入流对象,打开文件,如果文件不存在,则打开文件失败。。

// ios::in 缺省值。

//ifstream fin(filename);

//ifstream fin(filename, ios::in);

ifstream fin;

fin.open(filename,ios::in);

// 判断打开文件是否成功。

// 失败的原因主要有:1)目录不存在;2)文件不存在;3)没有权限,Linux 平台下很常见。

if (fin.is_open() == false)

{

cout << "打开文件" << filename << "失败。\n"; return 0;

}

第一种方法。

//string buffer; // 用于存放从文件中读取的内容。

文本文件一般以行的方式组织数据。

//while (getline(fin, buffer))

//{

// cout << buffer << endl;

//}

第二种方法。

//char buffer[16]; // 存放从文件中读取的内容。

注意:如果采用 ifstream.getline(),一定要保证缓冲区足够大。

//while (fin.getline(buffer, 15))

//{

// cout << buffer << endl;

//}

// 第三种方法。

string buffer;

while (fin >> buffer)

{

cout << buffer << endl;

}

fin.close(); // 关闭文件,fin 对象失效前会自动调用 close()。

cout << "操作文件完成。\n";

}

相关推荐
若亦_Royi10 分钟前
C++ 的大括号的用法合集
开发语言·c++
eybk2 小时前
Pytorch+Mumu模拟器+萤石摄像头实现对小孩学习的监控
学习
6.942 小时前
Scala学习记录 递归调用 练习
开发语言·学习·scala
Aileen_0v02 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
守护者1703 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
学会沉淀。3 小时前
Docker学习
java·开发语言·学习
Rinai_R3 小时前
计算机组成原理的学习笔记(7)-- 存储器·其二 容量扩展/多模块存储系统/外存/Cache/虚拟存储器
笔记·物联网·学习
吃着火锅x唱着歌3 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
ragnwang4 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
胡西风_foxww4 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest