标准IO与文件IO

目录

标准I/O

一.系统调用和用户程序编程接口

1.系统调用

2.用户程序编程接口

3.文件的分类

二.标准I/O概述

1.标准I/O的由来

2.流的含义

3.标准IO的缓存类型

三.标准IO编程

1.流的打开fopen

2.流的关闭fclose

3.错误处理perror和strerror

4.流的读写

(1)按字符(字节)输入(读)/输出(写)

(2)按行输入(读)/输出(写)

(3)以指定大小为单位读写文件

(4)格式化输入/输出函数

代码:

5.流的定位

文件IO

文件IO和标准IO的区别

文件IO接口

1.文件打开和关闭

2.文件读写

3.文件定位

案例:给bmp格式图片打码:

动态库和静态库

1.静态库

2.动态库


标准I/O

一.系统调用和用户程序编程接口

1.系统调用

操作系统负责管理和分配所有的计算机资源。为了更好的服务于应用程序,操作系统提供了一组特殊接口--系统调用,通过这组接口用户程序可以使用操作系统内核提供的各种功能,如分配内存,创建进程,实现进程之间的通讯等。(通过程序直接访问计算机资源是很不安全的!!)

2.用户程序编程接口

其实在实际的开发中我们是不直接使用系统调用接口的,而是使用API(用户程序编程接口)原因如下:

1.系统调用接口功能太过简单,无法满足程序需求

2.不同操作系统的系统调用接口不兼容,移植困难。

什么是用户程序编程接口呢,一般我们理解为各种库(C库等)中的函数,这些函数可以直接调用来实现一些功能,提高了代码复用率和可移植性

3.文件的分类

基本分为两类:二者在物理存储上没有区别(对于计算机来说都是二进制方式处理),逻辑上有差别(文件的编码方式不同)

1.文本文件(ASCII码文件):文件中的内容是用ASCII码或者字符来显示的

2.二进制文件:文件中存放的内容是二进制数据

Linux文件分类:

1.普通文件:-

2.目录文件:d

3.管道文件:p

4.套接字文件:s

5.链接文件:l

6.块设备文件:b

7.字符设备文件:c

二.标准I/O概述

1.标准I/O的由来

标准IO其实指的就是ANSI C中定义的用于I/O操作的一系列函数。

2.流的含义

标准IO的核心对象就是流。当用标准IO打开一个文件时,就会创建一个FILE结构体来描述该文件。

每一个应用程序会默认定义三个文件流指针:stdin,stdout,stderr.

stdin:标准输入流

stdout:标准输入流

stderr:标准错误输出流

man手册:

man 1:查看shell命令

man 2:查看系统调用

man 3:查看C标准库

3.标准IO的缓存类型

1.全缓存

文件的读写一般都会采用全缓存的形式,在这种情况下,当填满缓存区后才进行实际的IO操作。

刷新条件:1.使用fflush刷新。2.缓存区满。3.程序结束

2.行缓存

标准输入和标准输出采用的方式。

刷新条件:1.遇到\n 

2.使用fflush刷新

3.程序结束。

4.缓存区满

3.无缓存

标准错误输出使用的方式。在对流的读写时会立刻操作实际的文件,使出错信息可以立刻显示在终端上。

三.标准IO编程

1.流的打开fopen

所需头文件#include <stdio.h>
函数原型FILE* fopen(const char* path,const char* mode)
函数参数path:包含要打开的文件路径及文件名
mode:文件打开方式
函数返回值成功:返回文件流指针,该指针可以认为指向一个文件,可以利用它简介操作文件
失败:NULL

mode取值表

r或rb打开只读文件,不会创建新文件,默认从开头进行读取
r+或r+b打开可读写文件,不会创建新文件,默认从开头进行读取
w或wb打开只写文件,若文件存在则文件长度为0(清空),若文件不存在则建立该文件,从头写入
w+或w+b打开可读写文件,若文件存在则文件长度为0(清空),若文件不存在则建立该文件,从头写入
a或ab以附件的方式打开只写文件,若不存在则建立该文件,存在则将写入的数据加到文件尾,原来的文件内容会保留
a+或a+b以附加的方式打开可读写的文件,若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。

注:在每个选项中加入b字符用来告诉函数库打开的文件为二进制文件,而非纯文本文件。在Linux系统下r和rb其实是没有什么去别的,但是在window平台下有区别!!例:window平台下换行符为\r\n,若用文本文件形式解读会被解释为一个换行符\n,如果按二进制文件来解读,则会正常读取两个字节

