MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C 语言字符串和格式化输入输出

2022-03-101.4k 阅读

C 语言字符串

字符串的定义与表示

在 C 语言中,字符串是一个以空字符 '\0' 结尾的字符数组。例如,我们可以这样定义一个字符串:

char str1[] = "Hello, World!";

这里,str1 是一个字符数组,数组的大小会根据字符串常量的长度自动确定,并且编译器会自动在字符串末尾添加 '\0'。所以,str1 实际上占用 14 个字节(12 个字符加上 '\0' 和可能的填充字节,具体取决于系统)。

我们也可以先定义字符数组,然后再赋值:

char str2[20];
strcpy(str2, "Hello");

这里,str2 被定义为一个长度为 20 的字符数组,strcpy 函数用于将字符串 "Hello" 复制到 str2 中。注意,str2 的长度必须足够容纳要复制的字符串及其结束符 '\0',否则会导致缓冲区溢出,这是一种严重的安全漏洞。

字符串的存储与内存布局

当我们定义一个字符串时,它在内存中的存储方式如下:

char str[] = "test";

在内存中,str 数组的布局为:

内存地址存储内容
&str[0]'t'
&str[1]'e'
&str[2]'s'
&str[3]'t'
&str[4]'\0'

字符串常量通常存储在只读数据段中,例如:

const char *ptr = "constant string";

这里,"constant string" 存储在只读数据段,ptr 是一个指向该字符串的指针。试图通过 ptr 修改字符串内容会导致未定义行为。

字符串操作函数

  1. 字符串复制函数 strcpy

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char source[] = "Hello";
        char destination[20];
        strcpy(destination, source);
        printf("Copied string: %s\n", destination);
        return 0;
    }
    

    strcpy 函数将源字符串 source 复制到目标字符串 destination 中,包括结束符 '\0'。它会覆盖目标字符串中已有的内容,并且不会检查目标缓冲区是否足够大,因此使用时要小心。

  2. 字符串连接函数 strcat

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char str1[20] = "Hello";
        char str2[] = ", World!";
        strcat(str1, str2);
        printf("Concatenated string: %s\n", str1);
        return 0;
    }
    

    strcat 函数将 str2 连接到 str1 的末尾,并且 str1 必须有足够的空间来容纳连接后的字符串。连接时,str1 末尾的 '\0' 会被 str2 的第一个字符覆盖,然后 str2 的结束符 '\0' 会被添加到连接后的字符串末尾。

  3. 字符串比较函数 strcmp

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char str1[] = "apple";
        char str2[] = "banana";
        int result = strcmp(str1, str2);
        if (result < 0) {
            printf("%s is less than %s\n", str1, str2);
        } else if (result > 0) {
            printf("%s is greater than %s\n", str1, str2);
        } else {
            printf("%s is equal to %s\n", str1, str2);
        }
        return 0;
    }
    

    strcmp 函数按字典序比较两个字符串。如果 str1 小于 str2,返回一个负整数;如果 str1 大于 str2,返回一个正整数;如果两个字符串相等,返回 0。比较是基于字符的 ASCII 值进行的。

  4. 字符串长度计算函数 strlen

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char str[] = "Hello, World!";
        size_t length = strlen(str);
        printf("Length of the string: %zu\n", length);
        return 0;
    }
    

    strlen 函数返回字符串的长度,不包括结束符 '\0'。它从字符串的起始位置开始计数,直到遇到 '\0' 为止。

字符串的遍历与操作

遍历字符串是对字符串进行各种操作的基础。我们可以使用循环来遍历字符串中的每个字符。例如,统计字符串中字母 a 的个数:

#include <stdio.h>

int main() {
    char str[] = "banana";
    int count = 0;
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] == 'a') {
            count++;
        }
    }
    printf("Number of 'a' in the string: %d\n", count);
    return 0;
}

在这个例子中,我们使用 for 循环遍历字符串 str,通过判断当前字符是否为 'a' 来进行计数。

字符串与指针

在 C 语言中,字符串与指针密切相关。我们可以使用字符指针来操作字符串。例如:

#include <stdio.h>

int main() {
    const char *ptr = "Hello";
    printf("String: %s\n", ptr);
    return 0;
}

这里,ptr 是一个指向字符串常量 "Hello" 的指针。我们可以通过指针来访问字符串中的字符,例如:

#include <stdio.h>

int main() {
    const char *ptr = "Hello";
    printf("First character: %c\n", *ptr);
    return 0;
}

*ptr 表示指针 ptr 所指向的字符,即字符串的第一个字符 'H'

我们还可以通过指针运算来遍历字符串:

#include <stdio.h>

int main() {
    const char *ptr = "Hello";
    while (*ptr != '\0') {
        printf("%c ", *ptr);
        ptr++;
    }
    printf("\n");
    return 0;
}

在这个例子中,通过不断递增指针 ptr,我们遍历了整个字符串并逐个输出字符。

动态分配内存的字符串

