C++ primer 第十二章

动态分配的对象的生存期与它们在哪里创建无关,只有当显式地被释放时,这些对象才会销毁。

静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。

栈内存用来保存定义在函数内的非static对象。

堆内存用来存储动态分配的对象。

静态或栈内存中的对象由编译器自动创建和销毁,而堆内存中的对象必须显式地销毁它们。

1.动态内存与智能指针

运算符new在动态内存中为对象分配空间并返回一个指向该对象的指针。

运算符delete接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

智能指针行为类似于常规指针,但它能自动释放所指向的对象。

智能指针也是一种模板,都定义在memory头文件中。

智能指针是普通指针的封装,能有效地保证指针的安全性。

1.1、shared_ptr类

默认初始化的智能指针中保存着一个空指针。

shared_ptr p1;
shared_ptr p2;

在一个条件判断中使用智能指针,效果是检测它是否为空。

if(p1 && p1->empty()) //如果p1不为空,检查它是否指向一个空string
智能指针共有的操作

shared_ptr sp

unique_ptr up

空智能指针,可以指向类型为T的对象
p 将p作为条件来判断,若指向一个对象,则为true
p ->mem 等价于(*p).mem
p.get() 返回p中保存的普通指针

swap(p,q)

p.swap(q)

交换p和q中的指针
shared_ptr独有的操作
make_shared(args)

返回一个shared_ptr,指向一个动态分配的类型为T的对象

使用args初始化该对象

shared_ptrp(q)

p是shared_ptr q的拷贝,该操作会增加q中的计数器

q中的指针类必须能转换成T*

p = q

p和q都是智能指针,所保存的指针必须能相互转换

该操作会递减p的引用计数,递增q的引用计数

p.unique() 若p.use_count()为1,返回true
p.use_count() 返回与p共享对象的智能指针数量

调用一个make_shared函数是最安全的分配和使用动态内存的方法。

make_shared用其参数来构造给定类型的对象,若无参数,对象就会进行值初始化。

shared_ptr p1 = make_shared(42);
//指向一个值为42的int的shared_ptr
shared_ptr p2 = make_shared(3,'9');
//指向一个值为"999"的string

通常可以使用auto定义一个对象来保存make_shared的结果。

auto p2 = make_shared>();

引用计数可以理解为每个shared_ptr都有一个关联的计数器,来统计多少个shared_ptr指向相同的对象。 

拷贝一个shared_ptr、用一个shared_ptr初始化另一个shared_ptr、作为函数的返回值、作为参数赋给另一个函数时,都会递增它所关联的计数器。

当我们给shared_ptr赋予一个新值或shared_ptr被销毁时,计数器会递减。

一旦一个shared_ptr的计数器为0,它就会自动释放自己所管理的对象。

shared_ptr通常成员函数——析构函数来完成销毁工作。 

每个类都有一个析构函数,来控制此类型的对象销毁时做什么操作。

对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉。

shared_ptr指针作为元素存放在容器中,没有使用,指针不会被删除,需要通过erase来删除。

使用动态内存的一个常见原因是允许多个对象共享相同的状态。

1.2、直接管理内存

new在自由空间分配的内存是无名的,返回一个指向该对象的指针。

int *p = new int; //p指向一个动态分配的,未初始化的无名对象

默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的。

可以通过直接初始化、列表初始化或值初始化方式来初始化对象。

int *p1 = new int(1024);   //直接初始化
vector *p2 = new vector{1,2,3,4,5,6,7,8};  //列表初始化
string *p3 = new string();  //值初始化

若提供一个括号包围的初始化器,就能使用auto来推断对象的类型。

只有当括号中仅有单一初始化器时才能使用auto。 

auto p1 = new auto(obj); //正确
auto p2 = new auto{a,b,c};  //错误,含有多个初始化器

一个动态分配的const对象必须进行初始化,new返回的指针是一个指向const的指针。

const int *p = new const int(1024);

若内存耗尽,new无法分配所要求的空间,会抛出一个类型为bad_alloc的异常。

我们可以通过定位new形式来阻止new抛出异常。 

int *p1 = new int;   //分配失败,抛出异常
int *p2 = new (nothrow) int;  //分配失败,返回一个空指针

 delete表达式接受一个指针,释放该指针指向的对象的内存空间。

delete p; //p必须指向一个动态分配的对象或一个空指针

通常情况下,编译器不能分辨指针指向静态还是动态分配的对象。

虽然一个const对象的值不能被改变,但它本身是可以被销毁的。

const int *p = new const int(1024);
delete p;

由内置指针管理的动态内存在被显式释放前一直都会存在。 

空悬指针: 指向一块曾经保存数据对象但现在已经无效的内存的指针。

尽量在delete之后将nullptr赋予指针。

上述方法只对这个指针有效,对其他仍指向内存的指针是没有作用的。

int *p(new int(42));
auto q = p;
delete p;   //只对p有效,q仍然指向被删除的内存
p = nullptr;

1.3、shared_ptr和new结合使用 

如果我们不初始化一个智能指针,它就会被初始化为一个空指针。

接受一个指针参数的智能指针构造函数是explicit的,必须使用直接初始化来初始化智能指针。

shared_ptr p1 = new int(1024);   //错误,该操作类似于赋值
shared_ptr p2(new int(1024));    //正确,直接初始化

一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针:

shared_ptr clc(int p)
{
   return shared_ptr(new int(p)); //正确,显式地创建一个shared_ptr,并返回
   return new int(p);  //错误,需要隐式转换成shared_ptr
}

默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为使用到delete来释放。

允许将智能指针绑定到其他类型的指针上,但必须提供自己的操作来替代delete。 

定义和改变shared_ptr的方法
shared_ptrp(q)

p管理内置指针q所指向的对象

q必须指向new分配的内存,且能转换为T*类型

shared_ptrp(u) p从unique_ptr哪里接管了对象的所有权,将u置空
shared_ptr p(q,d)

p接管了内置指针所指的对象的所有权

p将使用可调用对象d来替代delete

shared_ptr p(p2,d)

p是shared_ptr p2的拷贝

p将用可调用对象d来代替delete

p.reset()

p.reset(q)

p.reset(q,d)

若p是唯一指向其对象的shared_ptr,reset会释放此对象

若传递了参数内置指针q,令p指向q

若还传递了参数d,将会调用d来替代delete来释放p

  make_shared在分配对象的同时就将指针与内存绑定,避免了同个内存绑定在多个独立创建的shared_ptr上。

当shared_ptr绑定到普通指针时,不应该再使用内置指针来访问shared_ptr所指的内存。

 get函数返回内置指针,该指针指向智能指针管理的对象。

不要使用get函数的返回值来初始化另一个智能指针或为智能指针赋值。

允许使用reset来将一个新的指针赋予给一个shared_ptr:

shared_ptr p = new int(1024);  //错误,不能将指针赋给shared_ptr
p.reset(new int(1024));  //正确,p指向新对象

 reset成员经常与unique一起使用,用来建厂当前指针是否是对象仅有的用户。

if(!p.unique()) //不是唯一用户,指向新的拷贝
    p.reset(new string(*p));
*p += newval;

1.4、智能指针和异常 

 如果使用智能指针,即使程序块异常结束,智能指针类也能确保内存不再需要时将其释放。

当程序块发生异常时,我们直接管理的内存是不会自动释放的。

通常使用智能指针类似的技术来管理不具有良好定义的析构函数的类。

默认情况下,shared_ptr假定它们指向的是动态内存。

 正确使用智能指针的基本规范:
1、不使用相同的内置指针值来初始化多个智能指针。

2、不delete get()返回的指针。

3、不使用get()初始化或reset另一个指针。

4、若你使用智能指针管理的资源不是new分配的内存,记住传递一个删除器。

1.5、unique_ptr

与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。

定义unique_ptr时,需要将其绑定到一个new返回的指针上,采用直接初始化方式。

unique_ptr不支持普通的拷贝或赋值操作。

unique_ptr p2(p1); //错误,unique_ptr不支持拷贝
unique_ptr p3;
p3 = p2;   //错误,unique_ptr不支持赋值
unique_ptr操作

unique_ptr u1

unique_ptr u2

空unique_ptr可以指向类型为T的对象,使用delete来释放指针

u2使用一个类型为D的可调用对象来释放它的指针

unique_ptr u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象替代delete
u = nullptr 释放u指向的对象,将u置为空
u.release() u放弃对指针的控制权,返回可作为右值指针,并将u置为空

u.reset()

u.reset(q)

u.reset(nullptr)

释放u指向的对象

如果提供了内置指针q,令u指向该对象,否则将u置为空

 通过调用release或reset来移动指针的所有权,替代拷贝和赋值操作。

unique_ptr p2(p1.release()); //把控制权从p1转移到p2,p1置为空
p2.reset(p3.reease()); //控制权p3转移到p2,reset释放p2原来指向的内存

调用release会切断unique_ptr和它原来管理的对象间的联系。

不能拷贝unique_ptr有特殊情况:可以拷贝或赋值一个将要被销毁的unique_ptr。 

unique_ptr clc(int p)
{
   return unique_ptr(new int(p));  //返回一个unique_ptr
   unique_ptr ret(new int(p));   
   return ret;   //返回一个局部对象的拷贝
}

auto_ptr类具有unique_ptr的部分特性,但不能在容器中保存,也不能在函数中返回。

unique_ptr默认情况下,使用delete来释放它指向的对象。

重载一个unique中的删除器会影响到unique_ptr类型以及如何构造该类型的对象。

unique_ptr p(&c,end_con);
//p指向类型为connect的对象,使用类型为end_con的对象来删除connect对象

decltype来指明函数指针的类型。

 1.6、weak_ptr

weak_ptr是一种不控制所指向对象生存期的指针,指向由一个shared_ptr管理的对象。

将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。 

weak_ptr
weak_ptr w 空weak_ptr可以指向类型为T的对象
weak_ptr w(sp)

与shared_ptr sp指向相同对象的weak_ptr

T必须能转换为sp指向的类型

w = p

p是一个shared_ptr或一个weak_ptr

赋值后w与p共享对象

w.reset() 将w置为空
w.use_count() 与w共享对象的shared_ptr的数量

w.expired()

若w.use_count()为0,返回true
w.lock()

如果expired为true,返回一个空shared_ptr

否则返回一个指向w的对象的shared_ptr

 当我们创建一个weak_ptr时,用shared_ptr来初始化它:

auto p = make_shared(42);
weak_ptr wp(p);

由于对象可能不存在,因此不能直接使用weak_ptr直接访问对象,必须调用lock来检查对象。 

if(shared_ptr np = wp.lock()) //若np不为空则条件成立

对于文中的StrBlob类来说,为该类定义begin和end操作,返回一个指向它自身的StrBlobPtr:

class StrBlobPtr;
class StrBlob{
    friend class StrBlobPtr;
    StrBlobPtr begin() { return StrBlobPtr(*this);}
//begin函数以StrBlob对象作为参数传递给StrBlobPtr的接受一个StrBlob构造函数
//this就是StrBlob对象,解引用得到指针指向的vector,curr为0,是vector的开头元素

    StrBlobPtr end()
       { auto ret = StrBlobPtr(*this,data->size());
         return ret;
       } 
//end函数与begin函数差不多,但是构造函数的sz有实参传入,sz = data->size(),是尾元素位置 
}

2.动态数组 

 标准库中的allocator类允许我们将分配和初始化分离。

分配动态数组的类必须定义自己版本的操作,在拷贝、复制及销毁对象时管理所关联的内存。

2.1、new和数组

 new分配要求数量的对象并返回指向第一个对象的指针。

int* p = new int[get_size()]; //方括号中的大小必须是整型,但不必是常量

允许使用表示数组类型的类型别名来分配数组:

