二、IO学习总结

IO学习总结

  • 一、概论
    • (一)为什么要学习IO?
    • (二)IO的分类
    • (三)系统调用和缓冲机制
  • 二、标准IO
    • (一)流
    • (二)流的分类
    • (三)缓冲机制
    • (四)操作文件
      • 1、流程
      • 2、按照字符操作
      • 3、按照行操作
      • 4、按照块操作
    • (五)文件流的定位(文件指针的指示位置)
  • 三、特性函数
  • 四、文件IO
    • (一)操作文件IO
      • 1、打开文件
      • 2、关闭文件
      • 3、写文件
      • 4、读文件
      • 5、文件IO中文件指示位置定位
    • (二)空洞文件
    • (三)操作目录
      • 1、打开目录
      • 2、操作目录
      • 3、关闭目录
      • 4、测试文件属性
    • (四)制作静态库和动态库
      • 1、什么是库?
      • 2、库存在的意义?
      • 3、静态库
      • 4、如何创建静态库?
      • 5、动态库
      • 6、如何创建动态库?
      • 7、用gcc创建共享库

一、概论

(一)为什么要学习IO?

  1. 目的:为了将数据存储以及对于文件的操作
  2. 注意:
    文件:普通文件或者硬件设备(Linux下一切皆文件)
  3. Linux下的7种文件属性:
    (1)普通文件 -
    (2)目录文件 d
    (3)符号链接文件(软链接): l
    (4)字符设备文件:c
    (5)块设备文件 :b
    (6)管道文件:p
    (7)套接字文件:s

(二)IO的分类

第一类:标准IO(高级磁盘IO)

  1. ANSI(美国标准协会)联合ISO(国际化标准组织)所形成的的一个C标准(C标准:属于C库,含有一些列输入输出函数),只要操作系统支持C库,就可以使用这一类标准IO提供的函数(移植性比较高)。
  2. 操作的文件一般是普通文件。
  3. 属于高级磁盘IO。存在缓冲区,减少了用户态切换至内核态,最后又返回用户态这样的频繁操作,意味着减少了系统开销。
  4. 通过文件流(FILE *)操作文件(打开文件时,系统会自动将该文件的信息定义结构体类型struct FILE 来进行存储,因此可以通过FILE *文件指针来操作文件)

第二类:文件IO(低级磁盘IO)

  1. POSIX(可移植操作系统接口)推出的对于支持POSIX标准的系统可以操作文件的一系列函数(UNIX系统一般都会支持POSIX的标准)。移植性不高(只能应用于UNIX系统)
  2. 操作的文件可以普通文件或者设备文件(硬件)。
  3. 低级磁盘IO。没有缓冲区,每一次都是系统调用,都会存在用户空间和内核空间的频繁切换工作,好处就是可以直接对于设备进行读写操作。
  4. 通过文件描述符来(非负的数字)操作文件。

(三)系统调用和缓冲机制

二、IO学习总结_第1张图片
注意:
库函数和系统调用函数的关系:大多数库函数都是由系统调用函数封装起来的。

二、标准IO

(一)流

  1. 概念:文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为“流”。
  2. 分析:为啥称结构体指针为流?
    因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据(每一次的放入数据:均是需要通过文件指针来进行读写指向的文件),存在三个特点:
    (1)有源头:APP
    (2)有目的:缓冲区
    (3)持续性:不断放入数据到缓冲区
    一旦具备以上3个特点,就会形成流,所以通过文件指针操作文件可以理解为是通过操作流来操作文件。

(二)流的分类

文件被打开的时候,会默认具备3个类:

  1. stdin(标准输入:键盘) -->0
  2. stdout(标准输出:终端) -->1
  3. stderr(标准出错:会向终端打印,不带缓冲区(意味着每一次出错就会立即刷新缓冲区)) -->2

因此:当用户以后想要操作文件时,就需要先打开文件,此时会拥有一个文件流指针,以后通过该文件指针就可以操作文件。

(三)缓冲机制

  1. 全缓冲
    缓冲区被放满,程序结束,强制刷新:会引起缓冲区的刷新
  2. 行缓冲
    缓冲区被放满,程序结束,强制刷新,遇到换行符:会引起缓冲区的刷新
  3. 不带缓冲
    不存在缓冲区的概念:每一次读写都是直接输出:stderr

(1)缓冲区被放满:

