Pixiv:
这次主要解决输入、输出、文件操作等的问题,先来认识一个常用的宏——EOF
EOF用来表示读写失败。
要想实现输入输出必须要使用流来控制。
【流】是一种抽象的概念,不同的【流】具有统一的一些特征,通过【流】可以控制键盘、文件、屏幕。
在C语言中有三种【流】分别是
stdin
标准输入流:通过键盘来输入数据stdout
标准输出流:用来输出数据stderr
标准错误流:用来输出一些error错误或者warning警告这个流是只读,只写 还是 读写 的。
具有只读的流只能读取,而无法写入
具有只写的流只能写入而无法读取
具有读写的流可以在写入的同时进行读取
text模式与binary模式最大的区别在于处理换行符的问题上,使用text模式会把\n或\r看成一个换行符,而binary模式只会把\n看成两个字符。
【【】】
刚打开时,流并不会指定对象
当进行输入输出操作时,流会指向对象。
在C语言里只有两种对象
(1)面向字节(byte-oriented)
(2)面向宽字节(wide-oriented)
大部分所接触的操作对象都是byte只有使用C语言标准库[wchar.h][]中才定义了宽字节
具有三种指示器,分别是文件尾指示器(eof指示器)、位置指示器、错误状态指示器(error指示器)
从名字上你也能理解指示器是做什么的
位置指示器用来判断当前流的位置在哪
文件尾指示器用来判断位置是否在文件末尾
错误状态指示器用来判断当前流有没有错误,是什么错误
打开文件
文件路径,这个文件路径可以是.../test.txt
相对路径,也可以是绝对路径
在C语言里提供了3种最基本的模式,然后外加+
、b
可以构成12种模式
模式 | 含义 | 文件存在时 | 文件不存在时 |
---|---|---|---|
r | 只读 | 从头读取 | 打开失败 |
w | 只写 | 从头写入(覆盖) | 创建新文件 |
a | 追加 | 末尾写入 | 创建新文件 |
r+ | 扩展只读 | 从头读写 | 报错 |
w+ | 扩展只写 | 从头读写(覆盖) | 创建新文件 |
a+ | 扩展追加 | 末尾读写 | 创建新文件 |
b表示为binary模式
,可以和其他模式混用比如r+b
、wb
、ab
若成功,则返回文件流的指针
若失败,则返回NULL并设置errno
b
可以将text模式更改为binary模式;关闭文件
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是函数,getc是宏,这两种功能一模一样只是具体细节略有不同。
getc作为宏可以避免fgetc函数调用的堆栈,速度略微快一些;但getc是宏,所以为了防止bug不能使用带有副作用的参数
副作用的参数是指除了有传递作用还能进行一次运算的参数,比如fp++
这样的参数
读取当前字符并推进位置指示器
若失败则返回EOF
从stream中读取size-1
个字符然后存储到s中 。若读取中遇到EOF、\n则会结束本次读取
? s 字符指针
? size 要读取的字符数(包括‘\0‘
)
? stream FILE指针
若调用成功,则返回s所指向的内存地址
若读取中遇到EOF,则设置eof指示器,返回NULL
若还未读取就遇到EOF,s不会发生变化,返回NULL
若读取时错误,则设置error指示器,返回NULL
将字符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一致
【【】】
将字符串(不包括‘\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流中读取一个字符
? 成功时返回读取的字符
? 失败时为EOF
? 将字符ch写至stdout流中
? 成功时返回读取的字符
? 失败时为EOF
? 将字符串str写至stdout流中
? 成功时返回非负值
? 失败时为EOF
int ch = getchar();
putchar(ch);
char *s1 = "123456789\n";
char s2[] = "123456789";
puts(s1);
puts(s2);
我们清楚输入输出要么是字符串、要么是宽字符串,那么有什么方法可以输入输出一些字符串以外的一些数据呢?
格式化正是为了解决输入输出其他数据类型而有的特殊语法
这是将其他数据类型转换为字符型的重要方法
同时这也是用来输入变量的重要方法
比如printf("%d",n)
那么就可以将整数型的n转换为字符型从而进行输入输出
用 %
加上一些字母来表示转换说明符
符号 | 含义 |
---|---|
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表示所涉及的字母大写) |
符号 | 含义 |
---|---|
- | |
+ | 强制让正数显示加号 |
< space > | 可插入一个空格 |
# | 与 o、x 或、X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X |
与 e/E 、 f、a/A 一起使用时,会强制输出包含一个小数点的数。默认情况下,若后边没有数字,则不会显示显示小数点 | |
与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除 | |
0 | 若结果位数少于width则填充0 |
符号 | 含义 |
---|---|
< numbers > | 用数字表示字符的长度。若不足会默认用空格填充。当让可以用flags中的0来填充0 |
* | 会增加一个参数,用变量来表示你需要的 < numbers > |
符号 | 含义 |
---|---|
< numbers > | 对于整数,和width的效果一致 对于浮点数,代表小数位数 对于字符串 s ,代表输入的最大位数对于字符 c ,没有任何效果 |
* | 会增加一个参数,用变量来表示你需要的 < numbers > |
【【】】
由图可以得出l
是对应long修饰符,h
是对应short修饰符,其余一般也不会用到。
从stdin流中读取字符串,读到参数中
? format 格式字符
? 返回参数个数
输出字符串至stdout流上
? format 格式字符
? 返回打印字符数
int a,b;
scanf("%d\n%d",&a,&b);
int c = printf("%d %d",a,b);
printf("%d",c);
从FILE文件流中读取字符串,读到参数中
? FILE* 文件指针
? 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);
}
现在我们要更进一步来更高效的完成读写操作,貌似到现在位置我们基本上输入输出都从头到尾一个个输入下来的,并没有跳到着输入一个跳到那输入一个。但这种情况也是经常遇到的,现在来操纵一个叫【位置指示器】的东西,它移动到哪,就决定了你在哪读写,在没有这一块的内容的情况下,位置指示器是从头到尾逐步增加的。当你学完这一块内容后也许你能逆着输入。
? 返回当前在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
//由此可以推断在最末尾时无法再推进位置指示器
? 将位置指示器移动到最开始的位置
? 将位置指示器移动到任意指定的位置
? 计算从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流上
当然这里我们侧重讲如何错误处理,
众所周知,CPU的速度和IO设备的速度是天差地别,为了弥补这种速度的差距,引入了缓冲区
这一概念。
就在C语言的视角来看,以前你可能是这么认为的
但现在你应该这么看
现在我们研究的对象正是这中间级【缓冲区】
这是很自然也是很容易误导人的观点,你可以这么对他说“函数一调用完就结束了鸭”
好像很有道理,但再一次强调缓冲区的功能,读写操作必须要先经过缓冲区才能到达对面,如果缓冲区都无法通过,那么也是不可能到达对面的。
比如
#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中。
? 数据一直存在缓冲区内,当缓冲区满溢的时候向流写入数据
\n
之前的数据)? 每读入一行数据,缓冲区向流写入一行数据
在默认情况下是全缓存模式
但我们可以通过函数setvbuf
来设置缓冲模式
? ■更改stream文件流的mode缓冲模式
? ■
? ■
? FILE* 文件指针
? buffer 为char*类型
? mode 表示缓冲模式
? size 缓冲区的大小
mode参数 | 含义 |
---|---|
_IOFBF | 全缓冲file |
_IOLBF | 行缓冲line |
_IONBF | 不缓冲none |
? 成功时为0
? 失败时为非零
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://www.cnblogs.com/AlienfronNova/p/14596395.html