因为学习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个参数的平均值/
float average(int n_values, int a, ...) /*可变参数列表函数至少有一个有名参数,即这里的n_values*/
{
va_list var_arg; /*va_list是stdarg.h头文件定义的一种类型,在访问可变参数列表之前要定义这种类型的变量,即var_arg*/
float sum = 0;
int temp;
int count = 2;//统计一共输入了多少个参数,因为有名参数有两个,所以输入的参数至少是两个
va_start(var_arg, a);//在真正访问参数之前,要执行该宏,该宏的第一个参数是va_list类型变量,第二个参数是省略号前最后一个有名参数
sum += n_values + a;//先把有名参数相加
/*约定可变参数的最后一个参数必须是0,以告知程序参数完成,0并不计入计算范围.
访问可变参数要使用宏va_arg,该宏第一个参数是va_list类型变量,第二个参数是要访问的参数的类型,因为这个函数比较简单,所有的类型均为整型*/
while(temp = va_arg(var_arg, int))
{
sum += temp;
count++;
}
va_end(var_arg);//访问完可变参数之后必须执行该宏,参数是va_list类型变量。
return sum / count; //返回平均数
}
int main()
{
printf("%3fn", average(3, 6, 7, 0)); //调用该函数,一共输入了4个参数,最后一个参数0表示参数列表的结束,并且0不参加计算。
return 0;
}
第九章 字符串,字符和字节
1.内存操作
当要拷贝字符串到另外的地方时,大家很容易想到strcpy,但是这个函数有缺陷,那就是遇到’ ’就停止工作了。如果你的字符串中恰好要包含’ ’呢?我们可以用以下几个函数来实现拷贝,这些函数可以拷贝任何的字符。
void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
void *memchr(const void *s, int c, size_t n);
void *memset(void *s, int c, size_t n);
这些函数的具体用法我就不再过多描述,大家可以google或者man一下。
第十三章 高级指针话题
1.高级声明
int f()[]; //表示声明了一个返回值为数组的函数,这种声明方式是非法的,因为函数返回值不能为数组,只能为标量值。理解这种声明的步骤是:1.(),[]的优先级相同,但结合性是自左向右,所以f与()结合表明是函数,而返回值为数组。
int f[](); //表示f为数组,其元素为返回值为整型的函数。但是这种声明方式也是非法的,因为数组的元素必须长度相同,但是不保证每个函数的长度都相同。理解步骤与上一个相同。
int (*f[])(); //表示f为数组,其元素是返回值为整型的函数指针。这种声明方式是合法的。理解步骤同上。
int (* (*f) ()) [10]; //表示f是一个函数指针,它指向的函数的返回值是一个指针,这个指针指向一个具有10个元素的数组。理解步骤同上。
第十六章 标准函数库
1.随机数
1.int rand(void);
2.void srand(unsigned int seed);
rand函数返回一个0-RAND_MAX(通常为32767)之间的数字,如果多次重复调用这个函数的话,出现的值很可能相同,所以这是个伪随机数函数。
srand通过使用一个种子seed来计算随机数,种子相同,得到的随机数就相同。所以,通常的做法是使用当前的时间来作为种子,因为任何时刻的时间是不相同的。调用方式:<span style="color:#E53333;">srand((unsigned int)time(0))</span>;
2.非本地跳转
通常,如果你写的函数有多层嵌套的话,如果最底层的函数发生了错误,要么直接exit,退出整个程序,要么一层层的返回一个错误码,然后在顶层函数根据错误码进行相应的处理,这样代码看起来很繁杂,所以这里提供一个非本地跳转的函数,与goto的作用类似。
函数原型如下:
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp函数把程序信息(如堆栈指针的当前位置及程序计数器)保存到跳转缓冲区中,返回0,调用setjmp的函数即为顶层函数。在其他地方调用longjmp的时候,执行流就会从setjmp中恢复保存的信息,并返回longjmp中的val参数,这个参数必须是非零值。
怎么分辨setjmp是否调用,或者怎么分辨到底是哪个地方的longjmp使得保存信息恢复呢?就是longjmp中的参数val。前提是多个longjmp的val参数的值是不相等的。
需要特别注意一点,如果顶层函数(调用setjmp的函数)返回了的话,那么setjmp函数所保存的程序信息就无效了(因为setjmp函数保存的程序信息存在于顶层函数的栈中,当顶层函数返回后,它所占用的栈被系统自动释放,所以程序信息就失效了),这时候如果调用longjmp就可能失效。所以longjmp必须在顶层函数或者顶层函数的直接或间接调用函数中被调用。
3.volatile数据
防止编译器对程序进行不符合编程者所想的含义的优化。举个例子,有如下的C语言片段:
if (c)
printf ("Truen");
else
printf ("Falsen");
if(c)
printf ("Truen");
else
printf ("Falsen");
通常情况下,第二个代码块与第一个代码块有相同的结果。但是,在第一个代码快执行完,第二个代码块还未执行时,如果信号发生了,在信号处理函数中程序改变了c的值,让其从1变为0或从0变为1,那么第二个代码块明显不会与第一个代码块的结果相同。如果c被生命为volatile,否则,编译器会将这两个代码快优化为:
if (c) {
printf ("Truen");
<span>printf ("Truen");</span>
} else {
<span>printf ("Falsen");</span>
<span>printf ("Falsen");</span><span></span>
}
经过优化后,第二种优化后的代码在一般情况下会与未优化的代码有相同的结果,但是如果未优化的代码如上所述被改变了原值,那么优化后的代码就会改变编程者的本意。
防止优化,就把变量c声明为volatile,如:
volatile char c = 1;
4.void atexit (void (*func) ());
该函数用于把func指向的函数注册为退出函数,在程序退出前,会按照注册时相反的顺序(逆序)调用退出函数。推出函数不能接受任何参数。
5.void assert (int expression);
断言,主要用于调试。当expression为真时,程序继续执行,否则,就会退出,并打印相关的信息,包括出错的文件名、行号等。当调试完成后,可以使用宏#define NDEBUG 使预处理器忽略所有断言。
6.void qsort (void *base, size_t n_elements, size_t el_size, void (*compare) (void const *, void const *));
对数组进行升序排序,compare是用户自定义比较函数,返回小于、等于、大于0,对应于第一个数小于、等于、大于第二个数。
7.void *bsearch (void const *key, void *base, size_t n_elements, size_t el_size, void (*compare) (void const *, void const *));
在排好序的数组中使用二分查找某个元素 key。