C语言项目(一)——基于Linux系统下的带有GUI界面的即时通信软件

一.项目设计知识点

1.Makefile的作用

    makefile的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

    通过对文件的更新时间进行比较,如果有文件被修改,那么其保存在计算机中的打开时间就发生变化,Makefile会根据

 时间的变化来更新那个文件,极大地节省了编译时间,提高效率。

    makefile文件保存了编译器和连接器的参数选项,还表述了所有源文件之间的关系(源代码文件需要的特定的包含文件,可执行文件要求包含的目标文件模块及库等). 创建程序(make程序) 首先读取makefile文件,然后再激活编译器,汇编器,资源编译器和连接器以便产生最后的输出,最后输出并生成的通常是可执行文件.创建程序利用内置的推理规则来激活编译器,以便通过对特定CPP文件的编译来产生特定的OBJ文件. 

 拓展:

1、Makefile里有什么?

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根 据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲 述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始

2.数据库与文件进行数据存储有哪些区别?

其区别在于:

(1)文件系统用文件将数据长期保存在外存上,数据库系统用数据库统一存储数据。

(2)文件系统中的程序和数据有一定的联系,数据库系统中的程序和数据分离。

(3)文件系统用操作系统中的存取方法对数据进行管理,数据库系统用DBMS统一管理和控制数据。

(4)文件系统实现以文件为单位的数据共享,数据库系统实现以记录和字段为单位的数据共享。

 其联系在于:

(1)均为数据组织的管理技术。

(2)均由数据管理软件管理数据,程序与数据之间用存取方法进行转换。

(3)数据库系统是在文件系统的基础上发展而来的。

3.系统调用与库函数的区别?

一. 概念

系统调用(英语:system call),指运行在用户空间应用程序操作系统内核请求某些服务的调用过程。 系统调用提供了用户程序与操作系统之间的接口。一般来说,系统调用都在内核态执行。由于系统调用不考虑平台差异性,由内核直接提供,因而移植性较差(几乎无移植性)。

库函数(library function),是由用户或组织自己开发的,具有一定功能的函数集合,一般具有较好平台移植性,通过库文件(静态库或动态库)向程序员提供功能性调用。程序员无需关心平台差异,由库来屏蔽平台差异性。

二,区别

调用※函数库调用 VS 系统

函数库调用

系统调用

平台移植性好

依赖于内核,不保证移植性

调用函数库中的一段程序(或函数)

调用系统内核的服务

一个普通功能函数的调用

是操作系统的一个入口点

在用户空间执行

在内核空间执行

它的运行时间属于“用户时间”

它的运行时间属于“系统”时间

属于过程调用,调用开销较小

在用户空间和内核上下文环境间切换,开销较大

库函数数量较多

UNIX中大约有90个系统调用,较少

典型的C函数库调用:printf scanf malloc 

典型的系统调用:fork open write

三. 联系

    一般而言,跟内核功能与操作系统特性紧密相关的服务,由系统调用提供;

具有共通特性的功能一般需要较好的平台移植性,故而由库函数提供。

    库函数与系统调用在功能上相互补充,如进程间通信资源的管理,进程控制等功能与平台特性和内核息息相关,必须由系统调用来实现。

文件 I/O操作等各平台都具有的共通功能一般采用库函数,也便于跨平台移植。

某些情况下,库函数与系统调用也有交集,

如 库函数中的I/O操作的内部实现依然需要调用系统的I/O方能实现。

4.为什么选择系统调用?什么时候使用系统调用操作文件?

相比较标准库函数(fopen、fread、fwrite…)的方式,系统调用的方式只能在类Unix系统下使用,而且,使用系统调用是不带缓冲机制的。

一、缓冲机制:

①.完全缓冲:只有数据超过缓冲区大小时才进行真正的读写操作,比如fread,fwrite函数就是完全缓冲。正常情况下指定读多少字节就读多少字节;

②.行缓冲:当数据为换行符的时候进行一次真正的读写操作,比如标准IO就是行缓冲,一般涉及到终端就是行缓冲的形式;

③.无缓冲:即不对字符进行缓存操作,标准错误输出和系统调用的读写函数就是无缓冲的。

