论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > C语言程序设计教程
Tag:新手,函数,指针,数据类型,对象,Turbo,入门,运算符,数组,结构,二级,,tc,游戏,试题,问答,编译,视频教程

C语言教程-跟我学NETBSD内核源码

文章类别:C语言程序设计 | 发表日期:2010-12-17 9:41:49

C语言教程-Linux源码

先来个简单的: 
#define LIBKERN_INLINE 
#include <lib/libkern/libkern.h> 

unsigned int 
min(a, b) 
unsigned int a, b; 
{ 
return (a < b ? a : b); 
} 

这个函数是什么作用,我想不用我多说了吧? 
这是一个非常简单的函数,简单到了极点,但它透露出来的信息不是那么简单。 
#define LIBKERN_INLINE 
这个define的作用,是告诉其它源代码,这是内核级(KERN)的一个库函数(LIB),它是INLINE的。什么叫INLINE函数?查书去。 
为什么要用INLINE函数,而不用宏呢?比如 #define min((a), (b)) ((a)<(b)?(a):(b)),这样是不是更好,速度更快? 
不是的,宏定义有一个致命的缺点:编译器不帮你检查参数类型!如果你传递了一个错误类型的参数给一个宏,它还是会以为是正常的。 
而定义成函数就好多了,编译器会检查的。 
但定义成函数又有个缺点:调用函数,要压栈,保护现场,函数执行完之后又要恢复现场,出栈。花费的时间太多,对于内核级的函数来说,这是不可容忍的。 
所以定义成INLINE函数。至于是如何实现的,这比较复杂,内核有它的办法,而我们,如果想用INLINE函数,现在有了好的选择:标准C99已经定义了INLINE,具体请参考它。 
这个函数告诉我们,如果有一个很简单的功能,请用内联函数(即INLINE FUNCTION)来实现它,而不是用宏,更不要直接写代码。那是不是所有函数都用INLINE来实现更好? 
当然不是。内联函数的定义,是有限制的。比如有多个循环(for,while),比如递归函数,都不可以声明成内联函数。 
如果一个比较大的函数,它的执行时间已经大大超过了调用它的时间,那么定义成内联函数已经失去了它的意义,反而会让程序变大(内联函数就是在每一个调用它的地方直接展开代码,这样调用多少次,程序里就会有多少个这样的代码,体积会变大)。 

所以,内联函数,仅仅是在,一个很小功能的函数实现的时候用。它的作用,是很好地替代宏。min函数如此简单,max函数呢?当然跟它一样简单,只不过return 那里要改一下顺序。 
大家注意到没有,这个min函数只能比较unsigned int参数。如果是long类型的呢? 
那就得实现一个lmin函数,如果是double类型的呢?那就得实现dmin函数。 
不过为什么NETBSD里没有dmin函数呢?其实,内核级的编程中,很少用到double类型。最多会用到long类型。这里C语言就没有C++方便了。C++可以实现运算符重载,或者用模板,方便地用一个函数进行不同类型数据的比较。不过它的内部,仍然是这样一种实现方法。C语言让你直接了解事情的本质。下面让我们看一个inline函数的例子,很简单的: 

#include <stdio.h> 

inline void fun_a(int a) 
{ 
 if(a==1)printf("1\n"); 
 else printf("not 1\n"); 
} 

int main() 
{ 
 fun_a(10000); 
} 
在mingw+gcc3.2.3下编译通过。 
这就是inline函数。你会了吗?再来个复杂点的: 

#include <sys/types.h> 
#include <sys/systm.h> 

struct queue {//定义一个队列结构,队列是什么?看书去!有单向的,双向的……而这个程序,定义的是双向的,有next(下一个),有prev(前一个) 
struct queue *q_next, *q_prev; 
}; 

/* 
 * insert an element into a queue 插队啦 
 */ 

void 
_insque(v1, v2) 
void *v1; 
void *v2;//为什么要用void *类型?呵呵,留个作业 
{ 
struct queue *elem = v1, *head = v2; 
struct queue *next; 
//搞清楚赋值的顺序与关系。是谁插在谁的后面? 
next = head->q_next; 
elem->q_next = next; 
head->q_next = elem; 
elem->q_prev = head; 
next->q_prev = elem; 
} 

/* 
 * remove an element from a queue删掉队列中的一个元素。注意,这里没有清空它占用的内存,为什么? 
 */ 

void 
_remque(v) 
void *v; 
{ 
struct queue *elem = v; 
struct queue *next, *prev; 

next = elem->q_next; 
prev = elem->q_prev; 
next->q_prev = prev; 
prev->q_next = next; 
elem->q_prev = 0; 
}为什么要用VOID *类型为参数呢? 
void *是一种很特殊的类型,特殊在它可以接受任何类型的指针,比如int *, long *,甚至其它指针,如struct _some_struct *等。 
这样其它函数在调用这个函数的时候,就不用做类型转换了,可以直接把某指针作为参数传递给这个函数。 
但是void *类型的参数不能直接使用,因为编译器不知道你要用的内存有多大。char *s,  
*s解引用的是1个字节,int *s, *s解引用的是sizeof(int)个字节。而void *s, *s解引用的是多少字节?sizeof(void)???不知道。 
所以在使用void *参数之前,必需进行类型转换。 
本函数中,就是转换成了struct queue *,利用的是隐性类型转换。接下来讲一个简单的函数 
#include <sys/cdefs.h> 
#include <lib/libkern/libkern.h> 

#undef bzero /* in case of LIBSA_USE_MEMSET */ 

void 
bzero(dstv, length) 
void *dstv; 
size_t length; 
{ 
u_char *dst = dstv; 

while (length-- > 0) { 
*dst++ = 0; 
} 
}#include <sys/cdefs.h> 
#include <lib/libkern/libkern.h> 
bzero的作用是把一段内存里的内容全部清零。 
而memset是把一段内存中的内容全部写成某个数。 
它们的作用是如此相似(实际上bzero就是用来代替memset最常用的情形--清零) 
void * 
memset(dstv, c, length) 
void *dstv; 
int c; 
size_t length; 
{ 
u_char *dst = dstv; 

while (length-- > 0) { 
*dst++ = c; 
} 
return dstv; 
}那为什么要设计bzero来代替memset的这种情形呢? 
大家注意到没有,memset的后两个参数,一个是int c,一个是size_t length. 
而在绝大多数的编译器和系统里,int和size_t是一样大小的。 
因此如果你不小心传送错了参数,编译器不会警告你! 
比如你想把int *s 的前20个字节都清零, 
你可能会memset(s, 0, 20), 
但也可能会memset(s,20, 0).如果是这样,就完全错了。可编译器无动于衷。 
而程序员一般不会记忆这种参数顺序的,特别是两种类型很像的情况下,很容易搞混。 
但要改写memset也不容易,毕竟已经用得很多了。 
于是bzero诞生了。 

这个例子告诉我们,设计函数的时候,应该多考虑一些。如果能利用编译器警告的,尽量用。 

当然有些函数你没办法设计不同类型的参数,比如swap(int a, int b),那怎么办呢?C语言设计函数有一个办法,约定俗成法。比如strcat这个函数,是把一个字符串连到另外一个字符串后面, 
当然你也可以这样理解,在一个字符串后面添加另外一个字符串。 

C语言怎么理解呢?它约定后一种理解方法。 
比如 
char * 
strcat(s, append) 
char *s;//第一个参数是目的字符串,即要增加(修改的)的 
const char *append;//第二个只是用来读的,因此声明成const,避免修改 
{ 
char *t = s; 

for (; *t; ++t) 
; 
while ((*t++ = *append++) != '\0') 
; 
return (s); 
} 
再看strcpy 

char * 
strcpy(to, from) 
char *to;//要修改的 
const char *from;//要读的,声明成const 
{ 
char *save = to; 

for (; (*to = *from) != '\0'; ++from, ++to); 
return(save); 
} 

再看sprintf 
sprintf(char *buf/*要修改的*/, const char *fmt/*只读的*/, ...); 
看出来了吗?都是要修改的是第一个参数,读的放在后面。 
这就是所谓的C语言约定俗成的定义函数参数的方法。这样会大大减少大家记忆参数的数量,也就大大减少了出错的可能性。 
但有一些函数因为历史原因,并没有遵循这样的方法。也有些人自己写函数,不遵循这样的方法。
在这里,我建议大家遵循这样的方法,不仅方便别人,更重要的是,方便你自己。跟大家接轨,你才能发展得更好。留一个作业,有哪些函数,遵循了这样的方法?有哪些函数,没有遵循这样的方法? 
提示:字符串函数和文件读写函数中,这两种情况都有。大家好好找找。

进入C语言程序设计视频教程专区

上一篇:{教程}C语言教程-C高效编程四招 人气:3650
下一篇:{教程}C语言教程-Linux源码 人气:3033
视频教程列表
文章教程搜索
 
C语言程序设计推荐教程
C语言程序设计热门教程
看全部视频教程
购买方式/价格
购买视频教程: 咨询客服
tel:15972130058