zoukankan      html  css  js  c++  java
  • C++ #define,typedef,using用法区别-C++11使用using定义别名(替代typedef)

    大家都知道,在 C++ 中可以通过 typedef 重定义一个类型:

    typedef unsigned int uint_t;

    被重定义的类型并不是一个新的类型,仅仅只是原有的类型取了一个新的名字。因此,下面这样将不是合法的函数重载:

    void func(unsigned int);
    void func(uint_t);  // error: redefinition

    使用 typedef 重定义类型是很方便的,但它也有一些限制,比如,无法重定义一个模板。

    想象下面这个场景:

    typedef std::map<std::string, int> map_int_t;
    // ...
    typedef std::map<std::string, std::string> map_str_t;
    // ...

    我们需要的其实是一个固定以 std::string 为 key 的 map,它可以映射到 int 或另一个 std::string。然而这个简单的需求仅通过 typedef 却很难办到。

    因此,在 C++98/03 中往往不得不这样写:

    1. template <typename Val>
    2. struct str_map
    3. {
    4. typedef std::map<std::string, Val> type;
    5. };
    6. // ...
    7. str_map<int>::type map1;
    8. // ...

    一个虽然简单但却略显烦琐的 str_map 外敷类是必要的。这明显让我们在复用某些泛型代码时非常难受。

    现在,在 C++11 中终于出现了可以重定义一个模板的语法。请看下面的示例:

    1. template <typename Val>
    2. using str_map_t = std::map<std::string, Val>;
    3. // ...
    4. str_map_t<int> map1;

    这里使用新的 using 别名语法定义了 std::map 的模板别名 str_map_t。比起前面使用外敷模板加 typedef 构建的 str_map,它完全就像是一个新的 map 类模板,因此,简洁了很多。

    实际上,using 的别名语法覆盖了 typedef 的全部功能。先来看看对普通类型的重定义示例,将这两种语法对比一下:

    1. // 重定义unsigned int
    2. typedef unsigned int uint_t;
    3. using uint_t = unsigned int;
    4. // 重定义std::map
    5. typedef std::map<std::string, int> map_int_t;
    6. using map_int_t = std::map<std::string, int>;

    可以看到,在重定义普通类型上,两种使用方法的效果是等价的,唯一不同的是定义语法。

    typedef 的定义方法和变量的声明类似:像声明一个变量一样,声明一个重定义类型,之后在声明之前加上 typedef 即可。这种写法凸显了 C/C++ 中的语法一致性,但有时却会增加代码的阅读难度。比如重定义一个函数指针时:

    typedef void (*func_t)(int, int);

    与之相比,using 后面总是立即跟随新标识符(Identifier),之后使用类似赋值的语法,把现有的类型(type-id)赋给新类型:

    using func_t = void (*)(int, int);

    从上面的对比中可以发现,C++11 的 using 别名语法比 typedef 更加清晰。因为 typedef 的别名语法本质上类似一种解方程的思路。而 using 语法通过赋值来定义别名,和我们平时的思考方式一致。

    下面再通过一个对比示例,看看新的 using 语法是如何定义模板别名的。

    1. /* C++98/03 */
    2. template <typename T>
    3. struct func_t
    4. {
    5. typedef void (*type)(T, T);
    6. };
    7. // 使用 func_t 模板
    8. func_t<int>::type xx_1;
    9. /* C++11 */
    10. template <typename T>
    11. using func_t = void (*)(T, T);
    12. // 使用 func_t 模板
    13. func_t<int> xx_2;

    从示例中可以看出,通过 using 定义模板别名的语法,只是在普通类型别名语法的基础上增加 template 的参数列表。使用 using 可以轻松地创建一个新的模板别名,而不需要像 C++98/03 那样使用烦琐的外敷模板。

    需要注意的是,using 语法和 typedef 一样,并不会创造新的类型。也就是说,上面示例中 C++11 的 using 写法只是 typedef 的等价物。虽然 using 重定义的 func_t 是一个模板,但 func_t<int> 定义的 xx_2 并不是一个由类模板实例化后的类,而是 void(*)(int, int) 的别名。

    因此,下面这样写:

    void foo(void (*func_call)(int, int));
    void foo(func_t<int> func_call);  // error: redefinition

    同样是无法实现重载的,func_t<int> 只是 void(*)(int, int) 类型的等价物。

    细心的读者可以发现,using 重定义的 func_t 是一个模板,但它既不是类模板也不是函数模板(函数模板实例化后是一个函数),而是一种新的模板形式:模板别名(alias template)。

    其实,通过 using 可以轻松定义任意类型的模板表达方式。比如下面这样:

    template <typename T>
    using type_t = T;
    // ...
    type_t<int> i;

    type_t 实例化后的类型和它的模板参数类型等价。这里,type_t<int> 将等价于 int。

    http://c.biancheng.net/view/3730.html

    C++ #define,typedef,using用法区别

    一.#define

    #define 是宏定义命令,宏定义就是将一个标识符定义为一个字符串,源程序中的该标识符均以指定的字符串来代替,是预编译命令,因此会在预编译阶段被执行

    1.无参宏定义

    无参宏的宏名后不带参数
    其定义的一般形式为:

    #define  标识符  字符串  

    其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
    例如:
    #define Success 13

    这样Success 就被简单的定义为13

    带#的都是预处理命令, 预处理命令后通常不加分号。但并不是所有的预处理命令都不能带分号,#define就可以,由于#define只是用宏名对一个字符串进行简单的替换,因此如果在宏定义命令后加了分号,将会连同分号一起进行置换,如:

    #define Success 13;

    这样也不会报错,只不过Success就被定义为了 13;   既13后面跟一个分号

    2.有参宏定义

    C++语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。
    对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
    带参宏定义的一般形式为:

     #define  宏名(形参表)  字符串

    在字符串中含有各个形参。在使用时调用带参宏调用的一般形式为:宏名(实参表); 如:

    1. #define add(x,y) (x+y) //此处要打括号,不然执行2*add(x,y) 会变成 2*x + y
    2. int main()
    3. {
    4. std::cout << add(9,12) << std::endl;//输出21
    5. return 0;
    6. }

    这个“函数”定义了加法,但是该“函数”没有类型检查,有点类似模板,但没有模板安全,可以看做一个简单的模板。

    #define也可以定义一些简单的函数,但因为只是简单的替换,有时后会发生一些错误,谨慎(不建议)使用,在替换列表中的各个参数最好使用圆括号括起来

    4.宏定义中的条件编译

    在大规模的开发过程中,头文件很容易发生嵌套包含,而#ifndef 配合 #define ,#endif 可以避免这个问题

    1. #ifndef DATATYPE_H
    2. #define DATATYPE_H
    3.  
    4. int a = 0;
    5. #endif

    这里的#ifndef #define #endif 其实和#pragma once 作用类似,都是为了防止多次编译一个头文件

    5.跨平台

    在大规模的开发过程中,特别是跨平台和系统的软件里,需要用到#define

    1. #ifdef WINDOWS
    2. ......
    3. (#else)
    4. ......
    5. #endif
    6. #ifdef LINUX
    7. ......
    8. (#else)
    9. ......
    10. #endif

    可以在编译的时候通过#define设置编译环境
     

    6.宏定义中的特殊操作符

    define 中的特殊操作符有#,## 和  … and __VA_ARGS__

    简单来说#,##都是类似于变量替换的功能,这里不赘述

    __VA_ARGS__ 是一个可变参数的宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持
    实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,替换省略号所代表的字符串,如:

    1. #define PR(...) printf(__VA_ARGS__)
    2. int main()
    3. {
    4. int wt=1,sp=2;
    5. PR("hello "); //输出:hello
    6. PR("weight = %d, shipping = %d",wt,sp); //输出:weight = 1, shipping = 2
    7. return 0;
    8. }

    这里再介绍几个系统的宏:
     __FILE__ 宏在预编译时会替换成当前的源文件cpp名
    __LINE__宏在预编译时会替换成当前的行号
    __FUNCTION__宏在预编译时会替换成当前的函数名称
    __DATE__:进行预处理的日期(“Mmm dd yyyy”形式的字符串文字)
    __TIME__:源文件编译时间,格式微“hh:mm:ss”

    这些是在编译器里定义的,所以F12并不能转到定义。在打印日志的时候特别好用。如:

    二.typedef

    C 语言提供了 typedef 关键字,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。

    1.最简单的类型替换

    下面的实例为单字节数字定义了一个新类型UBYTE:
    typedef unsigned char   UBYTE;
    在这个类型定义之后,标识符 UBYTE 可作为类型 unsigned char 的缩写,例如:
    UBYTE b1, b2;

    按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但也可以使用小写字母。

    2.结构体&自定类型义替换

    也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:

    1. #include <stdio.h>
    2. #include <string.h>
    3.  
    4. typedef struct LearningBooks
    5. {
    6. char title[50];
    7. char author[50];
    8. } Book;
    9.  
    10. int main( )
    11. {
    12. Book book;
    13.  
    14. strcpy( book.title, "c++");
    15. strcpy( book.author, "kevin");
    16. book.book_id = 0001;
    17.  
    18. printf( "书标题 : %s ", book.title);
    19. printf( "书作者 : %s ", book.author);
    20. return 0;
    21. }

    3.typedef函数指针用法

    typedef经常用于替换一些复制的函数指针,说到函数指针,就必须先了解一下函数指针和指针函数。了解了之后在来讲typedef

    1)指针函数

    指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针,声明格式如下:

    类型标识   *函数名(参数表)
    int *getptr(int x);                  //声明一个参数为int , 返回值为int * 的 指针函数

    首先它是一个函数,只不过这个函数的返回值是一个指针。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量,如下:

    1. double *getptr(double x) //声明同时定义一个返回值为double* 的指针函数
    2. {
    3. return &x;
    4. };
    5.  
    6. int main()
    7. {
    8. double *p;
    9. p = getptr(7); //返回double* 赋给p
    10. return 0;
    11. }

    2)函数指针

    函数指针是指向函数地址的指针变量,它的本质是一个指针变量,函数也是有分配内存的,所以指向函数地址的指针,即为函数指针,声明函数指针格式如下:

    类型说明符 (*变量名)(参数)
    int (*funp)  (int x);                          // 声明一个 指向参数类型为 int ,返回类型为 int 的函数 的 指针
    int *(*funp)  (int x);                          // 声明一个 指向参数类型为 int ,返回类型为 int* 的函数 的 指针

    其实这里的func不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明必须和它指向函数的声明保持一致,如:

    1. double (*funp)(double x); // 声明一个 指向参数类型为 int ,返回类型为 int 的函数 的 指针
    2. double *(*funp1)(double x); // 声明一个 指向参数类型为 int ,返回类型为 int* 的函数 的 指针
    3.  
    4. int main()
    5. {
    6. funp = getnum; //报错,因为类型对不上,右边类型是double *(*)(double x),左边是double(*)(double x)
    7. funp1 = getnum; //正确,两边类型都是double *(*)(double x)
    8. return 0;
    9. }

    看到这里应该能明白了函数指针的写法,再剖析一遍int (*funp)  (int x);这是我的理解:
           首先,变量名为funp,而funp左边有个*,代表funp是指针类型,这个*很关键,之前理解不准确,以为这个*是函数返回值,其实这个*是修饰funp本身的,代表funp是指针类型,至于具体是什么指针类型(是int?,char?,还是函数?)要结合整个表达式看,然后再看右边有括号,说明是个函数,这个函数的参数类型为int,既funp是一个指向函数的指针,再看最左边的int,那就说明func是一个指向参数类型为int,返回值为int的函数 的指针。
    那么int *(*funp)  (int x);就能瞬间秒懂了,funp是一个指向参数类型为int,返回值为int*的函数 的指针

    再来看看网上流行的“右左法则”解析:

    理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。还是那个例子:

    int (*func)(int *p);

    首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int

    再比如:  

    int (*func[5])(int *);

    看到数组了,所以func是一个数组呢还是一个指针呢?先别着急,不妨回顾下数组的定义

    int a[10] ;  //声明一个int型数组a,a有10个元素 ,     这个很简单吧,在进一步
    int *a[10] ;   //声明一个int*型数组a,a有10个元素

    所以这里可以看出来在 int *a[10] ; 中,a[10]是一个整体,*是修饰a[10]的,而不是修饰a的,如果修饰a,那么a就是一个指针了,但实际上a是数组,所以*是修饰a[10]的,意味着a[10]这个数组中的元素类型为指针。这是C++决定的,[]的优先级较高,把a[]当成一个整体来看,也就是说在类似数组的定义中,如:
    类型   变量名[数量n];    类型是用来表示数组元素的,而变量名也就是a本身是一个数组。

    那么问题来了?怎么表达一个指向数组的指针呢?怎么越扯越远了........

    数组指针(也称行指针)
    定义格式如下:
    类型 (*变量名)[数量n];
    int (*p)[5];               

    因为()优先级高,所以(*p)代表p是一个指针,指向一个int型的一维数组,这个一维数组的长度是5,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。所以 int (*p)[5]; 的含义是定义一个数组指针,指向含5个元素的一维int型数组。

    如要将二维数组赋给一指针,应这样赋值:
    int a[3][4];  //定义一个二维int型数组
    int (*p)[4];  //定义一个数组指针,指向含4个元素的一维int数组。
     p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
     p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

    所以数组指针也称指向一维数组的指针,亦称行指针

    好了,回到上面讨论的int (*func[5])(int *);
    再来对比
    int *a[10] ;                  //a是一个数组,数组元素类型为int*型指针
    int (*func[5])(int *);     //func是一个数组,数组元素类型是什么?   看到*应该知道了是指针类型,所以答案应该是某某类型的指针,什么类型的指针呢?

    再看完整的右左法则”解析:
    func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,把func[]看成一个整体)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int.

    所以在分析复制的声明时,优先级很关键()比较高,其次是[],再然后是*

    终于扯完了,讲完函数指针,指针函数,数组指针,指针数组了,最后回到我们的typedef上
    用typedef来定义这些复杂的类型,比如上面的函数指针,格式为:

    typedef  返回类型  (*新类型名)  (参数表)
    typedef  int  (*PTRFUN)  (int);

    1. typedef char(*PTRFUN)(int); //定义char(*)(int)的函数指针 的别名为PTRFUN
    2. PTRFUN pfun; //直接用别名PTRFUN定义char(*)(int)类型的变量
    3.  
    4. char getchar(int a) //定义一个形参为int,返回值为char的函数
    5. {
    6. return '1';
    7. }
    8. int main()
    9. {
    10. pfun = getchar; //把函数赋给指针
    11. (*pfun)(5); //用函数指针取函数,调用
    12. return 0;
    13. }

    这样能简化代码,隐藏难懂的声明类型,方便阅读

    三.using

    看某些项目源码时,里面使用了很多using关键字。之前对using关键字的概念,一直停留在引入命名空间中。其实,using关键字还些其他的用途。

    1.声明命名空间

    using namespace std;

    2.给类型取别名, 在C++11中提出了通过using指定别名

    例如:
    using 别名 = 原先类型;
    using ty=  unsigned char;

    以后使用ty  value; 就代表 unsigned char  value;

    这不就跟typedef没啥区别吗,那么using 跟typedef有什么区别呢?哪个更好用些呢?

    例如:
    typedef  std::unique_ptr<std::unordered_map<std::string, std::string>>   UPtrMapSS;

    而用using:
    using  UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;

    从这个例子中,可能看不出有什么明显的好处,但是从我个人角度,更倾向于using,因为更好理解,但是并没有要强烈使用using的感觉
    再来看下:

    typedef void (*FP) (int, const std::string&);

    这就是上面typedef 函数指针的用法,FP代表着的是一个函数指针,而指向的这个函数返回类型是void,接受参数是int, const std::string&。那么,让我们换成using的写法:

    using FP = void (*) (int, const std::string&);

    我想,即使第一次读到这样代码,并且不了解using的童鞋也能很容易知道FP是一个别名,using的写法把别名的名字强制分离到了左边,而把别名指向的放在了右边,中间用 = 号等起来,非常清晰

    那么,若是从可读性的理由支持using,力度也是稍微不足的。来看第二个理由,那就是举出了一个typedef做不到,而using可以做到的例子:alias templates, 模板别名

    template <typename T>
    using Vec = MyVector<T, MyAlloc<T>>;

        // usage
    Vec<int> vec;

    这一切都会非常的自然。


    那么,若你使用typedef来做这一切:

        template <typename T>
        typedef MyVector<T, MyAlloc<T>> Vec;
         
        // usage
        Vec<int> vec;

    当你使用编译器编译的时候,将会得到类似:error: a typedef cannot be a template的错误信息。。

    所以我个人认为using和typedef都有各自的用处,并没有谁好谁坏,而标准委员会他们的观点是,在C++11中,鼓励用using,而不用typedef的

    最后,p是什么类型

    void *(*(*p)[10]) (int*)

    答案:

    p是个指针,指向一个指针数组,数组的元素是指针,指向 返回值为void*,形参为int*的函数

    更深入的理解:https://blog.csdn.net/hai008007/article/details/80651886

    C++11里使用using代替typedef

    例1:

    1. void f() {}
    2. int main()
    3. {
    4. using FunctionPtr = void (*)(); //相当于 typedef void (*FunctionPtr)();
    5.  
    6. FunctionPtr ptr = f;
    7. }

    例2:
    typedef unsigned char u1;
    typedef unsigned short u2;


    using u4 = uint32_t;
    using u8 = uint64_t;


    例3:
    using line_no = std::vector<string>::size_type;
    相当于:typedef vector<string>::size_type line_no;


    例4:
    typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;
    using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;

    C++标准模板库从入门到精通 
    http://edu.csdn.net/course/detail/3324
    跟老菜鸟学C++
    http://edu.csdn.net/course/detail/2901

    Visual Studio 2015开发C++程序的基本使用 
    http://edu.csdn.net/course/detail/2570
    在VC2015里使用protobuf协议
    http://edu.csdn.net/course/detail/2582

     typedef bool (*ConvertFunc)(const std::string& from, const std::string& to, int flag);   

    等于

    using ConvertFunc = bool(*)(const std::string& from, const std::string& to, int flag);

    typedef std::vector<std::pair<std::string, unsigned short>> HostContainer;

    等于

    using HostContainer = std::vector<std::pair<std::string, unsigned short>>;

    =========================

    工作机会(内部推荐):发送邮件至gaoyabing@126.com,看到会帮转内部HR。

    邮件标题:X姓名X_X公司X_简历(如:张三_东方财富_简历),否则一律垃圾邮件!

    公司信息:

    1. 1.东方财富|上海徐汇、南京|微信客户端查看职位(可自助提交信息,微信打开);
  • 相关阅读:
    制作透明色:《CSS3 RGBA》与Opacity样式用法
    制作动画效果:《CSS3 Animation》
    CISCO运维记录之3650堆叠设备升级IOS(Version 16.3.6版本存在bug)
    08 Zabbix4.0系统配置事件通知
    24 Zabbix系统配置日志监控告警--关键字触发
    15 Zabbix Item类型之Zabbix trapper类型
    06 Zabbix4.0系统CISCO交换机告警模板规划信息(基础)
    05 Zabbix4.0触发器表达式Trigger expression支持的函数
    04 Zabbix4.0系统配置触发器trigger
    03 Zabbix4.0添加cisco交换机基本监控步骤
  • 原文地址:https://www.cnblogs.com/Chary/p/15471288.html
Copyright © 2011-2022 走看看