C++笔记——第十五篇 C++11来了 (上)


目录

1. C++11简介

2. 列表初始化

2.1 C++98中{}的初始化问题

2.2 内置类型的列表初始化 

2.3 自定义类型的列表初始化

1. 标准库支持单个对象的列表初始化 

2. 多个对象的列表初始化

扫描二维码关注公众号,回复: 15230316 查看本文章

3. 变量类型推导

3.1 为什么需要类型推导

3.2 decltype类型推导

3.2.1 为什么需要decltype

3.2.2 decltype

4.默认成员函数控制

4.1 显式缺省函数

4.2 删除默认函数

5.右值引用

5.1 右值引用概念

5.2 左值与右值

5.3移动语义

5.4 右值引用引用左值

6.lambda表达式

7.线程库

thread类的简单介绍



1. C++11简介



C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅
功能更强大,而且能提升程序员的开发效率。



2. 列表初始化




2.1 C++98中{}的初始化问题



在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:

int array1[] = {1,2,3,4,5};
int array2[5] = {0};

自定义的类型,却无法使用这样的初始化。

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。


2.2 内置类型的列表初始化 


int main()
{
	// 内置类型变量
	int x1 = { 10 };
	int x2{ 10 };
	int x3 = 1 + 2;
	int x4 = { 1 + 2 };
	int x5{ 1 + 2 };
	// 数组
	int arr1[5]{ 1,2,3,4,5 };
	int arr2[]{ 1,2,3,4,5 };
	// 动态数组,在C++98中不支持
	int* arr3 = new int[5]{ 1,2,3,4,5 };
	// 标准容器
	vector<int> v{ 1,2,3,4,5 };
	map<int, int> m{ {1,1}, {2,2,},{3,3},{4,4} };
	return 0;
}

2.3 自定义类型的列表初始化



1. 标准库支持单个对象的列表初始化 


class Point
{
public:
	Point(int x = 0, int y = 0) : _x(x), _y(y)
	{}
private:
	int _x;
	int _y;
};
int main()
{
	Point p{ 1, 2 };
	return 0;
}

2. 多个对象的列表初始化



多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即可。注意:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()。 

Vector(initializer_list<T> a) : _capacity(a.size()), _size(0)
{
    _array = new T[_capacity];
    for (auto e : a)
        _array[_size++] = e;
}



3. 变量类型推导




3.1 为什么需要类型推导



在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁


3.2 decltype类型推导



3.2.1 为什么需要decltype



auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力。

template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
        return left + right;
}

如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identification 运行时类型识别)。
C++98中确实已经支持RTTI:
typeid只能查看类型不能用其结果类定义类型
dynamic_cast只能应用于含有虚函数的继承体系中

运行时类型识别的缺陷是降低程序运行的效率。


3.2.2 decltype



decltype是根据表达式的实际类型推演出定义变量时所用的类型,比如:
1. 推演表达式类型作为变量的定义类型 


int main()
{
    int a = 10;
    int b = 20;
    // 用decltype推演a+b的实际类型,作为定义c的类型
    decltype(a + b) c;
    cout << typeid(c).name() << endl;
    return 0;
}

 2. 推演函数返回值的类型



4.默认成员函数控制




在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成。 


4.1 显式缺省函数



在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数 

class A
{
public:
    A(int a) : _a(a)
    {}
    // 显式缺省构造函数,由编译器生成
    A() = default;
    // 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
    A& operator=(const A& a);
private:
    int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
    A a1(10);
    A a2;
    a2 = a1;
    return 0;


4.2 删除默认函数



如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。 

注避避免删除函数和explicit一起使 



5.右值引用




5.1 右值引用概念



C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以提高程序的可读性。

为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。 

引用函数返回值,返回值是一个临时变量,为右值

以下的 p、b、c、* p 都是左值
int* p = new int(0);
int b = 1;
const int c = 2;


5.2 左值与右值



左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式,一般认为:可以放在=左边的,或者能够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值,但是也不一定完全正确。

int a = 10;
    int b = 20;
    // a和b都是左值,b既可以在=的左侧,也可在右侧,
    // 说明:左值既可放在=的左侧,也可放在=的右侧
    a = b;
    b = a;
    const int c = 30;
    // 编译失败,c为const常量,只读不允许被修改
    //c = a;


    // 因为可以对c取地址,因此c严格来说不算是左值
    cout << &c << endl;


    // 编译失败:因为b+1的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值
    //b + 1 = 20;

1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。
2. const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为其是左值。
3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
4. 如果表达式运行结果或单个变量是一个引用则认为是左值。
 

C++11对右值进行了严格的区分:
C语言中的纯右值,比如:a+b, 100
将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。


5.3移动语义



C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。

在C++11中如果需要实现移动语义,必须使用右值引用

1. 移动构造函数的参数千万不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。
2. 在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造 


5.4 右值引用引用左值



C++11中,std::move()函数位于 头文件中,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义



6.lambda表达式



lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }


1. lambda表达式各部分说明
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来
的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。


(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起
省略


mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修
饰符时,参数列表不可省略(即使参数为空)。


->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分
可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。


{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。


因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。



7.线程库




thread类的简单介绍


C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件

thread() ——构造一个线程对象,没有关联任何线程函数,即没有启动任何线程


thread(fn,args1, args2,...)——构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的参数


get_id() ——获取线程id


joinable() ——线程是否还在执行,joinable代表的是一个正在执行中的线程。


join() ——该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行


detach()——在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关


猜你喜欢

转载自blog.csdn.net/MuqiuWhite/article/details/130468404