软件网站建设专业,网站建设一个月做十单,互联网营销公司排名,近几年的网络营销案例目录
1. 列表初始化initializer_list
2. 前面提到的一些知识点
2.1 小语法
2.2 STL中的一些变化
3. 右值和右值引用
3.1 右值和右值引用概念
3.2 右值引用类型的左值属性
3.3 左值引用与右值引用比较
3.4 右值引用的使用场景
3.4.1 左值引用的功能和短板
3.4.2 移动…目录
1. 列表初始化initializer_list
2. 前面提到的一些知识点
2.1 小语法
2.2 STL中的一些变化
3. 右值和右值引用
3.1 右值和右值引用概念
3.2 右值引用类型的左值属性
3.3 左值引用与右值引用比较
3.4 右值引用的使用场景
3.4.1 左值引用的功能和短板
3.4.2 移动构造
3.4.3 移动赋值
3.4.4 插入右值时减少深拷贝
4. 完美转发
4.1 万能引用(引用折叠)
4.2 完美转发
5. 新的类功能
5.1 默认生成的移动构造/赋值
5.2 类里新的关键字
本篇完。 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞进行修复语言的核心部分则没有改动因此人们习惯性的把两个标准合并称为C98 / 03标准。从C0x到C11C标准10年磨一剑第二个真正意义上的标准珊珊来迟。相比于C98 / 03C11则带来了数量可观的变化其中包含了约140个新特性以及对C03标准中约600个缺陷的修正这使得C11更像是从C98 / 03中孕育出的一种新语言。相比较而言C11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全不仅功能更强大而且能提升程序员的开发效率公司实际项目开发中也用得比较多所以我们要作为一个重点去学习。C11增加的语法特性非常篇幅非常多我们这里没办法一 一讲解所以本主要讲解实际中比较实用的语法。 1. 列表初始化initializer_list
列表花括号{ }就被叫做列表。
我们之前可以使用列表来初始化数组初始化结构体变量初始化元素类型为结构体变量的数组等等。 C11扩大了用大括号括起的列表(初始化列表)的使用范围使其可用于所有的内置类型和用户自定义的类型使用初始化列表时可添加等号()也可不添加。 #include iostream
using namespace std;class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout Date(int year, int month, int day) endl;}protected:int _year 1;int _month 1;int _day 1;
};int main()
{int x1 1;int x2 { 2 }; // 要能看懂但是不建议使用int x3{ 2 };Date d1(2023, 1, 1); // 都是在调用构造函数Date d2 { 2023, 2, 2 }; // 要能看懂但是不建议使用Date d3{ 2023, 3, 3 };return 0;
} 可以不加等号进行初始化如上图代码所示但是强烈不建议使用。
这其实很鸡肋没有什么价值继续使用C98中的方式就挺好的而且容易理解C11中的方式反而不太好理解了。C中这种鸡肋的语法被很多人吐槽理性看待。
列表初始化真正有意义的地方是用于初始化STL中的容器
之前提到vector和list以及map等STL中的容器也可以像普通数组一样使用初始化列表来初始化了。这是因为列表初始化本身就是一个类模板 如上图所示这是C11才有的一个类型该类型叫做列表初始化而且还有自己的成员函数包括构造函数计算列表大小的接口获取列表迭代器位置。但几乎都不用
C11为这些容器提供了新的构造函数该构造函数是使用列表来初始化对象的它的形参就是initializer_list所以列表初始化才可以初始化STL中的容器。
赋值运算符重载函数也有一个列表的重载版本
#include iostream
#include vector
#include list
#include map
using namespace std;class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout Date(int year, int month, int day) endl;}protected:int _year 1;int _month 1;int _day 1;
};int main()
{int x1 1;int x2 { 2 }; // 要能看懂但是不建议使用int x3{ 2 };Date d1(2023, 1, 1); // 都是在调用构造函数Date d2 { 2023, 2, 2 }; // 要能看懂但是不建议使用Date d3{ 2023, 3, 3 };// 调用支持list (initializer_listvalue_type il类似这样的构造函数vectorint v1 { 1, 2, 3, 4, 5, 6 };vectorint v2 { 1, 2, 3, 4, 5, 6 };listint lt1 { 1, 2, 3, 4, 5, 6 };listint lt2{ 1, 2, 3, 4, 5, 6 };auto x { 1, 2, 3, 4, 5, 6 };cout typeid(x).name() endl; // 打印初始化列表的类型vectorDate v3 {d1, d2, d3};vectorDate v4 { { 2022, 1, 1 }, {2022, 11, 11} };string s1 11111;mapstring, string dict { { sort, 排序 }, { insert, 插入 } }; // 构造initializer_listpairconst string, string kvil { { left, 左边 }, { right, 右边 } }; // 赋值重载dict kvil; // 上面的类型就不能用auto推导编译器不知道那里是一个pairreturn 0;
} 2. 前面提到的一些知识点
2.1 小语法
C11提供了一些新的小语法很多我们都接触过甚至是使用过。 c11提供了多种简化声明的方式尤其是在使用模板时。这里讲auto和decltype
auto这个关键字我们已经使用过很多了 在C98中auto是一个存储类型的说明符表明变量是局部自动存储类型但是局部域中定义局部的变量默认就是自动存储类型所以auto就没什么价值了。C11中废弃auto原来的用法将其用于实现自动类型推断。这样要求必须进行显示初始化让编译器将定义对象的类型设置为初始化值的类型。 #define _CRT_SECURE_NO_WARNINGS 1#include iostream
#include map
using namespace std;int main()
{int i 10;auto p i;auto pf strcpy;cout typeid(p).name() endl;cout typeid(pf).name() endl;mapstring, string dict { {sort, 排序}, {insert, 插入} };//mapstring, string::iterator it dict.begin();auto it dict.begin();return 0;
} decltype:
关键字decltype将变量的类型声明为表达式指定的类型。
使用typeid().name()只能打印出类型的名称并不能用这个名称继续创建变量而decltype可以
templateclass T1, class T2
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout typeid(ret).name() endl;
}int main()
{const int x 1;double y 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(x) p; // p的类型是int*cout typeid(ret).name() endl;cout typeid(p).name() endl;F(1, a);return 0;
} 使用decltype可以自动推演类型并且可以用推演出的结果继续创建变量如上图所示对于一些不同类型直接的运算结果decltype有奇效。 nullptr
由于C中NULL被定义成字面量0这样就可能回带来一些问题因为0既能指针常量又能表示整形常量。所以出于清晰和安全的角度考虑C11中新增了nullptr用于表示空指针。
在C中存在条件编译以后用nullptr就行了这算是修复了一个bug
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif范围for循环
范围for我们也一直都在使用这是C11提供的语法糖使用起来非常方便它的底层就是迭代器只是编译器给自动替换了这里就不再讲解了。 2.2 STL中的一些变化
新容器 红色框中的是C11增加的新容器基本只有unordered_map和unordered_set有用其他很鸡肋。容器array对标的是静态数组array也是一个静态的也就是在栈区上的大小是通过一个非类型模板参数确定的。容器forward_list是一个单链表也很鸡肋因为绝大部分场景双链表都可以满足要求而且更加方便唯一使用到单链表的地方就是哈希桶中。前面都提到过。
至于unordered_map和unordered_set这两个容器的底层是哈希桶虽然不能实现排序但是可以降重。而且在查找时具有其他容器无法比拟的效率。这两个容器是非常实用的而且也是我们经常使用的。
容器中的使用新方法 1. 使用列表构造 在前面就讲解过了几乎每个容器都增加了新的接口使用std::initializer_list类型来构造。 2. 移动构造和移动赋值 在下面讲解了右值引用就可以明白了。 3. emplace_xxx插入接口或者右值引用版本的插入接口。 同样在后面才能学习到。 3. 右值和右值引用
传统的C语法中就有引用的语法而C11中新增了的右值引用语法特性 之前学习的引用就叫做左值引用。无论左值引用还是右值引用都是给对象取别名。 3.1 右值和右值引用概念
什么是左值什么是右值 左值一个表示数据的表达式如变量名或者指针解引用。特点可以对左值取地址 可以对左值赋值。 上图代码中所示的变量都属于左值要牢记左值可以取地址这一个特性。 定义时const修饰符后的左值不能给他赋值但是可以取它的地址。 左值可以出现在赋值符号的左边也可以出现在赋值符号的右边。 右值也是一个表示数据的表达式。如字面常量表达式返回值函数返回值类型转换时的临时变量等等。特点右值不可以取地址不可以赋值。 要牢记右值特性不能取地址不能赋值。 右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边。 什么是右值引用
左值引用是给左值取别名右值引用显而易见就是给右值取别名。 右值引用使用两个符号。 上图代码中的rr1,rr2,rr3就是三个右值的别名也就是右值引用。 3.2 右值引用类型的左值属性
右值是不能取地址的但是给右值取别名后会导致右值被存储到特定位置且可以取到该位置的地址。 对于内置类型的右值如字面常量一旦右值引用以后就会被存储到特定的位置 并且可以取到该地址而且还可以修改。 int main()
{int rr1 10;cout rr1 endl;rr1 5;cout rr1 endl;const double rr2 (1.1 2.2);//rr2 5.5; // 不能修改return 0;
}
字面常量10原本是不可以被修改的但是右值引用以后在特定的位置开辟了变量来存放10所以就可以被修改了。
表达式或者函数的返回值会有一个临时变量来存放返回值我们知道这样的临时变量具有常性也是右值。对于这种右值引用编译器会修改它的属性将常性修改并且存储在特定位置。
注意const类型的右值即便开辟了变量存放该右值也是不可以被修改的因为被const修饰了。 内置类型的右值被称为纯右值。
自定义类型的右值被称为将亡值。
对于自定义类型的右值如容器的临时变量它确确实实会被销毁而不会被存放。
自定义类型的右值才能体现出右值存在的意义后面会详细讲解。 右值引用是右值的别名它所指向的右值是不可以被修改的。但是右值引用本身也是一种类型并且它的属性是左值可以取地址可以赋值。 3.3 左值引用与右值引用比较
思考左值引用可以引用右值吗
我们要知道右值引用是C11才出来的右值传参给函数还是右值那我们以前写的函数都用不了右值传参了
templateclass T
void Func(const T x)
{}
这里去掉const肯定是不能传参的为了给右值传参当然还有其它原因所以const的左值引用可以引用右值。总结普通的左值引用不可以引用右值const的左值引用可以引用右值 思考右值引用可以引用左值吗
右值引用不可以引用普通的左值可以引用move以后的左值move这个语法先记住
左值经过move以后就变成了右值如
int main()
{// 左值引用可以引用右值吗 const的左值引用可以double x 1.1, y 2.2;//double r1 x y;const double r1 x y;// 右值引用可以引用左值吗可以引用move以后的左值int b 7;//int rr5 b;int rr5 move(b);return 0;
}
成功编译 3.4 右值引用的使用场景
namespace rtx
{class string{public:string(const char* str ):_size(strlen(str)), _capacity(_size){_str new char[_capacity 1];strcpy(_str, str);}void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string s) // 拷贝构造:_str(nullptr){cout string(const string s) -- 拷贝构造(深拷贝) endl;//string tmp(s._str);//swap(s);_str new char[s._capacity 1];strcpy(_str, s._str);_size s._size;_capacity s._capacity;}string operator(const string s) // 拷贝赋值{cout string operator(string s) -- 拷贝赋值(深拷贝) endl;string tmp(s);swap(tmp);return *this;}protected:char* _str;size_t _size;size_t _capacity;};
}
先自己实现一个string只有拷贝构造函数赋值运算符重载函数析构函数以及一个普通的构造函数。无论是拷贝构造还是赋值运算符重载都会进行深拷贝采用现代写法来实现
namespace rtx
{class string{public:string(const char* str ):_size(strlen(str)), _capacity(_size){_str new char[_capacity 1];strcpy(_str, str);}const char* c_str() const{return _str;}void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string s) // 拷贝构造:_str(nullptr), _size(0), _capacity(0){cout string(const string s) -- 拷贝构造(深拷贝) endl;string tmp(s._str);swap(tmp);} string operator(const string s) // 拷贝赋值{cout string operator(string s) -- 拷贝赋值(深拷贝) endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str nullptr;}protected:char* _str;size_t _size;size_t _capacity;};
}
左值引用的场景
使用普通传值调用存在一次深拷贝
void Func(rtx::string s)
{}int main()
{rtx::string s(hello world);Func(s);return 0;
} 使用传拷贝引用时不存在深拷贝Func函数直接使用main函数中的s1对象:
void Func(rtx::string s)
{}int main()
{rtx::string s(hello world);Func(s);return 0;
} 函数返回参数和上面一样传引用返回有时确实能提高效率
3.4.1 左值引用的功能和短板 左值引用的功能 1、做参数。 a、减少拷贝提高效率。b、做输出型参数。 2、做返回值。 a、减少拷贝提高效率。b、引用返回可以修改返回对象(比如: operator[ ])。 但是左值引用做返回值只解决了70%的问题在类似 to_string 函数中 传值返回时存在一次深拷贝。rtx::string to_string(int value)
要知道深拷贝的代价是比较大的深拷贝次数减少可以很大程度上提高代码的效率。 传左值引用返回时不存在深拷贝。可以吗rtx::string to_string(int value)
但是你敢传引用返回吗我们把int value 转换成string此时的 string 是一个形参。出了函数就销毁了。外面拿到的就是被销毁了的栈帧。 所以左值引用存在的短板
前面我们在调用 to_string 函数的时候我们把int value 转换成string此时的 string 是一个形参。
所以只能传值返回此时mian函数中拿到 to_string 中的 string 对象要进行两次深拷贝。 第一次深拷贝to_string函数返回时会将string对象放在一个临时变量中此时发生的深拷贝。函数返回时如果是内置类型等几个字节的变量会将函数中的临时变量放在寄存器中返回如果是自定义类型所占空间比较大就会放在临时变量中压栈到上一级栈帧中。 第二次深拷贝main函数中ret接收函数返回了的string对象时会再发生一次深拷贝。 但是编译器会进行优化将两次深拷贝优化成一次。虽然只有一次但有些情况代价还是很大的。
C98是如何解决上面的问题
那就是输出型参数rtx::string to_string(int value)变成rtx::void to_string(int valuestring s)
但是这样不太符合使用习惯。
有没有办法让它符合使用习惯并且一次深拷贝都没有那就要用到下面的C11新增的移动构造和移动赋值了 3.4.2 移动构造
此时用右值引用就可以解决这个问题。
右值引用的价值之一补齐临时对象不能传引用返回这个短板。
前面的深拷贝是拷贝构造产生的string(const string s) // 拷贝构造形参是左值引用 演示在string类中增加一个移动构造函数
前面提到过内置类型的右值被称为纯右值。
自定义类型的右值被称为将亡值。这里的传右值就是将亡值
基于拷贝构造无论是左值还是右值都老老实实地开空间 string(const string s) // 拷贝构造:_str(nullptr), _size(0), _capacity(0){cout string(const string s) -- 拷贝构造(深拷贝) endl;string tmp(s._str);swap(tmp);}
左值因为还要使用肯定要开空间的这里的右值是将亡值没用了所以也不用开空间了
因为不用开空间了所以深拷贝也没了而是资源转移直接swap string(string s) // 移动构造:_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 移动构造(资源转移) endl;swap(s);}
移动构造的形参是右值引用。
从to_string中返回的string对象是一个临时变量具有常性也就是我们所说的右值。
用右值来构造string对象时会自定匹配移动构造函数。以前没有移动构造时右值传参会走拷贝构造因为const 的左值引用可以接收右值但是这不是最优方案现在写了移动构造右值传参就会走移动构造 3.4.3 移动赋值
拷贝赋值移动赋值和拷贝构造移动构造类似 string operator(const string s) // 拷贝赋值{cout string operator(string s) -- 拷贝赋值(深拷贝) endl;string tmp(s);swap(tmp);return *this;}string operator(string s) // 移动赋值{cout string operator(string s) -- 移动赋值(资源移动) endl;swap(s);return *this;}
总结右值引用和左值引用减少拷贝的原理不太一样。
左值引用是别名直接在原本的对象上起作用。右值引用是间接起作用通过右值引用识别到右值然后在移动构造和移动赋值中进行资源转移。
使用移动构造和移动赋值时被转移资源的对象必须是个将亡值像to_string的使用一样因为会被销毁。
C11的STL标准库中也提供了移动构造和移动赋值函数。 3.4.4 插入右值时减少深拷贝
C11在STL库容器中的所有插入接口都提供了右值版本push_backinsert等。
在我们写的string恢复这两个接口 void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}
然后分别像库里的 list 插入左值和右值
int main()
{listrtx::string lt;rtx::string s1(hello); // 左值lt.push_back(s1); // 插入左值cout ---------------------------------- endl;lt.push_back(rtx::string(world)); // 插入右值//lt.push_back(world);return 0;
} 如果没有移动构造那么下面的也是深拷贝了。 4. 完美转发
4.1 万能引用(引用折叠)
写多个重载函数根据实参类型调用不同函数。 形参类型分别是左值引用const左值引用右值引用const右值引用
void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }// 万能引用(引用折叠)t既能引用左值也能引用右值
templatetypename T
void PerfectForward(T t)
{Fun(t); // 此时t变成了左值/const左值
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
代码中的perfectForward函数模板被叫做万能引用模板
无论调用该函数时传的是什么类型它都能推演出来 在函数模板推演的过程中会发生引用折叠模板参数T中的两个符号折叠成一个。
当传入的实参是左值时就会发生引用折叠是右值时就不会发生引用折叠。
无论传的实参是什么都不用改变模板参数T编译器都能够自己推演。这就是万能引用只需要一个模板就可以搞定不需要分类去写。
上面万能模板中虽然推演出来了各自实参类型但是由于右值引用本身是左值属性所以需要使用move改变属性后才能调用对应的重载函数。
有没有办法不用move改变左值属性让模板函数中的t保持它推演出来的类型。答案是有的完美转发就能够保持形参的属性不变。 4.2 完美转发
完美转发同样是C11提供的它也是一个模板 完美转发完美转发在传参的过程中保留对象原生类型属性。 实参传递过来后推演出的形参是什么类型就保持什么类型继续使用。
这里会语法就行
void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }// 万能引用(引用折叠)t既能引用左值也能引用右值
templatetypename T
void PerfectForward(T t)
{Fun(std::forwardT(t)); // 完美转发保持t引用对象属性
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
此时再使用万能引用的时候在函数模板中调用重载函数时只需要使用完美转发就可以保持推演出来的属性不变右值引用仍然是右值const右值引用也仍然是右值。 需要注意的是
虽然右值不可以被修改但是右值引用以后具有了左值属性才能被转移一旦被const修饰以后就无法转移了。所以我们在使用右值引用的时候不要使用const来修饰。 5. 新的类功能
在原来的C类中有6大默认成员函数
1. 构造函数 2. 析构函数 3. 拷贝构造函数 4. 拷贝赋值重载 5. 取地址重载 6. const 取地址重载
最后重要的是前4个后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的而且完全符号我们使用的需求。
5.1 默认生成的移动构造/赋值
C11中新增了两个移动构造和移动赋值运算符重载此时C11一共有8个默认成员函数了。
这两个成员函数在前面已经介绍过了这里站在默认成员函数的角度继续谈谈。 满足下列条件编译器会自定生成移动构造函数 没有自己显示定义移动构造函数且没有实现析构函数拷贝构造函数拷贝赋值重载中的任何一个。 此时编译器会自定生成一个默认的移动构造函数。 默认生成的移动构造函数对于内置类型会逐字节进行拷贝。 对于自定义类型如果实现了移动构造就调用移动构造没有实现就调用拷贝构造。 满足下列条件编译器会自动生成移动赋值重载函数 自己没有显示定义移动赋值重载函数。且且没有实现析构函数拷贝构造函数拷贝赋值重载中的任何一个。 此时编译器会自动生成一个默认移动赋值函数。 对于内置类型会按字节拷贝。 对于自定义类型如果实现了移动赋值就调用移动赋值如果没有实现就调用拷贝赋值。 创建一个类屏蔽掉拷贝构造拷贝赋值以及析构函数成员变量有一个是我们自己实现的string里面有移动构造和移动赋值。
namespace rtx
{class string{public:string(const char* str ):_size(strlen(str)), _capacity(_size){_str new char[_capacity 1];strcpy(_str, str);}void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string s) // 拷贝构造:_str(nullptr), _size(0), _capacity(0){cout string(const string s) -- 拷贝构造(深拷贝) endl;string tmp(s._str);swap(tmp);}string(string s) // 移动构造:_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 移动构造(资源转移) endl;swap(s);}string operator(const string s) // 拷贝赋值{cout string operator(string s) -- 拷贝赋值(深拷贝) endl;string tmp(s);swap(tmp);return *this;}string operator(string s) // 移动赋值{cout string operator(string s) -- 移动赋值(资源移动) endl;swap(s);return *this;}~string(){delete[] _str;_str nullptr;}protected:char* _str;size_t _size;size_t _capacity;};
}class Person
{
public://Person(const char* name , int age 0)// :_name(name)// , _age(age)//{}//Person(const Person p) // 拷贝构造// :_name(p._name)// , _age(p._age)//{}//Person operator(const Person p) // 拷贝赋值//{// if (this ! p)// {// _name p._name;// _age p._age;// }// return *this;//}//~Person()//{}protected:rtx::string _name;int _age;
};int main()
{Person s1;Person s2 s1;Person s3 std::move(s1);Person s4;s4 std::move(s2);return 0;
} 此时Person就自动生成了移动构造函数并且调用了string中的移动构造和移动赋值函数来构造string对象。 将Person中的拷贝构造拷贝赋值析构函数任意放出一个来。这里只放出了析构
使用右值构建string对象时都会调用string的拷贝构造和拷贝赋值函数。 编译器默认生成的移动赋值和移动构造非常类型。如果符合条件就生成内置内心按字节处理自定义类型调用自定义类型的移动赋值或者移动构造如果没有的化就调用它们的拷贝赋值或者拷贝构造。如果不符合条件就直接调用自定义类型的拷贝复制或者拷贝构造。 5.2 类里新的关键字
强制生成默认函数的关键字default:
这个default并不是switch中的default而是C11的新用法。
假设类中的某个默认成员函数没有自动生成但是我们需要它就可以用default强制让编译器自动生成默认函数。
5.1里的代码将Person中的拷贝构造拷贝复制析构函数都显示定义此时就破坏了自动生成移动构造的条件。把Person里的注释放开使用default强制生成默认的移动构造函数 从结果中可以看到仍然调用了string中的移动构造函数而不是调用的拷贝构造(深拷贝)。 说明Person中仍然生成了默认的移动构造函数。 禁止生成默认成员函数的关键字delete: 如果能想要限制某些默认函数的生成在C98中是该函数设置成private并且只声明补丁 已这样只要其他人想要调用就会报错。在C11中更简单只需在该函数声明加上delete即 可该语法指示编译器不生成对应函数的默认版本称delete修饰的函数为删除函数。 C98不生成默认成员函数的方法直接一个分号要放到保护或者私有里这里就不放了 在Person类中不显示定义拷贝构造函数拷贝复制函数析构函数此时符合自动生成默认移动构造的条件。 声明移动构造函数但是没有定义要放到保护或者私有里防止类外实现这里就不放了。此时在编译的时候就会报错这是C98中的方式利用链接时找不到函数的定义报错。C11就新增delete关键字使其在编译阶段就报错
C11中使用delete同样可以实现不让自动生成默认成员函数。 同样在编译时报错了。编译器会自动生成移动构造函数但是此时使用了delete编译器就会报错告诉我们这里生成了移动构造。
这是为了在编译阶段就报错而不是运行时再报错。
以前提到的一道题
// 要求delete关键字实现一个类只能在堆上创建对象
class HeapOnly
{
public:HeapOnly(){_str new char[10];}~HeapOnly() delete;//void Destroy() // 如果要销毁只能这样//{// delete[] _str;// operator delete(this);//}private:char* _str;//...
}; 继承和多态中的final与override关键字
这两个关键字在继承和多态部分详细讲解过这里不再详细讲解。
final
在继承中被final修饰的类叫做最终类是无法继承的。在多态中被final修饰的虚函数是无法进行重写的。
override
在多态中用来检查虚函数是否完成了重写。 本篇完。
C11中的很多东西虽然让C越来越不像C比如列表初始化等内容但是还是有一些非常有用的东西的比如今天讲到的右值引用和下一篇学的lambda表达式。
下一篇从C语言到C_34C11_下可变参数 lambdafunctionbind笔试题。