日文口譯缺職

C 語言中 typedef 可以用來擴充 C 原本的資料型態. 凡是華頓翻譯公司們會將某個資料型態或將經常使用的資料型態組合給予一個對照直觀而易懂的別名. 定義別名以後我們就能夠像利用原有的資料型態來宣告或界說變數一樣翻譯社 直接拿它來宣佈或界說(註一, 註二)變數.

接著, 我們進一步加一些轉變

圈套 -- 有關 storage class 和 qualifier 常常泛起的毛病


  1. 由於 typedef功效會被視為資料型態的擴充, 界說或宣佈變數時可使用指定儲區種別 (storage class) 的四個 keyword (auto, static, extern, register) 來加以潤飾, 是以 typedef 的內容自己是不行以利用這四個 keyword 的.
  2. 轉換化簡


    轉換化簡程序:

    • 先取一個適合的截斷點
    • 將截斷點以後的低優先權運算以 typedef 定義為別名
    • 然後用別號界說或宣佈截斷點之前的高優先權運算.

    enum color { black, white, gold翻譯社 pink };
    typedef enum color iPhoneColor;
    iPhoneColor x = gold;
    

    再下來是陣列 array 的例子:

    轉換化簡的原則:

    • 不克不及粉碎原有之運算前後順序.
    • 轉換化簡沒有固定的答案, 完全視程式的需要取 typedef 的截斷點.

    再來看一些用 typedef 轉換化簡的例子: from blog.sina.com.cn typedef的四个用途和两大圈套
    感覺很費勁看不下去了嗎? 先讀一下這一篇 C 說話:輕鬆讀懂複雜的界說 (Define and Read the complex declarations)

    typedef unsigned char bool;
    typedef struct _list_node_ {
        unsigned long       size;
        strcut _list_node_ *next;
    } LIST_NODE;
    
    bool      flag1, flag2;
    LIST_NODE node0, *free_list;
    
    • 第1行的 typedefunsigned char 取了個好記的別號 bool.
    • 第2~5行則將佈局 _list_node_ 擴充為資料型態並為其定名為 LIST_NODE. 這裡要注意的是結構內部有一個指標指向和自己一樣的結構. 在 typedef 的界說中華頓翻譯公司們只能使用 struct _list_node_ * 而不行以利用 typedef 的功效 LIST_NODE (因為 LIST_NODE 還沒有定義完成. 翻譯公司也能夠把 typedef 的界說和結構的界說拆開來).
    • 第7~8行則拿新界說的別號, 來界說本來程式要定義的變數. 如果再把 1~5 行的 typedef 移到標頭檔 (xxx.h), 只留下 7~8 行這二行變數定義的部分, 程式看起來就簡練多了.
    • 上面有關 LIST_NODE 的部份, 也可以換一個寫法:
      typedef struct _list_node_ {
          unsigned long       size;
          strcut _list_node_ *next;
      } LIST_NODE, *pLIST_NODE;
      
      LIST_NODE node0;
      pLIST_NODE free_list;
      
      這個 typedef 寫法用了一個一般比力不經常使用的 ,. 其實就是和 int32_t a, *p;(註三) 定義了 "一個 32 位元整數變數 a 和一個指到 32 位元整數的指標變數 p" 一樣, 這樣的寫法界說了一個佈局 _list_node_ 的別名 LIST_NODE翻譯社 和一個指標型的別號 pLIST_NODE. 所以本例和上例這二種寫法界說出來的變數 node0, free_list 後果是完全一致的.
    • 註三: 使用 , 將多個變數的定義/宣佈保持起來時, 要注意 * (指標) 並不算在配合的資料型態這一邊, 而是算在變數名稱這一邊. 所以上面的例子裡的 int32_t a, *p;int32_t *p, a;int32_t* p翻譯社 a; 意義上都是一樣的. 初學者需要稀奇小心最後一種寫法, 非常輕易讓人弄錯搞含混了.

      文章標籤
      C C 說話 typedef function pointer struct pointer pointers 指標 佈局指標 函數指標

      底下的程式片段是變數界說的部分, 沒有使用 typedef 的模樣:

      // 原始寫法:
      doube(*)() (*e)[9];
      // 轉換為:
      typedef double (*pFuny)();         // 左半部
      typedef pFuny (*pFunParamy)[9];    // 右半部
      pFunParamy e;
      

      函數指標最多見的運用是利用在 callback 的手藝上. 由於需要將某一函數的位址當做參數傳送給另一個函數翻譯社 因此利用 typedef 替這類 callback 函數的指標界說一個新名字 (新資料型態), 可以大幅提昇程式的可讀性, 日後保護及點竄上比力不會出錯.

      註一: 宣佈界說有所不同. 定義變數會實際佔據記憶體空間, 而宣佈變數則只產生參考的連結, 稍後保持程式時再保持到在其他模組界說的變數. 我們一般把宣佈變數擺放在 header file (.h 檔) 中, 有需要的模組或程式只要 include 便可. 而定義變數則視情況放在主程式或者相幹的模組中, 固然它凡是也會 include 該 header file.

      // 原始寫法:
      void (*b[10])(void (*)());
      // 轉換為:
      typedef void (*pFunParam)();       // 右半部, 函數的參數
      typedef void (*pFunx)(pFunParam);  // 左半部的函數
      pFunx b[10];
      

例三: 變數為指向陣列之指標翻譯社 陣列元素固定為 9, 陣列元素內容為 函數指標

參考


  1. en.wikipedia.org typedef
  2. blog.sina.com.cn typedef的四个用處和两大圈套
  3. pixnet.net/blog C 語言:輕鬆讀懂複雜的界說 (Define and Read the complex declarations)
  4. Declaring, Assigning, and Using Function Pointers
// Wrong definition:
typedef char * pstr;
mystrcmp(const pstr, const pstr);

// Correct definition:
typedef const char * cpstr;
mystrcmp(cpstr, cpstr);

// 原始寫法:
int *(*a[5])(int, char*);
// 轉換1:
typedef int *(*pFun)(int, char*);
pFun a[5];
// 轉換2:
typedef int *Func(int, char*);
Func *a[5];
  • 轉換1: 在 *a[5] 中, [] 優先權比 * 高, 故把 a[5] 留在原來變數界說的式子, 其餘的轉為 typedef
  • 轉換2: 把 *a[5] 全部留在本來的變數定義式, 其餘的轉為 typedef

例二: 變數為一陣列, 陣列元素內容為 函數指標, 函數之參數為 函數指標

int do_math(float arg1翻譯社 int arg2) {
    return arg2;
}

int call_a_func(int (*call_this)(float翻譯社 int)) {
    int output = call_this(5.5翻譯社 7);
    return output;
}

int final_result = call_a_func(&do_math);

套用 typedef 以後, typedef 自己易讀並且 call_a_func 的參數部分, 也變得簡單易讀.

// Examples of typedef a pointer
typedef struct _list_node_  * pLIST_NODE;    // (1)
typedef struct _list_node_ (* pLIST_NODE);   // (2)

// Examples of typedef a function or pointer of function
typedef int   IamFunc (int, int);            // (3)
typedef int  *IamFunc (int翻譯社 int);            // (4)
typedef int (*IamFunc)(int翻譯社 int);            // (5)
  • 式子(1) 是利用告終構指標翻譯社 寫法很平時翻譯社 看起來應該很習慣.
  • 式子(2) 的寫法看起來感覺好像有點玄機... 但其實並沒有, 式子(1)和(2)這二個寫法是一樣的:
    在界說或宣佈指標 int * ptr 的寫法中, 星號左右兩邊的空白是無關緊要的, 所以 int* ptr, int *ptr, int * ptr, int*ptr, 都是准確的而且意義也相同.
    而诠釋上你可以說 ptr 是一個 int*, 也能夠說 *ptr 是一個 int. 所以多加了括號並不會改變它的意義.

