C++ Primer 第十二章 12.1 动态内存与智能指针 练习和总结

前文

之前我们只学习过静态内存和栈内存。现在还有一个堆内存,他们保存的变量类型分别为

内存名字 该内存中保存的类型
静态内存 static局部变量、类的static数据成员、定义在函数体之外的变量
栈内存 函数体中的所有非static变量
堆内存 动态分配内存空间的变量

12.1动态内存和智能指针

我们可以使用new关键字动态的分配内存空间,并返回指向该对象的指针,我们可以使用delete关键字,传入一个动态分配内存的指针,销毁该对象,回收内存。

但是直接使用new和delete是非常容易犯错的,所以C++标准提供了两个智能指针。
shared_ptr,unique_ptr。

shared_ptr允许多个指针指向同一个对象。
unique_ptr,只允许一个指针指向一个对象。

这两个指针定义在memory头文件中

12.1.1 shared_ptr类

shared_ptr是一个模板,我们在创建一个智能指针变量时,需要指定这个指针的类型。

std::shared_ptr<string> p;

下面是unique_ptr和shared_ptr都支持的操作。
在这里插入图片描述
可以看到智能指针的用法和普通指针没有什么区别,我们可以使用get来获取,智能指针中保存的普通指针。

shared_ptr独有的操作
在这里插入图片描述
shared_ptr使用的是引用计数的方法来管理内存。我们不必在乎引用计数是如何实现了,只需要知道什么时候引用计数什么时候会+1,什么时候会-1,什么时候会回收内存空间。

引用计数情况 发生的情况
引用计数+1 创建一个智能指针+1;进行拷贝时,被拷贝者+1;进行赋值时,赋值语句右侧的智能指针+1;作为实参传入函数时+1;当做返回值返回时+1
引用计数-1 赋值语句左侧的智能指针-1;生命周期结束-1
引用计数=0 销毁指针所指向的对象,回收内存

例子

//创建p1,p1的引用计数+1,此时为1
std::shared_ptr<string> p1 = std::make_shared<string>("123");
//p2的引用计数+1,此时为1
std::shared_ptr<string> p2 = std::make_shared<string>("233");
//将p2的值赋值给p1,p2引用计数+1,p1引用计数-1,p1的引用计数为0,销毁p1指向的对象回收内存
//p1和p2现在指向同一个对象,引用计数为2.
p1 = p2;

最安全的分配和使用动态内存的方法是调用make_shared的标准库函数,他是一个模板函数,所以需要传入类型,和容器的emplace()一样,我们可以在()中,传入类型的构造函数所需的参数。

std::shared_ptr<string> p = std::make_shared<string>("123");

当引用计数为0的时候,智能指针会销毁指向的对象并回收内存,其本质是调用对象的析构函数

什么时候使用动态内存
1.程序不知道自己需要使用多少对象。(比如可以动态添加元素的容器类,就是使用动态内存)
2.程序不知道所需对象的准确类型(这个没有体会到)
3.程序需要在多个对象间共享数据。(比如问中提到了StrBlob)

练习
12.1

因为b1=b2,所以b1和b2的数据成员data指向的是同一个对象,所以他们的元素都为4个。

12.2

注意在编写fornt和back的const版本时,front和back的返回值也需要为const。不然我们依旧可以通过返回值来修改data所指向的对象的值。

class StrBlob {
   
public:
	using size_type = vector<string>::size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> il);
	size_type size() const {
    return data->size(); };
	bool empty() const {
    return data->empty(); };
	void push_back(const string& str) {
    data->push_back(str); };
	void pop_back();
	std::string& front();
	const std::string& front()const ;
	std::string& back();
	const std::string& back() const;
private:
	std::shared_ptr<vector<string>> data;
	void check(size_type i,const string& msg) const;
};
void StrBlob::check(size_type i, const string& msg) const{
   
	if (i>=data->size()) {
   
		throw std::out_of_range(msg);
	}
}
string& StrBlob::front() {
   
	check(0, "front out of range");
	return data->front();
}

const string& StrBlob::front() const{
   
	check(0, "front out of range");
	return data->front();
}
string& StrBlob::back(){
   
	check(0, "back out of range");
	return data->back();
}

const string& StrBlob::back() const{
   
	check(0, "back out of range");
	return data->back();
}

void StrBlob::pop_back() {
   
	check(0, "pop_back() out of range");
	data->pop_back();
}
StrBlob::StrBlob():data(std::make_shared<vector<string>>()) {
   
	
}


StrBlob::StrBlob(std::initializer_list<string> il) : data(std::make_shared<vector<string>>(il)) {
   
	
}
12.3

不需要,因为如果一个对象为常量,我们认为他是不能够改变数据成员的。push_back()和pop_back()都会data所指向的对象的数据成员,所以当常量调用push_back,pop_back()时,应该编译报错

12.4

因为i是stirng::size_type类型,而data->size()也是type()类型,size_type类型是size_t,而size_t无符号整型,它永远都不会为负数。

另一方面,我们写的check,只需要判断data所指向的对象是否有元素,所以可以直接传入0,如果0>=data->size()则表示容器为空,而不需要考虑是否大于0.其实我觉得用==也可以。

12.5

如果我们加入了explict。

优点是:
我们可以避免隐式转换,以免发生意料之外的事情

缺点:
如果加explicit,意味着我们需要显式传入一个类的对象,这增加的编码的负担,降低了灵活性。

12.1.2 直接管理内存

之前说到直接使用new和delete关键字是非常麻烦的,那么为什么这么棘手呢

使用new动态分配和初始化对象

我们可以使用new直接动态分类对象,因为new出来的对象是没有名字的,但是它会返回一个指向对象的指针,所以我们可以写

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值