本文摘要:
本文主要講述C語言中的數據類型,從基本的數據類型到派生的數據類型,從int ,char ,float double ....到指針,數組,函數,指向指針的指針,指向數組的指針,指向函數的指針,指針與數組的區別,指針作為函數參數,函數作為函數參數。作為例子,本文將通過通用鏈表結構來說明void*如何實現通用結構設計,通過相對通用的哈希結構來說明如何利用函數指針作為函數的參數以及如何在結構體中封裝函數指針以實現相當於類的功能結構。
首先,通過一些常見的聲明來開始本文,這些聲明幾乎包含本文所寫的全部內容。
int a ====>> int *a ====>> int a[] ====>> int *a[] ====>> int (*a)[] ====>> int a[][] ====>> int **a ====>> int (*a)() ====> int (*a[])()
那麼,我們可以將上述與sizeof組和得出結果。讀者可以試試寫出結果,然後再上機驗證
再來看看const
const char *str ====>> char *const str ====>> char const *str
ok,現在開始正文的內容。
對於基本數據類型類似int float的平時接觸還是比較多的。在此就不再贅述,下面從數組和指針開始寫起.
簡單的一維數組如int a[10],指向整型的指針如int *pa,我們學編程語言的是老外的,從老外的角度來看,int *pa可以解析為pa is a pointer to int。pa是指向整型數據的指針。但是我們可以這樣寫int *pa = malloc(sizeof(int) * 10);我現在來做一個sizeof對比。
int main() { int a[10]; int *pa = malloc(sizeof(int) * 10); printf("pa:%d\n", sizeof(pa)); printf("a:%d\n", sizeof(a)); return 0; }
pa:4 <<=====>>a:4*10
事實上,上面的測試pa = malloc(sizeof(int)*10)顯得比較多余,直接用pa[1]完全沒有問題,但是數據是棧中的殘渣。
那麼作為函數參數的情況又是怎樣呢?結果是:無論你傳入的是pa還是a,sizeof的結果都是4.這說明C語言數組作為參數的時候,傳入的只是一個指針,only pointer.C不會將整個數組作為參數傳入到被調用函數中的。萬一數組很大呢,達到100000個int型,那結果無法想想。關鍵點在於C語言是不會傳遞整個數組作為參數的,無論你聲明的是int func(int a[])還是int func(int a[][5]),他都不是數組傳遞。相當與int func(int *a), int func(int (*a)[5])
上面的只是最簡單的測試,基本上大家都曉得這個道理,接下來來一個復雜一點的聲明
int a[3][4] <<====>> int (*a)[4] <<====>> int *a[4]
這一波攻擊認真看還是能看出點端倪的。
首先就int a[3][4]來發表提問:a是什麼?它占用多大存儲空間,a[1][2]是什麼?它占用多大的存儲空間?a[1]又是什麼?占用多大的存儲空間?有a[4]嗎?你試著去輸出過他們的地址來嗎?
然後就int (*a)[4]提問:a是什麼?它占用多大存儲空間。能a到幾啊,a[10]到底能不能行?有a[10][4]嗎?
對於int *a[4]我就向問一個,他是什麼?因為很簡單,知道她是什麼,就無話可說了。
通用我也做一個簡單的測試。這次我附上測試結果.
int main() { int i; int a[3][4] = { 1,2,3,4, 5,6,7,8, 9,10,11,12 }; int (*pa)[4]; char *ppa[10]; printf("sizeof(a):%d\n", sizeof(a)); printf("sizeof(a[1]):%d\n", sizeof(a[1])); printf("sizeof(a[1][1]):%d\n", sizeof(a[1][1])); for(i = 0; i<4; i++) printf("addr a[%d]:%p ", i, a[i]); printf("\n"); printf("sizeof(pa):%d\n", sizeof(pa)); printf("sizeof(pa[10]):%d\n", sizeof(pa[10])); printf("sizeof(pa[1][1]):%d\n", sizeof(pa[1][1])); printf("sizeof(ppa):%d\n", sizeof(ppa)); printf("sizoef(ppa[1]):%d\n", sizeof(ppa[1])); }
在作者使用的系統測試的結果如下
sizeof(a):48 sizeof(a[1]):16 sizeof(a[1][1]):4 addr a[0]:0xbfb90ea4 addr a[1]:0xbfb90eb4 addr a[2]:0xbfb90ec4 addr a[3]:0xbfb90ed4 sizeof(pa):4 sizeof(pa[10]):16 sizeof(pa[1][1]):4 sizeof(ppa):40 sizoef(ppa[1]):4
可以看出:
a占用的大小是3*4*sizeof(int), a[1]占用的大小是:4*sizeof(int),說明a[1]是指針,讀者可以通過printf("%d\n", a[1])來測試,這裡我們就利用編譯器的警告功能來驗證a[1]是int*類型。a[]1[1]的大小是sizeof(int)。輸出地址的部分也是由於作者的筆誤,輸出來a[3]的地址,但是這一筆誤引起來作者的思考,進而作者輸出到a[6]發現依舊編譯通過,運行成功。結合a[i]是整型指針,和第一個例子後面說的不用malloc的pa版本依舊可以應用pa[i]。很容易明白這個道理,反正指針只負責增運算。誰知道增到什麼地方。
pa可以這樣理解:首先pa是個指針,那麼是什麼的指針?是數組的指針,那麼是什麼樣的數組?int型數組。整合===>>pa is a pointer to an array of int。pa是指向整型數組的指針。pa指向的是一個整型數組,這個數組的長度是4。我們知道,指針可以做增運算,實際上你可以理解pa指向一維數組的第一個元素,這個一維數組包含4個元素。
ppa就是一個指針數組,簡單來說就是他是一個數組,數組的每個元素都是指針。此處為方便對比,用char型指針。因為int型在本機上測試的是4,當sizeof(ppa[1])的時候結果是4,你知道他是int的大小還是指針的大小??指針的大小是4.
上面的例子相對第一個來說還是上升一點層次,但是明顯還不是很難,下面開始有關函數的介紹
有沒有想過對一個函數進行sizeof運算?別想了,去試試吧。
int func()
{
return 0;
}
sizeof(func);
我們主要關注點在函數和指針上,當然,還穿插一些數組的知識。
對於這個void* (*f)(void*)或許有些朋友還是挺陌生的。但是對於
void * func(void * par)
{
return NULL;
}
這個應該不陌生。那我告訴你,其實你還可以f = func;f(NULL);比如下面的代碼
void *func(void * par) { printf("function call\n"); return NULL; } int main() { void* (*f)(void *); f = func; func(NULL); f(NULL); (*f)(NULL); return 0; }
那 void (*func[])()呢?會不會有人說 void ((*func)[])()呢?,嗯。。我只能說void ((*func)[])()是不合法的聲明,void (*func[])()是一個函數數組。就是說你可以向訪問數組那樣訪問函數,就像下面代碼那樣
1 void *func(void * par) 2 { 3 static int i; 4 printf("function call:%d\n", ++i); 5 return NULL; 6 } 7 8 void *func1(void * par) 9 { 10 static int i; 11 printf("function1 call:%d\n", ++i); 12 return NULL; 13 } 14 15 int main() 16 { 17 void* (*f)(void *); 18 void* (*fa[5])(void *); 19 //void* ((*ff)[])(); 20 int i = 5; 21 22 f = func; 23 func(NULL); 24 f(NULL); 25 (*f)(NULL); 26 while(i--) 27 { 28 if(i%2 == 0) 29 fa[i] = func; 30 else 31 fa[i] = func1; 32 fa[i](NULL); 33 } 34 return 0; 35 }
對於結構體,共同體,枚舉類型,不在這裡的討論范圍,雖然標題是談談C語言的數據類型。
下面結合作者以前寫的一些代碼給讀者提供編程范式的思考。更多編程范式的問題,可以斯坦福大學編程范式相關課程。網上能夠找到相關視頻。筆者也是這兩天才在youtube上看到這個視頻,當看到編程范式-C語言的時候,感覺跟筆者寫的相對通用的數據結構庫的思想不謀而合。
首先,第一個是通用鏈表的設計,該程序旨在用純C編寫一個相對通用的鏈表,相當與實現C++泛型的功能。
1 /* 2 * 通用鏈表結構 3 * 作者: 4 * 編寫時間:2015年2月20號 5 * 6 * */ 7 8 /* 9 * 通用鏈表外部接口調用 10 * 提供一個鏈表結構 11 * typedef struct slist 12 * { 13 * void *data; 14 * struct slist *next; 15 * }*plist 16 * 17 * 提供下列操作結構 18 * slist_init() 19 * slist_add_head() 20 * slist_add_tail() 21 * slist_remove() 22 * slist_drop() 23 * 後續需求添加和修改具體測試 24 * */ 25 26 #include <malloc.h> 27 #include <stdlib.h> 28 #include <string.h> 29 30 /* 31 * 初始化鏈表 32 * 33 * */ 34 #define slist_init(head) \ 35 struct slist *head = malloc(sizeof(struct slist));head->next = NULL; 36 37 /* 38 * 取鏈表每個節點,遍歷鏈表需要 39 * 由於應對不同需要,遍歷數據難以得到通用(詳細請看demo) 40 * 所以僅僅提供能夠取得各個節點的代碼 41 * 42 * */ 43 #define slist_travel(head,list) \ 44 for(head = list->next;head!=NULL;head=head->next) 45 46 /* 47 * 判斷鏈表是否為空 48 * 49 * */ 50 51 #define slist_empty(l) \ 52 l->next == NULL; 53 54 /* 55 * 鏈表結構 56 * 數據域采用void *來存放任意數據類型的指針 57 * 58 * */ 59 typedef struct slist 60 { 61 void* data; 62 struct slist *next; 63 }*pslist; 64 65 /* 66 * 鏈表數據插入,頭插法 67 * 參數:鏈表頭指針,要添加的變量,變量所占用的存儲空間 68 * 返回值和:void 69 * */ 70 71 void slist_add_head(pslist, void*, int); 72 73 /* 74 * 鏈表數據插入,尾插法 75 * 參數和頭插法一樣 76 * 返回值:void 77 * 78 * */ 79 80 void slist_add_tail(pslist, void*, int); 81 82 /* 83 * 鏈表數據刪除 84 * 參數和插入傳入的參數一致 85 * 返回值:返回1如果刪除成功,返回0如果刪除失敗 86 * 87 * */ 88 89 int slist_remove(pslist, void*, int); 90 91 /* 92 * 鏈表銷毀 93 * 參數:鏈表頭指針 94 * 返回值:void 95 * 說明:銷毀鏈表的節點,留下頭指針,銷毀完的狀態相當與slist_init() 96 * 97 * */ 98 99 void slist_drop(pslist);
1 #include "slist.h" 2 3 //數據插入,頭插 4 5 void slist_add_head(pslist l,void* data, int len) 6 { 7 pslist node = malloc(sizeof(struct slist)); 8 void* pdata = malloc(len); 9 memcpy(pdata,data,len); 10 11 node->data = pdata; 12 node->next = l->next; 13 l->next = node; 14 } 15 //數據插入,尾插 16 void slist_add_tail(pslist l,void* data, int len) 17 { 18 pslist node = malloc(sizeof(struct slist)); 19 void* pdata = malloc(len); 20 pslist cur = l; 21 memmove(pdata,data,len); 22 node->data = pdata; 23 for(;cur->next!=NULL;cur =cur->next); 24 node->next = cur->next; 25 cur->next = node; 26 27 } 28 //數據刪除 29 int slist_remove(pslist l,void* data, int len) 30 { 31 pslist cur = l; 32 for(;cur->next!=NULL && memcmp(cur->next->data,data,len) != 0;cur = cur->next) 33 ; 34 if(cur->next != NULL) 35 { 36 pslist temp = cur->next; 37 cur->next = cur->next->next; 38 free(temp); 39 return 1; 40 } 41 return 0; 42 } 43 //銷毀鏈表 44 void slist_drop(pslist l) 45 { 46 pslist cur = l->next,next; 47 l->next = NULL; 48 while(cur!= NULL) 49 { 50 next = cur->next; 51 free(cur->data); 52 free(cur); 53 cur = next; 54 } 55 }
slist_init這個宏是有bug的,沒有做返回值檢查。在上面代碼中廣泛用到通用指針類型void *和內存拷貝函數。既然有來泛型,為何要用這段代碼,作者對C++的泛型思想也不是很了解,但是泛型應該是生成多段代碼的,比如說int型代碼有一段,float代碼有一段,double又有一段。那效率可見一般。作者最喜歡的編程語言是C,因為它面向過程,而且代碼高效自由,但想做好的設計需要一定功力,其次是java。它完全面向對象用起來還是不錯。但是對於C++就沒多大感覺來,希望C++用戶勿噴。
至於用函數作為參數和函數指針作為結構體成員的實例代碼,可以參考作者在寫哈希結構時候的代碼。
1 /* 2 * 通用散列表結構 3 * 編寫者: 4 * 編寫時間:2015.3.06 5 * 修改時間: 6 * 7 * */ 8 9 10 #include <stdio.h> 11 #include <malloc.h> 12 13 /* 14 * 用到的鏈表結構 15 * 自己設計的通用鏈表結構 16 * 17 * */ 18 19 #include "../list/_slist/slist.h" 20 21 //hash 表結構定義, 22 typedef struct hash 23 { 24 /* 表大小 */ 25 int table_size; 26 /* 各個頭指針 */ 27 struct slist **heads; 28 /* 供用戶設置的hash函數 */ 29 int (*hash_func)(void *, int table_size); 30 }*phash; 31 32 /* 33 * 哈希表初始化 34 * 參數:表大小,哈希函數指針 35 * 返回值:哈希表 36 * 說明:適應各個不同的需求,哈希函數由用戶自定義, 37 * 哈希函數接收表長和key兩個數據,返回哈希值 38 * 39 * */ 40 41 struct hash *hash_init(int table_size, int (*func)(void *, int table_size)); 42 43 /* 44 * 哈希表數據插入 45 * 參數:哈希表,數據,數據的長度 46 * 返回值:void 47 * 48 * */ 49 50 void hash_insert(struct hash*, void *data, int len); 51 52 /* 53 * 哈希表查詢 54 * 參數:哈希表,數據,長度 55 * 返回值:void 56 * 57 * */ 58 59 struct slist *hash_find(struct hash*, void *data, int len); 60 61 62 /* 63 * 本哈希表編寫沒有哈希表數據移除的操作 64 * 65 * */
1 #include "hash.h" 2 3 //哈希表初始化 4 struct hash *hash_init(int table_size, int(*Hash) (void *data, int table_size)) 5 { 6 struct hash *h = malloc(sizeof(struct hash)); 7 int i; 8 h->table_size = table_size; 9 h->hash_func = Hash; 10 h->heads = malloc(sizeof(struct slist) * h->table_size); 11 if(h->heads == NULL) 12 error("malloc"); 13 for(i = 0; i<h->table_size; i++) 14 { 15 h->heads[i] = malloc(sizeof( struct slist)); 16 if(h->heads[i] == NULL) 17 error("malloc"); 18 h->heads[i]->next = NULL; 19 } 20 return h; 21 } 22 23 //哈希表查詢 24 struct slist *hash_find(struct hash *h, void *data, int len) 25 { 26 struct slist *find_head = h->heads[h->hash_func(data,h->table_size)]; 27 struct slist *p = find_head->next; 28 while(p && memcmp(p->data, data, len) != 0) 29 p = p->next; 30 return p; 31 } 32 33 //哈希表插入 34 void hash_insert(struct hash *h, void *data, int len) 35 { 36 struct slist *new_come, *head; 37 38 struct slist *pos = hash_find(h, data, len); 39 if(pos == NULL) 40 { 41 new_come = malloc(sizeof(struct slist)); 42 if(new_come == NULL) 43 error("malloc"); 44 else 45 { 46 head = h->heads[h->hash_func(data, h->table_size)]; 47 new_come->next = head->next; 48 new_come->data = malloc(len); 49 memcpy(new_come->data, data, len); 50 head->next = new_come; 51 } 52 } 53 }
斷斷續續的寫,本文到此就要結束了,有問題留言,有錯誤留言。特別是後面的兩個代碼。作者也不敢確定是否實用。