C语言学习——8

一、多文件编程

.c:源文件 —— 写C语言源代码的文件

.h:头文件 —— 定义或者包含一些库和变量、函数声明等内容的文件

多文件编程的时候所有的C文件中只有一个主函数

头文件格式:

以下代码用于——防止头文件重复展开或者重复包含

#ifndef	__cal_H__
#define __cal_H__

//需要使用到的头文件、函数声明、构造类型定义等

#endif

练习(多文件编程)

将昨日作业1写成单独的源文件和头文件然后与主函数文件一起编译生成一个可执行文件并执行

1.先创建3个文件 Func.c Func.h mainCode.c

alt

2.对文件做相应的修改

Func.h 代码
/*===============================================
*   文件名称:Func.h
*   创 建 者:     
*   创建日期:2025年07月22日
*   描    述:
================================================*/
#ifndef __FUNC_H__
#define __FUNC_H__

#include <stdio.h>
typedef void FUNC(void * ,int ,int );
typedef void FUNCP(void *);

void sort_int(void *p,int len,int size);

void sort_float(void *p,int len,int size);

void sort_char(void *p,int len,int size);

void print_int(void *p);

void print_float(void *p);

void print_str(void *p);

void show_arr(const void *arr, int len,int size,FUNCP *print_data);

void sort(void * const p,int len,int size,FUNC *sort_data);

#endif

Func.c 代码

注意包含我们自己定义的头文件

/*===============================================
*   文件名称:Func.c
*   创 建 者:     
*   创建日期:2025年07月22日
*   描    述:
================================================*/
#include "Func.h"

//整型排序
void sort_int(void *p,int len,int size)
{
    for(int i = 0 ; i < len - 1 ; i++)
        for(int j = 0 ; j < len - i - 1 ; j++)
            if(*(int *)(p+j*size) > *(int *)(p+j*size+size))
            {
                int temp = *(int *)(p+j*size);
                *(int *)(p+j*size) = *(int *)(p+j*size+size);
                *(int *)(p+j*size+size) = temp;
            }
}
//浮点型排序
void sort_float(void *p,int len,int size)
{
    for(int i = 0 ; i < len - 1 ; i++)
        for(int j = 0 ; j < len - i - 1 ; j++)
            if(*(float *)(p+j*size) > *(float *)(p+j*size+size))
            {
                float temp = *(float *)(p+j*size);
                *(float *)(p+j*size) = *(float *)(p+j*size+size);
                *(float *)(p+j*size+size) = temp;
            }
}
//字符排序
void sort_char(void *p,int len,int size)
{
    for(int i = 0 ; i < len - 1 ; i++)
        for(int j = 0 ; j < len - i - 1 ; j++)
            if(*(char *)(p+j*size) > *(char *)(p+j*size+size))
            {
                char temp = *(char *)(p+j*size);
                *(char *)(p+j*size) = *(char *)(p+j*size+size);
                *(char *)(p+j*size+size) = temp;
            }
}

void print_int(void *p)
{
    printf("%d ",*(int *)p);
}
void print_float(void *p)
{
    printf("%.2f ",*(float *)p);
}
void print_str(void *p)
{
    printf("%c",*(char *)p);
}
void show_arr(const void *arr, int len,int size,FUNCP *print_data)
{
    char *p = (char *)arr;
    for(int i = 0 ; i < len ; i++)
    {
        print_data(p);
        p+=size;
    }
    printf("\n");
}

void sort(void * const p,int len,int size,FUNC *sort_data)
{
    sort_data(p,len,size);
}

mainCode.c 代码

注意包含自己定义的Func.h头文件

/*===============================================
*   文件名称:mainCode.c
*   创 建 者:     
*   创建日期:2025年07月22日
*   描    述:
================================================*/
#include "Func.h"

