C语言深度解剖读书笔记(6.函数的核心)

转自 http://blog.csdn.net/mbh_1991/article/details/10549725

于本节的函数内容其实就没什么难点了,但是对于函数这节又涉及到了顺序点的问题,我觉得可以还是忽略吧。

本节知识点:

1.函数中的顺序点:f(k,k++);  这样的问题大多跟编译器有关,不要去刻意追求。  这里给下顺序点的定义:顺序点是执行过程中修改变量值的最后时刻。在程序到达顺序点的时候,之前所做的一切操作都必须反应到后续的访问中。

2.函数参数:函数的参数是存储在这个函数的栈上面的(对于栈可以看上篇文章<内存管理的艺术>),是实参的拷贝。

3.函数的可变参数:

      a.对于可变参数要包含starg.h头文件。需要va_list变量,va_start函数,va_arg函数,va_end函数。对于其他函数没什么可说的,只有va_arg函数记得一定是按顺序的接收。这里有一个可变参数使用的小例子,代码如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdarg.h>  
  3.   
  4. float average(char c,int n, ...)  
  5. {  
  6.     va_list args;  
  7.     int i = 0;  
  8.     float sum = 0;  
  9.       
  10.     va_start(args, n);  
  11.       
  12.     for(i=0; i<n; i++)  
  13.     {  
  14.         sum += va_arg(args, int);  
  15.     }  
  16.       
  17.     va_end(args);  
  18.     printf("%c\n",c);  
  19.       
  20.     return sum / n;  
  21. }  
  22.   
  23. int main()  
  24. {  
  25.     char c = 'b';  
  26.     printf("%f\n", average(c,5, 1, 2, 3, 4, 5));  
  27.     printf("%f\n", average(c,4, 1, 2, 3, 4));  
  28.       
  29.     return 0;  
  30. }  

        b.可变参数的缺点:

                 (1).必须要从头到尾按照顺序逐个访问。

                 (2).参数列表中至少要存在一个确定的命名参数。

                 (3).可变参数宏无法判断实际存在的参数的数量。

                 (4).可变参数宏无法判断参数的实际类型。

                 (5).如果函数中想调用除了可变参数以外的参数,一定要放在可变参数前面。

注意:va_arg中如果指定了错误的类型,那么结果是不可预期的。

Ps:可变参数就说到这里,可变参数最经典的应用就是printf,等分析printf实现的时候,再好好写写。
4.函数与宏的比较:

注意:宏有一个函数不可取替的功能,宏的参数可以是类型名,这个是函数做不到的!代码如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <malloc.h>  
  3.   
  4. #define MALLOC(type, n) (type*)malloc(n * sizeof(type))  
  5.   
  6. int main()  
  7. {  
  8.     int* p = MALLOC(int, 5);  
  9.       
  10.     int i = 0;  
  11.       
  12.     for(i=0; i<5; i++)  
  13.     {  
  14.         p[i] = i + 1;  
  15.           
  16.         printf("%d\n", p[i]);  
  17.     }  
  18.       
  19.     free(p);  
  20.       
  21.     return 0;  
  22. }  

5.函数调用中的活动记录问题:包含参数入栈、调用约定等问题。见上篇文章<内存管理的艺术>

6.递归函数:递归函数有两个组成部分,一是递归点(以不同参数调用自身),另一个是出口(不再递归的终止条件)。

      对于递归函数要有一下几点注意:

       a.一定要有一个清晰的出口,不然递归就无限了。

       b.尽量不要进行太多层次的递归,因为递归是在不断调用函数,要不断的使用栈空间的,很容易造成栈空间溢出的,然后程序就会崩溃的。比如说:对一个已经排好序的结构进行快速排序(因为快排需要使用递归,且对排好顺序的结构排序是最坏情况,递归层数最多),就很容易造成栈空间溢出。一般不同的编译器分配的栈空间大小是不一样的,所以允许递归的层数也是不一样的!

        c.利用递归函数,实现不利用参数的strlen函数。代码如下:

[cpp]  view plain copy
  1. /*这是自己实现  strlen*/  
  2. /*  
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. #include <assert.h> 
  6.  
  7. int my_strlen(const char *str) 
  8. { 
  9.     int num=0; 
  10.     assert(NULL!=str); 
  11.     while(*str++) 
  12.     { 
  13.         num++; 
  14.     } 
  15.     return num; 
  16. } 
  17. int main(int argc, char *argv[]) 
  18. { 
  19.     char *a="hello world"; 
  20.     printf("%d\n",my_strlen(a)); 
  21.     return 0; 
  22. }*/  
  23.   
  24. /*这是不用变量 实现strlen  使用递归*/  
  25. #include <stdio.h>  
  26. #include <stdlib.h>  
  27. #include <assert.h>  
  28.   
  29. int my_strlen(const char *str)  
  30. {  
  31.     assert(NULL!=str);  
  32.     return ('\0'!=*str)?(1+my_strlen(str+1)):0; //这里之所以 是加1 不是++ 我是担心顺序点的问题   
  33. }  
  34.   
  35. int main(int argc, char *argv[])  
  36. {  
  37.     char *a="hello world";  
  38.     printf("%d\n",my_strlen(a));  
  39.     return 0;  
  40. }  

