类型别名
在C++中,我们可以使用typedef
关键字或using
关键字来创建类型别名。下面是两种方式的示例:
- 使用
typedef
关键字创建类型别名:
typedef int myInt;
typedef float myFloat;myInt a;//等价int a;
myFloat b;//等价float b;
- 使用
using
关键字创建类型别名:
using myInt = int;
using myFloat = float;myInt a;//等价int a;
myFloat b;//等价float b;
使用类型别名可以方便地重命名现有的类型,以提高代码的可读性和灵活性。例如,我们可以使用以下方式创建一个类型别名来代替std::vector<int>
:
using IntVector = std::vector<int>;
IntVector myVector;
注意点
我们在使用类型别名的时候,要特别注意将类型别名用于指针
我们先看个例子
我们发现啊,这个第二行这句还没编译就已经显示有错误了,这是为什么呢?
这是因为遇到使用类型别名的声明语句的时候,人们总是错误的尝试把类型别名替换为它本来的样子,也就是说我们把这下面两句话视作等效的
const A b="asss";
const char*b="asss";
但是事实并不是如此。
A实际上是指向char的指针,因此,const A就是指向char的常量指针,而非指向常量字符的指针。
也就是说,下面这两句是等效的
const A b = "asss";;char* const a = "asss";
我们再总结一下
声明语句中用到A时,其基本数据类型是指针。
可是用char*重写了声明语句后,数据类型就变成了char,*成了声明符的一部分。这样改写的结果是,const char成了基本数据类型
前后两种声明意义截然不同,前者声明了一个指向char的常量指针,改写后的形式则声明了一个指向const char的指针
关键字auto
在C++中,关键字auto
用于自动推导变量的类型。也就是说,编译器会根据变量的初始化值来确定变量的类型。
显然auto定义的变量必须要有初始值。
定义的具体语法如下:
auto variable = value;
其中variable
是变量名,value
是初始化值。
例如,以下是使用auto
定义变量的示例:
auto x = 10; // 推导x的类型为int,初始化为10
auto str = "Hello"; // 推导str的类型为const char*
auto vec = std::vector<int>(); // 推导vec的类型为std::vector<int>
auto sum = [](int a, int b) { return a + b; }; // 推导sum的类型为lambda表达式类型
在这些例子中,编译器会根据变量的初始化值自动推导出变量的类型,并将其赋值给相应的变量。
使用auto也可以在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始数据类型都必须一样
int a=9;
auto i=0,*p=&a;//正确,i是int型,p是int*型
auto b=0,c=9.0;//不正确,b和c的类型不正确
复合类型,常量和auto
编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。
第一点
首先,正如我们所熟知的,使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。
此时编译器以引用对象的类型作为auto的类型:
int i=0,&r=i;
auto a=r;//a是一个整数(r是i的别名,而i是一个整数)
也就是说,上面这些语句就等价于下面这个
int i=0,&r=i;
int a=i;
解决方法
那我们怎么使用auto来用于引用呢?
很简单,直接在auto后面加一个&就行了
int i=0,&r=i;
auto &a=i;
第二点
在介绍第二点时,我们先来了解一下顶层const和底层const
顶层const表明指针本身是个常量
int* const a;
底层const表示指针指向的对象是个常量
const int*a;
接下来进入正题
auto一般会忽略掉顶层const,同时保留底层const
什么意思呢?我们来看看一些例子
例子1
int i = 9;const int ci = i, & cr = ci;auto b = ci;//等价int b=i;auto c = cr;//等价int c=i;
我们可能会好奇
为啥b不是const int类型?c为啥不是const int&类型?
因为ci的顶层const特性被忽略掉了 ,c也是同样的道理
如果我们需要将b,c保留顶层const特性,则需要明确指出
int i = 9;const int ci = i, & cr = ci;const auto b = ci;//等价const int b=i;const auto c = cr;//等价const int c=i;
例子2
我们再看一个例子
int i = 9;
const int ci = i, & cr = ci;
auto d = &i;//等价int* d=&i;
auto e = &ci;//等价const int*e=&ci;
那为啥e就是const int*类型?
这是因为对常量对象取地址是一种底层const,会被保留下来
注意事项
以下是一些关于auto
关键字的注意事项:
-
auto
只能用于自动推导变量的类型,不能用于函数的返回值、函数参数或非静态成员变量的类型。 -
自动推导的类型将根据变量的初始化值进行推导。如果变量的初始化值不明确或不唯一,编译器将会报错。
-
auto
关键字可以与const
、&
等类型修饰符一起使用。例如:auto const&
表示自动推导一个const
引用。 -
当使用
auto
推导出的类型是一个模板类型时,编译器会根据变量的初始化值推导出具体的模板类型。 -
当使用
auto
推导出的类型是迭代器时,需要确保变量的初始化值是一个可迭代的容器,否则会导致编译错误。 -
自动推导类型的变量在编译时期确定类型,无法在运行时期更改类型。
总而言之,auto
关键字是一个方便的特性,可以根据初始化值自动推导变量的类型,减少了显式类型声明的冗余。但需要注意,过度使用auto
可能会降低代码的清晰度和可读性,所以在选择使用auto
时,要谨慎权衡代码的简洁性和可读性。
关键字decltype
有的时候,我们希望从表达式的类型推断出要定义的变量的类型,但是不是想用该表达式的值初始化变量。
为了满足这种情况,C++11引入了关键字decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
我们看个例子
decltype(f()) sum=x;//sum的类型就是f()返回值的类型
编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型。
换句话说,编译器为sum指定的类型是什么呢?就是假如f被调用的话将会返回的那个类型。
decltype和顶层const,引用
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 ints,y绑定到变量x
decltype(cj)z; //错误:z是一个引用,必须初始化
因为cj是一个引用,decltype(cj)的结果就是引用类型,因此作为引用的z必须被初始化。
需要指出的是,引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外,decltype直接给一个引用类型
decltype 和引用
左右值和decltype
如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型的情况有两种。
事实上,如果给decltype的表达式的结果是个左值,则decltype作用于该表达式(不是变量)得到一个引用类型。如果表达式是个右值,则直接得到对应的类型
假设p的类型是int*,因为解引用生成左值,所以decltype(*p)的结果是int&。
另一方面,因为取地址运算符生成右值,所以decltype(&p)的结果是int**,也就是说,结果是一个指向整型指针的指针。
我们再看些例子
// decltype 的结果可以是引用类型
int i=42, *p= &i, &r = i;decltype(r + 0) b; //正确:加法的结果是int,因此b是一个(未初始化的)intdecltype(*p) c; // 错误:c是int&,必须初始化
因为r是一个引用,是左值,因此decltype(r)的结果是引用类型。
如果想让结果类型是x所指的类型,可以把x作为表达式的一部分,如r+0,显然这个表达式的结果将是一个右值,所以类型是int。
另一方面,如果表达式的内容是解引用操作,表达式是左值,则decltype 将得到引用类型。因此,decltype(*p)的结果类型就是int&,而非int。
特殊情况
有一种情况需要特别注意:对于decltype 所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。
如果decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型;
如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型:
// decltype的表达式如果是加上了括号的变量,结果将是引用
decltype((i)) d; /1 错误:d是int&,必须初始化
decltype(i) e; // 正确:e是一个(未初始化的)int
切记:decltype((variable))(注意是双层括号)的结果永远是引用,而 decltype(variable)结果只有当 variable 本身就是一个引用时才是引用。
decltype和auto的区别
auto
和decltype
是C++11引入的两个关键字,用于类型推导和类型查询。它们的区别如下:
-
auto
用于自动推导变量的类型,而decltype
用于获取表达式的类型。 -
auto
在编译时期根据初始化表达式推导出变量的类型,而decltype
在编译时期查询表达式的类型,不会执行表达式。 -
auto
可以用于初始化变量,而decltype
不能直接用于变量的初始化。 -
auto
可以推导出值类型、指针类型、引用类型等,而decltype
更灵活,可以推导出表达式的类型,包括值类型、指针类型、引用类型、函数类型等。
下面是一些示例来说明它们的区别:
int x = 10;
auto a = x; // 推导出a的类型为int
decltype(x) b = x; // 查询x的类型,b的类型为intint& y = x;
auto c = y; // 推导出c的类型为int,而不是int&
decltype(y) d = y; // 查询y的类型,d的类型为int&int foo(int a, int b);
auto result = foo(1, 2); // 推导出result的类型为函数返回类型
decltype(foo(1, 2)) e = foo(1, 2); // 查询foo(1, 2)的类型,e的类型为int
总而言之,auto
用于方便地推导变量的类型,而decltype
用于查询表达式的类型。在实际使用中,根据需要选择合适的关键字。