《C++中文版Primer 第五版之C++11的新特性》

c++11新特性总结

1. long long 类型

c++11新增加了long long 类型,long long 在x86平台下为64位。

2. 列表初始化

在c++11新标准中,增加了使用列表初始化来初始化变量,即用花括号{}来初始化变量。

int value = 0;
int value = {0};
int value(0);

int vaue{0};    //c++11新特性
list<string> authors {"Milton","Austen","Shakespeare"}; //容器列表初始化

当用于内置类型的变量时,如果我们使用初始化列表初始化且初始化值存在丢失信息的风险,则编译器将报错。

long double ld = 3.1415926536
int a{ld}, b = {ld};   //错误:转换未执行,因为存在丢失信息的危险
int c(ld), d = ld;    // 成功:转换执行,且确实丢失了部分值

3. nullptr常量

空指针: 不指向任何对象的指针。下列是几种生成空指针的方法:

int *p1 = 0;          //直接将p1初始化为字面常量0

//需要#include <stdlib.h>
int *p2 = NULL;      //等价于 int *p2 = 0;

int *p3 = nullptr;   //c++11新特性,等价于int *p3 = 0;

nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。新标准下,C++程序员最好使用nullptr,尽量避免使用NULL。使用未初始化的指针是引发运行时错误的一大原因,如果实在不清楚指针应该指向何处,就把它初始化为nullptr或者0。

4. constexpr变量

c++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。

声明为constexpr的变量一定是一个常量,而且必须将常量表达式初始化。

constexpr int mf = 20;             //20是常量表达式
constexpr  int limit = mf + 1;     //mf + 1 是常量表达式
constexpr int sz = size();           //只有当size是一个constexpr函数时才是一条正确的声明语句

注意明确一点:在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。

const int *p = nullptr;    //p是一个指向 整型常量 的 指针
constexpr int *q = nullptr;  //q是一个指向 整数 的 常量指针

和其他常量指针类似,constexpr指针即可指向常量也可以指向一个非常量:

int j = 0;
constexpr int i = 42;  // i的类型是整型常量
// i 和 j 都必须定义在函数体之外
constexpr const int *p = &i; // p 是常量指针,指向整型常量i
constexpr int *p1 = &j; // p1是常量指针,指向整数j

5. 类型别名声明

类型别名是一个名字,它是某种类型的同义词。
有两种方法可用于定义类型别名:

1.传统方法:使用关键字  typedef

typedef double wages; //wages 是 double 的同义词
typedef wages base, *p; //base 是double的同义词,p是double* 的同义词

wages hourly,weekly; //等价于double hourly,weekly;

2.C++11新标准:使用 别名声明,关键字 using

using SI = Sales_item; //SI是Sales_item的同义词
SI item; //等价于Sales_item item;

6. auto类型指示符

C++11引入了auto 类型说明符,用它可以让编译器替我们去分析表达所属的类型。auto定义的变量必须有初始值。

//由val1和val2相加的结果可以推断出item的类型
  auto item = val1 + val2;
  
//使用auto可以在一条语句中声明多个变量
auto i = 0, *p = &i;     //正确:i是整数,p是整型指针
auto sz = 0,pi = 3.14;  // 错误:sz和pi的类型不一致

//auto 一般会忽略掉顶层的const
int i = 0;
const int ci = i, &pr = ci;
auto b = ci;   // b是一个整数(ci的顶层const特性被忽略掉了)
auto  c = &i ; //c是一个整型指针(整数的地址就是指向整数的指针)
auto d = &ci;  //d是一个指向整数常量的指针(对常量对象取地址是一种底层的const)

//如果希望推断出的auto类型是一个顶层const,需要明确指出:
const auto f = ci; //ci的推演类型是int,f是const int

7.decltype类型指示符

有时候我们希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。为了满足这一要求,c++11标准引入了第二种类型说明符decltype作用是选择并返回操作数的数据类型

decltype( fun() ) sum = x;   //sum的类型为函数fun的返回类型

decltype处理顶层的const和引用的方式与auto有点不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内):

