typedef自定義類型
- 給變量一個易記且意義明確的新名字,
- 簡化一些比較復雜的類型聲明。
其實就是為數據類型起一個別名。
typedef unsigned char AGE; //字符類型
AGE x; //等價於 unsigned char x;
typedef int * IPointer; //指針類型
IPointer p; //等價於 int *p;
typedef char Name[10]; //數組類型
Name name1; //char name1[10];
Typedef允許程序員為數據類型創建別名,並使用別名代替實際的類型名稱。Typedef的字面意思是“類型定義”。
要聲明typedef,只需使用typedef關鍵字,然后使用別名作為類型,再使用別名:
typedef double distance_t; // define distance_t as an alias for type double
// The following two statements are equivalent: double howFar; distance_t howFar; |
按照約定,typedef名稱使用“ _t”后綴聲明。這有助於指示標識符表示類型,而不是變量或函數,並且還有助於防止與其他標識符的命名沖突。
請注意,a typedef並未定義新類型。相反,它只是現有類型的別名(另一個名稱)。A typedef可以在可以使用常規類型的任何地方互換使用。
即使以下內容在語義上沒有意義,但它是有效的C ++:
int main() { typedef long miles_t; typedef long speed_t;
miles_t distance { 5 }; speed_t mhz { 3200 };
// The following is valid, because distance and mhz are both actually type long distance = mhz;
return 0; } |
typedefs和type別名遵循與變量相同的作用域規則。該typedef小號miles_t和speed_t僅在使用main()功能。如果將它們放在另一個函數中,main()將無法訪問它們。如果將它們放置main()在全局范圍之外,則所有功能都可以訪問它們。
但是,typedef存在一些問題。首先,很容易忘記類型名稱或類型定義是第一位的。哪個是正確的?
typedef distance_t double; // incorrect typedef double distance_t; // correct |
我不記得了
其次,typedef的語法在更復雜的類型上變得丑陋,尤其是函數指針(我們將在以后的第7.8節“函數指針”中介紹):
輸入別名
為了幫助解決這些問題,typedefs引入了一種改進的語法,該語法模仿了聲明變量的方式。此語法稱為類型別名。
給定以下typedef:
typedef double distance_t; // define distance_t as an alias for type double |
可以將其聲明為以下類型別名:
using distance_t = double; // define distance_t as an alias for type double |
兩者在功能上是等效的。
請注意,盡管類型別名語法使用“ using”關鍵字,但這是重載的含義,與using statements與名稱空間相關的內容無關。
對於更高級的類型定義情況,此類型別名語法更干凈,因此應首選。
使用類型別名提高可讀性
類型別名的一種用途是幫助文檔和可讀性。數據類型的名稱,如char,int,long,double,和bool是用於描述什么好鍵入一個函數返回,但更多的時候,我們想知道是什么目的返回值服務。
例如,考慮以下功能:
int GradeTest(); |
我們可以看到返回值是一個整數,但是該整數是什么意思呢?字母等級?遺漏了多少個問題?學生的身份證號?錯誤代碼?誰知道!Int沒有告訴我們任何信息。
using testScore_t = int; testScore_t GradeTest(); |
但是,使用返回類型為testScore_t的函數很明顯,該函數正在返回表示測試分數的類型。
使用類型別名來簡化代碼維護
類型別名還允許您更改對象的基礎類型,而不必更改大量代碼。例如,如果您使用a short持有學生的ID號,但后來又決定需要一個ID,則long必須梳理大量代碼並替換short為long。可能很難弄清楚哪些shorts被用於保存ID號以及哪些被用於其他目的。
但是,使用類型別名,您要做的就是更改using studentID_t = short;為using studentID_t = long;。但是,在將類型別名的類型更改為其他類型族中的類型時(例如,將整數更改為浮點值,反之亦然),仍需小心!新類型可能有比較或整數/浮點除法問題,或其他舊類型沒有的問題。
使用類型別名進行平台無關的編碼
類型別名的另一個優點是,它們可以用於隱藏平台特定的細節。在某些平台上,an int為2字節,在其他平台上為4字節。因此,int在編寫與平台無關的代碼時,使用存儲2個以上字節的信息可能具有潛在的危險。
因為char,short,int,並long沒有給出其大小的指示,這是很常見的跨平台程序使用類型別名定義別名包括在比特類型的大小。例如,int8_t將是一個8位帶符號整數,int16_t一個16位帶符號整數和int32_t一個32位帶符號整數。以這種方式使用類型別名有助於防止錯誤,並使對變量大小的假設變得更加清晰。
為了確保每個別名類型都能解析為正確大小的類型,通常將這種類型別名與預處理器指令結合使用:
#ifdef INT_2_BYTES using int8_t = char; using int16_t = int; using int32_t = long; #else using int8_t = char; using int16_t = short; using int32_t = int; #endif |
在整數只有2個字節的計算機上,INT_2_BYTES可以#defined定義,並且程序將使用最頂層的類型別名進行編譯。在整數為4個字節的計算機上,INT_2_BYTES未定義將導致使用底部的類型別名集。以這種方式,int8_t將解析為一個1個字節的整數,int16_t將解析為一個2字節整數,並且int32_t將解析為一個4字節整數使用的組合char,short,int,和long適合於該程序正在編譯上的機器。
正是這樣定義了int8_tC ++ 11中引入的固定寬度整數(如)(在第4.6節中介紹了-固定寬度整數和size_t)!
這也是int8_t被視為char來源的問題- int8_t是的類型別名char,因此僅是a的別名,char而不是唯一類型。結果是:
#include <cstdint> // for fixed-width integers #include <iostream>
int main() { std::int8_t i{ 97 }; // int8_t is actually a type alias for signed char std::cout << i;
return 0; } |
該程序打印:
一個
不是97,因為std :: cout打印char為ASCII字符,而不是數字。
使用類型別名使復雜類型變得簡單
盡管到目前為止我們只處理了簡單的數據類型,但是在高級C ++中,您可以看到這樣聲明的變量和函數:
std::vector<std::pair<std::string, int> > pairlist;
bool hasDuplicates(std::vector<std::pair<std::string, int> > pairlist) { // some code here } |
打字std::vector<std::pair<std::string, int> >無處不在,你需要使用類型可以很麻煩。使用類型別名要容易得多:
using pairlist_t = std::vector<std::pair<std::string, int> >; // make pairlist_t an alias for this crazy type
pairlist_t pairlist; // instantiate a pairlist_t variable
bool hasDuplicates(pairlist_t pairlist) // use pairlist_t in a function parameter { // some code here } |
好多了!現在我們只需要鍵入“ pairlist_t”而不是std::vector<std::pair<std::string, int> >。
如果您不知道什么是std :: vector,std :: pair或所有這些瘋狂的尖括號,請不要擔心。您在這里真正需要了解的唯一一件事是,類型別名使您可以采用復雜的類型並給它們一個簡單的名稱,這使這些類型更易於使用和理解。
最佳實踐
優先於typedef而使用類型別名,並廣泛使用它們來記錄類型的含義。
函數指針一般用於回調,例如信號處理,libcurl等會應用到回調。回調是比較常用的技術,而回調就要涉及函數指針。
當我們的程序中有以下函數:
void printHello(int i);
然后我們要定義一個函數指針,指向printHello,並且調用這個方法,代碼如下:
void (*pFunc)(int);
pFunc = &printHello;
(*pFunc)(110);
其中void (*pFunc)(int)是聲明一個函數指針,指向返回值是void,調用參數是(int)的函數,變量名是pFunc,pFunc就是函數指針了,以前是函數指針的簡單用法。
大家可以看到,聲明一個函數指針是比較復雜的,尤其是當你要在多處地方聲明同一個類型的函數指針變量,代碼更加復雜,所以有下面簡化的做法:
typedef void (*PrintHelloHandle)(int);
使用代碼如下:
PrintHelloHandle pFunc;
pFunc = &printHello;
(*pFunc)(110);
以后其他地方的程序需要聲明類似的函數指針,只需要下面代碼:
PrintHelloHandle pFuncOther;
這樣,我們的代碼就變得更加簡潔易懂。
typedef和#define的用法與區別
一、typedef的用法
在C/C++語言中,typedef常用來定義一個標識符及關鍵字的別名,它是語言編譯過程的一部分,但它並不實際分配內存空間,實例像:
typedef int INT;
typedef int ARRAY[10];
typedef (int*) pINT;
typedef可以增強程序的可讀性,以及標識符的靈活性,但它也有“非直觀性”等缺點。
二、#define的用法
#define為一宏定義語句,通常用它來定義常量(包括無參量與帶參量),以及用來實現那些“表面似和善、背后一長串”的宏,它本身並不在編
譯過程中進行,而是在這之前(預處理過程)就已經完成了,但也因此難以發現潛在的錯誤及其它代碼維護問題,它的實例像:
#define INT int
#define TRUE 1
#define Add(a,b) ((a)+(b));
#define Loop_10 for (int i=0; i<10; i++)
在Scott Meyer的Effective C++一書的條款1中有關於#define語句弊端的分析,以及好的替代方法,大家可參看。
三、typedef與#define的區別
從以上的概念便也能基本清楚,typedef只是為了增加可讀性而為標識符另起的新名稱(僅僅只是個別名),而#define原本在C中是為了定義常量
,到了C++,const、enum、inline的出現使它也漸漸成為了起別名的工具。有時很容易搞不清楚與typedef兩者到底該用哪個好,如#define
INT int這樣的語句,用typedef一樣可以完成,用哪個好呢?我主張用typedef,因為在早期的許多C編譯器中這條語句是非法的,只是現今的
編譯器又做了擴充。為了盡可能地兼容,一般都遵循#define定義“可讀”的常量以及一些宏語句的任務,而typedef則常用來定義關鍵字、冗
長的類型的別名。
宏定義只是簡單的字符串代換(原地擴展),而typedef則不是原地擴展,它的新名字具有一定的封裝性,以致於新命名的標識符具有更易定義變
量的功能。請看上面第一大點代碼的第三行:
typedef (int*) pINT;
以及下面這行:
#define pINT2 int*
實踐中見差別:pINT a,b;的效果同int *a; int *b;表示定義了兩個整型指針變量。而pINT2 a,b;的效果同int *a, b; 表示定義了一個整型指針變量a和整型變量b。
1) #define是預處理指令,在編譯預處理時進行簡單的替換,不作正確性檢查,不關含義是否正確照樣帶入,只有在編譯已被展開的源程序時才會發現可能的錯誤並報錯。例如:
#define PI 3.1415926
程序中的:area=PI*r*r 會替換為3.1415926*r*r
如果你把#define語句中的數字9 寫成字母g 預處理也照樣帶入。
2)typedef是在編譯時處理的。它在自己的作用域內給一個已經存在的類型一個別名,但是You cannot use the typedef specifier inside a function definition。
3)typedef int * int_ptr;
與
#define int_ptr int *
作用都是用int_ptr代表 int * ,但是二者不同,正如前面所說 ,#define在預處理 時進行簡單的替換,而typedef不是簡單替換 ,而是采用如同定義變量的方法那樣來聲明一種類型。也就是說;
//refer to (xzgyb(老達摩))
#define int_ptr int *
int_ptr a, b; //相當於int * a, b; 只是簡單的宏替換
typedef int* int_ptr;
int_ptr a, b; //a, b 都為指向int的指針,typedef為int* 引入了一個新的助記符
這也說明了為什么下面觀點成立
//QunKangLi(維護成本與程序員的創造力的平方成正比)
typedef int * pint ;
#define PINT int *
那么:
const pint p ;//p不可更改,但p指向的內容可更改
const PINT p ;//p可更改,但是p指向的內容不可更改。
pint是一種指針類型 const pint p 就是把指針給鎖住了 p不可更改
而const PINT p 是const int * p 鎖的是指針p所指的對象。
typedef的四個用途和兩個陷阱
用途一:
定義一種類型的別名,而不只是簡單的宏替換。可以用作同時聲明指針型的多個對象。比如:
char* pa, pb; // 這多數不符合我們的意圖,它只聲明了一個指向字符變量的指針,
// 和一個字符變量;
以下則可行:
typedef char* PCHAR; // 一般用大寫
PCHAR pa, pb; // 可行,同時聲明了兩個指向字符變量的指針
雖然:
char *pa, *pb;
也可行,但相對來說沒有用typedef的形式直觀,尤其在需要大量指針的地方,typedef的方式更省事。
用途二:
用在舊的C代碼中(具體多舊沒有查),幫助struct。以前的代碼中,聲明struct新對象時,必須要帶上struct,即形式為: struct 結構名 對象名,如:
struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
而在C++中,則可以直接寫:結構名 對象名,即:
tagPOINT1 p1;
估計某人覺得經常多寫一個struct太麻煩了,於是就發明了:
typedef struct tagPOINT
{
int x;
int y;
}POINT;
POINT p1; // 這樣就比原來的方式少寫了一個struct,比較省事,尤其在大量使用的時候
或許,在C++中,typedef的這種用途二不是很大,但是理解了它,對掌握以前的舊代碼還是有幫助的,畢竟我們在項目中有可能會遇到較早些年代遺留下來的代碼。
用途三:
用typedef來定義與平台無關的類型。
比如定義一個叫 REAL 的浮點類型,在目標平台一上,讓它表示最高精度的類型為:
typedef long double REAL;
在不支持 long double 的平台二上,改為:
typedef double REAL;
在連 double 都不支持的平台三上,改為:
typedef float REAL;
也就是說,當跨平台時,只要改下 typedef 本身就行,不用對其他源碼做任何修改。
標准庫就廣泛使用了這個技巧,比如size_t。
另外,因為typedef是定義了一種類型的新別名,不是簡單的字符串替換,所以它比宏來得穩健(雖然用宏有時也可以完成以上的用途)。
用途四:
為復雜的聲明定義一個新的簡單的別名。方法是:在原來的聲明里逐步用別名替換一部分復雜聲明,如此循環,把帶變量名的部分留到最后替換,得到的就是原聲明的最簡化版。舉例:
1. 原聲明:int *(*a[5])(int, char*);
變量名為a,直接用一個新別名pFun替換a就可以了:
typedef int *(*pFun)(int, char*);
原聲明的最簡化版:
pFun a[5];
2. 原聲明:void (*b[10]) (void (*)());
變量名為b,先替換右邊部分括號里的,pFunParam為別名一:
typedef void (*pFunParam)();
再替換左邊的變量b,pFunx為別名二:
typedef void (*pFunx)(pFunParam);
原聲明的最簡化版:
pFunx b[10];
3. 原聲明:doube(*)() (*e)[9];
變量名為e,先替換左邊部分,pFuny為別名一:
typedef double(*pFuny)();
再替換右邊的變量e,pFunParamy為別名二
typedef pFuny (*pFunParamy)[9];
原聲明的最簡化版:
pFunParamy e;
理解復雜聲明可用的“右左法則”:從變量名看起,先往右,再往左,碰到一個圓括號就調轉閱讀的方向;括號內分析完就跳出括號,還是按先右后左的順序,如此循環,直到整個聲明分析完。舉例:
int (*func)(int *p);
首先找到變量名func,外面有一對圓括號,而且左邊是一個*號,這說明func是一個指針;然后跳出這個圓括號,先看右邊,又遇到圓括號,這說明(*func)是一個函數,所以func是一個指向這類函數的指針,即函數指針,這類函數具有int*類型的形參,返回值類型是int。
int (*func[5])(int *);
func右邊是一個[]運算符,說明func是具有5個元素的數組;func的左邊有一個*,說明func的元素是指針(注意這里的*不是修飾func,而是修飾func[5]的,原因是[]運算符優先級比*高,func先跟[]結合)。跳出這個括號,看右邊,又遇到圓括號,說明func數組的元素是函數類型的指針,它指向的函數具有int*類型的形參,返回值類型為int。
也可以記住2個模式:
type (*)(....)函數指針
type (*)[]數組指針
---------------------------------
陷阱一:
記住,typedef是定義了一種類型的新別名,不同於宏,它不是簡單的字符串替換。比如:
先定義:
typedef char* PSTR;
然后:
int mystrcmp(const PSTR, const PSTR);
const PSTR實際上相當於const char*嗎?不是的,它實際上相當於char* const。
原因在於const給予了整個指針本身以常量性,也就是形成了常量指針char* const。
簡單來說,記住當const和typedef一起出現時,typedef不會是簡單的字符串替換就行。
陷阱二:
typedef在語法上是一個存儲類的關鍵字(如auto、extern、mutable、static、register等一樣),雖然它並不真正影響對象的存儲特性,如:
typedef static int INT2; //不可行
編譯將失敗,會提示“指定了一個以上的存儲類”。