字符串和字符串函数

C语言中, 所有字符串函数操作的对象都是指针指向的内存区域

字符串是以\0结尾的char类型数组

char name[] = {'f', 's', 's', '\0'};
char name[] = {'f', 's', 's'};  // 合法, 但缺少空字符作为结尾, 会引发错误
char *name = "hello world";
char name[] = "hello world";

字符串的输入输出

puts

只输出字符串, 在遇到空字符\0时停止输出, 并且自动输出换行符, 参数为字符串的地址

char *message = "hello world";  // 存储为: hello world\0
puts(message);  // 自动输出换行符
puts(message + 5 );  // 从第6个开始输出
puts(&message[5]);  // 从第6个开始输出

putchar

输出字符, 参数为需要输出的字符

putchar('\n');

gets

获取用户的输入, 可以用于读取整行输入, 当遇到换行符时, 停止读取, 并且将换行符丢弃, 存储剩余的字符

char *p = (char *)malloc(100 * sizeof(char));
gets(p);
puts(p);
// printf("%s\n", p);

但是从C11开始, 就从标准中抛弃了gets函数, 这是因为gets函数不安全, 它只接收一个参数, 也就是字符数组, 我们知道, 当数组作为参数传递时, 传递的是数组的首地址, 所以gets并不知道该数组的大小, 它会将所有输入的字符都存储到地址; 这样就会导致有可能访问到未被分配的地址, 甚至是其他程序所使用的地址, 这将导致程序段错误

call to 'gets' declared with attribute warning: 
Using gets() is always unsafe - use fgets() instead [-Wattribute-warning]gcc

fgets和fputs

fgets可以用于替代不安全的gets, 提供了第二个参数用于限制接收的最大字符数量, 第三个参数用于表示将从什么地方读入文件

与gets不同的是, fgets并不会丢弃字符串的最后一个换行符, 而是会将其存储在字符串内, 因此长度为字符数+1

fputs通常和fgets配套, 提供第二个参数用于表示写入文件到什么地方, 与puts不同的是并不会自动输出换行符

char *p = (char *)malloc(10 * sizeof(char));
fgets(p, 10, stdin);  // stdin标准输入, 从键盘读取输入
fputs(p, stdout);  // stdout标准输出, 用于在屏幕上输出

fgets返回一个指向char的指针, 正常情况下, 该函数返回的地址与传入的第一个参数相同, 如果遇到文件结尾, 将返回一个空指针null pointer(NULL)

while(fgets(p, 10, stdin) != NULL && p[0] != '\n')
{
    fputs(p, stdout);
}

fgets和fputs多用于对文件进行操作

gets_s

gets_s是C11新增的函数, 作为一个可选的扩展, 编译器不一定支持该函数. 该函数同样可以用于替代gets, 函数接收两个参数, 第二个参数用于限制接收的最大字符数量

当遇到换行符时, gets_s会和gets一样将其丢弃

如果读取到超过最大数量的字符时, 首先会将数组首字符设置为空字符\0, 读取并丢弃随后的输入, 直到读到换行符或文件的结尾, 然后返回一个空指针. 接着调用依赖实现的”处理函数”, 可能会中止或退出程序

char *p = (char *)malloc(10 * sizeof(char));
gets_s(p, 10);

scanf

返回正确读取参数的个数或者EOF(读取到文件的结尾), 典型用法是读取并转换混合数据类型为某种标准形式

字符串函数

strlen

包含在头文件string.h中, 用于统计字符串的长度

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

printf("%d\n", strlen(message));

strcat和strncat

strcat用于将两个字符串拼接在一起, 拼接后的结果将覆盖第一个字符串, 拼接后的长度将等于两个字符串长度之和

stecat接收两个字符串作为参数, 返回一个char *类型的值, 可以直接用于输出

#include <string.h>

char *p1 = (char *)malloc(10 *sizeof(char));
char *p2 = (char *)malloc(10 *sizeof(char));

fgets(p1, 10, stdin);  // 使用s_gets(p2, 10);
fgets(p2, 10, stdin);  // 使用s_gets(p2, 10);

strcat(p1, p2);
fputs(p1, stdout);
// fputs(strcat(p1, p2), stdout);

像上面的例子一样, 如果使用fgets来获取输入, 那么输出拼接后的字符串将仍然等于p1的值, 因为fgets会在输入的字符串后面保留换行符\n, 所以fputs输出时, 只会输出换行符之前的值, 也就是p1的值; 所以需要在拼接字符串时, 保证字符串中间没有换行符\n, 使用下面这个函数来将换行符替换为空字符\0

char *s_gets(char *str, int size)
{
    char *ret_val;
    int i = 0;

    ret_val = fgets(str, size, stdin);
    if(ret_val)
    {
        while(str[i] != '\n' && str[i] != '\0')
        {
            i++;
        }
        if(str[i] == '\n')
        {
            str[i] = '\0';
        }
        else  // 没有找到换行符, 表示输入字符超过了缓冲区大小
        {
            // 丢弃缓冲区内剩余的字符
            while(getchar() != '\n')
                continue;
        }
    }
    return ret_val;
}

strncat用于拼接两个字符串, 接受三个函数, 第三个函数用于指定最大添加的字符数, 表示从第二个字符串选取前几个字符拼接到第一个字符串末尾

strncat(p1, p2, 4);

strcmp和strncmp

strcmp用于将两个字符串进行比较, 接受两个参数, 表示两个需要进行比较的字符串; 如果相同, 返回0, 如果不同, 则返回非0

需要注意的是, strcmp比较的是字符串, 而不是数组, 所以可以用其来比较存储在不同大小数组中的字符串

fgets(p1, 10);
fgets(p2, 10);
printf("%d\n", strcmp(p1, p2));

strncmp用于比较两个字符串的前n个字符, 接受三个参数, 第三个参数表示要比较前几个参数

strncmp(p1, p2, 4);

strcpy和strncpy

strcpy将一个字符串拷贝到另一个字符串, 其返回值类型为char *, 即第一个字符的地址

strcpy(p1, p2);  // 将p2拷贝至p1指向的地址
strcpy(p1 + 3, p2);

strncpy可以指定最大拷贝多少个字符

strcpy(p1, p2, 5);

sprintf

函数包含在头文件stdio.h内, 用于把数据写入字符串, 可以把多个元素组合成一个字符串; 第一个参数为目标字符串的地址, 其余的参数与printf相同

char *formal = (char *)malloc(sizeof(10 * sizeof(char)));
char *message1 = "hello";
char *message2 = "world";
sprintf(formal, "%s, %s !", message1, message2);
fputs(formal, stdout);

其他字符串函数还有

strchar(const char *p, int c);  // p中是否含有c字符, 返回p, 否则NULL
strpbrk(const char *p1, const char p2);  // p1包含p2中任何字符, 返回p1, 否则NULL
strrchr(const char *s, int c);  // s中字符c最后出现的位置
strstr(const char *p1, const char *p2);  // p1中, p2首次出现的位置