註二: ANSI C 標準文件說: 會現實佔據記憶體空間的宣佈稱為定義. 所以 ANSI C 說的宣告包括了界說純宣告. 而註一及以下本文中所指的宣佈則是指沒有佔據記憶體空間的純宣佈, 而不是 ANSI C 本來所指的宣佈, 特此申明. 請參考維基網站 Declaration (computer programming) 段落二 'Declaration vs. definition' 及段落三 'Declarations and Definitions')

typedef int (*MathFunc)(float, int);

int do_math(float arg1, int arg2) {
    return arg2;
}

int call_a_func(MathFunc call_this) {
    int output = call_this(5.5, 7);
    return output;
}

int final_result = call_a_func(&do_math);

再來一個更極端的例子 (也是借用自 Wiki 網站對 typedef 的解說)


下面的例子含有例舉 (enum) 別名界說, 應當不消多作解釋.

void (*signal(int sig翻譯社 void (*func)(int)))(int);
// 轉換成下面的模樣
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig翻譯社 sighandler_t func);

下面的例子借用自 Wiki 網站對 typedef 的說明註解

// 下行的語法是毛病: static 弗成以泛起在 typedef 中
typedef static int newINT;
newINT x, y翻譯社 x;

// 要改成以下二行才行 (static 必需移到變數界說式)
typedef int newINT;
static newINT x翻譯社 y翻譯社 z;
  • 別的二個限制詞 (qualifier翻譯社 constvolatile) 則沒有上述的限制: 可以出現在新資料型態的 typedef 定義中; 也能夠不呈現在新資料型態的 typedef 定義中翻譯社 而改在變數界說或宣佈時加上限制. 固然翻譯社 我們弗成以兩者都加. 同時, 要當心變數界說或宣告內容包括有指標的情形翻譯社 此時 keyword constvolatile 的限制標的釀成有二個: 一個是指標本身另一個是指標所指向的資料. 這個時辰到底誰被 keyword 限制翻譯社 取決於 keyword 泛起的位置.
    • 下面的式子(1)翻譯社 式子(2)和式子(3)寫法相通翻譯社 是界說一個指標變數指向常數資料 (指標值可變翻譯社 資料值不可變).
    • 式子(4)和式子(5)寫法相通翻譯社 是界說一個常數指標指向可更改的資料 (指標值不行變翻譯社 資料值可變).
    • 式子(1)的寫法經常會被誤以為應當和式子(5)相等翻譯社 所以就毛病的把勢子(1)化簡為式子(5). 但現實上是式子(1)應當以化簡為式子(3). (我們應該把式子(5) const ptr p 中的 ptr 當作和 const int x 中的 int 一樣翻譯社 是一個資料型態. 而不要把它以 typedef 的定義 char * 來替代.)
    // define a non-const pointer to const data
    const char * p;             // (1)
    char const * p;             // (2)
    typedef const char * ptr;   // (3-1)
    ptr p;                      // (3-2)
    
    // define a const pointer to non-const data
    char * const p;            // (4)
    typedef char* ptr;         // (5-1)
    const ptr p;               // (5-2)
    
    • 備註:
      • 式子(4)和式子(5-2)在現實應用中是不 OK 的翻譯社 因為 (指標變數的) 變數值本身是一常數, 必須在定義變數的同時指定其常數值. 現實運用的例子以下: 式子(4a)是指定某一個變數的位址; 式子(4b)是指定一特定位址.
        char * const p = &x;       // (4a)
        char * const p = 0x200000; // (4b)
        
      • 不過式子(4b)的用法會多揮霍一個指標變數的空間 (即變數 p 自己). 這是因為 0x200000 自己就是一個 const, 所以沒需要用變數來貯存它然後又宣告說該變數是常數不成以更動. 其實我們可以直接用 type casting 的方法把 0x200000 轉型就能夠了, 即 ((char *)0x200000). 假如覺得後續利用它的程式敘述會欠好讀, 那可以加入 #define CONST_P ((char *)0x200000) 這樣的置換巨集, 然後把程式論述改成利用 CONST_P 來取代 ((char *)0x200000) 即可.
      • 式子(1), 式子(2)和式子(3)在現實運用中是 OK 的, 同時它只是限制不可以經過指標變數 p 來改變其所指到的變數, 而不是限制所指到的變數必須是常數.
        typedef const char * ptr;
        
        ptr p;
        char x = 0x20;
        
        p  = &x;
        *p = 0x21;      // Compiler will alert.
        x  = 0x21;      // OK
        

    最常看到的錯誤典範是華頓翻譯公司們想要寫一個像 strcmp() 那樣的函數, 於是宣佈了以下的函數原型 mystrcmp(const char *, const char *), 然後為了想簡化於是又增添了定義 typedef char * pstr; 接著把函數原型宣告改成 mystrcmp(const pstr, const pstr), 然後就掛掉了... (我們但願的是字串對照時不要去動到字串的內容, 而不是指標值不克不及更動)

    typedef uint8_t   Buffer[16];
    
    Buffer xBuf;
    
    xBuf[0] = 3;
    xBuf[1] = 2;
    
    • 第3行有些小小的奇異, 界說變數時似乎沒有指定是陣列翻譯社 後面卻可以用陣列的寫法. 其實第3行相當於 uint8_t xBuf[16]
    • 用法可能看起來有點奇異, 卻可以包管每次用 Buffer 界說或宣佈的陣列變數 必然是 16 個 uint8_t 元素. 優點是陣列的大小需要改變時翻譯社 只要點竄 typedef 沒必要全部專案翻找一遍翻譯社 還要憂慮是否是有改漏了.

    假如遇到看不懂時翻譯社 建議你可以把界說中的 typedef 拿掉翻譯社 同時資料型態名稱換成變數的名稱翻譯社 就會比力容易理解. 例如: 把 typedef uint8_t Buffer[16]; 去掉 typedef翻譯社 Buffer 換成變數名 xBuf翻譯社 釀成 uint8_t xBuf[16];

    例一: 變數為一陣列, 陣列元素內容為 函數指標

    • 式子(3) 現實是界說一個體名 IamFunc, 它是一個傳回值為整數且需要二個整數參數的函數.
    • 式子(4) 和式子(3) 比較, 只是回傳值由 int 變為 int*. 因為在 IamFunc 右邊的函數呼喚運算子 (), 比在左側的取值運算子 *, 優先官僚來得較高.
    • 式子(5) 和式子(3) 比較, 多加了括號強迫取值運算子 * 先履行, 所以函數釀成了指向函數的指標, (名為 IamFunc 的資料型態, 是一個指標指向一個傳回值為整數且需要二個整數參數的函數).
    • 有人說 式子(3) 和式子(5) 是對等的, 關於這點我不是很清晰, 需要多一點時候找資料及作些嘗試來驗證.
      • 謎底也對也錯: 就 typedef 的定義內容來說: 他們是紛歧樣的. 可是就後續程式的實際運用來講: 這二個 typedef 界說完全一致, 程式的寫法溝通, 結果也完全一樣. 之所以會有這類景遇翻譯社 是因為 C 編譯器對 function pointer 的界說和處置懲罰體式格局和其他指標其實不一樣, 並且還有點另類: function pointer 的取址運算 & 及取值運算 * 效果或有分歧 (一個是指標的位址, 一個是函數的位址) 然則只要一帶上 () 了局都是呼喚函數並且不會搞錯 (好奇異喔!). 這裡有一篇有關 function pointer 的申明 (英文) Declaring, Assigning, and Using Function Pointers 是 Usenet 上 comp.lang.c FAQ list 的維護人 Steve Summit 師長教師寫的, 對於 function pointer 的取址運算 & 及取值運算 * 有明白的說明註解, 供應給人人參考.
    unsigned char  flag1, flag2;
    struct _list_node_ {
        unsigned long       size;
        strcut _list_node_ *next;
    } node0, *free_list;