大家都知道,在 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 中往往不得不這樣寫:
- template <typename Val>
- struct str_map
- {
- typedef std::map<std::string, Val> type;
- };
- // ...
- str_map<int>::type map1;
- // ...
一個雖然簡單但卻略顯煩瑣的 str_map 外敷類是必要的。這明顯讓我們在復用某些泛型代碼時非常難受。
現在,在 C++11 中終於出現了可以重定義一個模板的語法。請看下面的示例:
- template <typename Val>
- using str_map_t = std::map<std::string, Val>;
- // ...
- str_map_t<int> map1;
這里使用新的 using 別名語法定義了 std::map 的模板別名 str_map_t。比起前面使用外敷模板加 typedef 構建的 str_map,它完全就像是一個新的 map 類模板,因此,簡潔了很多。
實際上,using 的別名語法覆蓋了 typedef 的全部功能。先來看看對普通類型的重定義示例,將這兩種語法對比一下:
- // 重定義unsigned int
- typedef unsigned int uint_t;
- using uint_t = unsigned int;
- // 重定義std::map
- typedef std::map<std::string, int> map_int_t;
- 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 語法是如何定義模板別名的。
- /* C++98/03 */
- template <typename T>
- struct func_t
- {
- typedef void (*type)(T, T);
- };
- // 使用 func_t 模板
- func_t<int>::type xx_1;
- /* C++11 */
- template <typename T>
- using func_t = void (*)(T, T);
- // 使用 func_t 模板
- 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 宏名(形參表) 字符串
在字符串中含有各個形參。在使用時調用帶參宏調用的一般形式為:宏名(實參表); 如:
-
-
int main()
-
{
-
std::cout << add(9,12) << std::endl;//輸出21
-
return 0;
-
}
這個“函數”定義了加法,但是該“函數”沒有類型檢查,有點類似模板,但沒有模板安全,可以看做一個簡單的模板。
#define也可以定義一些簡單的函數,但因為只是簡單的替換,有時后會發生一些錯誤,謹慎(不建議)使用,在替換列表中的各個參數最好使用圓括號括起來
4.宏定義中的條件編譯
在大規模的開發過程中,頭文件很容易發生嵌套包含,而#ifndef 配合 #define ,#endif 可以避免這個問題
-
-
-
-
int a = 0;
-
這里的#ifndef #define #endif 其實和#pragma once 作用類似,都是為了防止多次編譯一個頭文件
5.跨平台
在大規模的開發過程中,特別是跨平台和系統的軟件里,需要用到#define
-
-
......
-
(
-
......
-
-
-
......
-
(
-
......
-
可以在編譯的時候通過#define設置編譯環境
6.宏定義中的特殊操作符
define 中的特殊操作符有#,## 和 … and __VA_ARGS__
簡單來說#,##都是類似於變量替換的功能,這里不贅述
__VA_ARGS__ 是一個可變參數的宏,這個可變參數的宏是新的C99規范中新增的,目前似乎只有gcc支持
實現思想就是宏定義中參數列表的最后一個參數為省略號(也就是三個點)。這樣預定義宏__VA_ARGS__就可以被用在替換部分中,替換省略號所代表的字符串,如:
-
-
int main()
-
{
-
int wt=1,sp=2;
-
PR("hello\n"); //輸出:hello
-
PR("weight = %d, shipping = %d",wt,sp); //輸出:weight = 1, shipping = 2
-
return 0;
-
}
這里再介紹幾個系統的宏:
__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 來定義一個新的數據類型名字,然后使用這個新的數據類型來直接定義結構變量,如下:
-
-
-
-
typedef struct LearningBooks
-
{
-
char title[50];
-
char author[50];
-
} Book;
-
-
int main( )
-
{
-
Book book;
-
-
strcpy( book.title, "c++");
-
strcpy( book.author, "kevin");
-
book.book_id = 0001;
-
-
printf( "書標題 : %s\n", book.title);
-
printf( "書作者 : %s\n", book.author);
-
return 0;
-
}
3.typedef函數指針用法
typedef經常用於替換一些復制的函數指針,說到函數指針,就必須先了解一下函數指針和指針函數。了解了之后在來講typedef
1)指針函數
指針函數是指帶指針的函數,即本質是一個函數。函數返回類型是某一類型的指針,聲明格式如下:
類型標識 *函數名(參數表)
int *getptr(int x); //聲明一個參數為int , 返回值為int * 的 指針函數
首先它是一個函數,只不過這個函數的返回值是一個指針。函數返回值必須用同類型的指針變量來接受,也就是說,指針函數一定有函數返回值,而且,在主調函數中,函數返回值必須賦給同類型的指針變量,如下:
-
double *getptr(double x) //聲明同時定義一個返回值為double* 的指針函數
-
{
-
return &x;
-
};
-
-
int main()
-
{
-
double *p;
-
p = getptr(7); //返回double* 賦給p
-
return 0;
-
}
2)函數指針
函數指針是指向函數地址的指針變量,它的本質是一個指針變量,函數也是有分配內存的,所以指向函數地址的指針,即為函數指針,聲明函數指針格式如下:
類型說明符 (*變量名)(參數)
int (*funp) (int x); // 聲明一個 指向參數類型為 int ,返回類型為 int 的函數 的 指針
int *(*funp) (int x); // 聲明一個 指向參數類型為 int ,返回類型為 int* 的函數 的 指針
其實這里的func不能稱為函數名,應該叫做指針的變量名。這個特殊的指針指向一個返回整型值的函數。指針的聲明必須和它指向函數的聲明保持一致,如:
-
double (*funp)(double x); // 聲明一個 指向參數類型為 int ,返回類型為 int 的函數 的 指針
-
double *(*funp1)(double x); // 聲明一個 指向參數類型為 int ,返回類型為 int* 的函數 的 指針
-
-
int main()
-
{
-
funp = getnum; //報錯,因為類型對不上,右邊類型是double *(*)(double x),左邊是double(*)(double x)
-
funp1 = getnum; //正確,兩邊類型都是double *(*)(double x)
-
return 0;
-
}
看到這里應該能明白了函數指針的寫法,再剖析一遍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);
-
typedef char(*PTRFUN)(int); //定義char(*)(int)的函數指針 的別名為PTRFUN
-
PTRFUN pfun; //直接用別名PTRFUN定義char(*)(int)類型的變量
-
-
char getchar(int a) //定義一個形參為int,返回值為char的函數
-
{
-
return '1';
-
}
-
int main()
-
{
-
pfun = getchar; //把函數賦給指針
-
(*pfun)( 5); //用函數指針取函數,調用
-
return 0;
-
}
這樣能簡化代碼,隱藏難懂的聲明類型,方便閱讀
三.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:
-
void f() {}
-
int main()
-
{
-
using FunctionPtr = void (*)(); //相當於 typedef void (*FunctionPtr)();
-
-
FunctionPtr ptr = f;
-
}
例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>>;