const int ci = 0, &cj = ci;
decltype( ci ) x = 0; //x的类型是const int
decltype( cj ) y = x; //y的类型是const int& ,y绑定到变量x
decltype( cj ) z;  //错误:z是一个引用,必须初始化

decltype和auto的另一个重要区别是:decltype的结果类型与表达式形式密切相关。对于decltype所用的表达式来说,如果变量名加了一对括号,则得到的类型与不加括号时会有不同。

//decltype的表达式如果是加上了括号的变量,结果将是引用
decltype( (i) ) d; //错误:d是int& ,必须初始化
decltype(i) e;    // 正确,e是一个(未初始化)int

切记:decltype( (variable) ) (注意是双括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用。

8. 类内初始化

C++11新标准规定,可以为数据成员提供一个类内初始化值。对类内初始值的限制:或者放在花括号里,或者放在等号右边,不能使用圆括号。

class Sales_data{
public:
	Sales_data() = default;
private:
	unsigned units_sold = 0;   //类内初始化
	double revenue = 0;       //类内初始化
};

9. 范围for语句

C++11提供了基于范围的for语句,语法形式如下:

for (declaration : expression)
	statement
例子:
string str("some");
for( auto c : str )    //对于str中的每个字符
	cout << c << endl;  // 输出当前字符,后面紧跟一个换行符
	
输出结果:
				  s
				  o
				  m
				  e
				  
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
for(auto &r: v)
	r *= 2;       //将v中每个元素的值翻倍

10.vector对象的列表初始化

C++11新标准提供了为vector对象的元素赋初值的另一种方法,列表初始化即用花括号括起来的0个或多个初始元素赋值给vector对象。

vector<string> articles = {"a", "an", "the"}; //传统vector初始化
vector<string> v1{"a", "an", "the"};   //c++11列表初始化
vector<string> v2("a", "an", "the");   //错误,应用花括号

11.标准库的begin和end函数

尽管能计算得到尾后指针,但这种方法很容易出错。为了让指针的使用更简单、更安全,c++11新标准引入了两个名为begin和end的函数,这两个函数定义在iterator头文件中。使用形式是将数组作为它们的参数。

int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(arr);   //指向arr首元素的指针
int *last= end(arr);    //指向arr尾元素的下一个位置的指针

//计算数组的元素数量
auto n = end(arr) - begin(arr);   //n 为10

//找出第一个大于5的数
while(beg != last && *beg > 5)
	++beg;

12. 将sizeof用于类成员

C++11新标准允许我们使用作用域运算符来获取类成员大小。通常情况下只有通过类的对象才能访问到类的对象成员,但是sizeof运算符无须我们提供一个具体的对象,因为要想知道类成员的大小无须真的获取该成员。

Sales_data data;
sizeof data.revenue;             //Sales_data的revenue成员对应类型的大小
sizeof Sales_data::revenue;     //另一种获取revenue大小的方式

13. 标准库initializer_list类

如果函数的实参数量未知但是全部实参的类型都相同,可以使用initializer_list类型的形参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。

initializer_list提供的操作
initializer_list<T> lst; 默认初始化; T类型元素的空列表
initializer_list<T> lst{a,b,c}; lst的元素数量和初始值一样多,列表的元素是const
lst2(lst) 或 lst2 = lst 拷贝或赋值一个initializer_list对象,拷贝后,原始列表和副本共享元素
lst.size() 列表中元素的数量
lst.begin() 返回指向lst中首元素的指针
lst.end() 返回指向lst中尾元素的下一位置的指针

注意:与vector不同,initializer_list对象中的元素永远是常量。

void error_msg(initializer_list<string> il)
{
	for(auto beg = il.begin(); beg != il.end(); ++beg)
		cout << *beg << " ";
	cout << endl;
}

如果想向initializer_list形参中传递一个值的序列,必须把序列放在一对花括号内:

//expexced 和actual是string对象
if(expected != actual)
	error_msg({"functionX", expected, actual});
else
	error_msg({"functionX","okay"});

14.列表初始化返回值

C++11新标准规定,函数可以返回花括号包围的值的列表。

vector<string> process()
{
	//expexced 和actual是string对象
	if(expected.empty())
		return {};    //返回一个空vector对象
	else if(expected == actual)'
		return {"functionX", "okay"};  //返回列表初始化的vector对象
	else
		return {"functionX", expected, actual};
}

15.定义尾置返回类型

尾置返回类型跟在形参列表后面以一个 -> 符号开头。

auto a = []()->int{
	int i = 1;
	return i;
};  //返回int类型

16. 使用 =default 生成默认构造函数

C++11新标准中,如果我们需要默认的行为,可以在参数列表后面写上 =default来要求编译器生成构造函数。如果 =default在类的内部,则默认构造函数是内联;在类的外部,则该成员默认情况下不是内联的

struct Sales_data{
	Sales_data() = default; //默认构造函数
};

17.委托构造函数

委托构造函数使用其它所属类的其他构造函数执行它自己的初始化过程。

class Sales_data{
public:
	//非委托构造函数使用对应的实参初始化成员
	Sales_data(string s, unsigned cnt, double price):
		bookNo(s),units_sold(cnt),revenue(cn*price){ }
		
	//其余构造函数全部委托给另一个构造函数
	Sales_data():Sales_data("", 0, 0){ };
	Sales_data(string s):Sales_dtat(s, 0, 0){ }
private:
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};

18. array和forward_list容器

forward_list 单向链表。只支持单向顺序访问。在链表的任何位置进行插入/删除操作速度都很快
array 固定数组大小。支持快速随机访问,不能添加和删除元素

技巧: 通常使用vector是最好的选择,除非有很好的理由选择其他容器。

19.容器的emplace成员

新标准引入了三个新成员–emplace_front、emplace和emplace_back。这些操作构造而不是拷贝元素。这些操作分别对应push_front、insert和push_back,即允许我们将元素放置在容器头部、一个指定位置之前或容器尾部。
调用push或者insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数

//在c的末尾构造一个Sales_data对象
//使用三个参数的Sales_data构造函数
c.emplace_back("Jack",25,15.99);  

//错误,没有接受三个参数的push_back版本
c.push_back("Jack",25,15.99);

//正确,创建一个临时的Sales_data对象传递给push_back
c.push_back(Sales_data("Jack",25,15.99));

在调用emplace_back时,会在容器管理的内存空间中直接创建对象。而调用push_back则会创建一个局部临时对象,并将其压入容器中。

20. shrink_to_fit

新标准库中,可以调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间。即将容器的capacity()大小减少为与size()相同大小。但是调用shrink_to_fit并不保证一定退回内存空间。

21. lambda表达式

一个lambda表达式可以表示一个可调用的代码单元,可以理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式:

[capture list] (parameter list) -> return type {function body}

capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表;return type、parameter list 和function body与任何函数一样,分别表示返回类型、参数列表和函数体。但是,与普通函数不同,lambda必须使用尾置返回来指定返回类型。

//可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体
auto f = [ ] {return 42;};
//lambda调用方式与普通函数调用方式相同
cout << f() << endl;  //打印42

//向lambda传递参数
//空捕获列表表明此lambda不使用它所在函数的任何局部变量
[](const string &a,const string &b)
{
	return a.size() > b.size();
}

//按长度排序,vec为存放string类型的vector
stable_sort(vec.begin(),vec.end(),[](const string &a,const string &b)
{
	return a.size() > b.size();
});

lambda采用值捕获的前提是变量可以拷贝。 与参数不同的是,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。

//按值捕获
void fun1()
{
	size_t v1 = 42; //局部变量
	//将v1拷贝到名为f的可调用对象
	auto f = [v1] {return v1; };
	v1 = 0;
	auto j = fun(); //j为42;f保存了我们创建它时v1的拷贝
}

由于被捕获的变量的值是在lambda创建时拷贝,因此随后对其的修改不会影响到lambda内对应的值。

lambda采用引用方式捕获一个变量,必须确保被引用的对象在lambda执行的时候是存在。

//引用捕获
void fun2()
{
	size_t v1 = 42; //局部变量
	//对象f2包含v1的引用
	auto f2 = [&v1] {return v1;};
	v1 = 0;
	auto j = f2(); //j为0;f2保存v1的引用,而非拷贝
}

lambda还有一种捕获方式,隐式捕获。在捕获列表中写一个 & 或者 = 。&告诉编译器采用引用捕获方式,=则表示采用值捕获方式。

//混合使用隐式捕获和显示捕获时,捕获列表的第一个元素必须是 & 或 =
void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ')
{
	//os隐式捕获,引用捕获方式;c显示捕获,值捕获方式;其他变量按引用捕获方式
	for_each(words.begin(), words.end(), [&, c](const string &s){
		os << s << c;
	});
	//os显示捕获,引用捕获方式;c隐式捕获,值捕获方式;其他变量按值捕获方式
	for_each(words.begin(), words.end(), [=, &os] (const string &s){
	os << s << c;
	});
}
//指定lambda的返回类型,必须使用尾置返回类型
transform(vi.begin(), vi.end(), vi.begin(),[ ](int i) -> int)
{
	if(i < 0)
		 return -i;
	else 
		return i;
});

