C语言入门4-函数和程序结构

news2025/1/17 5:54:16

函数举例

读取字符串,如果字符串中含有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语言在不同场景下具有更强的适应性和灵活性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows和linux路径斜杆转换脚本,打开即用

前言&#xff1a; windows和linux的目录路径斜杆是相反的&#xff0c;在ssh或者其他什么工具在win和ubuntu传文件时候经常需要用到两边的路径&#xff0c;有这个工具就不用手动去修改斜杆反斜杠了。之前有个在线网站&#xff0c;后来挂了&#xff0c;就想着自己搞一个脚本来用。…

批量重命名神器揭秘:一键实现文件夹随机命名,自定义长度轻松搞定!

在数字化时代&#xff0c;我们经常需要管理大量的文件夹&#xff0c;尤其是对于那些需要频繁更改或整理的文件来说&#xff0c;给它们进行批量重命名可以大大提高工作效率。然而&#xff0c;传统的重命名方法既繁琐又耗时&#xff0c;无法满足高效工作的需求。今天&#xff0c;…

【C++】初始化列表、匿名对象、static成员、友元、内部类

文章目录 一、初始化列表构造函数体赋值初始化列表explicit关键字 二、匿名对象三、static成员四、友元友元函数友元类 五、内部类六、练习题 一、初始化列表 构造函数体赋值 实际上&#xff0c;构造函数的函数体内&#xff0c;并不是对 对象 初始化的地方&#xff0c;而是对…

电子杂志制作的必备软件:轻松提升制作效率

​电子杂志作为一种新型的媒体形式&#xff0c;具有互动性强、内容丰富、传播范围广等特点。随着互联网的普及&#xff0c;越来越多的企业和个人开始关注和投入电子杂志的制作。然而&#xff0c;电子杂志的制作过程往往复杂繁琐&#xff0c;需要付出大量的时间和精力。为了提高…

微信小程序学习(六):常用原生 API

&#x1f517;API官方文档 1、网络请求 wx.request({// 接口地址&#xff0c;仅为示例&#xff0c;并非真实的接口地址url: example.php,// 请求的参数data: { x: },// 请求方式 GET|POST|PUT|DELETEmethod: GET,success (res) {console.log(res.data)},fail(err) {console.…

本地离线模型搭建指南-本地运行显卡选择

搭建一个本地中文大语言模型&#xff08;LLM&#xff09;涉及多个关键步骤&#xff0c;从选择模型底座&#xff0c;到运行机器和框架&#xff0c;再到具体的架构实现和训练方式。以下是一个详细的指南&#xff0c;帮助你从零开始构建和运行一个中文大语言模型。 本地离线模型搭…

05 - matlab m_map地学绘图工具基础函数 - 设置比例尺指北针

05 - matlab m_map地学绘图工具基础函数 - 设置比例尺指北针 0. 引言1. 关于m_scale2. 关于m_ruler3. 关于m_northarrow4. 结语 0. 引言 本篇介绍下m_map中添加指北针(m_northarrow)、比例尺(m_ruler)和进行比例缩放(m_scale)的函数及其用法 。 1. 关于m_scale m_scale用于图件…

LeetCode 算法:二叉树的中序遍历 c++

原题链接&#x1f517;&#xff1a;二叉树的中序遍历 难度&#xff1a;简单⭐️ 题目 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2] 示例 2&#xff1a; 输入&…

【Pandas驯化-14】一文搞懂Pandas中的时间处理函数date_range、resample、shift技巧

【Pandas驯化-14】一文搞懂Pandas中的时间处理函数date_range、resample、shift技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 相关…

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

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

华为od-C卷200分题目3 - 两个字符串间的最短路径问题

