zoukankan      html  css  js  c++  java
  • C++Primer第五版 第十二章 动态内存

    习题首页

    12.1.1 shared_ptr类

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

    知识点2:栈内存用来保存定义在函数内的非static对象。

    知识点3:分配在栈或内存中的对象由编译器自动创建和销毁。

    知识点4: 栈对象在其定义的程序运行时才存在,static对象在使用之前分配,在程序结束时销毁。

         换句话说,静态变量和一般变量唯一的不同就是整个程序运行期间都将存在。她所在的内存不会被释放。
    

    知识点5:程序用堆来存储动态分配的对象,动态对象的生存周期由程序来控制,当不在使用时,必须用代码显示的销毁。

    知识点6:为了更安全的使用动态内存,新标准库定义了两种智能指针类型来管理动态对象,头文件为memory,智能指针的主要作用就是自动释放所指向的对象,shared_ptr类型允许多个指针指向同一对象,unique_ptr类型则独占所指向对象。

    shared_ptr<string> p1;//定义一个智能指针,指向对象为string类型
    shared_ptr<list<int>> p2;
    

    知识点7:make_shared()函数:最安全的分配和使用动态内存的方法,此函数在动态内存中分配一个对象并初始化它,返回指向该对象的shared_ptr,头文件为memory。

    shared_ptr<int> p3 = make_shared<int>(42);//p3为智能指针,指向一个值为42的对象
    auto p3 = make_shared<int>(42);//利用auto比较简便,若不传递任何参数,会值初始化
    

    知识点8:每个shared_ptr都有一个关联的计数器,称为引用计数,个人理解就是该对象被引用的次数,拷贝情况下会递增:

    1:用一个shared_ptr初始化另一个shared_ptr(拷贝)

    2:将一个shared_ptr传递给一个函数当参数,值传递(拷贝)

    3:作为函数的返回值,(返回的是自身的拷贝,也就是活引用的次数+1)

    计数器递减情况:

    1:给shared_ptr赋予一个新值(也就是说自身指向了另外一个地址,原来指向的对象已经没有引用者,则会自动释放)

    2:一个shared_ptr离开其作用域时,会被销毁(递减)

    当一个shared_ptr的计数器变为0,他就会自动释放自己所管理的对象,前提是其指向的对象只有一个引用者。

    知识点9:使用动态内存的原因:让多个对象共享相同的底层数据。也就是说拷贝的情况虽然发生,但是并不是元素的拷贝,而是将本身的指向拷贝给另一个指针对象,让这一个对象也指向自己所指向的对象,这样在本身释放以后,还有另一个对象指向自身原来所指向的对象。

    练习12.1

    b2已经被销毁了,但b2中的元素还没有被销毁,所以b1和b2都包含4个元素。

    练习12.2

    直接在函数声明之后加const。

    练习12.3

    是否要定义为const版本取决于是否需要加以修改参数,这两个函数都不会对参数进行修改,所以无需加const。

    练习12.4

    因为data_size的类型为size_type,是一个无符号类型,即使是负数,也会自动转化为非负。

    练习12.5

    explicit的作用就是抑制构造函数的隐式转换

    优点:不会自动的进行类型转换,必须清楚的知道类类型

    缺点:必须用构造函数显示创建一个对象,不够方便简单

    12.1.2

    知识点1:C++语言定义了new和delete来分配和释放内存,但相对于智能指针,这两个运算符管理内存容易出错。

    知识点2:在自由空间分配的内存是无名的,因此new无法为其分配对象命名,而是返回指向该对象的指针。

    知识点3:默认情况下,new分配的对象是默认初始化的,这就说明内置类型或者组合类型将是为定义的(例如:int,会指向一个为初始化的int),类类型对象将用默认构造函数进行初始化(例如string,会产生一个空string)。

    知识点4:建议使用值初始化(在最后加一对小括号即可),值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值是未定义的。

    知识点5:auto来推断分配对象的类型时,只有单一初始化器时才能使用。利用new分配const对象是合法的。

    auto p1 = new auto(obj);  //p指向一个与obj类型相同的对象
    auto p2 = new auto(a,b,c);//错误:括号内只能有单个初始化器
    
    const int *pci = new const int(1024); //分配并初始化一个const int
    

    知识点6:delete完成两个操作:销毁给定指针所指向的对象,释放对应的内存,delete的参数必须是指向动态分配的对象或是一个空指针。

    知识点7:内置类型指针管理的动态内存在被显式的释放前一直都会存在,因为内置类型与类类型不同,虽然内置类型的指针会在离开作用域后被销毁,但是其内存依然存在

    知识点8:同一块内存释放两次:两个内置类型的指针指向同一块自由空间分配的内存,在对一个指针进行delete之后,其指向的内存也会被释放,若再对第二个指针进行delete,会造成自由空间破坏

    知识点9:忘记使用delete,使用已经释放掉的对象都是经常发生的(使用new和delete时),所以尽可能的使用智能指针

    知识点10:在很多机器上,即使delete了某个内置类型的指针(也就是说释放了对应的内存空间),虽然指针已经无效,但是其仍然保留这释放空间的对应地址,变成了空悬指针,也就是说我们要保留指针,可以将其置为空指针

    练习12.6

    #include<vector>
    #include<iostream>
    #include<string>
    
    using namespace std;
    
    vector<int> *create_vi() {
    	return new std::vector<int>;
    }
    
    void push_vi(vector<int> *p) {
    	int i;
    	while (cin >> i) {
    		p->push_back(i);
    	}
    }
    
    void print_vi(vector<int> *p) {
    	for (const auto i : (*p)) {
    		cout << i << " ";
    	}
    	cout << endl;
    }
    
    int main() {
    	auto p = create_vi();
    	push_vi(p);
    	print_vi(p);
    	delete(p);
    	system("pause");
    	return 0;
    }
    

    练习12.7

    #include<vector>
    #include<iostream>
    #include<string>
    #include<memory>
    
    using namespace std;
    
    shared_ptr<vector<int>> create_vi() {
    	return make_shared<vector<int>>();
    }
    
    void push_vi(shared_ptr<vector<int>> p) {
    	int i;
    	while (cin >> i) {
    		p->push_back(i);
    	}
    }
    
    void print_vi(shared_ptr<vector<int>> p) {
    	for (const auto i : (*p)) {
    		cout << i << " ";
    	}
    	cout << endl;
    }
    
    int main() {
    	auto p = create_vi();
    	push_vi(p);
    	print_vi(p);
    	system("pause");
    	return 0;
    }
    

    练习12.8

    p是一个内置类型的指针,返回p会使得p的类型转化为bool类型,其指向的动态内存空间将无法得到释放。

    练习12.9

    q,r为内置类型指针,保存动态内存的地址,进行二次赋值之后,r原来指向的内存空间将得不到释放,造成内存泄漏。

    q2,r2为智能指针,r2进行赋值之后,其计数器将减一,由于r2是指向该内存空间的唯一智能指针,所以该内存会得到释放。

    练习12.10

    正确

    12.1.3

    知识点1:理解变量的销毁与其内存的释放之间的关系:内置类型的指针在离开作用域时,本身会被销毁,但是其指向的内存空间什么都不会发生,必须以显式的delete进行释放空间。智能指针在离开作用域时,本身也会被销毁,并且计数器减一,当其计数器为0且只有一个智能指针指向该对象时,该对象的内存空间会被释放。如若用智能指针的get()函数得到的一个内置指针来初始化一个临时的智能指针,一旦该内置指针被释放,指向的内存也会被释放,原来的智能指针就会变成空指针。

    知识点2:永远不要用get初始化另一个智能指针或是给智能指针赋值。

    练习12.11

    利用P的get()函数得到的内置指针来初始化一个临时的智能指针,一旦调用结束,该临时指针被销毁,内置指针所指对象内存会被释放,使得p变为空悬指针。

    练习12.12

    p为普通的内置指针指向一个动态内存,sp为智能指针指向一个动态内存。

    (a):合法,处理sp指针所指向内容,赋值的方式传递参数,处理完毕后内存不会被释放

    (b):不合法,参数必须是智能指针int类型

    (c):同上

    (d):合法,处理完毕后内存会被释放

    练习12.13

    删除p之后,会导致p指向的内存被释放,此时sp就会变成空悬指针,在sp指针被销毁时,该块内存会被二次delete,执行后产生错误:double free

    练习12.14

    知识点1:如果在new和delete之间发生了异常,且异常未被捕获,则该内存就永远不会被释放!(非常危险!!!),而智能指针只要离开作用域,计数器减一,则随着智能指针被销毁,该块内存也会被释放,这就说明在实际使用过程中应使用shared_ptr来防止内存泄漏

    知识点2:正确的使用智能指针:

    1:不使用相同的内置指针值初始化多个智能指针

    2:不delete get()返回的指针

    3:不使用get()初始化或reset另一个只能指针

    4:当你使用的智能指针管理的资源布氏new分配的内存,记住传递一个删除器

    #include<iostream>
    #include<memory>
    #include<string>
    
    using namespace std;
    
    struct destination {
    	string des;
    	destination(string des_) :des(des_) {}
    };
    
    struct connection{
    	string conn;
    	connection(string conn_) :conn(conn_) {}
    };
    
    connection connect(destination *des_) {
    	cout << "connect to: " << des_->des << endl;
    	return connection(des_->des);
    }
    
    void disconnect(connection conn_) {
    	cout << "disconnect " << conn_.conn << endl;
    }
    
    void end_connection(connection *p) { disconnect(*p); }
    
    void f(destination &d) {
    	connection c = connect(&d);
    	shared_ptr<connection> p(&c, end_connection);  //p接管了内置指针&c所指向的对象的所有权
    	cout << "connecting now(" << p.use_count() << ")" << endl;
    }
    
    int main() {
    	destination des("aa");
    	f(des);
    
    	system("pause");
    	return 0;
    }
    

    练习12.15

    #include<iostream>
    #include<memory>
    #include<string>
    
    using namespace std;
    
    struct destination {
    	string des;
    	destination(string des_) :des(des_) {}
    };
    
    struct connection{
    	string conn;
    	connection(string conn_) :conn(conn_) {}
    };
    
    connection connect(destination *des_) {
    	cout << "connect to: " << des_->des << endl;
    	return connection(des_->des);
    }
    
    void disconnect(connection conn_) {
    	cout << "disconnect " << conn_.conn << endl;
    }
    
    void end_connection(connection *p) { disconnect(*p); }
    
    void f(destination &d) {
    	connection c = connect(&d);
    	shared_ptr<connection> p(&c, [](connection *p) {disconnect(*p);});  //p接管了内置指针&c所指向的对象的所有权
    	cout << "connecting now(" << p.use_count() << ")" << endl;
    }
    
    int main() {
    	destination des("aa");
    	f(des);
    
    	system("pause");
    	return 0;
    }
    

    12.1.5 unique_ptr

    知识点1:unique_ptr拥有其所指向的对象,属于一一对应关系,unique_ptr被销毁时,其对象也会被销毁,unique_ptr不支持拷贝和赋值,必须采用直接初始化的方式,当我们定义一个unique_ptr时,必须将其绑定到一个new返回的指针上。

    知识点2:release成员返回unique_ptr当前保存的指针并将其置空。

    练习12.16

    出现的问题:一一对应的关系,其指向的对象是其类的私有成员。

    练习12.17

    (a):不合法,ix不是new返回的指针

    (b):同上

    (c):合法,unique_ptr必须采用直接初始化

    (d):不合法,同(a)

    (e):合法

    (f):不合法,必须使用new返回的指针进行初始化,赋值和拷贝的操作也不包含get()方法。

    练习12.18

    release()函数的作用就是放弃对指针指向对象的控制权,但shared_ptr是多对一的关系,其他的智能指针仍然可以删除这个对象,所以这个函数的话对shared_ptr没意义。

    12.1.6 weak_ptr

    知识点1:weak_ptr是一种不控制所指对象生存期的智能指针,它指向由一个shared_ptr管理的对象。共享数据,但不改变引用计数。

    知识点2:使用时必须判断是否存在,若weak_ptr指向的内容存在,则lock()返回一个指向共享对象的shared_ptr,若无,则返回空shared_ptr。

    练习12.19

    class StrBlob
    {
    public:
    	friend class StrBlobPtr;//声明friend
    	StrBlobPtr begin();
    	StrBlobPtr end();
    	StrBlob();//默认构造函数
    	StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)){}///C++11新特性
    	StrBlob(string il):data(make_shared<vector<string>> (il)){}//另一构造函数
    	typedef vector<string>::size_type size_type;//定义类型别名,方便使用
     
    	//定义函数,返回大小
    	size_type size() const
    	{
    		return data->size();
    	}
    	//判断vector<string>是否为空
    	bool empty()
    	{
    		return data->empty();
    	}
    	//向vector<string>中加入元素
    	void pushback(const string &s)
    	{
    		data->push_back(s);
    	}
    	//访问函数,应首先调用check()
    	string& front()
    	{
    		check(0,"front on empty StrBlob");
    		return data->front();
    	}
    	string& back()
    	{
    		check(0,"back on empty StrBlob");
    		return data->back();
    	}
    	void popback()
    	{
    		check(0,"pop_back on empty StrBlob");
    		data->pop_back();
    	}
     
    private:
    	shared_ptr<vector<string>> data;//指向vector<string>的智能指针
    	void check(size_type i,const string &msg) const//若访问元素的大小大于data的size,输出错误信息
    	{
    		if (i > data->size())
    		{
    			throw out_of_range(msg);//抛出该out_of_range异常,表示不在范围之内
    		}
    	}
    };
     
    class StrBlobPtr
    {
    public:
    	StrBlobPtr():curr(0){}//构造函数,将curr设定为0
    	StrBlobPtr(StrBlob &a, size_t sz = 0):wptr(a.data),curr(sz){}//构造函数,将StrBlob的智能指针与此类中的weak_ptr绑定
    	string& deref() const
    	{
    		auto p =check(curr,"deference past end");
    		return (*p)[curr];
    	}
    	StrBlobPtr& incr()
    	{
    		auto p =check(curr,"deference past end");
    		++curr;
    		return *this;
    	}
    private:
    	shared_ptr<vector<string>> check(size_t i,const string& msg) const//检查函数,返回一个vector<string>的智能指针
    	{
    		auto ret = wptr.lock();//检查对象是否还存在
    		if(!ret)
    		{
    			throw runtime_error("未绑定");
    		}
    		if (i >= ret->size())
    		{
    			throw out_of_range(msg);
    		}
    		return ret;
    	}
    	weak_ptr<vector<string>> wptr;//定义弱智能指针
    	size_t curr;//设立游标,表示下标
     
    };
     
    StrBlobPtr StrBlob::begin()
    {
    	return StrBlobPtr(*this);
    }
    StrBlobPtr StrBlob::end()
    {
    	return StrBlobPtr(*this, data->size());
    }
    

    练习12.20

    #include<iostream>  
    #include<string>  
    #include<fstream>
    #include<list>
    #include<vector> 
    #include<map>  
    #include<set>
    #include<cctype>//ctype无法打开,包含tolower()函数和ispunct函数
    #include<algorithm>
    #include<utility>//保存pair的头文件
    #include<Chapter12.h>
    using namespace std;
     
    int main(int argc, char**argv)  
    { 
    	ifstream in("1.txt");
    	string s;
    	StrBlob blob;
    	while (getline(in,s))
    	{
    		blob.pushback(s);
    	}
    	for (StrBlobPtr pbeg(blob.begin()), pend(blob.end()); pbeg != pend;
    		pbeg.incr())
    		cout << pbeg.deref() << std::endl;
    	return 0;
    }  
    

    练习12.21

    第一个版本,将合法性检查与元素获取的返回语句分离开来,代码更清晰易读,当执行到第二条语句时,已确保p是存在的vector,curr是合法的位置,可安全地获取元素并返回。

    练习12.22

    将构造函数定义为const

    12.2 动态数组

    知识点1:大多数的应用应该使用标准库容器,而不是使用动态分配的数组

    知识点2:用new分配的动态数组会返回一个元素类型的指针,而并未得到数组类型的对象

    知识点3:动态数组并不是数组类型,不能调用begin()和end()函数,详见106页

    知识点4:释放动态数组时,需要在指针名前加 [ ] ,数组中的元素按照逆序销毁

    知识点5:字符串字面常量不同于普通的局部变量,具有static duration lifetime,这整个程序的生命周期中都将存在

    练习12.23

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <sstream>
    #include <list>
    #include <deque>
    #include <forward_list>
    #include <algorithm>
    #include <numeric>
    #include <map>
    #include <set>
    #include <memory>
    using namespace std;
    
    
    
    int main(int argc,char **argv) {
    
    	const char a[] = "aaa";
    	const char b[] = "bbb";
    	char *ans = new char[strlen(a) + strlen(b) + 1];
    	strcpy(ans, a);
    	strcat(ans, b);
    	cout << string(ans) << endl;
    	string s1 = "a";
    	string s2 = "b";//标准库string对象
    	strcpy(ans, (s1 + s2).c_str());//必须转换为c类型字符串(c中无string类型)
    	cout << ans << endl;
    	delete[] ans;
    
    	
    	system("pause");
    	return 0;
    }
    

    练习12.24

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <sstream>
    #include <list>
    #include <deque>
    #include <forward_list>
    #include <algorithm>
    #include <numeric>
    #include <map>
    #include <set>
    #include <memory>
    using namespace std;
    
    int main(int argc,char **argv) {
    
    	string s;
    	cin>>s;
    	char *p = new char[s.size() + 1];
    	strcpy(p, s.c_str());
    	cout << p;
    	delete[]p;
    	system("pause");
    	return 0;
    }
    

    练习12.25

    delete[] pa;
    

    练习12.26

    #include<iostream>
    #include<memory>
    #include<string>	
    
    using namespace std;
    int main() {
    	allocator<string> alloc;
    	auto const p = alloc.allocate(10);
    	string s;
    	auto q = p;
    	while (cin >> s && q != p + 10) {
    		alloc.construct(q++, s);
    	}
    	while (q!=p)
    	{
    		alloc.destroy(--q);
    	}
    	alloc.deallocate(p, 100);
    
    	system("pause");
    	return 0;
    }
    
  • 相关阅读:
    Linux strip
    有趣的BUG
    GDB watch std::string size
    Redis Cluster Lua
    Double Buffer
    Yarn架构
    天池公交客流预测比赛
    hashmap,ConcurrentHashMap与hashtable的区别
    fail-fast和fail-safe
    常见机器学习算法优缺点
  • 原文地址:https://www.cnblogs.com/wsl540/p/13434731.html
Copyright © 2011-2022 走看看