当前位置: 首页 > news >正文

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
}

练习

  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);
}

练习

  1. 修改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

练习

  1. 为上面的程序添加取模运算,并且使其支持负数。
    支持负数
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;  // 返回表示数字的标记
}
  1. 支持命令:打印(输出堆栈中的数据)、复制(复制一个堆栈顶部数据)、交换(交换堆栈顶部的两个数据)、清理(清空堆栈数据)。
#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;
}

范围规则

局部变量的内存分配

  1. 声明时不分配存储空间

    • 当在函数内部声明局部变量时,编译器只会记录变量的名称、类型和作用域信息,并计算其在栈帧中的偏移量。此时并没有实际的内存分配。
    • 例如,在函数中声明一个局部变量 int localVar; 时,编译器知道 localVar 是一个 int 类型的变量,但没有立即为其分配内存。
  2. 函数调用时分配存储空间

    • 实际的内存分配发生在函数调用时。当函数被调用时,函数的栈帧在栈上被分配。栈帧包含了函数的所有局部变量的存储空间。
    • 当函数执行完毕并返回时,栈帧被销毁,局部变量的内存空间被释放。

在C语言中,全局变量在声明时确实会分配存储空间。以下是关于全局变量内存分配的详细解释:

全局变量的内存分配

  1. 声明和定义的区别

    • 声明:声明告诉编译器变量的类型和名字,但不分配存储空间。例如,在一个头文件中使用 extern 关键字声明一个变量,这仅仅是声明而不是定义:
      extern int globalVar;
      
    • 定义:定义实际分配存储空间。例如,在一个源文件中定义一个变量:
      int globalVar;
      
    • 当变量被定义时,存储空间会被分配。
  2. 分配时机

    • 全局变量在程序启动时分配存储空间,即在执行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寄存器中,以便快速访问和处理。然而,需要注意几点:

  1. 语法:声明一个寄存器变量的语法为在变量声明前加上register关键字,如下所示:

    register int x;
    
  2. 编译器提示register关键字只是对编译器的提示,它建议编译器将该变量存储在寄存器中,但并不强制。编译器可以选择忽略这个提示,特别是当寄存器变量的数量超过了可用的寄存器数量或者它不适合存储在寄存器中时。

  3. 无法取地址:不能对寄存器变量使用&运算符取地址,因为寄存器变量本身就可能不会在内存中有确切的地址。

  4. 使用场景:寄存器变量通常用于频繁访问和修改的局部变量,例如在循环中的计数器或者临时变量。它们的使用可以提高程序的执行速度,因为访问寄存器比访问内存要快。

  5. 效果限制:现代编译器通常能够根据优化算法自动决定哪些变量适合存储在寄存器中,因此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-函数和程序结构

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

分行业二氧化碳排放数据

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

【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的学生成绩管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术&#xff0c;B/S结构 工具&#xff1a;MyEclipse&#xff0c;MySQL 系统展示 首页 个人中…...

都2024年了,还有人不懂动态代理么?

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

ARM功耗管理框架之PPU

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

说说 SSL 的错误认识和不足之处

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

Go语言day1

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

【Python机器学习】利用t-SNE进行流形学习

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

03 - matlab m_map地学绘图工具基础函数 - 设置坐标系(m_coord)

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

UEC++ 虚幻5第三人称射击游戏(一)

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

java小代码(1)

代码 &#xff1a; 今日总结到此结束&#xff0c;拜拜&#xff01;...

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))

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

macbook配置adb环境和用adb操作安卓手机

&#xff08;参考&#xff1a;ADB工具包的安装与使用_adb工具箱-CSDN博客&#xff09; 第一步&#xff1a;从Android开发者网站下载Android SDK&#xff08;软件开发工具包&#xff09;。下载地址为&#xff1a; 第二步&#xff1a;解压下载的SDK压缩文件到某个目录中。 进入解…...

微软TTS最新模型,发布9种更真实的AI语音

很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新&#xff1a; 视频翻译和增强的实时语音翻译 API。 视频翻译&#xff08;批量&#xff09; 今天&#xff0c;我们宣布推出视频翻译预览版&#xff0c;这是一项突破性的服务&#xff0c;旨在改变企业本地化视频内容…...

python爬虫 -爬取 json 格式数据

