typedef的用法簡介(三)

來源:互聯網
上載者:User

1. 基本解釋

  typedef為C語言的關鍵字,作用是為一種資料類型定義一個新名字。這裡的資料類型包括內部資料類型(int,char等)和自訂的資料類型(struct等)。

  在編程中使用typedef目的一般有兩個,一個是給變數一個易記且意義明確的新名字,另一個是簡化一些比較複雜的型別宣告。

  至於typedef有什麼微妙之處,請你接著看下面對幾個問題的具體闡述。

2. typedef & 結構的問題

  當用下面的代碼定義一個結構時,編譯器報了一個錯誤,為什麼呢?莫非C語言不允許在結構中包含指向它自己的指標嗎?請你先猜想一下,然後看下文說明:
typedef struct tagNode
{
 char *pItem;
 pNode pNext;
} *pNode;

  答案與分析:

  1、typedef的最簡單使用
typedef long byte_4;

  給已知資料類型long起個新名字,叫byte_4。

  2、 typedef與結構結合使用
typedef struct tagMyStruct
{
 int iNum;
 long lLength;
} MyStruct;

  這語句實際上完成兩個操作:

  1) 定義一個新的結構類型
struct tagMyStruct
{
 int iNum;
 long lLength;
};

  分析:tagMyStruct稱為“tag”,即“標籤”,實際上是一個臨時名字,struct 關鍵字和tagMyStruct一起,構成了這個結構類型,不論是否有typedef,這個結構都存在。

  我們可以用struct tagMyStruct varName來定義變數,但要注意,使用tagMyStruct varName來定義變數是不對的,因為struct 和tagMyStruct合在一起才能表示一個結構類型。

  2) typedef為這個新的結構起了一個名字,叫MyStruct。
typedef struct tagMyStruct MyStruct;

  因此,MyStruct實際上相當於struct tagMyStruct,我們可以使用MyStruct varName來定義變數。

  答案與分析

  C語言當然允許在結構中包含指向它自己的指標,我們可以在建立鏈表等資料結構的實現上看到無數這樣的例子,上述代碼的根本問題在於typedef的應用。

  根據我們上面的闡述可以知道:新結構建立的過程中遇到了pNext域的聲明,類型是pNode,要知道pNode表示的是類型的新名字,那麼在類型本身還沒有建立完成的時候,這個類型的新名字也還不存在,也就是說這個時候編譯器根本不認識pNode。

  解決這個問題的方法有多種:

  1)、
typedef struct tagNode
{
 char *pItem;
 struct tagNode *pNext;
} *pNode;

  2)、
typedef struct tagNode *pNode;
struct tagNode
{
 char *pItem;
 pNode pNext;
};

  注意:在這個例子中,你用typedef給一個還未完全聲明的類型起新名字。C語言編譯器支援這種做法。

  3)、規範做法:
struct tagNode
{
 char *pItem;
 struct tagNode *pNext;
};
typedef struct tagNode *pNode;

3. typedef & #define的問題

  有下面兩種定義pStr資料類型的方法,兩者有什麼不同?哪一種更好一點?
typedef char *pStr;
#define pStr char *;

  答案與分析:

  通常講,typedef要比#define要好,特別是在有指標的場合。請看例子:
typedef char *pStr1;
#define pStr2 char *;
pStr1 s1, s2;
pStr2 s3, s4;

  在上述的變數定義中,s1、s2、s3都被定義為char *,而s4則定義成了char,不是我們所預期的指標變數,根本原因就在於#define只是簡單的字串替換而typedef則是為一個類型起新名字。

  #define用法例子:
#define f(x) x*x
main( )
{
 int a=6,b=2,c;
 c=f(a) / f(b);
 printf("%d ",c);
}

  以下程式的輸出結果是: 36。

  因為如此原因,在許多C語言編程規範中提到使用#define定義時,如果定義中包含運算式,必須使用括弧,則上述定義應該如下定義才對:
#define f(x) (x*x)

  當然,如果你使用typedef就沒有這樣的問題。

  4. typedef & #define的另一例

  下面的代碼中編譯器會報一個錯誤,你知道是哪個語句錯了嗎?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;

  答案與分析:

  是p2++出錯了。這個問題再一次提醒我們:typedef和#define不同,它不是簡單的文本替換。上述代碼中const pStr p2並不等於const char * p2。const pStr p2和const long x本質上沒有區別,都是對變數進行唯讀限制,只不過此處變數p2的資料類型是我們自己定義的而不是系統固有類型而已。因此,const pStr p2的含義是:限定資料類型為char *的變數p2為唯讀,因此p2++錯誤。

  #define與typedef引申談

  1) #define宏定義有一個特別的長處:可以使用 #ifdef ,#ifndef等來進行邏輯判斷,還可以使用#undef來取消定義。

  2) typedef也有一個特別的長處:它符合範圍規則,使用typedef定義的變數類型其作用範圍限制在所定義的函數或者檔案內(取決於此變數定義的位置),而宏定義則沒有這種特性。

  5. typedef & 複雜的變數聲明

  在編程實踐中,尤其是看別人代碼的時候,常常會遇到比較複雜的變數聲明,使用typedef作簡化自有其價值,比如:

  下面是三個變數的聲明,我想使用typdef分別給它們定義一個別名,請問該如何做?
