1. 程式人生 > >typedef 和結構體變數的定義方法

typedef 和結構體變數的定義方法

首先請看程式:
#include<stdio.h> 
#include<string.h> 
#pragma pack(4) 

/*
struct 

int num ; 
char name[8]; 
char sex; 
double score[2]; 
}stu1; //方法一 只能在定義結構體時用一次,以後再也無法定義此結構體變數,缺乏靈活性。
*/

struct student 

int num ; 
char name[8]; 
char sex; 
double score[2]; 
}stu1; //方法二 此處定義結構體時,順便定義了結構體變數。

/*
typedef struct student 


int num ; 
char name[8]; 
char sex; 
double score[2]; 
}stu;//方法三 注意:此處並非結構體變數,而是結構體型別(相當於人的小名,相當於struct student)。
*/

#pragma pack() 
int main( ) 

// student stu1; //方法二 (這裡在C++中沒有問題,在C語言中不可以,只能用下面方法定義)
// struct student stu1; //方法二 
// stu stu1; //方法三 

stu1.num=2001; 
strcpy(stu1.name,"張華"); 
stu1.sex='M'; 
stu1.score[0]=86.00; 

stu1.score[1]=92.2; 
printf("%d\t%s\t%c\t%f\t%f\n",stu1.num,stu1.name,stu1.sex,stu1.score[0],stu1.score[1]);
// if float score[2] and pack(8) or pack(4) will be 24 ,pack(2); will be 22 ,pack(1); will be 21
// and warning C4305: '=' : truncation from 'const double' to 'float' 
// if double score[2] and pack(8) or pack(4) will be 32 ,pack(2); will be 30 ,pack(1); will be 29

printf("%d\n",sizeof(struct student)); 
printf("%d\n",sizeof(char)); //1 
printf("%d\n",sizeof(int)); //4 
printf("%d\n",sizeof(long)); //4 
printf("%d\n",sizeof(float)); //4 
printf("%d\n",sizeof(double));//8 
return 0; 



進入正文:
 程式碼簡化, 促進跨平臺開發的目的。
typedef 行為有點像 #define 巨集,用其實際型別替代同義字。

 不同點:typedef 在編譯時被解釋,因此讓編譯器來應付超越前處理器能力的文字替換。

 另外,在C++中struct 的成員預設是公有的,而類的成員預設是私有的。struct 和 class 在其他方面是功能相當的。

用法一:

typedef int (*MYFUN)(int, int); 
這種用法一般用在給函式定義別名的時候
上面的例子定義MYFUN是一個函式指標, 函式型別是帶兩個int 引數, 返回一個int 。

在分析這種形式的定義的時候可以用下面的方法: 
先去掉typedef 和別名, 剩下的就是原變數的型別。
去掉typedef和MYFUN以後就剩:
int (*)(int, int)

用法二:
typedef給變數型別定義一個別名。

typedef struct

int a; 
int b; 
}MY_TYPE; 

這裡把一個未命名結構直接取了一個叫MY_TYPE的別名, 這樣如果你想定義結構的例項的時候就可以這樣: 
MY_TYPE tmp;

第二種用法擴充套件:typedef原變數類型別名

簡單的函式指標的用法

//形式1:返回型別(*函式名)(引數表)
char(*pFun)(int);
//typedef char(*pFun)(int) //跟上一行功能等同

/*typedef的功能是定義新的型別。第一句就是定義了一種PTRFUN的型別,並定義這種型別為指向某種函式的指標,這種函式以一個int為引數並返回char型別。*/
char glFun(int a){return;}
void main()
{
pFun =glFun;
(*pFun)(2);
}

第一行定義了一個指標變數pFun。它是一個指向某種函式的指標,這種函式引數是一個int型別,返回值是char型別。只有第一句我們還無法使用這個指標,因為我們還未對它進行賦值。
第二行定義了一個函式glFun()。該函式正好是一個以int為引數返回char的函式。我們要從指標的層次上理解函式------函式的函式名實際上就是一個指標,函式名指向該函式的程式碼在記憶體中的首地址。

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合在一起才能表示一個結構型別(這是在C中,在C++中結構體相當於一個類,可以用tagMyStruct varName來定義變數)。
  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語句必須寫成 pStr2 s3, *s4; 這這樣才能正常執行。


  #define用法例子:
#define f(x) x*x
  main( )
  {
  int a=6,b=2,c;
  c=f(a) / f(b);
  printf("%d \\n",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. double(*)() (*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. double(*(*pa)[9])();
  //首先為上面表示式藍色部分宣告一個新型別
  typedef double(*pFun)();
  //整體宣告一個新型別
  typedef pFun (*pFunParam)[9];
  //使用定義的新型別來宣告物件,等價於double(*(*pa)[9])();
  pFunParam pa;

小例子:
#include <stdio.h> 
void main()
{
union V {
    struct X {
unsigned char s1:2;//這種定義沒看到過,幫忙解釋一下
             unsigned char s2:3;
             unsigned char s3:3;
             } x;

     unsigned char c;
} v;

v.c = 100; // c不是字元嗎?
printf("%d", v.x.s3); //v.x.s3=3,是怎麼算的?

}

unsigned char s1:2;
位域,宣告s1佔用一個char的2個bit位

v.c = 100; 
c被宣告為char,只是宣告c的資料型別,賦值時不一定必須是字元

printf("%d", v.x.s3); 
c被賦值為100時,v在記憶體中值為0110 0100,s3佔有8bit中高3bit,值為011,換算成十進位制也就是3


實際上這個問題還蠻複雜的,和執行環境的bit序有關係,換一個bit序s3的值可能就完全不一樣了。

unsigned char s1:2;//這種定義稱為位欄位(或稱位域),:2意思是在記憶體中只佔用兩位,而不是標準char型別的8位