puts() 函数详解
函数概述
puts() 是C语言标准库中用于输出字符串的函数,其功能比简单的字符串输出更为完善,因为它会在输出字符串后自动添加换行符。
头文件 :#include <stdio.h>
函数原型:
c
int puts(const char *str);
参数说明:
str:指向要输出的字符串的指针。可以是字符数组的首地址,也可以是字符串常量的首地址。
返回值:
- 成功时返回非负整数(通常是最后输出的字符的索引值加一)
- 失败时返回EOF(通常是-1)
函数功能详解
puts()函数从参数str指定的地址开始,逐个字符输出直到遇到字符串结束符'\0'为止。输出完成后,它会自动输出一个换行符(\n),这是与printf("%s")的主要区别之一。
使用示例
c
#include <stdio.h>
int main(void) {
// 示例1:输出字符数组
char str1[] = "Hello, World!";
puts(str1); // 输出:Hello, World!(自动换行)
// 示例2:直接输出字符串常量
puts("Welcome to C Programming!"); // 输出:Welcome to C Programming!(自动换行)
// 示例3:比较puts和printf
char str2[] = "Comparison";
printf("%s", str2); // 不会自动换行
puts(str2); // 会自动换行
return 0;
}
注意事项
- puts()函数只能输出字符串,不能像printf()那样格式化输出
- 参数必须是有效的字符串(以
'\0'结尾) - 如果传入空指针(NULL),可能导致程序崩溃
- 自动添加换行符的特性使得在连续输出多行文本时代码更简洁
内部实现原理(简析)
puts()函数大致相当于以下代码:
c
int my_puts(const char *str) {
while (*str != '\0') {
putchar(*str); // 输出当前字符
str++; // 移动到下一个字符
}
putchar('\n'); // 输出换行符
return 0;
}
gets() 函数详解
函数概述
gets() 函数用于从标准输入(通常是键盘)读取一行字符串。由于安全漏洞,在现代C语言编程中已被弃用,但了解其原理仍有教育意义。
头文件 :#include <stdio.h>
函数原型:
c
char *gets(char *str);
参数说明:
str:指向存储输入字符串的字符数组的指针
返回值:
- 成功时返回
str(输入的字符串) - 失败或遇到文件结束符(EOF)时返回NULL
函数功能详解
gets()函数从标准输入读取字符,直到遇到换行符(\n)或文件结束符(EOF)。读取的换行符会被转换为字符串结束符'\0',然后存储到str指向的内存中。
使用示例(历史用法)
c
#include <stdio.h>
int main(void) {
char name[50];
printf("Please enter your name: ");
gets(name); // 危险:可能导致缓冲区溢出
printf("Hello, ");
puts(name);
return 0;
}
安全问题与替代方案
安全问题 :
gets()函数最大的问题是无法限制输入的长度,如果用户输入的字符串长度超过目标数组的大小,会导致缓冲区溢出,这是严重的安全漏洞。
安全替代方案:
-
fgets()函数(推荐):
c#include <stdio.h> int main(void) { char name[50]; printf("Please enter your name: "); fgets(name, sizeof(name), stdin); // 安全:指定最大读取长度 // 去除可能的换行符 size_t len = strlen(name); if (len > 0 && name[len-1] == '\n') { name[len-1] = '\0'; } printf("Hello, %s\n", name); return 0; } -
scanf()函数带宽度限制:
cscanf("%49s", name); // 限制最多读取49个字符
为什么gets()被废弃
- 无法防止缓冲区溢出攻击
- C11标准已将其从标准库中移除
- 现代编译器通常会给出警告或错误
strlen() 函数详解
函数概述
strlen() 函数用于计算字符串的长度(不包括结尾的'\0')。
头文件 :#include <string.h>
函数原型:
c
size_t strlen(const char *str);
参数说明:
str:指向要计算长度的字符串的指针
返回值:
- 返回字符串的长度(
size_t类型,是无符号整数)
函数功能详解
strlen()函数从str指向的地址开始,逐个字符计数,直到遇到字符串结束符'\0'为止。计数结果不包括'\0'本身。
使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
// 示例1:基本使用
char str1[] = "Hello";
printf("Length of '%s' is %zu\n", str1, strlen(str1)); // 输出:5
// 示例2:空字符串
char str2[] = "";
printf("Length of empty string is %zu\n", strlen(str2)); // 输出:0
// 示例3:包含空格
char str3[] = "Hello World";
printf("Length of '%s' is %zu\n", str3, strlen(str3)); // 输出:11
// 示例4:中文(注意:一个中文占多个字节)
char str4[] = "你好";
printf("Length of '%s' is %zu\n", str4, strlen(str4)); // 输出:6(UTF-8编码)
return 0;
}
注意事项
- strlen()计算的是字符数(字节数),不是显示长度(对于多字节字符)
- 参数必须是以
'\0'结尾的有效字符串 - 时间复杂度是O(n),需要遍历整个字符串
- 在循环中重复调用strlen()效率低下
性能优化技巧
c
// 低效写法
for (int i = 0; i < strlen(str); i++) {
// 每次循环都调用strlen(),时间复杂度O(n²)
}
// 高效写法
size_t len = strlen(str); // 只计算一次
for (size_t i = 0; i < len; i++) {
// 时间复杂度O(n)
}
手动实现strlen()
c
size_t my_strlen(const char *str) {
size_t length = 0;
while (*str != '\0') {
length++;
str++;
}
return length;
}
strcpy() 函数
函数概述
strcpy() 用于将一个字符串复制到另一个字符串。
头文件 :#include <string.h>
函数原型:
c
char *strcpy(char *dest, const char *src);
参数说明:
dest:目标字符串地址src:源字符串地址
返回值 :返回dest(目标字符串的地址)
使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello, World!";
char dest[20];
strcpy(dest, src);
printf("Source: %s\n", src); // 输出:Hello, World!
printf("Destination: %s\n", dest); // 输出:Hello, World!
// 验证复制效果
printf("strlen(dest) = %zu\n", strlen(dest)); // 输出:13
return 0;
}
安全注意事项
c
#include <stdio.h>
#include <string.h>
int main(void) {
// 危险示例:缓冲区溢出
char small_buffer[5];
char long_string[] = "This is a very long string";
// 以下操作会导致缓冲区溢出
// strcpy(small_buffer, long_string); // 不要这样做!
// 安全做法:确保目标缓冲区足够大
char large_buffer[100];
strcpy(large_buffer, long_string); // 安全的
return 0;
}
strncpy() 函数
函数概述
strncpy() 是strcpy()的安全版本,可以指定最大复制字符数。
头文件 :#include <string.h>
函数原型:
c
char *strncpy(char *dest, const char *src, size_t n);
参数说明:
dest:目标字符串地址src:源字符串地址n:最多复制的字符数
使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello, World!";
char dest1[10];
char dest2[20];
// 示例1:n小于源字符串长度
strncpy(dest1, src, 5);
dest1[5] = '\0'; // 必须手动添加结束符
printf("dest1: %s\n", dest1); // 输出:Hello
// 示例2:n大于源字符串长度
strncpy(dest2, src, 20);
printf("dest2: %s\n", dest2); // 输出:Hello, World!
// 示例3:查看填充情况
char dest3[15] = "XXXXXXXXXXXXXX";
strncpy(dest3, src, 20);
for (int i = 0; i < 15; i++) {
printf("dest3[%d] = %c (ASCII: %d)\n", i,
dest3[i] ? dest3[i] : ' ', dest3[i]);
}
return 0;
}
重要特性
- 如果
n小于等于src的长度,不会自动添加'\0' - 如果
n大于src的长度,会用'\0'填充剩余空间 - 总是复制正好
n个字符到目标缓冲区
strcat() 函数
函数概述
strcat() 用于将一个字符串追加到另一个字符串的末尾。
头文件 :#include <string.h>
函数原型:
c
char *strcat(char *dest, const char *src);
参数说明:
dest:目标字符串(必须有足够空间)src:要追加的源字符串
使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
char greeting[50] = "Hello, ";
char name[] = "Alice";
strcat(greeting, name);
printf("%s\n", greeting); // 输出:Hello, Alice
// 可以连续拼接
strcat(greeting, "! Welcome to C programming.");
printf("%s\n", greeting); // 输出:Hello, Alice! Welcome to C programming.
return 0;
}
strncat() 函数
函数概述
strncat() 是strcat()的安全版本,可以指定最大追加字符数。
头文件 :#include <string.h>
函数原型:
c
char *strncat(char *dest, const char *src, size_t n);
使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer[20] = "Hello";
// 安全地追加字符串
strncat(buffer, ", World!", 10);
printf("%s\n", buffer); // 输出:Hello, World!
// 示例:限制追加长度
char buffer2[15] = "Start";
strncat(buffer2, " too long string", 9); // 只追加前9个字符
printf("%s\n", buffer2); // 输出:Start too long
return 0;
}
安全优势
与strcat()相比,strncat():
- 可以防止缓冲区溢出
- 总是确保目标字符串以
'\0'结尾 - 即使
n大于src的长度,也只会追加实际存在的字符
strcmp() 函数
函数概述
strcmp() 用于比较两个字符串。
头文件 :#include <string.h>
函数原型:
c
int strcmp(const char *str1, const char *str2);
返回值:
- 返回0:两个字符串相等
- 返回负数:str1小于str2
- 返回正数:str1大于str2
比较规则
strcmp()按字典顺序比较字符串:
- 逐个比较字符的ASCII值
- 遇到不同字符时,根据ASCII值差返回结果
- 如果一直相同直到
'\0',则字符串相等
使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
// 示例1:相等比较
printf("strcmp('apple', 'apple') = %d\n", strcmp("apple", "apple")); // 0
// 示例2:小于比较
printf("strcmp('apple', 'banana') = %d\n", strcmp("apple", "banana")); // 负数
// 示例3:大于比较
printf("strcmp('banana', 'apple') = %d\n", strcmp("banana", "apple")); // 正数
// 示例4:大小写敏感
printf("strcmp('Apple', 'apple') = %d\n", strcmp("Apple", "apple")); // 负数('A'=65, 'a'=97)
// 示例5:实际应用
char password[] = "secret123";
char input[50];
printf("Enter password: ");
fgets(input, sizeof(input), stdin);
// 去除换行符
input[strcspn(input, "\n")] = '\0';
if (strcmp(input, password) == 0) {
printf("Access granted!\n");
} else {
printf("Access denied!\n");
}
return 0;
}
注意事项
- strcmp()是大小写敏感的
- 对于包含数字的字符串,比较的是字符而不是数值
- 如果要进行不区分大小写的比较,使用stricmp()或strcasecmp()
字符串结束符 \0 的重要性
\0 的作用
\0(空字符,ASCII值为0)是C语言中字符串的结束标志,具有以下重要作用:
- 确定字符串长度 :所有字符串处理函数都依赖
\0来确定字符串的结束位置 - 防止内存越界 :没有
\0,函数可能读取超出分配内存的数据 - 提高效率:不需要单独存储字符串长度
正确使用示例
c
#include <stdio.h>
#include <string.h>
int main(void) {
// 正确:自动添加\0
char str1[] = "Hello"; // 实际上是'H','e','l','l','o','\0'
// 正确:手动添加\0
char str2[6];
str2[0] = 'H';
str2[1] = 'i';
str2[2] = '\0'; // 必须手动添加
// 错误:忘记\0
char str3[5] = {'H', 'e', 'l', 'l', 'o'}; // 没有\0,不是有效的C字符串
printf("str1: %s (length: %zu)\n", str1, strlen(str1)); // 正确
printf("str2: %s (length: %zu)\n", str2, strlen(str2)); // 正确
// printf("str3: %s\n", str3); // 危险:可能越界
return 0;
}
常见错误
-
忘记分配\0的空间:
cchar str[5] = "Hello"; // 错误:需要6个字节(5个字符+1个\0) -
手动构建字符串忘记\0:
cchar str[10]; for (int i = 0; i < 5; i++) { str[i] = 'A' + i; } // 忘记:str[5] = '\0'; -
覆盖\0导致问题:
cchar str[] = "Hello"; str[2] = '\0'; // 现在str变成"He",后面的字符被忽略