C++ Primer(第五版)|练习题答案与解析(第十二章:动态内存)
本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++ Primer
练习题12.1
在此代码的结尾,b1和b2各包含多少元素?
StrBlob b1;
{
strBlob b2 = {
"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
“使用动态内存的原因:让多个对象共享相同的底层数据。也就是说拷贝的情况虽然发生,但是并不是元素的拷贝,而是将本身的指向拷贝给另一个指针对象,让这一个对象也指向自己所指向的对象,这样在本身释放以后,还有另一个对象指向自身原来所指向的对象。”
所以b1和b2都有4个,但是b2已经没了,如果接着使用b2会出错。
练习题12.2
编写你自己的StrBlob类,包含const版本的front和back。
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <exception>
#include <iostream>
using namespace std;
class StrBlob {
public:
using size_type = vector<string>::size_type;
StrBlob():data(make_shared<vector<string>>()) {
}
StrBlob(initializer_list<string> il):data(make_shared<vector<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() {
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
string& front() {
check(0, "front on empty StrBlob");
return data->front();
}
string& back() {
check(0, "back on empty StrBlob");
return data->back();
}
const string& front() const {
check(0, "front on empty StrBlob");
return data->front();
}
const string& back() const {
check(0, "back on empty StrBlob");
return data->back();
}
private:
void check(size_type i, const string &msg) const {
if (i >= data->size()) throw out_of_range(msg);
}
private:
shared_ptr<vector<string>> data;
};
int main(){
const StrBlob csb{
"train", "test", "overfit" };
StrBlob sb{
"train", "NN", "ML" };
cout << csb.front() << " " << csb.back() << endl;//csb是const 使用pop_back报错
sb.pop_back();
cout << sb.front() << " " << sb.back() << endl;
sb.back() = "CNN";
cout << sb.front() << " " << sb.back() << endl;
return 0;
}
测试:
train overfit
train NN
train CNN
练习题12.3
StrBlob需要const版本的push_back和pop_back吗?如果需要,添加进去。否则,解释为什么不需要。
这两个函数不会对参数进行修改,所以无需加const。
练习题12.4
在我们的check函数中,没有检查i是否大于0。为什么可以忽略这个检查?
因为size_type本身就是unsigned类型的,非负数,即使赋值负数也会转换成大于0的数。
练习题12.5
我们未编写接受一个initializer_list explicit参数的构造函数。讨论这个设计策略的优点和缺点。
explicit
的作用就是抑制构造函数的隐式转换
- 优点:不会自动的进行类型转换,阻止
initializer_list
自动转换为StrBlob
,防止出错。 - 缺点:必须用构造函数显示创建一个对象,不够方便简单
练习题12.6
编写函数,返回一个动态分配的int的vector。将此vector传递给另一个函数,这个函数读取标准输入,将读入的值保存在vector元素中。再将vector传递给另一个函数,打印读入的值。记得在恰当的时刻delete vector。
#include <iostream>
#include <vector>
using namespace std;
vector<int>* applicateVector()
{
return new (nothrow) vector<int>();
}
void readToVector(vector<int>* ivec)
{
if (nullptr == ivec) {
cout << "vector is illegal." << endl;
}
int num;
while (cin >> num) {
(*ivec).push_back(num);
}
}
void printVector(vector<int>* ivec)
{
if (nullptr == ivec) {
cout << "vector is illegal." << endl;
}
for (auto i : *ivec) {
cout << i << " ";
}
cout << endl;
}
int main(){
vector<int> *ivec = applicateVector();//动态分配的int的vector
if (nullptr == ivec) {
cout << "vector is illegal." << endl;
}
readToVector (ivec);//读取 即输入
printVector (ivec);//打印
delete ivec;
return 0;
}
测试:
12 12 10 5 3
^Z
12 12 10 5 3
练习题12.7
重做上一题,这次使用shared_ptr而不是内置指针。
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
shared_ptr<vector<int>> applicateVectorPtr()
{
return make_shared<vector<int>>();
}
void readToVectorPtr(shared_ptr<vector<int>> ivec)
{
if (!ivec) {
cout << "vector is illegal." << endl;
}
int num;
while (cin >> num) {
ivec->push_back(num);
}
}
void printVectorPtr(shared_ptr<vector<int>> ivec)
{
if (!ivec) {
cout << "vector is illegal." << endl;
}
for (auto i : *ivec) {
cout << i << " ";
}
cout << endl;
}
int main(){
shared_ptr<vector<int>> p;
p = applicateVectorPtr();
readToVectorPtr(p);
printVectorPtr(p);
return 0;
}
测试:
10 9 8 15 12
^Z
10 9 8 15 12
练习题12.8
下面的函数是否有错误?如果有,解释错误原因。
bool b() {
int *p = new int;
// …
return p;
}
函数定义的返回值类型与实际的返回类型不匹配。int* 会转换为bool类型。这会导致p将没有机会被delete,最终造成内存泄漏。
练习题12.9
解释下面代码执行的结果:
int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;
- 当
r = q
,则r原来的空间没有指针指向,也不会被释放,因此造成内存泄漏。 - 当
r2 = q2
, q2的引用计数+1,r2的引用计数-1,变为0,则r2原来指向的那块空间会被释放,不会造成内存泄漏。
练习题12.10
下面的代码调用了413页中定义的process函数,解释此调用是否正确,如果不正确,应如何修改?
shared_ptr<int> p (new int(42));
process (shared_ptr<int>(p));
此调用正确。
练习题12.11
如果我们像下面这样调用process,会发生什么?
process(shared_ptr(p.get()));
p.get()初始化返回一个内置指针,传给process的是由p.get()初始化的一个新的shared_ptr,与p指向同一块内存。在process函数结束时,新的shared_ptr被释放,p就变成了一个空悬指针。P414。
练习题12.12
p 和q的定义如下,对应接下来的对process的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:
auto p = new int();
auto sp = make_shared<int>();
(a ) process (sp);
合法,将一个shared_ptr传给process。
(b ) process(new int());
不合法,不能进行内置指针隐式转换为shared_ptr,P412。
(c ) process (p);
不合法,不能进行内置指针隐式转换为shared_ptr,P412。
(d ) process (shared ptr<int>(p));
合法。但不建议这样使用,智能指针和常量指针混用容易引起问题,比如有可能被释放两次,P413。
练习题12.13
如果执行下面的代码,会发生什么?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
使用sp初始化p,p和sp指向同一块内存。delete p之后,这块内存被释放,sp也会被释放,导致同一块内存被释放两次。P414。
练习题12.14
编写你自己版本的用shared_ptr管理connection的函数。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct connection {
string ip;
int port;
connection(string ip_, int port_):ip(ip_), port(port_){
}
};
struct destination {
string ip;
int port;
destination(string ip_, int port_):ip(ip_), port(port_){
}
};
connection connect(destination* pDest)
{
shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
cout << "creating connection(" << pConn.use_count() << ")" << endl;
return *pConn;
}
void disconnect(connection pConn)
{
cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << endl;
}
void end_connection(connection *pConn)
{
disconnect(*pConn);
}
void f(destination &d)
{
connection conn = connect(&d);
shared_ptr<connection> p(&conn, end_connection);
cout << "connecting now(" << p.use_count() << ")" << endl;
}
int main()
{
destination dest("202.192.01.02", 8888);
f(dest);
}
测试:
creating connection(1)
connecting now(1)
connection close(202.192.01.02:8888)
练习题12.15
重写第一题的程序,用lambda代替end_connection函数。
//其中f修改为:
void f(destination &d)
{
connection conn = connect(&d);
shared_ptr<connection> p(&conn, [](connection *p){
disconnect(*p); });
cout << "connecting now(" << p.use_count() << ")" << endl;
}
练习题12.16
如果你试图拷贝或赋值unique_ptr,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> p1 (new int(42));
unique_ptr<int> p2 = p1;
unique_ptr<int> p3 (p1);
return 0;
}
练习题12.17
下面的unique_ptr声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。
int ix = 1024,