16.1 翻译程序的第一步
在预处理之前,编译器会进行一些翻译处理:
-
处理多字节字符和“三字符序列”(目前已很少使用),映射到源字符集;
-
将两个物理行(physical line)转换为一个逻辑行(logical line):
转换前:
printf("Wonder\
ful");
转换后:printf("Wonderful");
- 用空格替换注释,用1个空格替换所有空白字符(不包括换行符)。
16.2 明示常量:#define
有类对象宏(object-like macro)、类函数宏(function-like macro)。
每行#define
由3部分组成——#define指令、宏和替换体:#define PX printf("x == %d\n", x)
,从宏替换为最终文本的过程称为宏展开(macro expansion)。
双引号中的宏不会被展开:
#define TWO 2
printf("TWO"); //输出TWO
printf("%s", TWO); //输出2
宏可以用来指定const对象的初始值、const对象数组的大小等(而const却无法指定,见下例):
#define DEF_LIMIT 20
const int CONST_LIM = 50;
static int data1[DEF_LIMIT]; //有效
static int data2[CONST_LIM]; //无效
const int VAL1 = 2 * DEF_LIMIT; //有效
const int VAL2 = 2 * CONST_LIM; //无效
16.2.1 记号
宏的替换体可以看作记号(token)型字符串。比如#define SIX 2*2
中有1个记号(2*2
);#define SIX 2 * 3
中有3个记号(2
、*
、3
)。
16.2.2 重定义常量
在ANSI标准中,只有新定义和旧定义完全相同才允许重定义:
#define SIX 2 * 3
#define SIX 2 * 3 //允许
#define SIX 2*3 //不允许,token数量不同
如果需要重定义,使用#undef
指令。
16.3 在#define中使用参数
类函数宏:#define MEAN(X, Y) ((X)+(Y)/2)
直接替换会产生一些运算上的问题,比如:
#define SQUARE(X) X*X
SQUARE(x+2) //预期为(x+2)^2,实际被替换为"x+2*x+2",即3x+2
//解决方法:参数带括号,如#define SQUARE(X) (X)*(X)
100/SQUARE(x) //预期为100/x^2,实际被替换为"100/x*x",即100
//解决方法:替换体带括号,如#define SQUARE(X) ((X)*(X))
//所以在#define MEAN(X, Y) ((X)+(Y)/2)中,参数和替换体都带了括号
SQUARE(++x) //预期为(x+1)^2,被替换为++x*++x,即(x+1)(x+2)
//暂无解决方法,不要在类函数宏中使用递增或递减运算符
16.3.1 用宏参数创建字符串:#
运算符
C字符串的串联特性:
printf("a" "b" "c");
的输出是abc。
预处理运算符#
可以将记号替换为字符串(称为“字符串化”(stringizing)):
#define PRINT_SQUARE(X) printf("The square of " #x "is %d\n", ((x)*(x)))
y=2;
PRINT_SQUARE(y); //宏展开将#x替换为y, 输出"The square of y is 4"
PRINT_SQUARE(2 + 4); //宏展开将#x替换为2 + 4,输出"The square of 2 + 4 is 36"
16.3.2 预处理器粘合剂:##
运算符
将2个记号组合成1个记号,如:#define XNAME(n) x ## n
,则XNAME(4)
将展开为x4
,可以用于变量命名。
16.3.3 变参宏:...和__VA_ARGS__
以三个点"..."的方式表示,使类函数宏的参数数量可变:
#define PRINT(...) printf(__VA_ARGS__)
PRINT("ABC");
PRINT("VAL=%d, str=%s", 2, "Helloworld");
16.4 宏和函数的选择
多次调用时,函数只产生1份副本,节省空间,然而需要多次跳转,耗费时间;宏需要插入多次,耗费空间,然而无需跳转,节省时间。
简单的函数一般用宏定义。调用次数少,宏的效果不明显;调用次数多(如嵌套循环中),用宏有助于提高效率。
16.5 文件包含:#include
在头文件中,只声明不定义!
extern
的作用是使一个文件中的全局变量或函数可以在其他文件中使用,实现共享。
步骤:
- 在file1.c中定义全局变量和函数(可供其他文件使用):
int val = 5;
int fun(){ return 0; }
- 在header.h中声明相关变量和函数:
extern int val = 5;
extern int fun();
-
在file2.c文件中包含.h头文件:
#include "header.h"
-
编译时,只需要编译连接.c文件:
gcc -o file file1.c file2.c
原理:
#include
的原理是将头文件整个地复制到#include
的位置,所以可以理解,编译连接时只用到.c文件(不用.h)。所以extern会使程序在其他.c文件中寻找定义。
16.6 其他指令
16.6.1 #undef
取消之前的#define
定义。即使之前没有定义过,#undef
也有效,所以当想使用宏定义却不确定之前是否定义过时,为了安全,仍可使用#undef
。
16.6.3 条件编译
1. #ifdef
、#else
、#endif
#ifdef
、#else
、#endif
,可用于调试程序代码:
#define DEBUG
int main(){
int total = 0;
for (int i=0;i<10;i++){
total += 1;
#ifdef DEBUG
printf("current value:%d", total);
#endif
}
printf("total value:%d", total);
}
不调试时去掉#define DEBUG
,调试时加上即可,从而不用去掉printf()
调试代码。
2. #ifndef
①防止重复包含
如果.c文件同时包含了A.h和B.h,B.h又包含了A.h,则造成重复包含。为了避免重复包含,需要在.h文件开头加上#ifndef HEADER_NAME_H
和#endif
(HEADER_NAME_H可任意更改,只要是唯一的能标识当前.h文件即可,一般用头文件名+_H
)。(补充说明:在.h文件开头添加的原因是,包含.h文件相当于整个地复制到.c文件中)
②便于调试代码
#ifndef
也可以用于方便代码调试,比如header.h中有:
#ifndef LIMIT
#define LIMIT 100
#endif
则在包含了该头文件的.c文件中,可通过如下方式,测试较小的LIMIT
数值:
#define LIMIT 10 //临时测试
#include "header.h"
或者用#undef
:
#include "header.h"
#undef LIMIT //临时测试
#define LIMIT 10 //临时测试
这样就无需在header.h中修改LIMIT
的值。
3. #if
和#elif
#if
或#elif
后接整型常量表达式,示例:
#if SYS == 1
#include "ibm.h"
#elif SYS == 2
#include "vax.h"
#else
#include "general.h"
#endif
16.6.4 预定义宏
预定义宏 | 含义 |
---|---|
DATE | 当前日期,格式为 "MMM DD YYYY" |
TIME | 当前时间,格式为 "HH:MM:SS" |
FILE | 当前源文件的文件名 |
LINE | 当前代码行号 |
STDC | 如果编译器遵循 ANSI 标准,则为 1 |
__cplusplus | 如果编译器用于 C++,则定义为一个数字 |
__func__
是预定义标识符,展开为代表函数名的字符串。
16.6.5 #line
和#error
#line
:重置行号和文件名。
#line 100
#line 10 "cool.c"
#error
:发出一条错误消息。
#if __STDC_VERSION__ != 201112L
#error Not C11
#endif
16.6.6 #pragma
(编译器设置)
16.6.7 泛型选择(C11)
泛型编程(generic programming)的代码没有特定类型,可以转换为指定类型。C11引入了泛型选择表达式,可以根据表达式的类型选择1个值。示例:
_Generic(x, int: 0, float: 1, double: 2, default: 3)
结合#define
使用示例:
#define MYTYPE(X) _Generic((X), int: "int", float: "float", double: "double", default: "others")
当X为int类型,宏展开为字符串"int";X为float类型,宏展开为字符串"float";等等。
16.7 内联函数
编译器可能会将函数调用直接替换为内联代码。内联函数应当比较短小。如果多个文件需要用到同一内联函数,可以在头文件中定义内联函数(一般头文件只声明、不定义,内联函数是特例)。
16.8 _Noreturn
16.9 C库
16.10 数学库
16.11 通用工具库
16.11.1 exit()
和atexit()
16.11.2 qsort()
(快速排序)
16.12 断言库
16.13 string.h库中的memcpy()
和memmove()
函数原型:
void* memcpy(void* restrict s1, const void* restrict s2, size_t n);
void* memmove(void* s1, const void* s2, size_t n);
从s2指向的位置拷贝到s1指向的位置。memcpy()
不允许s1和s2内存区域有重叠;memmove()
则没有这样的要求。
16.14 可变参数:stdarg.h
可变参数的函数,参数列表以"..."结尾,"..."之前的1个参数是可变参数的数量(如下面例子中的num)。
#include <stdarg.h>
int sum(int num, ...){
va_list ap; //初始化va_list对象ap
va_list ap_copy;
va_start(ap, num); //传入va_list对象ap和可变参数的数量num,使ap初始化
va_copy(ap_copy, ap); //将ap拷贝给ap_copy
int sum=0;
for (int i=0;i<num;i++)
sum+=va_arg(ap, double); //传入va_list对象ap和当前参数的类型
//va_arg的第n次调用返回第n个可变参数
//由于不支持退回之前的参数,所以需要ap_copy进行备份
va_end(ap); //清理工作
va_end(ap_copy);
return sum;
}
标签:include,函数,16,int,ap,Plus,LIMIT,Primer,define
From: https://www.cnblogs.com/EndPoem-ZH/p/18076993