2.流的关闭fclose

所需头文件#include <stdio.h>
函数原型int fclose(FILE *stream);
函数参数stream:已打开的流指针
函数返回值成功:0
失败:EOF

3.错误处理perror和strerror

所需头文件#include <stdio.h>
函数原型void perror(const char* s);
函数参数s:在标准错误流上输出的信息 例:perror("fail to open");
函数返回值
所需头文件

#include <string.h>

#include <errno.h>

函数原型char* strerror(int errnum)
函数参数错误码
函数返回值错误码对应的错误信息

4.流的读写

(1)按字符(字节)输入(读)/输出(写)

读fgetc/getc

所需头文件#include <stdio.h>
函数原型

int fgetc(FILE* stream)

int getc(FILE*  stream)

函数参数stream:要输入的文件流
函数返回值成功:返回当前读取的字符
失败:EOF(或者到文件末尾)

代码如下:

写 putc/fputc

字符输出函数语法要点
所需头文件#include <stdio.h>
函数原型

int putc(int c,FILE* stream)

int fputc(int c,FILE* stream)

函数参数c:要输出的字符 stream:要输出的文件流可以为stdout
函数返回值

成功:要输出的字符c

失败:EOF

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
int main()
{
//只读方式打开1.txt
FILE *fp = fopen("1.txt", "a");
if(fp == NULL)
{
	printf("fopen failed\n");
	return -1;
}

//向文件写入内容.
int ret = fputc('r', fp);
if(ret == EOF)
{
	printf("fputc failed\n");
	return -1;
}

printf("end of file-------\n");
return 0;
}

(2)按行输入(读)/输出(写)

行输入函数语法要点
所需头文件#include <stdio.h>
函数原型

char* gets(char* s)

char* fgets(char*s,int size,FILE* stream)

函数参数s:存放输入字符串的缓冲区首地址
size:输入的字符串长度
stream:对应的文件流指针
函数返回值成功:返回字符串首地址s
失败或到达文件末尾:返回NULL

代码例子如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
int main()
{
char str[32];
FILE *fp = fopen("1.txt", "r");
if(fp == NULL)
{
	printf("fopen failed\n");
	return -1;
}
//gets(str);   //不做越界检查.
//fgets(str, 32, stdin);

if(NULL == fgets(str, sizeof(str), fp))
{
	printf("fgets failed\n");
	return -1;
}

printf("%s", str);

return 0;
}

注意:

1.gets函数容易造成缓冲区溢出,不推荐使用!!!!

2.fgets是从指定的流中读取一行数据,当遇到\n时表示一行结束,或者读取size-1个字符后返回(如果size比文件中一行数据个数小时,会默认在后面加\0表示字符串结束)所以不能保证每次都能读出一行!!!

3.当size-1的值大于一行的个数时,遇到\n正常结束,并且读取的内容包含\n,两种清空都要以\0为结束标志。

行输出函数语法要点
所需头文件#include <stdio.h>
函数原型

int puts(const char* s)

int fputs(const char* s,FILE* stream)

函数参数s:存放输出字符串的缓冲区首地址
stream:对应的文件流指针
函数返回值成功:返回s
失败:返回NULL

代码例子:

* #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include <strings.h>
  int main()
  {
  char str[32];
  FILE *fp = fopen("1.txt", "w");
  if(fp == NULL)
  {
  	printf("fopen failed\n");
  	return -1;
  }

  if(EOF == fputs("hello", fp))
  {
  	printf("fputs failed\n");
  	return -1;
  }

  return 0;
  }

(3)以指定大小为单位读写文件

fread函数的语法要点
所需头文件#include <stdio.h>
函数原型size_t fread(void* ptr,size_t size,size_t nmemb,FILE* stream)
函数参数ptr:存放读入记录的缓冲区首地址,可以为任意指针类型
size:读取的每个数据的字节大小
nmemb:读取的数据个数
stream:要读取的文件流
函数返回值成功:返回实际读取的nmemb数目
失败或文件末尾:0或者比nmemb小的数字

代码:

struct data
{
	int num;
	char ch;
	char name[20];
};