22.标准库bind函数

c++11 bind标准库函数,定义在头文件functional中。bind接受一个可调用对象,生成一个新的可调用对象来适配原对象的参数列表。

调用bind的一般形式为:
auto newCallable = bind(callable,arg_list);

newCallable 为一个可调用的对象,arg_list是一个逗号分隔的参数列表,也就是给callable的参数。调用newCallable 时,newCallable 会调用callable,并传递给它arg_list中的参数。
arg_list的参数也可能包含形如 _n 的名字,其中n为一个整数,这些参数是“占位符”,表示newCallable 的参数,在命名空间std::placeholders中。例如:_1为newCallable 的第一个参数,_2为第二个参数,依此类推。

/*checkTest是一个可调用对象,接收一个string类型的参数,
并用string和值6来调用check_size*/
using std::placeholders::_1;
auto checkTest = bind(check_size, _1, 6);

std::string s = "hello world";
bool b1 = checkTest(s);  //checkTest会调用check—_size(s, 6)

此bind调用只有一个占位符,表示checkTest只接受单一参数。占位符出现在arg_list的第一个位置。

ostream &print(ostream &os, const string &s, char c)
{
	return os << s << c;
}

//错误的做法,不能直接用bind来代替对os的捕获
for_each(words.begin(),words.end(),bind(print, os, _1, ' '));

//正确做法如下
for_each(words.begin(),words.end(),bind(print, std::ref(os), _1, ' '))

原因在于bind拷贝其参数,而我们不能拷贝一个ostream。如果我们希望传递给bind一个对象而不拷贝它,就必须得用标准库 ref 函数。ref 定义在functional头文件中。

23. 关联容器的列表初始化

定义一个map时,必须既指明关键字类型又指明值类型;而定义一个set时,只需指明关键字类型。在新标准下,我们可以对关联容器进行值初始化。

map<string, size_t> word_count; //空容器
//列表初始化
set<string> exclude = {"the", "but", "and", "or"};

map<string, string> authors = {{"Joyce","James"}, {"Austen","Janes"}};

mapset的关键字是唯一的。而multimapmultiset允许多个元素具有相同的关键字。

vector<int> ivec;
for(vector<int>::size_type i = 0; i != 10; ++i)
{
	ivec.push_back(i);
	ivec.push_back(i); //每个数重复保存一次
}

set<int> iset(ivec.cbegin(), ivec.cend());
multiset<int> mset(ivec.cbegin(), ivec.cend());

cout << ivec.size() << end;    //20
cout << iset.size() << end;    //10
cout << mset.size() << end;    //20

24. pair的列表初始化

新标准下,创建一个pair最简单的方法是在参数列表中使用花括号初始化。也可以调用make_pair显示构造pair

//向word_count插入word的4种方法
map<string, int> word_count;
string word = "hello";

word_count.insert((word, 1));
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string,size_t>::value_type(word, 1));

25. 无序容器

