C语言入门4-函数和程序结构
函数举例
读取字符串,如果字符串中含有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++) { // 遍历字符串 sfor (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 中的位置// 外循环遍历字符串 sfor (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^2printf("%f\n", atof("-3.123e-03")); // 测试科学计数法:-3.123 * 10^-3return 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); // 弹出被除数与除数相除后再压入栈中(避免除数为零的情况)elseprintf("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; // 将操作数压入栈顶,并将栈顶指针向上移动elseprintf("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"); // 若缓冲区已满,则报错elsebuf[bufp++] = c; // 将字符放回缓冲区
}
运行
1 2 - 4 5 + *-9
12 12 +24
12 121212error: stach empty0
程序核心原理:
while (next operator or operand is not end-of-file indicator)if (number)push itelse if (operator)pop operandsdo operationpush resultelse if (newline)pop and print top of stackelseerror
练习
- 为上面的程序添加取模运算,并且使其支持负数。
支持负数
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);elseprintf("error: zero divisor\n");break;case '%': // 取模操作op2 = pop();if (op2 != 0)push((int) pop() % (int) op2);elseprintf("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;elseprintf("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 100char 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");elsebuf[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: 1counter(); // 输出:Count: 2counter(); // 输出:Count: 3return 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: 1modifyVar(); // 输出:globalVar in file1: 2return 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; // 全局变量,默认初始化为0void function() {static int static_var; // 静态变量,默认初始化为0int 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语言在不同场景下具有更强的适应性和灵活性。
相关文章:

C语言入门4-函数和程序结构
函数举例 读取字符串,如果字符串中含有ould则输出该字符串,否则不输出。 #include <stdio.h>// 函数声明 int getLine(char s[], int lim); int strindex(char s[], char t[]);int main() {char t[] "ould"; // 要查找的目标子字符串…...

分行业二氧化碳排放数据
分行业二氧化碳排放量 资源名称:分行业二氧化碳排放量 数据来源:中国能源统计年鉴 时间范围:1995-2018年指标:八类能源和总量:煤炭、焦炭、原油、汽油、煤油、柴油、燃料油、天然气...

【OS基础】符合AUTOSAR标准的RTAOS-Alarms详解
目录 前言 正文 7.报警Alarms 7.1配置Alarms 7.1.1激活一个任务 7.1.2 设置一个事件 7.1.3报警回调Alarm Callback 7.1.4 增加计数器值 7.2设置Alarms 7.2.1 绝对Alarms 7.2.2 相对Alarm 7.3自启动Alarms 7.4 删除Alarms 7.5确认何时会发生Alarm 7.6非周期Alarm…...

基于Java的学生成绩管理系统
你好呀,我是计算机学姐码农小野!如果有相关需求,可以私信联系我。 开发语言:Java 数据库:MySQL 技术:Java技术,B/S结构 工具:MyEclipse,MySQL 系统展示 首页 个人中…...

都2024年了,还有人不懂动态代理么?
文章目录 一、定义二、静态代理三、动态代理1. JDK代理1.1 JDK代理实现流程1.2 动态生成的类字节码 2. Cglib代理2.1 Cglib实现流程 四、总结 一、定义 静态代理和动态代理都反映了一个代理模式,代理模式是一种经典的设计模式,常用于为其他对象提供一种…...

ARM功耗管理框架之PPU
安全之安全(security)博客目录导读 思考:功耗管理框架?SCP?PPU?LPI?之间的关系?如何配合? 目录 一、功耗管理框架中的PPU 二、PPU的结构与连接关系 三、PPU操作模式和电源模式及其之间的转…...

说说 SSL 的错误认识和不足之处
最近明月在学习折腾 LNMP 期间无意中建了一个 Typecho 的博客小站,近一周的折腾下来,收获真的不少,致使兴趣也越来越浓了,在升级 LNMP 的时候捎带手的给这个 Typecho 博客也启用了 SSL。并且开启了 memcached 和 OPcache 优化加速…...

Go语言day1
下载go语言的安装程序: All releases - The Go Programming Language 配置go语言的环境变量: 写第一个go语言 在E:\go_workspace当前窗口使用cmd命令: 输入 go run test.go...