>1:int *(*a[5])(int, char*);
>2:void (*b[10]) (void (*)());
>3. doube(*)() (*pa)[9];

  答案與分析:

  對複雜變數建立一個類型別名的方法很簡單,你只要在傳統的變數聲明運算式裡用類型名替代變數名,然後把關鍵字typedef加在該語句的開頭就行了。
>1:int *(*a[5])(int, char*);
//pFun是我們建的一個類型別名
typedef int *(*pFun)(int, char*);
//使用定義的新類型來聲明對象,等價於int* (*a[5])(int, char*);
pFun a[5];

>2:void (*b[10]) (void (*)());
//首先為上面運算式藍色部分聲明一個新類型
typedef void (*pFunParam)();
//整體聲明一個新類型
typedef void (*pFun)(pFunParam);
//使用定義的新類型來聲明對象,等價於void (*b[10]) (void (*)());
pFun b[10];

>3. doube(*)() (*pa)[9];
//首先為上面運算式藍色部分聲明一個新類型
typedef double(*pFun)();
//整體聲明一個新類型
typedef pFun (*pFunParam)[9];
//使用定義的新類型來聲明對象,等價於doube(*)() (*pa)[9];
pFunParam pa;

 

下面的3篇文檔是在學習typedef歸納的總結:
//第1篇:typedef語句格式
#include<stdio.h>
typedef int GTYPE;//定義全域類型GTYPE
void main()
{
  //一、基本格式
  typedef char CH;//重定義基本類型
  typedef struct{int a,b,c;}STRU;//重定義自訂類型(結構、共用、枚舉)
  typedef union{int a,b,c;}UNIO;
  typedef enum{one,two,three}NUM;
  typedef char* STR;//重定義衍生類別型(指標、數組、函數)
  typedef int AI[10];
  typedef void FUN(void);
  //可見,typedef使用的格式為:typedef 變數/函數定義語句;即在原變數/函數定義語句的開頭加上
  //typedef,便可將原變數/函數定義語句改寫為類型定義語句,原來定義的變數名/函數名都成了類型名。
  //註:當重定義基本、自訂類型時,格式也可總結為:typedef 原類型 新類型1,新類型2,…;

  //二、觀察原類型
  //1.原類型可以帶有類型限定符
  typedef  const int CI;//原類型含const限定符
  CI ci=3;
  //ci=4;//可見CI確實代表const int
 
  //2.原類型可以是typedef定義的新類型
  typedef CH NEWCH;//CH已在前面定義為char
  NEWCH nc='a';
 
  //3.原類型不可帶有儲存類別
  //typedef static int SI;//錯誤,"指定的儲存類多於1個"
  //typedef register int RI;//錯誤同上
 
  //4.原類型應是一種類型,而不可是變數/對象
  float f=0;//將f定義為變數
  //typedef f FL;//錯誤
 
  //5.不宜重定義的類型
  typedef const CON;//重定義const
  //CON int a=0;//但該類型無法正常使用
  typedef unsigned US;//重定義unsigned
  US us1=0;//正確,相當於unsigned int
  //US int us2;//錯誤,無法正常使用
  //註:因const、unsigned等並不是一種獨立的類型,故不便對它們重定義

  //三、觀察新類型
  //1.新類型的範圍
  typedef int LTYPE;//定義局部類型LTYPE
  void fun();//觀察LTYPE在fun中是否有效
  fun();
  //可見,用typedef定義的類型也有範圍之分。在函數內用typedef定義的是局部類型
  //typedef也可以像變數定義語句一樣放置在函數之外,這時定義的是全域類型
 
  //2.新類型可否是已有類型
  //typedef int float;//錯誤,不能重定義標準類型
  typedef int TYPE; //定義了新類型TYPE
  //typedef char TYPE;//錯誤,"TYPE重定義"
  typedef int GTYPE;//正確,儘管GTYPE是已有類型,但它是本函數外定義的
  //可見,新類型名必須是合法的標識符,它在其範圍內必須是唯一的

  //3.新類型可否不止一個
  typedef float LENGTH,WIDTH;//對float取兩個別名,LENGTH和WIDTH
  LENGTH  L=0;//用新類型定義變數
  WIDTH W=0;
  //可見,可以一次性為某個原類型指定多個別名

  //4.一次可否定義多個不同類型
  //typedef int I,float F;//一次定義了二個不同類型I和F
  //F v=6.0;//試圖使用F類型
  //printf("v=%f ",v);//v=0,類型F無效
  //可見,一條typedef語句只宜為一個原類型定義別名

}
void fun()
{ //LTYPE i;//錯誤,"LTYPE:未定義的標識符"
  GTYPE g=1;//使用全域類型GTYPE
  printf("g=%d ",g);//正常使用
}

//第3篇:typedef具體應用

#include<stdio.h>
int fun1(int a,int b){return 0;}
int fun2(int a,int b){return 0;}
int fun3(int a,int b){return 0;}
void main()
{ //一、使用typedef的優點
  //1可以為現有類型取一個更有意義的名字,增加程式的可讀性,如
  typedef char* STRING;
  STRING s1="string1",s2="string2";//STRING便可當作字串類型來使用
 
  //2使變數定義更簡短,減少書寫麻煩,如
  typedef float A3[2][3][4];//為2*3*4的實型數組取簡短的名字A3
  A3 a,b,c;//相當於定義float a[2][3][4],b[2][3][4],c[2][3][4]
  typedef unsigned int(*PFUN)(int(*)[4]);//PFUN是一種函數指標,該類函數參數為一維數組型指標,傳回值為無符號整型
  PFUN pf1,pf2;//相當於定義unsigned int(*pf1)(int(*)[4]);unsigned int(*pf2)(int(*)[4])
 
  //3在定義複雜類型前,先用typedef建立一些中間類型,再用中間類型去構造複雜類型,以降低其複雜性(主要用途)
  //例:對於如下的複雜定義
  int(*ap[3])(int,int);//ap是一個數組,其元素為函數型指標,該類函數的參數和傳回值都是整型
  //採用typedef以降低定義難度
  //方法1
  typedef int(*PF)(int,int);//定義PF為該種函數的指標
  PF ap1[3];//ap1為一數組,每個元素都是PF類型
  ap1[0]=fun1;ap1[1]=fun2;ap1[2]=fun3;
  //方法2
  typedef int FUN(int,int);//將該種函數定義為FUN類型
  FUN* ap2[3];//ap2為一數組,每個元素都是指向FUN的指標
  ap2[0]=fun1;ap2[1]=fun2;ap2[2]=fun3;
 
  //4增加程式的可移植性(有利於程式在不同處理器、作業系統和編譯系統之間的移植)
  /*例如,在TC下讀檔案的程式段如下:
  FILE* fp;
  long buffer1;
  fread(&buffer1,sizeof(long),1,fp);//每次讀出4個位元組
  若在VC下每次需要讀出8個位元組,程式需如下修改:
  double buffer2;
  fread(&buffer2,sizeof(double),1,fp);
  現用typedef方法,程式段如下:
  typedef long UNIT;//UNIT在TC中代表long,在VC中代表double
  UNIT buffer;
  fread(&buffer,sizeof(UNIT),1,fp);//每次讀出UNIT個位元組
  當移植到VC下時,只需改動UNIT的定義即可:typedef double UNIT;*/

  //二、typedef與define的區別
  //用define也可實現簡單的類型替換,如
  #define INT long //用INT來代替long
  //兩種方式的區別如下:
  //1.二者處理時間不同,宏替換是在先行編譯時進行的,而類型定義是在正式編譯時間處理的
  //2二者本質不同,宏替換隻是將宏名簡單替換為目標字串,而類型定義如同定義變數一樣
  //是真的為程式增加了一種可用類型
  //3.二者複雜性不同,用typedef可定義各種複雜的類型,並以各種方式使用新類型(詳見10_10_2.cpp)
  //而define只能替換基本類型和自訂類型,無法替換衍生類別型,且使用起來很不安全,例如
  #define pi int* //試圖用pi代替整型指標
  pi pi1;//正確,展開後為int* pi1;
  pi pi2,pi3;//錯誤,原意是將pi2,pi3都定義成整型指標,但展開後為int* pi2,pi3; pi3並未定義成指標

  #define NUM enum{one,two,three}//試圖用NUM代替該枚舉類型
  NUM n1;//正確,定義了枚舉常量one,two,three和枚舉變數n1
  //NUM n2;//錯誤,展開後為enum{one,two,three}n2;從而造成枚舉常量one,two,three的重定義

  #define DATE struct{int y,m,d;}
  DATE *pd;//正確,定義了該結構型指標
  //pd=(DATE)1;//錯誤,展開後為pi=(struct{int y,m,d;})1;目前尚不支援此種類型轉換寫法

  #define TIME union{int h,m,s;}
  //int L=sizeof(TIME);//錯誤,展開後為int L=sizeof(union{int h,m,s;});sizeof運算元錯誤
  //可見,用define進行類型替換時,會產生各種意想不到的錯誤,故應避免使用,而改用安全的typedef

}

 

