目录
- 类型别名
-
- 什么是类型别名
- typedef关键字
-
- typedef的使用
- using关键字
-
- using的使用
- using和typedef区别
- 类型别名的注意事项
- auto类型说明符
-
- auto的使用
- decltype类型指示符
-
- decltype的使用
- decltype和auto的区别
- 参考
类型别名
什么是类型别名
类型别名是一个名字,使用类型别名可以让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名
目前有两种定义类型别名的方法,分别是typedef
和using
typedef关键字
关键字typedef作为声明语句中的基本数据类型的一部分出现。含有typedef的声明语句定义的不再是变量而是类型别名。
typedef的使用
- typedef普通类型
typedef unsigned int u_int;//u_int是 unsigned int的同义词
typedef u_int *p;//p是unsigned int*的同义词
/*================使用===================*/
u_int i=10; //相当于unsigned int i=10
p p1=&i; //相当于unsigned int*p1=&i
int j=20;
p1=&j; //错误,不能将int*转换为unsigned int*
- typedef结构体
可以使用 typedef 来为用户自定义的数据类型取一个新的名字。比如,可以对结构体使用 typedef 来给定义的数据类型起一个新名字,然后使用这个新名字来直接定义结构变量。
typedef struct people {
string name;int age;string gender;
}student;
/*================使用===================*/
student stud1;//相当于people stud1
-
typedef函数指针
typedef经常用于替换一些复杂的函数指针,说到函数指针,就必须先了解一下函数指针和指针函数。在一文搞懂函数指针这篇博客中详细介绍了什么是函数指针,什么是指针函数。回到typedef上,用typedef来定义这些复杂的类型,比如上面的函数指针
声明格式:typedef 返回类型 (*新名字) (参数表)
typedef int(*func)(int); //定义int(*)(int)的函数指针的别名为func func pfun; //直接用别名func定义int(*)(int)类型的变量int getVal(int n) //定义一个形参为int,返回值为int的函数 { cout << n << endl;return n; } /*================使用===================*/ int main() { pfun = getVal; //把函数赋给指针(*pfun)(5); //调用函数getVal,输出5return 0; }
using关键字
using的使用
-
使用命名空间
为了防止名字冲突从而有了命名空间,我们可以通过使用using声明来指定使用哪个命名空间,这样后面再使用到该命名空间中的内容则无需加前缀命名空间名字::
using namespace std;//释放整个命名空间到当前作用域 using std::cout; //释放某个变量到当前作用域
-
类型别名
using起别名的作用和typedef十分相似,都是使用别名声明来定义类型的别名,其使用方式也十分简单/* using 别名=原先类型 */ using student = people;//使用student代替people来声明结构体变量
-
修改继承的成员权限
可以修改继承自基类中成员变量或函数的访问权限,比如私有继承基类,则基类的成员在派生类中成了私有成员,但是我们想改成公有的,则可以使用using- 在基类中的private成员,不能在派生类中任何地方使用using
- 在基类中的protected成员,可以在派生类中任何地方使用using。当在派生类中public区域内使用using,则在类定义体外,可以用派生类对象访问该成员,但不能用基类对象访问该成员。当在派生类中protected区域内使用using,则在类定义体外,不能用派生类和基类对象访问该成员,但可以被继承下去。当在派生类中private区域内使用using,则在类定义体外,该成员被当做派生类的私有成员看待
- 在基类中的public成员,可以在派生类中任何地方使用using。各区域效果参考基类中的protected成员
class Base{ public:void funcB(int val) { cout << "Base:" << val << endl;cout << "num:" << num << endl;} protected:int num = 10; };class Derived :private Base{ public://需要在Derived的public下释放才能对外使用using Base::funcB;//只是声明,不参与形参的指定using Base::num;}; int main() { Base b;Derived da;b.num = 20;//错误:Base类的num成员不可访问b.funcB(5); //输出Base:5 num:10da.num = 20; //正确:在派生类中将基类的num成员的访问权限提高到了publicda.funcB(10);//输出Base:10 num:20return 0; }
using和typedef区别
那很多小伙伴会问那using和typedef有什么区别呢
- 首先从主观方面,使用using相比typedef更好理解,更易使用
using namespace std;
//使用typedef
typedef unique_ptr<unordered_map<string, string>> UPtrMapSS;
//使用using
using UPtrMapSS = unique_ptr<unordered_map<string, string>>;
当然这个仁者见仁智者见智,在这种情况下,其实差别不是很大,我们再看一个例子
using namespace std;
//使用typedef
typedef void(*FP) (int, const string&);
//使用using
using FP = void(*) (int, const string&);
上面是使用typedef和using方式两种方式,用于给函数指针起别名。对于第一次读到这样代码,并且不了解using的童鞋也能很容易知道FP是一个别名,using的写法把别名的名字强制分离到了左边,而把别名指向的放在了右边,中间用 = 号等起来,非常清晰
- typede不支持模板别名
首先对于使用using来进行模板别名的声明,从下面代码看出来使用起来非常自然。
//使用using
template <typename T>
using Vec = vector<T, vector<T>>;
Vec<int> vec;
再看看使用typedef进行模板别名的声明
//使用typedef
template <typename T>
typedef vector<T, vector<T>> Vec;
Vec<int> vec;//出错
当我们用编译器对上述代码进行编译的使用,就会报错,错误如下,可以看到typedef不支持模板别名的声明
假如我们偏要使用typedef实现模板别名的功能,就需要再包装一层
template <typename T>
struct Vec
{
typedef vector<T, vector<T>> type;
};
Vec<int>::type vec;
可以看到,这样声明起来非常别扭,而且把这样的类型用在模板类或者进行参数传递的时候,你需要使用typename强制指定这样的成员为类型,而不是说这样的::type是一个静态成员亦或者其它情况可以满足这样的语法,如:
template <typename T>
class A
{
typename Vec<T>::type vec;
};
然而,如果是使用using语法的模板别名,你则完全避免了因为::type引起的问题,也就完全不需要typename来指定了。
template <typename T>
class A
{
Vec<T> vec;
};
所以相比于传统的typedef声明别名,更推荐使用using
类型别名的注意事项
如果某个类型别名指代的是复合类型或常量,那么把它用到声明语句里就会产生意想不到的后果。
typedef char* cp; //cp是类型char*的别名,即cp是指向char的指针
const cp c_cp=0 ; //cp是指向指向char的常量指针,并不是指向常量的指针
const cp *p=nullptr; //p是一个指针,指向char的常量指针
遇到一条使用了类型别名的声明语句时,我们通常会错误的把类型别名替换成它本来的样子,比如上面的
const cp c_cp=0
,可能就会被替换成const char* c_cp=0
,按照我在C++ const和constexpr详解这篇博客中介绍的方法,它的确死一个指向常量的指针,但是这里特别强调一下,这样子是错误的,声明语句用到cp时,其基本数据类型是指针,而替换成char *后,基本数据类型是const char,*成了声明符的一部分,前后两种声明含义截然不同。而const是对给定类型的修饰。cp由于是指针,所以c_cp是指向char的常量指针
auto类型说明符
有时我们在声明变量的时候想清楚地知道表达式的类型,但是在某些复杂系统中,很难一眼看出,所以为了解决这个问题,引入了auto类型说明符,它能让编译器替我们去分析表达式所属的类型。简单地说就是,auto能让编译器通过初始值来推算变量的类型
auto的使用
- auto定义的变量必须有初始值
int val1 = 5;
double val2 = 10.2;
auto val3 = val1 + val2; //val3初始化为val1和val2相加的结果,val3类型位double
auto val4; //错误val4没有初始值
假如val1和val2是某个类的对象,且重载了‘+’运算符,则val3的类型就是该类
- 一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样
auto i = 10, *p = &i;//正确:i是整数,p是指向整数的指针,*不属于基本数据类型,只是声明符的一部分
auto val1=10,val2=10.5 //错误,val1和val2的类型不一致
- 引用作为auto初始值,实际使用的是引用所绑定的对象
int i = 0, &r = i;//r是整数i的引用
auto a = r;//a是一个整数;由于r是引用,所以相对于auto a=i;
- auto一般会忽略顶层const,保留底层const
关于顶层const和底层const可以在C++ const和constexpr详解这篇博客中有介绍。
const int i = 10;//i是值为10的常量,且是顶层const
const int&r = i;//声明引用的const都是底层const,因为不允许改变其绑定的对象
auto a = i;//a的类型为int(i的顶层const被忽略)
auto b = r;//b的类型是int,(r是i的引用,相当于auto b=i)
auto c = &i;//c是一个指向常量的指针,即const int*(对常量对象取地址是一种底层const)
当然我们也可以将引用的类型设为auto,原来的初始化规则任然适用,具体规则可以看,指针和引用这篇博客
这里注意,如果设置一个类型为auto引用时,初始值中顶层const属性仍然保留,比如将上面代码中auto a = i改成auto &a=i,那么a就是一个整型常量的引用
decltype类型指示符
假如我们希望从表达式的类型推断出要定于的变量的类型,但是不想用该表达式的值初始化变量时。可以考虑使用decltype。
decltype的使用
- 对于表达式是函数,且有参数列表则decltype结果是函数的返回类型;如果没有参数列表,即只有函数名,则返回函数的类型;
int func(int val);
decltype(func(5))x; //x的类型是函数f的返回类型,即int
decltype(func)y; //y的类型是函数f的类型,即int(int)
编译器并不实际调用函数func,而是使用当调用发生时func的返回值类型作为sum的类型。
- 对于表达式的内容是解引用操作,则decltype得到引用类型
int i=10;
int*p=&i;
decltype(*p)c;//错误:c是int&,必须初始化
解引用指针可以得到指针所指的对象,而且还能给这个对象赋值,所以decltype(*p)的结果是int&,而不是int
- 对于表达式是引用的,则decltype结果就是引用,如果想使用引用所绑定的对象,则需要把引用作为表达式的一部分
int i=10;
int&r=i;
decltype(r+0) a;//正确:a的类型是int
decltype和auto的区别
- 如果decltype使用的表达式是一个变量,则decltype返回该变量的类型,不会像auto一样会忽略顶层const和使用引用绑定的对象作为初始值。
const int i = 10;//i是值为10的常量,且是顶层const
const int&r = i;//声明引用的const都是底层const,因为不允许改变其绑定的对象
decltype(i) a = 0;//a的类型是const int
decltype(r) b = a;//b的类型是const int&,b绑定到变量a
decltype(r) c;//错误,c是一个引用,必须初始化
需要注意的是,引用从来都是作为其绑定对象的别名出现,只有用在decltype处是一个例外
- decltype的结果类型与表达式形式密切相关。对于decltype来说,如果变量名加上一对括号,则得到的类型与不加括号是不同的。
int i = 10;
decltype(i) a;//正确:a的类型是int
decltype((i)) b;//错误:b的类型是int&,必须初始化
decltype((variable)) (注意是双括号)的结果永远是引用,而decltype(variable) 的结果只有当variable本身就是引用时才是引用,否则就是该变量的类型
参考
《C++ Primer 第五版》
C++ #define,typedef,using用法区别-C++11使用using定义别名
Effective Modern C++ Note 02
C++ using用法总结