#include 
#include 
int main(int argc, const char *argv[])
{
	while(1)
	{
		printf("hello");
		//延时函数
		sleep(1);
	}
	return 0;
}

此时处于不断向缓冲区放hello,延时时间为1秒钟1次,因此当缓冲区没有放满时,不会刷新缓冲区,也就看不到hello。

(2)程序结束:

#include 
#include 
int main(int argc, const char *argv[])
{
	printf("hello");
	return 0;
}

(3)强制刷新:

#include 
#include 
int main(int argc, const char *argv[])
{
	
	while(1)
	{
		printf("hello");
		sleep(1);
		//强制刷新
		fflush(stdout);
	}
	return 0;
}

一秒钟后再强制刷新输出流

(四)操作文件

1、流程

提供给用户来操作文件的方式有3种:
不管是哪种方式来操作文件,流程为:
(1)打开文件FILE *fopen(const char *path,const char *mode);

  • 参数1:需要打开文件的名字(可以包含路径,指针类型)
  • 参数2:打开文件的方式(指针类型)
  • 返回值:
    打开文件成功之后的文件流指针
    失败为NULL
    二、IO学习总结_第2张图片
    打开文件代码如下:
#include 

int main(int argc, const char *argv[])
{
	//打开一个指定文件
	//char *FileName = "1.c";
	//char *Mode = "r";
	FILE *fr = fopen(argv[1],"r");
	//FILE *fr = fopen("2.c","r");
	//FILE *fr = fopen(FileName, Mode);
	//判断fr的值
	if(NULL == fr)
	{
		perror("fopen error");
		return -1;
	}
	printf("fopen_read ok!\n");
	//关闭文件
	fclose(fr);
	return 0;
}

(2)操作文件:读写文件
3种方法:按照字符操作、按照行操作、按照块操作

(3)关闭文件
头文件:#include
函数原型:int fclose(FILE *stream);
因此:打开文件和关闭文件在标准IO下都是使用同一个接口,只是在操作文件的方式上存在选择.

2、按照字符操作

(1)函数名:fgetc() fputc
(2)头文件:#include
(3)函数原型:

int fgetc(FILE *stream);

  • 功能:从指定的文件流中获取一个字符
  • 参数:指定获取一个字符所处文件的文件流
  • 返回值:
    成功返回获取到的字符值
    读取到文件末尾返回EOF(-1)
    操作中失败返回负数

int fputc(int c, FILE*stream);

  • 功能:向指定的文件流中输出一个字符
  • 参数:
    参数1:需要输出的指定字符(字符被称为单字节的整形)
    参数2:指定输出字符到的文件对应的文件流
  • 返回值:
    成功返回刚写入的字符值
    失败返回EOF(-1)

案例:
实现对于指定一个文件读取,读取方式为按照字符操作,将读取完毕之后的结果显示在终端上。(自己实现命令cat 文件名)
思路:
1、打开需要显示文件内容所在的文件
2、获取一个字符
3、输出一个字符
4、重复2、3直至文件末尾截止
5、关闭文件
代码:

#include 

//功能:实现命令cat的功能
int main(int argc, const char *argv[])
{
	//入参检查
	if(argc < 2)
	{
		printf("传参过少!\n");
		return -1;
	}
	
	//打开需要读取的文件
	FILE * fr = fopen(argv[1], "r");
	if(NULL == fr)
	{
		perror("fopen_read error");
	}
	printf("fopen_read success!\n");

	//借助于循环完成读取并显示
	while(1)
	{
		//读取
		char chr = fgetc(fr);
		//判断--》什么时候结束while循环
		/*if(EOF == chr)
		{
			printf("文件读取完毕!\n");
			break;
		}
		*/
		if(feof(fr))//任何类型的文件都可判断是否到达文件末尾
		{
			//抵达文件末尾
			printf("到头了!\n");
			break;
		}
		
		//显示
		char ret = fputc(chr, stdout);//putchar()
	}

	//关闭文件
	fclose(fr);
	return 0;
}

3、按照行操作

(1)标志:寻找’\n’
(2)函数: fgets fputs
(3)函数原型:char * fgets(char *s, int size, FILE *stream);

  • 参数
    参数1:即将存储内存空间首地址
    参数2:存储内容空间的大小(可以sizeof()测得)
    参数3:指定的文件流
  • 返回值:
    成功返回存储内容空间首地址
    失败返回NULL

