目录
一、标准IO介绍及缓冲区
1. 文件的概念和类型
概念:
一组相关数据的有序集合
文件类型:
- 常规文件 r
- 目录文件 d
- 字符设备文件 c
- 块设备文件 b
- 管道文件 p
- 套接字文件 s
- 符号链接文件 l
2. 如何理解标准IO
标准I/O由ANSI C标准定义
主流操作系统上都实现了C库
标准I/O通过缓冲机制减少系统调用,实现更高的效率
3. 流(FILE)的含义
流的概念
就是数据的流,在程序中就是一个结构体。
Windows 和linux的换行符区别
Windows是\r\n
Linux 是\n
(1)标准I/O – 流
FILE
标准IO用一个结构体类型来存放打开的文件的相关信息
标准I/O的所有操作都是围绕FILE来进行
流(stream)
FILE又被称为流(stream)
文本流/二进制流
(2)标准I/O – 文本流和二进制流
Windows
二进制流: 换行符 ‘\n’
文本流: 换行符 ‘\r’ ‘\n’
Linux
换行符 ‘\n’
(3)标准I/O – 流的缓冲类型
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作(最大缓冲1024个字节)
行缓冲
当在输入和输出中遇到换行符(‘\n’)时,进行I/O操作
当流和一个终端关联时,典型的行缓冲
无缓冲
数据直接写入文件,流不进行缓冲
(3)标准I/O –stdin,stdout,stderr
标准I/O预定义3个流,程序运行时自动打开
二、标准IO:文件的打开、关闭及代码实现
1. 文件的打开实现(fopen)
下列函数可用于打开一个标准I/O流:
FILE *fopen (const char *path, const char *mode);
成功时返回流指针;出错时返回NULL
示例:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main (int agrc, char * agrv[]) {
FILE *fp;
fp = fopen("text.c","w");
if (fp == NULL) {
printf("fopen: %s\n", strerror(errno));//strerror输出错误信息的函数
} else {
perror("fopen: ");//perror输出错误信息的函数
}
return 0;
}
mode参数:
(1)标准I/O – fopen – 新建文件权限
fopen() 创建的文件访问权限是0666(rw-rw-rw-)
Linux系统中umask设定会影响文件的访问权限,其规则为(0666 & ~umask)
Root用户是 022 普通用户是002
用户可以通过umask函数或者命令修改相关设定
如果希望umask不影响文件访问权限,该如何设定?
(2)标准I/O – 处理错误信息
extern int errno;
void perror(const char *s);
char *strerror(int errno);
errno 存放错误号,由系统生成
perror 先输出字符串s,再输出错误号对应的错误信息
strerror 根据错误号返回对应的错误信息
示例-1:
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
perror(“fopen”);
return -1;
}
……
fopen: No such file or directory
示例-2:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
printf(“fopen: %s\n”, strerror(errno));
return -1;
}
……
fopen: No such file or directory
2. 文件的关闭实现(fclose)
int fclose(FILE *stream);
- fclose()调用成功返回0,失败返回EOF,并设置errno
- 流关闭时自动刷新缓冲中的数据并释放缓冲区
- 当一个程序正常终止时,所有打开的流都会被关闭。
- 流一旦关闭后就不能执行任何操作
- 当调用该函数时,*stream必须是存在的文件,否则会出现段错误
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main (int agrc, char * agrv[]) {
FILE *fp;
int fpret;
fp = fopen("text.c","w");
if (fp == NULL) {
printf("fopen: %s\n", strerror(errno));
perror("fopen: ");
} else {
perror("fopen: ");
fpret = fclose(fp);//fpert存放fclose函数的返回值
if (fpret == 0) {
printf("fclose: %s\n", strerror(errno));
} else {
perror("fclose:");
}
}
return 0;
}
三、标准IO的读写(字符、行)
1. 字符的输入(读单个字符)
int fgetc(FILE *stream);
int getc(FILE *stream); //宏
int getchar(void);
成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1),
getchar()等同于fgetc(stdin)
getc和fgetc区别是一个是宏一个是函数
注意事项:
- 函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
- stdin 也是FILE *的指针,是系统定义好的,指向的是标准输入(键盘输入)
- 打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
- 调用getchar会阻塞,等待你的键盘输入
#include <stdio.h>
int main (int agrc, char * agrv[]) {
FILE *fp;
fp = fopen("1.txt","r+");
int rec;
if (fp == NULL) {
perror("fopen: ");
return 0;
}
rec = fgetc(fp);
printf("Get char : %c\n", rec);
return 0;
}
输出:w
2. 字符的输出(写单个字符)
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
成功时返回写入的字符;出错时返回EOF
putchar©等同于fputc(c, stdout)
注意事项:
- 返回和输入参数都是int类型
- 遇到这种错误:Bad file descriptor, 很可能是文件打开的模式错误(只读模式去写,只写模式去读)
#include <stdio.h>
int main (int agrc, char * agrv[]) {
FILE *fp;
fp = fopen("1.txt","r+");
int rec;
if (fp == NULL) {
perror("fopen: ");
return 0;
}
int wrc = 'w';
rec = fputc(wrc,fp);
if (rec == -1) {
perror("fputc:");
fclose(fp);
return 0;
}
putchar(wrc);
return 0;
}
3. 行输入(读取整个行)
char *gets(char *s); 读取标准输入到缓冲区s
char *fgets(char *s, int size, FILE *stream);
成功时返回s,到文件末尾或出错时返回NULL
遇到’\n’或已输入size-1个字符时返回,总是包含’\0’
注意事项:
- gets函数已经被淘汰,因为会导致缓冲区溢出
- fgets 函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于size-1 后面会添加换行符。
#include <stdio.h>
int main (int agec, char * agev[]) {
FILE *fp;
char buff[100];
char *ret;
fp = fopen("1.txt","a+");
if (fp == NULL) {
perror("fopen:");
return 0;
}
ret = fgets(buff, 100, fp);
if (ret == NULL) {
perror("fgets");
fclose(fp);
return 0;
}
printf("buff = %s\n",ret);
return 0;
}
4. 行输出(写整行)
int puts(const char *s);
int fputs(const char *s, FILE *stream);
成功时返回非负整数;出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加’\n’
fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’
#include <stdio.h>
int main (int agec, char * agev[]) {
FILE *fp;
char buff[100];
int retn;
fp = fopen("1.txt","a+");
if (fp == NULL) {
perror("fopen:");
return 0;
}
retn = fputs("HEllO WORLD!!!!", fp);
if (retn == -1) {
perror("fputs:");
return 0;
}
printf("Hello world!\n");
fclose(fp);
return 0;
}
四、标准IO读写:二进制方式
文本文件和二进制的区别:
存储的格式不同:文本文件只能存储文本。
计算机内码概念:文本符号在计算机内部的编码(计算机内部只能存储数字0101001…,所以所有符号都要编码)
二进制读写函数格式
size_t fread(void *ptr, size_t size, size_t n, FILE *fp);
void *ptr 读取内容放的位置指针
size_t size 读取的块大小
size_t n 读取的个数
FILE *fp 读取的文件指针
将*fp文件的内容读出来,放入*ptr当中
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
void *ptr 写文件的内容的位置指针
size_t size 写的块大小
size_t n 写的个数
FILE *fp 要写的文件指针
将*ptr缓冲区当中写入到*fp文件当中
注意事项:
文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
解决办法:移动指针,流的定位或关闭文件,重新打开
示例:
#include <stdio.h>
#include <stdlib.h>
struct student {
char name[16];
int age;
char sex[8];
};
int main (int agrc, char * agrv[]) {
FILE *fp;
size_t ret;
struct student stu[2] = {
"zhangsan", 61, "male",
"zhangcuihua", 49, "woman"
};
struct student stu2[2];
fp = fopen("student.bin", "w");//打开文件
if (fp == NULL) {
perror("fopen");
return 0;
}
ret = fwrite(&stu, sizeof(stu), 1, fp);//读入文件
if (ret == -1) {
perror("fwrite");
goto end;
} else {
printf("fwrite struct student success\n");
}
fclose(fp);//关闭文件
fp = fopen("student.bin", "r");//重新打开文件
if (fp == NULL) {
perror("fopen");
goto end;
}
ret = fread(&stu2, sizeof(stu2), 1, fp);//读出文件
if (ret == -1) {
perror("fread");
goto end;
}
printf("name = %s, age = %d, sex = %s\n", stu2[0].name, stu2[0].age, stu2[0].sex);
printf("name = %s, age = %d, sex = %s\n", stu2[1].name, stu2[1].age, stu2[1].sex);
end:
fclose(fp);
return 0;
}
五、流的刷新定位
1. 流的刷新
int fflush(FILE *fp);
成功时返回0;出错时返回EOF
将流缓冲区中的数据写入实际的文件
Linux下只能刷新输出缓冲区,输入缓冲区丢弃
如果输出到屏幕使用fflush(stdout)
1. 流的定位
long ftell(FILE *stream);
long fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);
fseek 参数whence参数:SEEK_SET/SEEK_CUR/SEEK_END
SEEK_SET 从距文件开头 offset 位移量为新的读写位置
SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量
offset参数:偏移量,可正可负
注意事项:
1.文件的打开使用a模式 fseek无效
2.rewind(fp) 相当于 fseek(fp,0,SEEK_SET);
3.这三个函数只适用2G以下的文件
编译告警错误:
表示参数类型不匹配
示例:
#include <stdio.h>
int main (int argv, char *argc[]) {
FILE *fp;
fp = fopen("1.txt", "w");
if (fp == NULL) {
perror("fopen");
return 0;
}
char ch1[] = "abcdefgh";
char ch2[] = "!!!";
fwrite(&ch1, sizeof(ch1)-1 , 1, fp);
// printf("current fp = %d\n", (int)ftell(fp)); //输出: 6
// rewind(fp);
// printf("After rewind fp = %d\n", (int)ftell(fp));// 输出:0
fseek(fp, 2, SEEK_SET);//输出: ab!!!fgh
// fseek(fp, -2, SEEK_CUR);// 输出: abcdef!!!
fseek(fp, 2, SEEK_END);// 输出:abcdefgh !!!
fwrite(&ch2, sizeof(ch2)-1 , 1, fp);
fclose(fp);
return 0;
}
六、格式化输入输出
1. 格式化输出(fprintf、sprintf)
int fprintf(FILE *stream, const char *fmt, …); 将内容输出到文件流上面
int sprintf(char *s, const char *fmt, …); 将内容输出到字符串上面
成功时返回输出的字符个数;出错时返回EOF
fprintf
#include <stdio.h>
int main(int argv, char *argc[]) {
FILE *fp;
fp = fopen("ftext.txt", "w");
if (fp == NULL) {
perror("fopen");
return 0;
}
int year = 2000;
int month = 10;
int day = 19;
fprintf(fp, "%d-%d-%d", year, month, day);
fclose(fp);
return 0;
}
sprintf
#include <stdio.h>
int main(int argv, char *argc[]) {
char buf[100] = {0};
int year = 2000;
int month = 10;
int day = 19;
sprintf(buf, "%d-%d-%d", year, month, day);
printf("%s\n",buf);
return 0;
}
2. 格式化输入(fscanf、sscanf)
int fscanf(FILE *stream, const char *format, …); 从文件流里面取出指定的变量
int sscanf(const char *str, const char *format, …); 从字符串里面取出指定的变量
重点掌握sprintf 和sscanf
fscanf:
#include <stdio.h>
int main (int argv, char *argc[]) {
int year = 2000;
int month = 10;
int day = 19;
FILE *fp;
fp = fopen("ftext.txt", "w");
if (fp == NULL) {
perror("fopen");
return 0;
}
fscanf(fp, "%d-%d-%d", &year, &month, &day);
printf("%d-%d-%d\n",year, month, day);
fclose(fp);
return 0;
}
sscanf:
#include <stdio.h>
int main (int argv, char *argc[]) {
char buf[100] = {0};
int year = 2000;
int month = 10;
int day = 19;
sprintf(buf, "%d-%d-%d", year, month, day);
printf("%s\n",buf);
int syear;
int smonth;
int sday;
sscanf(buf, "%d-%d-%d", &syear, &smonth, &sday);
printf("%d-%d-%d\n",syear, smonth, sday);
return 0;
}
七、标准IO练习
问题:
代码实现:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char *argv[]) {
FILE *fp;
time_t ctime;
struct tm *ctimestr;
int linecount = 0;
char buf[32];
fp = fopen("1.txt", "a+");
if (fp == NULL) {
perror("fopen");
return 0;
}
while(fgets(buf, 32, fp) != NULL) {//利用fgets判断文件里面有多少行
if(buf[strlen(buf)-1] == '\n'){//注意判断是否是一行结束
linecount++;
}
}
while(1) {
ctime = time(NULL);
ctimestr = localtime(&ctime);
printf("%d, %04d-%02d-%02d %02d:%02d:%02d\n", linecount, ctimestr->tm_year+1900, ctimestr->tm_mon+1, ctimestr->tm_mday,
ctimestr->tm_hour, ctimestr->tm_min, ctimestr->tm_sec);
fprintf(fp, "%d, %04d-%02d-%02d %02d:%02d:%02d\n", linecount, ctimestr->tm_year+1900, ctimestr->tm_mon+1, ctimestr->tm_mday,
ctimestr->tm_hour, ctimestr->tm_min, ctimestr->tm_sec);
fflush(fp);
linecount++;
sleep(1);
}
return 0;
}
提示:
time()用来获取系统时间(秒数)
time_t time(time_t *seconds) 1970.1.1 0:0:0
localtime()将系统时间转换成本地时间
struct tm *localtime(const time_t *timer)
struct tm {
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月份,范围从 0 到 11 */
int tm_year; /* 自 1900 起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
注意:
int tm_mon; 获取的值要加1是正确的月份
int tm_year; 获取的值加1900是正确的年份
获取文件内的所有行数量:
while(fgets(buf,32,fp)!=NULL){
if(buf[strlen(buf)-1] =='\n'){ //注意判断是否是一行结束
linecount++;
}
}
写完文件记得fflush ,写到磁盘里面去。
标准IO磁盘文件的缓冲区一般为4096
注意和标准输出的全缓冲区别,标准输出是1024
八、文件IO(概念、打开、读、写、关闭)
1. 文件I/O的概念
什么是文件IO,又称系统IO,系统调用
是操作系统提供的API接口函数。
POSIX接口 (了解)
注意:文件IO不提供缓冲机制
文件IO的API
open close read read
文件描述符概念:
英文:缩写fd(file descriptor)
是0-1023的数字,表示文件。
0, 1, 2 的含义 标准输入,标准输出,错误
2. 文件I/O – open打开
open函数用来创建或打开一个文件:
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
成功时返回文件描述符;出错时返回EOF
打开文件时使用两个参数
创建文件时第三个参数指定新文件的权限,(只有在建立新文件时有效)此外真正建文件时的权限会受到umask 值影响,实际权限是mode-umaks
可以打开设备文件,但是不能创建设备文件(创建设备mknode 驱动部分会讲)
成功时返回文件描述符;出错时返回EOF
文件IO和标准的模式对应关系:
r O_RDONLY
r+ O_RDWR
w O_WRONLY | O_CREAT | O_TRUNC, 0664
w+ O_RDWR | O_CREAT | O_TRUNC, 0664
a O_WRONLY | O_CREAT | O_APPEND, 0664
a+ O_RDWR | O_CREAT | O_APPEND, 0664
umask概念:
umask :用来设定文件或目录的初始权限
文件和目录的真正初始权限
文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (int argv, char *argc[]) {
int fd;
fd = open("1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0){
perror("open");
return 0;
}
}
3. 文件I/O – read 读取
read函数用来从文件中读取数据:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
成功时返回实际读取的字节数;出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区
count不应超过buf大小
4. 文件I/O – write写入
write函数用来向文件写入数据:
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
成功时返回实际写入的字节数;出错时返回EOF
buf是发送数据的缓冲区
count不应超过buf大小
容易出错点:
字符串长度使用sizeof,对二进制数据使用strlen
printf 的字符最后没有’\0’
示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
int fd;
int ret;
char buf[32] = "hello world";
char buf2[32] = {0};
fd = open("1.txt", O_RDWR | O_CREAT | O_APPEND, 0666);//打开文件
if(fd < 0) {
perror("open");
return 0;
}
ret = write(fd, buf, strlen(buf)); //将buf写入fd文件当中
if (ret < 0) {
perror("write");
goto END;
}
lseek(fd, 0, SEEK_SET);//文件流定位到开始
ret = read(fd, buf2, 32);//读取文件到buf2当中
if (ret < 0) {
perror("read");
goto END;
}
printf("read buf2 = %s\n", buf2);
END:
close(fd);
return 0;
}
5. 文件I/O – lseek定位
lseek函数用来定位文件:
#include <unistd.h>
off_t lseek(int fd, off_t offset, intt whence);
成功时返回当前的文件读写位置;出错时返回EOF
参数offset和参数whence同fseek完全一样
6. 文件I/O – close关闭
close函数用来关闭一个打开的文件:
#include <unistd.h>
int close(int fd);
成功时返回0;出错时返回EOF
程序结束时自动关闭所有打开的文件
文件关闭后,文件描述符不再代表文件
九、目录操作和文件属性获取
1. 打开目录 (opendir)
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd); 使用文件描述符,要配合open函数使用
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针;出错时返回NULL
2. 读取目录(readdir)
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
struct dirent是用来描述目录流中一个目录项的结构体类型
包含成员char d_name[256] 参考帮助文档
成功时返回目录流dirp中下一个目录项;
出错或到末尾时时返回NULL
struct dirent结构体
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all filesystem types */
char d_name[256]; /* filename */
};
示例:
#include <dirent.h>
#include <stdio.h>
int main(int argc,char **argv){
DIR* dp;
struct dirent *dt;
dp=opendir("/mnt/hgfs/share/newIOP/");
if(dp == NULL){
perror("opendir");
return 0;
}
while((dt=readdir(dp))!=NULL){
printf("%s\n",dt->d_name);
}
closedir(dp);
}
3. 关闭目录(clodedir)
closedir函数用来关闭一个目录文件:
#include <dirent.h>
int closedir(DIR *dirp);
成功时返回0;出错时返回EOF
4. 修改文件权限(chmod)
chmod/fchmod函数用来修改文件的访问权限:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
成功时返回0;出错时返回EOF
注意:在vmware和windows共享的文件夹下,有些权限不能改变。
示例:
#include <stdio.h>
#include <sys/stat.h>
int main(int argc,char **argv){
int ret;
ret = chmod("temp",0444); //将temp文件修改成0444八进制对应的 r--r--r--
if(ret<0){
perror("chmod");
return 0;
}
}
5. 获取文件属性(stat)
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
成功时返回0;出错时返回EOF
如果path是符号链接stat获取的是目标文件的属性;而lstat获取的是链接文件的属性
stat结构体
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
> mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小)
unsigned long st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
文件类型—— st_mode
通过系统提供的宏来判断文件类型:
S_IFMT 0170000 文件类型的位遮罩
S_ISREG(st_mode) 0100000 是否常规文件
S_ISDIR(st_mode) 0040000 是否目录
S_ISCHR(st_mode) 0020000 是否字符设备
S_ISBLK(st_mode) 0060000 是否块设备
S_ISFIFO(st_mode) 0010000 是否FIFO文件
S_ISLNK(st_mode) 0120000 是否链接文件
S_ISSOCK(st_mode) 0140000 是否SOCKET文件
文件访问权限 – st_mode
通过系统提供的宏来获取文件访问权限:
S_IRUSR 00400 bit:8 所有者有读权限
S_IWUSR 00200 7 所有者拥有写权限
S_IXUSR 00100 6 所有者拥有执行权限
S_IRGRP 00040 5 群组拥有读权限
S_IWGRP 00020 4 群组拥有写权限
S_IXGRP 00010 3 群组拥有执行权限
S_IROTH 00004 2 其他用户拥有读权限
S_IWOTH 00002 1 其他用户拥有写权限
S_IXOTH 00001 0 其他用户拥有执行权限
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
int main (int argc,char **argv){
struct stat buf;
int ret;
ret = stat("chmod_t.c",&buf);
if(ret<0){
perror("stat");
return 0;
}
/* 判断文件类型 是否为常规文件 */
if(S_ISREG(buf.st_mode)){
printf("-");
}
/* 判断文件的权限 */
int i;
for(i=8;i>=0;i--) {
/*if(buf.st_mode & (1<<i)) 这个条件语句检查 buf.st_mode 中第 i 位是否为 1。(1<<i) 表示将二进制数 1 左移 i 位,然后和 buf.st_mode 进行按位与运算。如果结果不为 0,则表示第 i 位是 1,即文件权限中有该权限。*/
if(buf.st_mode & (1<<i)) {
/* 根据 i 对 3 取模的结果来确定该权限属于哪一组(每组有 3 位,对应 rwx 的权限) */
switch(i%3) {
case 2:
printf("r");
break;
case 1:
printf("w");
break;
case 0:
printf("x");
break;
}
} else {
printf("-");
}
}
/* 文件字节数(文件大小) */
printf(" %d",(int)buf.st_size);
/* 打印该文件的时间 */
struct tm *t;
t = localtime(&buf.st_ctime);
printf(" %d-%d-%d %d:%d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min);
printf(" chmod_t.c\n");
}
readdir函数是一个结构体返回值有(可通过 man 函数查看):
struct old_linux_dirent {
long d_ino; /* inode number */
off_t d_off; /* offset to this old_linux_dirent */
unsigned short d_reclen; /* length of this d_name */
char d_name[NAME_MAX+1]; /* filename (null-terminated) */
}
十、静态库和动态库的使用
库的概念
库是一个二进制文件,包含的代码可被程序调用
标准C库、数学库、线程库……
库有源码,可下载后编译;也可以直接安装二进制包
/lib /usr/lib
1. 静态库
特点:
编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已包含代码,运行时不再需要静态库
程序运行时无需加载库,运行速度更快
占用更多磁盘和内存空间
静态库升级后,程序需要重新编译链接
创建静态库步骤:
- 编写库文件代码,编译为.o 目标文件。
确定库中函数的功能、接口
编写库源码hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}
编译生成目标文件
$ gcc -c hello.c -Wall
- 创建静态库 hello
ar 命令 创建libxxxx.a .o文件
$ ar -rsv libhello.a hello.o
ar 参数:
c 禁止在创建库时产生的正常消息
r 如果指定的文件已经存在于库中,则替换它
s 无论 ar 命令是否修改了库内容都强制重新生成库符号表
v 将建立新库的详细的逐个文件的描述写至标准输出
q 将指定的文件添加到库的末尾
t 将库的目录写至标准输出
注意:
静态库名字要以lib开头,后缀名为.a
没有main函数的.c 文件不能生成可执行文件。
链接错误:
test.c:(.text+0x15):对‘hello’未定义的引用
collect2: error: ld returned 1 exit status
含义:表示hello函数在编译的源码内没有找到实现
解决:实现代码或者找到对应函数的库并且链接它。
- 链接静态库:
gcc -o 目标文件 源码.c -L路径 -lxxxx
编写应用程序test.c
#include <stdio.h>
void hello(void);
int main() {
hello();
return 0;
}
编译test.c 并链接静态库 libhello.a
$ gcc -o test test.c -L. -lhello
$ ./test
hello world
-L 表示库所在的路径
-l 后面跟库的名称
查看库中符号信息
$nm libhello.a -a
hello.o:
0000000 T hello
U puts
nm:显示指定文件中的符号信息
-a:显示所有符号
2. 动态库
特点:
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
程序不包含库中代码,尺寸小
多个程序可共享同一个库
程序运行时需要加载库
库升级方便,无需重新编译程序
使用更加广泛
动态库的生成步骤:
- 生成位置无关代码的目标文件
确定库中函数的功能、接口
编写库源码hello.c bye.c
#include <stdio.h>
void hello(void) {
printf("hello world\n");
return;
}
#include <stdio.h>
void bye(void) {
printf("bye\n");
return;
}
编译生成目标文件
$ gcc -c -fPIC hello.c bye.c -Wall
-
生成动态库
$ gcc -shared -o libcommon.so hello.o bye.o -
编译可执行文件(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
执行程序
$ ./test
./test: error while loading shared libraries: libcommon.so cannot open shared object file : No such file or directory
因为创建的是动态库,系统找不到该链接库文件,解决方法有五种:
第一种:把库拷贝到/usr/lib目录下
$ sudo cp libcommon.o /usr/lib
$ ./test
hello
bye
注:这种方法会把lib文件夹搞得很乱,所以不方便
第二种:在LD_LIBRARY_PATH环境变量中添加库所在路径
$ export LD_LIBRARY_PATH=./
$ ./test
hello world
bye
注:当前窗口下可执行,关闭当前窗口就失效了
第三种:将环境变量写入到系统默认的环境变量配置 bashrc 文件当中
$ vi ~/.bashrc
将
export LD_LIBRARY_PATH=./ //注意=号两侧不要有空格
这一行命令添加到bashrc文件末尾 "./"换成libcommon.o所在的绝对路径
$ source ~/.bashrc //生效环境变量
$ ./test
hello world
bye
注:这个方法就可以在打开任意的终端窗口都会执行这个环境变量,快关机也不影响
查看可执行文件使用的动态库
ldd 命令 : ldd 你的可执行文件
$ lld test
linux-gate.so.1 => (0xb771d000)
libcommon.so => /home/myubuntu/lv6/libcommon.so (0xb7716000) //库文件所在的路径
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb754f000)
/lib/ld-linux.so.2 (0xb771f000)
删除libmyheby.so库以后的结果将会找不到该文件链接的库文件