侯捷C++八部曲笔记(四、C++2.0新特性)

侯捷C++八部曲笔记(四、C++2.0新特性)

  • 关键字
    • noexcept
    • override
    • final
    • decltype
    • =default, =delete
    • nullptr
    • auto
    • explicit
  • 可变参数:Variadic Templates
    • 初级语法
    • 更深入的了解(例子)
      • 例子:printf
      • 例子:maximun
      • 例子:tuple(头尾处理方式不同)
      • 例子:tuple(递归继承)
      • 例子:tuple(递归复合)
  • 模板表达式的空格
  • 一致性初始化:Uniform Initialization
  • 初始化列表:Initializer Lists
  • 范围for循环
  • 模板别名:Alias Template
    • 模板模板参数
  • 类型别名:Type Alias
  • lambdas表达式
  • ------------------------------------上面是新特性语法,下面是STL------------------------------------
  • 右值引用
  • 新增容器
    • array
    • forward list
    • unordered containers
  • 枚举类
  • C++14

C++2.0从2011年开始,所以又叫C++11.

如果编译器支持C++2.0版本的特性,就会有如下特性。

#define __cplusplus 201103L

关键字

noexcept

保证一个函数不会抛出异常:

void foo() noexcept;
void foo() noexcept(predicate); // 在条件predicate为真时,不抛出异常

异常一定要被处理,如果一个函数声明为noexcept就会把异常交给上一层调用。

你最好是告诉C++(特别是vector):你的移动构造函数和移动赋值构造函数是noexcept的。因为vector要增长空间,增长空间有构造函数调用过程,如果不显示声明移动构造时noexcept的,编译器不敢调用!

override

只应用到虚函数上面:让编译器检测自己重写的虚函数有没有写错,防止自己粗心。我觉得还有一个作用就是,在回看源码的时候,能够清楚知道一个函数是在重写父类的虚函数。

final

修饰类,表示被修饰的类不能够继承;
修饰虚函数,表示虚函数不能被重写。

decltype

主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。有点类似typeof。

// C++11
map<string, float> coll;
decltype(coll)::value_type elem;

// C++11之前
map<string, float> coll;
map<string, float>::value_type elem;

应用:

  1. 用于声明返回类型
// 希望如此
templat<typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y);

// 这么实现
templat<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y); //和lambdas表达式相似
  1. 元编程
// C++11
map<string, float> coll;
decltype(coll)::value_type elem;

// C++11之前
map<string, float> coll;
map<string, float>::value_type elem;
  1. 得到lambda表达式的类型
auto add = [](int x, int y){
	return x + y;
}
cout << decltype(add) << endl; //假设可以输出

=default, =delete

如果自行定义了一个构造函数,那么编译器就不会给你一个默认的构造函数;

如果你强行加一个 =default,就可以重新获得并使用编译器给出的默认构造函数。

=default只可以用于构造函数,=delete可用于任何函数,给编译器说明禁止使用这个函数。

一个空类真的是空的吗?显然不是。因为编译器会给一个空类添加上默认的构造函数、析构函数。

什么时候需要自己写一套构造函数呢?当一个类里面有指针成员变量的时候,是需要去自己写构造函数的。比如说,复数的成员变量只有两个double的实部虚部,无指针,可以不自己实现构造函数;string类有一个指向头部的指针,需要去实现。

class noncopyable{
public:
	noncopyable() = default;
	noncopyable(noncopyable&) = delete;
	noncopyable& operator=(noncopyable&) = delete;
	~noncopyable() = default;
}

nullptr

NULL默认值是0,在之前,如果想要一个指向NULL的指针,也可以让指针为0。引入nullptr来区分NULL和指针

void f(int);
void f(void*);
f(0); //调用f(int)
f(NULL); //调用f(int)
f(nullptr); //调用f(void*)

auto

引入auto,让编译器自己推断变量的类型。

确定变量的类型对于程序员很重要,在类型很长,而且自己很清楚具体是哪个类型的情况下才建议使用auto,比如说容器的迭代器。

auto i = 42; // auto = int
double f();
auto d = f(); // auto = double

auto l = [](int x) -> bool { // 不确定返回类型的情况下使用auto
	...
};

explicit

struct complex{
	int real, imag;
	// explicit
	complex(int re, int im = 0) : real(re), imag(im){}
	complex operator+(const complex& x){
		return complex((real + x.real), (imag + x.imag));
	}
}

11之前,用于一个参数构造函数。有上面的代码,那么在执行complex c1(12, 5); complex c2 = c1 + 5;的时候,构造函数不用explicit修饰,则会把5变成一个复数类,但是如果用explicit修饰了,这两句代码就会报错。也就是,让编译器知道,用户希望只在显式调用构造函数的时候才会调用,其他时候不允许调用。

