目录
11.1类型推导
C++11引入了 auto 和 decltype 关键字实现类型推导,通过这两个关键字不仅能方便地获取复杂的类型,而且还能简化书写,提高编码效率。
auto类型推导: auto 定义的变量,可以根据初始化的值,在编译时推导出变量名的类型。
int main()
{
auto a;//error,没有初始化值 auto无法推导出s的类型
auto int b;//error,C++11中auto不再表示存储类型指示符,C语言中auto为存储类型指示符
auto dx = 12.23;//ok,double
auto fx = 12.23f;//ok,float
auto x = 5;//ok,x为int类型
auto ip = new int(10);//ok,ip为int*类型
const auto *xp = &x,u = 6;//ok,xp为const int*类型,u为const int类型,auto的推导类型为int
auto xp1 = &x,u1 = 6;//error,因为这样auto即推导出xp1为整型指针类型,又推导出u1为整型,产生矛盾。auto推导的类型必须保持一致
//练习
auto x=5;
const auto *xp = &x,u;//error,虽然经过前面const auto *xp = &x的推导,auto的类型可以确定为int了,但是u仍然必须要写 "= 6" 后面否则编译器不能通过
const auto *ip = &x,u = 6.0;//error,前半句使auto推导为int,后半句使auto推导为double,推导类型不一致,产生矛盾
return 0;
}
auto 并不能代表一个实际的类型声明 (如s的编译错误),只是一个类型声明的"占位符"。使用 auto 声明的变量必须要有初始化值,以让编译器推断出它的实际类型,并在编译时将auto 占位符替换为真正的数据类型。
11.2auto的推导规则
int main()
{
int x = 10;
auto *ip = &x;//ok,ip为int*类型,auto被推导为int类型
auto xp = &x;//ok,xp为int*类型,auto被推导为int*类型
auto& c = x;//ok,c是int &(整型引用)类型,auto是int类型
auto d = c;//ok,d是int类型,auto也是int类型,无法推导出auto也是int&(整型引用)类型
const auto e = x;//ok,e是const int类型,auto为int类型
auto f = e;//ok,e为常变量,但f仍然为int类型,auto也是int类型,不具有常性。就等价于const f = 10;
const auto &g = x;//ok,g为const int &类型,auto为int类型
auto & h = g;//ok,h为const int &类型,auto为const int类型,因为如果h这个引用不具有常性,它就可以去改变g也是x的值,但g为常性引用不允许修改值,所以h必须为常性。与f不同的原因在于h是引用可以修改g即x的值,而f只是一个普通变量不会改变e的值。
}
ip 和 c的推导结果是很显然的,auto 在编译时被替换为 int,因此 a 和c分别被推导为int*和 int&。
xp的推导结果说明,其实 auto 不声明为指针,也可以推导出指针类型。
d 的推导结果说明当表达式是一个引用类型时,auto 会把引用类型抛弃,直接推导成原始类型 int。
e 的推导结果说明,const auto 会在编译时被替换为 const int。
f 的推导结果说明,当表达式带有const (实际上 volatile (异变属性)也会得到同样的结果) 属性时,auto 会把const属性抛弃掉,推导成没有 const 类型的 int。
g、h的推导说明,当 auto 和引用(换成指针在这里也将得到同样的结果) 结合时,auto 的推导将保留表达式的 const 属性。
通过上面的一系列示例,可以得到下面这两条规则:
1、当不声明为指针或引用时,auto 的推导结果和初始化表达式抛弃引用和cv 限定符后类型一致
2、当声明为指针或引用时,auto 的推导结果将保持初始化表达式的cv 属性
11.3auto作为函数的形参类型
早期(C++11)中,auto不能作为函数的参数,需要将编译器的标准改为C++20标准
#include<typeinfo>
void func(auto x)
{
cout<<sizeof(x)<<endl;
cout<<typeid(x).name<<endl;
}
void funr(auto &x)
{
cout<<sizeof(x)<<endl;
cout<<typeid(x).name<<endl;
}
int main()
{
int a=10;
int ar[]={12,23,34,45,56};
func(a);//4 int
func(ar);//4 int*
funr(a);//4 int
funr(ar);//20 int [5] //数组引用作为参数时不会退化为指针,它依然具有数组的两个属性
return 0;
}
11.4auto的限制
struct Foo
{
auto value=0;//error auto不能用于非静态成员变量
static const int num =10;//ok
}
int main()
{
int ar[10]={0};
auto br=ar;//ok,br ->int*
auto dr[5]={1,2,3,4,5};//error,auto无法定义数组
auto &er=ar;//ok,er为int(&)[10]类型的数组引用
return 0;
}
C++11中auto成为类型指示符。
auto不能用于函数参数(对于早期的标准,对于C++20标准中可以)
auto不能用于非静态成员变量
auto无法定义数组,但能推导数组引用
实例化模板时不能使用auto作为模板参数
11.5auto可以推导函数的返回值类型
template<class T>
T my_max(T a,T b)
{
return a>b?a:b;
}
int main()
{
auto x=my_max(12,23);
auto y=my_max('a','b');
cout<< x << endl;
cout<< y << endl;
return 0;
}
11.6decltype关键字
auto用于通过一个表达式在编译时确定待定义的变量类型,auto 所修饰的变量必须被初始化,编译器需要通过初始化来确定 auto 所代表的类型,即必须要定义变量。如果只获取类型而不定义变量,就需要decltype关键字。
语法格式:decltype(exp)
从格式上来看,decltype 很像sizeof-一用来推导表达式类型大小的操作符。类似于sizeofdecltype 的推导过程是在编译期完成的,并且不会真正计算表达式的值。
对于sizeof():
int main()
{
int a=10;
doubel dx=12.23;
cout<<sizeof(++a)<<endl;//4
cout<<sizeof(dx+a)<<endl;//8
cout<<"a="<<a<<endl;//10;
return 0;
}
由此可见,sizeof()只是根据其类型计算其大小,并不执行括号内的表达式。这点和decltype()相同
int max_int(int a.int b)
{
return a>b?a:b;
}
int main()
{
int x=10;
decltype(x) y=1;//y -> int
decltype(x) k;//k -> int,可以不用初始化
decltype((x)) a=x;//a -> int &,必须初始化,因为a为引用
decltype((x+1)) b=x;//b -> int,并不计算x+1
//想要得到某种类型的引用,需要给其变量加上括号,使得定义类型为引用
decltype((x+y)) c=x;//c -> int,并不计算x+y
decltype(x+y) z=0;//z -> int,并不计算x+y
const int &i=x;
decltype(i) j=y;//j -> const int &
const decltype(z)*p=&z;//p -> const int*
decltype(z) *ip=&z;//ip -> int*
decltype(ip) *pp=&ip;//pp -> int**
int a=10;
int* p=&a;
decltype(p) t = p;//t -> int*
decltype(*p) rc = a;//rc -> int &
decltype((p)) s = p;//s -> int* &
int a=10,b=20;
decltype(max_int(a,b)) c = max_int(a,b);//decltype中的函数不会被调动,它是在编译期就完成类型的推导
}
auto和decltype的最大区别:auto在推导时会丢失CV属性,而decltype会保留原有表达式的类型,不会丢失CV属性
11.7decltype的推导规则
推导规则1:exp 是标识符、类访问表达式,decltype(exp)和exp 的类型一致。
推导规则2:exp 是函数调用,decltype(exp)和返回值的类型一致。
推导规则3:其他情况,若exp 是一个左值,则 decltype(exp)是exp 类型的左值引用,否则和 exp类型一致。
11.8auto和decltype的使用
使用一个函数比较不同类型或相同类型的变量大小
template<class T,class U>
T MAX1(T a,U b)
{
return a>b?a:b;
}
template<class R,class T,class U>
R MAX2(T a,U b)
{
return a>b?a:b;
}
template<class T,class U>
auto MAX3(T a,U b)//->decltype(a>b?a:b)//类型后置方式
{
return a>b?a:b;
}
int main()
{
auto x=MAX1(12,23.34);//会丢失精度,结果为23
//早期的解决方法
auto y=MAX2<double>(12,23.34);//结果为23.34,但是每次使用时必须明确函数的返回值类型,因为函数模板无法推演函数的返回值类型
//最优的解决方法
auto z=MAX3(12,23.34);//结果为23.34 z为double类型
auto a=MAX3(45,23.34);//结果为45 a为double类型
auto b=MAX3(12,'a');//结果为97 b为int类型
auto c=MAX3('a',23.34);//结果为97 c为double类型
return 0;
}
猜想:对于MAX3()函数的返回值类型的推导为参数列表类型中精度更高的类型。
11.9基于范围的for循环
在C98中,不同的容器和数组,遍历的方法不尽相同,写法不统一,也不够简洁,而C++11基于范围的 for循环以统一、简洁的方式来遍历容器和数组,用起来更方便了。
11.9.1一般遍历一个数组(容器)的方法
int main()
{
int ar[]={1,2,3,4,5,6,7,8,9,10};
int len=sizeof(ar)/sizeof(ar[0]);
for(int i=0;i<len;++i)
{
cout<<ar[i]<<" ";
}
cout<<endl;
return 0;
}
11.9.2C++11基于范围的for循环
int main()
{
int ar[]={1,2,3,4,5,6,7,8,9,10};
for(int x:ar)//这样使用的前提是ar必须为容器名(存在两个属性)
{
cout<<x<<" ";
}
cout<<endl;
return 0;
}
基于范围的for循环的一般格式:
for(ElemType val:array)
{
...//statement 循环体
}
ElemType:是范围变量的数据类型。它必须与数组(容器)元素的数据类型一样,或者是数组元素可以自动转换过来的类型。
val:是范围变量的名称。该变量将在循环迭代期间依次接收数组中的元素值。在第一次循环迭代期间它接收的是第一个元素的值;在第二次循环迭代期间,它接收的是第二个元素的值,以此类推。
array:是要让该循环进行处理的数组(容器)的名称。该循环将对数组中的每个元素迭代一次。
statement:是在每次循环选代期间要执行的语句。要在循环中执行更多的语句,则可以使用一组大括号来包围多个语句。与其他循环体一样,可以用continue来结束本次循环,也可以用break来跳出整个循环。
11.9.3基于范围的for循环的优势:
void Print(auto & xr)//此处传参时不能使用指针,即auto xr。因为指针具有容器的大小,而引用具有容器的两个属性(类型和大小)
{
for(auto x:xr)
{
cout<<x<<" ";
}
cout<<endl;
}
int main()
{
int ar[]={12,23,34,45,56,67};
double br[]={1.2,2.3,3.4,4.5,5.6,6.7,7.8};
char ch[]={'a','b','c','d'};
//使用一个函数打印三种不同类型的数组元素
Print(ar);//12 23 34 45 56 67
Print(br);//1.2 2.3 3.4 4.5 5.6 6.7 7.8
Print(cr);//a b c d
//使用基于范围的for对容器的元素进行更改
int* p1=ar;
int(&p2)[]=ar;//ok,但大小未知,不能用为基于范围的for
auto &p3=ar;//ok,p3为int(&)[6]类型
int(&p4)[6]=ar;//pk
for(auto &x:ar)//需要加上引用,否则无法改变ar容器中的元素。
//for(auto &x:p1)//error,这里不能将ar替换为p1,这是错误的,因为基于范围的for在使用时必须知道该容器的两个属性(类型和大小)
//for(auto &x:p2)//error,p2是引用,类型为int(&)[],大小未知
//for(auto &x:p3)//ok,p3类型为int(&)[6],具有类型和大小属性
//for(auto &x:p4)//ok,p4为ar容器的别名具有类型和大小两个属性,所以可以使用基于范围的for
{
if(x%2==0)
{
x+=10;
}
cout<<x<<" ";
}//22 23 44 45 66 67
Print(ar);//22 23 44 45 66 67
//当上面循环中没加引用,则ar容器中的值不会改变
//Print(ar);//12 23 34 45 56 67
return 0;
}
11.10指针空值--nullptrptr
//NULL是个宏定义,在C头文件(stddef.h)中有如下定义:
#undef NULL
#if defined (__cplusplus)//C++
#define NULL 0
#else
#define NULL((void*)0)//C
#endif
在C++中,NULL可能被定义为字面常量或者为无类型指针(void*)0常量,这样在使用时会出现问题,如下重载的例子:
void fun(char *p)
{
cout<<"fun char *p"<<endl;
}
void fun(int i)
{
cout<<"fun int i"<<endl;
}
int main()
{
fun(0);//fun int i,0为字面常量会调用第二个函数
fun(NULL);//fun int i,NULL在C++中会宏替换为0,这样依旧调用第二个函数。
fun((void*)0);//fun char *p,表明参数为指针类型
}
fun(NULL);表达的意思是想要调用第一个函数,但实际调动第二个函数。引起该问题的原因就是字面常量0的二义性,在C98标准中,字面常量0的类型既是一个整型,又是一个无类型指针,如果想要调用第一个函数就必须对字面常量0进行强转为((char *)0)或((void *)0),否则编译器总是优先将0看作字面常量。
为了解决这种问题,C++11标准就给出了nullptr,nullptr是一个所谓”指针空值类型“的常量。指针空值类型被名为nullptr_t。在头文件cstddef中可以看到如下定义:
typedef decltype(nullptr) nullptr_t;
在使用nullptr_t时必须#include,而nullptr不用。因为nullptr是关键字,而nullptr_t是推导而来的。
所有定义为 nullptr_t类型的数据都是等价的,行为也是完全一致。nullptr _t类型数据可以隐式转换成任意一个指针类型。nullptr_t类型数据不能转换为非指针类型使用
int main()
{
int a=nullptr;//error
int *p=nullptr;//ok
char* cp=nullptr;//ok
}
nullptr_类型数据不适用于算术运算表达式。
nullptr_t 类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者指针类型数据进行比较当且仅当关系运算符为==、<、>= 等时返回 true。
注意:
1、nullptr 是C++11新引入的关键字,是一个所谓"指针空值类型”的常量,在C++程序中直接使用。
2、在C++11中,sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同都 (4或8)。
3、为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
11.11typedef与using
一切合法的变量定义都可以通过typedef转成类型的声明
//二者等价
typedef unsigned int u_int;
using u_int=unsigned int;
//二者等价
typedef double array[10];
using array=double[10];
//二者等价
typedef void (*pfun)(int,int);
using pfun=void(*)(int,int);
void add(int a,int b)
{
return a+b;
}
int main()
{
u_int a=10;//a的类型为unsigned int
array b,c;//b和c的类型都为有十个元素的数组->double [10]
pfun d=add;//d的类型是参数为两个整型,返回值类型为void的函数指针->void(*)(int,int)
return 0;
}
using可以和模板一起使用,而typedef不能,如下:
template<class T,size_t N>//class T叫模板参数类型,size_t N为非类型,代表一个常量值,为整型常量
using array=T[N];
int main()
{
array<int,100> a;//a为int类型数组有100个元素
array<double,10> b;//b为double类型数组有10个元素
array<char,20> c;//c为char类型数组有20个元素
return 0;
}