10 C++11
- 1、类型推导
- 1.1 auto关键字
- 1.2 auto类型推断本质
- 2、类型计算
- 2.1 类型计算分类
- 2.2 类型计算的四种规则
- 2.3 返回值类型计算
- 3、列表初始化
- 4、Lambda表达式
- 4.1 前置知识
- 4.2 Lambda表达式
- 4.3 捕获表
- 5、右值引用
- 5.1 概念
- 5.2 左值引用和右值引用
- 6、移动语义
1、类型推导
1.1 auto关键字
- C++98中,auto表示栈变量,通常省略不写
void foo(void){
int i;
auto int j;//表示在栈里分配的
}
- C++11中,给auto赋予新的语义,表示自动类型推导
- 既根据对变量进行初始化时所使用的数据的类型,由编译器自动推导出所定义变量的实际类型
auto i=0; -> int j=10;
auto j=i; -> int j=i;
1.2 auto类型推断本质
·按照定义独立对象并根据初始化数据的类型进行推导。
注意:无法自动推断const,只能自己在auto的上下文显示指明。但是有两种情况是除外的:
1:如果给出的初始化数据类型为常量指针,则可以自动推导出const
2:auto与引用的联合联用
- 按照定义独立对象并根据初始化数据的类型进行推导,所以不可能推导出引用
- 除非auto的上下文指明按照引用推导若指明按引用推导并且目标带有常属性,则可以自动推导const
/*类型推导
linux 默认是c98标准的,如果需要编译的话需要添加 -std=c++11
类型推导绝对不是类型照抄
*/
int main(){
int a = 10;
auto c = a;
cout << "c的类型" << typeid(c).name() << endl;// typeid无法获取到对象的常属性
c++;// 允许更改,说明不被const修饰
cout <<"&c:"<< &c <<"&a:"<< &a << endl;
const int b = 20;
auto d = b;
cout << "d的类型" << typeid(d).name() << endl;
cout << "&d:" << &d<< "&b:" << &b << endl;
d++;// 允许更改,说明不带const
const auto e = b;// 自己在auto上添加const
cout << "e的类型" << typeid(e).name() << endl;
cout << "&e:" << &e << "&d:" << &d << endl;
// e++;// 不允许更改,说明带const
auto f = &b;// 如果初始化数据的类型为常指针,则可以自动推导出const
cout << "f的类型为:" << typeid(f).name() << endl;
// *f = 888; // *f不允许更改
f = NULL; // f是可以更改,说明推导出来的类型是 const int *
return 0;
}
/*类型推导和引用的联合使用*/
int main(){
int a = 10;
const int b = 10;
auto & d = a;
cout << "d的类型" << typeid(d).name() << endl;
cout << "&d:" << &d << "&a:" << &a << endl; // 地址相同,说明是别名
d++;
auto&e = b; // 这里指明了e是引用推导,并且b带有常属性,则可以自动推导出const
cout << "e的类型" << typeid(e).name() << endl;
cout << "&e:" << &e << "&b:" << &b << endl;// // 地址相同,说明是别名
//e++; // 出错,说明不能更改 那么e的类型为const int &
return 0;
}
- auto关键字的使用限制
- 1:函数形参类型无法推导(C++14标准支持)。
- 2:类的成员变量无法推导。
void foo(auto v){}
2、类型计算
2.1 类型计算分类
c语言:sizeof-计算类型的大小
C++语言:typeid-可以获取类型的信息字符串
C++11:decltype-获取参数表达式的类型
注意事项:类型推导和类型计算都是由编译器确定,并不是运行期确定
/*类型计算*/
int main(){
const int a = 10;
auto b = a; // 类型推导
cout << "b的类型" << typeid(b).name() << endl;
cout << "&b:" << &b << "&a:" << &a << endl;
b++;// 允许更改,所以推导出来的类型是int
decltype(a)c = 100;// 类型计算 初始值可以设置和a不一样
cout << "c的类型" << typeid(c).name() << endl;
cout << "&c:" << &c << "&a:" << &a << endl;// // 地址不相同相同
// c++; // 出错,说明不能更改 那么c的类型为const int
return 0;
}
- 类型推导和类型计算的比较
1:类型计算比类型推导在类型的确定上更加精准
2:类型计算比类型推导在初值的确定上更加灵活
2.2 类型计算的四种规则
- 1:如果给decltype传递的为标识符表达式,decltype取该标识符的类型作为最终计算出的类型
int main(){
int a = 10;
// 如果给decltype传递的为标识符表达式,decltype取该标识符的类型作为最终计算出的类型
decltype(a)b = a;// 类型计算
cout << "b的类型" << typeid(b).name() << endl;
cout << "&b:" << &b << "&a:" << &a << endl;// // 地址不相同
b++; // 能更改说明b的类型为 int
return 0;
}
- 2:如果给decltype传递的为函数表达式,decltype取该函数的返回值类型作为最终计算出的类型
float foo(){
cout << "函数被调用" << endl;
return 3.14;
}
int main(){
int a = 10;
// 如果给decltype传递的为函数表达式,decltype取该函数的返回值类型作为最终计算出的类型
decltype(foo())b = a;// 并不会去实际调用foo()函数,类型计算是编译器确定的,不是运行时
cout << "b的类型" << typeid(b).name() << endl;
cout << "&b:" << &b << "&a:" << &a << endl;// // 地址不相同
b++; // 能更改说明b的类型为 float
return 0;
}
- 3:如果给decltype传递的为其他表达式,并且表达式的结果为左值,则取该左值引用的类型作为最终计算出的类型
int main(){
int a = 10;
// 如果给decltype传递的为其他表达式,并且表达式的结果为左值,则取该左值引用的类型作为最终计算出的类型
decltype(++a)b = a;
cout << "b的类型" << typeid(b).name() << endl;
cout << "&b:" << &b << "&a:" << &a << endl;// // 地址相同
b++; //允许更改 说明b的类型为int &
return 0;
}
- 4:如果给decltype传递的为其他表达式,并且表达式的结果为右值,则取该右值本身的类型作为最终计算出的类型
int main(){
int a = 10;
// 如果给decltype传递的为其他表达式,并且表达式的结果为右值,则取该右值本身的类型作为最终计算出的类型
decltype(a++)b = a;
cout << "b的类型" << typeid(b).name() << endl;
cout << "&b:" << &b << "&a:" << &a << endl;// // 地址不相同
b++; //允许更改 说明b的类型为int
return 0;
}
2.3 返回值类型计算
- 返回值类型后置
auto foo(int x, double y)->decltype(x+y){// 返回值类型后置,通过decltype计算得出
return x + y;
}
int main(){
auto f = foo(1, 3.1);// 类型推导
cout << typeid(f).name() << endl; // double类型
return 0;
}
3、列表初始化
基本类型,类类型,结构/联合/枚举类型等等的单个对象或对象数组,都可以采用形式完全统一的列表初始化语法
进行对象的初始化
- 书写形式:类型 对象 {初值表};
-int a{123);
-new double {1.23);
-string c{“123”};
-struct Student {d,“张飞”,20,{1997,10,10}};
-float e[]{1.1,2.2,3.3};
struct BD{
int m_year;
int m_month;
int m_day;
};
struct myStudent{
string m_name;
int m_age;
BD m_body;
};
class Human{
public:
Human(int age = 0, const char* name = "无名") :m_age(age), m_name(name){
}
int m_age;
string m_name;
};
int main(){
int a = { 123 }; // int a=123
cout << "a=" << a << endl;
double* pa = new double{ 3.14 };// double *pa = new double(3.14);
double *pb{ new double{ 3.14 } };
cout << "*pa=" << *pa << " *pb=" << *pb << endl;
int b[]{1, 2, 3};//int b[] = { 1, 2, 3 };
for (int i = 0; i < 3; i++){ cout << b[i] << ' '; }
int *parr{ new int[3]{4, 5, 6} };
for (int i = 0; i < 3; i++){ cout << parr[i] << ' '; }
delete[]parr;
myStudent s{ "zs", 22, { 1997, 5, 7 } }; // myStudent s = { "zs", 22, { 1997, 5, 7 } };
cout << s.m_name << s.m_age << s.m_body.m_year << s.m_body.m_month << s.m_body.m_day << endl;
Human h{ 20, "赵云" };//Human h (20, "赵云" )
cout << h.m_age << h.m_name << endl;
return 0;
}
- 小括号操作符函数
class AA{
public:
int operator()(int x, int y){
return x + y;
}
};
int main(){
AA a;
cout << a(100, 200) << endl;;// a.operator()(100,200)
}
4、Lambda表达式
4.1 前置知识
在C++中函数的作用域中可以有类型,也可以有表达式
- 针对于函数内部定义了类型,编译器先编译函数内部的类型,然后在编译函数体本身的代码
int a;
void foo(int b){
int c;
class A{
public:
void bar(int d){
a = 0;// 能访问
// b = 0; // 不能访问
// c = 0; // 不能访问
d = 0;// 能访问
}
};
}
4.2 Lambda表达式
- 语法规则:
[捕获表](参数表)选项->返回类型
{
函数体
}
- 使用
int main(void){
int a = 10, b = 20;
auto c =[](int x, int y)->int{return x > y ? x : y; };
// 编译器 (1) 生成一个类 (2) 类内定义一个小括号操作符函数 (3) 返回这个类的匿名对象
cout<<c(a,b)<<endl;
return 0;
}
- 本质
lambda表达式本质其实是一个类并且最终返回值为这个类的对象,因此对lambda表达式的调用就是该对象的函数操作符的调用
解释:编译器在编译到Lambda表达式时,编译器会生成一个类Z4XXX的类,类中定义一个小括号操作函数,函数体里面填充Lambda的函数体内容,函数的返回值类型为Lambda中定义的返回类型。
其中
- 可以没有返回值类型,将根据return推断
int main(void){
int a = 10, b = 20;
auto c = [](int x, int y){return x + y; };// 当没有返回值类型时,中间的箭头可以省略
cout << c(a, b) << endl;
return 0;
}
- 如果连return也没有,则返回值为void
int main(void){
int a = 10, b = 20;
[](int x, int y){cout<< x + y<<endl; }(a,b);// 如果连return也没有,则返回值为void
return 0;
}
- 参数为void可以省略不写的
int main(void){
int a = 10, b = 20;
[]{cout << "12345" << endl; }();// 如果没有形参,返回值类型也为void那么小括号和中间的剪头都可以省略
return 0;
}
4.3 捕获表
- []-不捕获任何外部变量
- [variable]-捕获外部变量的值
- [&variable]-按引用捕获,外部变量的别名
- [this]-捕获this指针,访问外部对象的成员
int a = 10;
class Y{
public:
Y(int m_e) :e(m_e){};
void foo(int c = 30){
cout << "---" << endl;
// []-不捕获外部变量的值
[](int d = 40){
cout << "a=" << a << endl;
cout << "b=" << b << endl;
// cout << "c=" << c << endl; // 错误
cout << "d=" << d << endl;
// cout << "e=" << e << endl; // 错误
}();
cout << "--------------[c]--------" << endl;
// [variable]-捕获外部变量只读
[c](int d = 0){cout << "c=" << c << endl; }();
cout << "--------[&c]---------" << endl;
// [&variable]-按引用捕获,外部变量的别名
[&c]{c++; cout << "c=" << c << endl; }();
cout << c << endl;
// [this]-捕获this指针,访问外部对象的成员
cout << "--------[this]---------" << endl;
[this]{cout << "e=" << e << endl; }();
}
private:
static int b;
int e;
};
int Y::b = 10;
int main(){
Y y(4);
y.foo();
return 0;
}
- [=]-按值捕获所有的外部变量,也包括this
- [&]-按引用捕获所有的外部变量,也包括his
- [=,&variable]-按值捕获所有的外部变量包括this,但是指定的外部变量按引用捕获。
- [&,=variable]-按引用捕获所有的外部变量,也包括this,但是指定的外部变量按值捕获。
5、右值引用
5.1 概念
左值引用是别名,右值引用就是真名
左值:可以“取”地址的值就是左值,左值通常具名
右值:不可“取”地址的值就是右值,右值通常匿名
左值细分为非常左值和常左值
- 非常左值:有名字、可以取地址、没有常属性
- 常左值:有名字、可以取地址、有常属性
右值细分为纯右值和将亡值
- 纯右值:有一块无名内存,里面存放了基本类型的数据
- 将亡值:有一块无名内存,里面存放了类类型的数据
5.2 左值引用和右值引用
- 左值引用只能引用左值,不能引用右值
- 右值引用只能引用右值,不能引用左值
- 常左值引用,既能引用左值,也能引用右值
- 常右值引用,完全可以被常左值引用替代
/*左值引用和右值引用的差别*/
int main(){
// 左值引用只能引用左值,不能引用右值
int a=1, c=2;
int & ra = a;
// int & rb = a + c; // 错误 左值引用不能引用右值
int &&rb = a + c;// 使用右值引用
// int &&rc = a;// 错误 右值引用只能引用右值,不能引用左值
cout << rb << endl;
const int & _l = a; // 引用左值
const int & _r = a + c; // 引用右值
// 常左值引用会丧失修改目标的权限
// _l = 9;// 错误
// 右值引用不会丧失修改目标的权限
rb = 90;
cout << rb << endl;
}
6、移动语义
- 方法:
资源的转移 代替 资源的重建 - 作用:
保证功能正确的情况下,做到性能提升
//深拷贝构造函数 资源的重建
String(const String & that) :m_psz(new char[strlen(that.m_psz) + 1]){
cout << "深拷贝构造 资源的重建" << endl;
strcpy(m_psz, that.m_psz); // 没有复制地址,复制了数据(深拷贝)
}
// 深拷贝构造函数 资源的转移
String(String && that):m_psz(that.m_psz){
that.m_psz = NULL;
cout << "资源转移" << endl;
}
// 深拷贝赋值函数 资源的重建
String& operator=(/*String* this*/const String & that){
cout<<"深拷贝赋值函数 资源的重建"<<endl;
if (this == &that){}// 防止出现用户自己给自己赋值
else{
delete[] this->m_psz;// 编译器会先定义一个m_psz,并初始>化为空串,所以需要先释放内存
this->m_psz = new char[strlen(that.m_psz) + 1];// 申请新资源
strcpy(m_psz, that.m_psz); // 拷贝新内容
}
return *this;// 返回自引用
}
// 拷贝赋值函数 资源的转移
String& operator=(String&& that){
cout<<"拷贝赋值函数 资源的转移"<<endl;
delete this->m_psz;
this->m_psz=that.m_psz;
that.m_psz=NULL;
return *this;
}
//在linux下 命令行输入:g++ abc.cpp -std=c++11 -fno-elide-constructors
int main(){
String s1=String("hello"); // 深拷贝构造函数 资源的转移
String s2=s1;// 拷贝构造函数 资源的重建
s2=s1;// 拷贝赋值函数 资源的重建
s2=String("hello");// 拷贝赋值函数 资源的转移
}