新标准定义了4个无序关联容器(unordered_map, unordered_multimap, unordered_set, unordered_multiset)。这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。
在关键字的元素没有明显的序关系的情况下,无序容器是非常有用的。无序容器提供了与有序容器相同的操作。

无序容器的管理操作
c.bucket_count() 正在使用的桶的数目
c.max_bucket_count() 容器能容纳的最多的桶的数量
c.bucket_size(n) 第n个桶中有多少个元素
c.bucket(k) 关键字为k的元素在哪个桶里面
local_iterator 可以用来访问桶中的迭代器类型
const_local_iterator 桶迭代器的const版本
c.begin(n), c.end(n) 桶n的首元素迭代器和尾后迭代器
c.load_factor() 每个桶的平均元素数量,返回float类型的值
c.max_load_factor() c试图维护的平均桶大小,返回float值。c会在需要时添加新的桶,使得load_factor <= max_load_factor
c.rehash(n) 重组存储,使得bucket_count >= n ,且bucket_count > size/max_load_factor
c.reserve(n) 重组存储,使得c可以保存n个元素且不必rehash

26.动态内存与智能指针

c++中,动态内存管理通过newdelete来完成。new在动态中为对象分配空间并返回一个指向该对象的指针,delete接收一个动态对象指针,销毁该对象,释放内存。
动态内存的使用很容易出现问题。有时候,我们会忘记释放内存,从而产生内存泄露;有时候在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。
为了更容易和更安全的使用动态内存,新的标准库提供了智能指针。shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象;weak_ptr为弱引用,指向一个shared_ptr管理的对象,而不会改变shared_ptr的引用计数,解决shared_ptr互指的问题。这三种类型都定义在memory头文件中。

  1. shared_ptr类

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。make_shared也是定义在memeory头文件中。

//str 指向一个值为"9999999999"的string
shared_ptr<string> str = make_shared<string>(10, '9');
//p指向一个值初始化的int,值为0
shared_ptr<int> p = make_shared<int>();
//p1指向一个值初始化的int,值为20
shared_ptr p1 = make_shared<int>(20);

如果将shared_ptr存放于一个容器中,而后不再需要全部元素,只使用其中的一部分,要记得用erase删除不再需要的那些元素。

