C语言实现通用数据类型栈
C++的模板机制,给通用数据类型编程带来了很大的方便.那么C语言能否实现与此类似的 功能呢?当然可以,看一看C的某些库函数接口就知道了,比如快速排序函数便是通用数据 类型编程的一个例子:
void qsort(void *base, sizet_t nmemb, sizet_t size,
int(*compar)(const void *, const void *));该函数可以排序任意类型的数据,只要提供正确 的比较函数即可.
那么,C语言中如何实现通用数据编程?本文将对这个问题作详细的解析,并最终利用所述 方法,实现一个通用数据类型栈.
数据的共性
所谓通用数据类型编程,即对于不同类型的数据,完成相同的操作时所使用的代码也相 同.比如,对于变量赋值操作,不管实际的变量类型是什么,若均可以通过一条相同的语 句完成赋值操作,则此时的编程是通用数据类型的.此时的代码可能为:void assign(T var2, T var1);其中assign函数完成赋值操作,var1的值赋给var2,暂且用T表示变量var1和var2的类型.
观察assign函数,我们不禁要问:若想将var1的值赋给var2,直接使用语句:var2=var1不 就行了吗,有必要写一个专门的函数吗?要回答这个问题,首先需要知道以下两点:
"="本质上是一个函数,与var2=var1等价的函数形式为:=(var2,var1),即名为"=",且 接受两个参数的函数.
"="只能用于常规类型数据间的赋值操作,而不能用于字符串,结构体等类型的赋值 操作.
因此,为了实现通用数据类型的赋值操作,将该操作写为一个函数是很有必要的.那么如 何实现该函数呢?首先分析一下已有数据类型的赋值操作是如何完成的:
常规数据类型.对于这类数据,使用"="便可完成变量间的赋值操作,但在赋值的过程 中,真正发生了哪些事情呢?实际上,虽然不同数据类型的赋值语句均相同(均为var2=var1),但其背后发生的事情并不相同.例如若var1,var2均为int型,则语句var2=var1的实际效果是:将var1所占用4个字节内存空间中的内容拷贝到var2所占 用4个字节的内存空间中;但若var1,var2均为char型,则语句var2=var1的实际效果 是:将var1所占用1个字节的内存空间中内容拷贝到var2所占用1个字节的内存空间 中.
由以上分析可知,数据赋值是通过 内存单元的复制实现的,数据类型不同,复制的内 存单元数也不同.
字符串.字符串的赋值操作通过 函数strcpy()完成.那么strcpy()是如何完成字符 串的赋值操作呢?strcpy()的一个简化实现为:
char *strcpy(char *dest, const char *src)
{
char *desto=dest;
while(*dest++ = *src++);
return desto;
} 从代码可以看出,字符串的赋值实际上为将源字符串所占用内存空间内容拷贝到目 标字符串所占用内存空间.
通过以上两点分析,可以发现一个共性:对于赋值操作,不管数据类型是什么,均通过拷 贝内存空间内容实现,只不过对于不同的数据类型,拷贝的内存单元数目不同而已.因此,我 们可以根据相同的思路实现我们的赋值函数.那么此时摆在面前的问题是:如何获得变 量所占用内存空间,这又相当于以下两个问题:
如何获取变量所占用内存空间的起始地址;
如何获取变量所占用内存空间的单元数.
在接下来的内容中,我们将对这两个问题逐一解答.
void指针
获取变量的起始地址非常容易,因为C语言提供了专门的操作符:"&",取地址操作符.我 们可以将该地址保存在一个指针中,以方便使用.但问题在于:由于我们要实现通用数据 类型编程,而实际的操作数的类型都是某一特定数据类型,那么我们应该将这个操作数 的地址保存在什么类型的指针变量中呢?让我们先设想一下,这个指针不应该是任何特 定类型的指针,但它却必须几乎总是被赋值为另一类型的指针,因此其应该能与其它类 型指针方便的进行类型转换.实际上,C语言提供了这样的指针:即void指针,该指针的特 性如下:
void指针不与任何数据类型关联,它可以不使用强制类型转换,自动的与其它类型指 针进行转换.
对于void指针不能使用"*"操作符,因为void指针不知道其所指数据所占用的内存单 元数.
void指针的++操作为其地址值增1.
由于不能取消引用一个void指针(第2条),因此它只能用于与其它类型指针转换.实际上 总是利用这一点来完成通用数据类型编程.这一过程可表示为:
专用代码->特定类型指针->void指针->通用代码->void指针->选定类型指针->专用代码.
从中可看出,void指针是通用代码与专用代码间的纽带,对于需要通用处理的代码,其与 其它代码的信息传递必须通过void指针,因此其接口必须设计为void指针类型.
赋值函数的实现
让我们回到之前的通用数据类型赋值函数,现在我们完全可以实现它了!实现的思路为: 将它的两个操作数的指针传递给该函数,并指定数据所占用内存空间的单元数,然后在 函数内部将源操作数内存空间内容拷贝至目标操作数内存空间,即可完成赋值操作.那 么,函数的原型应该为:void assign(void *pvdes, void *pvsrc, sizet size);其中参数的意义为:
pvdes:目标操作数指针
pvsrc:源操作数指针
size:数据字节宽度
函数的一个使用实例为:
int ia=2;
int ib;
assign(&ib,&ia,sizeof(int)) double da=2.0;
double db;
assign(&db,&da,sizeof(double));
该例子使用assign函数完成了int型变量和double型变量的赋值操作,虽然变量类型不 同,但assign的调用方式完全相同,因此该函数具有通用数据类型的性质.
以上赋值操作的本质是内存的复制,对于这一操作,C语言提供了专门的函数:
void *memcpy(void *dest, const void *src, sizet n);可以看出,assign()与memcpy()的接口基本相同,因此,在接下来的内容中,我们将直接 使用memcpy()完成赋值操作.
通用数据类型栈
在以上内容的基础之上,离真正的实现一个通用数据类型栈已经不远了.因为其中最关键 的部分是实现变量的赋值,而该问题已经被我们解决.
栈的实现思路为:在栈的初始化过程中,指定栈中元素的数据宽度,将该值保存在栈中一 个变量中;在使用push向栈中保存元素时,采用以上的赋值函数进行数据传递;pop操作 与push实现机理相同;栈使用单链表保存数据.
补充:软件开发 , C语言 ,