这里写目录标题
- 华清远见嵌入式2017年线下班:文件IO笔记
- 文件权限
- 文件IO
- 文件创建和打开操作
- 文件关闭操作
- 出错处理
- 创建设备文件 || create || 老师自己忘了
- 文件读操作
- 练习:计算文件的大小?
- 文件写操作
- 练习:打开file1和file2,将file1中的内容拷贝到file2,实现文件拷贝功能
- 文件定位操作
- 练习:通过lseek计算文件的大小
- 空洞与非空洞文件 (Linux)
- 作业:打开file1,file2,file3 ,将file1前一半内容拷贝到file2,后一半内容拷贝file3```cpp
- 标准IO
- 文件读操作fopen
- 读写流
- 练习: 循环的从标准输入中获取字符(如果是数字则显示,其他的就不显示)
- 练习:要求输入字符,将大写字母转换成小写,小写字母转换成大写。
- 行IO
- 练习:获取以个文件的行数。
- 检查文件出错函数
- 直接IO
- 流的格式化输入输出
- 流的定位
- 获取时间
- 文件信息
- 硬链接和软连接
- 获取用户相关信息
- 获取用户分组
- 目录操作函数
- 库
- 静态库特点
- 共享库的特点
- 如何找到共享库
华清远见嵌入式2017年线下班:文件IO笔记
文件权限
(一)第一个字段表示文件类型 和 文件权限。
第1个字母表示文件类型:d表示目录类型。其他的linux 文件类型以下七种:
(二)其他6个字母表示分别表示属主权限,属组权限,其他用户权限。
权限构成
文件权限由三个部分构成,分别为
文件所有者 User
同组用户 Group
其他用户 Other
每个部分可以用一个数字或三个字母表示:
读取的权限等于4,用r表示
写入的权限等于2,用w表示
执行的权限等于1,用x表示
数字解释
rwx 属性:4+2+1 = 7;
rw- 属性:4+2 = 6;
r-x 属性:4+1 = 7。
以755为例:
1-3 位 7:等于4+2+1,rwx,所有者具有读取、写入、执行权限;
4-6 位 5:等于4+1+0,r-x,同组用户具有读取、执行权限但没有写入权限;
7-9 位 5:等于4+1+0,r-x,其他用户具有读取、执行权限但没有写入权限。
444 r–r–r–
600 drw-------
644 drw-r–r–
666 drw-rw-rw-
700 drwx------
744 drwxr–r–
755 drwxr-xr-x
777 drwxrwxrwx
文件IO
类Linux系统下使用
文件创建和打开操作
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
函数功能:打开、创建文件
函数参数: pathname : 需要打开文件的文件名,文件名长度不能超过255。 flags : 打开方式
O_RDONLY:只读方式打开文件。
O_WRONLY:可写方式打开文件。
O_RDWR:读写方式打开文件。
O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限(读写执行权限)。
O_EXCL:如果使用
O_CREAT时文件存在,则可返回错误消息。这一参数可测试文件是否存在。
O_TRUNC:如文件已经存在,那么打开文件时先删除文件中原有数据。
O_APPEND:以添加方式打开文件,所以对文件的写操作都在文件的末尾进行。//write 文件开始位置开始写
文件关闭操作
#include <unistd.h>
int close(int fildes);
函数参数:
fildes : 需要关闭文件的文件描述符
返回值:
成功 0
失败 -1
例1:以所有者只写方式(参数O_WRONLY)打开文件(mode=64),如果文件不存在则创建(参数O_CREAT),如果文件存在则清空(参数O_TRUNC)。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
/*
以所有者只写方式(参数O_WRONLY)打开文件(mode=64),
如果文件不存在则创建(参数O_CREAT),
如果文件存在则截短(参数O_TRUNC)
*/
int main(int argc, const char *argv[])
{
int fd;
// 不指定umask生成的test.txt文件的权限是0664,指定了就是0666
umask(0);
// 在当前目录创建"test.txt"
// 0666 = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
// fd = open("test.txt",
// O_WRONLY | O_CREAT | O_TRUNC ,
// S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC | O_EXCL,0666);
if(-1 == fd)
{
perror("open failed");// 后面会自动接perror对应的错误信息
}
close(fd);//1024
return 0;
}
在终端中执行,使用命令 ./open
如果存在text.txt文件,perror则会报错,自动提示错误信息
文件权限还可以使用宏进行表示
出错处理
#include <string.h>
char * strerror(int errnum);
全局错误码errno 是记录系统的最后一次错误代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> //一定要加这个头文件
/*
以所有者只写方式(参数O_WRONLY)打开文件(mode=64),
如果文件不存在则创建(参数O_CREAT),
如果文件存在则截短(参数O_TRUNC)
*/
int main(int argc, const char *argv[])
{
int fd;
// 不指定umask生成的test.txt文件的权限是0664,指定了就是0666
umask(0);
// 在当前目录创建"test.txt"
fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC | O_EXCL,0666);
if(-1 == fd)
{
print("%s\n", strerror(errno));// errno是记录系统的最后一次错误代码
}
close(fd);//1024
return 0;
}
创建设备文件 || create || 老师自己忘了
cat /proc/devices 查看当前系统支持的驱动
主设备号
usb 字符设备
180 设备号
int creat(const char *pathname, mode_t mode);
等价于open(pathname,
O_CREAT|O_WRONLY|O_TRUNC, mode)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int fd;
fd = creat("test.txt", 0666); // open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
if(-1 == fd)
{
perror("creat");
}
close(fd);//1024
return 0;
}
文件读操作
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
函数功能:
从一个已经打开的文件中读取内容
函数参数:
fd : 文件描述符
buf : 用来存放读到的内容的缓冲区首地址
count :读取的字节数
返回值:
成功: 返回实际读取的字节数(0 读到文件末尾)
失败: -1 并设置errno
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[1024] = "\0"; // 可以人为指定,不能超过1024字节
int count;
fd = open("test.txt", O_RDONLY | O_CREAT , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed");
}
// 每次读取5个字节
while((count = read(fd, buf, 5)) > 0) // 不能 >=
{
if(-1 == count)
{
perror("read failed");
return -1;
}
printf("count = %d : %s\n", count, buf); //count = 5
}
close(fd);//1024
return 0;
}
练习:计算文件的大小?
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[1024] = "\0";
int count = 0 , sum = 0;
fd = open("test.txt", O_RDONLY | O_CREAT , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed");
}
while((count = read(fd, buf, 5)) > 0)
{
if(-1 == count)
{
perror("read failed");
return -1;
}
sum = sum + count;
}
printf("The size of file is %d\n", sum);
close(fd);//1024
return 0;
}
文件写操作
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
函数功能:
向已经打开的文件中写内容
函数参数:
fd : 文件描述符
buf : 写缓冲缓冲区首地址
count : 指定写入的字节数
返回值:
成功:实际写到文件的字节数
失败:-1
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[1024] = "\0";
char writebuf[1024] = "xxxxx";
int count;
// fd = open("test.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
// 没有O_APPEND,直接覆盖
fd = open("test.txt", O_RDWR | O_CREAT , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed");
}
count = read(fd, buf, 1);
if(-1 == count)
{
perror("read failed");
return -1;
}
printf("count = %d : %s\n", count, buf);
count = write(fd, writebuf, strlen(writebuf));
if(-1 == count)
{
perror("write failed");
return -1;
}
printf("count = %d\n", count);
// 这里读了一个字符,所以偏移了1
count = read(fd, buf, 1);
if(-1 == count)
{
perror("read failed");
return -1;
}
printf("count = %d : %s\n", count, buf);
close(fd);//1024
return 0;
}
text.txt里原来的内容是 “abcdefghijk”
输出:文件初始read了一个字符,位移在第一位,往后覆盖 xxxxx
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[1024] = "\0";
char writebuf[1024] = "xxxxx";
int count;
// 有O_APPEND
fd = open("test.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed");
}
count = read(fd, buf, 1);
if(-1 == count)
{
perror("read failed");
return -1;
}
printf("count = %d : %s\n", count, buf);
count = write(fd, writebuf, strlen(writebuf));
if(-1 == count)
{
perror("write failed");
return -1;
}
printf("count = %d\n", count);
count = read(fd, buf, 1);
if(-1 == count)
{
perror("read failed");
return -1;
}
printf("count = %d : %s\n", count, buf);
close(fd);//1024
return 0;
}
输出:在下一行追加 xxxxx
练习:打开file1和file2,将file1中的内容拷贝到file2,实现文件拷贝功能
diff -s file1 file2 (比较两个文件是否一致)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd1, fd2;
char buf[1024] = "\0";
int count;
if(argc < 3)
{
printf("command error <source file> <dest file>!\n");
return -1;
}
// 从命令行参数获得文件名
fd1 = open(argv[1], O_RDONLY);
if(-1 == fd1)
{
perror("open file1 failed");
return -1;
}
fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(-1 == fd2)
{
perror("open file2 failed");
return -1;
}
while((count = read(fd1, buf, sizeof(buf))) > 0)
{
write(fd2, buf, count);
}
close(fd1);//1024
close(fd2);//1024
return 0;
}
文件定位操作
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数功能:文件定位
函数参数:
fd:文件描述符
offset:相对于偏移量
偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移)
whence: 基准点
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小。
SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。
返回值:
失败 :-1
成功:当前文件的偏移量(当前读写位置相对文件开始位置的字节数)
通过lseek计算文件的大小
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[1024] = "\0";
char writebuf[1024] = "xxxxx";
int count;
int offset;
fd = open("test.txt", O_RDWR | O_CREAT , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed");
}
count = write(fd, writebuf, strlen(writebuf));
if(-1 == count)
{
perror("write failed");
return -1;
}
printf("count = %d\n", count);
//offset = lseek(fd, -6, SEEK_CUR);
offset = lseek(fd, -5, SEEK_CUR);
if(-1 == offset)
{
perror("lseek");
return -1;
}
count = read(fd, buf, 5);
if(-1 == count)
{
perror("read failed");
return -1;
}
printf("count = %d : %s\n", count, buf);
close(fd);//1024
return 0;
}
新建文件,写入“xxxxx”,然后读取出来。
如果向前偏移量越界,时候会直接报错
练习:通过lseek计算文件的大小
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[1024] = "\0";
char writebuf[1024] = "xxxxx";
int count;
int offset;
fd = open("test.txt", O_RDWR | O_CREAT , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed");
}
//SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量0的大小
offset = lseek(fd, 0, SEEK_END);
if(-1 == offset)
{
perror("lseek");
return -1;
}
printf("sizeof file is %d \n", offset);
close(fd);//1024
return 0;
}
空洞与非空洞文件 (Linux)
空洞文件和真实文件的区别在于磁盘的使用情况
du -sh 查看空洞文件和真实文件的区别
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[32] = "hello, world";
int offset;
fd = open("hole.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if(fd < 0)
{
printf("%s\n",sterror(errno));
return -1;
}
write(fd, buf, strlen(buf));
if((offset = lseek(fd, 168898, SEEK_CUR)) < 0)
{
perror("lseek");
return -1;
}
write(fd, buf, strlen(buf));
close(fd);//1024
return 0;
}
前后都有hello world.中间全是空洞
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[32] = "hello, world";
int offset;
fd = open("hole.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if(fd < 0)
{
printf("%s\n",sterror(errno));
return -1;
}
write(fd, buf, strlen(buf));
/*
if((offset = lseek(fd, 168898, SEEK_CUR)) < 0)
{
perror("lseek");
return -1;
}
*/
//真实地往磁盘里写内容
for(i = 0; i < 168898; i++)
{
write(fd, "x", 1);
}
write(fd, buf, strlen(buf));
close(fd);//1024
return 0;
}
空洞和非空洞文件都是165K
但是可以通过查看磁盘使用情况区别两个文件的内存大小
作业:打开file1,file2,file3 ,将file1前一半内容拷贝到file2,后一半内容拷贝file3```cpp
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd1, fd2, fd3, count, nread, size;
int buf[1024] = "\0";
if(argc < 4)
{
printf("argc < 4");
return -1;
}
fd1 = open(argv[1], O_RDONLY);
if(fd1 < 0)
{
printf("%s\n",sterror(errno));
return -1;
}
fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd2 < 0)
{
printf("%s\n",sterror(errno));
return -1;
}
fd3 = open(argv[3], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd3 < 0)
{
printf("%s\n",sterror(errno));
return -1;
}
size = lseek(fd1, 0, SEEK_END);
lseek(fd1, 0, SEEK_SET)
while((nread = read(fd1, buf, 1)) > 0)
{
count++;
if(count <= (size/2))
write(fd2, buf, nread);
else if (count > (size/2))
write(fd3, buf, nread);
}
close(fd1);//1024
close(fd2);//1024
close(fd3);//1024
return 0;
}
标准IO
只要装了C库,就能使用标准IO
open 返回 fd
fopen 返回指向FILE结构的指针
1)理解什么是流?
输入流入和流出的对象就是流
2)如何描述一个流
内存中开辟一个区域,用来存放流的有关信息,这些信息是保存在一个结构体类型的变量中,该结构体类型是由系统定义的,取名为FILE.
3)FILE 指针
fopen 返回指向FILE结构的指针
流的分类:
文本流和二进制流
(Linux不区分文本和二进制流, windows)
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i = 0;
for(i = 0;i < 379;i++) //每次向缓冲区写3个字符
{
if (i >= 100)
fprintf(stdout,"%d",i);
else if (i >= 10)
fprintf(stdout,"0%d",i);
else if (i >= 0)
fprintf(stdout,"00%d",i);
}
while(1);
return 0;
}
目标是打印000-376,
实际上只输出前1024字节,
1024/3=341.3 个数,
即000-340.3,因此结尾的4个数是340|3
getchar(); /* why do we need this? 实际使用时才会申请空间*/
缓冲:
缓冲区是通过malloc申请的空间
申请的实际,是发生I/O操作的时候
当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止(回车字符也放在缓冲区中),当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。
getchar函数的返回值是用户输入的字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕.
总结一下:
对于磁盘文件通常使用全缓冲区访问。
当流涉及一个终端时(例如标准输入和标准输出),使用行缓冲区
标准出错流stderr 通常是不带缓冲区的
#include <stdio.h>
/*1.測試標準輸入和標準輸出採用的是什麼類型的緩衝以及緩衝區的大小
*2.普通文件採用的是什麼類型的緩衝以及緩衝區的大小
*3.malloc的時間
* */
int stream_attribute(FILE *fp)
{
if(fp->_flags & _IO_UNBUFFERED) {
printf("The IO type is unbuffered\n");
} else if (fp->_flags & _IO_LINE_BUF) {
printf("The IO type is line buf\n");
} else {
printf("The IO type is full buf\n");
}
printf("The IO buffer size = %d (bytes)\n", fp->_IO_buf_end - fp->_IO_buf_base);
return 0;
}
int main()
{
FILE *fp;
// 尝试注释掉getchar();观察输出有什么不同
getchar(); /* why do we need this? 实际使用时才会申请空间*/
printf("stdin ___________________________________\n");
stream_attribute(stdin);
printf("stdout ___________________________________\n");
stream_attribute(stdout);
printf("stderr ___________________________________\n");
stream_attribute(stderr);
printf("normal file ___________________________________\n");//普通文件
if ((fp = fopen("test.txt","w+")) == NULL) {
perror("fail to fopen");
}
printf("before write:\n");
stream_attribute(fp);
fputc('a', fp); //putchar('a') == fputc('a', stdout)
printf("after write:\n");
stream_attribute(fp);
return 0;
}
文件读操作fopen
#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);
path : 路径名
mode : 打开方式
r
r+
w
w+
a
a+
返回值:
成功返回 指向FILE的结构体指针(流指针)
失败返回 NULL
./hello > file
FILE *freopen(const char *path, const char *mode, FILE *stream);
作用:重定向输入输出流
const char *restrict pathname:
重新定向的文件或者是路径
const char *restrict type:
文件打开的方式
restrict fp
被改变的流
返回一个新的FILE指针
freopen("file1", "w+", stdout);
读写流
fgetc和getc基本一致,除了getc()可能是由宏实现的,getchar () == getc(stdin) 。
#include <stdio.h>
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
函数参数:
stream : 流
从指定流中获取一个字符
函数返回值:
成功 返回读到字符的ascII码
失败 EOF
到文件末尾
getchar()等同于getc(stdin);
#include <stdio.h>
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
注意:三个函数在使用的时候要一一对应
c : 输出的字符(acII码)
stream : 指定的流
putchar(c)等价于putc(c,stdout)
练习: 循环的从标准输入中获取字符(如果是数字则显示,其他的就不显示)
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, const char *argv[])
{
int c;
while(1)
{
c = fgetc(stdin);
if( ( c >= '0' ) && ( c <= '9') )
fputc(c, stdout);
else if('\n' == c)
break;
}
return 0;
}
练习:要求输入字符,将大写字母转换成小写,小写字母转换成大写。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int c;
while(1)
{
c = fgetc(stdin); //放到了缓冲区
if((c >= 'a') && (c <= 'z'))
{
c = c - ('a' - 'A');
fputc(c, stdout);
}
else if((c >= 'A') && (c <= 'Z'))
{
c = c + ('a' - 'A');
fputc(c, stdout);
}
if('\n' == c)
break;
}
return 0;
}
行IO
char * fgets(char *s, int size, FILE *stream);
char *gets(char *s);
char* s:用于存储从制定流的缓冲区中获取的数据的首地址
size:缓冲区的长度
stream:指定的流
fgets
(1) 必须指定缓存的长度n。
此函数一直读到下一个新行符为止,但是不超过n-1个字符,读入的字符被送入缓存。该缓存以null字符结尾。
(2) 如若该行,包括最后一个新行符的字符数超过n-1,则只返回一个不完整的行,而且缓存总是以null字符结尾。对fgets()的下一次调用会继续读该行。
缓存:buf[7]
(3) gets()与fgets()的另一个区别是,gets()并不将换行符存入缓存中,fges会将换行符存到存到缓冲区中
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[32];
gets(buf);
// fgets(buf, sizeof(buf), stdin);
printf("%s", buf);
return 0;
}
注意1:char *fgets(char *s ,int size,FILE *stream)的用法
fgets每次至多多size-1个字符,每次都把都到的字符存储到s字符串数组内,当读到文件末尾(EOF)或者是换行的时候它会结束。换行符(‘/n’),会被存储到字符数组内部,结束时会在字符数组最后一位补一个空位(’/0’)。
如果文件中的该行,不足bufsize个字符,则读完该行就结束。如若该行(包括最后一个换行符)的字符数超过bufsize-1,则fgets只返回一个不完整的行,但是,缓冲区总是以NULL字符结尾,对fgets的下一次调用会继续读该行。
如果n大于一行的字符串长度,那么当读到字符串末尾的换行符时,fgets(..)会返回。并且在s的最后插入字符串结束标志'\0'。 而s缓冲区剩余的位置不会再填充。
注意2:char *gets(char *s,FILE *stream)
gets不推荐使用,该函数容易造成缓冲区的溢出,造成不良的后果。
二进制直接I/O。fread()和fwrite()函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写一个结构。
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);
练习:获取以个文件的行数。
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp;
char buf[1024] = "\0";
int line = 0;
//step 1: 打开流
fp = fopen(argv[1], "r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
//step 2: 获取行号
while(fgets(buf, sizeof(buf), fp) != NULL)
{
if(buf[strlen(buf) - 1] == '\n')
line++;
}
printf("The line of %s is %d\n", argv[1], line);
return 0;
}
int puts(const char *s);
函数功能:
向标准输出输出一行
int fputs(const char *s, FILE *stream);
函数功能:
向指定的流里输出一行
函数fputs()将一个以null符终止的字符串写到指定的流,终止符null不写出。
== 注意,这并不一定是每次输出一行,因为它并不要求在null符之前一定是新行符。==
#include <stdio.h>
int main(int argc, const char *argv[])
{
// 中间添加'\0'
char buf[] = {'a', 'b', 'c','\0', 'd' , 'e', 'f', '\n', '\0'};
fputs(buf, stdout);
puts(buf);
return 0;
}
puts()将一个以null符终止的字符串写到标准输出,终止符不写出。但是,puts()然后又将一个新行符写到标准输出。
#include <stdio.h>
int main(int argc, const char *argv[])
{
// 中间添加'\0'
char buf[] = {'a', 'b', 'c', 'd' , 'e', 'f', '\n', '\0'};
//fputs(buf, stdout);
puts(buf);
return 0;
}
注意:
puts()然后又将一个新行符写到标准输出。fputs不加换行符。
检查文件出错函数
#include <stdio.h>
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
函数描述
feof : 用于检测文件末尾标志,如果该标志被设置返回非0的值,如果没有被设置返回0
ferror :用于检测出错标志,如果该标志被设置返回非0的值,如果没有设置返回0
clearerr :用于清除这两个标志
用法:
while (!feof(fp) && !ferror(stdin)) {
cTemp = fgetc(fp);
}
直接IO
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
函数参数:
ptr: 缓冲区的首地址
stream : 流
nmemb :元素个数(人为指定的)
size : 每个元素的大小(人为指定)
返回值:
成功 实际读到元素的个数(凑不足一个元素就不算)
失败 -1
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
函数参数:
ptr : 缓冲区首地址
size : 元素的daxiao
nmemb :元素的个数
stream : 流
返回值:
成功 实际写入的元素的个数
失败 -1
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
FILE *fp;
int data[10];
int data1[10];
int i;
for(i = 0; i < 10; i++)
{
data[i] = i;
}
if((fp = fopen("test_file", "w+")) == NULL)
{
fprintf(stderr, "fopen failed : %s", strerror(errno));
return -1;
}
fprintf(stdout, "[DEBUG] sizeof(int) = %d\n", sizeof(int));
// fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
// data为缓冲区首地址
// fwrite把缓冲区的写入fp
if(fwrite(data, sizeof(int), 3, fp) != 3)
{
perror("fwrite");
return -1;
}
fseek(fp, 0, SEEK_SET);
// 换数组data1
// fread把fp的写入缓冲区data1
if(fread(data1, 3, 2, fp) != 2)
{
perror("fread");
return -1;
}
// 输出缓冲区
for(i = 0; i < 3; i++)
{
printf("%d ", data1[i]);
}
fclose(fp);
return 0;
}
凑不够字节的时候
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
FILE *fp;
char buf[20] = "\0";
int nread;
if((fp = fopen("test_file", "r")) == NULL)
{
fprintf(stderr, "fopen failed : %s", strerror(errno));
return -1;
}
// nread读取2个元素,每个元素3个字节,所以需要读取6个字节
if((nread = fread(buf, 3, 2, fp)) == -1)
{
perror("fread");
return -1;
}
printf("%s\n", buf);
printf("nread = %d\n", nread);
fclose(fp);
return 0;
}
"test_file"里只有1 2 3 4 \0, 5个字节
nread读取2个元素,每个元素3个字节,所以需要读取6个字节,
但是因为"test_file"只有5个字节,所以只能成功读取1个元素,nread==
1,凑不够就不算。
流的格式化输入输出
int printf(const char *format, …); //输出到stdout
int fprintf(FILE *stream, const char *format, …); //输出到stream
int sprintf(char *str, const char *format, …); // 输出到char *str
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp = NULL;
int a = 10;
char buf[32] = "hello world";
fp = fopen("test", "w+");
if(NULL == fp)
{
perror("fopen_2");
return -1;
}
fprintf(fp, "%d %s\n",a, buf);
printf("%s\n", buf);
// 缓冲区被覆盖
sprintf(buf, "hello farsight %d", a);
printf("%s\n", buf);
a = 11;
fprintf(fp, "%d %s\n",a, buf);
fclose(fp);
return 0;
}
#include <stdio.h>
int scanf(const char *format, …); //stdinint fscanf(FILE *stream, const char *format, …);//stream
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp = NULL;
int a = 10;
char buff[32] = "\0";
fp = fopen("test", "r+");
if (NULL == fp)
{
perror("fopen");
return -1;
}
fscanf(fp,"%s%d", buff, &a);
printf("%s, %d\n", buff, a);
fclose(fp);
return 0;
}
>
int sscanf(const char *str, const char format, …);//charstr
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp = NULL;
int a, b;
char buff[32] = "\0";
char buf[32] = "1 2 hello";
fp = fopen("test", "r+");
if (NULL == fp)
{
perror("fopen_2");
return -1;
}
sscanf(buf, "%d%d%s", &a, &b, buff);
printf("%s, %d, %d\n", buff, a, b);
fclose(fp);
return 0;
}
流的定位
int fseek(FILE *stream, long offset, int whence);
off_t lseek(int fd, off_t offset, int whence);
参数说明
stream : 打开的流
offset : 相对于基准点偏移量
whence : 基准点
返回值: 返回值:偏移量
成功 返回0
失败 返回-1
返回当前流的偏移量
long ftell(FILE *stream);
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp;
int ret;
long int size;
fp = fopen(argv[1], "r+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
ret = fseek(fp, 0, SEEK_END);
if(-1 == ret)
{
perror("fseek");
}
size = ftell(fp);
printf("sizeo file is %ld\n", size);
return 0;
}
void rewind(FILE *stream);
rewind() 等价于 (void)fseek(stream, 0L, SEEK_SET)
实验三:
- 打开文件方式(a+)
- 接上一次的行号(计算行号)
获取时间
time
#include <time.h>
time_t time(time_t *t); time_t == long
函数功能:
返回从1970-1-1 0:0:0到函数调用那一刻的秒数
如果t非NULL,就将时间填入到t所指向的内存空间
返回值:
成功 : 时间
失败 :((time_t) -1)
#include <time.h>
struct tm *localtime(const time_t *timep);
函数功能:
将秒数转换成具体的时间
函数参数:
timep : 存秒数的地址
返回值:
#include <stdio.h>
#include <time.h>
int main(int argc, const char *argv[])
{
time_t mytime;
struct tm *p;
mytime = time(NULL);
p = localtime(&mytime);
printf("%d-%d-%d %d:%d:%d\n", p->tm_year+1900, p->tm_mon+1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
return 0;
}
文件信息
获取文件属性:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
函数参数:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */ };
三个函数的返回:若成功则为0,若出错则为-1,并且设置errno.
给定一个pathname的情况下:
stat函数返回一个与此命名文件有关的信息结构
fstat函数获得已在描述符filedes上打开的文件的有关信息
lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息
stat与之相反,返回的是链接文件引用的文件的信息
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
struct stat buf;
stat(argv[1], &buf);
printf("size of %s is %d\n", argv[1], buf.st_size);
return 0;
}
st_mode
首先S_IFMT是一个掩码,它的值是017000(注意这里用的是八进制), 可以用来过滤出前四位表示的文件类型。
其后的连续七个分别对应套接口文件、符号链接文件、普通文件、块设备、目录、字符设备、管道,它们分别对应一个不同的值。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
struct stat buf;
lstat(argv[1], &buf);
#if 0
if(S_ISREG(buf.st_mode))
printf("regular file\n");
else if(S_ISDIR(buf.st_mode))
printf("directory\n");
else if(S_ISCHR(buf.st_mode))
printf("character device\n");
else if(S_ISBLK(buf.st_mode))
printf("block device\n");
else if(S_ISFIFO(buf.st_mode))
printf("FIFO\n");
else if(S_ISLNK(buf.st_mode))
printf("symbolic link\n");
else if(S_ISSOCK(buf.st_mode))
printf("socket\n");
#else
switch(S_IFMT & buf.st_mode)
{
case S_IFSOCK:
printf("socket\n");break;
case S_IFLNK:
printf("symbolic link\n");break;
case S_IFREG:
printf("regular file\n");break;
case S_IFBLK:
printf("block device\n");break;
case S_IFDIR:
printf("directory\n");break;
case S_IFCHR:
printf("character device\n");break;
case S_IFIFO:
printf("FIFO\n");break;
default:
printf("error\n");
}
#endif
return 0;
}
硬链接和软连接
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
struct stat buf;
stat(argv[1], &buf);
printf("number of hand link is %d\n", buf.st_nlink);
return 0;
}
获取用户相关信息
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
uid : 用户ID
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
int main(int argc, const char *argv[])
{
struct stat buf;
struct passwd *p;
stat(argv[1], &buf);
p = getpwuid(buf.st_uid);
printf("file owner is %s\n", p->pw_name);
return 0;
}
获取用户分组
#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
gid :用户组ID
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* group members */
};
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
int main(int argc, const char *argv[])
{
struct stat buf;
struct group *p;
stat(argv[1], &buf);
p = getgrgid(buf.st_gid);
printf("file group is %s\n", p->gr_name);
return 0;
}
目录操作函数
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, const char *argv[])
{
DIR *dir;
struct dirent *p;
//step 1:打开目录
dir = opendir(argv[1]);
if(NULL == dir)
{
perror("opendir");
return -1;
}
//step 2:读目录
while((p = readdir(dir)) != NULL)
{
if(!strncmp(".", p->d_name, 1))
continue;
printf("%s\t", p->d_name);
}
printf("\n");
//step 3:关闭目录
closedir(dir);
return 0;
}
库
Linux的库文件保存在 /lib /usr/lib文件夹下
Linux和window下库文件的格式不兼容。
静态库特点
编译(链接)时把静态库中相关代码复制到可执行文件中,直接加载静态库中对应的代码 (不是全部,是部分)。
程序中已包含代码,运行时不再需要静态库。
静态库升级后,程序需要重新编译链接。
编写库源码hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}
编译生成目标文件
gcc -c hello.c -Wall
创建静态库 hello
ar crs libhello.a hello.o
静态库名称:libhello.a
名称都是有规范的:libXXXX.a
根据后面的执行文件hello.o生成静态库
查看库中符号信息
nm libhello.a
链接静态库
编写应用程序test.c
#include <stdio.h>
void hello(void);
int main() {
hello();
return 0;
}
编译test.c 并链接静态库libhello.a
gcc -o test test.c -L. -lhello
因为静态库里的代码已经加载到了程序里,所以删除静态库,不影响程序执行
共享库的特点
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码。
程序不包含库中代码,尺寸小。
程序运行时需要加载库。
库升级方便,无需重新编译程序。
编写库源码hello.c bye.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}
#include <stdio.h>
void hello(void) {
printf(“Bye\n”);
return;
}
编译生成目标文件
gcc -c -fPIC hello.c bye.c -Wall
使用相对寻址,可以加载到不同位置
创建共享库 common
gcc -shared -o libcommon.so.1 hello.o bye.o
libcommon.so.1:libcommon.so.X的X代表库的版本,libcommon.so.2,libcommon.so.3…
为共享库文件创建链接文件
ln -s libcommon.so.1 libcommon.so
符号链接文件命名规则
lib<库名>.so
链接共享库
编写应用程序test.c
#include <stdio.h>
#include “common.h”
int main() {
hello();
bye();
return 0;
}
编译test.c 并链接共享库libcommon.so
gcc -o test test.c -L. -lcommon
gcc首先链接共享库,再链接静态库
gcc -o test test.c -L. -lcommon -static
指定链接静态库
执行程序
./test
./test: error while loading shared libraries: libcommon.so
cannot open shared object file : No such file or directory
当前目录不在系统搜索路径里,所以会报错。
添加共享库的加载路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
LD_LIBRARY_PATH可以添加环境中缺省的路径
如何找到共享库
-
把库拷贝到/usr/lib和/lib目录下
不推荐,会跟系统库混乱 -
在LD_LIBRARY_PATH环境变量中添加库所在路径
临时的,只对当前shell起作用 -
添加/etc/ld.so.conf.d/XXX.conf文件,执行ldconfig刷新