linux_c学习笔记(一)标准IO

1、说明与参考

IO操作分为标准IO和系统调用IO,IO操作是一切实现的基础,学C语言的时候写的很多小demo都是运行就结束了,中间产生的数据都随之消息了,这在真正的开发中是不存在的。

  • stdio 标准IO
  • sysio 系统调用IO

为什么要有这两种IO?

在不同的操作系统上(win和linux),想要实现通一套标准,就需要使用标准IO,因此标准IO的通用性更高。

IO操作中标准IO相关的常用函数如下所示:

fopen();
fclose();

fgetc();
fputc();
fgets();
fputs();
fread();
fwrite();

printf();
scanf();

fseek();
ftell();
rewind();

fflush();

其中一个数据结构贯穿始终 FILE

2、fopen()和fclose()

使用man手册查看fopen函数,第一个参数是文件名,第二个是mode

img
继续往下看可以知道mode的选择
img
因此可以知道相关参数的含义如下 (因此对于r和r+,必须要求文件存在)

mode 说明
r 只读模式打开文件,指针在文件的起始位置
r+ 读写模式打开文件,指针在文件的起始位置
w 只写模式(有则清空,无则新增),指针在文件起始位置
w+ 读写形式(有则清空,无则新增),指针在文件起始位置
a 追加只写形式,文件不存在创建,指针在文件末尾,最后一个有效字节的下一个位置
a+ 追加读写模式,文件不存在创建,指针位置根据后续操作是读还是写决定,读就在起始位置,写就在末尾

下面看一段程序,这里主要是注意这个errno的使用

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

/*fopen函数的基本使用*/
int main()
{
    FILE *fp;

    fp = fopen("tmp","r");
    if(fp == NULL)
    {
        fprintf(stderr, "fopen():%s\n",strerror(errno));
        exit(1);
    }
    puts("OK!");
    fclose(fp);
    exit(0);
}

运行结果如下,因为这里没有对应的文件,所以为报错,使用errno可以查看错误原因 img

这里可以用perror进一步优化报错的效,这样更简洁一点
img

3、fgetc()和fputc()

直接看代码,这两个函数比较好理解,实现了每次一个字符的IO操作,一个读一个写,使用这两个函数实现一个复制文件的实现

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

int main(int argc, char **argv)
{
    FILE *fps, *fpd;
    int ch;
    int count = 0;

    if(argc < 3)
    {
        fprintf(stderr,"usage:%s <src_file> <dest_file>\n",argv[0]);
        exit(1);
    }
    fps = fopen(argv[1],"r");
    if(fps == NULL)
    {
        perror("fopen()");
        exit(1);
    }
    fpd = fopen(argv[2],"w");
    if(fpd == NULL)
    {
        fclose(fps);//这行不写,如果失败就会造成前面的没有关闭,造成内存泄漏
        perror("fopen()");
        exit(1);
    }
    // while (fgetc(fps) != EOF)//这个是一个个的读取的
    // {
    //     count++;
    // }
    // printf("count is %d\n",count);
    while(1)
    {
        ch = fgetc(fps);
        if(ch == EOF)//读到了文件尾
            break;
        fputc(ch,fpd);
    }
    fclose(fpd);
    fclose(fps);
    exit(0);
}

这里使用了这个方法做了一下输入的检查:

img

另外这里需要注意的是,读出来的是一个int型的数据(ch为int型的数据),这里不要怀疑,要跟手册为准

4、fgets()和fputs()

简单来说就是把原来字符操作转为字符串的操作了,含义是每次读一行,在上面的代码中进行如下修改,这里定义了一个buf[BUFFERSIZE]的char型的数组,使用fets进行读取,这里读到末尾是返回一个NULL了。

img

5、fread()和fwrite()

这两个函数的用法在man手册中描述如下
img

这里的nmemb理解为对象,size为字节,就是每次读多少个对象,一次多少个字节,用法示例如下:

  • ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size -- 这是要读取的每个元素的大小,以字节为单位。
  • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

fread(buf, 1, 10, fp) 读取一个元素的大小是一个字节,读10个元素
fread(buf, 10, 1, fp) 读取一个元素的大小是十个字节,读1个元素

再次修改上面的代码,将while中的代码修改为如下形式
img

上面的部分可以理解为,读BUFFER_SIZE个,之后再写BUFFER_SIZE个,但是这样最后的效果是不对的,因为无法保证一定可以读到BUFFER_SIZE大小的内容,因此对这个部分的代码做如下修改:

img

详细的说明已经在图片中展示出来了

6、printf()和scanf()

这里重点关注printf相关的一些函数族printf() fprintf() sprintf()和snprintf()

img

先介绍一个函数atoi,提取字符串里面的数字
img

上面这段代码输出的结果是123,因为a是字母,所以运行到这里就会停止了。

下面看一段代码

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

int main()
{
    char buf[1024];
    int year = 2014,mouth = 5, day = 13;
    sprintf(buf, "%d-%d-%d\n", year,mouth,day); //snprintf加上了字符数量的限制
    puts(buf);
    
    exit(0);
}

运行结果如下

img

这里要注意就是这个函数会自动加上\n换行,所以这里本身不需要把\n添加进去了

接下来是scanf一族的函数,函数描述和上面的printf基本是相似的

img

7、fseek()相关函数

这几个函数是用于操作文件位置指针的函数,在man手册中这几个函数如下所示

img

相关的解释如下

img

大意就是fseek就是移动操作文件位置的指针,然后ftell范围当前位置的指针,rewind就是将文件指针移动到开始的位置

下面演示一段示例代码


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

int main(int argc, char **argv)
{
    FILE *fp;
    int count = 0;
    if(argc < 2)
    {
        fprintf(stderr, "usage... \n");
        exit(0);
    }
    fp = fopen(argv[1],"r");
    if(fp == NULL)
    {
        perror("fopen()");
        exit(1);
    }

    fseek(fp,0,SEEK_END); //将文件指针移动到文件末尾
    printf("%ld\n", ftell(fp)); 
    rewind(fp);
    printf("%ld\n", ftell(fp)); 
    fclose(fp);

    exit(0);
}

代码运行结果如下所示,可以看到其实是第一次将位置指针放到了文件末尾,这个时候其实是获取了文件大小,第二次使用rewind成功将文件移动到了开头的位置

img

fseek的作用,一般比较多用来制造空洞文件,就是touch一个文件之后,就立马用fseek占满需要的空间,之后用多进程进行同时读写。

8、fflush()函数

先看一段程序

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

int main()
{
    int i;
    printf("before while()");

    while(1);

    printf("after while()");

    exit(0);
}

这段代码以为是第一个打印会输出,第二个应该不会输出,但是实际运行结果确是一个也没有输出

img

现在对代码进行一点点小的修改

img

可以看到输出结果发生了改变,这是因为标准终端是按照换行符来刷新缓冲区,或者是行满了才刷新缓冲区

img

我们也可以用fflush来刷新缓冲区

img

输出结果如下所示,可以看到成功进行了输出

img

下面说明一下缓冲区,缓冲区的作用是合并系统调用,模式分为下面几种:

  • 行缓冲 换行的时候刷新和满了的时候刷新
  • 全缓冲 满了的时候刷新
  • 无缓冲 例如stderr,需要立即输出的内容

可以通过setvbuf来实现修改缓冲模式

9、getline()函数

这个函数是获取一行的内容,这个函数本身会申请一段内存,如果空间不够,就会继续申请一段内存,本身是一个动态申请内存


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

int main(int argc, char **argv)
{
    FILE *fp;
    char *linebuf;
    size_t linesize;
    if(argc < 2)
    {
        fprintf(stderr, "usage... \n");
        exit(0);
    }
    fp = fopen("1.txt","r");

    linebuf = NULL; //这一句和下面的一句非常重要,一定要加,不然就是段错误,没有指定内存,还会造成内存泄漏
    linesize = 0;

    while(1)
    {
        if(getline(&linebuf, &linesize, fp) < 0)
            break;
        puts(linebuf);
        printf("%d\n", strlen(linebuf));
        printf("%d\n",linesize); //申请的内存大小
    }

    exit(0);
}

运行结果如下

img

posted @ 2023-03-12 14:47  LX2020  阅读(55)  评论(0)    收藏  举报