从描述可以看出来,无缓冲一般是比较紧急的,但是因为IO速度比CPU实际运行速度慢,效率会比较低。完全缓冲读满后一次性操作,效率就高了, 但是在缓冲区还有数据分时候程序直接停止就可能导致缓冲区内的数据丢失。

二、文件操作

系统调用是对文件描述符(int类型)进行操作的(相对应的是标准库函数是对文件指针(FILE*)进行操作的)

1.打开文件:

当程序运行时,会有三个文件是默认打开的,分别是:标准输入(0)、标准输出(1)、标准错误输出(2)。

Linux下一个进程默认最多可打开1024个文件,实际上由于Linux把其他设备也当成文件,比如管道、套接字的描述符也会占用文件描述符。不用的文件就要及时关掉。

open函数:int open(const char* pathname, int flags);

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

其中path:文件的路径以及文件名

flags取值:O_RDONLY(只读)或O_WRONLY(只写)或O_RDWR(可读可写) 也可以对这几个值进行位或表示增加这几个选项

O_CREAT(文件不存在则创建)O_EXCL(文件存在则出错)O_TRUNC(文件存在则清空该文件)O_APPEND(写数据追加到文件尾)O_NONBLOCK(非阻塞)

mode创建文件时选择文件权限,和chmod的选项一样。

函数返回值为文件描述符。

2.创建文件:

int  creat(const char * pathname, mode_t mode);

3.关闭文件:

int close(int fd);

4.读取文件:

ssize_t read(int fd, void* buf, size_t count);

fd:文件描述符,buf缓冲区指针,count读取的字节数;返回值为读取到的字节数

5.写入文件:

ssize_t write(itn fd,void* buf, size_t count);

参数设置与返回值和读取函数类似

6.文件定位:

off_t lseek(int fd, off_t offset, int whence);

fd:文件描述符,offset:偏移的字节数,可为负,whence:SEEK_SET(文件开头)SEEK_CUR(当前位置)SEEK_END(文件结尾)

函数功能:将当前位置从whence移动offset个字节,返回值是新的文件偏移量

可以移到文件结尾后面去,然后在再写入数据,就会在原文件结尾处和写入数据处形成一个数据为0的“空洞”

也可以将位置移到文件尾,根据返回值得到该文件的大小。

7.复制文件描述符:

①:进程中有一个进程文件表通过记录文件描述符记录当前进程打开了哪些文件;文件描述符在内核中对应一个内核文件表,记录了当前位置等信息;内核文件表指向i结构表,i结构表记录了文件的长度、所有者、在硬盘的位置等信息。

②:同一个进程中不同的文件描述符指向不同的内核文件表;

不同进程中如两个文件描述符指向同一个文件则它们各自指向了自己的内核文件表,内核文件表指向了同一个i结构表;

复制文件描述符可以让两个文件描述符指向同一个内核文件;

函数:int dup(int oldfd); 传入旧文件描述符,返回新文件描述符

   int dup2(int oldfd, int newfd); 传入旧文件描述符和指定的新文件描述符

由以上图片可以知道,复制后的文件描述符和旧文件描述符实际上指的是同一个内核文件表,就是说当文件变化时两个文件描述符指向的内核都即时的发生了变化,即他们共享了文件位置等信息。

三、目录操作:

1.删除文件:

int unlink(const char *pathname); 删除pathname

目录下的文件其实就是该目录下有指针指向了文件在磁盘中的位置,删除文件就是讲指向该文件的链表结点删除。

2.读取目录:

①.打开目录:DIR* opendir(const char* name);

返回值是一个指向dir结构体的指针,用于读取目录数据。失败返回NULL

②.读取目录下的文件:struct dirent* readdir(DIR* dirp)

返回一个目录项的信息,包括文件的inode节点号和文件名,目录指针指向下一个目录项;

③:关闭目录:int close(DIR* dirp);

四、如何获取当前文件的文件名、行号等信息。(非系统调用)

编译器有几个内置的宏可以比较方便的得到当前的文件信息;

比较重要的就是文件名:__FILE__;

   行号:__LINE__;

    

猜你喜欢

转载自blog.csdn.net/zhanganliu/article/details/81202833