首页 > 编程语言 > 详细

C语言入门学习the end——stdio标准流(未写完)

时间:2021-04-13 17:01:28      阅读:24      评论:0      收藏:0      [点我收藏+]

镇楼图

Pixiv:

这次主要解决输入、输出、文件操作等的问题,先来认识一个常用的宏——EOF

EOF用来表示读写失败。

==================

〇、流是什么?

要想实现输入输出必须要使用流来控制。

【流】是一种抽象的概念,不同的【流】具有统一的一些特征,通过【流】可以控制键盘、文件、屏幕。

在C语言中有三种【流】分别是

stdin 标准输入流:通过键盘来输入数据

stdout 标准输出流:用来输出数据

stderr 标准错误流:用来输出一些error错误或者warning警告

==================

一、流的特征

①读 / 写 Read / Write

这个流是只读,只写 还是 读写 的。

具有只读的流只能读取,而无法写入

具有只写的流只能写入而无法读取

具有读写的流可以在写入的同时进行读取

②文本 / 二进制 text / binary

text模式与binary模式最大的区别在于处理换行符的问题上,使用text模式会把\n或\r看成一个换行符,而binary模式只会把\n看成两个字符。

③缓冲区 buffer

【【】】

④操作对象 orientation

刚打开时,流并不会指定对象

当进行输入输出操作时,流会指向对象。

在C语言里只有两种对象

(1)面向字节(byte-oriented)

(2)面向宽字节(wide-oriented)

大部分所接触的操作对象都是byte只有使用C语言标准库[wchar.h][]中才定义了宽字节

⑤指示器

具有三种指示器,分别是文件尾指示器(eof指示器)、位置指示器、错误状态指示器(error指示器)

从名字上你也能理解指示器是做什么的

位置指示器用来判断当前流的位置在哪

文件尾指示器用来判断位置是否在文件末尾

错误状态指示器用来判断当前流有没有错误,是什么错误

==================

二、文件打开关闭

①fopen ( const char* filename,const char* mode )

作用:

打开文件

参数:

filename

文件路径,这个文件路径可以是.../test.txt相对路径,也可以是绝对路径

mode

在C语言里提供了3种最基本的模式,然后外加+b可以构成12种模式

模式 含义 文件存在时 文件不存在时
r 只读 从头读取 打开失败
w 只写 从头写入(覆盖) 创建新文件
a 追加 末尾写入 创建新文件
r+ 扩展只读 从头读写 报错
w+ 扩展只写 从头读写(覆盖) 创建新文件
a+ 扩展追加 末尾读写 创建新文件

b表示为binary模式,可以和其他模式混用比如r+bwbab

返回值

若成功,则返回文件流的指针

若失败,则返回NULL并设置errno

注:用 b 可以将text模式更改为binary模式;

使用w模式是会将所有text清空再进行写入,使用r+是将写入的字符进行替换

②fclose ( FILE* stream )

作用:

关闭文件

参数

stream文件指针

返回

若成功,则返回0

若失败,则返回EOF并设置errno

---hello.txt-------------
Hello World
你好鸭~

---hello.c----------------
#include<stdio.h>
#include<stdlib.h>

int main(void){
    FILE* fp = fopen("hello.txt","r");
    int c;
    if(fp == NULL){
        printf("打开失败");
        exit(EXIT_FAILURE);
    }

    while((c = fgetc(fp)) != EOF){
        putchar(c);
        //这是输出函数,具体作用会在下面说明
    }
    fclose(fp);
}

==================

三、文件读取

fgetc ( FILE * stream )

getc ( FILE * stream )

fgetc是函数,getc是宏,这两种功能一模一样只是具体细节略有不同。

getc作为宏可以避免fgetc函数调用的堆栈,速度略微快一些;但getc是宏,所以为了防止bug不能使用带有副作用的参数

副作用的参数是指除了有传递作用还能进行一次运算的参数,比如fp++这样的参数

作用:

读取当前字符并推进位置指示器

若失败则返回EOF

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

作用

从stream中读取size-1个字符然后存储到s中 。若读取中遇到EOF、\n则会结束本次读取

参数

? s 字符指针