//第2篇:typedef詳細使用/* 為了從易到難地使用typedef,現將C++資料類型按照類型名的來源和複雜性重分類如下:一、基本類型(類型名是系統指定的單一標識符)in,char,float,double,void,const二、自訂類型(類型名是使用者定義的單一標識符)1.結構類型struct stru{int i;struct stru*;};2.共用類型union unio{int i;enum num[10];};3.枚舉類型enum num{a,b,c};4.typedef類型typedef double db;三、衍生類別型(類型名由已有類型與其它符號組合而成)1.指標類型(由 已有類型* 組成)void*,char**,void(*)(),struct stru*,enum num*2.數群組類型(由 已有類型[][] 組成)int[3][4],struct stru[10],enum num[10],char*[10]3.函數類型(類型名是各種符號組成的函數原型)void main(void),char* strcpy(char*,char*)以上三大類別的類型標識符由簡單到複雜,學習typedef時要依照先後順序,練習每種類型的重定義每定義出一種新類型後,從以下幾個方面使用它:用它定義變數、指標、數組、帶儲存類別的對象、帶const的對象;用它作函數參數和傳回值類型;用它進行類型轉換;用sizeof求長度*/#include<stdio.h>#include<stdlib.h>void main(){  //一、重定義基本類型  //1.定義  typedef int in;  typedef char ch;  typedef float fl;  typedef double db;  typedef unsigned int ui;//或寫為typedef unsigned ui;  typedef unsigned char uc;  typedef void vo;  //2.使用  in i=3;printf("i=%d",i);  //用新類型定義變數  ch* pc="pc";printf("pc=%s",pc);//用新類型定義指標  fl af[3]={1,2,3};printf("af[0]=%f",af[0]);//用新類型定義數組  static double sd;printf("sd=%f",sd);//用新類型定義帶儲存類別的變數  const ui cui=3;printf("cui=%d",cui);//用新類型定義帶類型限定符的變數  vo fun1(uc);fun1('a');//用新類型作函數的參數和傳回值類型  printf("in(1.5)=%d",in(1.5));//將新類型用作類型轉換  printf("$db=%d",sizeof(db));//對新類型求長度,$db=$double=8  //二、重定義自訂類型  //1.定義  //(1)完整寫法(先定義好自訂類型,再重定義它)  struct datetype                 //重定義結構類型  { ui year;//ui即是unsigned int;    uc month;//uc即是unsigned char;    uc day;};  typedef datetype date;  union scoretype                 //重定義共用類型  { fl sco1;    ch* sco2;};  typedef scoretype score;  enum numtype{one,two,three};    //重定義枚舉類型  typedef numtype num;  typedef num newnum;             //重定義typedef類型  //(2)通常寫法(對無類型名的結構、共用、枚舉定義語句直接重定義)  typedef struct{uc hou,min,sec;} time;  typedef union{fl sco1;ch* sco2;} sco;  typedef enum{red,green,blue} color;  //可見,無論何種寫法,都遵循typedef語句的格式:typedef 原類型  新類型;  //2.使用  date vs={2002,10,15};//定義結構變數  printf("vs:%d.%d.%d",vs.year,vs.month,vs.day);  score vu,*pu=&vu;printf("&vu=%x pu=%x",&vu,pu);//定義共用指標  newnum ae[3]={one,two,three};//定義枚舉數組  printf("ae[0]=%d ae[1]=%d ae[2]=%d",ae[0],ae[1],ae[2]);  static num vn;//定義帶儲存類別的對象  printf("vn=%d",vn);  const sco cs={90};//定義const對象  printf("cs.sco1=%f",cs.sco1);  time fun2(time);//作函數的參數和傳回值類型  static time vt=fun2(vt);  printf("fun2:%d:%d:%d",vt.hou,vt.min,vt.sec);  vn=(num)10;printf("vn=%d",vn);//將新類型用作類型轉換  printf("$time=%d",sizeof(time));//對新類型求長度,$time=3  printf("$sco=%d",sizeof(sco));//對新類型求長度,$sco=4  printf("$color=%d",sizeof(color));//對新類型求長度,$color=4//未完待續  //2.重定義數群組類型  //(1)定義  typedef int ai[5];//重定義基本類型數組  typedef float a2[3][4];//重定義數組型數組(二維數組)  typedef time as[2];//重定義結構型數組  typedef sco au[2];//重定義共用型數組  typedef color ae2[2];//重定義枚舉型數組  typedef char* ap2[2];//重定義指標型數組  //註:重定義數組時,C++暫不支援typedef int[10] ai這種正規寫法  //(2)使用  //定義變數(陣列變數)  ai vai={1,2,3,4,5};//相當於int vai[5];  printf("vai:%d %d",vai[0],vai[1]);  //定義指標(數組指標)  a2 va2,*pa2=&va2;//相當於float va2[3][4],(*pa2)[3][4]=&va2;  printf("&va2=%x pa2=%x",va2,pa2);  //定義數組(多維陣列)  as vas[2]={{10,20,30},{20,40,59}};//vas此時是二維結構數組,相當於time vas[2][2];  printf("vas[0][0]:%d:%d:%d",vas[0][0].hou,vas[0][0].min,vas[0][0].sec);  //定義帶儲存類別的數組  static au vau;//vau是含2元素的靜態共用數組  printf("vau[2]:%f %f",vau[0].sco1,vau[1].sco1);  //定義cosnt數組  const ae2 cae2={red,green};//cae2是含2元素的常量枚舉數組  printf("cae2[2]:%d %d",cae2[0],cae2[1]);  //定義函數  void fun4(ap2);//參數是指標數組,相當於void fun4(char* ap2[2]);  ap2 vap2={"str1","str2"};//vap2的兩元素分別指向"str1"和"str2"  fun4(vap2);  printf("$ai=%d",sizeof(ai));//對新類型求長度,$ai=$int[5]=20  printf("$a2=%d",sizeof(a2));//對新類型求長度,$a2=$float[3][4]=48  printf("$as=%d",sizeof(as));//對新類型求長度,$as=$=time[2]=6  printf("$au=%d",sizeof(au));//對新類型求長度,$au=$=sco[2]=8  printf("$ae2=%d",sizeof(ae2));//對新類型求長度,$ae2=$color[2]=8  printf("$ap2=%d",sizeof(ap2));//對新類型求長度,$ap2=$=char*[2]=8  //註:因函數傳回值不能是數群組類型,故這裡不再將重定義類型用作函數傳回值類型  //3.重定義函數類型  //(1)定義方法  int fun(int,int);//聲明了一個函數,它有兩個整型參數、傳回值為整型  typedef int FUN(int,int);//定義了一個類型,它代表含兩個整型參數、傳回值為整型的函數  //(2)使用方法  //定義變數(函數)  FUN max,min;//用類型FUN定義了兩個對象max和min,因FUN是函數類型,故它們自然是函數對象  //這裡相當於int max(int,int)和int min(int,int)兩個聲明  printf("max(3,5)=%d",max(3,5));//調用這兩個函數  printf("min(3,5)=%d",min(3,5));  //定義指標(函數指標)  FUN* pfun;//pfun是一種指標,它專門指向FUN類型的對象。相當於定義int(*pfun)(int,int);  pfun=max;//max和min都是FUN類型的對象  printf("&max=%x pfun=%x",max,pfun);  //其它使用基本無意義,略  //可見,若程式中要用到許多類型相同但名稱不同的函數,可先用typedef抽象出類型,再用該類型  //一次便可聲明許多函數,以簡化函數原型的書寫。但這種類型只能用來聲明函數,而不能用來定義函數} //main end//下面是main中用到的函數void fun1(unsigned char p)//因vo與uc的範圍不在此處,故這裡直接使用void和unsigned { printf("fun1:%c",p);//這樣與函數原型vo fun1(uc)亦對應}typedef struct{unsigned char hou,min,sec;} time;//因main中的time類型範圍已結束,故這裡要使用必須重新定義time fun2(time p){ p.hou=20;p.min=30,p.sec=55;   return p;}/*註:嚴格來說這裡的time與main中函數原型裡的time並不是同一類型,但系統忽略了這點正確用法應將main中的time作為全域類型定義在程式開頭,這樣其後的函數都可共用使用該類型*/void* fun3(float** p){ printf("fun3:malloc(%d)",p);   return malloc(int(p));}typedef char* ap2[2];void fun4(ap2 p){ printf("fun4:p[0]=%s p[1]=%s",p[0],p[1]);}int max(int a,int b){ return a>b?a:b;}int min(int a,int b){ return a<b?a:b;}

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.