shared_ptr和unique_ptr都支持的操作
shared_ptr< T > sp 空智能指针,可以指向类型为T的对象
unique_ptr< T > up 空智能指针,可以指向类型为T的对象
p 将p用作一个条件判断,若p指向一个对象,则为true
*p 解引用p,获得它指向的对象
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针。要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了
swap(p, q) 交换p和q中的指针
p.swap(q) 交换p和q中的指针
shared_ptr独有的操作
make_shared< T > (args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化对象
shared_ptr< T > p(q) p是shared_ptr的拷贝;此操作会递增q的计数器。q中的指针必须能转换为T*
p = q p和q都是shared_ptr,所保存的指针都必须能相互转换。此此操作会递减p的引用计数,递增q的引用计数。若p的引用计数为0,则将其管理的原内存释放
p.unique() 若p.use_count()为1,返回true;否则返回false
p.use_count() 返回与p共享对象的智能指针数量;可能很慢,主要用于调试
定义和改变shared_ptr的方法
shared_ptr< T > p(q) p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型
shared_ptr< T > p(q, d) p接管了内置指针q所指向的对象的所有权。q必须能转换成T*类型。p将使用可调用对象d来替代delete
p.reset() 若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空。若还传递了参数d,将会调用d而不是delete来释放q
p.reset(q)
p.reset(q, d)
  1. unique_ptr类

与shared_ptr不同,unique_ptr没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。初始化unique_ptr必须采用直接初始化形式:

unique_ptr<double> p1;
unique_ptr<int> p2(new int(42)); //p2指向一个值为42的int

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

unique_ptr<string> p1(new string("hello")); //正确,直接初始化
unique_ptr<string> p2(p1); //错误:unique_ptr不支持拷贝
unique_ptr<string> p3;
p3 = p2; //错误:unique_ptr不支持赋值
unique_ptr操作
unique_ptr< T > u1 空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针。
unique_ptr< T, D > u2 u2会使用一个类型为D的可调用对象来释放它的指针
u = nullptr 释放u指向的对象,将u置为空
u.release() u放弃对指针的控制,返回指针,并将u置为空
u.reset() 释放u指向的对象
u.reset(q) 如果提供了内置指针q,令u指向这个对象;否则将u置为空
u.reset(nullptr) 将u置为空

虽然不能拷贝或赋值unique_ptr,但可以通过release或reset将指针的所有权从一个(非const)unique_ptr转移到另一个unique_ptr。

//将所有权从p1转移给p2
unique_ptr<string> p2(p1.release()); //release将p1置为空
unique_ptr<string> p3(new string("hello"));
//将所有权从p3转移给p2
p2.reset(p3.release()); //reset释放了p2原来指向的内存

release成员返回unique_ptr当前保存的指针并将其置为空。因此,p2被初始化为p1原来保存的指针,而p1被置为空。
调用release会切断unique_ptr和它原来管理的对象间的联系。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值,如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:

p2.release(); //错误:p2不会释放内存,而且我们丢失了指针
auto p = p2.release(); //正确,但我们必须记得delete(p)
  1. weak_ptr类

weak_ptr是一种不控制所指向对象生存周期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr所指向的对象,对象也还是会被释放。

weak_ptr 操作
weak_ptr < T > w 空weak_ptr 可以指向类型为T的对象
weak_ptr < T> w(sp) 与shared_ptr指向相同对象的weak_ptr 。T必须能转换为sp指向的类型
w = p p可以是一个shared_ptr或一个weak_ptr 。赋值后w与p共享对象
w.reset() 将w置为空
w.use_count() 与w共享对象的shared_ptr的数量
w.expired() 若w.use_count()为0,返回true,否则返回false
w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

创建一个weak_ptr,要用一个shared_ptr来初始化它:

auto p =make_shared<int>(42);
weak_ptr<int> wp(p); //wp弱共享p;p的引用计数未改变

由于对象可能不存在,我们不能直接使用weak_ptr访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。存在,lock返回一个指向共享对象的shared_ptr。

if(shared_ptr<int> np = wp.lock())  //如果np不为空则条件成立
{

}

27.将=default用于拷贝控制成员

class Sales_data{
public:
	//拷贝控制成员;使用default
	Sales_data() = default;
	Sales_data(const Sales_data&) = default;
	Sales_data& operator = (const Sales_data &);
	~Sales_data() = default;
};

Sales_data& Sales_data::operator=(const Sales_data&) = default;

当我们在类内使用=default修饰成员的声明时,合成的函数将隐式地声明为内联的。如果不希望合成的成员是内联函数,应该对成员的类外定义使用=default。

28.使用=delete阻止拷贝类对象

新标准发布之前,类是通过将其拷贝构造函数和拷贝赋值函数运算符声明为private的来阻止拷贝。而在新标准下,我们可以将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝
删除函数,就是我们虽然声明了它们,但不能以任何方式使用它们。在函数的参数列表后面加上 =delete指出我们希望将它定义为删除的。

struct NoCopy{
	NoCopy() = default;  //使用过合成的默认构造函数
	NoCopy(const NoCopy&) = delete;   //阻止拷贝
	NoCopy &operator =(const NoCopy&) = delete; //阻止赋值
	~NoCopy() = default;  //使用合成的析构函数
};

注意:析构函数不能是删除的成员。因为如果析构函数被删除,就无法销毁此类型的对象了。

29.右值引用

为了支持移动操作,新标准引入了一种新的引用类型——右值引用右值引用,就是必须绑定到右值的引用,通过&&而不是&来获得右值引用。
一般而言,左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值。

int i = 42;
int &r = i;                 //正确:r引用i
int &&rr = i;              //错误:不能将一个右值引用绑定到一个左值上
int &r2 = i * 42;         //错误:i * 42是一个右值
const int &r3 = i * 42;  //正确:我们可以将一个const的引用绑定到一个右值上
int &rr2 = i *42;       //正确:将rr2绑定到乘法结果上

变量是左值,不能将一个右值引用直接绑定到一个变量上,即使这个变量时右值引用类型也不行。

int &&rr1 = 42;   //正确:字面常量是右值
int &&rr2 = rr1;  //错误:表达式rr1是左值

虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显示地将一个左值转换为对应的右值引用类型。通过调用一个名为move的新标准函数来获得绑定到左值上的右值引用,该函数定义在utility中。

int &&rr3 = std::move(rr1);  //正确

30.移动构造函数和移动赋值运算符

为了让我们自己的类型支持移动操作,需要为其定义移动构造函数和移动赋值运算符。除了完成资源移动,移动构造函数还必须确保移动后源对象处于这样一个状态——销毁它是无害的。一旦资源完成移动,源对象必须不再指向被移动的资源。

StrVec::StrVec(StrVec && s) noexcept   //移动操作不应抛出任何异常
:elements(s.elements),first_free(s.first_free),cap(s.cap)
{
	//令s进入这样的状态——对其运行析构函数是安全的
	s.elements = s.first_free = s.cap = nullptr;
}

如果没有移动构造函数,右值也会被拷贝。

与拷贝构造函数不同,移动构造函数不分配任何新内存。它接管给定的StrVec中的内存。在接管内存之后,将给定对象中的指针都置为nullptr。这样就完成了从给定对象的移动操作,此对象将继续存在。

我们编写一个移动操作时,确保移动后源对象进入一个可析构的状态。

StrVec &StrVec::operator = (StrVec && rhs) noexcept
{
	if(this != &rhs)
	{
		elements = rhs.elements; 
		first_free = rhs.first_free;
		cap = rhs.cap;
		rhs.elements  = rhs.first_free = rhs.cap = nullptr;
	}
	return *this;
}

建议:不要随意使用移动操作,因为移动后源对象具有不确定状态,对其调用std::move是危险的。当我们调用move时,必须绝对确认移动后源对象没有其他用户。

31.function类模板

function定义在functional头文件中。

function的操作
function< T > f f是一个用来存储可调用对象的空function,这些课调用对象的调用形式应该与函数类型T相同
function< T > f(nullptr) 显示地构造一个空function
function< T > f(obj) 在f中存储可调用对象obj的副本
f 将f作为条件:当f含有一个可调用对象时为真;否则为假
f(args) 调用f中的对象,参数是args
定义为function< T >的成员的类型
result_type 该function类型的可调用对象返回的类型
argument_type 当T有一个或两个实参时定义的类型。如果T只有一个实参,则argument_type是该类型的同义词。
first_argument_type second_argument_type : T有两个实参,则first_argument_type和second_argument_type分别代表两个实参的类型
int add(int i, int j){return i + j;}
struct divide{
	int operator()(int denominator, int divisor)
	{
		return denominator / divisor;
	}
};

function<int(int, int)> f1 =add;  //函数指针
function<int(int, int)> f2 = devide(); //函数对象类的对象
function<int(int, int)> f3 = [] (int i, int j){return i * j;};  //lambda

cout << f1(4,2) << endl; //打印6
cout << f2(4,2) << endl; //打印2
cout << f3(4,2) << endl; //打印8

32.explicit类型转换运算符

class SmallInt
{
public:
	//编译器不会自动执行这一类型转换
	explicit operator int() const{ return val; }
};

SmallInt si = 3; //正确:SmallInt的构造函数不是显式的
si + 3; //错误:此处需要隐式的类型转换,但类的运算符是显式的
static_cast<int> (si) + 3; //正确:显式地请求类型转换

33.虚函数的override指示符

C++11 中的 override 关键字,可以显式的声明派生类的虚函数,如果我们使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错。

class Base {
public:
    virtual void f1() const;
    virtual void f2(unsigned  int x);
};

class Derived: public Base {
public:
    virtual void f1() override;
    virtual void f2(unsigned int x) override;
};

这样,即使不小心漏写了虚函数重写的某个苛刻条件,也可以通过编译器的报错,快速改正错误。

34.通过定义类为final来阻止继承

有时候我们会定义这样一种类,我们不希望其他类继承它,或者不想考虑它是否适合作为一个基类。所以,c++11新标准提供了一种防止继承发生的方法,即在类名后跟一个关键字 final。之后任何尝试覆盖该函数的操作都将引发错误。

class Base final
{
};
 
// 错误,Derive不能从Base派生。
class Derive: public Base
{
};

class Base2
{
public:
	virtual void Fun() final{}
};
 
class Derive2: public Base2
{
public:
    // 错误,不能覆盖基类的函数。
    virtual void Fun() override{ }
};

35.模板类型别名

由于模板不是一个类型,我们不能定义一个typedef引用一个模板。即,无法定义一个typedef引用的Blob< T >。但是,新标准允许我们为类模板定义一个类型别名。

template<typename T> using twin = pair<T, T>;
twin<string> authors; // author 是一个pair<string, string> 

36.尾置返回类型与类型转换

template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
	return *beg; //返回序列中一个元素的引用
}

