预处理
#和##是两个预处理运算符(注意不是C语言表达式的运算符),#后接形参(中间可以空格),用于创建字符串字面值;##用于连接两个形参。
函数式宏定义也可以带可变参数,同样是用…表示可变参数,宏定义中的可变参数的部分用_VA_ARGS_表示。如:
#define showlist(…) printf(#_VA_ARGS_)
取消宏定义用#undef ,取消未定义的宏不算错误。
对条件预处理的编译(#if … #elseif… #else… #endif),可选的方法有:1、手动编辑代码,添加定义目标宏;2、在所有需要配置的源文件开头包含一个头文件,在该头文件中定义目标宏;3、通过gcc -D选项在编译时定义目标宏 43。
C标准中规定了几个特殊的宏,不需定义即可使用,最常用的是_FILE_ (展开成当前源文件名)和_LINE_(当前代码行的行号)。
C99标准引入特殊标识符_func_可表示当前函数名,这是一个变量名(字符串变量)而不是宏定义。
第21章 Makefile基础
make程序自动读取当前目录下的Makefile文件(依次查找名为GNUmakefile、makefile和Makefile,一般建议用Makefile做文件名)。
make执行的命令前加了@,则不显示命令本身而只显示输出结果,前加- ,则即使该命令出错也继续执行后面的命令。
Makefile中约定俗成的目标名字有:all(执行编译,通常为缺省)、install(执行编译后的安装工作,把可执行文件、配置文件、文档等分别复制到不同的安装目录)、clean(删除编译生成的二进制文件)、distclean(不仅删除二进制文件,也删除其他的生成文件)。
make中, := 赋值时遇到变量立即展开; ?= 赋值时如果没定义过,就进行定义并赋值(不立即展开),如果定义过了就什么也不做;还可使用+=。
gcc 的 -M选项自动分析目标文件和源文件的依赖关系。
第22章 指针
gcc把字符串字面值分配在.rodata段,所以字符串字面值做右值时最好理解成const char* 类型。
在gdb中,可以在run或start命令后面加入命令行参数(从arg[1]开始),也可以用set args命令设置命令行参数之后用run或start运行程序。
两层指针如果是传出的,可能有两种情况:1、传出的指针指向静态内存,或指向已分配的动态内容;2、在函数中动态分配内存,然后传出的指针指向这块内存。
第23章 函数接口
回调函数
如果参数是函数指针,调用者可以传递一个函数的地址给回调函数的实现者,也就是说,调用者提供一个函数但自己不去调用它,而是让通过使用回调函数,让回调函数的实现者去调用它。
回调函数可以实现类似于C++的虚函数或者泛型算法的功能。
处理可变参数要用到C标准库的va_list类型和va_start、va_arg、va_end宏,它们都定义在stdarg.h头文件中。从下面的例子可大致看出具体的用法:
#include <stdio.h> #include <stdarg.h> void printlist(int begin,…) { va_list ap; char *p; va_start(ap,begin); p=va_arg(ap,char *); while(p!=NULL) { fputs(p,stdout); putchar(‘\n‘); p=va_arg(ap, char*); } va_end(ap); } int main(void) { printlist(0,"hello","world","foo","bar",NULL); return 0; }
第24章 C标准库
Linux平台提供的C标准库包括:1、一组头文件,定义了很多类型和宏,声明了很多库函数和全局变量;2、一组库文件,提供了库函数和局变量的定义。
分割字符串:
char *strtok(char *str, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr);
strtok会不断修改str的值,同时在函数中使用一个静态变量记住处理到字符串的位置,这使得strtok不可重入。而strtok_r不使用静态变量,而是要求调用者将处理位置传给它作为第三个参数。strtok_r中的r表示Reentrant(可重入的)。Man Page中有一个使用strtok_r的很好的例子。