码迷,mamicode.com
首页 > 编程语言 > 详细

C语言不完全类型与延迟定义

时间:2014-08-11 21:20:22      阅读:327      评论:0      收藏:0      [点我收藏+]

标签:c语言   不完全类型   延迟定义   模块化   

一直以为我的C语言学的还可以,虽说不是出神入化,但是至少比较熟悉吧。但是前一段时间看了一篇微信推文,再百度了一下C语言不完全类型。发现我居然C语言不完全类型和用途甚广的延迟定义都没概念。这两天仔细查阅了相关概念并用代码实验了一下。

本文结构如下:

  •  C语言不完全类型概念介绍
  • 一个故事
  • 延迟定义的优点
  • 思考…

C语言不完全类型

不完全类型也就是不知道变量的所有的类型信息。比如可以声明一个数组,但是不给出该数组的长度;声明一个指针,但是不给出该指针的类型;声明一个结构体类型,但是不给出完整的结构体定义,只说它是一个结构体。但是最终你必须得给出完整的类型信息。要不然编译会报错的。编译器在编译某个单元时,如果遇到一个不完全类型的定义的类型或变量(假设它叫p),它会把这当作正常现象,然后继续编译该单元,如果在本单元内找不到p完整的类型信息,它就去其它编译单元找。如果把整个编译过程分为编译、链接两个过程。在编译阶段遇到不完全类型是正常的,但是在链接过程中,所有的不完全类型必须存在对应的完整类型信息,否则报错。

举个例子,下面的代码先声明了一个不完全类型的变量字符数组str,没有给出它的长度信息。然后再定义了一次str数组,这次给出的长度信息。

char str[];//不完全类型定义

char str[10];//终于遇到了str数组的完整类型信息,编译器松了一口气

注意:不完全类型定义不适合局部变量,如果把上面两行代码放在一个函数体中,会出现符号重定义错误。

