Skip to content

字符串

字符串是程序中处理文本数据的基本单位,在C语言中,字符串是由字符组成的数组,以空字符'\0'作为结束标志。在本章节中,我们将学习C语言中字符串的定义、使用和操作方法。

1. 什么是字符串?

在C语言中,字符串是由一系列字符组成的数组,以空字符'\0')作为结束标志。空字符是一个特殊的字符,其ASCII值为0,用于标记字符串的结束。

例如,字符串"Hello"在内存中的表示为:

H e l l o \0

注意,虽然我们看到的是5个字符,但在内存中实际占用了6个字节(包括结束标志'\0')。

2. 字符串的定义和初始化

2.1 使用字符数组定义字符串

c
// 方法1:指定数组大小
char str1[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };

// 方法2:不指定数组大小,由编译器自动计算
char str2[] = { 'H', 'e', 'l', 'l', 'o', '\0' };

// 方法3:使用字符串字面量(最常用)
char str3[] = "Hello";

2.2 示例

c
#include <stdio.h>

int main() {
    // 使用字符数组定义字符串
    char str1[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };
    char str2[] = { 'W', 'o', 'r', 'l', 'd', '\0' };
    char str3[] = "Hello, World!";
    
    // 打印字符串
    printf("str1: %s\n", str1);  // Hello
    printf("str2: %s\n", str2);  // World
    printf("str3: %s\n", str3);  // Hello, World!
    
    return 0;
}

2.3 注意事项

  • 当使用字符串字面量初始化字符数组时,编译器会自动在末尾添加'\0'
  • 当使用字符列表初始化字符数组时,需要手动添加'\0'
  • 数组大小应至少为字符串长度加1(用于存放结束标志)

3. 字符串的输入和输出

3.1 使用printf输出字符串

c
printf("%s", str);

3.2 使用scanf输入字符串

c
scanf("%s", str);

注意scanf会在遇到空格、制表符或换行符时停止读取,因此无法读取包含空格的字符串。

3.3 使用gets输入字符串(不推荐)

c
gets(str);

注意gets函数不会检查数组边界,可能导致缓冲区溢出,因此不推荐使用。

3.4 使用fgets输入字符串(推荐)

c
fgets(str, size, stdin);
  • str:字符数组
  • size:数组大小
  • stdin:标准输入流

注意fgets会读取换行符并将其存储在字符串中。

3.5 示例

c
#include <stdio.h>

int main() {
    char str1[50];
    char str2[50];
    
    printf("请输入一个单词: ");
    scanf("%s", str1);  // 无法读取包含空格的字符串
    printf("你输入的单词: %s\n", str1);
    
    // 清除输入缓冲区的换行符
    while (getchar() != '\n');
    
    printf("请输入一个句子: ");
    fgets(str2, 50, stdin);  // 可以读取包含空格的字符串
    printf("你输入的句子: %s\n", str2);
    
    return 0;
}

4. 字符串处理函数

C语言标准库提供了丰富的字符串处理函数,这些函数定义在头文件<string.h>中。

4.1 字符串长度函数:strlen

c
#include <string.h>

size_t strlen(const char *str);
  • 功能:计算字符串的长度(不包括结束标志'\0'
  • 参数str - 字符串
  • 返回值:字符串的长度

4.2 字符串复制函数:strcpy, strncpy

c
#include <string.h>

char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
  • 功能:将源字符串复制到目标字符串
  • 参数
    • dest - 目标字符串
    • src - 源字符串
    • n - 复制的最大字符数
  • 返回值:目标字符串的地址

注意strcpy会一直复制直到遇到源字符串的'\0',可能导致缓冲区溢出。strncpy则可以指定复制的最大字符数,更加安全。

4.3 字符串连接函数:strcat, strncat

c
#include <string.h>

char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n);
  • 功能:将源字符串连接到目标字符串的末尾
  • 参数
    • dest - 目标字符串
    • src - 源字符串
    • n - 连接的最大字符数
  • 返回值:目标字符串的地址

