linux系统编程06-标准IO2

printf\scanf函数族

printf一族: man 3 printf

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

printf 是把待输出内容 ... 按照指定格式 format 输出到 stdout 中。

fprintf 则可指定输出的位置,默认 stdoutstderr 指向屏幕, stdin 指向键盘。

sprintf将不同类型的数据综合成一个串

snprintf 则解决 sprintf 没有指定缓冲区大小的问题,类似 fgetsgets

相比于 printf ,其他函数更常用。

sprintf() 应用举例: atoi 实现把串转换为整数【串结束和碰到非数字字符时停止】,但是c中并没有 itoa 即把整型转换为一个串,,用 sprintf 可以实现这一点。

示例代码:

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

int main()
{
    char s[] = "123a456";
    printf("%d\n", atoi(s));

    char buf[1024];
    int year = 2022, month = 12, day = 28; 
    sprintf(buf, "%d-%d-%d", year, month, day);
    puts(buf);
                                                                                        
    exit(1);
}

运行结果:

Untitled

scanf一族: man 3 scanf

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

scanfstdin 中获取内容,按照 format 格式,填充到变量 ... 中。

fscanf 则可指定从哪个stream中获取内容。

sscanf 则从字符串 str 中获取内容,可以理解为 sprintf 的逆过程,把一个字符串按格式拆分为不同类型。【功能包含了 atoi

sscanf 例子:

示例代码:

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

int main()
{
    char s[] = "123a456";
    int x1, x2; 
    sscanf(s, "%da%d", &x1, &x2);
    printf("%d %d\n", x1, x2); 

    exit(1);
}

运行结果:

Untitled

fseek\ftell\rewind

int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);

fseek :设置文件指针的位置

  • 参数:流、偏移、基本位置( SEEK_SET SEEK_CUR SEEK_END 文件头,当前位置,文件尾)
  • 返回值: 0表示设置成功,1表示设置失败

ftell :判断当前文件指针的位置

  • 参数:流
  • 返回值:相对于开头的位置

rewind :将文件指针重置到开头,相当于 fseek(stream, 0, SEEK_SET)

fseekftell 使用举例:

//1. 读取10个字节并输出
fp = fopen();
fgetc(fp) * 10 -> rewind/fseek(fp,0,SEEK_SET) -> fput()

//2. fseek可以用来产生空洞文件

//3. fseek和ftell判断文件大小
##include<stdio.h>
##include<stdlib.h>

int main(char argc, char **argv)
{
    if(argc < 2)
    {   
        fprintf(stderr, "Usage: %s <file_name>", argv[0]);
        exit(1);
    }   

    FILE *fp;
    int cnt = 0;

    fp = fopen(argv[1], "r");
    if(fp == NULL)
    {   
        perror("fopen()");
        exit(1);
    }   
    fseek(fp, 0, SEEK_END);
    printf("%ld\n",ftell(fp));       

/*    while(fgetc(fp) != EOF)
        cnt++;
			printf("cnt = %d\n", cnt);
*/
    fclose(fp);
    exit(0);
}

运行结果:

Untitled

fflush :刷新缓冲区

int fflush(FILE *stream);
  • 参数:流,如果填NULL,表示刷新所有打开的流

缓冲区的作用:大多数情况下是好事,合并系统调用

三种缓冲方式:

  1. 行缓冲:换行刷新、满了刷新、强制刷新【 stdout ,因为是终端设备】
  2. 全缓冲:满了刷新、强制刷新【默认,只要不是终端设备】
  3. 无缓冲:如 stderr ,需要立即输出的内容

现象:什么都不打印,因为缓冲区没有刷新

int main()
{
	int i;
	printf("Before while()");
	while(1);
	printf("After while()");
	exit(0);
}

现象:打印 “Before while()”,因为缓冲区刷新了

int main()
{
	int i;
	printf("Before while()\n");
	/*
	printf("Before while()");
	fflush(stdout); or fflush(NULL)
	*/
	while(1);
	printf("After while()\n");
	exit(0);
}

fseek和ftell的缺陷:二者同时使用的时候由于ftell只能返回long类型的正数部分,而long类型的大小不同机子上定义不同,一般是2G-1(32bit),所以两个函数最多只能操作2G大小

fseeko,ftello用一个自定义类型解决了这个问题,但是不支持C89,C99,是方言,所以移植性不足

使用时要在编译时加 _FILE_OFFSET_BITS=64

Untitled

写法如 gcc a.c -o a -D_FILE_OFFSET_BITS=64 太麻烦,写入makefile中

##终端执行
vim makefile
##makefile中写入
CFLAGS += -D_FILE_OFFSET_BITS=64

如果要移植性好又要支持大文件,就得另想办法

getline

具有一个不可替代性:获取一行的大小,而无需在意该行有多大

内部实现:malloc,如果不够再realloc,从而取到足够大的空间来存储读取到的数据

ssize_t getline(char **lineptr, size_t *n, FILE *stream);
  • 参数:
    • lineptrchar * 的地址,作为缓冲区
    • nsize_t 的地址,getline会反填n,表示 lineptr 已分配的大小
  • 返回值:成功0,失败-1

例子:获取文件每一行的大小,并打印

示例代码:

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

int main(int argc, char **argv)
{
    if(argc < 2)
    {   
        fprintf(stderr, "Usage:...\n");
        exit(1);
    }   

    FILE *fp;
    char *linebuf;
    size_t linesize;

    fp = fopen(argv[1], "r");
    if(fp == NULL)
    {   
        perror("fopen()");
        exit(1);
    }   

		//很重要!!!
    linebuf = NULL;
		linesize = 0;

		while(1)
		{   
        if(getline(&linebuf, &linesize, fp) < 0)
            break;
        printf("%d\n", (int)strlen(linebuf));
        printf("%d\n", (int)linesize);
    }   

   fclose(fp);

   exit(0);
}

运行结果:

Untitled

*getline 存在可控的内存泄漏问题,因为它没有互逆操作来 freelinebuf 的内存*

临时文件

//临时文件:
1. 名字如何不冲突
2. 及时销毁

char* tmpnam(char *s);
FILE* tmpfile();

tmpnam 产生一个可用的临时文件名,然后我们拿这个文件去创建临时文件。

可以看书拿到文件名和创建文件是分两步的,中间可能会被打断,所以存在 并发问题 ,比如进程A执行 tmpnam 后没来得及创建文件,就轮到进程B执行了, 而进程B同样执行了 tmpnam ,由于进程A执行 tmpnam 后没有创建文件,所以系统认为 进程A中 tmpnam 返回的文件名仍是可用的,于是返回给了进程B,然后进程B创建文件,就会导致进程A的名字冲突。

tmpfile:产生匿名文件,返回它的 FILE*

  • 匿名文件:存在于磁盘但无法查看也无法 ls -a 查看的文件
  • fclose 后,匿名文件会自动销毁【硬链接为0,文件计数符为0】
  • 如果忘记 fclose :可控内存泄漏【如果程序 exitreturn 正常结束,进程申请的空间会被释放】
posted @ 2025-09-17 00:04  Miaops  阅读(10)  评论(0)    收藏  举报