int main(int argc, char *argv[])
{ 
    int a[8] = {1,2,33,14,55,62,7,28};
    printf("------------int------------\n");
    show_arr(a,8,sizeof(int),print_int);
    sort(a,8,sizeof(int),sort_int);
    show_arr(a,8,sizeof(int),print_int);
    
    float b[8] = {11.2,2.3,31.5,14.5,25.6,6.44,71.22,18.22};
    printf("------------float------------\n");
    show_arr(b,8,sizeof(float),print_float);
    sort(b,8,sizeof(float),sort_float);
    show_arr(b,8,sizeof(float),print_float);
    
    char c[32] = "gfedcba";
    printf("------------string------------\n");
    show_arr(c,7,sizeof(char),print_str);
    sort(c,7,sizeof(char),sort_char);
    show_arr(c,7,sizeof(char),print_str);
    return 0;
    return 0;
}

3.进行编译

gcc Func.c mainCode.c -o main -I ./

alt

4.运行

./main

alt

二、GUN工具

GCC

编译工具:把一个源程序编译成一个可执行的程序

调试工具:能对执行程序进行源码或者汇编级调试

软件工程工具:用于协助多人开发或大型软件项目的管理,如make、 CVS、Subvision

其它工具:用于把多个目标为难链接成可执行文件的链接器,或者用作格式转换的工具

全称:GUN CC,GUN项目中符合ANSI C标准的编译系统

编译:C、C++、Object C、java等多种语言

GCC是可以在多种硬件平台上编译出可执行程序的超级编译器

gcc可支持的后缀名解释

后缀 解释
.c C源程序
.C/.cc/.cxx C++源程序
.m Objective-C源程序
.i 已经经过预处理后的C源程序
.ii 已经经过预处理后的C++源程序
.s/.S 汇编语言的原始程序
.h 预处理头文件
.o 目标文件
.a/.so 编译后的库文件

gcc的基本使用和选项

选项 解释
-c 只编译,不链接为可执行文件,通常用于编译不包含主程序的子程序文件
-o output-filename 确定输出的文件名为output-filename,不能与源文件同名,如果没有默认生成a.out
-g 产生符号调试工具(GDB)所需要的必要的符号资讯,若要对源码进行调试,就必须加上这个选项
-O(大写) 对程序进行优化编译、链接,采用这个选项,整个源代码都会在编译、链接的过程中进行优化处理,
这个样可以使生成的可执行文件的执行效率提高,但是会降低整个编译链接的效率
-O2 -O2 比 -O更好地优化编译、链接,但是整个编译、链接过程会更慢
-I dirname 将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用到的参数
-L dirname 将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是链接过程中使用到的参数

引用头文件

符号 解释
"头文件名" 优先从当前目录寻找,然后再去标准库的目录寻找
<头文件名> 直接从标准库的目录寻找

GCC的四个编译步骤

1.预处理

预处理指令都在这个时候执行,头文件展开、宏定义替换、去注释、条件编译等

gcc -E test.c -o test.i

2.编译

检查语法错误,没有错误则编译生成汇编文件

gcc -S test.i -o test.s

3.汇编

把生成的汇编代码,编译生成机器码

gcc -c test.s -o test.o

4.链接

把所有的.0和相关的库链接生成可执行文件

gcc test.o -o test

三、GDB调试工具

要使用调试功能,需在编译时在gcc test.c -g,加上-g参数

gcc filename -o -g file
gdb file

单步执行

命令 功能
n 不进入函数单步执行
s 会进入函数单步执行

其他执行命令

命令 功能
l 查看文件(查看10行代码)
b 行数 设置断点
info b 查看断点情况
r 运行代码(到断点会停止)
p n 查看变量值
n 或者 s 单步运行
c 恢复程序运行
help 帮助
quit 退出

四、make

​ 工程管理器,顾名思义,是指管理较多的文件

​ make工程管理器也是个自动编译管理器,这里的自动是指的他能够根据“文件时间戳”自动发现更新的文件而减少编译 的工作量,同时通过读入Makefile文件的内容来执行大量的编译工作

