谈谈C语言的数据类型
本文摘要:
本文主要讲述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 }
断断续续的写,本文到此就要结束了,有问题留言,有错误留言。特别是后面的两个代码。作者也不敢确定是否实用。