(4)思考:fgets什么时候会返回?
情况1:第一次遇到’\n’就会返回
情况2:该行会比较长,需要多次读取才能读完,所以位于读到’\n’之前的多次都是读取到size-1就返回。

(5)案例:
实现wc -l测试文件行数的功能

#include 
#include 

//功能:实现wc -l 命令统计文件行数的功能

int main(int argc, const char *argv[])
{
	if(argc < 2)
	{
		printf("error!\n");
		return -1;
	}
	FILE *fr = fopen(argv[1], "r");	
	if(NULL == fr)
	{
		perror("fopen_read error");
		return -1;
	}
	printf("fopen_read ok!\n");

	int line = 0;
	char Buf[20] = {0};
	while(1)
	{
		if(NULL == fgets(Buf, sizeof(Buf), fr))
		{
			break;
		}
		//判断
		if( '\n' == Buf[strlen(Buf)-1] )
		{
			line++;
		}
	}
	printf("%d %s\n",line, argv[1]);

	fclose(fr);
	return 0;
}

4、按照块操作

(1)函数:fread() fwrite()
(2)函数原型:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  • 参数
    参数1:存储读取一块内容之后的空间地址
    参数2:块的大小—》建议给1
    参数3:块的个数—》建议给sizeof()
    参数4:要读取的文件对应的文件流
  • 返回值:成功代表读取的块的个数,失败返回0

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);

  • 参数
    参数1:需要写入的内容的空间地址
    参数2:块的大小—》建议给1
    参数3:块的个数—》建议给sizeof()
    参数4:要写入的文件对应的文件流
  • 返回值:成功代表写入的块的个数,失败返回0

(3)案例:
保存学生信息:

#include 
#include 

enum VALUE
{
	FOPEN_ERROR = -4,
	MALLOC_ERROR = -3,
	NULL_ERROR,
	ERROR,
	OK,
};

//操作一块空间
typedef struct student
{
	char Name[20];
	int Age;
	float Height;
}Stu;

int SaveStuMessage_Func(Stu *pStu)
{
	if(NULL == pStu)
	{
		return NULL_ERROR;
	}
	//打开文件
	FILE *fw = fopen("Message.txt", "w");
	if(NULL == fw)
	{
		perror("fopen error");
		return FOPEN_ERROR;
	}
	Stu *pStudent = pStu;
	fwrite(pStudent, 1, sizeof(Stu), fw);

	fclose(fw);
	return OK;
}
int main(int argc, const char *argv[])
{
	//自己输入该学生的信息
	printf("请输入该学生的信息:\n");
	Stu *pStu = (Stu *)malloc(sizeof(Stu));
	if(NULL == pStu)
	{
		printf("malloc_error!\n");
		return ERROR;
	}
	scanf("%s%d%f",pStu->Name, &pStu->Age, &pStu->Height);

	printf("保存学生信息之前:姓名:%s\t年龄:%d\t身高:%.1f\n", \
			pStu->Name, pStu->Age, pStu->Height);

	//保存学生信息到文件中
	int ret = SaveStuMessage_Func(pStu);
	if(ret < 0)
	{
		return ERROR;
	}
	else
	{
		printf("保存学生信息完毕!\n");
	}
	return OK;
}

读取学生信息到结构体空间:

#include 
#include 

enum VALUE
{
	FOPEN_ERROR = -4,
	MALLOC_ERROR = -3,
	NULL_ERROR,
	ERROR,
	OK,
};

//操作一块空间
typedef struct student
{
	char Name[20];
	int Age;
	float Height;
}Stu;

int LoadStuMessage_Func(Stu *pStu)
{
	if(NULL == pStu)
	{
		return NULL_ERROR;
	}
	//打开文件
	FILE *fr = fopen("Message.txt", "r");
	if(NULL == fr)
	{
		perror("fopen error");
		return FOPEN_ERROR;
	}
	Stu *pStudent = pStu;
	fread(pStudent, 1, sizeof(Stu), fr);

	fclose(fr);
	return OK;
}
int main(int argc, const char *argv[])
{
	//申请学生的空间
	Stu *pStu = (Stu *)malloc(sizeof(Stu));
	if(NULL == pStu)
	{
		printf("malloc_error!\n");
		return ERROR;
	}
	
	//导入信息到结构体空间
	int ret = LoadStuMessage_Func(pStu);
	if(ret < 0)
	{
		return ERROR;
	}
	else
	{
		printf("导入的学生信息打印为:姓名:%s\t年龄:%d\t身高:%.1f\n", \
				pStu->Name, pStu->Age, pStu->Height);
	}
	return OK;
}