华为od-C卷200分题目3 - 两个字符串间的最短路径问题 题目描述 给定两个字符串&#xff0c;分别为字符串A与字符串B。 例如A字符串为ABCABBA&#xff0c;B字符串为CBABAC可以得到下图m*n的二维数组&#xff0c;定义原点为(0, 0)&#xff0c;终点为(m, n)&#xff0c;水平与垂…

【Android逆向】小白也能学会的一个小时破解某猫社区VIP会员

第二步&#xff1a;使用 dex2jar 将 classes.dex 转成 jar 文件 cmd到dex2jar文件夹目录&#xff0c;执行 d2j-dex2jar D://xxx/xxx/classes.dex 得到 jar 文件 静态分析 拿到源码后&#xff0c;首先我们需要找到应用的限制点&#xff0c;绕过App里面的判断。 然后分析源码&…

520. 检测大写字母

题目 我们定义&#xff0c;在以下情况时&#xff0c;单词的大写用法是正确的&#xff1a; 全部字母都是大写&#xff0c;比如 “USA” 。单词中所有字母都不是大写&#xff0c;比如 “leetcode” 。如果单词不只含有一个字母&#xff0c;只有首字母大写&#xff0c;比如 “Go…

React学习(二)——状态(数据)与状态修改

useState 在React中&#xff0c;useState 是一个非常重要的Hook&#xff0c;它允许你在函数组件中添加“状态”&#xff08;state&#xff09;。在传统的React类组件中&#xff0c;我们使用this.state来管理和更新组件的状态。然而&#xff0c;在函数组件中&#xff0c;由于它们…

【Docker】Docker下载安装_使用阿里云加速配置

1、下载安装 1.1前提条件 安装环境&#xff1a; 目前&#xff0c;CentOS 仅发行版本中的内核支持 Docker。Docker 运行在 CentOS 7 上&#xff0c;要求系统为64位、系统内核版本为 3.10 以上。Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上&#xff0c;要求系统为64位…

管理不到位,活该执行力差?狠抓这4点要素,强化执行力

管理不到位&#xff0c;活该执行力差&#xff1f;狠抓这4点要素&#xff0c;强化执行力 一&#xff1a;强化制度管理 1、权责分明&#xff0c;追责管理 要知道&#xff0c;规章制度其实就是一种“契约”。 在制定制度和规则的时候&#xff0c;民主一点&#xff0c;征求团队成员…

基于格网的边缘点检测(python)

1、背景介绍 前文已介绍对点云进行格网处理&#xff0c;可以计算平面点云面积、格网拓扑关系构建&#xff0c;相关博客如下&#xff1a; &#xff08;1&#xff09;点云格网过程可视化&#xff08;C PCL&#xff09;-CSDN博客 &#xff08;2&#xff09;平面点云格网过程及可…

一小时搞定Git(含盖IDEA使用)

文章目录 1. git基本概念1.1版本控制1.1.1 版本控制软件 2. 命令的使用2.1 Linux命令2.2 git基础指令2.2.1 设置用户2.2.2 初始化本地仓库2.2.3 查看本地仓库状态2.2.4 添加暂存区域2.2.5 提交本地库2.2.6 切换版本 2.3 分支操作2.3.1 分支基本操作2.3.2 合并操作2.3.4 分支开发…

C语言| 数组的插入

在下标为index的位置插入一个数字 1 定义数组a&#xff0c;数组b存放插入元素后的数组&#xff0c;下标index 值num 循环变量i 2 输入要插入的位置下标和数值 3 for循环 嵌套if多层语句 if数组的最大下标i < index,说明插入元素的位置在数组中不存在&#xff0c;系统随机分…

Python使用tkinter制作无边框透明时钟源码讲解(tkinter如何实现窗口无边框透明)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 导入必要的库📝 创建主窗口🎯 去掉窗口边框🎯 设置窗口透明度🎯 允许窗口背景透明🎯 设置窗口背景颜色为透明🎯 设置窗口位置🎯 创建用于显示时间的标签📝 更新时间函数📝 使窗口可移动📝…