int main()
{	
	char buf[64] = {"hello"};
	struct data s = {12, '\0', "hjk"};
	struct data s2;

	FILE *fp = fopen("2.txt", "wb+");
	if(fp == NULL)
	{
		printf("fopen failed\n");
		return -1;
	}
	
	int ret = fwrite(&s, sizeof(s), 1, fp);
	if(ret == 0)
	{
		printf("fwrite failed\n");
		return -1;
	}
	
	fclose(fp);
	fp = fopen("2.txt", "rb");
	
	ret = fread(&s2, sizeof(s2), 1, fp);
	if(ret == 0)
	{
		if(feof(fp) != 0)
		{
			printf("end of file\n");			
		}
		else
		{
			printf("fread failed\n");
			return -1;
		}
	}
	
	printf("%d--%s--%c\n", s2.num, s2.name, s2.ch);
	
	return 0;

}
fwrite函数的语法要点
所需的头文件#include <stdio.h>
函数原型size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE*stream)
函数参数ptr:存放写入记录的缓冲区首地址
size:写入的每个记录数据的大小
nmemb:写入的数据个数
stream:要写入的文件流
函数返回值成功:返回实际写入的nmemb数目
失败:返回:0或则比nmemb小的数目

注:

1.feof():判断是否到达文件末尾

if(feof(fp)!=0)

2.ferror():判断文件是否出错

if(ferror(fp)!=0)

(4)格式化输入/输出函数

格式化输入函数语法要点
所需头文件#include <stdio.h>
函数原型

int fscanf(FILE* fp,const char* format,.......)

int sscanf(char* buf,const char* format,........)

函数传入值format:输入格式
fp:作为输入的流
buf:作为输入的缓冲区
函数返回值成功:输出字符数
失败:EOF
格式化输出函数语法要点
所需头文件#include <stdio.h>
函数原型

int fprintf(FILE* fp,const char* format,.......)

int sprintf(char* buf,const char* format,......)

函数参值format:输出的格式
fp:接收输出的流
buf:接收输出的缓存区
函数返回值成功:输出字符数(sprintf返回存入数组的字符数)
失败:EOF

代码:

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

int main(int argc, char *argv[])
{
	int tmp = 35;
	int hum = 56;
	int light = 1026;
char name[20] = "ioioio";
int num = 12;

char env[1024] = {0};

FILE *fp = fopen("1.txt", "a");
if(fp == NULL)
{
	printf("failed\n");
	return -1;
}
char info[1024] = {0};

sprintf(info, "name:%s,num:%d", name, num);
printf("%s\n", info);

sprintf(env, "tmp:%d hum:%d light:%d\n", tmp, hum, light);
//printf("%s", env);
if(EOF == fputs(env, fp))
{
	printf("fputs failed\n");
	return -1;
}

fclose(fp);

return 0;
}

5.流的定位

fseek函数语法要点
所需头文件#include <stdio.h>
函数原型int fseek(FILE* stream,long offset,int whence)
函数参数stream:要定位的文件流
offset:相对于基准值的偏移量

whence:

SEEK_SET 代表文件起始位置

SEEK_END 代表文件结束位置

SEEK_CUR 代表文件当前读写位置

函数返回值成功;0
失败:EOF
ftell函数语法要点
所需头文件#include <stdio.h>
函数原型long ftell(FILE* stream)
函数参数stream:要定位的文件流
函数返回值成功:返回当前读写位置
失败:EOF

案例:用定位操作判断一个文件大小

#include <stdio.h>

int main(int argc, char *argv[])
{ 
    FILE* fp;
    if(argc!=2)
    {
        printf("Usage:%s<filename1>",argv[0]);
        return -1;
    }
    if((fp=fopen(argv[1],"r"))==NULL)
    {
        perror("fail open\n");
        return -1;
    }
    fseek(fp,0,SEEK_END);//将文件定位到末尾
    printf("The size of %s is %ld\n",argv[1],ftell(fp));
    return 0;
} 

案例:用读写操作复制一个文件

代码如下:

#include <stdio.h>
#include <string.h>
#define N 64
int main(int argc, char *argv[])
{ 
    char arr[N]={0};
    int n;
    //文件从命令行进行输入
    FILE *fp1=fopen(argv[1],"r");
    FILE *fp2=fopen(argv[2],"w");
    //命令行参数必须为三个
    if(argc!=3)
    {
        printf("Usage:%s<filename1><filename2>",argv[1]);
        return -1;
    }
    if(fp1==NULL)
    {
        printf("open fail\n");
        return -1;
    }
    //打开文件失败
    if(fp2==NULL)
    {
        printf("open fail\n");
        fclose(fp1);//关闭文件
        return -1;
    }
    //循环读写文件,直到文件末尾退出循环
    while((n=fread(arr,sizeof(char),N,fp1))>0)
    {
        fwrite(arr,sizeof(char),n,fp2);//n为每次读取的字节个数,不能为数组大小以免没有读到那么多
        //数据,那么就会在原文件后面添加一些数据。
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
} 

文件IO

Linux文件IO其实就是系统提供的接口我们又称为系统调用。那么它与标准IO相比有什么区别呢?

文件IO和标准IO的区别

标准IO属于c库函数 

1、接口是由C标准(ANSI C标准)提供的,与语言以及程序有关

2、又被称为高级IO(所谓的高级就是带缓存的IO)!!!   

3、一样可以用于所有普通文件的读写操作. 

 文件IO---> 系统调用 

1、接口使用操作系统(POSIX标准)提供, 与操作系统有关   

2、又被称为低级IO(不带缓存的IO) 

3、Linux系统下有的特殊类型文件只能用文件IO来操作,例如: 管道、设备文件等

文件IO接口

1.文件打开和关闭

open函数语法要点
所需头文件

#include <sys/stat.h>

#include <fcntl.h>

函数原型int open(const char* pathname,int flags,int perms);
函数参数pathname:被打开的文件名
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_APPEND:以添加方式打开文件,在写文件时,文件读写位置自动只想文件的末尾,即将写入的数据添加到文件末尾。
O_TRUNC:若文件已经存在,那么就会删除文件中所有数据,并且设置文件大小为0
O_NOCTTY:使用本参数,若打开的是终端文件,那么该终端不会成为当前进程的控制终端。
perm:新建文件的存取权限用八进制表示,R/W/X分别表示读写执行权限 rw-rw-r--: 0664 
函数返回值成功:返回文件描述符
失败:返回-1
close函数语法要点
所需头文件#include <unistd.h>
函数原型int close(int fd);
函数输入值fd:文件描述符
函数返回值0:成功
-1:出错

2.文件读写

read函数语法要点
所需头文件#include <unistd.h>
函数原型ssize_t read(int fd,void* buf,size_t count)
函数传入值fd:文件描述符
buf:指定存储器读出数据的缓存区
count:指定读出的字节数
函数返回值成功:读到的字节数
0:已到达文件末尾
-1:出错
write函数语法要点
所需头文件#include <unistd.h>
函数原型ssize_t write(int fd,void* buf,size_t count)
函数传入值fd:文件描述符
buf:指定存储器写入数据的缓冲区
count:指定读书的字节数
函数返回值成功:已写的字节数
-1:出错

3.文件定位

lseek函数语法要点
所需头文件

#include <unistd.h>

#include <sys/types.h>

函数原型off_t lseek(int fd,off_t offset,int whence)
函数传入值fd:文件描述符
offset:相对于基准点whence的偏移量,以字节为单位,正数向前移,负数向后移
whence:
SEEK_SET:文件起始位置
SEEK_CUR:文件当前读写位置
SEEK_END:文件的结束位置
函数返回值

成功:文件当前读写位置

-1:出错

案例:给bmp格式图片打码:

 

代码如下:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
//三原色结构体类型定义
typedef struct color
{
    unsigned char B;//蓝
    unsigned char G;//绿
    unsigned char R;//红
}COLOR;
int main(int argc, char *argv[])
{ 
    //打开目标文件,命令行传参
    int src=open(argv[1],O_RDWR);
    int n;
    //命令行参数个数必须为8个
    if(argc!=8)
    {
        //提示参数输入格式
        printf("Usage:%s<file1><file2><x><y><w><h><len>",argv[0]);
        return -1;
    }
    //打开文件失败
    if(-1==src)
    {
        perror(argv[1]);
        return -1;
    }
    //创建文件并打开
    int dst=open(argv[2],O_RDWR | O_CREAT | O_TRUNC,0666);
    if(-1==dst)
    {
        perror(argv[2]);
        return -1;
    }
    //定义一个数组存放文件数据
    char buff[1024*1024*4]={0};
    //读取目标文件
    n=read(src,buff,sizeof(buff));
    //将图片高度宽度取出,从18字节开始
    int* pw=(int*)&buff[18];
    int* ph=(int*)&buff[18+4];
    int w=*pw;//图片的宽
    int h=*ph;//图片的高
    int x=atoi(argv[3]);//开始的像素宽度
    int y=atoi(argv[4]);//开始的像素高度
    int mask_w=atoi(argv[5]);//马赛克区域宽度
    int mask_h=atoi(argv[6]);//马赛克区域高度
    int mask_len=atoi(argv[7]);
    int num=0,cut=0;
    COLOR temp[100],color;//保存三原色
    int l=54;//BMP前54个字节为头部,所以指向54
    l=l+3*(w*(h-y-mask_h)+x);//加入起始位置后我们的起始偏移量如图所示
    int i,j;
    for(i=0;i<mask_h;i++)
    {
        for(j=0;j<mask_w;j++)
        {
            if(j%mask_len==0)//将LEN*LEN个像素点变成一个颜色
            {
                if(i%mask_len==0)
                {
                    color.B=buff[l];
                    color.G=buff[l+1];
                    color.R=buff[l+2];
                    temp[cut]=color;//保存前一行的像素值
                    cut++;
                }
                else
                {
                    color=temp[num];//在高度为10的像素内使用一种颜色
                    num++;
                }
            }
            buff[l]=color.B;
            buff[l+1]=color.G;
            buff[l+2]=color.R;
            l=l+3;
        }
        l=l+3*(w-mask_w);//偏移到下一行
        cut=0;
        num=0;
    }
    write(dst,buff,n);///将打马赛克的BUFF写入文件
    close(src);//关闭文件
    close(dst);
    return 0;
} 

动态库和静态库

什么是库呢?我们可以把它看作是可执行文件的二进制形式。可以加载到内存进行,当你想要把你写的算法给别人使用但又不想别人知道源码,往往采用库的方式,而库又分为静态库和动态库,接下里我们分别介绍他们的用法和区别。

1.静态库

1.在编译阶段被链接

2.生成的可执行文件体积较大(链接了静态库)

3.可执行文件的移植方便(库已经链接好了)

编译静态库:

用法:ar [选项] 【libxx.a】【目标文件1.o】【目标文件2.o】.....

选项:c:创建一个库,不管以前是否存在。r:替换库中重名的,已经存在的模块。s:建立索引方式,可以快速查找库中模板。

例: ar crs libmy.a fun1.o fun2.o

编译时需要指明库的路径,使用 -L与 -l来完成

例: gcc test.c -L ./ -lmyfun -L ./day2/ -lmyfun2

-L:表示需要链接库,后续紧跟库的路径

-l:紧跟库的名字 不需要加lib 和后缀 .a

2.动态库

1.在运行阶段被链接。

2.生成的可执行文件体积较小。

3.可执行文件的移植不方便(运行时需要库,所以还需要移植库)。

编译动态库

用法: 1.先生成目标文件 gcc -c -fPIC fun.c(-fPIC:创建与地址无关的编译程序,表示当动态库里的模板被链接到程序中时,放在任意地址都可以)

2. gcc -shared -o libmyfun.so fun1.o fun2.o...

程序运行链接动态库方式:

1.将动态库移动到/usr/lib 或 /lib下(注意不要覆盖系统自带的库!!)

2.临时修改环境变量LD_LIBRARY_PATH=./ ./a.out 只在当前有效。

3.添加 /etc/ld.so.conf.d/xxx.conf文件,把库所在路径添加到xxx.conf文件末尾,并执行ldconfig刷新。这样操作,加入的目录下的所有库文件都可见。

补充知识点:

EOF : 是一个宏,C中宏定义 #define EOF -1;

为什么不直接返回-1,而是返回EOF呢?
既然返回的是一个字节,为什么返回值确用4字节的int表示,不用unsigned char这1个字节类型表示,或直接

用char类型表示哪?

原因就是出在返回值要能表示-1.unsigned char表示无符号的,而这返回值,需要是-1才可以表示文件读到末尾了,那可以char类型表示-1,但是-1在char类型中表示的值为0xff,然而它本身就是一个字节的内容,如果文件中的一个字节是0xff,难道就认为文件结束,这是不合理的,用int作返回值,就不会出现这个问题,-1用int表示为0xffffffff;而字节值如果0xff这它返回的int值就会是0x000000ff,这样就可以区别开字节值为0xff和文件结束标识EOF.

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值