11之后,explicit可用在接受一个以上参数构造函数。突破了一个参数的限制,阻止任意多个参数的构造函数的隐式调用。

class p {

public:
	p(int a, int b){
		cout << "p(int a, int b)" << endl;
	}

	explicit p(int a, int b, int c){
		cout << "expilict p(int a, int b, int c)" << endl;
	}
}

p p1(1, 2); //p(int a, int b)
p p2 = {1, 2, 3}; // 报错,因为initializer想要去调用用explicit修饰的构造函数,被编译器阻止

可变参数:Variadic Templates

初级语法

允许一个函数的参数可以是任意类型,任意个数。

函数在执行的时候,会将参数包解析为一个参数和剩余参数组成的参数包两个部分。

需要一个空函数用于处理在参数包解析到不包含参数的情况。

void printX(){

}

template <typename T, typename... Types>
void printX(const T& firstArg, const Types&... args){
	cout << firstArg << endl;
	printX(args...);
}

// 可以这么调用
print(7.5, "hello", biset<16>(337), 42);

// 想知道参数包中有几个参数,可以用sizeof...(args)
cout << sizeof...(args) << endl;

STL中HashCode、Tuple的实现就使用了Variadic Template:

图中1,2两个函数的关系就像模板的泛化与特化的关系,第2个函数是特化版本,在1函数中虽然从理论上讲可以递归调用自己,但是调用2函数更加匹配。

更深入的了解(例子)

变化的是:参数个数,参数类型。

例子:printf

例子:maximun

例子:tuple(头尾处理方式不同)

例子:tuple(递归继承)

例子:tuple(递归复合)

模板表达式的空格

vector<list<int> > vec; // 之前在两个尖括号之间必须要有空格,也报过这个错误
vector<list<int>> vec; // 11新特性,可以不加空格了

一致性初始化:Uniform Initialization

11之前,初始化方法包括:小括号、大括号、赋值号,这让人困惑。基于这个原因,给他来个统一,即,任何初始化都能够使用大括号来实现。实现的方法使用另外一个新特性:初始化列表。下一个特性介绍。

// 11之前
Rect r1 = {3, 7, 30};
Rect r2(3, 7, 20);
int ia[6] = {27, 89, 20};

// 11之后
int values[] {1, 2, 3};
vector<int> v {1, 2, 3};
complex<double> c {4.0, 3.0};

初始化列表:Initializer Lists

前面的一致性初始化中的大括号会形成一个初始化列表。具体实现代码如下:

侯捷C++八部曲笔记(四、C++2.0新特性)_第1张图片
侯捷C++八部曲笔记(四、C++2.0新特性)_第2张图片

也就是说:如果类中提供一个以initializer_list为参数的构造函数,那么使用{}来进行初始化的时候就直接调用他;如果不提供,那么就会将{}中的内容进行拆解,拆解为一个一个的参数,然后调用和{}中元素个数相同参数个数的构造函数。

范围for循环

for( auto decl : coll ){
	statement...
}

// 如果要改变元素内容的话
for( auto& decl : coll ){
	statement...
}

模板别名:Alias Template

别以为它只是让一个类换个名字,方便书写,它后面会引发一个大事情。

比如下面这个例子:

template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;

Vec<int> coll; 

那我们可以用#define或是typedef来达到同样的效果吗?

#define Vec<T> template<typename T> std::vector<T, MyAlloc<T>>;
Vec<int> coll; //------> template std::vector>; //这不是我们想要的

//如果使用typedef,因为typedef是不能够接受参数的,最多写成下面这样
typedef std::vector<int, MyAlloc<int>> Vec;

所以,using的优势就是能够指定参数

那这样做,只是为了减少代码量吗?如果有一个场景,需要以一个类型作为参数,而不是以一个类的对象作为参数呢?比如说下面这种情况:
侯捷C++八部曲笔记(四、C++2.0新特性)_第3张图片

Container是一种类型,所以上面的语句就会报错,找不到Container定义。有一种解决方案是牺牲方法的通用性,将Container和T进行组合变成一个参数传入,这种实现方法也不赖。那有没有更优雅的实现方案呢?程序员嘛,都是将就优雅的!

所以要进行下面的思考:有没有模板语法,能够在一个模板类中接受一个模板参数,在模板类中取出这个模板参数呢?有!模板模板参数

模板模板参数

template<typename T, template<class> class Container>
class XCIs{
private:
	Container<T> c;
public:
	XCIs(){
		for(long i = 0; i < size; ++i)
			c.insert(c.end(), T());
		...(上图中的实现)
	}
}

