这部分之前有所总结:
参看:C语言再学习 -- 文件
一、流
#include <wchar.h>
int fwide(FILE *stream, int mode);
1、参数解析
2、函数功能
3、返回值
4、示例说明
#include<stdio.h>
#include <wchar.h>
int main()
{
int res = fwide (stdin, 6);
printf("%d\n",res);
return 0;
}
输出结果:
1
二、FILE 对象
当打开一个流时,标准 I/O 函数 fopen 返回一个指向 FILE 对象的指针。该对象通常是一个结构,它包含了标准 I/O 库为管理该流需要的所有信息,包括用于实际 I/O 的文件描述符,指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
在 /usr/include/stdio.h 也可以看到对标准输入、标准输出、标准错误的定义。
/* Standard streams. */
extern struct _IO_FILE *stdin; /* Standard input stream. */
extern struct _IO_FILE *stdout; /* Standard output stream. */
extern struct _IO_FILE *stderr; /* Standard error output stream. */
/* C89/C99 say they're macros. Make them happy. */
#define stdin stdin
#define stdout stdout
#define stderr stderr
三、缓冲
标准 I/O 库提供缓冲的目的是尽可能减少使用 read 和 write 调用的次数。它也对每个 I/O 流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。1、标准 I/O 提供了以下 3 种类型的缓冲。
(1)全缓冲
(2)行缓冲
(3)不带缓冲
2、ISO要求下列缓冲特征
3、函数 setbuf 和 setvbuf
四、打开流
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
1、参数解析
mode:C 字符串,包含了文件访问模式,模式如下:
模式字符串 |
|
“r” | 以只读方式打开文件,该文件必须存在 |
“r+” | 以只读写方式打开文件,该文件必须存在 |
“w” | 打开只写文件,若文件存在则文件长度清零,即该文件内容会消失。 若文件不存在则建立该文件 |
“w+” | 打开可读写文件,若文件存在则文件长度清零,即该文件内容会消失。 若文件不存在则建立该文件。 |
“a” | 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在, 写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留) |
“a+” | 以附加方式打开可读写的文件。若文件不存在,则会建立文件,如果文件存在, 写入的数据会被加到文件尾后,即文件原先的内容会被保留。(原来的EOF符不保留) |
“rb”, “wb”, “ab”, “ab+”, “a+b”, “wb+”, “w+b”, “ab+”, “a+b” | 与前面的模式相似,只是使用二进制模式而非文本模式打开文件 |
2、函数功能
3、返回值
4、示例说明
//示例一 fopen函数
#include<stdio.h>
#include <stdlib.h>
int main (void)
{
FILE*fp = NULL;
fp = fopen("abc.txt", "r");
if(NULL == fp)
perror("fail to fopen"), exit (1);
fclose (fp);
fp = NULL;
return 0;
}
//示例二 freopen 函数
#include<stdio.h>
#include <stdlib.h>
int main (void)
{
FILE*fp = NULL;
fp = freopen("abc.txt", "w", stdout);
if(NULL == fp)
perror("fail to freopen"), exit (1);
printf ("hello world!\n");
fclose (stdout);
fclose (fp);
fp = NULL;
return 0;
}
查看 abc.txt
# cat abc.txt
hello world!
//示例三 fdopen 函数
#include <stdio.h>
#include <unistd.h>
int main (void)
{
FILE * fp = fdopen (STDOUT_FILENO, "w+");
fprintf (fp, "%s\n", "hello!");
fclose(fp);
return 0;
}
输出结果:
hello!
5、示例总结
6、扩展部分
五、读和写流 (文件输入/输出)
1、输入函数
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
(1)返回值
成功返回下一个字符,若已到达文件尾端或出错,则返回 EOF(2)函数比较
最简单的如++i,这个表达式执行后,i 的值会改变,这样的表达式是不应该在宏调用里出现的。
(3)示例说明
//示例一 fgetc 函数
#include <stdio.h>
int main (void)
{
FILE *fp;
int c;
fp = fopen ("abc.txt", "r");
while ((c = fgetc (fp)) != EOF)
{
if (c == 'b')
fputc (c,stdout);
}
printf ("\n");
fclose (fp);
return 0;
}
输出结果:
b
//示例二 getc 函数
#include<stdio.h>
int main()
{
char c;
printf("请输入字符:");
c = getc(stdin); //等同函数 getchar
printf("输入的字符:");
putc(c, stdout);
return(0);
}
输出结果:
请输入字符:f
输入的字符:f
//示例三 getchar 函数
#include <stdio.h>
int main (void)
{
int c;
while((c=getchar())!=EOF)
putchar(c);
return 0;
}
(4)示例总结
2、输出函数
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
(1)函数比较
putchar函数,输出到显示器
putc函数,将字符输入到文件
把stdout作为putc()函数的第二个参数。stdout是在stdout中定义的与标准输出相关的文件指针,所以putc (ch, stdout) 和 putchar ( )的作用是一样的。
(2)示例说明 同上
六、每次一行 I/O (字符串输入/输出)
1、下面两个函数提供了每次输入一行的功能。
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
(1)函数比较
(2)示例说明
//示例一 fgets函数
#include <stdio.h>
int main (void)
{
char name [20];
char *ptr;
ptr = fgets (name, 20, stdin);
printf ("%s?, hi %s!\n", name, ptr);
return 0;
}
输出结果:
JOY
JOY
?, hi JOY
!
//示例二 gets 函数
#include <stdio.h>
int main (void)
{
char name[50];
char *ptr;
while ((ptr = gets (name)) != NULL)
{
printf ("name is %s\n", name);
printf ("ptr is %s\n", ptr);
break;
}
return 0;
}
输出结果:
HELLO
name is HELLO
ptr is HELLO
2、fputs 和 puts 提供每次输出一行的功能
#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);
(1)函数比较
函数 fputs 将一个以 null 字节终止的字符串写到指定的流,尾端的终止符 null 不写出。注意,这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非 null 字节。通常,在 null 字节之前是一个换行符,但并不要求总是如此。(2)示例说明
//示例一 fputs函数
#include <stdio.h>
int main (void)
{
char name [20];
fgets (name, 20, stdin);
fputs (name, stdout);
return 0;
}
输出结果:
hello
hello
//示例二 puts函数
#include <stdio.h>
int main (void)
{
char name[50];
char *ptr;
while ((ptr = gets (name)) != NULL)
{
puts (name);
}
return 0;
}
七、二进制 I/O
#include <stdio.h>
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);
1、函数用法
这些函数有以下两种常见的用法。(1)读或写一个二进制数组。
//fwrite 函数
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
FILE *fp = fopen ("abc.dat", "wb");
char data[10] = "123456789";
if (fwrite (&data[2], sizeof (char), 4, fp) != 4)
perror ("fail to fwrite"), exit (1);
return 0;
}
查看 abc.data
# cat abc.dat
3456
//fread 函数
#include <stdio.h>
int main()
{
int arr[5]={},size=0,num=0; //不要老是打错了好吗,仔细仔细再仔细。
FILE *p_file=fopen("a.bin","r");
if(p_file)
{
size=fread(arr,sizeof(int),5,p_file); //就是个数
printf("size是%d\n",size);
for(num=0;num<=4;num++)
{
printf("%d ",arr[num]);
}
printf("\n");
fclose(p_file);
p_file=NULL;
}
return 0;
}
(2)读或写一个结构。例如,可以编写如下程序:
//fwrite 函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct
{
int n;
float m;
char name[20];
}Ptr;
int main (void)
{
Ptr p;
strcpy (p.name, "hello"); //注意字符串不能直接赋值
p.n = 11;
p.m = 12.9;
FILE *fp = fopen ("abc.dat", "wb");
if (fwrite (&p, sizeof (Ptr), 1, fp) != 1)
perror ("fail to fwrite"), exit (1);
return 0;
}
//fread 函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int id;
float salary;
char name[20];
}person;
int main()
{
person p={};
FILE *p_file=fopen("person.bin","rb");
if(p_file)
{
while(1)
{
if(!fread(&p,sizeof(person),1,p_file))
{
break;
}
fread(&p,sizeof(person),1,p_file);//二进制写操作
printf("id是%d,工资是%g,姓名是%s\n",p.id,p.salary,p.name);
}
fclose(p_file);
p_file=NULL;
}
return 0;
}
2、使用二进制 I/O 的基本问题
八、定位流
1、ftell 和 fseek 函数。
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
2、ftello 和 fseeko 函数。
#include <stdio.h>
int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);
3、fgetpos 和 fsetpos 函数
#include <stdio.h>
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
返回值:成功返回0,失败返回非0
(1)示例说明
#include <stdio.h>
#include <string.h>
int main (void)
{
FILE *fp;
fpos_t filepos;
fp = fopen ("abc.txt", "w+");
fgetpos (fp, &filepos);
fputs ("hello world!", fp);
fsetpos (fp, &filepos);
fputs ("这将覆盖之前的内容", fp);
fclose (fp);
return 0;
}
查看 abc.ttx
# cat abc.txt
这将覆盖之前的内容
(2)示例总结
首先我们使用 fgetpos() 函数获取文件的初始位置,接着我们向文件写入 Hello, World!,然后我们使用 fsetpos() 函数来重置写指针到文件的开头,重写文件。九、格式化 I/O
1、格式化输出
#include <stdio.h>
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, ...);
(1)函数比较
printf 将格式化数据写到标准输出;(2)示例说明
//示例一 fprintf 函数
#include <stdio.h>
int main()
{
FILE *p_file = fopen("b.txt","w");
if(p_file)
{ //fprintf函数可以把数据按照格式记录到文本文件中
fprintf(p_file,"%c,%g,%d\n",'c',3.14,46);
fclose(p_file);
p_file=NULL;
}
return 0;
}
查看 b.txt
# cat b.txt
c,3.14,46
//示例二 dprintf 函数
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
int main (void)
{
int fd = open ("a.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (-1 == fd)
perror ("fail to fopen"), exit (1);
dprintf (fd, "%c, %g, %d\n", 'A', 5.12, 68);
close (fd);
return 0;
}
输出结果:
# cat a.txt
A, 5.12, 68
//示例三 sprintf 函数
#include <stdio.h>
#define SIZE 30
int main (void)
{
char str[SIZE];
sprintf (str, "%s %s %d\n", "I","love",512 );
puts (str);
return 0;
}
输出结果:
I love 512
(3)示例总结
示例一:fprintf () 的工作方式和 printf() 相似,区别在于前者需要第一个参数来指定合适的文件流。
例:rv = printf ("hello"); 结果为rv = 5;
再有,需要掌握的 printf( ) 格式转换说明符,这里就不讲了。
示例三:注意,sprintf 函数可能会造成由 buf 指向的缓冲区的溢出。调用者有责任确保该缓冲区足够大。
溢出错误如下:
# ./a.out
str = 12345678901234567890
len = 20
*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0xb7677dd5]
/lib/i386-linux-gnu/libc.so.6(+0xffd8a)[0xb7677d8a]
./a.out[0x80484d5]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb75914d3]
./a.out[0x80483a1]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:01 2102158 /home/tarena/project/c_test/a.out
08049000-0804a000 r--p 00000000 08:01 2102158 /home/tarena/project/c_test/a.out
0804a000-0804b000 rw-p 00001000 08:01 2102158 /home/tarena/project/c_test/a.out
094f1000-09512000 rw-p 00000000 00:00 0 [heap]
b7547000-b7563000 r-xp 00000000 08:01 1704884 /lib/i386-linux-gnu/libgcc_s.so.1
b7563000-b7564000 r--p 0001b000 08:01 1704884 /lib/i386-linux-gnu/libgcc_s.so.1
b7564000-b7565000 rw-p 0001c000 08:01 1704884 /lib/i386-linux-gnu/libgcc_s.so.1
b7577000-b7578000 rw-p 00000000 00:00 0
b7578000-b7717000 r-xp 00000000 08:01 1704863 /lib/i386-linux-gnu/libc-2.15.so
b7717000-b7719000 r--p 0019f000 08:01 1704863 /lib/i386-linux-gnu/libc-2.15.so
b7719000-b771a000 rw-p 001a1000 08:01 1704863 /lib/i386-linux-gnu/libc-2.15.so
b771a000-b771d000 rw-p 00000000 00:00 0
b772d000-b7731000 rw-p 00000000 00:00 0
b7731000-b7732000 r-xp 00000000 00:00 0 [vdso]
b7732000-b7752000 r-xp 00000000 08:01 1704843 /lib/i386-linux-gnu/ld-2.15.so
b7752000-b7753000 r--p 0001f000 08:01 1704843 /lib/i386-linux-gnu/ld-2.15.so
b7753000-b7754000 rw-p 00020000 08:01 1704843 /lib/i386-linux-gnu/ld-2.15.so
bf960000-bf981000 rw-p 00000000 00:00 0 [stack]
已放弃 (核心已转储)
因为缓冲区溢出会造成程序不稳定甚至安全隐患,为了解决这种缓冲区溢出问题,引入了 snprintf 函数。例如:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char str[10] = {0};
int len = snprintf (str, sizeof (str), "12345678901234567890");
printf ("str = %s\n", str);
printf ("len = %d\n", len);
return 0;
}
输出结果:
str = 123456789
len = 20
在该函数中,缓冲区长度是一个显示参数,超过缓冲区尾端的所有字符都被丢弃。如果缓冲区足够大,snprintf 函数就会返回写入缓冲区的字符数。与 sprintf 相同该返回值不包括结尾的 null 字节。
以上这 2 个函数的返回值,成功返回将要存入数组的字符数,若编码出错,返回负值。
2、格式化输入
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
3个函数返回值:赋值的输入项数;若输入出错或在任一转换前已到达文件尾端,返回 EOF
(1)函数比较
scanf 族用于分析输入字符串,并将字符序列转换成指定类型的变量。在格式之后的各参数包含了变量的地址,用转换结果对这些变量赋值。(2)示例说明
//示例一 fscanf 函数
#include <stdio.h>
int main()
{
char ch=0;
float fnum=0.0;
int num=0;
FILE *p_file=fopen("b.txt","r");
if(p_file)
{
//fscanf函数可以从文件中按照格式把数据拷贝到内存的存储区里
fscanf(p_file,"%c %g %d",&ch,&fnum,&num);
//拷贝到存储区我们就可以打印出来
printf("%c %g %d\n",ch, fnum, num);
fclose(p_file);
p_file=NULL;
}
return 0;
}
输出结果:
c 3.14 46
//示例二 sscanf 函数
#include <stdio.h>
#define SIZE 30
int main (void)
{
char str[SIZE];
sscanf ("12345", "%s", str);
puts (str);
return 0;
}
输出结果:
12345
(3)示例总结
示例一:fscanf 函数工作方式和 scanf 相似,区别在于前者需要第一个参数来指定合适的文件流。示例二:参看:C语言函数sscanf()的用法
status = scanf ("%ld", &num);
while (status == 1)
{
status = scanf ("%ld", &num);
}
/*当输入整数则执行while循环,例如输入Q,则scanf返回值为0,循环终止*/
也可用下列形式代替:
while(scanf ("%ld", &num) == 1) {}
十、函数 fileno
#include <stdio.h>
int fileno(FILE *stream);
返回值:与该流相关联的文件描述符
1、函数功能
2、示例说明
//示例一
#include <stdio.h>
int main(void)
{
FILE *fp;
int fd;
fp = fopen("/etc/passwd", "r");
fd = fileno(fp);
printf("fd = %d\n", fd);
fclose(fp);
return 0;
}
输出结果:
fd = 3
//示例二
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
int fd = fileno (stdout);
int newfd = dup (fd);
if (-1 == newfd)
perror ("Fail to dup"), exit (-1);
printf ("newfd = %d\n", newfd);
write (newfd, "hello world\n", 12);
return 0;
}
输出结果:
newfd = 3
hello world