写惯了PHP,Js,Python这些脚本语言,再写C语言最大的感受就是:强类型真的太不方便了。
比如说要写了个函数,取两个整数的最大值:
int max_int(int n1, int n2)
{
return (n1 > n2) ? n1 : n2;
}
如果取两个double类型的最大值。于是又写个函数:
int max_double(double n1, double n2)
{
return (n1 > n2) ? n1 : n2;
}
如果取两个unsigned类型的最大值,难道把同样的代码,再写一遍?
int max_uint(unsigned n1, unsigned n2)
{
return (n1 > n2) ? n1 : n2;
}
再比如,用C语言开发的栈和队列只能存一种类型,如果要存另一种类型要把同样的代码再写一遍?
C语言如何实现“弱类型”的效果呢?有两种方法可以实现这种效果。
Python和Js的变量和值分离是分离的,变量相当于是一个void *指针,这个指针可以指向任意类型的变量,当变量赋值不同的类型时只需将指针指向不同的变量即可。
#include <stdio.h>
typedef struct pval{
void *val;
int type;
} pval;
void myprint(pval val);
int main(){
pval pval1;
int a = 1;
char b = 'c';
float c = 1.2;
pval1.val = &a;
pval1.type = 1;
myprint(pval1);
pval1.val = &b;
pval1.type = 2;
myprint(pval1);
pval1.val = &c;
pval1.type = 3;
myprint(pval1);
}
void myprint(pval val){
if(val.type == 1){
printf("%s ", "int");
printf("%ld\n", *(int *)(val.val));
} else if(val.type == 2){
printf("%s ", "char");
printf("%c\n", *(char *)(val.val));
} else if(val.type == 3){
printf("%s ", "float");
printf("%f\n", *(float *)(val.val));
}
}
PHP的实现方法是用一个联合体用来存变量的值,再用一个变量来标识变量的类型。
#include <stdio.h>
typedef union uval{
long a;
double b;
char * c;
} uval;
typedef struct pval{
uval val;
int phptype;
} pval;
//enum
void var_dump(pval);
int main(){
pval pval1 = {{.a = 111}, 1};
var_dump(pval1);// int 111
pval1.phptype = 2;
pval1.val.b = 1.21;
var_dump(pval1);// float 1.210000
pval1.phptype = 3;
pval1.val.c = "abc";
var_dump(pval1);// string abc
}
/**
* @param val
* 根据pval变量类型,决定读取联合体的那个类型
*/
void var_dump(pval val){
if(val.phptype == 1){
printf("%s ", "int");
printf("%ld\n", val.val.a);
} else if(val.phptype == 2){
printf("%s ", "float");
printf("%f\n", val.val.b);
} else if(val.phptype == 3){
printf("%s ", "string");
printf("%s\n", val.val.c);
}
}
把这个问题再扩展一下,假如现在要写一个队列,不仅要存整型、字符、浮点型,还想在这个队列里面存自己定义的结构体类型,比如商户待处理的订单struct order
,售票系统排队的用户struct consumer
,工厂中排队加工的汽车struct car
等等。
现在问题来了,因为结构体是我们自己定义的,所以不可能预先写到代码里,这可咋办?
其实类型这个概念,是我们人为规定的,内存中存储的都是一些1和0,计算机执行的只不过是内存的拷贝,它也不知道这段内存意味着什么,整型,字符型等只不过是我们常用的内存长度,c语言中拷贝一个字符型,和拷贝一个字节长度的内存是一样的。
char a = 'a';
char b = 'b';
//下面两句等效
a = b;
memcpy(&a, &b, sizeof(char));
由此可见,计算机根本不需要知道什么类型,只需要知道内存从哪拷贝到哪及拷贝多长就足够了。基于这点我们就有思路实现“万能”的队列了。
//初始化时,传入要存储类型的长度n
struct queue init_queue(int n)
{
q.elem = malloc(n);
q.n = n;
......
}
//新增,相当于拷贝n个长度的内存
void insert(struct queue *q, void* e)
{
memcpy(q->elem+q->n*q->size, e, q->n);
......
}
//获取可以直接返回指针
void* queue_get(struct queue *q,int r)
{
return q->elem + r*q->n;
}
存不同类型的数据结构,只需初始化时传入不同的长度。
//存储订单的队列
struct queue q1 = init_queue(sizeof(struct order));
//存储用户的队列
struct queue q2 = init_queue(sizeof(struct consumer));
//存储汽车的队列
struct queue q3 = init_queue(sizeof(struct car));
上述方法用memcpy函数实现,缺乏可读性,这里介绍另外一种方法。假设我们要实现一个双向链表,要求这个链表即可以装int类型,又可以装float类型,甚至可以多种类型混合装。
首先设计一个通用的双向链表结构:
struct list_entry {
struct list_entry *prev, *next;
};
下面两个数据结构分别是装int型与float型变量的节点:
struct node_int{
list_entry list;
int i;
};
struct node_float{
list_entry list;
float i;
};
双向链表链接的是这些结构体中的list_entry,如此这般,就相当于将int类型与float类型放到一个双向链表中了: