《C和指针》笔记

因为学习C语言也有两年了吧,所以这次看《C和指针》还是蛮快的,里面提到的大部分知识都已经见过了。但是某些内容还是属于比较经典的,我整理一下。想要详细了解的话,还是看书吧。这本书讲的很详细的。如果你刚刚学C或者学C不久的,都可以看一下。
  

第二章 基本概念

 1.编译过程:

    预编译(将头文件包含进来和替换#define宏),

    编译(把源文件(.c文

件)编译为目标文件(.o文件),

    链接(把目标文件链接起来,并链接程序中调用的

库函数,形成可执行文件) 

第七章 函数 

 1.可变参数列表函数声明及调用 

    很多函数都具有可变参数,比如经常使用的printf的声明为:

int printf(const char *format, …);
具有可变参。那么怎么实现可变参的函数呢?
可变参数列表是通过宏实现的,这些宏定义于stdarg.h头文件中,是标准库的一部分。

    这个头文件声明了一个类型va_list和三个宏va_start,va_arg,va_end。下面我直接实现一个可变参数列表的函数来说明用法。

代码如下:

     #include <stdio.h>

#include <stdarg.h>
/这个函数是求输入的n个参数的平均值/

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float average(int n_values, int a, ...) /*可变参数列表函数至少有一个有名参数,即这里的n_values*/&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;va_list var_arg; /*va_list是stdarg.h头文件定义的一种类型,在访问可变参数列表之前要定义这种类型的变量,即var_arg*/&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float sum = 0;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int temp;&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int count = 2;//统计一共输入了多少个参数,因为有名参数有两个,所以输入的参数至少是两个

        va_start(var_arg, a);//在真正访问参数之前,要执行该宏,该宏的第一个参数是va_list类型变量,第二个参数是省略号前最后一个有名参数 

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum += n_values + a;//先把有名参数相加&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*约定可变参数的最后一个参数必须是0,以告知程序参数完成,0并不计入计算范围.

访问可变参数要使用宏va_arg,该宏第一个参数是va_list类型变量,第二个参数是要访问的参数的类型,因为这个函数比较简单,所有的类型均为整型*/ 

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(temp = va_arg(var_arg, int))

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum += temp;

count++;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;va_end(var_arg);//访问完可变参数之后必须执行该宏,参数是va_list类型变量。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return sum / count; //返回平均数&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;int main()&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("%3fn", average(3, 6, 7, 0)); //调用该函数,一共输入了4个参数,最后一个参数0表示参数列表的结束,并且0不参加计算。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;}

第九章 字符串,字符和字节&nbsp;

1.内存操作

当要拷贝字符串到另外的地方时,大家很容易想到strcpy,但是这个函数有缺陷,那就是遇到’’就停止工作了。如果你的字符串中恰好要包含’’呢?我们可以用以下几个函数来实现拷贝,这些函数可以拷贝任何的字符。 

&nbsp;&nbsp;&nbsp;&nbsp;void *memcpy(void *dest, const void *src, size_t n);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;void *memmove(void *dest, const void *src, size_t n);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;int memcmp(const void *s1, const void *s2, size_t n);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;void *memchr(const void *s, int c, size_t n);

&nbsp;&nbsp;&nbsp;&nbsp;void *memset(void *s, int c, size_t n);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;这些函数的具体用法我就不再过多描述,大家可以google或者man一下。&nbsp;

第十三章 高级指针话题&nbsp;

1.高级声明&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;int f()[]; //表示声明了一个返回值为数组的函数,这种声明方式是非法的,因为函数返回值不能为数组,只能为标量值。理解这种声明的步骤是:1.(),[]的优先级相同,但结合性是自左向右,所以f与()结合表明是函数,而返回值为数组。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;int f[](); //表示f为数组,其元素为返回值为整型的函数。但是这种声明方式也是非法的,因为数组的元素必须长度相同,但是不保证每个函数的长度都相同。理解步骤与上一个相同。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;int (*f[])(); //表示f为数组,其元素是返回值为整型的函数指针。这种声明方式是合法的。理解步骤同上。

&nbsp; &nbsp; int (* (*f) ()) [10]; //表示f是一个函数指针,它指向的函数的返回值是一个指针,这个指针指向一个具有10个元素的数组。理解步骤同上。

第十六章 标准函数库&nbsp;

1.随机数&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;1.int rand(void);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;2.void srand(unsigned int seed);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;rand函数返回一个0-RAND_MAX(通常为32767)之间的数字,如果多次重复调用这个函数的话,出现的值很可能相同,所以这是个伪随机数函数。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;srand通过使用一个种子seed来计算随机数,种子相同,得到的随机数就相同。所以,通常的做法是使用当前的时间来作为种子,因为任何时刻的时间是不相同的。调用方式:<span style="color:#E53333;">srand((unsigned int)time(0))</span>;&nbsp;

&nbsp;&nbsp;2.非本地跳转&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;通常,如果你写的函数有多层嵌套的话,如果最底层的函数发生了错误,要么直接exit,退出整个程序,要么一层层的返回一个错误码,然后在顶层函数根据错误码进行相应的处理,这样代码看起来很繁杂,所以这里提供一个非本地跳转的函数,与goto的作用类似。

函数原型如下: 

&nbsp;&nbsp;&nbsp;&nbsp;#include &lt;setjmp.h&gt;&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;int setjmp(jmp_buf env);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;void longjmp(jmp_buf env, int val);&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;setjmp函数把程序信息(如堆栈指针的当前位置及程序计数器)保存到跳转缓冲区中,返回0,调用setjmp的函数即为顶层函数。在其他地方调用longjmp的时候,执行流就会从setjmp中恢复保存的信息,并返回longjmp中的val参数,这个参数必须是非零值。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;怎么分辨setjmp是否调用,或者怎么分辨到底是哪个地方的longjmp使得保存信息恢复呢?就是longjmp中的参数val。前提是多个longjmp的val参数的值是不相等的。&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;需要特别注意一点,如果顶层函数(调用setjmp的函数)返回了的话,那么setjmp函数所保存的程序信息就无效了(因为setjmp函数保存的程序信息存在于顶层函数的栈中,当顶层函数返回后,它所占用的栈被系统自动释放,所以程序信息就失效了),这时候如果调用longjmp就可能失效。所以longjmp必须在顶层函数或者顶层函数的直接或间接调用函数中被调用。

3.volatile数据

&nbsp; &nbsp; 防止编译器对程序进行不符合编程者所想的含义的优化。举个例子,有如下的C语言片段:

&nbsp; &nbsp; if (c)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; printf ("Truen");

&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; printf ("Falsen");

&nbsp; &nbsp; if(c)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; printf ("Truen");

&nbsp; &nbsp; else

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; printf ("Falsen");

&nbsp; &nbsp; 通常情况下,第二个代码块与第一个代码块有相同的结果。但是,在第一个代码快执行完,第二个代码块还未执行时,如果信号发生了,在信号处理函数中程序改变了c的值,让其从1变为0或从0变为1,那么第二个代码块明显不会与第一个代码块的结果相同。如果c被生命为volatile,否则,编译器会将这两个代码快优化为:

&nbsp; &nbsp; if (c) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; printf ("Truen");

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>printf ("Truen");</span>

&nbsp;&nbsp;&nbsp;&nbsp;} else {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>printf ("Falsen");</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>printf ("Falsen");</span><span></span>

&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp; &nbsp; 经过优化后,第二种优化后的代码在一般情况下会与未优化的代码有相同的结果,但是如果未优化的代码如上所述被改变了原值,那么优化后的代码就会改变编程者的本意。

&nbsp; &nbsp; 防止优化,就把变量c声明为volatile,如:

&nbsp; &nbsp; volatile char c = 1;

4.void atexit (void (*func) ());

&nbsp; &nbsp; 该函数用于把func指向的函数注册为退出函数,在程序退出前,会按照注册时相反的顺序(逆序)调用退出函数。推出函数不能接受任何参数。

5.void assert (int expression);

&nbsp; &nbsp; 断言,主要用于调试。当expression为真时,程序继续执行,否则,就会退出,并打印相关的信息,包括出错的文件名、行号等。当调试完成后,可以使用宏#define NDEBUG 使预处理器忽略所有断言。

6.void qsort (void *base, size_t n_elements, size_t el_size, void (*compare) (void const *, void const *));&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;对数组进行升序排序,compare是用户自定义比较函数,返回小于、等于、大于0,对应于第一个数小于、等于、大于第二个数。

7.void *bsearch (void const *key, void *base, size_t n_elements, size_t el_size, void (*compare) (void const *, void const *));

&nbsp;&nbsp;&nbsp;&nbsp;在排好序的数组中使用二分查找某个元素 key。