不完全类型由于不包含具体的类型信息,所以不能通过sizeof来获得其大小。(编译器君的旁边:我连它的完整类型都不知道,我怎么告诉你它的大小。)下面的代码不能编译通过。它会报错 error: invalid application of `sizeof‘ to an incomplete type 不能对不完全类型使用sizeof。

#include<stdio.h>

 

char str[];

int n =sizeof(str);

char str[10];

 

int main()

{

      printf("%d ",n);

      return 0;

}

如果把int n = sizeof(str)放到charstr[10]后面就没事了。

再举一个结构体的例子。下面的代码先声明了一个不完全类型的结构体s。然后又定义了该结构体。

struct s;

struct s{

      int a;

      int b;

};


一个故事,不完全类型的应用:延迟定义

(哈哈,终于要讲应用了,好激动!)

C语言的不完全类型的一般用作模块化编程中。现在假设你要实现一个保存字符的栈给你的程序员队友用。于是你写出了下面的代码。

//Stack.h

typedef structNode{

      char       data;

      struct Node*       next;

}Node;

 

typedef struct {

      Node*    top;//只需要栈顶指针

      int num;

}Stack;

/*为了快速返回栈中元素的个数,你用一个int变量来记录栈中元素的个数,在入栈和出栈时对num进行增减操作,这样在返回栈中元素的个数的时候,就可以直接返回num变量了。*/

 

/构造一个空栈S

voidInitStack(Stack **s);

//销毁栈S

voidDestroyStack(Stack **s);

//判断栈是否为空

intStackEmpty(Stack *s);

//返回栈的长度

intStackLength(Stack *s);

//用e返回栈顶元素

int GetTop(Stack*s,char *e);

//入栈

void Push(Stack*s,char e);

//弹出栈顶元素,并用e返回其值

int Pop(Stack*s,char *e);

为了不暴露实现细节,你将实现栈操作的各种实现代码封装成到了一个库文件中(静态、动态都行)。然后将这个.h文件和库文件信心满满的提交给了队友。并暗示自己“我果然是个牛x的程序员,小美一定会崇拜我、尊敬我、对我欲罢不能!不行,我要谦虚,当作什么也没发生”。

几个小时候,队友皱着眉头过来了。你心想“不对啊,怎么不是崇拜的眼神”。然后发生了下面的一段对话。

“你封装的什么啊?连个栈中元素的个数都统计不对”,队友很生气

“怎么可能?为了快速返回栈中元素的个数,我特意用了一个int变量来保存栈中元素的个数”你也很生气,明明用一个单独变量来保存元素个数是你的得意之处,居然被人说不对。

“我知道,可就是不对”,队友坚持说你不对。

然后为了找出问题,你和队友一起检查他写的代码。你发现他写了如下代码。

Push(s);

++(s->num);

Pop(s,&e);

--(s->num);

原来队友在压栈和出栈时都对栈s的num进行了加减操作。

你:“为什么你要动我的num?”

队友:“你的num不是用来保存栈中元素的个数的吗?我压栈、出栈当然要对它进行增减啊!”

你:“我在实现代码中已经这么做了。”

原来你以为理所当然的事,队友却和你想的不一样。然后你告诉他不要多事,num的大小由你来维持。你们终于又可以一起愉快的玩耍了。

但是你在队友的代码里还是发现像下面一些不爽的代码,这让你感到膈应。

e =s->top->data;

int len =s->num;

为了获得栈顶元素,他没有用你提供的GetTop函数,而是自作主张的直接通过指针直接访问栈内部的top指针。而且没用你给的StackLength函数,而是直接访问栈结构体的num成员。虽然你可以和他沟通,让他不要自己去访问你的实现细节。但是你不能保证他会完全听你的。能不能从技术上解决这个问题呢?个人觉得,团队协作时,能从技术上解决的事,不必从沟通上解决。这样可以减少团队沟通成本。扯多了,回到刚才的问题——我们能否从技术上解决这个问题?

问题分析和解决方法

回头看看我们刚才写的代码。队友会直接访问我们封装的内部细节,是因为他看到了我们给的.h文件中结构体的详细定义。那么能不能去掉.h文件中Stack结构体的定义呢?显然不行,因为我们给出的几个函数接口都要用到Stack类型的参数,如果用户看不到Stack的定义,那他怎么定义Stack类型的变量然后传递给这些接口函数呢?答案是,我们给出Stack结构体类型的定义,但是不给出Stack结构体的详细信息。也就是前面讲到的不完全类型定义。改进后的代码如下。

//Stack.h

typedef structSqStack Stack;

//…各个函数接口定义

//Stack.c

typedef structNode{

      char       data;

      struct Node*       next;

}Node;

struct SqStack{

      Node*    top;

      int num;

};

在改进后的代码中,我们只是定义了一个不完全类型struct SqStack结构体,并用typedef将其和Stack名的类型等价。而将SqStack结构体的详细定义放到了.c文件中。这样将.h文件和库文件提交给队友后,他再也看不到Stack的详细信息了。这下,他就没有直接访问结构体内部的冲动了。(看你丫还怎么访问?老老实实用哥提供的函数接口吧!)队友看不到Stack结构体的内部是怎么定义的,自然也不知道怎么访问,而且任何通过Stack类型指针变量的访问方式都会让编译器报错。这下,我再也不用担心队友的愚蠢了。

上面讲到的,在头文件中只定义一个结构体的不完全类型,而将结构体的详细定义推迟到在.c文件的方式就叫“延迟定义”。


延迟定义带来的其它好处

在上面的实例过程中,虽然我们是为了隐藏实现细节才使用的延迟定义。但是延迟定义也带来了其它好处。另一个好处就是,我们可以以很小的工作量代价来更改实现。比如我们发现Stack结构的实现有bug需要修正,或者我们想将内部的栈的链式存储改为顺序存储。我都可以直接在.c文件中修改,然后重新编译生成新的库文件,不需要修改.h文件,因为我们的接口函数并不需要变。将库文件提交给用户,用户只需替换原来旧的库文件,而不需要修改代码。如果是动态链接库,用户只需替换库文件就行。如果是静态库,用户只需替换库后重新链接一下。至始至终,用户都不需要修改代码,不需要重新编译自己的客户端代码。

如果没有用延迟定义,将内部细节放在.h文件中,那修改细节后,用户的客户端代码也需要重新编译。



 延迟定义的优点

我们来总结一下延迟定义的优点:

1.       隐藏了内部实现细节,强制用户按接口规则访问。减少沟通成本。

2.       便于修改。

上面两点都是实现模块化编程所必须的。而且个人认为,站在客户的角度,知道的细节越少越好,知道的越多,要记忆和思考的东西也越多。就像电视剧中的经典台词:“有时候知道的太多并不是好事”。角色被人干掉的理由也是“你知道的太多了”,或者“你知道了你不该知道的事情”。我只按照既定的规则来访问别人提供的代码,有什么问题直接问别人,而不是看实现代码。按照既定规则来访问也便于职责划分。如果非要总结延迟定义带来的第3个好处,个人认为是:便于职责划分,清晰职责边界。

 

 

思考…

Q:既然延迟定义便于模块化编程,那C++、Java等面向对象的语言中有用到延迟定义类似的技术吗?

A:当然有,只是面向对象语言中不叫“延迟定义”。而是叫封装、访问权限、接口和多态。类的封装和访问权限可以阻止客户访问类的实现细节,而接口和多态可以隐藏实现细节。当然它们的好处不只这些。

C语言不完全类型与延迟定义,布布扣,bubuko.com

C语言不完全类型与延迟定义

标签:c语言   不完全类型   延迟定义   模块化   

原文地址:http://blog.csdn.net/candcplusplus/article/details/38498707

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!