文件I/O(标准,文件、静态库和动态库)

一、标准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区别是一个是宏一个是函数

注意事项:

  1. 函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
  2. stdin 也是FILE *的指针,是系统定义好的,指向的是标准输入(键盘输入)
  3. 打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
  4. 调用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)
注意事项:

  1. 返回和输入参数都是int类型
  2. 遇到这种错误: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’

注意事项:

  1. gets函数已经被淘汰,因为会导致缓冲区溢出
  2. 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. 静态库

特点:
编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已包含代码,运行时不再需要静态库
程序运行时无需加载库,运行速度更快
占用更多磁盘和内存空间
静态库升级后,程序需要重新编译链接

创建静态库步骤:

  1. 编写库文件代码,编译为.o 目标文件。
确定库中函数的功能、接口 

编写库源码hello.c

     #include  <stdio.h>
     void  hello(void) {
        printf(“hello  world\n”);
        return;
     }

编译生成目标文件
     $ gcc  -c  hello.c  -Wall
  1. 创建静态库 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函数在编译的源码内没有找到实现
解决:实现代码或者找到对应函数的库并且链接它。

  1. 链接静态库:

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. 动态库

特点:
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
程序不包含库中代码,尺寸小
多个程序可共享同一个库
程序运行时需要加载库
库升级方便,无需重新编译程序
使用更加广泛

动态库的生成步骤:

  1. 生成位置无关代码的目标文件

确定库中函数的功能、接口

编写库源码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

  1. 生成动态库
    $ gcc -shared -o libcommon.so hello.o bye.o

  2. 编译可执行文件(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库以后的结果将会找不到该文件链接的库文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值