当前位置: 代码迷 >> 综合 >> C++关键字系列【一】:typedef、using、auto、decltype
  详细解决方案

C++关键字系列【一】:typedef、using、auto、decltype

热度:35   发布时间:2023-11-24 01:41:09.0

目录

  • 类型别名
    • 什么是类型别名
    • typedef关键字
      • typedef的使用
    • using关键字
      • using的使用
    • using和typedef区别
    • 类型别名的注意事项
  • auto类型说明符
    • auto的使用
  • decltype类型指示符
    • decltype的使用
  • decltype和auto的区别
  • 参考

类型别名

什么是类型别名

类型别名是一个名字,使用类型别名可以让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名
目前有两种定义类型别名的方法,分别是typedefusing

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用法总结

  相关解决方案