视频地址:华清远见—https://www.bilibili.com/video/BV1dz411B7vj?p=1
一组相关数据的有序集合
常规文件: r ----- 二进制文件;ASCII码文件
目录文件: d
字符设备文件: c
块设备文件: b
管道文件: p
套接字文件: s
符号链接文件: l
不同的操作系统,所支持的文件类型不一样;
C库中定义好的一些用于输入和输出的函数;主流操作系统上都实现了C库;
标准IO通过缓冲机制减少系统调用,实现了更高的效率;
没有缓冲机制的情况下,应用程序每次读写文件都需要通过系统调用来完成;(应用程序指定读取多少数据,系统调用就读取多少数据)
应用程序内部开辟一个缓冲区buf(4k),(一次性读取4k的数据返回应用程序);缓冲区有数据,直接从缓冲区读取,避免重复的系统调用。(写入数据的时候也是先往缓冲区中写数据,缓冲区满了才会去写实际的文件)
代码直接操作硬件:单片机;
有操作系统的情况下,应用程序访问硬件,必须通过操作操作系统提供的系统调用接口来操作硬件;
不同的操作系统,系统调用接口不兼容。
标准IO中用一个结构体类型来存放打开的文件的相关信息;(系统头文件中定义好的)
标准IO中所有的操作都是围绕FILE来进行。(一个FILE就代表一个打开的文件)
FILE又被称为流;
文本流/二进制流
Windows系统中:(对换行符的处理不同)
二进制流:换行符 — ‘\n’ 文本流:换行符 — ‘\r’‘\n’//回车和换行
Linux系统中不作区分,都看作二进制流;
当流的缓冲区无数据(读)或无空间(写)时才执行实际I/O操作;(默认缓冲类型)
当在输入和输出时遇到换行符‘\n’时,进行I/O操作;(进行实际的文件操作,读或写)
当流和一个终端关联时,默认为行缓冲。(典型:标准输入和标准输出流。如:打印调试信息时,如果不加换行符,打印的信息仅仅会写在标准输出流的缓冲区中,并没有写在实际的终端上)
数据直接写入文件,流不进行缓冲。(对流进行操作的时候都是访问实际的文件;如:打印错误信息时,希望错误信息直接打印在终端上。标准错误流默认为无缓冲)
在linux下运行一个程序时,系统会自动打开三个流,(标准输入stdin,标准输出stdout,标准错误流stderr)可以在程序中直接使用。
FILE *fopen(const char *path,const char *mode);
//第一个参数:指定打开文件的路径(通过字符串或者命令行参数把==文件名传递进来==);
//第二个参数:流的打开方式
//成功时返回流指针;出错时返回NULL。(fopen的返回值通常要保存下来进行错误处理)
mode参数:
‘r’或‘rb’ — 文件以只读方式打开,文件必须存在。
r表示以文本流的方式打开一个文件;rb表示以二进制的方式打开文件。//linux不区分文本流和二进制流
‘r +’或‘r + b’ — 文件以读写方式打开,文件必须存在。
‘w’或‘wb’ — 文件以只写方式打开,文件必须存在则文件长度清0(内容清空);若文件不存在则创建。
‘w + ’或‘w + b’ — 文件以读写方式打开,文件必须存在则文件长度清0(内容清空);若文件不存在则创建。
//文件原来内容已经被清空,只能读取新写入的内容。
‘a’或‘ab’ — 文件以只写方式打开,若文件不存在则创建;向文件写入的数据会被追加到文件末尾。
‘a + ’或‘a + b’ — 文件以读写方式打开,若文件不存在则创建;向文件写入的数据会被追加到文件末尾。
//可以读取到文件原来的内容。
#include
int main(int argc,char *argv[])
{
FILE *fp;//定于一个流指针fp
if((fp = fopen("text.txt","r +")) == NULL)//调用fopen(文件名,打开方式),如果只有文件名无路径,默认为当前路径;
//判断fp的值是否存在,若不存在打印错误信息;
{
printf("fopen error\n");//标准输出流是行缓冲,加'\n'可以立即显示在终端上;
return -1;
}
...
return 0;
}
fopen()创建的文件默认访问权限是0666(rw-rw-rw-) //0表示8进制数,后面分别表示用户、同组用户和其他用户的权限。
在linux中,umask的设定会影响新建文件的访问权限,其规则为(0666 &~ umask)
umask值:root用户默认是0022,普通用户默认是 0002;
// root用户先位反再进行与操作之后文件权限为0644;
用户可以通过umask函数来设置权限掩码,只会影响到当前程序
umask 002//权限改为002
umask//查看umask值
掩码设置为0时,对文件的权限无影响
extern int errno; // errno用来存放错误号的整型变量;
void perror(const char *s); // perror先输出字符串s,再打印错误号对应的错误信息;
char *strerror(int errno); // 根据错误号返回对应的错误信息;(返回值是字符串首地址,存放的是错误信息)
1.perror打印错误信息
#include
int main(int argc, char *argv[])
{
FILE *fp;
if((fp = fopen("text.txt","r +")) == NULL){
perror("fopen");
return -1;
}
...
}
2.strerror(erron)打印错误信息
#include
#include //声明strerror函数
#include //声明errno变量
int main(int argc, char *argv[])
{
FILE *fp;
if((fp = fopen("text.txt","r +")) == NULL){
printf("fopen:%s\n",strerror(errno));
return -1;
}
...
}
int fclose(FILE *stream);//参数是之前打开的流指针
//调用成功返回0,失败返回EOF,并设置errno;
流关闭时自动强制刷新缓冲区的数据并释放缓冲区;
当一个程序正常终止时,所有打开的流都会被关闭;(要养成手动关闭流的习惯)
流一旦关闭后就不能执行任何操作。
if(fp != NULL)
fclose(fp);//流关闭之前需要判断是否为NULL
如何测试程序中能够打开的文件或流的最大个数
#include
#include
#include
int main(int argc, char *argv[])
{
int count = 0;
FILE *fp;
while(1)
{
if((fp = fopen("text.txt","a")) == NULL){
perror("fopen");
break;
}
count++;
}
printf("count = %d\n",count);
if(fp != NULL)
fclose(fp);
return 0;
}
***
fopen:Too many open files
count = 1021
段错误 (核心已转储)//段错误原因---fp有为空的情况,可用fclose(stdout)关闭;或者先判断是否为空指针
1021 + stdin + stdout + stderr = 1024
fgetc()/fputc() 一次读/写一个字 //文本文件和二进制文件都可以但效率低
#include
int fgetc(FILE *stream);//参数为流指针,用来指定从哪个流中读取字符
int getc(FILE *stream);//等同fgetc
int getchar(void);//没有参数,只能从标准输入流中读取一个字符并返回
getchar() == fgetc(stdin)
//成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)
FILE *fp;
int ch,count = 0;
if(fp = fopen("text.txt","r")) == NULL){//依次从文件中读取一个字符;
perror("fopen");
return -1;
}
while((ch = fgetc(fp)) != EOF){//如果读取的数据不是-1,就计数;
count++;
}
printf("total %d bytes\n",count);
#include
int fputc(int c,FILE *stream);//(要输出的字符,指定写入的流)
int putc(int c,FILE *stream);//等同fputc
int putchar(int c);//默认向标准输出流写入一个字符;
//成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)
FILE *fp;
int ch;
if((fp = fopen("text.txt","w")) == NULL){
perror("fopen");
return -1;
}
for(ch = 'a';ch <='z';ch++){
fputc(ch,fp);//把ch中的字符写入到fp指定的流中
}
//通过命令行参数传递源文件和目标文件名
FILE *fps, *fpd;
int ch;
if (argc < 3)//判断命令行参数个数
{
printf("Usage : %S \n" ,argv[0]);//告诉用户正确格式
return -1;
}
if ((fps = fopen(argv[1],"r")) == NULL)//打开源文件并判断是否有效
{
perror("fopen src file");
return -1;
}
if ((fpd = fopen(argv[2],"w")) == NULL)//打开目标文件并判断是否有效
{
perror("fopen dst file");
return -1;
}
while((ch = fgetc(fps)) != EOF))//从源文件读取内容并写入目标文件
{
fputc(ch, fpd);
}
fclose(fps);
fclose(fpd);
fgets()/fputs() 一次读/写一行
//只能读写文本文件
#include
char *gets(char *s);//不建议使用,未指定缓冲区大小,会使文件缓冲区溢出;
char *fgets(char *s, int size, FILE *stream);
//从流指针中读取一行字符串,保存至s所指向的缓冲区中(size指定缓冲区的大小)
//成功时返回缓冲区首地址,到文件末尾或出错时返回NULL;
//遇到换行符或者size-1个有效字符时返回,都会在后面追加'\0'表示字符串结束;
#define N 6
char buf[N];//定义字符数组保存读到的字符串
fgets(buf,N,stdin);//stdin从标准输入读取
printf("%s",buf);//abcd换行 --- abcd'\n''\0'//abcdef --- abcde'\0'
//下次再调用fgets,缓冲区剩余内容会重新读到buf
#include
int puts(const char *s);//把存放在s缓存区的字符串写入到标准输出流
int fputs(const char *s,FILE *stream);//向指定的流中写入缓冲区s中的字符串
//成功时返回字符串长度,出错时返回EOF
//puts将缓冲区s中的字符串输出到stdout,并追加'\n'
注意
仅仅是将缓冲区中的字符串写入流,无论缓冲区中有无换行符都无影响;
FILE *fp;
char buf[]="hello world";
if((fp = fopen(argv[1],"a")) == NULL){//将字符串一追加的形式写入命令行参数
perror("fopen");
return -1;
}
fputs(buf,fp);
#define N 64
FILE *fp;
int count = 0;
char buf[N]={0};
if(argc != 2){
printf("%s 文本文件名\n",argv[0]);
return -1;
}
if((fp = fopen(argv[1],"r")) == NULL){
perror("fopen");
return -1;
}
while((fgets(buf,N,fp))!=NULL){//一直读取到文件末尾返回NULL
if(buf[strlen(buf)-1] == '\n')
{
count++;
}
}
printf("%d\n",count);
换行符也算一个字符,并且字符串以’\0’结束,所以倒数第二位是’\n’,详见按行读取5.2.1
fread()/fwrite() 每次读/写若干对象,而每个对象具有相同的长度
//指定对象可以指定字符对象,整型对象,结构体对象等,以这个对象为基本单位去读/写流
//文本文件和二进制文件都可以
#include
size_t fread(void *ptr, size_t size,size_t n,FILE *fp);
//缓冲区首地址//指定读取的每个对象所占的字节//指定从流中读取多少个对象//指定从哪个流中读取数据
size_t fwrite(const void *ptr, size_t size,size_t n,FILE *fp);
//将存放在缓冲区ptr中的n个size大小的对象写入fp流指针指定的对象中
//成功时返回实际读写的对象个数,出错时返回EOF(-1);读到文档末尾返回0
既可以读写文本文件,也可以读写数据文件
int s[10];
if(fread(s,sizeof(int), 10, fp)< 0){
perror("fread");
return -1;
}
//将结构体写入流
struct student{
int no;
char name[8];
float score;
}s[]={{1,"zhang",97},{2,"wang",95}};
fwrite(s, sizeof(struct student), 2, fp);
//通过命令行参数传递源文件和目标文件名
#define N 64
FILE *fps, *fpd;
char buf[N];
int n;
if (argc < 3)//判断命令行参数个数
{
printf("Usage : %S \n" ,argv[0]);//告诉用户正确格式
return -1;
}
if ((fps = fopen(argv[1],"r")) == NULL)//打开源文件并判断是否有效
{
perror("fopen src file");
return -1;
}
if ((fpd = fopen(argv[2],"w")) == NULL)//打开目标文件并判断是否有效
{
perror("fopen dst file");
return -1;
}
while((n = fread(buf,1,N,fps)) > 0)//通过fread返回值判断是否读到文件末尾
{
fwrite(buf, 1, n, fpd);//将缓冲区中保存的数据写入fpd
}
fclose(fps);
fclose(fpd);
1.当流的缓冲区满或者有换行符时,系统会自动刷新流的缓冲区,将内容写入实际的文件中。
对于全缓冲,当缓冲区满时才会自动刷新;行缓冲满足一个条件即可自动刷新。
2.当流关闭的时候,如果流中存有数据,系统会将内容写入实际的文件并刷新流。
3.通过fflush函数刷新流
#include
int fflush(FILE *fp);
//成功时返回0;出错时返回EOF(-1);
//使用此函数时,会强制将流中缓冲区的内容写入实际的文件中;
//Linux下只能刷新输出缓冲区。输入缓冲区的内容只能通过fgetc或者其他函数读走
#include
int main()
{
FILE *fp;
if((fp = fopen("text.txt","w")) == NULL)
{
perror("fopen");
return -1;
}
fputc('a',fp);
fflush(fp);//如果不刷新缓冲区,由于12行程序会一直运行,全缓冲不会刷新,'a'就会一直在缓冲区中,而不会存入文件中;
while(1);
return 0;
}
当打开一个流的时候,其内部有一个当前读写位置;读写位置会自动后移;
#include
long ftell(FILE *stream);
//返回值为长整型,返回指定流的当前读写位置;流打开时其读写位置为0
long fseek(FILE *stream,long offset, int whence);
//用来设定一个流的当前读写位置
//(流指针,偏移量(可正可负),基准点) ---whence参数SEEK_SET/SEEK_CUR/SEEK_END
//正数表示基准点之后,负数表示基准点之前;
//不允许偏移到初始位置以前;允许超出当前文件的长度;
void rewind(FILE *stream);
//直接将流定位到起始位置
成功时返回流的读写位置,出错时返回EOF;
FILE *fp = fopen("text.txt","r+");
fseek(fp,0,SEEK_END);
fputc('t',fp);
FILE *fp;
if((fp = fopen("text.txt","r+")) == NULL){
perror("fopen");
return -1;
}
fseek(fp,0,SEEK_END);//将文件读写位置定位到文件末尾
printf("length is %d\n",ftell(fp));//获取当前读写位置的值
#include
int ferror(FILE *stream);
//判断流是否出错:出错时返回1;没有出错时返回0,表示逻辑假。
int feof(FILE *stream);
//测试流的读写位置是否已经到了末尾
//已经到末尾返回1,表示逻辑真;否则返回0。
#include
int fprintf(FILE *stream, const char *fmt, ...);
//流指针设置为stdout此函数同printf
//将格式化后的字符串输出到指定的流中
参数:(流指针, 格式符...)
int sprintf(char *s,const char *fmt, ...);
//以指定的格式把一个字符串输出到缓冲区中
参数:(缓冲区首地址, 格式符...)//一般用字符数组存放字符串
int year, month, data;
FILE *fp;
char buf[64];
year = 2014; month = 10; date = 26;
fp = fopen("text.txt", "a+");
fprintf(fp,"%d-%d-%d\n", year, month, date);
sprintf(buf,"%d-%d-%d\n", year, month, date);
time()用来获取系统时间(秒数)
//1970年到现在的秒数
localtime()将系统时间转换成本地时间
FILE *fp;
int line = 0;
char buf[64];
time_t t;
struct tm *tp;
if((fp = fopen("text.txt", "a+")) == NULL)
{
perror("fopen");
return -1;
}
while(fgets(buf, 64, fp) != NULL)
{
if(buf[strlen(buf)-1] == '\n') line++;
}
while(1)
{
time(&t);
tp = localtime(&t);
fprintf(fp, "%02d, %d-%02d-%02d %02d:%02d:%02d\n", ++line, tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
fflush(fp);//默认打开的普通文件是全缓冲,用强制刷新确保字符串立刻写到文件中
sleep(1);
}
遵循POSIX规范,也是一组函数,无缓冲,每次读写都会执行系统调用。
通过文件描述符fd表示一个打开的文件,可以访问各种类型的文件。
Linux下,标准IO基于文件IO实现。
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符;
文件描述符由系统从0开始分配,依次递增;
每个程序中打开的文件所对应的文件描述符都是独立的,互不影响。
#include
int open(const char *path, int oflag, ...);
//打开已经存在的文件时使用两个参数
//创建文件时,第三个参数指定新文件的访问权限
参数:
要打开的文件的路径
指定打开方式
返回值:
成功时返回文件描述符;出错时返回EOF
设备文件只能通过open打开;需要通过特定的函数来创建。
#include
int open(const char *pathname, int flags, mode_t mode);
pathname:被打开的文件名(可包括路径名)
mode:三位8进制数表示 CREAT时必须加权限
O_RDONLY:只读方式打开文件;
O_WRONLY:只写方式打开;
O_RDWR:以读写的方式打开;
//以上三种读写方式互斥
O_CREAT:如果文件不存在,就创建新的文件;
//创建文件时mode表示新建文件的权限
O_EXCL:测试用CREAT创建文件时文件是否存在;
//如果文件存在,则可以返回错误信息
O_TRUNC:如果文件已经存在,打开文件时删除原有数据;
O_APPEND:以添加的方式打开文件,写的内容会追加到文件末尾;
只写方式打开文件1.txt,不存在创建,存在清空;
int fd;
if((fd = open("1.txt", O_WRONLY|O_CREAT|O_TRUNC,0666)) < 0)
{
perror("open");
return -1;
}
读写方式打开1.txt,不存在创建,存在报错
int fd;
if((fd = open("1.txt", O_RDWR|O_CREAT|O_EXCL,0666)) < 0)
{
if(errno == EEXIST)//因为文件存在报错
{
perror("exit error");
}
else
{
perror("other open");
}
}
#include
int close(int fd);
参数:要关闭文件的文件描述符
返回值:成功时:0 出错时:-1
//程序结束时自动关闭打开的所有文件
//文件关闭后,文件描述符不再代表文件
#include
ssize_t read(int fd, void *buf, size_t count);
参数:
指定读取的文件描述符
缓冲区首地址//count一般指定为缓冲区大小
指定读取的字节数//不能超过缓冲区大小
返回值:
成功时返回实际读取的字节数;出错时返回-1
读到文件末尾时返回0
从指定文件中读取内容并统计大小
int fd, n, count = 0;
char buf[64];
if(argc < 2)
{
printf("Usage: %s \n" ,argv[0]);
return -1;
}
if((fd = open(argv[1], O_RDONLY)) < 0)
{
perror("open");
return -1;
}
while((n = read(fd, buf, 64)) > 0)
{
count += n;
}
printf("文件大小为:%dByte\n",count);
#include
ssize_t write(int fd, void *buf,size_t count);
参数:
指定写入的文件描述符
发送缓冲区首地址//count一般指定为缓冲区大小
指定读取的字节数//不能超过缓冲区大小
返回值:
成功时返回实际写入的字节数;出错时返回-1
将键盘输入的内容写入文件,直到输入quit
int fd;
char buf[20];
if((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC,0666)) < 0)
{
perror("open");
return -1;
}
while(fgets(buf, 20, stdin) != NULL)
{
if(strcmp(buf, "quit\n") == 0)//fgets会读取换行符
break;
write(fd, buf, strlen(buf));//buf中字符串长度不确定,所以用strlen
}
#include
off_t lseek(int fd, off_t offset, int whence);
参数:
文件描述符
偏移量//同fseek
基准点//同fseek
返回值:
成功返回当前文件的读写位置(基准点+偏移量);出错时返回-1
利用文件IO实现文件的复制 — 文件名通过命令行参数确定
#include
#include
#include
//定义缓冲区
#define N 64
int main()
{
int fds, fdt, n;//定义两个文件描述符,n每次读取的字节数
char buf[N];
//检查命令行参数
if(argc < 3)
{
printf("Usage: %s \n" ,argv[0]);
return -1;
}
//只读方式打开源文件
if((fds = open(argv[1], O_RDONLY)) == -1)
{//往标准错误流中打印错误信息
fprintf(stderr, "open %s : %s\n", argv[2], strerror(errno));
return -1;
}
//只写方式打开目标文件
if((fds = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1)
{//往标准错误流中打印错误信息
fprintf(stderr, "open %s : %s\n", argv[2], strerror(errno));
return -1;
}
//将从源文件中读取到的n个数据存入buf
while((n = read(fds, buf, N)) > 0)
{
write(fdt, buf, n);//把缓冲区的内容写入目标文件,读多少写多少,用n不用N
}
close(fds);
close(fdt);
return 0;
}
打开一个目录文件
#include
DIR *opendir(const char *name);
//DIR是一个结构体类型,用来描述一个打开的目录文件
参数:打开目录文件的路径
返回值:成功返回目录流指针;出错时返回NULL;
读取目录流中的内容
#include
struct dirent *readdir(DIR *dirp);
结构体中包含成员char d_name[256] 存放目录项中文件的名称
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
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]; /* Null-terminated filename */
};
参数:opendir打开的目录流指针
返回值:一个结构体指针(用来描述目录流中的目录项) //需要用循环来读取直到结束
出错或到末尾时返回NULL;
关闭目录文件
#include
int closedir(DIR *dirp);
参数:打开的目录流指针
返回值:成功时返回0;失败时返回-1
打印指定目录下的所有文件名称
DIR *dirp;
struct dirent *dp;
if(argc < 2){
printf("Usage: %s \n" ,argv[0]);return -1;
}
if((dirp = opendir(argv[1])) == NULL){
perror("opendir");return -1;
}
while((dp = readdir(dirp)) != NULL){
printf("%s\n", dp->d_name);
}
closedir(dirp);
#include
int chmod(const char *path, mode_t mode);
//通过路径指定要修改的文件
int fchmod(int fd, mode_t mode);
//通过文件按描述符指定要修改的文件---所以要先用open打开文件
返回值:
成功时返回0;出错时返回-1;
//超级用户和文件所有者能修改文件权限
示例:
chmod("text.txt", 0666);
#include
int stat(const char *pathname, struct stat *statbuf);
//符号链接文件指向的文件的文件属性
int lstat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
//符号文件本身的文件属性
参数:
路径
结构体指针(指向的地址保存获取的文件属性)
int fstat(int fd, struct stat *statbuf);
//通过文件描述符获取文件属性
参数:
文件描述符
结构体指针(指向的地址保存获取的文件属性)
返回值:
成功时返回0;出错时返回-1;
struct stat是存放文件属性的结构体类型
//常用文件属性如下;完整结构体可以man 2 stat查看
struct stat {
mode_t st_mode;//文件的类型和访问权限
uid_t st_uid;//文件所有者ID
uid_t st_gid//用户组ID
off_t st_size;//文件大小
time_t st_mtime;//文件最后修改时间
}
st_mode的文件类型获取—通过系统提供的宏来判断
S_IFREG(st_mode); //普通文件
S_IFDIR(st_mode); //目录文件
...
st_mode的文件访问权限获取—通过系统提供的宏来判断
USR代表用户;GRP代表同组用户;OTH代表其他用户;
R、W、X分别代表可读、可写、可执行;1表示有相应权限,0表示无权限;
获取并显示文件属性;$ ./a.out text.c ;-rw-r–r-- 317 2014-11-08 text.c
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
//创建结构体保存文件属性
struct stat buf;
int n;
struct tm *tp;//将time获得的秒利用localtime转换为本地时间
//判断终端是否输入参数
if(argc < 2)
{
printf("Usage : %s \n" , argv[0]);
return -1;
}
//获取指定文件的属性并保存到buf中
if(lstat(argv[1], &buf) < 0)
{
perror("lstat");
return -1;
}
//判断当前文件类型
switch(buf.st_mode & S_IFMT)
{
case S_IFREG:
printf("-");//判断是否为普通文件
case S_IFDIR:
printf("d");//判断是否位目录文件
//可以加case继续判断文件类型
}
//判断文件权限(从第8位开始判断每一位是1是0)
for(n = 8; n>=0; n--)
{
if(buf.st_mode & (1<<n))//&(1左移n位,从第n位开始,依次检测是否为1)
//如果第n位为1,&完以后结果为真,执行下列程序
{
//876.543.210 第8.5.2位表示读权限位,共同点是%3==2;同理可case1;case0;
case 2:
printf("r");
break;
case 1:
printf("w");
break;
case 0:
printf("x");
break;
}
else
{
printf("-");
}
}
//打印文件大小-无符号的长整型
printf(" %lu",buf.st_size);
tp = localtime(&buf.st_mtime);
printf(" %d-%02d-%02d", tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday);
printf(" %s\n", argv[1]);
return 0;
}
一个二进制文件,包含的代码可以被程序调用。
根据库的不同功能可以分为标准c库、数学库、线程库等。
库有源码,可下载源码后编译,也可以直接安装二进制包。
库的目录/lib或/user/lib
库是事先编译好的,可以复用的代码。
在OS上运行的程序基本上都要使用库,可以提高开发效率。
Windows和Linux下库文件你的格式不兼容。
Linux下包含静态库和动态库
编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已经包含静态库中的部分代码,运行时不再需要静态库;
程序运行时无需加载库,运行速度更快;
占用更多磁盘和内存空间;
静态库升级后,程序需要重新编译链接;
确定库中函数的功能、接口
hello.c文件
#include
void hello(void){
printf("hello word\n");
return;
}
$gcc -c hello.c -Wall//先把 .c 生成对应的 .o
$ar crs libhello.a hello.o//ar crs 库文件名称 库中包含的目标文件(可以多个)
//库文件命名规则lib+name+.a name为库名 已经为2进制形式
查看库中包含的函数名称
$nm libhello.a
hello.o:
0000000 T hello
U puts
text.c文件
#include
void hello(void);//声明函数原型
int main(){
hello();//调用库函数
return 0;
编译text.c并链接静态库libhello.a
$ gcc -o text text.c -L. -lhello
//L.添加库的搜索路径,'.'代表当前路径(hello这个库放在当前路径下,并没有放到系统默认的lib路径中,系统编译时只会去默认路径搜索)
//l 指定要链接的库名,***而不是库文件名*** 不加-l 编译器默认链接c库
//-o 指定程序名称
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码;
程序不包含库中代码,尺寸小;
多个程序可共享一个库;
程序运行时需要加载库;
库升级方便,无需重新编译程序;
确定库中函数的功能、接口
hello.c bye.c文件
#include
void hello(void){
printf("hello word\n");
return;
}
#include
void hello(void){
printf("Bye\n");
return;
}
$gcc -c -fPIC hello.c bye.c -Wall//先把 .c 生成对应的 .o
//-fPIC 生成位置无关代码---生成的.o文件可以在任意位置执行
$gcc -share -o libcommon.so.1 hello.o bye.o//gcc -shar -o 共享库文件名 库中包含的目标文件列表(可以多个)
//lib+name+.so.X 数字X表示版本号 name为库名 已经为2进制形式
$ln -s libcommon.so.1 libcommon.so//ln -s 目标文件 创建的符号链接文件名称
//符号链接文件命名规则lib+name+.so 符号链接指向->实际的库文件
text.c文件
#include
#include "common.h"
int main(){
hello();
bye();//调用库函数
return 0;
}
common.h文件
void hello(void);
void bye(void);
编译text.c并链接静态库libcommon.so
$ gcc -o text text.c -L. -lcommon
//L.添加库的搜索路径,'.'代表当前路径(hello这个库放在当前路径下,并没有放到系统默认的lib路径中,系统编译时只会去默认路径搜索)
//l 指定要链接的库名,***而不是库文件名*** 不加-l 编译器默认链接c库
先去链接动态库,再去链接静态库,如果都没有则报错;
-static //直接连接静态库
出错:共享库需要加载,系统没有在/lib或/user/lib中找到共享库,共享库在当前路径;
修改当前share的环境变量;//可以添加库的指定搜索路径
$export LD_LIBRARY_path=$LD_LIBRARY_path:.
//$引用原来的值:追加的新的路径 .代表当前路径
缺陷:只对当前的share有效;
修改系统的配置文件,把路径加到系统默认搜索路径;
*.conf文件存放库名称和库的路径信息
// 添加/etc/ld.so.conf.d/*.conf文件,执行ldconfig刷新
$sudo vi /etc/ld.so.conf.d/my.conf
//在打开的文件中输入以下绝对路径并保存退出
/home/linux/io/share //绝对路径
$sudo ldconfig