目录
1 由变量声明到函数声明
2 strcpy和strcat
3 缓冲输出
4 返回整数的getchar函数
5 使用errno检测错误
6 库函数signal
7 更新顺序文件
1 由变量声明到函数声明
C语言变量声明由数据类型、变量名和可选的初始化值组成,用一个通用表达式来描述变量声明的结构,可以表示为:
<数据类型> <变量名> [= <初始化值>]
如下的变量声明,是*pf是一个浮点数,也就是说,pf是一个指向浮点数的指针:
float *pf;
进一步在声明中还可以进一步组合,因此:
float *pf(), (*h)();
表示*pf()与(*h)()是浮点表达式。因为()结合优先级高于*,*pf()也就是*(pf()),pf是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。
2 strcpy和strcat
strcpy和strcat是C语言中两个常用的字符串操作函数,它们分别用于复制和连接字符串。函数原型如下:
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
- strcpy: 将源字符串src复制到目标字符串dest中。如果dest已经包含数据,strcpy会覆盖这些数据。
- strcat: 将源字符串src连接到目标字符串dest的末尾。strcat会在dest的末尾加上src的字符串内容。
3 缓冲输出
即时输出意味着每次调用输出函数时,数据都会立即被发送到目标(如终端或文件)。在C语言中,标准输出(stdout)默认是带缓冲的,缓冲输出方式可以提高性能,因为减少了系统调用的次数(每次系统调用都有一定的开销)。然而,这也可能导致数据在缓冲区中停留一段时间,直到缓冲区满或程序显式地刷新缓冲区。
但你可以通过特定的方式设置它为无缓冲模式。如果你需要即时输出,可以考虑以下几个方式:
- 使用stderr:标准错误输出流(stderr)通常是无缓冲的,适用于需要即时反馈的情况。
- 手动刷新缓冲区:虽然这不是即时输出,但你可以通过fflush(stdout);手动刷新stdout缓冲区,以确保到目前为止的所有输出都被发送到目标。
下面的示例中使用fprintf(stderr, ...)来向stderr流写入数据。由于stderr通常是无缓冲的,所以每次调用fprintf时,数据都会立即显示在终端上。
#include <stdio.h>
int main() {
// 使用stderr进行即时输出
for (int i = 0; i < 5; i++) {
fprintf(stderr, "即时输出: %d\n", i);
// 注意:stderr通常是无缓冲的,所以这里的输出会立即显示在终端上
// 模拟一些耗时操作
for (int j = 0; j < 1000000; j++) {
// 空操作,仅为了消耗时间
}
}
return 0;
}
缓冲输出是C语言标准I/O库提供的一种机制,用于提高文件或控制台输出的效率。当程序使用printf、fprintf、puts等函数输出数据时,这些数据首先被存储在内存中的一个缓冲区中,而不是直接发送到输出设备(如屏幕或文件)。当缓冲区满、遇到换行符、或显式地刷新缓冲区(如使用fflush函数)时,缓冲区中的数据才会被一次性发送到输出设备。
下面的示例程序使用printf函数向stdout流写入数据。由于stdout是带缓冲的,所以输出可能不会立即显示在终端上,而是先存储在缓冲区中。
#include <stdio.h>
int main() {
// 使用stdout进行缓冲区输出
for (int i = 0; i < 5; i++) {
// 注意:stdout是带缓冲的,所以这里的输出可能不会立即显示在终端上
printf("缓冲区输出: %d\n", i);
// 模拟一些耗时操作
for (int j = 0; j < 1000000; j++) {
}
// 如果需要立即看到输出,可以手动刷新stdout缓冲区
// fflush(stdout);
}
// 如果在循环结束后调用fflush,则所有缓冲的输出都会显示在终端上
fflush(stdout);
return 0;
}
4 返回整数的getchar函数
getchar 函数是 C 语言标准库中的一个函数,用于从标准输入(通常是键盘)读取下一个可用的字符,原型定义在 <stdio.h> 头文件中。
getchar 返回的是一个 int 类型的值,而不是 char。getchar返回所有可能的字符值(通常是 0 到 127 或 0 到 255,取决于字符集)以及一个特殊的文件结束标志 EOF。EOF 通常定义为 -1。
#include <stdio.h>
main()
{
char c;
while((c = getchar()) != EOF)
putchar(c);
}
5 使用errno检测错误
在C语言中,errno 是一个全局变量,用于报告库函数在执行过程中遇到的最近一个错误。当库函数遇到错误时,它们通常会设置 errno 为一个特定的错误码,这些错误码在 <errno.h> 头文件中定义。然而,需要注意的是,并不是所有的库函数都会设置 errno。
下面的示例使用 errno 来检测 fopen 函数在尝试打开文件时是否发生了错误:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp = fopen("nonexistentfile.txt", "r");
if (fp == NULL) {
// fopen 失败,检查 errno
switch (errno) {
case ENOENT:
printf("文件不存在。\n");
break;
case EACCES:
printf("没有权限打开文件。\n");
break;
default:
printf("打开文件时发生未知错误。\n");
}
} else {
// fopen 成功,处理文件...
fclose(fp);
}
return 0;
}
下面的代码利用这一特性进行错误处理,似乎再清楚明白不过,然而却是错误的:
/*调用库函数*/
if(errno){
/*处理错误*/
}
出错原因在于,在库函数调用没有失败的情况下,并没有强制要求库函数一定要设置errno为0,这样ermo的值就可能是前一个执行失败的库函数设置的值。下面的代码作了更正,很可惜还是错误的:
errno = 0;
/*调用库函数*/
if(errno){
/*处理错误*/
}
出错原因在于,库函数在调用成功时,既没有强制要求对errno清零,但同时也没有禁止设置errno为其它值。正确的使用errno的方式是在调用库函数后,应该首先检查该函数的返回值来确定是否发生了错误。如果函数返回值表明发生了错误,那么再检查 errno 以获取具体的错误代码。
6 库函数signal
signal 函数允许程序为特定的信号指定一个信号处理函数,当进程接收到该信号时,将调用指定的函数。函数是 C 语言中用于设置信号处理函数的库函数之一,它定义在 <signal.h>头文件中。函数原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
- sig:要处理的信号编号。这些信号编号在 <signal.h> 中定义,例如 SIGINT(通常由 Ctrl+C 触发)、SIGTERM(请求程序终止)、SIGSEGV(无效的内存引用)等。
- func:当接收到信号 sig 时要调用的函数指针。如果 func 是 SIG_IGN,则忽略该信号;如果 func 是 SIG_DFL,则使用信号的默认处理方式。
下面的程序示例使用 signal 函数来处理 SIGINT 信号(通常由 Ctrl+C 触发),当用户按下 Ctrl+C 时,会触发 SIGINT 信号,然后调用 signal_handler 函数。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int signum) {
printf("捕获到信号 %d\n", signum);
// 清理并关闭
// ...
// 退出程序
exit(signum);
}
int main () {
// 设置信号 SIGINT 和信号处理函数
signal(SIGINT, signal_handler);
while(1) {
printf("程序正在运行...\n");
sleep(1);
}
return 0;
}
但是库函数 signal 在使用中存在一些问题和限制,这些主要源于信号处理的异步性质以及 signal 函数本身的设计。信号处理函数应该被设计为可重入的,因为当信号处理函数正在执行时,主程序或其他信号处理函数可能会被中断。然而,由于 signal 函数没有提供任何机制来确保信号处理函数的原子性或可重入性,因此编写安全的信号处理函数可能非常困难。因此在需要处理复杂信号或编写跨平台、多线程代码时,建议使用可移植性和线程安全性更好的sigaction 函数。
7 更新顺序文件
许多系统中的标准输入/输出库都允许程序打开一个文件,同时进行写入和读出的操作:
FILE *fp;
fp = fopen (file,"r+");
上面的例子代码打开了文件名由变量file指定的文件,对于存取权限的设定表明程序希望对这个文件进行输入和输出操作。编程者也许认为,程序一旦执行上述操作完毕,就可以自由地交错进行读出和写入的操作。遗憾的是,事实总难遂人所愿,为了保持与过去不能同时进行读写操作的程序的向下兼容性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。下面的程序片段似乎更新了一个顺序文件中选定的记录:
while(fread((char*)&rec, sizeof(rec), 1, fp) == 1){
/*对rec执行某些操作*/
if(/*rec必须被重新写入*/){
fseek(fp,-(long)sizeof(rec),1);
fwrite((char *)&rec,sizeof(rec),1,fp);
fseek(fp,OL,1);
}
}