​ make将指编译改动的代码问题,不用完全编译

​ Makefile是make读入的唯一配置文件由make工具创建的目标体(target),通常是目标文件或可执行文件

​ 要创建的目标体所以来的文件(dependency _file)

​ 创建每个目标时需要运行的命令(command)

注意

​ 命令前必须是一个"Tab制表符",否则会编译错误

Makefile 格式:

target:dependency_file
<TAB> command

示例

main:Generics.c main.c Generics.h
	gcc -c Generics.c -o Generics.o
	gcc -c main.c -o main.o

1.预定义变量

AR  库文件维护程序的名称,默认值为ar,AS汇编程序的名称,默认值as
CC  C编译器的名称,默认值cc,CPP C预编译的名称,默认值$(cc) -E
CXX C+预编译的名称,默认值为g++
FC	FORTAN编译器的名称,默认值f77
RM  文件删除程序错名称,默认值为rm -f

ARFLAGS 库文件维护程序的选项,无默认值。
ASFLAGS 汇编程序的选项,无默认值。
CFLAGS   C编译器的选项,无默认值。
CPPFLAGS	 C预编译的选项,无默认值。
CXXFLAGS	 C++编译器的选项,无默认值。
FFLAGS	FORTRAN编译器的选项,无默认值。

2.创建变量的目的

用来代替一个文本字符串:
    1.系列文件的名字 
    2. 传递给编译器的参数 
    3. 需要运行的程序 
    4. 需要查找源代码的目录 
    5. 你需要输出信息的目录 
    6. 你想做的其它事情。 

3.变量定义的两种方式

递归展开方式VAR=var
    简单方式 VAR:=var
变量使用$(VAR)
    用”$”则用”$$”来表示
    类似于编程语言中的宏 

4.递归展开方式

递归展开方式VAR=var
例子:
foo = $(bar) 
bar = $(ugh) 
ugh = Huh? 

优点:
   它可以向后引用变量
缺点: 
	不能对该变量进行任何扩展,
例如 
	CFLAGS = $(CFLAGS) -O
 	会造成死循环

简单方式 VAR:=var
	m := mm
	x := $(m)
	y := $(x) bar
	x := later
	echo $(x) $(y) 
用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值进行展开 

这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言用?=定义变量 
	dir := /foo/bar

    FOO ?= bar
含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
   ifeq ($(origin FOO), undefined)
      FOO = bar
   endif

5.为变量添加值

为变量添加值 
你可以通过+=为已定义的变量添加新的值 
    Main=hello.o hello-1.o
    Main+=hello-2.o

自动变量
    $*	  不包含扩展名的目标文件名称
    $+	  所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
    $<	  第一个依赖文件的名称
    $?	  所有时间戳比目标文件晚的的依赖文件,并以空格分开
    $@    目标文件的完整名称
    $^	  所有不重复的目标依赖文件,以空格分开
    $%    如果目标是归档成员,则该变量表示目标的归档成员名称

6.直接运行make

选项

-C dir读入指定目录下的Makefile
-f
file读入当前目录下的file文件作为Makefile
-i忽略所有的命令执行错误
-I dir指定被包含的Makefile所在目录
-n只打印要执行的命令,但不执行这些命令
-p显示make变量数据库和隐含规则
-s在执行命令时不显示命令
-w如果make在执行过程中改变目录,打印当前目录名

alt

练习

将自己的工程进行目录结构的设计,然后使用make完成编译

OBJC = ./lib/Func.o ./lib/mainCode.o
INC = -I ./inc -c
RUN = ./bin/mainCode
main:$(OBJC)
	$(CC) $(OBJC) -o $(RUN)
./lib/Func.o:./src/Func.c ./inc/Func.h
	$(CC) $(INC) $< -o ./lib/Func.o
./lib/mainCode.o:./src/mainCode.c
	$(CC) $(INC) $< -o ./lib/mainCode.o

clean:
	$(RM) ./lib/*.o $(RUN)

alt