在Python中&#xff0c;爬取JSON格式的数据通常涉及到发送 HTTP请求到某个URL&#xff0c;并解析返回的JSON数据。以下是一个简单的示例&#xff0c;说明如何使用Python的requests库来爬取JSON格式的数据&#xff1a; 1. 首先&#xff0c;确保你已经安装了requests库。如果没…...

Pytorch(5)-----梯度计算

一、问题 如何使用Pytorch计算样本张量的基本梯度呢&#xff1f;考虑一个样本数据集&#xff0c;且有两个展示变量&#xff0c;在给定初始权重的基础上&#xff0c;如何在每次迭代中计算梯度呢&#xff1f; 二、如何运行 假设有x_data 和 y_data 列表&#xff0c;计算两个列表需…...

C#的膨胀之路:创新还是灭亡

开篇概述 C#&#xff0c;这门由微软推出的编程语言&#xff0c;自2000年诞生以来&#xff0c;以其简洁的语法、强大的功能和广泛的应用场景&#xff0c;赢得了我等程序员的热爱。它在.NET框架的加持下&#xff0c;展现出无与伦比的开发效率和性能。然而&#xff0c;随着时间的流…...

SpringBoot 过滤器和拦截器的区别

SpringBoot 过滤器和拦截器的区别 Spring拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;是Spring框架中用于处理请求的两种机制&#xff0c;虽然它们都可以在请求处理的不同阶段进行拦截和处理&#xff0c;但它们的工作原理和应用场景有所…...

协程执行顺序引发的问题

引言 在Golang中&#xff0c;因为协程执行的顺序是不固定的&#xff0c;如果不在代码里进行控制&#xff0c;可能就会导致预期外的输出。 本文通过分析一段代码的执行来介绍这种情况&#xff0c;以及可行的控制协程执行顺序的方法&#xff1a; sleep()waitGroup 实例分析 代…...

android webview调用js滚动到指定位置