7.使用函数时应该注意的好习惯:

   a.如果函数参数是指针,且仅作为输入参数用的时候,应该加上const防止指针在函数体内被以外改变,如:

[cpp]  view plain copy
  1. void str_copy(char *strDestination,const char *strSource);  

   b.在函数的入口处,应尽可能使用assert宏对指针进行有效性检查,函数参数的有效性检查是十分必要的。不用assert也行,if(NULL == p)也可以。

   c.函数不能返回指向栈内存的指针

   d.函数不仅仅要对输入的参数,进行有效性的检查 。还要对通过其他途径进入函数体的数据进行有效性的检查 ,如全局变量,文件句柄等。

   e.不要在函数中使用全局变量,尽量让函数从意义上是一个独立的模块

   f.尽量避免编写带有记忆性的函数。函数的规模要小,控制在80行。函数的参数不要太多,控制在4个以内,过多就使用结构体。

   g.函数名与返回值类型在语言上不可以冲突,这里有一个经典的例子getchar,getchar的返回值是int型,会隐藏这么一个问题:

[cpp]  view plain copy
  1.  char c;  
  2.  c=getchar();  
  3.  if(XXX==c)  
  4.  {  
  5. /*code*/  
  6.  }  

      如果XXX的值不在char的范围之内, 那c中存储的就是XXX的低8位 ,if就永远不会成立。但是getchar当然不会惹这个祸了,因为getchar获得的值是从键盘中的输入的,是满足ASCII码的范围的,ASCII码是从0~127的,是在char的范围里面的,就算是用char去接getchar的值也不会有问题,getchar还是相对安全的。可是对于fgetc和fgetchar就没这么幸运了,他们的返回值类型同样是int,如果你还用char去接收,那文件中的一些大于127的字符,就会造成越界了,然后导致你从文件中接收的数据错误。这里面就有隐藏的危险了!!!对于字符越界问题可以看看这篇文章<c语言深度解剖读书笔记(1.关键字的秘密)>
8.陈正冲老师还有一个第七章是讲文件的我觉得总结不多,就写在这里了:

   a.每个头文件和源文件的头部 ,都应该包含文件的说明和修改记录 。

   b.需要对外公开的常量放在头文件中 ,不需要对外公开的常量放在定义文件的头部。

9.最终的胜利,进军c++(唐老师的最后一课,讲了些c++的知识,总结如下):

    a.类与对象:

    b.c++中类有三种访问权限:

           (1).public  类外部可以自由访问

           (2).protected   类自身和子类中可以访问

           (3).private     类自身中可以访问

小例子:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. struct Student  
  4. {  
  5. protected:  
  6.     const char* name;  
  7.     int number;  
  8. public:  
  9.     void set(const char* n, int i)  
  10.     {  
  11.         name = n;  
  12.         number = i;  
  13.     }  
  14.       
  15.     void info()  
  16.     {  
  17.         printf("Name = %s, Number = %d\n", name, number);  
  18.     }  
  19. };  
  20.   
  21. int main()  
  22. {  
  23.     Student s;  
  24.       
  25.     s.set("Delphi", 100);  
  26.     s.info();  
  27.       
  28.     return 0;  
  29. }  

注意:上面这段代码要在c++的编译器中进行编译,在gcc中会报错的,因为c标准中是不允许struct中有函数的。
        c.继承的使用,如图:

小例子:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. struct Student  
  4. {  
  5. protected:  
  6.     const char* name;  
  7.     int number;  
  8. public:  
  9.     void set(const char* n, int i)  
  10.     {  
  11.         name = n;  
  12.         number = i;  
  13.     }  
  14.       
  15.     void info()  
  16.     {  
  17.         printf("Name = %s, Number = %d\n", name, number);  
  18.     }  
  19. };  
  20.   
  21. class Master : public Student  
  22. {  
  23. protected:  
  24.     const char* domain;  
  25. public:  
  26.     void setDomain(const char* d)  
  27.     {  
  28.         domain = d;  
  29.     }  
  30.       
  31.     const char* getDomain()  
  32.     {  
  33.         return domain;  
  34.     }  
  35. };  
  36.   
  37. int main()  
  38. {  
  39.     Master s;  
  40.       
  41.     s.set("Delphi", 100);  
  42.     s.setDomain("Software");  
  43.     s.info();  
  44.       
  45.     printf("Domain = %s\n", s.getDomain());  
  46.       
  47.     return 0;  
  48. }  

Ps:以上6篇文章终于更新完了,是我对陈正冲老师的<c语言深度解剖>一书和国嵌唐老师c语言课程的一些总结和理解,针对c语言,后面的一点c++仅仅是做个笔记而已,望大牛莫喷~~~


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值