C++primer学习笔记及作业答案之第十二章

笔记:

12.1 动态内存与智能指针

使用动态内存的三种原因:

1.程序不知道自己需要使用多少对象——容器

2.程序不知道所需对象的准确类型

3.程序需要在多个对象间共享数据——

内存泄漏:忘记delete内存。

接受指针参数的智能指针构造函数是explicit的,因此必须使用直接初始化,用“=”是拷贝初始化,是不行的。

永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。

传递给delete的指针必须指向动态分配的内存,或者是一个空指针。

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

12.2 动态数组

大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单,更不容易出现内存管理错误并且有可能有更好的性能。

分配一个数组会得到一个元素类型的指针。

delete 动态数组指针时,一定要记得加上方括号[ ]。

shared_ptr未定义下标运算符,而且智能指针类型不支持指针算术运算。

 

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

 

课后习题:

练习12.1:在此代码的结尾,b1 和b2 各包含多少个元素?

StrBlob b1;
{
    StrBlob b2 = {"a", "an", "the"};
    b1 = b2;
    b2.push_back("about");
}

答:由于StrBlob的data成员是一个指向string的vector的shared_ptr,因此StrBlob的赋值不会拷贝vector的内容,而是多个StrBlob对象共享同一个(创建于动态内存空间上)vector对象。b1和b2共享的是底层数据,因此,b1和b2均包含4个string。

练习12.2:编写你自己的StrBlob 类,包含const 版本的front 和back 。

//头文件
#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

class StrBlob
{
public:
	typedef vector<string>::size_type size_type;
	StrBlob();
	StrBlob(initializer_list<string> il);
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	//添加和删除元素
	void push_back(const string &t) { data->push_back(t); }
	void pop_back();
	//元素访问
	string& front();
	const string& front() const;
	string& back();
	const string& back() const;
private:
	shared_ptr<vector<string>> data;
	//如果data[i]不合法,抛出一个异常。
	void check(size_type i, const string &msg) const;
};

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

void StrBlob::check(size_type i, const string &msg) const
{
	if (i >= data->size())
	{
		throw out_of_range(msg);
	}
}

string& StrBlob::front()
{
	//如果vector为空,check会抛出一个异常
	check(0, "front on empty StrBolb");
	return data->front();
}

//const版本的front
const string& StrBlob::front() const
{
	check(0, "front on empty StrBlob");
	return data->front();
}

string& StrBlob::back()
{
	check(0, "back on empty StrBlob");
	return data->back();
}

//const版本的back
const string& StrBlob::back() const
{
	check(0, "back on empty StrBlob");
	return data->back();
}

void StrBlob::pop_back()
{
	check(0, "pop_back on empty StrBlob");
	return data->pop_back();
}

#endif
#include<iostream>
#include "My_StrBlob.h"

using namespace std;

int main()
{
	StrBlob b1;
	{
		StrBlob b2 = { "a", "an", "the" };
		b1 = b2;
		b2.push_back("about");
		cout << b2.size() << endl;
	}
	cout << b1.size() << endl;
	cout << b1.front() << " " << b1.back() << endl;
	
	const StrBlob b3 = b1;
	cout << b3.front() << " " << b3.back() << endl;

	system("pause");
	return 0;
}

练习12.3:StrBlob 需要const 版本的push_back 和pop_back 吗?如果需要,添加进去。否则,解释为什么不需要。

答:push_back和pop_back的语义分别是向StrBlob对象共享的vector对象添加和从其删除元素。因此,我们不应该为其重载const版本,因为常量StrBlob对象是不应该被允许修改共享vector对象的内容的。

练习12.4:在我们的check 函数中,没有检查i 是否大于0 。为什么可以忽略这个检查?

答:check定义为私有成员函数,它只会被StrBlob的成员函数调用,而不会被用户程序调用。因此可以保证传递给它的i的值符合要求,而不必进行检查。size_type是无符号类型,一定大于等于0。

练习12.5:我们未编写接受一个initializer_list explicit (参见7.5.4 节,第264 页)参数的构造函数。讨论这个设计策略的优点和缺点。

答:未编写一个初始化列表参数的显式构造函数,意味着可以进行列表向StrBlob的隐式类型转换,在需要StrBlob的地方可以使用列表进行替代。而且还可以进行拷贝形式的初始化,这令程序编写更为简单方便。

但这种隐式转换并不总是好的。例如,列表中可能并非都是合法的值。再如,对于接受StrBlob的函数,传递给它一个列表,会创建一个临时的StrBlob对象,用列表对其初始化,然后将其传递给函数,当函数完成后,此对象将被丢弃,再也无法访问了。对于这种情况,我们可以定义显式的构造函数,禁止隐式类类型转换。

练习12.6:编写函数,返回一个动态分配的int 的vectorc 将此vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在vector 元素中。再将vector传递给另一个函数,打印读入的值。记得在恰当的时刻delete vector 。

// 练习 12.6
#include <iostream>
#include <vector>
#include <new>

using namespace std;

vector<int>* new_vector(void)
{
	return new (nothrow) vector<int>;
}

void read_ints(vector<int> *pv)
{
	int num;
	while (cin >> num)
	{
		pv->push_back(num);
	}
}

void print_ints(vector<int> *pv)
{
	for (auto &r : *pv)
		cout << r << " ";
	cout << endl;
}

int main()
{
	vector<int> *pv = new_vector();
	if (!pv)
	{
		cout << "内存不足!" << endl;
		return -1;
	}

	read_ints(pv);
	print_ints(pv);

	delete pv;
	pv = nullptr;

	system("pause");
	return 0;
}

练习12.7:重做上一题,这次使用shared_ptr 而不是内置指针。

// 练习 12.7
#include <iostream>
#include <vector>
#include <memory>

using namespace std;

shared_ptr<vector<int>> new_vector()
{
	return make_shared<vector<int>>();
}

void read_ints(shared_ptr<vector<int>> spv)
{
	int num;
	while (cin >> num)
	{
		spv->push_back(num);
	}
}

void print_ints(shared_ptr<vector<int>> spv)
{
	for (auto &r : *spv)
		cout << r << " ";
	cout << endl;
}

int main()
{
	shared_ptr<vector<int>> spv = new_vector();

	read_ints(spv);
	print_ints(spv);

	system("pause");
	return 0;
}

练习12.8:下面的函数是否有错误?如果有,解释错误原因。

bool b() {
    int* p = new int;
    //...
    return p;
}

答:从程序片段看,可以猜测程序员的意图是通过new返回的指针值来区分内存分配成功或失败——成功返回一个合法指针,转换为整型是一个非零值,可转换为bool值true;分配失败,p得到nullptr,其整型值是0,可转换为bool值false.
但普通new调用在分配失败时抛出一个异常bad_alloc,而不是返回nullptr,因此程序不能达到预想目的。

可将new int改为new (nothrow) int 来令ne

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值