- using
using的用法:
using 类型名 = 定义的类型的别名;
using SI = Sales_item; //SI是Sales_item的同义词
指针、常量和类型别名:如果某个类型别名指代的是复合类型或常量,那么把它用到声明语句里就会产生意想不到的后果
例如下面的声明语句用到了类型pstring,它实际上是类型char*的别名
typedef char* pstring;
const pstring cstr = 0; //cstr是指向char的常量指针 而不是指向常量char的指针
const pstring* ps; //ps是一个指针,它的对象是指向char的常量指针
上述俩条声明语句的基本类型都是const pstring,和过去一样,const是对给定的类型的修饰,pstring实际上是指向char的指针,也就是const修饰的是指针,因此,const pstring就是指向char的常量指针,而不能直接把类型别名替换来思考,所以它不是指向常量字符的指针,遇到一条使用了类型别名的声明语句时,人们往往会错误地尝试把类型别名替换成它本来的样子,以理解
该语句的含义。const char* cstr = 0; //是对const pstring cstr的错误理解
再强调一遍:这种理解是错误的,声明语句中用到了pstring时,其基本数据类型是指针,可是用char*重写了声明语句后,数据类型就变成了char*,*成了声明符的一部分。这样改写的结果是,const char成了基本数据类型,前后俩种声明含义截然不同,前者声明了一个指向char的常量指针,改写后的形式则声明了一个指向const char的指针
返回数组指针:因为数组不能被拷贝,所以函数不能返回数组,不过函数可以返回数组的指针或引用,可以使用类型别名来简化
typedef int arrT[10]; //arrT是一个类型别名,它表示的类型是含有10个整数的数组
using arrT = int[10]; //arrT的等价声明
arrT* func(int i); //func返回一个指向含有10个整数的数组的指针
- auto
auto:用auto就能让编译器替我们去分析表达式所属的类型,auto定义的变量必须有初始值,使用auto也能在一条语句中声明多个变量,因为一条声明语句中只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样
auto i = 0, *p = &i; //正确 i是整数、p是整型指针
auto sz = 0, pi = 3.14; //错误 sz和pi的类型不一致
复合类型、常量和auto:首先正如我们所熟知的,使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值,此时编译器以引用对象的类型作为auto的类型
所以想用auto来推导出引用类型时,必须适用auto&,否则推导出的类型是非引用类型的。
int i = 0, &r = i;
auto a = r; //a是一个整数(r是i的别名,而i是一个整数)
int i = 0, &r = i;
auto a = r; //发生拷贝a为r的一个拷贝
auto& a2 = r; //ar为r的一个别名
r += 10;
cout << i << endl; //10
cout << r << endl; //10
cout << a << endl; //0
cout << a2 << endl; //10
如图所示a是一份拷贝,它的地址和r、i、a2的地址不同。
auto不能推导引用类型但是可以推导出指针类型
int * foo(int* i)
{
return i;
}
int main()
{
auto p1 = new int(1000); //p1为int*类型
cout << p1 << endl; //打印为地址
cout << *p1 << endl; //1000
int *p2 = new int(100);
auto p = foo(p2); //p为int*类型
*p2 = 200;
cout << *p << endl; //200
}
auto一般会忽略掉顶层const,同时底层const则会保留下来
const int ci = i, &cr = ci;
auto b = ci; //b是一个整数(ci的顶层const特性被忽略掉了)
auto c = cr; //c是一个整数(虽然cr是底层const,但真正参与初始化的是引用对象(ci)的值,ci本身是一个顶层const
auto d = &i; //d是一个整型指针
auto e = &ci;//e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
如果希望推断出的auto类型是一个顶层const需要明确指出
const auto f = ci; //ci的推演类型是int,f是const int
- decltype
有时会遇到这种情况:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。decltype的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到他的类型,却不实际计算表达式的值
decltype(f())sum = x; //sum的类型就是函数f的返回类型
换句话说,编译器为sum指定的类型就是假如f被调用的话会返回的那个类型
decltype 处理顶层const和引用的方式与auto有些不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //x的类型是const int
decltype(cj) y = x; //y的类型是const int& y绑定到变量x
decltype(cj) z; //错误 z是一个引用必须初始化
需要指出的是,引用从来都作为其所指对象的同义词出现,只有用在decltype处理是一个例外,decltype和引用:有些表达式将向decltype返回一个引用类型,代表着该表达式的结果对象能作为一条赋值语句的左值
//decltype的结果可以是引用类型
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; //正确 加法的结果是int,因此b是一个(未初始化的)int
decltype(*p) c; //错误 c是int&,必须初始化
decltype(p) d; //正确 d是int*
因为r是一个引用,因此decltype(r)的结果是引用类型,如果想让结果类型是r所指的类型,则可以把r作为表达式的一部分,如r + 0,显然这个表达式的结果将是一个具体指而非一个引用,另一方面,如果表达式的内容是解引用操作,则decltype将得到引用类型,正如我们所熟悉的那样解引用指针可以得到指针所指的对象,而且还能给这个对象赋值,因此decltype(*p)的结果类型就是int&,而非int
decltype和auto的另一处重要区别是,decltype的结果类型与表达式形式密切相关,有一种情况需要特别注意,对于decltype所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同,如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型,如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式,变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型,decltype的表达式如果是加上了括号的变量,结果将是引用,切记:decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用
decltype((i)) d; //错误 d是int& 必须初始化
decltype(i) e; //正确 e是一个(未初始化的)int
decltype与数组:还有一种情况,如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型
例如下面的函数返回一个指针,该指针根据参数i的不同指向俩个已知数组中的某一个
int odd[] = { 1,3,5,7,9 };
int even[] = { 0,2,4,6,8 };
//返回一个指针,该指针指向含有5个整数的数组
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even; //返回一个指向数组的指针
}
decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号
- 尾置返回类型-->
任何函数的定义都能使用位置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用,尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto
//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i)->int(*)[10];