[C程序设计语言]第五部分
UNIX系统接口
文件描述符
在UNIX操作系统中,所有的外围设备(包括键盘和显示器)都被看作是文件系统中的文件,因此,所有的输入/输出都要通过读文件或写文件完成。
因为大多数的输入/输出是通过键盘和显示器来实现的,为了方便起见,UNIX对此做了特别的安排。个文件,对应的文件描述符分别为中读,对1和2进行写,就可以进行输/输出而不必关心打开文件的问题。
程序的使用者可通过<和>重定向程序的I/O:
prog <输入文件名 >输出文件名
这种情况下,shell把文件描述符0和l的默认赋值改变为指定的文件。通常,文件描述符2仍与显示器相关联,这样,出错信息会输出到显示器上。在任何悄况下,文件赋值的改变都不是程序完成的,而是由shell完成的。只要程序使用文件0作为输入,文件l和2作为输出,它就不会知程序的输入从哪里来,并输出到哪里去。
底层I/O — read和write
输入与输出是通过现read和write系统调用实现的。在C语言程序中,可以通过函数read和write访问这两个系统调用。这两个函数中,第一个参数是文件描述符,第二个参数是程序中存放读和写的数据的字符数组,第三个参数是要传输的字节数:
int n_read = read(int fd, char *buf, int n);
int n_written = write(int fd, char *buf, int n);
每个调用返回实际传输(读或写)的字节数。在,则表示已到达文件的结尾;如果返回值为,即每次读出或写入1个字符(无缓冲),或是类似于1024~4096这样的与外围设备的物理块大小相应的值。用更大的值调用该函数可以获得更高的效率,因为系统调用的次数减少了。
#include"syscalls.h"
main() /*将输入复制到输出*/
{
char buf[BUFSIZ];
int n;
while ((n = read(0, buf, BUFSIZ)) > 0)
write(1, buf, n);
return 0;
}
本章中的程序都将包含该头文件syscalls.h,不过,该文件的名字不是标准的。参数BUFSIZ已经在syscalls.h头文件中定义了。对于所使用的操作系统来说,该值是一个较合适的数值。如果文件大小不是BUFSIZ的倍数,则对read的某次调用会返回一个较小的字节数,write再按这个字节数写。
构造类似于getchar高级函数: 从标准输入读入一个字符来实现无缓冲输入(第一个版本)
#include"syscalls.h"
int getchar(void) {
char c;
return (read(0, &c, 1) == 1) ? (unsignedchar) c : EOF;
}
第二个版本:一次读入一组字符,但每次只输出一个字符
#include"syscalls.h"
int getchar(void) {
staticchar buf[BUFSIZ];
staticchar *bufp = buf;
staticint n = 0;
if (n == 0) { /* buffer is empty */
n = read(0, buf, sizeof buf);
bufp = buf;
}
return (n >= 0) ? (unsignedchar) *bufp++ : EOF;
}
如果要在包含头文件始。
使用create创建或覆盖已存在的旧文件:
int creat(char *name, int perms);
fd = creat(name, perms);
如果,从而丢弃原先己有的内容使用creat创建一个已存在的文件不会导致错误
如果要创建的文件不存在,则creat用参数pems指定的权限创建文件。在UNIX文件系统中,每个文件对应一个9比特的权限信息,它们分别控制文件的所有者、所有者组和其他成员对文件的读、写和执行访问。因此,通过一个3位的八进制数就可方便地说明不同的权限,例如,0755(八进制)说明文件的所有者可以对它进行读、写和执行操作,而所有者组和其他成员只能进行读和执行操作。
下面通过一个简化UNIX程序cp说明creat的用法,该程序将一个文件复制到另一个文件。目标文件的权限不是通过复制获得的,而是重新定义的:
#include<stdio.h>
#include<fcntl.h>
#include<stdarg.h>
#include"syscalls.h"
#define PERMS 0666 /* RW for owner, group, others */
void error(char *, ...);
/* cp: copy f1 to f2 */
main(int argc, char *argv[]) {
int f1, f2, n;
char buf[BUFSIZ];
if (argc != 3)
error("Usage: cp from to");
if ((f1 = open(argv[1], O_RDONLY, 0)) == 1)
error("cp: can't open %s", argv[1]);
if ((f2 = creat(argv[2], PERMS)) == 1)
error("cp: can't create %s, mode %03o", argv[2], PERMS);
while ((n = read(f1, buf, BUFSIZ)) > 0)
if (write(f2, buf, n) != n)
error("cp: write error on file %s", argv[2]);
return 0;
}
void error(char *fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "error: ");
vprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
exit(1);
}
该程序创建的输出文件具有固定的权限0666,也可使用stat系统调用,可以获得一个已存在文件的模式,并将此模式赋值给它的副本。标准库函数vprintf数与printf函数类似,所不同的是,它用一个参数取代了变长参数表,因此参数通过调用va_start宏进行初始化。同样,vfprintf和vsprintf函数分别与fprintf和sprintf函数类似。
函数close (int fd)用来断开文件描述符和已打开文件之间的连接,并释放此文件描述符,以供其它文件使用。close函数与标准库中的fclose函数相对应,但它不需要清除(flush)缓冲区。
函数unlink(char *name)将文件name从文件系统中删除,它对应于标准库函数remove
随机访问-lseek
系统调用lseek可以在文件中任意移动位置、1或2,分别用于指定offset从文件开始、从当前位置或从文件结束处开始算起。例如,为了向一个文件的尾部添加内容(在UNIX shell程序中使用重定向符>>或在系统调用fopen中使用参数“a”):
lseek(fd, 0L, 2);
#include"syscalls.h"
/*get: read n bytes from position pos 从文件的任意位置读入任意数目的字节,它返回读入的字节数,若发生错误返回-1*/
int get(int fd, long pos, char *buf, int n) {
if (lseek(fd, pos, 0) >= 0) /* get to pos 先移动到指定的位置*/
returnread(fd, buf, n); /* 再读取*/
else
return 1;
}
lseek系统调用返回long类型的值,此值表示文件的新位置,若发生错误,则返回-1。标准库函数fseek与系统调用lseek类似,不同的是,前者第一个参数为FILE *类型,并且发生错误时返回一个非0值。
实例-fopen和getc函数的实现
标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:一个指向缓冲区的指针,通过它可以一次读入文件的一大块内容;一个记录缓冲区中剩余的字符数的计数器;一个指向缓冲区中下一个字符的指针;文件描述符;描述读/写模式的标志;描述错误状态的标志等。
——相关宏定义——
#define NULL 0
#define EOF (1)
#define BUFSIZ 1024
#define OPEN_MAX 20 /* 一次允许打开的最大文件数 */
typedefstruct _iobuf {
intcnt; /* 剩余字符数 */
char *ptr; /* 下一个字符的位置 */
char *base; /* 缓冲区的位置 */
intflag; /* 文件访问模式与出错标示 */
intfd; /* 文件描述符 */
} FILE;
externFILE _iob[OPEN_MAX]; //用来存储打开的文件结构数据
#define stdin (&_iob[0]) //第一个用作标准输入
#define stdout (&_iob[1]) //第二个用作标准输出
#define stderr (&_iob[2]) //第三个用作错误输出
enum _flags {
/* 使用了5位来标示文件的访问方式与文件访问时出错标示: 00 011 111,
以位来标示便于使用位运算符&来判断文件的打开后所处的状态,如判断打开
文件是否出错:(fp -> flag & (_EOF | _ERR)) != 0 */
_READ = 01, /* 以读方式打开文件 */
_WRITE = 02, /* 以写方式打开文件 */
_UNBUF = 04, /* 不对文件进行缓冲 */
_EOF = 010, /* 已到文件尾 */
_ERR = 020 /* 该文件发生错误 */
};
int _fillbuf(FILE *);
int _flushbuf(int, FILE *);
#define feof(p) ((p)->flag & _EOF) != 0)
#define ferror(p) ((p)->flag & _ERR) != 0)
#define fileno(p) ((p)->fd)
/* 先将计数器cnt减一,再将指针移到下一个位置,然后返回字符,
* 如果小于0,则就填允缓冲区,重新初始化结构的内容,并返回
* 一个字符*/
#define getc(p) (--(p)->cnt >= 0 \
? (unsignedchar) *(p)->ptr++ : _fillbuf(p))
/* 缓冲区满后刷新缓冲到文件中*/
#define putc(x,p) (--(p)->cnt >= 0 \
? *(p)->ptr++ = (x) : _flushbuf((x),p))
#define getchar() getc(stdin)
#define putcher(x) putc((x), stdout)
——fopen实现——
#include<fcntl.h>
#include"syscalls.h"
#define PERMS 0666 /* RW for owner, group, others */
FILE *fopen(char *name, char *mode) {
int fd;
FILE *fp;
if (*mode != 'r' && *mode != 'w' && *mode != 'a')
return NULL;
for (fp = _iob; fp < _iob + OPEN_MAX; fp++)
if ((fp -> flag & (_READ | _WRITE)) == 0)
break; /* 寻找一个空位置,即让fp指向_iob数据还未使用的元素
,如果结构数据未赋值,则_READ | _WRITE位所在的
位为0,则(_READ | _WRITE)就为0 */
if (fp >= _iob + OPEN_MAX) /* 最大文件个数超限 */
return NULL;
if (*mode == 'w')
fd = creat(name, PERMS);
elseif (*mode == 'a') {
//如果以写的方式打开文件后返回的文件描述符为1(标准输出),
//则重新创建一个文件
if ((fd = open(name, O_WRONLY, 0)) == 1)
fd = creat(name, PERMS);
lseek(fd, 0L, 2);//定位到文件结尾
} else
fd = open(name, O_RDONLY, 0);
if (fd == -1)
/* 不能访问文件 */
return NULL;
/* 正常打开文件后,将对应的文件结构数据存储到_iob结构数组中相应空闲元素的位置中,
_iob结构数组主要的是一个全局的,用来记录当前程序已打开文件,这样就可以限制打开的
最多文件数*/
fp -> fd = fd;
fp -> cnt = 0;
fp -> base = NULL;
fp -> flag = (*mode == 'r') ? _READ : _WRITE;
return fp;
}
——缓冲函数的实现——
#include"syscalls.h"
/* _fillbuf: 分配并填允缓冲区 */
int _fillbuf(FILE *fp) {
int bufsize;
if ((fp -> flag & (_READ | _EOF | _ERR)) != _READ)
return EOF;
bufsize = (fp -> flag & _UNBUF) ? 1 : BUFSIZ;
if (fp -> base == NULL) /* 还未分配缓冲区时 */
if ((fp -> base = (char *) malloc(bufsize)) == NULL)
return EOF; /* 不能分配缓冲区 */
fp -> ptr = fp -> base;
fp -> cnt = read(fp -> fd, fp -> ptr, bufsize);
if (fp -> cnt < 0) {
if (fp -> cnt == 1)
fp -> flag |= _EOF;
else
fp -> flag |= _ERR;
fp -> cnt = 0;
return EOF;
}
return (unsignedchar) *fp -> ptr++;
}
/* 最后我们还必须定义和初始化数组_iob中的stdin, stdout, stderr*/
FILE _iob[OPEN_MAX] = {
{ 0, (char *) 0, (char *) 0, _READ, 0},
{ 0, (char *) 0, (char *) 0, _WRITE, 1},
{ 0, (char *) 0, (char *) 0, _WRITE | _UNBUF, 2}/* 标准错误输出不用缓冲的 */
};
性能忠告
在内存紧张和要求运行速度的情况下,最好使用数值范围比较小的整数数据类型。
在声明时就赋值可能提高程序运行时间。
c +=5 比 c = c + 5要快,原因是前者中c只分析一次,而后者中c要分析两次。
在使用运算符&&的表达式中,把最可能为假的条件放在最左边。在使用运算符||的表达式中,把最可能为真的条件放在最左边,这样能减少程序的执行时间。
可移植性忠告
使用符号常量EOF而不是使用-1可使用程序具有更好的可移植性。ANSI标准规定EOF是一个负整数,但不是必须为-1。因此,在不同的系统中,EOF可能有不同的值。
因为int类型的整数值在不同的系统中具有不同的范围,所以如果要处理 -32768 ~ +32767范围之外的整数值并且要能够使用程序在不同的计算机系统中运行,使用long数据类型。
函数原型
函数原型告诉编译器函数返回的数据类型,函数所要接收的参数个数,参数类型和参数顺序,编译器用函数原型校验函数调用。
int maxinum(int, int, int);
这里省略了参数名,也可以带上参数名
函数原型另一个重要特点是强制转换参数类型,如标准库函数sqrt函数原型指定了参数为double类型,但在调用时可以传递整型,这是因为编译器根据函数原型,调用前将整型转换为double型了。如果不遵循C语言的类型“提升规则”,参数类型转换可能会导致不正确的结果。
[C程序设计语言]第五部分的更多相关文章
- 20145213《Java程序设计》第五周学习总结
20145213<Java程序设计>第五周学习总结 教材学习内容总结 "素衣莫起风尘叹,犹及清明可到家."每每念此,不得不心疼自己.古人清明长假都进城耍了,还担心自己清 ...
- 20145330《Java程序设计》第五次实验报告
20145330<Java程序设计>第五次实验报告 实验五 Java网络编程及安全 实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统 4.结队伙伴 ...
- 20145225《Java程序设计》 实验五 Java网络编程及安全
20145225<Java程序设计> 实验五 Java网络编程及安全 实验报告 一.实验内容 基于Java Socket实现安全传输. 基于TCP实现客户端和服务器,结对编程一人负责客户端 ...
- 20145218 《Java程序设计》第五周学习总结
20145218 <Java程序设计>第五周学习总结 教材学习内容总结 异常 程序中总有些意想不到的状况所引发的错误,如果不对异常进行正确的处理,则可能导致程序的中断执行,造成不必要的损失 ...
- 清风注解-Swift程序设计语言:Point6~10
目录索引 清风注解-Swift程序设计语言 Point 6. 输出常量和变量 代码事例: // 输出的内容会在最后换行 println("hello, world") // 输出的 ...
- 20155304 2016-2017-2 《Java程序设计》第五周学习总结
20155304 2016-2017-2 <Java程序设计>第五周学习总结 教材学习内容总结 第八章 try catch JVM会先尝试执行try区块中的内容,若发生错误且与catch后 ...
- 20155313 2016-2017-2 《Java程序设计》第五周学习总结
20155313 2016-2017-2 <Java程序设计>第五周学习总结 教材内容学习 第八章 异常处理 程序中总有些意想不到的状况所引发的错误,Java中的错误也以对象方式呈现为ja ...
- 20165205 2017-2018-2 《Java程序设计》第五周学习总结
20165205 2017-2018-2 <Java程序设计>第五周学习总结 教材学习内容总结 学会内部类,匿名类,异常类的写法 牚握try...catch...finally处理异常的方 ...
- 20172306《Java程序设计》第五周学习总结
20172306 2016-2017-2 <Java程序设计>第五周学习总结 教材学习内容总结 第五章主要学习了if以及while的语句的运用 运算符:== 代表相等,是两个之间的内存地址 ...
随机推荐
- struts2 笔记04 杂记
流程: 1. StrutsPrepareAndExcuteFilter:StrutsPrepareAndExcuteFilter作为前端控制器,是整个Struts2的调度中心. 2. ActionMa ...
- 161010、在大型项目中组织CSS
编写CSS容易. 编写可维护的CSS难. 这句话你之前可能听过100次了. 原因是CSS中的一切都默认为全局的.如果你是一个C程序员你就知道全局变量不好.如果你是任何一种程序员,你都知道隔离和可组合的 ...
- hadoop概述测试题和基础模版代码
hadoop概述测试题和基础模版代码 1.Hadoop的创始人是DougCutting?() A.正确 B.错误答对了!正确答案:A解析:参考课程里的文档,这个就不解释了2.下列有关Hadoop的说法 ...
- Linux和Windows远程桌面互连
1.Ubuntu 连 windows W.1 Windows设置允许远程桌面连接 U.1 Ubuntu设置 apt-get install rdesktop U.2 Ubuntu连接 ...
- discuz x2 diy 模块的样式点击不管用,模块的数据、标题都可以编辑
这个是diy模板的文件忘记添加 <style id="diy_style" type="text/css"></style> 一 ...
- shell基础二十篇 一些笔记
shell基础二十篇 转自 http://bbs.chinaunix.net/thread-452942-1-1.html 研讨:Bash 内建命令 read (read命令更具体的说明见博客收藏的一 ...
- Spring MVC 中请求返回之后的页面没法加载css、js等静态文件
1.是否被拦截,这个在Web.xml配置中servlet拦截是“/”,如果是则 a.使用spring MVC 的静态资源文件 <!-- 静态文件访问,主要是针对DispatcherServlet ...
- YTU 2924: 文件操作--二进制文件读入
2924: 文件操作--二进制文件读入 时间限制: 1 Sec 内存限制: 128 MB 提交: 58 解决: 20 题目描述 现有100名学生的姓名(name).学号(num).英语(Engli ...
- poj1811 Prime Test
http://poj.org/problem?id=1811 #include <cstdio> #include <cstring> #include <algorit ...
- 周赛-DZY Loves Chessboard 分类: 比赛 搜索 2015-08-08 15:48 4人阅读 评论(0) 收藏
DZY Loves Chessboard time limit per test 1 second memory limit per test 256 megabytes input standard ...