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\n"); //輸出: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\n", book.title);
  19. printf( "書作者 : %s\n", 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>>;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM