文章目录
1 #define
#define LIMIT 20
const int SIZE = 50;
static int data1[LIMIT]; // 有效
static int data2[SIZE]; // 无效
const int LIM2 = 2 * LIMIT; // 有效
const int LIM3 = 2 * SIZE; // 无效
#include<stdio.h>
#include SQUARE1(X) X*X // x+2*x+2
#include SQUARE2(X) (X)*(X) // (x+2)*(x+2)
#define PR(X) printf("The result is %d", X);
int main(void) {
int x = 5;
int z;
z = SQUARE1(x); // 使用预定义的数据替换表达式
PR(z);
PR(SQUARE1(x + 2));
PR(SQUARE2(x + 2));
}
2 #运算符
#include<stdio.h>
#define PSQR(x) printf("The square of " #x " is %d\n", ((x)*(x)));
int main(void) {
int x = 5;
PSQR(x); // #x代表按变量名输出:the square of x is 5
}
3 ##运算符
#include<stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void) {
// int x1 = 14
int XNAME(1) = 14;
// printf("x1 = %d", x1);
PRINT_XN(1);
}
4 变参宏…和__VA_ARGS__
...
参数输入的字符串会在 __VA_ARGS__
完整替换,可输入多个参数。...
参数只能代替最后的宏参数。
#define WRONG(X, ..., Y) #X #__VA_ARGS__ #y // 错误
#include<stdio.h>
#include<math.h>
#define PR(X, ...) printf("Message " #X ": "__VA_ARGS__);
int main(void) {
double x = 5;
double y;
y = sqrt(x);
// Message 1: x = 5;
PR(1, "x = %g\n", x);
// Message 2: x = 5.00, y = 10.0000
PR(2, "x = %.2f, y = %.4f\n", x, y);
}
5 #include
#include<stdio.h> // <>告诉预处理器从标准系统目录中查找头文件
#include "hot.h" // ""告诉预处理首先在当前目录或指定目录查找头文件,如果没有再从标准系统目录查找
#include "/usr/biff/p.h"
头文件引入程序编译运行注意事项:
-
两个源代码文件
name_st.c
和useheader.c
都使用了names_st
类型结构,所以必须包含names_st.h
头文件 -
必须编译和链接
names_st.c
和useheader.c
源代码文件 -
声明和指令放在
names_st.h
头文件中,函数定义放在names_st.c
源代码文件中
names_st.h
#include<string.h>
#define SLEN 32
struct names_st {
char first[SLEN];
char last[SLEN];
};
typedef struct names_st names;
// 函数原型声明
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);
name_st.c
#include<stdio.h>
#include "names_st.h"
// 函数定义
void get_names(names * pn) {
printf("Please enter your first name:");
s_gets(pn->first, SLEN);
printf("Please enter your last name:");
s_gets(pn->last, SLEN);
}
void show_names(const names * pn) {
printf("%s %s", pn->first, pn->last);
}
useheader.c // 编译时链接name_st.c
#include<stdio.h>
#incluce "names_st.h"
int main(void) {
names candidate;
get_names(&candidate);
show_names(&candidate);
return 0;
}
6 #undef
指令 #undef
用于取消已定义的#define指令。如果想使用一个名称,又不确定之前是否已经用过,为安全起见,可使用#undef指令取消该名字的定义。
#define LIMIT 400
#undef LIMIT // 取消了#define的定义,可以重新定义LIMIT
7 #ifdef、#else、#endif
条件编译指令,使用这些指令告诉编译器根据编译时的条件执行或忽略信息块。
指令 #ifdef
表示是否已经定义,要与 #endif
配合使用结束指令判断。
#include<stdio.h>
#define JUST_CHECKING
#deinfe LIMIT 4
int main(void) {
int i;
int total = 0;
for (i = 1; i <= LIMIT; i++) {
total += 2 * i*i + 1;
// 如果JUST_CHECKING没有定义,则执行下面语句块
#ifdef JUST_CHECKING
printf("i=%d, running total=%d\n", i, total);
#endif
}
printf("Grand total = %d\n", total);
return 0;
}
8 #ifndef
指令 #ifndef
表示是否未定义,可以和 #else
、#endif
配合使用。#ifndef
通常用于防止多次包含一个文件。
names.h
#ifndef NAMES_H_ // 使用#ifndef可以避免被重复预定义
#define NAMES_H_
#define SLEN 32
struct names_st {
char first[SLEN];
char last[SLEN];
};
typedef struct names_st names;
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);
#endif
doubincl.c
#include<stdio.h>
#includde "names.h"
#include 'names.h" // 不小心包含了两次头文件
9 #if、#elif
// #if判断条件是否成立
#if SYS == 1
#include "libm.h"
#endif
#if SYS == 1
#include "ibmpc.h"
#elif SYS == 2
#include "vax.h"
#elif SYS == 3
#include "mac.h"
#else
#include "general.h"
#endif
10 预定义的宏
以上的宏都是在标准库预先定义好的,可以直接使用。
11 #line、#error
指令 #line
重置 __LINE__
和 __FILE__
宏报告的行号和文件名。
#line 100 // 把当前行号重置为100
#line 10 "cool.c" // 把行号重置为10,把文件名重置为cool.c
指令 #error
让预处理器发出一条错误消息,该消息包含指令中的而文件。如果可能的话,编译过程应该中断。
#if __STDC__VERSION__ != 201112L
#error NOT C11
#endif
编译时会显示#error的提示
12 #pragma
指令 #pragma
可以修改编译器设置支持某些编译指令集。C99还提供了 _Pragma
预处理器运算符,该运算符把字符串转换成普通的编译指示。
#pragma c9x on // 开发C99时标准被称为C9X,让编译器支持C9X
_Pragma("nonstandardtreatmenttypeB on")
等价于下面的指令
#pragma nonstandardtreatmenttypeB on
由于该运算符不适用#符号,所以可以把它作为宏展开的一部分
#define PRAGMA(X) _Pragma(#X)
#define LIMRG(X) PRAGMA(STD CX_LIMITED_RANGE X)
然后使用类似代码:
LIMRG( ON )
定义上没问题,但实际上无法正常运行:
#define LIMRG(X) PRAGMA(STD CX_LIMITED_RANGE #X)
问题在于这行代码依赖字符串的串联功能,而预处理过程完成之后才会串联字符串。
_Pragma运算符完成“解字符串”的工作,即把字符串中的转义序列转换成它所代表的字符,因此
_Pragma("use_bool \"true \"false")
变成了
#pragma use_bool "true "false
13 数学库
数学库的函数使用需要引入头文件 math.h
14 exit()和atexit()
- atexit()
atexit()
函数需要把退出时要调用的函数地址(一般是函数名)传递给 atexit()
作为参数即可。atexit()
会注册传入的函数,当调用 exit()
时就会调用 atexit()
所注册的函数。ANSI保证,最少可以注册32个函数,执行顺序与注册顺序相反。
被注册的函数应该不带任何参数且返回类型为 void
。通常,这些函数会执行一些清理任务,例如根棍监视程序的文件或重置环境变量。
注意,main()执行结束时也会隐式调用exit()。
- exit()
exit()
可以终止程序,可传递对应的int数值表示程序执行情况,比如 exit(0)
表示成功终止。在main()以外的函数调用exit()也会终止整个程序。
15 string.h库中的memcpy()和memov()
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmov(void * s1, const void *s2, size_t n);
这两个函数都从 s2
指向的位置拷贝n字节到 s1
指向的位置,而且都返回 s1
的值。所不同的是,memcpy()
的参数带关键字 restrict
,即memcpy()
假设两个内存区域之间没有重叠;而 memmov()
不做这样的假设。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define SIZE 10
void show_array(const int ar[], int n);
// 如果编译器不支持C11的_Static_assert断言,可以注释掉
_Static_assert(sizeof(double) == 2 * sizeof(int), "double not twice int size");
int main() {
int values[SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int target[SIZE];
double curious[SIZE / 2] = {2.0, 2.0e5, 2.0e10, 2.0e20, 2.0e30};
puts("memcpy() used:");
puts("value (original data):");
show_array(values, SIZE);
// 将values数组拷贝到target数组
memcpy(target, values, SIZE * sizeof(int));
puts("target (copy of values):");
show_array(target, SIZE);
puts("\nUsing memmov() with overlapping ranges:");
memmov(values + 2, values, 5 * sizeof(int));
put("values -- elements 0-4 copied to 2-6:");
show_array(values, SIZE);
puts("\nUsing memcpy() to copy double to int:");
memcpy(target, curious, (SIZE / 2) * sizeof(double));
puts("target --5 double into 10 int positions:");
show_array(target, SIZE / 2);
show_array(target + 5, SIZE / 2);
return 0;
}