天天看点

C++Primer第五版 第十三章习题答案(41~50)

41:就是前置先加后用,后置先用后加,first_free指向的是一个空位置,前置的话会跳过一个空位置。

42:本章所实现的StrVec类属于简化版本的容器类,只适用于string,运行时可动态分配内存的大小

43:使用for_each和lambda表达式可能会更好一点,无需循环,语义更加明显

void free()
	{
		if (elements)
		{
// 			for (auto p = first_free; p != elements; )
// 			{
// 				alloc.destroy(--p);
// 			}
			for_each(elements, first_free, [this](std::string &rhs){ alloc.destroy(&rhs); });
			alloc.deallocate(elements,cap-elements);
		}
	}
           

44:知识点1:const_cast<type_id> (expression)

该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

一、常量指针被转化成非常量的指针,并且仍然指向原来的对象;(本例中用到)

二、常量引用被转换成非常量的引用,并且仍然指向原来的对象;

三、const_cast一般用于修改底指针。如const char *p形式。

知识点2:别怕烦,动手写!

#ifndef STRVEC_H
#define STRVEC_H

#include <string>
#include <algorithm>
#include <memory>
using namespace std;

class String
{
public:
	String();//默认构造函数

	String(const char*s)//接受c风格字符串参数的构造函数,s为指向字符串的指针(首位置)
	{
		auto s1 = const_cast<char*>(s);//转化为非常量的指针
		while(*s1)
		{
			++s1;//使其指向最后一个位置的尾部
		}
		alloc_n_copy(s,s1);//进行拷贝
	}
	String(const String&);//拷贝构造函数
	String& operator=(const String&);//拷贝赋值运算符
	~String()//析构函数
	{
		free();
	}
	void free()//释放内存
	{
		if (elements)
		{
			for_each(elements,end,[this](char &rhs){alloc.destroy(&rhs);});
			alloc.deallocate(elements,end-elements);
		}
	}

private:
	allocator<char> alloc;//分配内存的方法
	char *elements;//首尾指针
	char *end;

	std::pair<char*, char*> alloc_n_copy(const char*a, const char*b)//拷贝赋值函数
	{
		auto s1 = alloc.allocate(b-a);//allocate参数为分配内存的大小
		auto s2 = uninitialized_copy(a,b,s1);//拷贝赋值,将a到b之间的元素拷贝至s1,返回的是最后一个构造元素之后的位置
		return make_pair(s1,s2);//返回首尾指针
	}

	void range_initializer(const char*c, const char*d)//初始化
	{
		auto p = alloc_n_copy(d,c);//拷贝并初始化新的string
		elements = p.first;
		end = p.second;//将新的string的首尾指针赋值
	}
};
#endif STRVEC_H
           

45:知识点1:新标准的特性:可以移动而非拷贝对象,可直接调用std::move(),移动而非拷贝会大幅度的提高性能:不必要进行旧内存到新内存的拷贝,当对象很大时省下很多的内存,另一个原因是像IO类和unique_ptr这样的类不可以进行拷贝,但是我们可以使用移动来共享其中的资源

知识点2:右值引用:必须绑定到右值的引用通过两个取址符号&&来获得右值引用:只绑定到一个将要销毁的对象,因此,我们可以自由的将右值引用的资源“移动”到另一个对象中

知识点3:左值和右值的区别:P121页

知识点4:一个右值引用本质上也只是一个对象的另外一个名字而已。对于常规的引用:称之为左值引用,我们不能将其绑定到所要转换的表达式,而右值引用可以绑定,见下例:

int a = 42;
int &p1 = a;//正确,是a的引用
int &&p2 = a;//错误,不能将一个右值绑定到一个左值上
int &p3 = a *42;//错误,a*42为右值,&iii为左值引用
const int &p4 = a*42;//正确,可以将一个右值绑定到一个const的引用上
int &&p5 = a*42;//正确,右值绑定
           

知识点5:由上例可知,左值持久,右值短暂,左值具有持久的状态,右值要么是字面值常量,要么是在表达式求值的过程中创建的临时对象(没有其他用户进行绑定且要被销毁):使用右值引用的代码可以自由的接管所引用的对象的资源

知识点6:变量是左值

int &&p6 = 42;//正确,字面值是右值
int &&p7 = p6;//错误,不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型
           

答案见知识点

46:

(a):f()为函数的返回值,临时值,属于右值,&&

(b):vi[0]为变量,属于左值,&

(c):r1为变量,属于左值,&

(d):右侧为表达式,属于右值,&&

47:知识点:可以通过一个名为move()的函数来获得绑定到左值上的右值引用,头文件为utility,move告诉编译器,我有一个左值,但是我想像一个右值一样处理它:除了赋值或者销毁,不能再使用它,使用move时应为std::move

String(const String& rhs)//拷贝构造函数
	{
		range_initializer(rhs.elements, rhs.end);
		cout << "拷贝构造函数" << endl;
	}
	String& operator=(const String& rhs)//拷贝赋值运算符
	{
		auto newstr = alloc_n_copy(rhs.elements, rhs.end);
		free();
		elements = newstr.first;
		end = newstr.second;
		cout << "拷贝赋值运算符" << endl;
		return *this;
	}
           

48:见47

#include <iostream> 
#include <vector>
#include "StrVec.h"
using namespace std;

int main(int argc, char**argv)  
{
	vector<String> vec;
	String s1("hello");
	String s2 = s1;
	vec.push_back(s1);
	vec.push_back(s2);
	return 0;
} 
           

49:知识点1:类的移动构造函数:参数为该类类型的右值引用,任何额外参数须有默认参数,一旦资源被移动,源对象对移动之后的资源已经不再有控制权,最后源对象会被销毁。注意,移动构造函数是“窃取”资源,并不会分配资源。

知识点2:移动操作不会报出任何异常,因为其不分配资源,所以我们需要告知标准库,以方便一起处理时多做一些额外的工作——加上noexpect(C++11新标准),在类的声明和定义是都需要指出noexpect

知识点3:不抛出异常的移动构造函数和移动赋值运算符必须标记为noexpect——解释见书本P474页

知识点4:移动后的源对象可能会在移动后被销毁,所以必须进入可析构的状态——将源对象的指针成员置为nullptr来实现

知识点5:当一个类为其自身定义了拷贝构造函数和拷贝赋值运算符或者析构函数,且其所有数据成员皆可移动构造时,那么编译器将不会再为该类合成移动构造函数和移动赋值运算符

知识点6:移动右值、拷贝左值、没有移动构造函数时,右值也会被拷贝

知识点7:所有五个拷贝控制成员应该在类中一起出现!

String& String::operator=(String&& rhs) NOEXCEPT
{
	if (this != &rhs) {
		free();
		elements = rhs.elements;
		end = rhs.end;
		rhs.elements = rhs.end = nullptr;//将源对象置为可析构的状态
	}
	return *this;
}
String::String(String&& s) NOEXCEPT : elements(s.elements), end(s.end)
{
	s.elements = s.end = nullptr;//将源对象置为可析构的状态
}
           

50:

String baz()
{
    String ret("world");
    return ret; // 返回值会避免拷贝
}

String s5 = baz(); // 右值会避免拷贝