目录
186. 字符串
字符串是由一系列字符组成的文本。它可以通过字符数组或指针来表示。C 语言中的字符串通常以空字符 '\0' 结尾来标记字符串的结束。接下来,我们将介绍字符串的不同表示方式以及它们的使用方法。
1. 单个字符
C 语言中,单个字符可以通过字符类型(char)来表示。例如:
char c = 't';
printf("%c\n\n", c); // 输出:t
't'是一个单个字符,使用%c来输出。
2. 字符串数组
可以使用字符数组来存储一个字符串。一个字符数组可以存储多个字符,并且最后会自动加入一个 '\0' 作为字符串的结束符。
char arr[] = { "Hello" }; // 包含 'H', 'e', 'l', 'l', 'o', '\0'
arr[0] = 'W'; // 可以修改字符数组的内容
printf("%s\n\n", arr); // 输出:Wello
- 这里,
arr[]实际上是一个字符数组,存储了Hello字符串。 - 数组中的每个字符都被存储为
char类型,最后自动加上'\0'来表示字符串的结束。
3. 字符串常量
字符串常量是不可修改的,因此使用指针来存储字符串时,不能修改字符串的内容。
char* ptr = "Hello"; // 使用指针指向字符串
printf("%c\n", *ptr); // 输出:H
ptr是一个指向字符串常量"Hello"的指针。*ptr表示指向的字符,即'H'。- 如果尝试修改
ptr指向的内容,如ptr[0] = 'w';,将会报错,因为字符串常量是只读的。
4. 字符串的数组和指针
C 语言中的字符串可以用字符数组或字符指针来表示。它们在内存中是不同的:
- 字符数组:存储字符和
'\0',并且可以被修改。 - 字符指针:指向一个字符串常量,不能被修改。
char string[6] = "hello"; // 字符数组存储字符串 "hello" + '\0'
printf("%s\n\n", string); // 输出:hello
string是一个字符数组,它足够大来存储"hello"和结束符'\0',所以它的长度是 6。- 如果尝试将字符数组的长度设置为 5,就会遇到问题,因为它没有空间存储
'\0'结束符,可能会导致字符串截断或未定义行为。
5. 完整代码示例
#include <stdio.h>
int main(void) {
// 单个字符
char c = 't';
printf("%c\n\n", c); // 输出:t
// 字符串作为字符数组
char arr[] = { "Hello" }; // 包含 'H', 'e', 'l', 'l', 'o', '\0'
arr[0] = 'W'; // 修改字符数组的内容
printf("%s\n\n", arr); // 输出:Wello
// 字符串常量
char string[6] = "hello"; // 存储字符串 "hello" 和 '\0'
printf("%s\n\n", string); // 输出:hello
// 字符串指针
char* ptr = "Hello"; // 字符指针指向字符串常量
printf("%c\n", *ptr); // 输出:H
// ptr[0] = 'w'; // 错误!尝试修改字符串常量会报错
return 0;
}
6. 输出结果
t
Wello
hello
H
7. 总结
- 单个字符使用
char类型表示,使用%c输出。 - 字符串可以通过字符数组或字符指针表示,但字符数组可以修改内容,而字符指针指向的字符串常量则是只读的。
- 字符数组必须包含足够的空间来存储字符串及其结束符
'\0'。
187. strcpy_s 的用法
strcpy_s 是一种安全的字符串复制函数,通常用于防止 strcpy 中的缓冲区溢出问题。与传统的 strcpy 不同,strcpy_s 需要传入目标缓冲区的大小,它会在复制时进行边界检查,从而避免复制时超出目标数组的大小。
1. strcpy_s 函数原型:
errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);
dest:目标字符串数组。destsz:目标数组的大小(以字符为单位)。src:源字符串。
如果 strcpy_s 发现目标数组不够大来存放源字符串,它会返回一个错误,并不会执行复制操作。
2. 代码示例
#include <stdio.h>
#include <string.h>
#define SIZE 50 // 定义目标数组的大小
int main(void) {
const char src[] = "hello"; // 源字符串
char dest[SIZE]; // 目标字符数组
// 使用 strcpy_s 安全地将 src 复制到 dest
strcpy_s(dest, SIZE, src);
// 输出源字符串和目标字符串
printf("src: %s\n", src); // 输出:hello
printf("dest: %s\n", dest); // 输出:hello
return 0;
}
3. 解释
const char src[] = "hello";:定义一个源字符串src。char dest[SIZE];:定义一个大小为SIZE的目标字符数组dest,它用于接收复制的内容。strcpy_s(dest, SIZE, src);:使用strcpy_s将src字符串的内容安全地复制到dest中。此函数会确保不会超出dest数组的边界,如果目标数组的大小不足以容纳源字符串,它会返回错误。
4. 输出
src: hello
dest: hello
5. 为什么使用 strcpy_s?
strcpy_s提供了额外的安全检查,防止了strcpy中可能发生的缓冲区溢出问题。- 使用
strcpy_s时,必须指定目标缓冲区的大小,这样可以确保不会写入超出数组大小的内容。 - 如果目标缓冲区太小,
strcpy_s不会执行任何复制操作,并且会返回一个错误码,确保程序的健壮性。
6. 注意事项
strcpy_s是 C11 标准中定义的函数,某些编译器和平台可能不支持该函数,因此在一些环境中使用时需要确保它的可用性。如果不可用,可以使用其他方式进行安全的字符串复制。strcpy_s会修改目标数组,所以必须确保目标数组有足够的空间以容纳源字符串及其结束符'\0'。
194. gets_s 用法
gets_s 是 gets 函数的安全版本,用于从标准输入获取字符串。gets 因为没有限制输入长度,容易导致缓冲区溢出问题,已经被 C11 标准废弃。gets_s 通过要求传入目标缓冲区的大小,防止了缓冲区溢出。
1. gets_s 函数原型:
errno_t gets_s(char *str, rsize_t size);
str:目标字符数组,用于存储用户输入的字符串。size:目标字符数组的大小(以字符为单位)。
2. 代码示例
#include <stdio.h>
#include <string.h>
int main(void) {
// 输出静态字符串
const char* str = "Hello!";
puts(str); // 使用 puts 输出字符串
// 创建字符数组缓冲区
char buffer[100];
// 提示用户输入
printf("Enter a string: ");
// 使用 gets_s 获取输入,并限制输入的大小
gets_s(buffer, sizeof(buffer));
// 输出用户输入的内容
printf("You entered: %s\n", buffer);
return 0;
}
3. 解释
puts(str):将静态字符串"Hello!"输出到标准输出(控制台)。char buffer[100]:定义一个字符数组buffer,用于存储用户输入的字符串。其大小为 100 字节。gets_s(buffer, sizeof(buffer)):使用gets_s从用户获取输入,并将输入内容存储到buffer中。sizeof(buffer)是缓冲区的大小,用来防止用户输入超过buffer大小的数据。printf("You entered: %s\n", buffer);:输出用户输入的字符串。
4. 注意事项
gets_s的使用可以有效防止缓冲区溢出,因为它要求提供目标缓冲区的大小。如果用户输入的数据超过缓冲区大小,gets_s将不执行任何操作,返回错误。- 如果用户输入的内容包含
\n(换行符),gets_s会自动将其替换为字符串结束符\0,因此用户输入的字符串不会包含换行符。
5. gets_s 的错误处理
如果输入的字符串超出了目标缓冲区的大小,gets_s 会返回错误,并不会修改缓冲区内容。你可以根据需要进行错误处理。
if (gets_s(buffer, sizeof(buffer)) == NULL) {
printf("Error or end of file encountered.\n");
} else {
printf("You entered: %s\n", buffer);
}
6. 输出示例
Hello!
Enter a string: Hello, world!
You entered: Hello, world!
7. gets_s 与 gets 的区别
- 安全性:
gets没有对输入的长度做任何限制,容易造成缓冲区溢出,导致程序崩溃或被恶意攻击。而gets_s要求指定缓冲区的大小,从而有效防止缓冲区溢出问题。 - 废弃:
gets在 C11 标准中已被废弃,而gets_s是 C11 标准中为提高安全性引入的替代方案。
因此,使用 gets_s 代替 gets 是一种更安全的编程实践。
204. 输入输出初步认识
在 C 语言中,输入和输出的处理是通过 输入流(input stream) 和 输出流(output stream) 来实现的。通过这些流,程序能够与外部进行交互,例如读取用户输入或将结果输出到屏幕。
1. 输入流与输出流
- 输入流:是从外部(通常是键盘或文件)读取数据的通道。在 C 语言中,标准输入通常表示为
stdin。 - 输出流:是将数据发送到外部的通道,通常是输出到显示器或文件。在 C 语言中,标准输出表示为
stdout。
2. 缓冲区(Buffer)
缓冲区是一块内存区域,用于暂时存储输入或输出的数据。使用缓冲区能够提高输入输出操作的效率,减少频繁的硬件操作。
- 输入缓冲区:当用户从键盘输入数据时,数据会首先存储在缓冲区中,直到程序显式地读取缓冲区中的内容。
- 输出缓冲区:程序通过
printf等函数将数据写入输出缓冲区,缓冲区中的内容在适当时机(例如缓冲区满时或程序结束时)才会被写到标准输出。
3. 标准输入(stdin)和标准输出(stdout)
- 标准输入(
stdin):是程序接收输入的默认流。它通常指向键盘,用户通过键盘输入数据,程序从stdin中读取。 - 标准输出(
stdout):是程序输出数据的默认流。它通常指向屏幕,程序通过stdout将数据输出到屏幕上。
4. 示例代码:基本输入输出
#include <stdio.h>
int main(void) {
// 使用 stdin 输入
char name[50];
printf("Enter your name: ");
fgets(name, sizeof(name), stdin); // 使用 fgets 替代 gets 防止缓冲区溢出
// 使用 stdout 输出
printf("Hello, %s!\n", name);
return 0;
}
5. 解释
fgets(name, sizeof(name), stdin);:从标准输入(键盘)读取一行字符串,fgets会自动将输入的换行符(\n)存储在字符串中,并且它不会发生缓冲区溢出。printf("Hello, %s!\n", name);:将用户输入的名字输出到屏幕,%s格式化符用于输出字符串。
6. 流的特点
- 缓冲区的作用:缓冲区的使用可以提高 I/O 操作的效率。尤其在处理大量数据时,缓冲区能够将数据一次性批量读取或写入,减少了每次 I/O 操作的开销。
- 自动刷新与手动刷新:
stdout在某些情况下(例如遇到换行符)会自动刷新缓冲区。- 如果需要手动刷新,可以使用
fflush(stdout);。
7. 标准库中的输入输出函数
printf:将数据输出到标准输出。scanf:从标准输入读取数据。fgets:从标准输入读取一行数据(比scanf更安全,防止缓冲区溢出)。fputs:将字符串输出到指定文件流。fread和fwrite:用于二进制数据的文件读取和写入。
8. 总结
输入输出流是程序与外部世界交互的桥梁,通过缓冲区提升数据处理的效率。理解 stdin 和 stdout 的概念,以及如何使用不同的函数进行输入输出操作,能够帮助开发者写出更加高效和安全的程序。
206. scanf_s 返回值
在 C 语言中,scanf_s 是一个更安全的输入函数,通常用于替代 scanf。与 scanf 类似,scanf_s 用来从标准输入流(通常是键盘)中读取数据。与 scanf 一样,scanf_s 也会返回一个整数,表示成功读取的项数。
scanf_s 返回值:
- 返回值为
1:表示成功读取一个项(例如一个整数、浮点数或字符)并将其存储到对应的变量中。 - 返回值为
EOF(通常为-1):表示读取操作失败,通常是因为遇到输入流的结束(如文件末尾或用户输入了不合法的数据)。 - 返回值为其他正整数:表示成功读取多个项。
示例代码解释:
#include <stdio.h>
// 206. scanf_s返回值
int main(void) {
int number;
int result;
puts("Enter an integer:");
// 读取一个整数,并将其存储到 number 变量中
result = scanf_s("%d", &number);
if (result == 1) {
// 如果成功读取一个整数
printf("You entered the integer: %d\n", number);
}
else if (result == EOF) {
// 如果遇到文件末尾或读取错误
printf("An error occurred or end file was reached.\n");
return 1;
}
else {
// 如果输入不合法
printf("Invalid input for integer.\n");
return 1;
}
return 0;
}
代码解释:
- 读取输入:
scanf_s("%d", &number);会尝试从标准输入读取一个整数。%d格式说明符表示整数。- 读取成功时,
scanf_s会返回读取的项数(在本例中为1),并且输入的整数会存储在number中。
- 处理返回值:
result == 1:表示成功读取了一个整数,程序会输出该整数。result == EOF:表示遇到输入流的结束(如文件结束或者输入错误),会输出错误信息并退出程序。- 其他情况:如果输入的不是有效的整数(如用户输入了字母或其他无效字符),
scanf_s会返回0,程序会输出“Invalid input for integer.”并退出。
常见问题:
scanf_s与scanf的区别:scanf_s是 Microsoft 提供的安全版本,它需要在读取字符串时指定最大输入长度来避免缓冲区溢出。而scanf并没有这种强制要求。scanf_s不仅对字符串处理有更多要求,且通常在 Windows 平台中使用。
- 输入错误处理:
scanf_s在读取输入时可能会遇到错误(例如用户输入了非数字字符)。这时,scanf_s会返回0,并且对应的变量不会被修改。为了避免程序出错,通常会在读取数据后检查返回值。
总结:
scanf_s 是一个比 scanf 更安全的输入函数,通过检查其返回值,我们能够确保输入是否成功,并且能够处理文件结束或输入错误的情况。
208. 使用 fopen_s, fgetc, fgets, fclose 读取文件
代码解析:
在这个示例中,程序展示了如何使用文件操作函数来读取文件内容。在 C 语言中,打开文件、读取文件和关闭文件是常见的操作。这里的重点是:
fopen_s: 用于打开文件。fgetc: 逐个字符读取文件。fgets: 按行读取文件。fclose: 关闭文件。
代码实现:
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <errno.h>
// 208. fopen_s, fgetc, fgets, fclose读取r模式 r模式:read读
int main(void) {
FILE* file_stream = NULL; // 文件流
char buffer[256];
// 打开文件,设定文件路径要读取的文件,设定文件的操作模式r
errno_t err = fopen_s(&file_stream, "C:\\Users\\86157\\Desktop\\123.txt", "r");
// 检查文件是否成功打开
if (err != 0 || file_stream == NULL) {
perror("Error opening file");
return EXIT_FAILURE;
}
// 使用 fgetc 一个一个地读取字符
int ch;
while ((ch = fgetc(file_stream)) != EOF) {
putchar(ch); // 将每个字符打印到标准输出
}
// 关闭文件流
fclose(file_stream);
return 0;
}
代码解释:
-
文件打开:
-
fopen_s(&file_stream, "C:\\Users\\86157\\Desktop\\123.txt", "r");fopen_s是一种安全的文件打开方式,它比传统的
fopen更加安全,能够处理潜在的错误(例如文件无法打开)。
file_stream: 是一个FILE*类型的文件指针,用来保存文件流。"C:\\Users\\86157\\Desktop\\123.txt": 文件路径,可以是相对路径或绝对路径。"r": 读模式,表示文件以只读方式打开。
-
-
文件打开失败处理:
- 如果
fopen_s返回值不为0或file_stream为NULL,说明文件打开失败,程序通过perror打印错误信息并退出。
- 如果
-
逐字符读取文件:
fgetc(file_stream)逐个字符读取文件,直到遇到文件末尾(EOF)。- 每读取一个字符,
putchar(ch)会将字符打印到标准输出。
-
文件关闭:
- 使用
fclose(file_stream)关闭文件,以释放文件流资源。
- 使用
fopen_s, fgetc, fgets, 和 fclose 的用法:
fopen_s: 安全地打开文件。返回0表示成功,非零表示错误。fgetc: 从文件中读取一个字符,直到遇到 EOF。fgets: 一次读取一行内容,可以用来读取较长的文本行。它比fgetc更方便用于处理多行文本。fclose: 关闭文件流,确保文件资源被正确释放。
示例输出:
假设 C:\\Users\\86157\\Desktop\\123.txt 文件内容如下:
Hello, world!
This is a test file.
程序输出会是:
Hello, world!
This is a test file.
fgets 示例:
如果需要按行读取文件,可以使用 fgets,它可以一次性读取一行内容。示例如下:
// 使用 fgets 读取文件每行
while (fgets(buffer, sizeof(buffer), file_stream) != NULL) {
printf("%s", buffer);
}
这种方式会比 fgetc 更方便,尤其是在处理较长的文件时。
209. 使用 fputs, fputc 与 w 模式写文件
代码解析:
在这个示例中,程序展示了如何使用文件操作函数来写入数据到文件。在 C 语言中,常用的写文件操作函数包括:
fputc: 用于将一个字符写入文件。fputs: 用于将一个字符串写入文件。fprintf_s: 用于将格式化的数据写入文件。
文件以 w 模式打开,这表示以写入模式打开文件。如果文件已经存在,会被覆盖;如果文件不存在,会创建新文件。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
// 209. fputs, fputc与w模式
int main(void) {
FILE* file_ptr = NULL;
// 打开文件,w模式:write,写入模式
errno_t err = fopen_s(&file_ptr, "C:\\Users\\86157\\Desktop\\123.txt", "w");
// 检查文件是否成功打开
if (err != 0 || file_ptr == NULL) {
perror("Failed to open file");
return EXIT_FAILURE;
}
// 使用 fputc() 写入字符
fputc('H', file_ptr); // 写入字符 'H'
fputc('i', file_ptr); // 写入字符 'i'
fputc('\n', file_ptr); // 写入换行符
// 使用 fputs() 写入字符串
fputs("This is a line written by fputs.\n", file_ptr);
// 使用 fprintf_s() 写入格式化内容
double pi = 3.14;
fprintf_s(file_ptr, "Numbers: %d, %lf, %s\n", 10, pi, "End of example");
// 关闭文件
fclose(file_ptr);
// 输出成功信息
puts("File '123.txt' has been written successfully.\n");
return 0;
}
代码解释:
-
打开文件:
-
fopen_s(&file_ptr, "C:\\Users\\86157\\Desktop\\123.txt", "w"):以
w模式打开文件。
w模式表示写入模式,文件内容会被完全覆盖。如果文件不存在,将会创建新文件。file_ptr是文件指针,用于操作打开的文件。
-
-
写入字符:
fputc('H', file_ptr):写入字符 ‘H’。fputc('i', file_ptr):写入字符 ‘i’。fputc('\n', file_ptr):写入换行符。
-
写入字符串:
fputs("This is a line written by fputs.\n", file_ptr):写入字符串。
-
格式化输出:
fprintf_s(file_ptr, "Numbers: %d, %lf, %s\n", 10, pi, "End of example"):写入格式化字符串(整数、浮点数和字符串)。
-
关闭文件:
fclose(file_ptr):关闭文件,释放文件资源。
-
文件写入成功提示:
puts("File '123.txt' has been written successfully.\n"):输出文件写入成功的提示信息。
写入模式说明:
w模式:会重写文件内容,若文件已存在则清空内容;若文件不存在,则创建新文件。fputc:逐个字符写入文件。fputs:将字符串写入文件,直到遇到空字符\0。fprintf_s:类似printf,但写入到文件中,支持格式化输出。
示例输出文件内容:
假设文件 123.txt 最终的内容如下:
Hi
This is a line written by fputs.
Numbers: 10, 3.140000, End of example
注意事项:
fopen_s函数返回errno_t类型,表示打开文件时是否出错。如果文件打开失败,程序会通过perror输出错误信息。- 使用
fputc、fputs和fprintf_s时,确保文件是以正确的模式打开的。 - 在使用文件操作时,记得调用
fclose关闭文件,以释放文件句柄。
这段代码演示了如何通过不同方式将数据写入文件,使用了 C 标准库中的多种文件输出函数,并确保文件在操作后被正确关闭。
218. 游戏设置案例:二进制文件存储与读取
错误分析:
在你的代码中,有两个问题导致报错:
- 拼写错误: 在
save_game_settings函数中,fwirte应该是fwrite,fwrite是标准库中的一个函数,用于将数据写入文件。 fwrite和fread参数的问题:fwrite和fread函数的使用是正确的,但需要确保操作的是一个有效的文件指针,并且需要正确指定读取和写入的数据量。
修正后的代码:
#include <stdio.h>
#include <stdlib.h>
// 游戏设置结构体
typedef struct GameSettings {
float volume; // 音量
int resolution_x; // 分辨率X
int resolution_y; // 分辨率Y
int difficulty; // 难度
} GameSettings;
// 函数声明
void save_game_settings(const GameSettings* settings, const char* filename);
void load_game_settings(GameSettings* settings, const char* filename);
int main(void) {
// 游戏设置实例
GameSettings settings = { 0.75, 1920, 1080, 2 };
// 保存游戏设置到二进制文件
save_game_settings(&settings, "C:\\Users\\86157\\Desktop\\game_settings.bin");
GameSettings loaded_settings;
// 从二进制文件读取游戏设置
load_game_settings(&loaded_settings, "C:\\Users\\86157\\Desktop\\game_settings.bin");
printf("游戏设置已经加载!\n");
printf("音量:%f\n分辨率:%dx%d\n难度:%d\n", loaded_settings.volume, loaded_settings.resolution_x, loaded_settings.resolution_y, loaded_settings.difficulty);
return 0;
}
// 保存游戏设置到二进制文件
void save_game_settings(const GameSettings* settings, const char* filename) {
FILE* file;
// 使用wb模式打开文件进行写入
errno_t err = fopen_s(&file, filename, "wb");
if (err != 0 || file == NULL) {
perror("无法打开文件进行写入操作");
return;
}
// 写入数据
fwrite(settings, sizeof(GameSettings), 1, file);
fclose(file); // 关闭文件
}
// 从二进制文件读取游戏设置
void load_game_settings(GameSettings* settings, const char* filename) {
FILE* file;
// 使用rb模式打开文件进行读取
errno_t err = fopen_s(&file, filename, "rb");
if (err != 0 || file == NULL) {
perror("无法打开文件进行读取操作");
return;
}
// 读取数据
fread(settings, sizeof(GameSettings), 1, file);
fclose(file); // 关闭文件
}
代码解释:
- 结构体定义:
GameSettings结构体包含了音量、分辨率和难度等设置项,用于存储游戏设置。
- 文件写入:
save_game_settings函数使用fwrite将GameSettings结构体的内容写入到指定的二进制文件。- 打开文件时使用
"wb"模式,这意味着以二进制方式写入文件。如果文件不存在,它将创建新文件;如果文件存在,它将覆盖文件内容。
- 文件读取:
load_game_settings函数使用fread从二进制文件读取数据到结构体中。- 打开文件时使用
"rb"模式,这意味着以二进制方式读取文件。
- 错误检查:
- 如果文件无法打开(例如路径错误或没有访问权限),则使用
perror输出错误信息。
- 如果文件无法打开(例如路径错误或没有访问权限),则使用
- 文件关闭:
- 使用
fclose关闭文件以释放资源。
- 使用
主要函数:
-
fwrite:用于写入二进制数据到文件。size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);ptr: 指向要写入数据的内存块。size: 每个元素的大小。count: 写入的元素数量。stream: 文件指针。
-
fread:用于从文件中读取二进制数据。size_t fread(void* ptr, size_t size, size_t count, FILE* stream);ptr: 指向存储读取数据的内存块。size: 每个元素的大小。count: 读取的元素数量。stream: 文件指针。
使用 "wb" 和 "rb" 模式:
wb:打开文件进行写入,如果文件已存在,清空文件内容。rb:以二进制模式打开文件进行读取,文件必须存在。
常见问题和修复:
- 拼写错误:
fwirte应该是fwrite。 - 路径错误:确保文件路径是正确的,并且程序有权限访问该文件。
- 数据类型问题:使用
sizeof(GameSettings)来确保写入或读取的大小与结构体大小一致。
输出结果:
假设文件 game_settings.bin 存储了以下数据:
音量:0.750000
分辨率:1920x1080
难度:2
该程序可以正确地保存和加载游戏设置。
472

被折叠的 条评论
为什么被折叠?