上述就是模板模板参数的使用。在引入模板模板参数之后,就可以这么使用:XCIs c1;完成我们的任务。但是!这一句又会报错,因为vector模板容器需要两个模板参数(类型和分配器)!这,不就需要using了吗?

template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;

XCIs<Mystring,  Vec> c1;

只能说,妙!

类型别名:Type Alias

类似于typedef,不过是借助using来实现:

typedef void(*func)(int, int); // 函数指针
				|
				|
				v
using func = void(*)(int, int);

和typedef的唯一区别是using多了一个模板别名(带参数)的功能。

using的所有用法

  1. using namespace std; //打开一个命名空间
    using std::count; //打开一个命名空间的一个组件
  2. using Base::_M_Prime; //打开一个类的一个成员
  3. 模板别名、类型别名

lambdas表达式

C++11引入lambdas表达式,允许定义一个匿名函数,这个匿名函数时inline的。可以定义在一些状态或者表达式中。下图中的auto就是一个functor的匿名类。lambdas无默认构造函数(所以在容器中指定sort方法的时候,需要调用该容器对应的lambda构造函数)。

[...] (...) [mutable throwSpec -> retType] {...}

[]开头的就是lambdas表达式,叫做introducer,用于捕获lambdas表达式外的变量用到具体方法内,可以指定传值方法。如果是=,则方法内可以以值传递的方式捕获lambdas外的所有变量

()放函数参数

mutable关系到[]中的内容是否可被改写,用在传值上(写上mutable说明[]中的值可以在{}中进行修改),可选

throwSpec指定是否抛出异常,可选

retType指定返回类型,可选

{}中放具体的方法实现

定义出来的是一个对象,需要()来调用

lambdas表达式妙用:

vector<int> vec {1,30,69,20,40,195,124};
int x = 20;
int y = 100;
vec.erase(
	remove_if(vec.begin(), vec.end(), [x, y](int n){return x , n && n < y;}),
	vec.end()	
); // 之前需要使用仿函数适配器才能完成这个任务

------------------------------------上面是新特性语法,下面是STL------------------------------------


右值引用

对容器的效率有大幅度改善,避免不必要的copy(当拷贝的来源端是一个右值,则接受端可以去偷(steal)这个右值的资源),perfect forward

左值:可以出现在operator=左侧
右值:只能出现在operator=右侧

临时对象是一个右值。拷贝构造是个深拷贝。

为什么放在标准库这一块谈呢?因为它和标准库密切相关。

int foo(){return 5;}

int x = foo();
int *p = &foo(); //不行!不能够取右值地址
foo() = 7; //不行

当右值出现于operator=的右侧时,我们认为其对资源进行搬移而非拷贝是可以的,合理的,那么:

  1. 必须要有语法让我们在调用端告诉编译器,这里是个右值(move)
  2. 必须有语法让我们在被调用端写出一个专门处理右值的move assignment函数(移动构造)

进行移动构造的时候,把新指针指向旧指针指向的地方,旧指针的指向会被取消掉,旧指针不能够再使用!所以,右值和移动构造配合得很好,因为右值以后也不会再使用了啊!如果本来是一个左值,可以把这个左值传入move函数作为参数,就是显式调用移动构造函数(move把左值变成了右值)。

写一个移动构造、移动赋值构造的例子:

MyString(MyString&& str) noexcept
	: _data(str._data), len(str._len){
	str._len = 0;
	str._data = NULL; //为什么不直接delete?因为要把工作交给析构函数
}

MyString& operator=(MyString&& str) noexcept{
	if(this != &str){
		_len = str._len;
		_data = str._data;
		str._len = 0;
		str._dtat = NULL;
	}
	return *this;
}

新增容器

因为前面讲STL的时候讲过了,就不做笔记了。

array

forward list

unordered containers

枚举类

https://blog.csdn.net/weixin_42817477/article/details/109029172

C++14

插播一点C++14的新特性:

  1. 函数的返回类型可以写成auto,即编译器会自动进行推导。函数内如果有多个返回语句,它们必须返回相同的类型,否则编译失败。
  2. 泛型lambada,即lambda表达式的参数可以是auto类型的
  3. [[deprecated]]弃用标记
  4. 二进制字面量和数位分隔符
  5. constexpr,只要保证返回值和参数是字面值就行了,函数体中可以加入更多的语句,方便了更灵活的计算。一旦以上任何元素被constexpr修饰,那么等于说是告诉编译器 “请大胆地将我看成编译时就能得出常量值的表达式去优化我”。

你可能感兴趣的:(侯捷C++八部曲笔记,c++,visual,studio,mfc)