注意strcat会一直连接直到遇到源字符串的'\0',可能导致缓冲区溢出。strncat则可以指定连接的最大字符数,更加安全。

4.4 字符串比较函数:strcmp, strncmp

c
#include <string.h>

int strcmp(const char *str1, const char *str2);
int strncmp(const char *str1, const char *str2, size_t n);
  • 功能:比较两个字符串
  • 参数
    • str1 - 第一个字符串
    • str2 - 第二个字符串
    • n - 比较的最大字符数
  • 返回值
    • 小于0:str1小于str2
    • 等于0:str1等于str2
    • 大于0:str1大于str2

4.5 字符串查找函数:strchr, strrchr, strstr

c
#include <string.h>

char *strchr(const char *str, int c);
char *strrchr(const char *str, int c);
char *strstr(const char *haystack, const char *needle);
  • 功能
    • strchr:在字符串中查找指定字符的第一次出现
    • strrchr:在字符串中查找指定字符的最后一次出现
    • strstr:在字符串中查找指定子字符串的第一次出现
  • 参数
    • str/haystack - 要查找的字符串
    • c - 要查找的字符
    • needle - 要查找的子字符串
  • 返回值
    • 找到:返回指向找到位置的指针
    • 未找到:返回NULL

4.6 示例

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

int main() {
    char str1[] = "Hello";
    char str2[] = "World";
    char str3[20];
    
    // 字符串长度
    printf("str1长度: %zu\n", strlen(str1));  // 5
    
    // 字符串复制
    strcpy(str3, str1);
    printf("str3: %s\n", str3);  // Hello
    
    // 字符串连接
    strcat(str3, " ");
    strcat(str3, str2);
    printf("str3: %s\n", str3);  // Hello World
    
    // 字符串比较
    int result = strcmp(str1, str2);
    if (result < 0) {
        printf("str1 < str2\n");
    } else if (result > 0) {
        printf("str1 > str2\n");
    } else {
        printf("str1 == str2\n");
    }
    
    // 字符串查找
    char *pos = strchr(str3, 'W');
    if (pos != NULL) {
        printf("找到 'W' 在位置: %d\n", pos - str3);  // 6
    }
    
    pos = strstr(str3, "World");
    if (pos != NULL) {
        printf("找到 'World' 在位置: %d\n", pos - str3);  // 6
    }
    
    return 0;
}

5. 字符串的遍历和修改

5.1 遍历字符串

c
#include <stdio.h>

int main() {
    char str[] = "Hello";
    int i = 0;
    
    // 方法1:使用索引
    printf("方法1(使用索引): ");
    while (str[i] != '\0') {
        printf("%c ", str[i]);
        i++;
    }
    printf("\n");
    
    // 方法2:使用指针
    printf("方法2(使用指针): ");
    char *p = str;
    while (*p != '\0') {
        printf("%c ", *p);
        p++;
    }
    printf("\n");
    
    return 0;
}

5.2 修改字符串

c
#include <stdio.h>

int main() {
    char str[] = "Hello";
    
    // 修改单个字符
    str[0] = 'h';
    printf("修改后: %s\n", str);  // hello
    
    // 遍历修改字符串
    char *p = str;
    while (*p != '\0') {
        if (*p >= 'a' && *p <= 'z') {
            *p = *p - 'a' + 'A';  // 转换为大写
        }
        p++;
    }
    printf("转换为大写: %s\n", str);  // HELLO
    
    return 0;
}

注意:字符串字面量是不可修改的,尝试修改会导致未定义行为。

c
// 错误:尝试修改字符串字面量
char *str = "Hello";
str[0] = 'h';  // 未定义行为

6. 字符串的常见操作

6.1 字符串反转

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

void reverse_string(char *str) {
    int length = strlen(str);
    int start = 0;
    int end = length - 1;
    
    while (start < end) {
        // 交换字符
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        
        start++;
        end--;
    }
}