一、activity import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tencent.smtt.sdk.WebView import com.tencent.smtt.sdk.WebViewClientclass MainActivity : AppCompatActivity() {private lateinit var webView: WebViewoverride fun …...

WPF 深入理解一、基础知识介绍

基础知识 本系列文章是对个人 B站 up 微软系列技术教程 记录 视频地址 https://www.bilibili.com/video/BV1HC4y1b76v/?spm_id_from333.999.0.0&vd_source0748f94a553c71a2b0125078697617e3 winform 与 wpf 异同 1.winform 项目结构 编辑主要是在 Form1.cs(页面)&#…...

腾讯云点播ugc upload | lack signature 问题处理

我犯一个很傻的错误 参考腾讯云官方文档&#xff1a;云点播 Web 端上传 SDK-开发指南-文档中心-腾讯云 进行开发&#xff0c;但是却报错了&#xff0c;始终找不到问题&#xff0c;错误提示&#xff1a;ugc upload | lack signature&#xff0c;意思是缺少签名或者签名失败&…...

计算机视觉实验二:基于支持向量机和随机森林的分类(Part one: 编程实现基于支持向量机的人脸识别分类 )

目录 一、实验内容 二、实验目的 三、实验步骤 四、实验结果截图 五、实验完整代码 六、报错及解决方案 PS:实验的运行速度受电脑性能影响,如遇运行卡顿请耐心等待。 一、实验内容 编程实现基于支持向量机的人脸识别分类,基本功能包括:Labeled Faces in th…...

5.什么是C语言

什么是 C 语言? C语言是一种用于和计算机交流的高级语言, 它既具有高级语言的特点&#xff0c;又具有汇编语言的特点 非常接近自然语言程序的执行效率非常高 C语言是所有编程语言中的经典&#xff0c;很多高级语言都是从C语言中衍生出来的&#xff0c; 例如:C、C#、Object-C、…...

DINO-DETR

DINO-DETR DETR收敛慢的问题1. Contrastive DeNoising Training(对比方法降噪训练)2. Mixed Query Selection(混合查询选择方法对锚点进行初始化)3. Look Forward Twice(两次前向方法)==DINO模型的传播过程,以及部分模块的改进==DETR收敛慢的问题 PnP-DETR(ICCV 2021) 改进了…...

Representation RL:HarmonyDream: Task Harmonization Inside World Models

ICML2024 paper code Intro 基于状态表征的model-based强化学习方法一般需要学习状态转移模型以及奖励模型。现有方法都是将二者联合训练但普遍缺乏对如何平衡二者之间的比重进行研究。本文提出的HarmonyDream便是通过自动调整损失系数来维持任务间的和谐&#xff0c;即在世界…...

Centos7系统下Docker的安装与配置

文章目录 前言下载Docker安装yum库安装Docker启动和校验配置Docker镜像加速卸载Docker 前言 此博客的内容的为自己的学习笔记&#xff0c;如果需要更具体的内容&#xff0c;可查看Docker官网文档内容 注意&#xff1a;以下命令在root管理员用户下运行&#xff0c;如果在普通用…...

无人机校企合作

有没有想过&#xff0c;无人机和校企合作能碰撞出怎样的火花&#xff1f;&#x1f525;今天就来给大家揭秘一下这个神秘组合&#xff01; 无人机&#xff0c;作为现代科技的代表&#xff0c;已经渗透到我们生活的方方面面。而校企合作&#xff0c;更是推动科技创新、培养人才的…...

八爪鱼现金流-028,个人网站访问数据统计分析,解决方案

个人网站访问数据统计分析&#xff0c;解决方案 调研 结论&#xff1a;使用百度统计 步骤 1.注册百度统计 2.获取安装代码 3.在项目中&#xff0c;页面代码添加如下片段 <script>var _hmt _hmt || [];(function() {var hm document.createElement("script&…...

大厂面试官问我:布隆过滤器有不能扩容和删除的缺陷,有没有可以替代的数据结构呢?【后端八股文二:布隆过滤器八股文合集】

往期内容&#xff1a; 面试官问我&#xff1a;Redis处理点赞&#xff0c;如果瞬时涌入大量用户点赞&#xff08;千万级&#xff09;&#xff0c;应当如何进行处理&#xff1f;【后端八股文&#xff08;1&#xff09;】-CSDN博客 本文为【布隆过滤器八股文合集】初版&#xff0c…...

PHP米表域名出售管理源码带后台

源码介绍 html5米表源码PHP域名销售程序安装方法&#xff1a; 本站已测试,各项功能正常,功能易用,不复杂,非常适合个人米表使用 1、所有文件传至网站目录 2、浏览器执行http://你的访问网址/install 3、输入mysql帐号及密码信息&#xff0c;提交安装 源码截图 源码下载 …...

【开发12年码农教你】Android端简单易用的SPI框架-——-SPA

Service(priority 1) public class APrinterService implements IPrinterService { Override public void print() { System.out.println(“this is a printer service.”); } } 复制代码 B模块 —— BPrinterService Service(path“b_printer”, priority 2) public class…...

以太坊==MetaMask获取测试币最新网址

估算分数https://community.infura.io/t/unable-to-receive-sepolia-eth-from-faucet/7715 Gitcoin Passport 水龙头地址&#xff0c;填入自己的测试地址 水龙头项目地址 GitHub - pk910/PoWFaucet: Modularized faucet for EVM chains with different protection methods (…...

军用FPGA软件 Verilog语言的编码准测之触发器、锁存器

军用FPGA软件 Verilog语言的编码准测之触发器、锁存器 语言 &#xff1a;Verilg HDL EDA工具&#xff1a;ISE、Vivado、Quartus II 军用FPGA软件 Verilog语言的编码准测之触发器、锁存器一、引言二、基本编程规范之触发器强制准则1---禁止在同一个 always 语句中混合使用有复位…...

智能汽车 UI 风格独具魅力

智能汽车 UI 风格独具魅力...

javafx例子笔记

文章目录 创建过程javafx独立版报错 Exception in thread "WindowsNativeRunloopThread" java.lang.NoSuchMethodError: <init> javafx是java gui工具。 一般会转换为exe&#xff0c;成为可交互的应用。 那么来个简单的例子吧。 先说明一点&#xff0c;javafx不…...

【ajax基础】回调函数地狱

一&#xff1a;什么是回调函数地狱 在一个回调函数中嵌套另一个回调函数&#xff08;甚至一直嵌套下去&#xff09;&#xff0c;形成回调函数地狱 回调函数地狱存在问题&#xff1a; 可读性差异常捕获严重耦合性严重 // 1. 获取默认第一个省份的名字axios({url: http://hmaj…...

SparkSQL的分布式执行引擎-Thrift服务:学习总结(第七天)

系列文章目录 SparkSQL的分布式执行引擎 1、启动Thrift服务 2、beeline连接Thrift服务 3、开发工具连接Thrift服务 4、控制台编写SQL代码 文章目录 系列文章目录前言一、SparkSQL的分布式执行引擎(了解)1、启动Thrift服务2、beeline连接Thrift服务3、开发工具连接Thrift服务4、…...

联华集团:IT团队如何实现从成本中心提升至价值中心|OceanBase 《DB大咖说》(十)

OceanBase《DB大咖说》第 10 期&#xff0c;我们邀请到了联华集团的CTO楼杰&#xff0c;来分享他如何思考 IT 业务价值&#xff0c;以及联华华商数据库的升级实践。 楼杰从大学毕业后就进入了联华工作&#xff0c;并一直扎根在近 20 年的&#xff0c;从一名底层的技术员成长为…...

计算机系统基础实训五—CacheLab实验

实验目的与要求 1、让学生更好地应用程序性能的优化方法&#xff1b; 2、让学生更好地理解存储器层次结构在程序运行过程中所起的重要作用&#xff1b; 3、让学生更好地理解高速缓存对程序性能的影响&#xff1b; 实验原理与内容 本实验将帮助您了解缓存对C程序性能的影响…...

PHP框架之CodeIgniter框架

CodeIgniter框架详细说明 CodeIgniter是一个简单而强大的PHP框架&#xff0c;专为快速开发Web应用程序而设计。它遵循MVC&#xff08;模型-视图-控制器&#xff09;设计模式&#xff0c;为开发者提供了丰富的功能和灵活性&#xff0c;同时保持代码的轻量级和易于管理。CodeIgn…...

714. 买卖股票的最佳时机含手续费

714. 买卖股票的最佳时机含手续费 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;ExplanationSummary 参考代码&#xff1a;_714买卖股票的最佳时机含手续费 错误经验吸取 原题链接&#xff1a; 714. 买卖股票的最佳时机含手续费 https://leetcode.cn/probl…...

Linux系统查看程序内存及CPU占用

文章目录 1.free命令2.top命令3.PS命令3.1 查看内存占用前10位&#xff1a;3.2 查看CPU占用前10位 参考文档 1.free命令 可以通过free命令查看物理内存占用情况 #单位KB free #单位MB free -m #单位GB free -h 2.top命令 输入top命令&#xff0c;会输出定时刷新的程序PID、内…...

数据结构7---图

一、定义 对于图的定义&#xff0c;我们需要明确几个注意的地方:一线性表中我们把数据元素叫元素&#xff0c;树中叫结点&#xff0c;在途中数据元素我们则称之为顶点(Vertex)。 对于图的定义&#xff0c;我们需要明确几个注意的地方: 线性表中我们把数据元素叫元素&#xf…...

Excel 如何复制单元格而不换行

1. 打开excle, sheet1右键单击>查看代码>插入>模块 输入代码 Sub CopyText() Updated by NirmalDim xAutoWrapper As ObjectSet xAutoWrapper New DataObject or GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")xAutoWrapper.SetText ActiveC…...

前端 CSS 经典:mix-blend-mode 属性

前言&#xff1a;这是一个混合属性&#xff0c;作用是将两个颜色混合生成一个新颜色。可以将视频和文字相融合&#xff0c;产生动态文字效果。 效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8" />&l…...

OpenCV--滤波器(一)

低通滤波器 代码和笔记 代码和笔记 import cv2 import numpy as np""" 滤波器--用于图像处理的重要工具&#xff0c;它们可以根据图像中像素的邻域信息来修改像素值&#xff0c;以实现去噪、模糊、锐化、边缘检测等效果。低通滤波器&#xff08;Low-pass Filte…...

MK的前端精华笔记

文章目录 MK的前端精华笔记第一阶段&#xff1a;前端基础入门1、&#xff08;1&#xff09;、&#xff08;2&#xff09;、 2、3、4、5、6、7、 第二阶段&#xff1a;组件化与移动WebAPP开发1、&#xff08;1&#xff09;、&#xff08;2&#xff09;、 2、3、4、5、6、7、 第三…...

2.8亿东亚五国建筑数据分享

数据是GIS的血液&#xff01; 我们现在为你分享东亚5国的2.8亿条建筑轮廓数据&#xff0c;该数据包括中国、日本、朝鲜、韩国和蒙古5个东亚国家完整、高质量的建筑物轮廓数据&#xff0c;你可以在文末查看领取方法。 数据介绍 虽然开源的全球的建筑数据已经有微软的建筑数据…...

gitLab使用流程

标题1.配置账户 git config --global user.name git config --global user.email mygitlabmali.cn 标题2.生成秘匙 ssh-keygen -t rsa -C “mygitlabmail.cn” 。 //输入命令后一直回车 &#xff0c;输入命令后一直回车&#xff08;密码可以不填&#xff09;&#xff0c;至…...

深度分析:Maven在软件构建管理中的地位与选型指南

引言 在现代软件开发中&#xff0c;构建管理工具扮演着至关重要的角色。它们不仅简化了构建过程&#xff0c;还提高了构建的可重复性和可靠性。Apache Maven 是其中的佼佼者&#xff0c;但它并不是唯一的选择。本文将详细分析 Maven&#xff0c;比较其与其他同类工具的优缺点&…...

一元线性回归-R语言

# # 安装包 # install.packages(ggplot2) # library(ggplot2) Sys.setlocale(category LC_ALL, locale English_United States.1252) # Sys.setlocale("LC_ALL","Chinese") x <- c(18, 20, 22, 24, 26, 28, 30) y <- c(26.86, 28.35, 28.87,28.75,…...

K8S集群进行分布式负载测试

使用K8S集群执行分布式负载测试 本教程介绍如何使用Kubernetes部署分布式负载测试框架&#xff0c;该框架使用分布式部署的locust 产生压测流量&#xff0c;对一个部署到 K8S集群的 Web 应用执行负载测试&#xff0c;该 Web 应用公开了 REST 格式的端点&#xff0c;以响应传入…...

C++:枚举类的使用案例及场景

一、使用案例 在C中&#xff0c;枚举类&#xff08;也称为枚举类型或enum class&#xff09;是C11及以后版本中引入的一种更加强大的枚举类型。与传统的枚举&#xff08;enum&#xff09;相比&#xff0c;枚举类提供了更好的类型安全性和作用域控制。下面是一个使用枚举类的案…...

荣威D5XDMH:长续航大动力,开启混动SUV新篇章

随着汽车市场的日益繁荣,消费者对车辆性能与品质的要求也在不断提升。荣威品牌深谙此道,于5月20日正式推出了其全新力作——荣威D5X DMH插混SUV,不仅为消费者带来了"入门即享长续航大动力"的极致体验,更以超高的性价比,在混动SUV市场掀起了一股"价值革命&q…...

【Linux进程篇】Linux进程管理——进程创建与终止

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 目录 进程创建 fork函数初识 写时拷贝 fork常规用法 fork调用失败的原因 进程终止 进程退出场景 _exit函数 exit函数 return退出 进程创建 fork函数初识 在linux中fork函数时非常重要的函数&#xff0c;它从已…...

C语言 指针——指针变量的定义、初始化及解引用

目录 指针 内存如何编址&#xff1f; 如何对变量进行寻址&#xff1f; 用什么类型的变量来存放变量的地址? 如何显示变量的地址?​编辑 使用未初始化的指针会怎样&#xff1f; NULL是什么&#xff1f; 如何访问指针变量指向的存储单元中的数据&#xff1f; 指针变量的…...

Python OCR 文字识别使用模型:读光-文字识别-行识别模型-中英-通用领域

介绍 什么是OCR&#xff1f; OCR是“Optical Character Recognition”的缩写&#xff0c;中文意为“光学字符识别”。它是一种技术&#xff0c;可以识别和转换打印在纸张或图像上的文字和字符为机器可处理的格式&#xff0c;如计算机文本文件。通过使用OCR技术&#xff0c;可…...

使用NuScenes数据集生成ROS Bag文件:深度学习与机器人操作的桥梁

在自动驾驶、机器人导航及环境感知的研究中&#xff0c;高质量的数据集是推动算法发展的关键。NuScenes数据集作为一项开源的多模态自动驾驶数据集&#xff0c;提供了丰富的雷达、激光雷达&#xff08;LiDAR&#xff09;、摄像头等多种传感器数据&#xff0c;是进行多传感器融合…...

在做题中学习(62):矩阵区域和

1314. 矩阵区域和 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;二维前缀和 思路&#xff1a;读题画图才能理解意思&#xff1a;dun点点的是mat中的一个数&#xff0c;而要求的answer同位置的数 以点为中心上下左右延长 k 个单位所围成长方形的和。 因为最后answ…...