typedef int arr[42];  //arr表示42个int的数组类型
int* p =new arr;    //分配一个42个int的数组,p指向第一个int

虽然我们通常称new T[ ]分配的内存为动态数组,但我们并未获得一个数组类型的对象,而是得到一个数组类型的指针。

 由于分配的内存并不是一个数组类型,因此不能对该数组调用begin、end和范围for语句来处理。

 默认情况下,new分配的对象都是默认初始化的。

int* p1 = new int[10];   //未初始化
int* p2 = new int[10](); //值初始化为0
int* p3 = new int[10]{1,2,3,4,5,6,7,8,9,10}; //列表初始化
int* p4 = new int[10]{1,2,3,4}; //前四个进行列表初始化,剩下的进行值初始化

若列表中元素数目大于元素数目,则new表达式失败,不会分配任何内存。

动态分配一个空数组时,new会返回一个合法的非空指针(尾后指针)。

char* p = new char[0]; //合法,但cp不能解引用

通过一种特殊的delete来释放动态数组:

delete [] p; //p必须指向一个动态分配的数组或为空

数组中的元素按逆序销毁。

当我们使用类型别名来定义数组类型时,在释放该数组指针时也必须使用方括号。

typedef int arr[10];
int* p = new arr;
delete [] p;  //方括号是必需的

标准库提供了管理new分配的数组的unique_ptr版本,在对象类型后跟空方括号。

unique_ptr u(new int[10]); //u指向一个包含10个未初始化的int的数组
指向数组的unique_ptr
unique_ptr u

u指向一个动态分配的数组,数组元素类型为T

unique_ptr u(p)

u指向内置指针p所指向的动态分配的数组

p必须能转换为类型T*

u[i]

返回u拥有的数组中位置i处的对象

u必须指向一个数组

指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符)。

shared_ptr不直接支持管理动态数组,必须提供自己定义的删除器才能使用。

shared_ptr sp(new int[10],[](int* p){delete [] p;});
//使用lambda来释放数组

shared_ptr不直接支持动态数组管理会影响我们如何访问数组中的元素。

//shared_ptr未定义下标运算符,且不支持指针的算术运算
for(size_t i = 0; i != 10; ++i)
   *(sp.get() + i) = i;  //使用get获得一个内置指针

2.2、allocator类 

 new将内存分配和对象构造组合在一起,delete将对象析构和内存释放组合在一起。

当分配一大块内存时,通常希望能在内存上按需构造对象,因此要求把内存分配和对象构造分离。

一般情况下,将内存分配和对象构造组合的方式的缺点:
1、可能会导致不必要的浪费。

2、每个使用到的元素先被默认初始化,再进行赋值;性能有所下降。

3、没有默认构造函数的类不能动态分配数组。

标准库allocator类定义在头文件memory中。

allocator类是一个模板,它所分配的内存是原始的、未构造的。 

定义一个allocator对象,必须指明它可以分配的对象类型。

allocator alloc;    //可以分配string的allocator对象
auto const p = alloc.allocate(n);  //分配n个未初始化的string
allocator类及其算法
allocator a 定义了一个allocator对象,可以为类型为T的对象分配空间
a.allocate(n) 分配一段原始的未构造的内存,保存n个类型为T的对象
a.deallocate(p,n)

释放从指针p地址开始的内存

p必须由allocate返回的指针,n是p创建时所要求的大小

在调用此函数时,必须对每个在这块内存中创建的对象调用destroy

a.construct(p,args)

指针p指向一块原始内存

args是构造函数,用来在p指向的内存中构造对象

a.destroy(p) 此算符对指针p指向的对象执行析构函数

 destroy函数删除数组中的数据,deallocate函数回收内存块。 

construct在给定位置构造一个元素,额外参数用来初始化构造的对象。

auto q = p;   //用q来代替p移动
alloc.construct(q++);  //*p是空字符串
alloc.construct(q++,"hello");  //*p是hello