(五)文件流的定位(文件指针的指示位置)

fseek()/ftell()/rewind()函数原型:
(1)头文件:#include
(2)函数原型:

int fseek(FILE * stream,long offset,intwhence);

  • fseek()用户设定stream流的文件位置指示,调用成功返回0,失败返回-1,并设置errno
  • fseek()的whence参数:SEEK SET/SEEK CUR/SEEK END.

long ftell(FILE * stream);

  • ftell()用于取得当前的文件位置,调用成功则为当前文件位置指示,若出错则为-1L

void rewind(FILE * stream);

  • rewind()用于设定流的文件位置指示为文件开始,该函数调用成功无返回值
  • rewind()等价于(void)fseek(streamLSEEK SET)

代码如下:

#include 

int main(int argc, const char *argv[])
{
	//FILE *fd = fopen(argv[1], "w");
	FILE *fd = fopen(argv[1], "a");
	if(NULL == fd)
	{
		return -1;
	}
	long size = ftell(fd);
	printf("目前:文件指示位置的大小为%ld个字节\n",size);
	
	//偏移到文件开头位置
	//fseek(fd, 0, SEEK_SET);
	rewind(fd); //等价于fseek(fd, 0, SEEK_SET)
	size = ftell(fd);
	printf("目前:文件指示位置的大小为%ld个字节\n",size);
	fclose(fd);
	return 0;
}

三、特性函数

  1. perror(“string”);—》可以输出出错的原因
  2. feof(FILE* Stream);
    作用:判断文件是否抵达末尾(不管是文本文件还是二进制文件,都可以判断)
    返回值:
    抵达文件末尾:返回值为非零
    未抵达文件末尾:返回值0

四、文件IO

(一)操作文件IO

open close read write

头文件:
#include
#include
#include

1、打开文件

函数原型:

int open(const char *pathname, int flags);

  • 功能:打开指定的文件
  • 参数:
    参数1:所需打开文件的名字(包含路径)
    参数2:打开文件的方式—》主标志 | 副标志
  • 返回值:
    成功代表一个大于0 的数字(文件描述符)
    失败返回-1(errno ie set…)

int open(const char *pathname, int flags, mode_t mode);

  • 功能:打开指定的文件
  • 参数:
    参数1:所需打开文件的名字(包含路径)
    参数2:打开文件的方式—》主标志 | 副标志
    参数3:当需要O_CREAT时,就要写该参数来为创建的新文件指明权限(八进制表示)
  • 返回值:
    成功代表一个大于0 的数字(文件描述符)
    失败返回-1(errno ie set…)

2、关闭文件

头文件:#include
函数原型:int close(int fd);

  • 功能:关闭一个文件描述符
  • 参数:打开文件成功之后的文件描述符

3、写文件

头文件:#include
函数原型:ssize_t write(int fd, const void *buf, size_t count);

  • 功能:向指定的文件描述符的文件内写入内容
  • 参数:
    参数1:打开文件之后的文件描述符
    参数2:所需写入的内容所在空间首地址
    参数3:需要写入的字节数
  • 返回值:
    成功返回写入的字节数
    返回0代表没有写入任何内容
    返回-1代表出错(errno is set…)

4、读文件

(1)头文件:#include
(2)函数原型:ssize_t read(int fd, void *buf, size_t count);

  • 功能:从指定的文件描述符的文件中读取内容
  • 参数:
    参数1:打开文件之后的文件描述符
    参数2:存储读取到结果的空间首地址
    参数3:可以读取的字节数—》sizeof测得
  • 返回值:
    成功返回读取的字节数
    返回0代表读取完毕
    返回-1代表出错(errno is set…)