【Python机器学习】利用t-SNE进行流形学习
虽然PCA通常是用于变换数据的首选方法,使你能够用散点图将其可视化,但这一方法的性质限制了其有效性。 有一类用于可视化的算法叫做流形学习算法,它允许进行更复杂的映射,通常也可以给出更好的可视化。其中特别有用的一个就是t-S…...

03 - matlab m_map地学绘图工具基础函数 - 设置坐标系(m_coord)
03 - matlab m_map地学绘图工具基础函数 - 设置坐标系(m_coord) 0. 引言1. m_proj使用方法2. 结语 0. 引言 上一篇介绍了m_proj函数用于初始化投影,本篇介绍的函数m_coord用于初始化地理坐标系或地磁坐标系,地理/地磁坐标系和投影…...

UEC++ 虚幻5第三人称射击游戏(一)
UEC 虚幻5第三人称射击游戏(一) 创建一个空白的C工程 人物角色基本移动 创建一个Character类添加一些虚幻商城中的基础动画 给角色类添加Camera与SPringArm组件 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category "SpringArm")clas…...

java小代码(1)
代码 : 今日总结到此结束,拜拜!...
SLAM ORB-SLAM2(27)词袋模型
SLAM ORB-SLAM2(27)词袋模型 1. 词袋模型1.1. 词汇树1.2. 逆向索引表1.3. 逆向索引表2. 词袋向量3. 匹配候选帧3.1. 找出和当前帧具有公共单词的所有关键帧3.2. 找出和当前帧最多公共单词的关键帧3.3. 剔除共享单词数较少的关键帧3.4. 计算关键帧的共视关键帧组的总得分3.5. …...

OpenAI 的 GPT-5:CTO米拉-穆拉提说,到 2026 年将实现博士级智能(Ph.D.-Level))
据首席技术官米拉-穆拉提(Mira Murati)介绍,GPT-5 是 OpenAI 人工智能的下一代进化产品,将于 2025 年底或 2026 年初在特定任务中实现博士级智能。 GPT-5 内部代号为 "Gobi "和 “Arrakis”,将是一个多模态…...

macbook配置adb环境和用adb操作安卓手机
(参考:ADB工具包的安装与使用_adb工具箱-CSDN博客) 第一步:从Android开发者网站下载Android SDK(软件开发工具包)。下载地址为: 第二步:解压下载的SDK压缩文件到某个目录中。 进入解…...

微软TTS最新模型,发布9种更真实的AI语音
很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新: 视频翻译和增强的实时语音翻译 API。 视频翻译(批量) 今天,我们宣布推出视频翻译预览版,这是一项突破性的服务,旨在改变企业本地化视频内容…...
python爬虫 -爬取 json 格式数据
在Python中,爬取JSON格式的数据通常涉及到发送 HTTP请求到某个URL,并解析返回的JSON数据。以下是一个简单的示例,说明如何使用Python的requests库来爬取JSON格式的数据: 1. 首先,确保你已经安装了requests库。如果没…...
Pytorch(5)-----梯度计算
一、问题 如何使用Pytorch计算样本张量的基本梯度呢?考虑一个样本数据集,且有两个展示变量,在给定初始权重的基础上,如何在每次迭代中计算梯度呢? 二、如何运行 假设有x_data 和 y_data 列表,计算两个列表需…...
C#的膨胀之路:创新还是灭亡
开篇概述 C#,这门由微软推出的编程语言,自2000年诞生以来,以其简洁的语法、强大的功能和广泛的应用场景,赢得了我等程序员的热爱。它在.NET框架的加持下,展现出无与伦比的开发效率和性能。然而,随着时间的流…...

SpringBoot 过滤器和拦截器的区别
SpringBoot 过滤器和拦截器的区别 Spring拦截器(Interceptor)和过滤器(Filter)是Spring框架中用于处理请求的两种机制,虽然它们都可以在请求处理的不同阶段进行拦截和处理,但它们的工作原理和应用场景有所…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...