函数举例
读取字符串,如果字符串中含有ould
则输出该字符串,否则不输出。
#include <stdio.h>
// 函数声明
int getLine(char s[], int lim);
int strindex(char s[], char t[]);
int main() {
char t[] = "ould"; // 要查找的目标子字符串
char s[100]; // 存储输入行的字符数组,最大长度为 100
// 循环读取输入的每一行,并进行处理
while (getLine(s, 100) > 0) { // 调用 getLine 函数获取一行输入,直到输入结束或达到最大限制
if (strindex(s, t) != -1) // 调用 strindex 函数查找子字符串 "ould"
printf("%s", s); // 如果找到,打印当前行
}
}
// 函数:从输入中获取一行字符,并存储在 s 中,最多存储 lim-1 个字符
int getLine(char s[], int lim) {
int i, c;
i = 0;
while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
s[i++] = c; // 将读取到的字符存储在数组 s 中
if (c == '\n')
s[i++] = c; // 如果读取到换行符,也存储在数组 s 中
s[i] = '\0'; // 添加字符串结束符
return i; // 返回当前行的字符个数(不包括结束符)
}
// 函数:在字符串 s 中查找子字符串 t,如果找到返回第一个匹配的位置,否则返回 -1
int strindex(char s[], char t[]) {
int i, j;
for (i = 0; s[i] != '\0'; i++) { // 遍历字符串 s
for (j = 0; s[i + j] == t[j] && t[j] != '\0'; j++)
; // 检查 s 中从位置 i 开始的子串是否与 t 匹配
if (t[j] == '\0') // 如果 t 的末尾符号已达到
return i; // 返回 t 在 s 中首次出现的位置
}
return -1; // 没有找到 t,则返回 -1
}
练习
- 编写函数
strindex(s,t)
,返回 t 在 s 中最右边出现的位置,如果没有,则返回 -1。
// 函数:返回 t 在 s 中最右边出现的位置,如果不存在则返回 -1
int strindex(char s[], char t[]) {
int i, j, index;
index = -1; // 初始化 index 为 -1,表示未找到 t 在 s 中的位置
// 外循环遍历字符串 s
for (i = 0; s[i] != '\0'; i++) {
// 内循环尝试在 s 的当前位置 i 开始查找与 t 匹配的子串
for (j = 0; s[i + j] == t[j] && t[j] != '\0'; j++)
; // 仅递增 j,直到找到不匹配的字符或者 t 的末尾符号
// 如果 t 的末尾符号已经达到,说明在 s 中找到了与 t 完全匹配的子串
if (t[j] == '\0') {
index = i; // 记录当前位置 i 为找到的最右边位置
}
}
return index; // 返回 t 在 s 中最右边出现的位置,或者 -1 如果未找到
}
返回值
举例
编写函数,将字符串转为double
类型,并返回。
- 第一种解决方案,分开处理整数部分和小数部分。
#include <stdio.h>
#include <ctype.h>
double atof(char s[]);
int main() {
printf("%f\n", atof("-321.123"));
}
double atof(char s[]) {
int i, sign;
double n;
double decimal;
n = 0;
for (i = 0; isspace(s[i]); i++)
;
sign = (s[i++] == '-') ? -1: 1;
do {
n = 10 * n + (s[i++] - '0');
} while (s[i] != '\0' && s[i] != '.');
if (s[i++] == '.') {
decimal = 10;
do {
n += (s[i++] - '0') / decimal;
decimal *= 10;
} while (s[i] != '\0');
}
return sign * n;
}
- 第二种解决方案,统一处理整数部分和小数部分。
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <float.h>
double atof(char s[]);
int main() {
printf("%f\n", atof("-321.123")); // 测试将字符串 "-321.123" 转换为浮点数并打印结果
return 0;
}
double atof(char s[]) {
int i, sign;
double n, power;
// 跳过空白字符
for (i = 0; isspace(s[i]); i++)
;
// 确定符号位
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-') // 跳过符号位
i++;
// 处理整数部分
for (n = 0.0; isdigit(s[i]); i++)
n = 10.0 * n + (s[i] - '0');
// 处理小数部分
if (s[i] == '.')
i++;
for (power = 1.0; isdigit(s[i]); i++) {
n = 10.0 * n + (s[i] - '0');
power *= 10.0;
}
return sign * n / power;
}
- 在
atof
的基础上,编写atoi
将会变得非常简单。
int atoi(char s[]) {
return (int) atof(s);
}
练习
- 修改
atof
,使其可以转换由科学计数法表示的浮点数。(例如1.23e-3、-1.231e2)
#include <stdio.h>
#include <ctype.h>
double atof(char s[]);
int main() {
printf("%f\n", atof("-321.123")); // 测试普通的负浮点数
printf("%f\n", atof("-3.123e2")); // 测试科学计数法:-3.123 * 10^2
printf("%f\n", atof("-3.123e-03")); // 测试科学计数法:-3.123 * 10^-3
return 0;
}
/*
* atof: 将字符串 s 转换为相应的双精度浮点数
* 参数: s - 输入的字符串,可以包含整数、小数和科学计数法表示形式
* 返回值: 对应的双精度浮点数
*/
double atof(char s[]) {
int i, sign, science_sign, e_n;
double n; // 存储尾数部分
double power; // 处理小数点和科学计数法中的幂次
n = 0;
e_n = 0;
power = 1.0; // 初始权重为1
// 跳过空白字符
for (i = 0; isspace(s[i]); i++)
;
// 确定符号位
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
// 处理整数部分
do {
n = 10 * n + (s[i++] - '0');
} while (isdigit(s[i]));
// 处理小数部分
if (s[i++] == '.') {
for (; isdigit(s[i]); power *= 10)
n = 10 * n + (s[i++] - '0');
}
n /= power; // 将尾数部分除以权重,得到小数部分的值
// 处理科学计数法部分
if (s[i++] == 'e' || s[i++] == 'E') {
science_sign = (s[i] == '-') ? -1 : 1; // 确定科学计数法的指数符号
if (s[i] == '+' || s[i] == '-') // 跳过指数部分的符号位
i++;
// 处理指数部分
do {
e_n = 10 * e_n + (s[i++] - '0');
} while (isdigit(s[i]));
// 计算科学计数法中的指数部分的幂次
for (int j = 0; j < e_n; j++)
power *= 10;
}
// 返回最终结果,考虑科学计数法的符号影响
return (science_sign == -1) ? sign * n / power : sign * n * power;
}
外部变量
与内部变量相对立,可以简单的把外部变量理解为定义在函数外的变量,它在程序运行的期间一直存在。
下面是外部变量的应用。使用逆波兰表达式编写一个计算器,实现加减乘除的功能。逆波兰表达式(Reverse Polish Notation,RPN)是一种数学表达式的写法,其中操作符位于其操作数之后,而不是通常的中缀表示法中间。例如,表达式 3 + 4 在逆波兰表达式中表示为 3 4 +。
示例:
- 中缀表达式 3 + 4 * 5 的逆波兰表示为 3 4 5 * +。
- 中缀表达式 (1 + 2) * 3 - 4 的逆波兰表示为 1 2 + 3 * 4 -。
#include <stdio.h>
#include <stdlib.h>
#define MAXOP 100 // 操作数和运算符的最大长度
#define NUMBER '0' // 表示获取到的是一个数字
int getop(char []);
void push(double);
double pop();
int main() {
int type;
double op2;
char s[MAXOP]; // 用于存储获取的操作数或运算符的数组
while ((type = getop(s)) != EOF) { // 循环获取输入,直到遇到文件结尾
switch (type) {
case NUMBER: // 如果是数字
push(atof(s)); // 将字符串转换为浮点数并压入栈中
break;
case '+': // 如果是加号
push(pop() + pop()); // 弹出栈顶两个数相加后再压入栈中
break;
case '-': // 如果是减号
op2 = pop(); // 弹出第二个操作数
push(pop() - op2); // 弹出第一个操作数与第二个操作数相减后再压入栈中
break;
case '*': // 如果是乘号
push(pop() * pop()); // 弹出栈顶两个数相乘后再压入栈中
break;
case '/': // 如果是除号
op2 = pop(); // 弹出除数
if (op2 != 0.0)
push(pop() / op2); // 弹出被除数与除数相除后再压入栈中(避免除数为零的情况)
else
printf("error: zero divisor\n"); // 若除数为零,则报错
break;
case '\n': // 如果是换行符(表示结束一行计算)
printf("\t%.8g\n", pop()); // 输出栈顶的结果(计算的最终结果)
break;
default:
printf("error: unknown command %s\n", s); // 若遇到未知命令,则报错
break;
}
}
}
#define MAXVAL 100 // 栈的最大深度
int sp = 0; // 栈顶指针,指向下一个空闲位置
double val[MAXVAL]; // 栈,用于存储操作数和中间结果
void push(double f) {
if (sp < MAXVAL)
val[sp++] = f; // 将操作数压入栈顶,并将栈顶指针向上移动
else
printf("error: stack full, can't push %g\n", f); // 若栈已满,则报错
}
double pop() {
if (sp > 0)
return val[--sp]; // 弹出栈顶操作数,并将栈顶指针向下移动
else {
printf("error: stack empty\n"); // 若栈为空,则报错
return 0.0; // 返回默认值
}
}
#include <ctype.h>
int getch();
void ungetch(int);
int getop(char s[]) {
int i, c;
while((s[0] = c = getch()) == ' ' || c == '\t')
; // 跳过空白字符
s[1] = '\0';
if (!isdigit(c) && c != '.') // 若不是数字且不是小数点
return c; // 直接返回运算符
i = 0;
if (isdigit(c))
while (isdigit(s[++i] = c = getch()))
; // 收集整数部分
if (c == '.')
while (isdigit(s[++i] = c = getch()))
; // 收集小数部分
s[i] = '\0';
if (c != EOF)
ungetch(c); // 将多读入的字符放回输入缓冲区
return NUMBER; // 返回表示数字的标记
}
#define BUFSIZE 100 // 输入缓冲区的大小
char buf[BUFSIZE]; // 输入缓冲区
int bufp = 0; // 输入缓冲区的下一个空闲位置
int getch() {
return (bufp > 0) ? buf[--bufp] : getchar(); // 获取字符(从缓冲区或标准输入中)
}
void ungetch(int c) {
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n"); // 若缓冲区已满,则报错
else
buf[bufp++] = c; // 将字符放回缓冲区
}
运行
1 2 - 4 5 + *
-9
12 12 +
24
12 12
12
12
error: stach empty
0
程序核心原理:
while (next operator or operand is not end-of-file indicator)
if (number)
push it
else if (operator)
pop operands
do operation
push result
else if (newline)
pop and print top of stack
else
error
练习
- 为上面的程序添加取模运算,并且使其支持负数。
支持负数
int getop(char s[]) {
int i, c;
// 跳过空白字符
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
i = 0;
if (c == '-' || c == '+') { // 处理可能的负号或正号
if (isdigit(c = getch())) {
s[++i] = c; // 如果后面紧跟着数字,则将符号和数字一起存入s数组
} else {
ungetch(c); // 如果后面不是数字,则将字符放回输入缓冲区
return s[0]; // 返回符号字符本身
}
}
if (!isdigit(c) && c != '.') // 如果不是数字且不是小数点,则直接返回该字符
return c;
if (isdigit(c)) // 收集整数部分
while (isdigit(s[++i] = c = getch()))
;
if (c == '.') // 收集小数部分
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0'; // 添加字符串结束符
if (c != EOF)
ungetch(c); // 将多读入的字符放回输入缓冲区
return NUMBER; // 返回表示数字的标记
}
- 支持命令:打印(输出堆栈中的数据)、复制(复制一个堆栈顶部数据)、交换(交换堆栈顶部的两个数据)、清理(清空堆栈数据)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 常量定义
#define MAXOP 100 // 操作数或操作符的最大长度
#define NUMBER '0' // 标识找到一个数
#define COMMAND 'C' // 标识找到一个命令
int getop(char []);
void push(double);
double pop();
int action(char []);
// 主函数,执行逆波兰计算器
int main() {
int type; // 存储当前的操作符或操作数类型
double op2; // 存储第二个操作数
char s[MAXOP]; // 存储输入的操作数或操作符
// 主循环,处理输入
while ((type = getop(s)) != EOF) {
switch (type) {
case COMMAND: // 如果是命令,执行对应操作
if (action(s) != 0)
printf("error: unknow command %s\n", s);
break;
case NUMBER: // 如果是数,将其压入栈中
push(atof(s));
break;
case '+': // 加法操作
push(pop() + pop());
break;
case '-': // 减法操作
op2 = pop();
push(pop() - op2);
break;
case '*': // 乘法操作
push(pop() * pop());
break;
case '/': // 除法操作
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '%': // 取模操作
op2 = pop();
if (op2 != 0)
push((int) pop() % (int) op2);
else
printf("error: zero modulus\n");
break;
case '\n': // 换行,输出栈顶元素的值
printf("\t%.8g\n", pop());
break;
default: // 未知操作符或操作数
printf("error: unknown command %s\n", s);
break;
}
}
}
#define MAXVAL 100 // 栈的最大深度
int sp = 0; // 下一个空闲栈位置
double val[MAXVAL]; // 值栈
// 将值压入栈中
void push(double f) {
if (sp < MAXVAL)
val[sp++] = f;
else
printf("error: stack full, can't push %g\n", f);
}
// 从栈中弹出并返回值
double pop() {
if (sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}
#include <ctype.h>
int getch();
void ungetch(int);
// 获取下一个操作数或操作符
int getop(char s[]) {
int i, c;
// 跳过空白字符
while((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
i = 0;
// 处理命令
if (c >= 'a' && c <= 'z') {
while (!isblank(s[++i] = c = getch()) && c != '\n')
;
s[i] = '\0';
if (c != EOF && c != '\n')
ungetch(c);
return COMMAND;
}
// 处理可能的负号或正号
if (c == '-' || c == '+') {
if (isdigit(c = getch())) {
s[++i] = c;
} else {
ungetch(c);
return s[0];
}
}
// 处理数
if (!isdigit(c) && c != '.')
return c;
if (isdigit(c))
while (isdigit(s[++i] = c = getch()))
;
if (c == '.')
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}
#define BUFSIZE 100
char buf[BUFSIZE]; // 缓冲区,用于ungetch
int bufp = 0; // 缓冲区指针
// 获取下一个输入字符
int getch() {
return (bufp > 0) ? buf[--bufp] : getchar();
}
// 将字符放回输入中
void ungetch(int c) {
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
// 定义命令字符串
#define PRINT "print"
#define DUPLICATE "duplicate"
#define SWAP "swap"
#define CLEAR "clear"
// 执行命令
int action(char s[]) {
int result, i;
double op1, op2;
result = -1;
if ((result = strcmp(s, PRINT)) == 0) {
for (i = 0; i < sp; i++)
printf("\t%.8g", val[i]);
printf("\n");
} else if ((result = strcmp(s, DUPLICATE)) == 0) {
op2 = pop();
push(op2);
push(op2);
} else if ((result = strcmp(s, SWAP)) == 0) {
op2 = pop();
op1 = pop();
push(op2);
push(op1);
} else if ((result = strcmp(s, CLEAR)) == 0) {
sp = 0;
}
return result;
}
范围规则
局部变量的内存分配
-
声明时不分配存储空间:
- 当在函数内部声明局部变量时,编译器只会记录变量的名称、类型和作用域信息,并计算其在栈帧中的偏移量。此时并没有实际的内存分配。
- 例如,在函数中声明一个局部变量
int localVar;
时,编译器知道localVar
是一个int
类型的变量,但没有立即为其分配内存。
-
函数调用时分配存储空间:
- 实际的内存分配发生在函数调用时。当函数被调用时,函数的栈帧在栈上被分配。栈帧包含了函数的所有局部变量的存储空间。
- 当函数执行完毕并返回时,栈帧被销毁,局部变量的内存空间被释放。
在C语言中,全局变量在声明时确实会分配存储空间。以下是关于全局变量内存分配的详细解释:
全局变量的内存分配
-
声明和定义的区别:
- 声明:声明告诉编译器变量的类型和名字,但不分配存储空间。例如,在一个头文件中使用
extern
关键字声明一个变量,这仅仅是声明而不是定义:extern int globalVar;
- 定义:定义实际分配存储空间。例如,在一个源文件中定义一个变量:
int globalVar;
- 当变量被定义时,存储空间会被分配。
- 声明:声明告诉编译器变量的类型和名字,但不分配存储空间。例如,在一个头文件中使用
-
分配时机:
- 全局变量在程序启动时分配存储空间,即在执行main函数之前,通常在程序加载到内存时由运行时系统完成。
- 全局变量的存储空间分配在数据段(data segment)中,包括初始化的数据段(.data)和未初始化的数据段(.bss)。
示例代码
以下是一个全局变量的定义和使用示例:
#include <stdio.h>
// 定义全局变量
int globalVar = 5;
void exampleFunction() {
printf("Global Variable: %d\n", globalVar);
}
int main() {
exampleFunction();
return 0;
}
头文件
C语言中的头文件(Header Files)是以.h结尾的文件,主要用于声明函数、宏、常量和数据类型,以便在多个源文件中共享。
计算器程序拆分:
静态变量
在C语言中,static
关键字用于声明静态变量。静态变量有几个重要特性和用途,具体如下:
局部静态变量
当在函数内部声明一个静态变量时,该变量的生命周期贯穿程序的整个运行时间,但它的作用域仍然是局部的。这意味着即使函数已经退出,该静态变量仍然保持其值,下次再次调用该函数时,变量不会重新初始化。
示例:
#include <stdio.h>
void counter() {
static int count = 0; // 局部静态变量
count++;
printf("Count: %d\n", count);
}
int main() {
counter(); // 输出:Count: 1
counter(); // 输出:Count: 2
counter(); // 输出:Count: 3
return 0;
}
在这个例子中,count
变量在第一次调用counter
函数时初始化为0,但在后续调用中,它保持其值而不是重新初始化。
全局静态变量
当在函数外部(即在文件的全局范围内)声明一个静态变量时,该变量的作用域被限制在声明它的文件内部。其他文件无法访问这个变量,即使使用extern
关键字也不行。这在编写大型项目时非常有用,可以防止命名冲突。
示例:
// file1.c
static int globalVar = 0; // 全局静态变量
void modifyVar() {
globalVar++;
printf("globalVar in file1: %d\n", globalVar);
}
// file2.c
extern void modifyVar();
int main() {
modifyVar(); // 输出:globalVar in file1: 1
modifyVar(); // 输出:globalVar in file1: 2
return 0;
}
在这个例子中,globalVar
是一个静态全局变量,尽管file2.c
调用了modifyVar
函数,但它无法直接访问或修改globalVar
。
静态函数
除了静态变量,函数也可以被声明为静态的。静态函数只能在声明它们的文件中可见,这有助于实现文件级的封装,防止命名冲突。
示例:
// file1.c
static void staticFunction() {
printf("This is a static function.\n");
}
void publicFunction() {
staticFunction();
}
// file2.c
extern void publicFunction();
int main() {
publicFunction(); // 输出:This is a static function.
// staticFunction(); // 错误:无法访问静态函数
return 0;
}
在这个例子中,staticFunction
只能在file1.c
中被调用,其他文件无法调用它。
总结
static
关键字在C语言中用于控制变量和函数的可见性和生命周期:
- 局部静态变量:在函数内部声明,生命周期贯穿整个程序运行,但作用域局限于函数内。
- 全局静态变量:在文件的全局范围内声明,作用域限于声明它的文件。
- 静态函数:只能在声明它们的文件中可见。
使用static
可以提高程序的模块化和封装性,减少命名冲突,并且在某些情况下可以提高程序的性能。
寄存器变量
在C语言中,register
关键字用于声明变量为寄存器变量(register variables)。寄存器变量是一种提示,它告诉编译器将该变量存储在CPU寄存器中,以便快速访问和处理。然而,需要注意几点:
-
语法:声明一个寄存器变量的语法为在变量声明前加上
register
关键字,如下所示:register int x;
-
编译器提示:
register
关键字只是对编译器的提示,它建议编译器将该变量存储在寄存器中,但并不强制。编译器可以选择忽略这个提示,特别是当寄存器变量的数量超过了可用的寄存器数量或者它不适合存储在寄存器中时。 -
无法取地址:不能对寄存器变量使用
&
运算符取地址,因为寄存器变量本身就可能不会在内存中有确切的地址。 -
使用场景:寄存器变量通常用于频繁访问和修改的局部变量,例如在循环中的计数器或者临时变量。它们的使用可以提高程序的执行速度,因为访问寄存器比访问内存要快。
-
效果限制:现代编译器通常能够根据优化算法自动决定哪些变量适合存储在寄存器中,因此
register
关键字的实际效果可能受限制或者无法感知到显著的性能改进。
总之,尽管register
关键字曾经是一种常用的优化手段,但由于现代编译器的进步和优化策略,它的实际效果可能不如预期。因此,现代C程序员通常不会显式使用register
关键字,而是依赖编译器自动进行优化。
初始化
在C语言中,变量的初始化是指在声明变量的同时为其赋予一个初始值。变量可以在声明时进行初始化,也可以在后续的代码中进行赋值操作。这里简要介绍C语言中变量初始化的几种方式和注意事项:
1. 声明时初始化
在声明变量的同时为其赋值,称为声明时初始化。示例如下:
int x = 10; // 声明一个整型变量x,并初始化为10
float y = 3.14f; // 声明一个浮点型变量y,并初始化为3.14
char ch = 'A'; // 声明一个字符型变量ch,并初始化为字符'A'
2. 后续赋值
变量可以在声明后的任何时候赋值,使用赋值运算符(=
)将一个值赋给变量。示例如下:
int x; // 声明一个整型变量x
x = 20; // 给变量x赋值为20
3. 默认初始化
如果变量在声明时没有被显式初始化,它们将会被默认初始化。默认初始化的值取决于变量的存储类型和作用域:
- 全局变量和静态变量:如果没有显式初始化,将会被初始化为0。
- 局部变量:如果没有显式初始化,它们将包含一个随机值(未定义行为),因此在使用前最好显式初始化。
示例:
int global_var; // 全局变量,默认初始化为0
void function() {
static int static_var; // 静态变量,默认初始化为0
int local_var; // 局部变量,未定义初始化值,可能包含随机值
}
4. 复合类型的初始化
- 数组初始化:
int arr[5] = {1, 2, 3, 4, 5}; // 声明一个包含5个元素的整型数组,并初始化
5. 字符串初始化
char str1[] = "Hello"; // 自动确定数组大小并初始化
char pattern[] = { 'o', 'u', 'l', 'd', '\0' };
char str2[10] = "Hello"; // 显式指定数组大小,初始化字符串
注意事项
- 初始化与赋值的区别:初始化是在声明变量时给它一个初始值;赋值是在变量已经声明后修改其值。
- 局部变量的初始化:局部变量如果没有显式初始化,其值是未定义的(不确定的),因此使用前最好显式初始化。
- 全局变量和静态变量的初始化:它们如果没有显式初始化,默认会被初始化为0。
通过适当的初始化,可以确保变量在使用时具有正确的初始值,有助于避免潜在的错误和不确定行为。
递归
递归是指函数调用自身的过程。
示例:不借助printf的情况下,打印输入数字
/* printd: print n in decimal */
void printd(int n) {
if (n < 0) {
putchar('-'); // 如果n为负数,输出负号
n = -n; // 将n变为正数
}
if (n / 10) // 如果n大于等于10,递归调用printd函数
printd(n / 10);
putchar(n % 10 + '0'); // 输出n的个位数字,将数字转换为字符
}
C预处理器
The C Preprocessor(C预处理器)是C语言编译过程中的一个重要组成部分,它在实际编译之前对源代码进行处理。预处理器指令由以 #
开头的命令组成,用于在编译之前执行一些文本替换和条件编译等操作。以下是C预处理器的常见用法和功能:
1. 包含文件 (#include
)
#include
指令用于将外部文件的内容包含到当前文件中,通常用来包含标准库头文件或自定义头文件。
#include <stdio.h> // 包含标准输入输出库的头文件
#include "myheader.h" // 包含自定义头文件
2. 宏定义 (#define
)
#define
指令用于定义宏,宏是一种简单的文本替换。宏定义通常用来定义常量、函数或代码片段。
#define PI 3.14159 // 定义常量PI
#define SQUARE(x) ((x) * (x)) // 定义宏函数计算平方
#define forever for (;;) // 无限循环
#define max(A, B) ((A) > (B) ? (A) : (B)) // 函数
3. 条件编译 (#if
, #ifdef
, #ifndef
, #endif
)
条件编译指令用于根据条件包含或排除代码段。常见的条件编译指令有 #if
, #ifdef
, #ifndef
和 #endif
。
#if !defined(HDR)
#define HDR
/* contents of hdr.h go here */
#endif
4. 条件语句
可以在 #if
或 #ifdef
指令中使用 #include
来条件包含文件。
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
注意事项
- 预处理器指令在编译前被处理,不是C语言的一部分,所以它们不受语法检查和类型检查的限制。
- 使用预处理器可以增强代码的灵活性和可维护性,但过度使用可能会导致代码可读性降低和调试困难。
- 宏定义和条件编译是预处理器最常见的用途,它们使得代码能够在不同平台和条件下进行编译。
综上所述,C预处理器提供了许多有用的工具和技术,可以在编译之前对源代码进行多种形式的处理,从而使得C语言在不同场景下具有更强的适应性和灵活性。