(3)案例:对于同一个文件进行读写操作的代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[])
{
	//打开文件
	int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0664);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}
	printf("open ok!\n");

	//读写操作
	//定义存储即将写入文件内容的空间
	char Buf[100] = {0};
	printf("请输入文本:\n");
	fgets(Buf, sizeof(Buf), stdin);

	int wr_count = write(fd, Buf, sizeof(Buf));
	//int wr_count = write(fd, Buf, strlen(Buf));
	if(wr_count < 0)
	{
		perror("write error");
		close(fd);
		return -1;
	}
	else if(0 ==  wr_count)
	{
		printf("未写入内容!\n");
		close(fd);
		return -1;
	}
	else
	{
		printf("写入文件成功,写入的字符个数为:%d\n",wr_count);
	}

	//恢复文件指示位置至文件开头
	lseek(fd, 0, SEEK_SET);

	//读取文件内容
	//定义存储从文件中读取结果的空间
	char Data[100] = {0};
	while(1)
	{
		//清空空间
		memset(Data, '\0', sizeof(Data));
		int rd_count = read(fd, Data, sizeof(Data));
		if(rd_count < 0)
		{
			perror("read error");
			break;
		}
		else if(0 == rd_count)
		{
			printf("读取完毕!\n");
			break;
		}
		else
		{
			printf("读取成功!\n");
			printf("本次读取字符个数为:%d个\t读取结果为:Data=%s\n", \
					rd_count, Data);
		}
	}
	//关闭文件
	close(fd);
	return 0;
}

5、文件IO中文件指示位置定位

调用lseek()函数可以显示的定位一个已打开的文件
(1)头文件:
#include
#include
(2)函数原型:off_t Iseek(int fd, off_t offset, int whence);

  • 参数
    1、 fd:文件描述符
    2、 offset:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移)
    3、 whence(当前位置基点):
    SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小。
    SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
    SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。
  • 返回值
    成功:文件的当前位移
    -1:出错

(二)空洞文件

  1. 空洞文件:在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的,位于文件中但没有写过的字节都被设为0。
  2. 空洞文件有什么用?
    下载的时候如果没有空洞文件,多线程下载时文件就都只能从一个地方写入,这就不是多线程了,如果有了空洞文件,可以从不同的地址写入,就完成了多线程的优势任务。
  3. 如何创建一个空洞文件?
    (1)以只写或者读写的方式打开该文件
    (2)移动文件指示位置(假设下载一个1G的文件,需要移动1G的空间)
    (3)在移动完之后的末尾写一个字节
    (4)关闭文件
    实现方式:标准IO以及文件IO都可以实现空洞文件。
  4. 创建空洞文件的代码如下:
#include 
#include 
#include 
#include 
#include 


int main(int argc, const char *argv[])
{
	//功能:创建一个空洞文件
	//第一步:打开文件
	int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0664);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}
	printf("open ok!\n");
	//第二步:移动文件指示位置至自己指定的偏移量的位置
	lseek(fd, 1024*1024*1024, SEEK_SET);

	//第三步:在文件末尾写入一个字符:'\0'
	write(fd, "\0", 1);

	//第四步:关闭文件
	close(fd);

	return 0;
}

(三)操作目录

操作目录流程流程:
opendir readdir closedir

1、打开目录

opendir
(1)头文件:
#include
#include
(2)函数原型:
DIR *opendir(const char *name);
DIR *:返回值是一个目录流指针,可以作为readdir的参数进行绑定需要读取目录的信息。

2、操作目录

readdir
(1)头文件:
#include
(2)函数原型:
struct dirent *readdir(DIR *dirp);
struct dirent *:返回值代表需要读取的目录的信息

3、关闭目录

closedir
(1)头文件:
#include
#include
(2)函数原型:
int closedir(DIR *dirp);
DIR *:opendir函数的返回值

4、测试文件属性

(1)头文件:
#include
#include
#include
(2)函数原型:
int stat(const char *pathname,struct stat *statbuf);
int fstat(int fd,struct stat *statbuf);
int lstat(const char *pathname,struct stat *statbuf);
(3)穿透函数:当测试的文件是软链接文件时,此时测得属性是属于软链接所指向的源文件的属性。
(4)非穿透函数:当测试的文件是软链接文件时,此时测得属性是软链接文件自身的属性。

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
	//功能:实现打开目录,并读取目录中的文件的filename

	//(1)opendir
	DIR *pDir = opendir(argv[1]);
	if(NULL == pDir)
	{
		perror("opendir error");
		return -1;
	}
	printf("opendir ok!\n");

	//定义struct stat结构体类型的变量
	struct stat MyStat = {0};
	//(2)readdir
	while(1)
	{
		struct dirent *pSD = readdir(pDir);
		if(NULL == pSD)
		{
			break;
		}
		if(*(pSD->d_name) == '.')
		{
			continue;
		}
		//使用pSD该结构体指针来访问结构体中的文件名
		printf("文件名:%s\t",pSD->d_name);
		//定义存储空间用来将文件名和其路径拼接之后的结果
		char NewSpace[1024] = {0};
		
		//拼接方式1
		//strcat(NewSpace, argv[1]);
		strcpy(NewSpace, argv[1]);
		strcat(NewSpace, pSD->d_name);
		//拼接方式2
		//sprintf(NewSpace,"%s%s", argv[1], pSD->d_name);
		
		if(lstat(NewSpace, &MyStat))
		{
			perror("lstat file error");
			break;
		}
		//测试文件对应的大小
		printf("文件大小:%ld\n",MyStat.st_size);

	}
	//关闭目录
	closedir(pDir);
	return 0;
}