int main() {
    char str[] = "Hello, World!";
    printf("原始字符串: %s\n", str);  // Hello, World!
    
    reverse_string(str);
    printf("反转后: %s\n", str);  // !dlroW ,olleH
    
    return 0;
}

6.2 字符串转换为整数

c
#include <stdio.h>

int string_to_int(const char *str) {
    int result = 0;
    int sign = 1;
    int i = 0;
    
    // 处理负号
    if (str[0] == '-') {
        sign = -1;
        i = 1;
    }
    
    // 转换数字
    while (str[i] != '\0') {
        if (str[i] >= '0' && str[i] <= '9') {
            result = result * 10 + (str[i] - '0');
            i++;
        } else {
            break;  // 遇到非数字字符停止
        }
    }
    
    return result * sign;
}

int main() {
    char str1[] = "12345";
    char str2[] = "-6789";
    
    int num1 = string_to_int(str1);
    int num2 = string_to_int(str2);
    
    printf("str1转换为整数: %d\n", num1);  // 12345
    printf("str2转换为整数: %d\n", num2);  // -6789
    
    return 0;
}

6.3 整数转换为字符串

c
#include <stdio.h>

void int_to_string(int num, char *str) {
    int i = 0;
    int is_negative = 0;
    
    // 处理0的情况
    if (num == 0) {
        str[0] = '0';
        str[1] = '\0';
        return;
    }
    
    // 处理负数
    if (num < 0) {
        is_negative = 1;
        num = -num;
    }
    
    // 转换数字
    while (num > 0) {
        str[i] = num % 10 + '0';
        num = num / 10;
        i++;
    }
    
    // 添加负号
    if (is_negative) {
        str[i] = '-';
        i++;
    }
    
    // 添加结束标志
    str[i] = '\0';
    
    // 反转字符串
    int start = 0;
    int end = i - 1;
    while (start < end) {
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        start++;
        end--;
    }
}

int main() {
    char str[20];
    
    int_to_string(12345, str);
    printf("12345转换为字符串: %s\n", str);  // 12345
    
    int_to_string(-6789, str);
    printf("-6789转换为字符串: %s\n", str);  // -6789
    
    int_to_string(0, str);
    printf("0转换为字符串: %s\n", str);  // 0
    
    return 0;
}

7. 字符串和指针

7.1 字符串指针

c
// 字符串指针
char *str = "Hello";

这里的str是一个指向字符串字面量的指针,字符串字面量存储在只读内存中,不能修改。

7.2 字符数组和字符串指针的区别

特性字符数组字符串指针
内存分配在栈上分配内存指向常量区的字符串字面量
修改可以修改内容不能修改指向的字符串字面量
初始化可以使用字符列表或字符串字面量只能使用字符串字面量
大小可以使用sizeof获取数组大小sizeof返回指针大小(通常为4或8字节)

7.3 示例

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

int main() {
    // 字符数组
    char arr[] = "Hello";
    printf("arr: %s\n", arr);  // Hello
    printf("sizeof(arr): %zu\n", sizeof(arr));  // 6
    
    // 修改字符数组
    arr[0] = 'h';
    printf("修改后arr: %s\n", arr);  // hello
    
    // 字符串指针
    char *ptr = "World";
    printf("ptr: %s\n", ptr);  // World
    printf("sizeof(ptr): %zu\n", sizeof(ptr));  // 4或8
    
    // 注意:不能修改字符串字面量
    // ptr[0] = 'w';  // 未定义行为
    
    // 指针可以指向不同的字符串
    ptr = "Hello";
    printf("ptr现在指向: %s\n", ptr);  // Hello
    
    return 0;
}

8. 动态内存分配与字符串

8.1 使用malloc分配内存

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

