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++) {         // 遍历字符串 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
}

练习

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

练习

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

练习

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

范围规则

局部变量的内存分配

  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: 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寄存器中,以便快速访问和处理。然而,需要注意几点:

  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; // 全局变量,默认初始化为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语言在不同场景下具有更强的适应性和灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/735635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理学习路线(1)——NLP的基本流程

NLP基本流程 【NLP基本流程】 0. 获取语料 ——> 1. 语料预处理 ——> 2. 特征工程&选择 ——> 3. 模型训练 ——> 4. 模型输出&上线 【NLP基本流程图】 Reference 1. 自然语言处理(NLP)的一般处理流程&#xff01;-腾讯云开发者社区-腾讯云 2. …

Vatee万腾平台:智能科技的领航者

随着科技的飞速发展&#xff0c;数字化转型已成为企业、行业乃至整个社会不可逆转的趋势。在这个变革的浪潮中&#xff0c;Vatee万腾平台凭借其卓越的技术实力、前瞻的战略眼光和卓越的服务品质&#xff0c;成为了智能科技的领航者。 Vatee万腾平台致力于为企业提供全方位的数字…

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

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

google gemini1.5 flash视频图文理解能力初探(一)

市面能够对视频直接进行分析的大模型着实不多&#xff0c;而且很多支持多模态的大模型那效果着实也不好。 从这篇公众号不只是100万上下文&#xff0c;谷歌Gemini 1.5超强功能展示得知&#xff0c;Gemini 1.5可以一次性处理1小时的视频、11小时的音频或100,000行代码&#xff0…

c++设计模式之一创建型模式

1、创建型模式&#xff08;常见的设计模式&#xff09; Factory 模式&#xff08;工厂模式&#xff0c;被实例化的子类&#xff09; 在面向对象系统设计中经常可以遇到以下的两类问题&#xff1a; 下面是第一类问题和代码示例&#xff1a;我们经常会抽象出一些类的公共接口以…

stm32学习笔记---新建工程步骤和点灯演示

目录 STM32的三种开发方式 基于寄存器的方式 基于库函数的方式 基于Hal库的方式 固件库介绍 新建基于标准库的工程步骤 配置寄存器来完成点灯操作 添加库函数来完成点灯操作 添加库函数 开始点灯操作 第一步&#xff1a;使能时钟 第二步&#xff1a;配置端口模式 …

ic基础|功耗篇03:ic设计人员如何在代码中降低功耗?一文带你了解行为级以及RTL级低功耗技术

大家好&#xff0c;我是数字小熊饼干&#xff0c;一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结&#xff0c;并通过汇总成文章的形式进行输出&#xff0c;相信无论你是在职的还是…

鞠婧祎多个商标被丝芭传媒申请注册!

近日鞠婧祎与丝芭传媒合约引发网络关注&#xff0c;普推商标老杨经检索发现&#xff0c;丝芭传媒早在2016起就申请注册了“鞠婧祎”24个商标&#xff0c;涉及多个商标分类&#xff0c;这些基本都下商标注册证。 不管对经纪公司还是网红公司&#xff0c;有实力的基本都会对旗下的…

# Kafka_深入探秘者(2):kafka 生产者

Kafka_深入探秘者&#xff08;2&#xff09;&#xff1a;kafka 生产者 一、kafka 消息发送流程解析 1、kafka &#xff1a;java 客户端 数据生产流程解析 二、kafka 发送类型 1、kafka 发送类型–发送即忘记&#xff1a;producer.send(record) 同步发送 //通过 send() 发送完…

【Vue3组件】分享一下自己写的简约风格评论区组件

代码比较简单&#xff0c;方便大家二次开发&#xff0c;旨在快速提供基础的样式模板&#xff0c;自行迭代定制 预览 简介 通用评论组件 组件功能 此组件旨在创建一个具备嵌套回复能力的通用评论区域&#xff0c;适用于构建动态、互动性强的用户讨论场景。 接收数据结构 组件通…

Paimon Trino Presto的关系 分布式查询引擎

Paimon支持的引擎兼容性矩阵&#xff1a; Trino 是 Presto 同项目的不同版本&#xff0c;是原Faceboo Presto创始人团队核心开发和维护人员分离出来后开发和维护的分支&#xff0c;Trino基于Presto&#xff0c;目前 Trino 和 Presto 都仍在继续开发和维护。 Trino 生态系统-客…

虚拟机IP地址频繁变化的解决方法

勾八动态分配IP&#xff0c;让我在学习redis集群的时候&#xff0c;配置很多的IP地址&#xff0c;但是由于以下原因导致我IP频繁变动&#xff0c;报错让我烦恼&#xff01;&#xff01;&#xff01;&#xff01; 为什么虚拟机的IP地址会频繁变化&#xff1f; 虚拟机IP地址频繁…

webpack处理js资源10--webpack入门学习

处理 js 资源 有人可能会问&#xff0c;js 资源 Webpack 不能已经处理了吗&#xff0c;为什么我们还要处理呢&#xff1f; 原因是 Webpack 对 js 处理是有限的&#xff0c;只能编译 js 中 ES 模块化语法&#xff0c;不能编译其他语法&#xff0c;导致 js 不能在 IE 等浏览器运…

Zabbix+Garafana监控部署

ZabbixGarafana监控部署 一、IP规划 服务器IP备注zabbix-server192.168.100.128zabbix服务端zabbix-mysql192.168.100.130数据库zabbix-client192.168.100.132zabbix客户端garafana-server192.168.100.134Garafana 二、zabbix-server安装zabbix ​ 配置IP地址为&#xff1a…

俄语打招呼和问候的12种表达方式,柯桥俄语培训

- Как дела ? 近况如何&#xff1f; -Нормально, а ты как? 还行吧&#xff0c;你呢&#xff1f; Vol.2 -Как себя чувствуете? 你感觉如何&#xff1f; -Все замечательно! 一切都非常棒。 Vol.3 -Ка…

基于matlab的图像灰度化与图像反白

1原理 2.1 图像灰度化原理 图像灰度化是将彩色图像转换为灰度图像的过程&#xff0c;使得每个像素点仅包含一个灰度值&#xff0c;从而简化了图像的复杂度。灰度化原理主要可以分为以下几种方法&#xff1a; 亮度平均法 原理&#xff1a;将图像中每个像素的RGB值的平均值作为…

Vue40 修改默认配置

修改默认配置 在官网查看各个属性的作用 ### 在vue.config.js文件中&#xff0c;修改属性的值

计算机网络 静态路由及动态路由RIP

一、理论知识 1.静态路由 静态路由是由网络管理员手动配置在路由器上的固定路由路径。其优点是简单和对网络拓扑变化不敏感&#xff0c;缺点是维护复杂、易出错&#xff0c;且无法自动适应网络变化。 2.动态路由协议RIP RIP是一种基于距离向量的动态路由协议。它使用跳数作…

接口自动化拓展:Flask框架安装、介绍及工作中的应用!

Flask是一个轻量级的Python Web框架&#xff0c;用于构建Web应用程序和API。它简洁而灵活&#xff0c;容易上手&#xff0c;并且非常适合用于开发小型到中型规模的应用程序。在接口自动化测试中&#xff0c;Flask可以作为服务器框架&#xff0c;用于搭建测试接口。 本文将从零…

使用USI作为主SPI接口

代码; lcd_drive.c //***************************************************************************** // // File........: LCD_driver.c // // Author(s)...: ATMEL Norway // // Target(s)...: ATmega169 // // Compiler....: AVR-GCC 3.3.1; avr-libc 1.0 // // D…