此例中通知编译器fcn的返回类型与解引用beg参数的结果类型相同。

37.tuple类型

tuple是类似pair的模板。每个pair的成员类型都不相同,但每个pair都恰好有两个成员。不同的tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员。定义在tuple头文件中。

tuple<size_t, size_t, size_t> threeD; //三个成员的设置都为0
tuple<string, vector<double>, int, list<int>> someVal("hello",{3.14,2.718},42,{0,1,2,3,4,5})

tuple的构造函数是explicit的,必须采用直接初始化语法。

tuple<size_t, size_t, size_t> threeD = {1, 2, 3}; //错误
tuple<size_t, size_t, size_t> threeD{1, 2, 3}; //正确
//也可以使用make_pair生成对象
auto item = make_tuple{"0-999-789-X", 3, 20.00};

要访问一个tuple成员。就要使用一个名为get的标准库函数模板。

auto book = get<0> (item);  //返回item的第一个成员
auto book = get<1> (item)  ;//返回item的第二个成员
auto book = get<2> (item);  //返回item的第三个成员
get<2>(item) *= 0.8;  //打折20%

typedef decltype(item) trans; //trans是item类型
//返回trans类型对象中成员的数量
size_t sz = tuple_size<trans>::value; //返回3
//cnt的类型与item中第二成员相同
tuple_element<1, trans>::type cnt = get<1>(item); //cnt是一个int

