1、多文件编程步骤
①把所有函数分解在不同的源文件里,主函数要是单独的文件
②为每个源文件编写以.h作为拓展名的头文件,主函数不需要头文件,只要是不分配内存的内容都可以写在头文件里,至少要包含配对源文件所有函数的声明语句
③在所有源文件里使用#include指令包含必要的头文件,配对头文件都是必要的头文件
④为了防止多个函数包含同样的头文件造成重复编译,需要为头文件加上头文件卫士(#ifndef.....#define.....#endif),意思是如果定义了该宏,就不在重复定义,保证了头文件只被编译一次,也就是条件编译
例如:
//此为add.c文件
#include"add.h"
int add(int val,int val1){
return val+val1;
}
//此为add.h文件
#ifndef _ADD_H
#define _ADD_H
#include
int add(int,int);//函数声明
#endif
//此为main.c文件
#include"add.h"
int main(void){
printf("%d\n",add(5,8));
return 0;
}
用图来说明一下,基本通过这几步得到一个可执行文件
gcc的其他选项
-o ,认为给定输出的可执行文件名,如果不给出这个选项,gcc就给出预设的可执行文件a.out。
-g,产生符号调试工具(GNU的gdb),要想对源代码进行调试,我们就必须加入这个选项。
-O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化 处理,这样产生的可执行文件的执行效率可以提高, 但是,编译、连接的速度就相应地 要慢一些。
-O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
-D ,可以在后面定义宏名
3、条件编译
从几组语句中选择一组进行编译
#ifdef.....#else.....#endif //如果定义了宏,则编译ifdef下语句,否则执行else下语句
#ifndef.....#else......#endif //与上述效果相反
#if.........#elif(任意多次)......#else........#endif //可以根据宏的定义从多组语句中选择一组编译,#if //和#elif后写逻辑表达式,可以包括任意逻辑操作符
例如:
#include
int main(void){
#ifdef HP
printf("ok\n");
#else
printf("error\n");
#endif
return 0;
}
//直接运行时结果会打印error
//如果编译时 gcc -D HP去定义这个宏,运行结果会打印ok
//如果把#ifdef换成#ifndef,那么结果会反过来
#include
int main(void){
#if defined(HP)//如果定义了布尔值会为1
printf("1\n");
#elif !defined(WJ)
printf("2\n");
#else
printf("3\n");
#endif
return 0;
}
//定义了哪个宏就会输出哪个
4、Makefile的使用
1.Make是自动编译管理器,这里的“自动”是指它能构根据文件时间戳自动发现更新过的文件而减少
编译的工作量,同时,它通过读入Makefile来执行大量的编译工作
2.make工具的作用
当项目中包含多个c文件,但只对其中1个文件进行了修改,gcc编译会将所有的文件从头编译一 遍,这样效率会非常低,所以通过make工具,只对修改过的文件进行编译,这样大大减少了编 译时间,提高编译效率
3.Makefile是Make读入的唯一配置文件
4.Makefile的编写格式
格式:
目标:依赖
目标实现,需要通过依赖文件实现
例如编写一个基本的Makefile(名称不能变)文件,写出依赖关系,以及如何得到这些目标文件 的命令, .PHONY:clean 声明一个伪命令。若之前的目标中有生成可执行文件名为clean。执行 “make clean”时,make会认为clean目标已实现。不执行clean目标对应的指令,用.PHONY声明 后,make认为clean是一个指令,“make clean”时会执行clean指令对应的命令,这里删除所有的 生成的.o文件和a.out文件。
a.out:add.o main.o
gcc add.o main.o -o add
add.o:add.c
gcc -c add.c -o add.o
main.o:main.c
gcc -c main.c -o main.o
.PHONY:clean
clean:
rm *.o a.out
通过变量来改写Makefile的方法,比传统方法相对来说较为简单,更容易维护
1)预定义变量
CC 默认值为cc,与gcc同
RM 默认值为rm -f
CFLAGS 无默认值,一般为c编译器的选项
OBJS 一般为目标文件xx.
2)自动变量:
$< 第一个依赖文件的名称
$@ 目标文件的完整名称
$^ 所有不重复的目标依赖文件,
以空格分开 % 匹配所有
例如:
我们有如下这些文件需要,stu_sytem.c为主函数,
5、文件操作
标准IO:input/output,针对于文件输入输出。
linux下文件类型:
b(块设备) c(字符设备) d(目录) -(普通文件) l(链接文件) s(套接字) p(管道)
概念:在C库中定义的一组专门用于输入输出的函数
标准IO通过缓冲机制减少系统调用的次数,提高效率
标准IO围绕流进行操作,流用FILE *描述;(FILE 就是一个结构体,用于存放操作文件的相关信息,它在stdio.h文件中定义;vi -t FILE,ctags,ctrl+]:代码追踪,ctrl+t:回退)
标准IO默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准出错)(FILE *stdin)
文件里的内容都是采用二进制的方式来记录,所有的二进制数字都来自于字符,叫做文本文件,其他文件叫做二进制文件,文本文件也可以看成二进制文件。
两种操作文件的方法:①只操作文本文件 ②操作所有文件
基本步骤:
1、打开文件(fopen)
2、操作文件(fgetc、fputc、fread、fwrite)
3、关闭文件(fclose)
FILE*fopen(const char *path.const char*mode)
功能:打开文件
参数:
path:文件路径
mode:打开方式
"r":只看不改,从文件头开始看
"r+":增加了修改文件的功能
"w":只改不看,从文件头开始修改,文件不存在就创建文件,文件存在删除文件内所有内容
"w+":比w多了查看文件的内容功能
"a":只改不看,从原有内容末尾追加新内容
"a+":比a多了查看文件内容的功能
"b":采用二进制操作文件
返回值:
成功:返回一个FILE*类型的文件指针,保存打开文件的入口地址
失败:返回NULL
int*fclose(FILE*stream)
功能:关闭一个文件
参数:
stream:fopen返回的地址
返回值:
成功:返回0
失败:返回EOF,errno(错误码)被设置
int fgetc(FILE * stream)
功能:从文件中读取一个字符
参数:stream:文件流
返回值:成功:读到的字符
失败或读到文件末尾:EOF(-1)
int fputc(int c, FILE * stream)
功能:向文件中写入一个字符
参数:c:要写的字符ASCII,输入字符也会被隐式转换,因此可以直接输入字符
stream:文件流
返回值:成功:写的字符的ASCII
失败:EOF
int feof(FILE * stream);
功能:判断文件有没有到结尾
返回:到达文件末尾,返回非零值
int ferror(FILE * stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值
fread(读操作)、fwrite(写操作)---》二进制方式
都需要四个参数
① 第一个存储区地址
②单个存储区大小
③希望操作的存储区个数
④文件指针
返回值:代表实际操作的存储区个数
以文本方式操作文件
fprintf函数:把数字记录到文本文件里,参数比printf()函数多一个,第一个参数是文件指针
fscanf函数:可以从文本文件中获得数字并记录到存储区,参数比scanf(函数多一个),第一个参数是文件指针
两种方式操作的文件内容不同:
计算机会为每个打开的文件保留一个整数,以记录下次读写的位置,此整数表示文件头到这个位置包含的字节个数叫做文件位置指针
ftell 获得位置指针数值
rewind 将位置指针移到文件开头
fseek 将位置指针移动到任何位置
参数:
stream:文件指针
offset:偏移量
whence:基准
SEEK_SET 把文件头作为基准
SEEK_CUR 把当前位置作为基准
SEEK_END 把文件尾作为基准,尾部应该是实际尾部字符再往后移两位
注:一般操作的是字符串类型
freopen函数,比fopen函数最后多了一个流参数,使用如下