有时候,我们无法在编译时确定字符串的长度,这时就需要动态分配内存。例如,从用户输入获取一个字符串并存储:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *str;
    int size = 100;
    str = (char *)malloc(size * sizeof(char));
    if (str == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    printf("Enter a string: ");
    fgets(str, size, stdin);
    // fgets 会读取换行符,我们可以将其替换为 '\0'
    str[strcspn(str, "\n")] = '\0';
    printf("You entered: %s\n", str);
    free(str);
    return 0;
}

在这个例子中,我们使用 malloc 函数动态分配了 size 个字节的内存来存储字符串。fgets 函数用于从标准输入读取字符串,它会读取最多 size - 1 个字符,以防止缓冲区溢出。最后,我们使用 free 函数释放分配的内存,避免内存泄漏。

C 语言格式化输入输出

格式化输出函数 printf

printf 是 C 语言中最常用的格式化输出函数,它用于将格式化的数据输出到标准输出(通常是控制台)。其基本语法为:

int printf(const char *format, ...);

format 是一个字符串,包含普通字符和格式说明符。格式说明符以 % 开头,用于指定要输出的数据类型和格式。例如:

#include <stdio.h>

int main() {
    int num = 10;
    float fnum = 3.14;
    char ch = 'A';
    printf("Integer: %d\n", num);
    printf("Float: %f\n", fnum);
    printf("Character: %c\n", ch);
    return 0;
}

在这个例子中,%d 用于输出整数,%f 用于输出浮点数,%c 用于输出字符。

常用格式说明符

  1. 整数格式说明符
    • %d:以十进制形式输出有符号整数。
    • %u:以十进制形式输出无符号整数。
    • %o:以八进制形式输出无符号整数。
    • %x%X:以十六进制形式输出无符号整数,%x 使用小写字母,%X 使用大写字母。
    #include <stdio.h>
    
    int main() {
        int num = 255;
        printf("Decimal: %d\n", num);
        printf("Unsigned decimal: %u\n", num);
        printf("Octal: %o\n", num);
        printf("Hexadecimal (lowercase): %x\n", num);
        printf("Hexadecimal (uppercase): %X\n", num);
        return 0;
    }
    
  2. 浮点数格式说明符
    • %f:以十进制小数形式输出浮点数,默认保留 6 位小数。
    • %e%E:以科学计数法形式输出浮点数,%e 使用小写 e%E 使用大写 E
    • %g%G:根据数值的大小自动选择 %f%e 格式,以较短的表示形式输出。
    #include <stdio.h>
    
    int main() {
        float fnum = 12345.6789;
        printf("Decimal float: %f\n", fnum);
        printf("Scientific notation (lowercase): %e\n", fnum);
        printf("Scientific notation (uppercase): %E\n", fnum);
        printf("Auto - selected format: %g\n", fnum);
        return 0;
    }
    
  3. 字符和字符串格式说明符
    • %c:输出单个字符。
    • %s:输出字符串。
    #include <stdio.h>
    
    int main() {
        char ch = 'B';
        char str[] = "Hello";
        printf("Character: %c\n", ch);
        printf("String: %s\n", str);
        return 0;
    }
    

格式修饰符

除了基本的格式说明符,printf 还支持一些格式修饰符,用于更精细地控制输出格式。

  1. 宽度修饰符:可以指定输出字段的最小宽度。例如,%5d 表示输出的整数至少占用 5 个字符的宽度,如果整数本身不足 5 位,会在前面填充空格。
    #include <stdio.h>
    
    int main() {
        int num = 12;
        printf("Width 5: |%5d|\n", num);
        return 0;
    }
    
  2. 精度修饰符:对于浮点数,用于指定小数点后的位数;对于字符串,用于指定输出的最大字符数。例如,%.2f 表示输出浮点数时保留 2 位小数,%.5s 表示输出字符串时最多输出 5 个字符。
    #include <stdio.h>
    
    int main() {
        float fnum = 3.14159;
        char str[] = "Hello, World!";
        printf("Float with 2 decimal places: %.2f\n", fnum);
        printf("First 5 characters of string: %.5s\n", str);
        return 0;
    }
    
  3. 左对齐修饰符 -:默认情况下,输出是右对齐的。使用 - 修饰符可以使输出左对齐。例如,%-5d 表示输出的整数左对齐,占用 5 个字符的宽度。
    #include <stdio.h>
    
    int main() {
        int num = 12;
        printf("Left - aligned width 5: |%-5d|\n", num);
        return 0;
    }
    

格式化输入函数 scanf

scanf 用于从标准输入读取格式化的数据。其基本语法为:

int scanf(const char *format, ...);

format 字符串包含格式说明符,用于指定要读取的数据类型。与 printf 类似,scanf 的格式说明符以 % 开头。例如:

#include <stdio.h>

int main() {
    int num;
    float fnum;
    char ch;
    printf("Enter an integer, a float, and a character: ");
    scanf("%d %f %c", &num, &fnum, &ch);
    printf("You entered: %d, %f, %c\n", num, fnum, ch);
    return 0;
}

在这个例子中,scanf 从标准输入读取一个整数、一个浮点数和一个字符,并分别存储到变量 numfnumch 中。注意,在 scanf 中,变量名前需要加上 & 运算符,以获取变量的地址,这样 scanf 才能将读取的数据存储到正确的位置。