为了使用allocate返回的内存,必须用construct来构造对象。使用未构造的内存,是未定义的。

 当我们使用完对象后,必须对每个构造的元素调用destroy来销毁它们。

 传递给deallocate的指针不能为空,它必须指向由allocate分配的内存。

alloc.deallocate(p,n);
allocator算法
uninitialized_copy(b,e,b2) 从迭代器b和e的范围中拷贝元素到迭代器b2指定的未构造的内存中。假定b2指定的内存足够大
uninitialized_copy_n(b,e,b2) 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中
uninitialized_fill(b,e,t) 在迭代器b和e的范围中创建对象,对象的值为t的拷贝
uninitialized_fill_n(b,n,t)

从迭代器b指向的内存地址开始创建n个对象

b必须指向足够大的未构造的原始内存

3.使用标准库:文本查询程序 

 程序需求:

1、逐行读取输入文件内容,并将每一行分解为独立的单词。

2、将单词与查询单词进行匹配,若匹配成功,则记录行号和文本。

3、打印行号和该行文本。

使用到以下的标准库设施来实现需求:

vector来保存整个输入文件的内容。

istringstream来将每行分解为单词,与查询单词进行匹配。

set来保存每个单词在输入文本中出现的行号。

map来将每个单词与它出现的行号关联起来。

class queryresult; //未定义,但使用到,先声明
class textquery  //保存输入文件
{
public:
	using line_no = std::vector::size_type; //类型别名
	textquery(std::ifstream&); //构造函数,接受一个文件流为参数
	queryresult query(const std::string&) const; //
private:
	std::shared_ptr> file; //输入文件
	std::map>> wm; //用查找的单词做关键字,保存行号的set做值
};

textquery::textquery(std::ifstream& is) : file(new vector)
{
	string text;
	while (getline(is, text))
	{
		file->push_back(text); //保存每一行,file是指针,需解引用
		int n = file->size() - 1;  //当前行号
		std::istringstream line(text); //分解成单个单词
		string word;
		while(line >> word) 
		{
			auto& line = wm[word]; //line与wm[word]绑定,line是智能指针
			if (!line)  //若单词不在wm中,返回一个空指针
				line.reset(new std::set); //指针指向新创建的set
			line->insert(n); //将行号插入set中

		}
	}
}

class queryresult {
	friend std::ostream& printis(std::ostream&, const queryresult&); //友元声明
public:
	queryresult(std::string s,std::shared_ptr> p,std::shared_ptr> f):  //构造函数
		sought(s), lines(p),file(f) {}
private:
	std::string sought;  //查找的单词
	std::shared_ptr> lines;  //指向保存单词出现的行号的set
	std::shared_ptr> file;  //指向保存文件的vector
};

queryresult textquery::query(const string& sought) const  //查找单词
{
	static std::shared_ptr> nodata(new std::set); //空set
	auto loc = wm.find(sought);
	if (loc == wm.end())
		return queryresult(sought, nodata, file); //未找到
	else
		return queryresult(sought, loc->second, file);
}

string make_plural(size_t ctr, const string& word, const string& ending)
{     //make_plural根据大小是否等于1来打印time或times
	return (ctr > 1) ? word + ending : word;
}

std::ostream& printis(std::ostream& os, const queryresult& qr)
{   
	os << qr.sought << "occurs" << qr.lines->size() << " " << make_plural(qr.lines->size(),"time","s") << endl;
	for (auto num : *qr.lines)
		os << "\tline" << num + 1 << " " << *(qr.file->begin() + num) << endl;
	return os;
}
void runque(std::ifstream& in)  //主函数
{
	textquery tq(in);
	while(true)
	{
		cout << "enter word or quit:";  //输入查找单词或退出指令
		string s;
		if (!(cin >> s) || s == "quit") break; // 退出指令
		printis(cout, tq.query(s)) << endl; //打印且把参数传入tq.query
	}
}

int main()
{
	std::ifstream in("arr.txt");
	if (!in)
	{
		std::cerr << "no input file" << endl;
		return -1;
	}
	runque(in);
	return 0;
}

你可能感兴趣的:(c++,primer,c++,开发语言)