? size 要读取的字符数(包括‘\0‘

? stream FILE指针

返回

若调用成功,则返回s所指向的内存地址

若读取中遇到EOF,则设置eof指示器,返回NULL

若还未读取就遇到EOF,s不会发生变化,返回NULL

若读取时错误,则设置error指示器,返回NULL

==================

四、文件写入

① fputc ( int ch , FILE * stream )

putc ( int ch , FILE * stream )

作用:

将字符ch写入到stream中,并推进位置指示器

参数

? ch 要写入的字符

? stream FILE指针

返回

若成功写入,返回写入的字符ch

若写入失败,返回EOF

fputc为函数

putc为宏

---hello.txt-------------
Hello world!
    
---test.txt-------------

    
---hello.c----------------
#include<stdio.h>
#include<stdlib.h>

int main(void){
    FILE * fp1,* fp2;
    char c;
    if((fp1 = fopen("hello.txt","r")) == NULL){
        printf("打开失败");
        exit(EXIT_FAILURE);
    }

    if((fp2 = fopen("test.txt","w")) == NULL){
        printf("打开失败");
        exit(EXIT_FAILURE);
    }

    while((c = fgetc(fp1)) != EOF){
		fputc(c,fp2);
		printf("%c",c);
    }

    fclose(fp1);
    fclose(fp2);
}

运行前

只有hello.c、hello.txt两个文件

【【】】

运行后

多出新的test.txt文件,而且内容和hello.txt一致

【【】】

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

作用

将字符串(不包括‘\0‘)写入到stream中

参数

? s 字符指针

? stream FILE指针

返回

若成功,返回非0值

若失败,返回EOF

---test.txt-------------

    
---hello.c----------------
#include<stdio.h>
#include<stdlib.h>

int main(void){
    FILE * fp;

    if((fp = fopen("test.txt","a")) == NULL){
        printf("打开失败");
        exit(EXIT_FAILURE);
    }
	fgets("Hello World\n",fp);
    fgets("你好鸭~",fp);
    
    fclose(fp);
}

==================

五、stdin、stdout流的读取/写入

①int getchar()

作用

? 从stdin流中读取一个字符

返回

? 成功时返回读取的字符

? 失败时为EOF

②int putchar(int ch)

作用

? 将字符ch写至stdout流中

返回

? 成功时返回读取的字符

? 失败时为EOF

③int puts(const char *str)

作用

? 将字符串str写至stdout流中

返回

? 成功时返回非负值

? 失败时为EOF

int ch = getchar();
putchar(ch);
char *s1 = "123456789\n";
char s2[] = "123456789";
puts(s1);
puts(s2);

==================

六、格式标签的构成

我们清楚输入输出要么是字符串、要么是宽字符串,那么有什么方法可以输入输出一些字符串以外的一些数据呢?

格式化正是为了解决输入输出其他数据类型而有的特殊语法

这是将其他数据类型转换为字符型的重要方法

同时这也是用来输入变量的重要方法

比如printf("%d",n)那么就可以将整数型的n转换为字符型从而进行输入输出

组成部分

%[flags] [width] [.precision] [length] specifier

①说明符specifier

%加上一些字母来表示转换说明符

符号 含义
c 单个字符
s 多个字符
d/i 整数
u 无符号位整数
o 无符号位八进制整数
x/X 无符号位十六进制整数(x表示所涉及的字母小写,X表示所涉及的字母大写)
p 指针
f 浮点数
e/E 科学计数法(e表示所涉及的字母小写,E表示所涉及的字母大写)
a/A 十六进制浮点数(a表示所涉及的字母小写,A表示所涉及的字母大写)
g/G 分为%f和%e/E,按照结果最短的来(g表示所涉及的字母小写,G表示所涉及的字母大写)
注意(1)浮点数的inf和nan

②标志flags

符号 含义
-
+ 强制让正数显示加号
< space > 可插入一个空格
# 与 o、x 或、X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X
与 e/E 、 f、a/A 一起使用时,会强制输出包含一个小数点的数。默认情况下,若后边没有数字,则不会显示显示小数点
与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除
0 若结果位数少于width则填充0

③宽度width

符号 含义
< numbers > 用数字表示字符的长度。若不足会默认用空格填充。当让可以用flags中的0来填充0
* 会增加一个参数,用变量来表示你需要的 < numbers >

④精度.precision

符号 含义
< numbers > 对于整数,和width的效果一致
对于浮点数,代表小数位数
对于字符串s,代表输入的最大位数
对于字符c,没有任何效果
* 会增加一个参数,用变量来表示你需要的 < numbers >

⑤长度length

【【】】

由图可以得出l是对应long修饰符,h是对应short修饰符,其余一般也不会用到。

==================

七、格式化读写

①int scanf( const char* format, ... )

作用

从stdin流中读取字符串,读到参数中

参数

? format 格式字符

返回

? 返回参数个数

②int printf( const char* format, ... )

作用

输出字符串至stdout流上

参数

? format 格式字符

返回

? 返回打印字符数

int a,b;
scanf("%d\n%d",&a,&b);
int c = printf("%d %d",a,b);
printf("%d",c);

③int fscanf( FILE * stream,const char* format, ... )

作用

从FILE文件流中读取字符串,读到参数中

参数

? FILE* 文件指针

? format 格式字符

返回

? 返回参数个数

④int fprintf( const char* format, ... )

作用

输出字符串至FILE文件流上

参数

? FILE* 文件指针

? format 格式字符

返回

? 返回打印字符数

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

int main(void){
    FILE *fp;
    if((fp = fopen("date.txt","w")) == NULL){
        printf("打开失败");
        exit(EXIT_FAILURE);
    }
    
    int buf = fprintf(fp,"hp:%d,atk:%d,def:%d",100,30,72);
    printf("打印字符数为%d\n\n\n",buf);
    fclose(fp);
    
    int hp,atk,def;
    if((fp = fopen("date.txt","r")) == NULL){
        printf("打开失败");
        exit(EXIT_FAILURE);
    }
    fscanf(fp,"hp:%d,atk:%d,def:%d",&hp,&atk,&def);
    fclose(fp);
    
    printf("hp是%d,atk是%d,def是%d",hp,atk,def);
    
}

==================

八、位置指示器

现在我们要更进一步来更高效的完成读写操作,貌似到现在位置我们基本上输入输出都从头到尾一个个输入下来的,并没有跳到着输入一个跳到那输入一个。但这种情况也是经常遇到的,现在来操纵一个叫【位置指示器】的东西,它移动到哪,就决定了你在哪读写,在没有这一块的内容的情况下,位置指示器是从头到尾逐步增加的。当你学完这一块内容后也许你能逆着输入。

①long ftell ( FILE* stream )

作用

? 返回当前在stream文件中的位置指示器的位置。

? 若操作失败,则会出现-1L

----test.txt----------
Hello World

----hello.c-----------
#include<stdio.h> 

int main(void){
	FILE* fp;
	fp = fopen("test.txt","r");
	printf("当前位置在%ld\n",ftell(fp));
	fgetc(fp);
	fgetc(fp);
	printf("当前位置在%ld\n",ftell(fp));
	for(int i = 0;i < 10;i++) {
		fgetc(fp);
	}
	printf("当前位置在%ld\n",ftell(fp));
	fgetc(fp);
	printf("当前位置在%ld\n",ftell(fp));
}
//结果分别显示0,2,11,11
//由此可以推断在最末尾时无法再推进位置指示器

②rewind ( FILE*stream )

作用

? 将位置指示器移动到最开始的位置

③fseek ( FILE*stream , long offset , int origin )

作用

? 将位置指示器移动到任意指定的位置

? 计算从origin初始量开始,offset偏移量

\[pos \ = \ origin + offset\通过对初始量的加减来指定偏移位置 \]

参数

? FILE* 文件指针

? offset 偏移量

? origin 初始量

origin参数值 含义
SEEK_SET 文件头
SEEK_CUR 当前位置
SEEK_END 文件尾
返回

? 成功时为0

? 失败时为非零

/*在搭配二进制模式、结构体时可以用C构建出一个数据库,然后可以通过fseek函数来查找到指定的数据
这里就不演示了,有兴趣的可以和我探讨探讨

另外这里可以引申出因为不同系统对于字符存放的某些差别导致的可移植性问题。

==================

九、错误处理

在经典C的标准流当中,我们打开一个文件需要使用文件指针,同样,stdin、stdout、stderr同样也是一个指针,我们也可以使用fputs、fprintf、fputc等具有写入功能的函数来输出到屏幕上

fputs("Hello World",stdout);
//要输出到stdout流 / 屏幕上不一定要使用printf,只要具有写入功能的函数都可以指定到stdout流上

当然这里我们侧重讲如何错误处理,

==================

十、IO缓冲区

众所周知,CPU的速度和IO设备的速度是天差地别,为了弥补这种速度的差距,引入了缓冲区这一概念。

就在C语言的视角来看,以前你可能是这么认为的

【程序】←→【IO流】

但现在你应该这么看

【程序】←→【缓冲区】←→【IO流】

现在我们研究的对象正是这中间级【缓冲区】

一个误区:读写函数在调用完成后就立刻读写

这是很自然也是很容易误导人的观点,你可以这么对他说“函数一调用完就结束了鸭”

好像很有道理,但再一次强调缓冲区的功能,读写操作必须要先经过缓冲区才能到达对面,如果缓冲区都无法通过,那么也是不可能到达对面的。

比如

#include<stdio.h>

int main(void){
	FILE* fp;
    fp = fopen("test.txt","w");
    //这里就不考虑fopen失败情况了
    fputs("Hello World",fp);
    getchar();
    fclose(fp);
}

首先我们可以看到一个getchar,也就是如果我们不输入,程序就会一直等待我们去输入。

现在先执行程序不要输入来看看结果会是怎样

【【】】

已经执行了fputs函数却没有写入到fp中,这个误区已经不攻自破了。

现在再来输入一下试试。

【【】】

只有我们输入完成后才将fputs要写入的内容写入到fp中。

那么这个过程发生了什么?

在C语言中提供了三种缓存模式

——按块缓存(全缓存)

? 数据一直存在缓冲区内,当缓冲区满溢的时候向流写入数据

——按行缓存(缓冲\n之前的数据)

? 每读入一行数据,缓冲区向流写入一行数据

——不缓存(不经过缓冲区直接读写)

在默认情况下是全缓存模式

但我们可以通过函数setvbuf来设置缓冲模式

①int setvbuf ( FILE*stream , char * buffer , int mode , size_t size )

作用

? ■更改stream文件流的mode缓冲模式

? ■

? ■

参数

? FILE* 文件指针

? buffer 为char*类型

? mode 表示缓冲模式

? size 缓冲区的大小

mode参数 含义
_IOFBF 全缓冲file
_IOLBF 行缓冲line
_IONBF 不缓冲none
返回

? 成功时为0

? 失败时为非零


==================

LAST、索引 csv 文件

csv文件是一种表格文件,可以通过excel导出得到。

一开始我尝试使用结构体,发现太复杂了,而且无法用一个变量索引到结构体的成员变量,不具有通用性(可能是我太菜了)

【【】】

于是我改成三维数组了(因为博主太菜,只能一个个getc)

#include<stdio.h> 
#include<stdlib.h>
#define f(n) (n)

void csvdata(FILE*,int);

int main(void){
	FILE* fp;
	char n; 
	printf("请输入要截取的数据数:\n",&n);
	scanf("%d",&n);
	csvdata(fp,n);
}

void csvdata(FILE* fp,int n){
	fp = fopen("data.csv","r");
	char s[5][3][20];
	for(int i = 0;i < 5;i++){
		for(int j = 0;j < 3;j++){
			int count = 0;
			for(int k = 0;;k++){
				s[i][j][k] = getc(fp);
				int ch = s[i][j][k];
				if(ch == ‘\n‘ || ch == ‘,‘)break;
				count++;
			}
			
			for(int k = 0;k <= count-1;k++){
				printf("%c",s[i][j][k]);
			}
			printf("\n");
		}
	}
	fclose(fp); 	
}

参考资料

https://fishc.com.cn

http://www.cplusplus.com/reference

https://www.icourse163.org/course/ZJU-200001

C语言入门学习the end——stdio标准流(未写完)

原文:https://www.cnblogs.com/AlienfronNova/p/14596395.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!