38.有作用域的enum

枚举属于字面值常量类型。c++有两种枚举:限定作用域的和不限定作用域的。c++11标准库引入了限定作用域的枚举类型。形式:关键字enum class(或者等价的使用enum struct),随后是枚举类型名字以及用花括号括起来的以逗号分隔的枚举成员,最后是一个分号。

//限定作用域的枚举类型
enum class open_modes{
	intput,
	output,
	append
};

//不限定作用域的枚举类型
enum color{
	red,
	yellow,
	green
};

在限定作用域的枚举类型中,枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不可访问的。而在不限定作用域的枚举类型中,枚举成员的作用域与类型本身的作用域相同。

enum color {red, yellow, green};   //不限定作用域的枚举类型
enum stoplight{red, yellow, green}; //错误:重复定义了枚举成员
enum class peppers{red, yellow, green}; //正确:枚举成员被隐藏了

color eyes = green; //正确:不限定作用域的枚举类型的枚举成员位于有效的作用域中
peppers p = green; //错误:peppers的枚举成员不在有效的作用域中 corlor::color

color hair = color::red; //正确:允许显示的访问枚举成员
peppers p2 = peppers::red; //正确:使用peppers的red

enum是由某种类型表示的。在c++11中,可以在enum的名字后面加上冒号以及我们想在enum中使用的类型。

enum intValues: unsigned long long{
	charType = 255, shortType = 65535, intType = 65535,
	longType = 4294967295UL,
	long_longType = 1844865612314567UL
};

默认情况下,限定作用域的enum成员类型是int。对于不限定作用域的枚举类型,其枚举成员不存在默认类型,我们只知道成员的潜在类型足够大,肯定能够容纳枚举值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值