(四)制作静态库和动态库

1、什么是库?

(1)本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于windows和linux的本质不同,因此二者库的二进制是不兼容的。
(2)linux下的库:
静态库和共享库(动态库)二者的不同点在于代码被载入的时刻不同。
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。

2、库存在的意义?

(1)库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。
(2)现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
(3)共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

3、静态库

(1)静态库对函数库的链接是放在编译时期 (compiletime) 完成的。

  • 程序在运行时与函数库再无瓜葛。
  • 浪费空间和资源,因为所有相关的对象文件 (obiect file)与牵涉到的函数库 (library) 被链接合成一个可执行文件 (executable file)。

(2)编译时把静态库中的相关代码复制到可执行程序中。

  • 优点:程序运行时,无需加载库,运行速度更快。
  • 缺点:占用更多磁盘和内存空间,静态库升级后,需要重新编译链接。

4、如何创建静态库?

(1)写源代码
eg:只要是一个文件,文件的内容是一个子函数即可 (目的是为了生成库)。
vim file_length.c / vim file_Line.c

(2)生成相应的o文件
gcc -c file_length.c -o file_length.o
gcc -c file_Linec -o file_Line.o

(3)创建成相应的静态库
静态库的命名方式:lib + 库名a
ar rcs libfile.a *,o 注意: libfile.a中的file是库名! !

  • ar–>用来创建静态库
  • r–>在库中插入模块
  • c–>创建一个库,不管库是否存在,都将创建
  • s–>创建目标文件索引,这在创建较大的库时能加快时间

(4)编译时链接静态库
gcc test.c -o test -L. -lfile
注意: -L.中的点代表当前路径(库的位置)

(5)运行
eg:./可执行文件名

5、动态库

(1)动态库把对一些库函数的链接载入推迟到程序运行的时期 (runtime)。

  • 可以实现进程之间的资源共享。
  • 将一些程序升级变得简单。
  • 甚至可以真正做到链接载入完全由程序员在程序代码中控制。

(2)编译时仅记录用到哪个共享库中的哪个符号,不复制共享库中的相关代码

  • 优点:程序不包含库中代码,体积比较小,库升级方便无需重新编译
  • 缺点:在运行需要加载共享库

6、如何创建动态库?

(1)编写源文件vim file_length.c / vim file_Line.c

(2)生成相应的o文件
gec -c -fPIC file _length.c -o file_length.o
gcc -c -fPIC file_Line.c o file_Line.o
注意: -fPIC 要生成与位置无关的代码,可以在任何位置执行

(3)生成动态库(共享库)

  • 动态库的命名方式: lib + 库名so.版本号
    用数字来表示版本号
  • gcc -shared *.o -o libfile.so.1

(4)为共享库文件创建软连接文件

  • 创建链接文件的目的就是为了能够让我们的编译器在编译时找到共享库
  • In -s libfile.so.1 (绝对路径) libfle.so
    软链接文件的取名方式一般就是去控版本号即可

(5)拷贝软链接文件到指定的目录/usr/lib下去
sudo cp libfile.so /usr/lib

(6)编译并链接共享库
gcc main_test.c -o myApp -lfile
此时的动态库的软链接必须存在于/usr/lib 目录下方可成功找到共享库

(7)运行myApp可执行文件即可

7、用gcc创建共享库

  • #gcc -fPIC -Wall -c hello.c
  • #gcc -shared -o libmyhello.so hello.o
  • -fPIC 创建与地址无关的编译程序

你可能感兴趣的:(学习总结,学习,linux,unix)