int main() {
    // 分配内存
    char *str = (char *)malloc(100 * sizeof(char));
    if (str == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    // 复制字符串
    strcpy(str, "Hello, Dynamic Memory!");
    printf("str: %s\n", str);  // Hello, Dynamic Memory!
    
    // 重新分配内存
    str = (char *)realloc(str, 200 * sizeof(char));
    if (str == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    // 连接字符串
    strcat(str, " This is more text.");
    printf("str: %s\n", str);  // Hello, Dynamic Memory! This is more text.
    
    // 释放内存
    free(str);
    
    return 0;
}

8.2 使用strdup复制字符串

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

int main() {
    char *original = "Hello, World!";
    
    // 使用strdup复制字符串(自动分配内存)
    char *copy = strdup(original);
    if (copy == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    printf("original: %s\n", original);  // Hello, World!
    printf("copy: %s\n", copy);  // Hello, World!
    
    // 修改复制的字符串
    copy[0] = 'h';
    printf("修改后copy: %s\n", copy);  // hello, World!
    printf("original不变: %s\n", original);  // Hello, World!
    
    // 释放内存
    free(copy);
    
    return 0;
}

9. 字符串的注意事项

  1. 结束标志:字符串必须以'\0'结束,否则可能导致未定义行为
  2. 缓冲区溢出:使用字符串处理函数时,要确保目标缓冲区足够大,避免缓冲区溢出
  3. 字符串字面量:字符串字面量是不可修改的,尝试修改会导致未定义行为
  4. 内存管理:使用动态内存分配时,记得释放内存,避免内存泄漏
  5. 输入处理:使用fgets代替gets,避免缓冲区溢出
  6. 字符串比较:使用strcmp函数比较字符串,而不是使用==运算符
  7. 数组大小:定义字符数组时,要预留足够的空间存放结束标志'\0'

10. 示例:综合运用

让我们看一个综合运用字符串的例子:

c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

// 字符串处理函数
void to_upper(char *str) {
    while (*str != '\0') {
        *str = toupper(*str);
        str++;
    }
}

void to_lower(char *str) {
    while (*str != '\0') {
        *str = tolower(*str);
        str++;
    }
}

int count_vowels(const char *str) {
    int count = 0;
    while (*str != '\0') {
        char c = tolower(*str);
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
            count++;
        }
        str++;
    }
    return count;
}

int main() {
    char str[100];
    
    // 输入字符串
    printf("请输入一个字符串: ");
    fgets(str, 100, stdin);
    
    // 移除末尾的换行符
    size_t len = strlen(str);
    if (len > 0 && str[len - 1] == '\n') {
        str[len - 1] = '\0';
    }
    
    // 打印原始字符串
    printf("原始字符串: %s\n", str);
    
    // 转换为大写
    char upper_str[100];
    strcpy(upper_str, str);
    to_upper(upper_str);
    printf("大写: %s\n", upper_str);
    
    // 转换为小写
    char lower_str[100];
    strcpy(lower_str, str);
    to_lower(lower_str);
    printf("小写: %s\n", lower_str);
    
    // 计算长度
    printf("长度: %zu\n", strlen(str));
    
    // 计算元音字母数量
    int vowels = count_vowels(str);
    printf("元音字母数量: %d\n", vowels);
    
    return 0;
}

11. 小结

字符串是C语言中处理文本数据的基本单位,掌握字符串的处理技巧对于编写实用的C程序至关重要。在本章节中,我们学习了:

  1. 字符串的定义:C语言中的字符串是由字符组成的数组,以'\0'作为结束标志
  2. 字符串的初始化:可以使用字符列表或字符串字面量初始化字符串
  3. 字符串的输入输出:使用printf输出字符串,使用scanffgets输入字符串
  4. 字符串处理函数:C标准库提供了丰富的字符串处理函数,如strlenstrcpystrcatstrcmp
  5. 字符串的遍历和修改:可以使用索引或指针遍历和修改字符串
  6. 字符串和指针:字符数组和字符串指针有不同的特性和用法
  7. 动态内存分配:可以使用mallocfree管理字符串的内存
  8. 常见操作:字符串反转、转换等常见操作的实现

通过合理使用字符串处理函数和技巧,可以编写出更加灵活、高效的C程序。在使用字符串时,要注意结束标志、缓冲区溢出、内存管理等问题,确保程序的安全性和可靠性。