常见格式说明符与 scanf 的使用

  1. 整数输入%d 用于读取有符号整数,%u 用于读取无符号整数。
    #include <stdio.h>
    
    int main() {
        int signed_num;
        unsigned int unsigned_num;
        printf("Enter a signed integer and an unsigned integer: ");
        scanf("%d %u", &signed_num, &unsigned_num);
        printf("Signed integer: %d, Unsigned integer: %u\n", signed_num, unsigned_num);
        return 0;
    }
    
  2. 浮点数输入%f 用于读取单精度浮点数,%lf 用于读取双精度浮点数。
    #include <stdio.h>
    
    int main() {
        float fnum;
        double dnum;
        printf("Enter a float and a double: ");
        scanf("%f %lf", &fnum, &dnum);
        printf("Float: %f, Double: %lf\n", fnum, dnum);
        return 0;
    }
    
  3. 字符和字符串输入%c 用于读取单个字符,%s 用于读取字符串。读取字符串时,scanf 会在遇到空白字符(空格、制表符、换行符等)时停止读取。
    #include <stdio.h>
    
    int main() {
        char ch;
        char str[20];
        printf("Enter a character and a string: ");
        scanf(" %c %s", &ch, str);
        // 在 %c 前加一个空格,以跳过前面的空白字符
        printf("Character: %c, String: %s\n", ch, str);
        return 0;
    }
    

scanf 的注意事项

  1. 空白字符处理scanf 在读取输入时,会自动跳过开头的空白字符(空格、制表符、换行符等),除了 %c 格式说明符。如果要读取包含空白字符的字符串,可以使用 fgets 函数代替 scanf
  2. 缓冲区溢出:当使用 %s 读取字符串时,scanf 不会检查目标数组是否足够大,容易导致缓冲区溢出。例如:
    #include <stdio.h>
    
    int main() {
        char str[5];
        printf("Enter a string: ");
        scanf("%s", str);
        // 如果输入超过 4 个字符(不包括 '\0'),会导致缓冲区溢出
        printf("You entered: %s\n", str);
        return 0;
    }
    
    为了避免缓冲区溢出,可以使用 fgets 并对输入进行处理,或者使用 scanf 的宽度限定修饰符,如 %4s 表示最多读取 4 个字符到 str 中。

其他输入输出函数

  1. 字符输入输出函数 getcharputchar
    • getchar 用于从标准输入读取一个字符,它返回读取的字符的 ASCII 值。例如:
    #include <stdio.h>
    
    int main() {
        char ch;
        printf("Enter a character: ");
        ch = getchar();
        printf("You entered: %c\n", ch);
        return 0;
    }
    
    • putchar 用于将一个字符输出到标准输出。例如:
    #include <stdio.h>
    
    int main() {
        char ch = 'A';
        putchar(ch);
        putchar('\n');
        return 0;
    }
    
  2. 字符串输入输出函数 getsputs
    • gets 用于从标准输入读取一行字符串,直到遇到换行符为止,并将换行符替换为 '\0'。但是,gets 存在缓冲区溢出风险,已被 C11 标准弃用。
    • puts 用于将字符串输出到标准输出,并在末尾自动添加换行符。例如:
    #include <stdio.h>
    
    int main() {
        char str[20];
        printf("Enter a string: ");
        // gets(str); // 不推荐使用
        fgets(str, 20, stdin);
        str[strcspn(str, "\n")] = '\0';
        puts(str);
        return 0;
    }
    
  3. 文件输入输出函数 fprintffscanf
    • fprintf 用于将格式化的数据输出到文件。其语法为:
    int fprintf(FILE *stream, const char *format, ...);
    
    例如,将数据写入文件:
    #include <stdio.h>
    
    int main() {
        FILE *file = fopen("output.txt", "w");
        if (file == NULL) {
            printf("Failed to open file\n");
            return 1;
        }
        int num = 10;
        float fnum = 3.14;
        fprintf(file, "Integer: %d, Float: %f\n", num, fnum);
        fclose(file);
        return 0;
    }
    
    • fscanf 用于从文件读取格式化的数据。其语法为:
    int fscanf(FILE *stream, const char *format, ...);
    
    例如,从文件读取数据:
    #include <stdio.h>
    
    int main() {
        FILE *file = fopen("input.txt", "r");
        if (file == NULL) {
            printf("Failed to open file\n");
            return 1;
        }
        int num;
        float fnum;
        fscanf(file, "Integer: %d, Float: %f", &num, &fnum);
        printf("Read from file: %d, %f\n", num, fnum);
        fclose(file);
        return 0;
    }
    

通过对 C 语言字符串和格式化输入输出的深入了解,开发者能够更好地处理文本数据的输入输出、存储和操作,这对于编写各种类型的 C 语言程序,无论是简单的控制台应用还是复杂的系统软件,都是至关重要的基础技能。在实际编程中,要注意字符串操作的安全性,避免缓冲区溢出等问题,同时合理使用格式化输入输出函数,以满足不同的需求。