基础实验题目.doc
基础实验题目目录实验一 线性表的基础训练(2次上机)2实验二 栈、队列及其应用(1.52.5次上机)3实验三 二叉树及其应用(2次上机)5实验四 图及其应用(23次上机)7附录 Makefile 、GCC、pkg-config 使用说明81GCC的使用82pkg-config的作用113Makefile的作用11实验一 线性表的基础训练(12次上机)【上机时间】第12次【实验目的】熟悉掌握本门课程所使用的程序设计语言(C语言),体会算法与程序之间的区别:1、 熟悉VC等编程环境,学会单步跟踪、调试自己的程序;2、 了解project的创建、使用以及意义;3、 熟练定义含指向结构体自身的指针域的结构体类型,掌握此类变量、指针变量的初始化、赋值、输入/输出、参数传递;4、 熟练使用C中的动态分配与释放函数(malloc, realloc, free);5、 熟悉带参数的main函数的编写与运行;6、 类C的引用参数在C中的变换处理;7、 利用输入导向,从文件中获取输入数据。初步理解线性表的顺序存储和链式存储特性,掌握在不同存储结构、不同约定下,其基本操作的实现方法与差异。体会以下几点(注意你所做的约定):1、 静态分配的顺序表及增量式分配的顺序表在表示与实现上的差别,各有何特点;2、 有头结点的链表与无头结点链表在操作实现上的区别;3、 头插法与尾插法的操作方法及应用效果对比;4、 插入、删除操作在顺序存储和链式存储上的差别;5、 非循环单链表、循环单链表各适用于解决哪些问题,它们在数据类型定义、操作的定义及实现上各有什么区别?6、 静态链表与动态链表之间的映射与差别(自选)。【实验要求】1、 下载Gzip的相关资源,用VC为Gzip建立project,编译并运行Gzip;给出3种以上的命令行输入,单步跟踪Gzip对命令行参数的处理;学习带参的main的使用与编程。消化理解一些标识符和文件操作。目的:开展程序理解的第一阶段。2、 下载ch2.rar并阅读其中的代码。其中c1.h是第1章预设的一些宏和类型名, c2-1.h是顺序表的类型定义,c2-2.h是链表的类型定义,bo2-1.c是ADT List中基本操作的顺序表实现, bo2-2.c是ADT List中基本操作的链表实现,algo2-1.c是例2-1的顺序表实现,algo2-12.c是例2-1的链表实现,algo2-12a.c是改写algo2-12.c的Union()函数。目的:体会用伪C表示的算法和C程序之间的差异。3、 阅读数据结构题集P79 1.2约瑟夫环,理解约瑟夫环的定义。编写一个程序,该程序根据输入的命令行参数创建一个单循环链表表示的约瑟夫环,然后输出约瑟夫环出列的顺序。命令行格式: 可执行程序名人数n初始的报数上限m密码1 密码n·第1个参数是你所编写的程序的可执行文件名,·第2个参数是指定形成约瑟夫环的人数n·第3个参数是指定初始的报数上限m·后面n个参数是n个人所持有的整数密码。当除可执行程序名外,没有参数时,将继续执行程序并提示用户输入这些参数。基本要求:1)假设命令行参数是齐全的且是正确的,运行所编写的程序能正确地输出结果;2)能将输出结果导到文件中。实验提示:该实验的处理可分以下几个模块:1)命令行参数的处理;2)单循环链表的创建;3)根据m和起始报数人对应在单循环链表中的位置,确定出列人的位置;4)删除出列人对应的结点。选作要求:1)程序有对命令行参数不全或不正确的处理(如提示输入、报错等);2)将约瑟夫环用顺序表实现。4、 撰写实验报告。【检查期限】1、 上机内容检查时间:第2次和第3次上机时,以第3次上机为截止时间;2、 报告上交截止时间:第2次上机后的第一次课的上课前截止。实验二 栈、队列及其应用(1.52.5次上机)【上机时间】第3次,第4次【实验目的】深入理解栈和队列的特性,领会它们各自的应用背景。熟练掌握它们在不同存储结构、不同的约定中,其基本操作的实现方法与差异。体会以下几点(注意你所做的约定):1、 栈:顺序栈(栈空/栈满条件,入栈/出栈)、链栈(栈空条件,入栈/出栈);2、 队列:链队列(队空条件,入队/出队)、顺序队列/循环顺序队列(队空/队满条件,入队/出队);【实验内容】本次实验共五个题目,可任选其中的一题或多题。1. 魔王语言解释具体要求参见数据结构题集P97,实习2.2中的描述。2. 算术表达式求值的演示具体要求参见数据结构题集P99实习2.5中的描述。3. N-皇后问题假设有一N×N的棋盘和N个皇后,请为这N个皇后进行布局使得这N个皇后互不攻击(即任意两个皇后不在同一行、同一列、同一对角线上。要求:1) 输入N,输出N个皇后互不攻击的布局;2) 要求用非递归方法来解决N-皇后问题,即自己设置栈来处理。4. 背包问题假设有一个能装入总体积为T的背包和n件体积分别为w1 , w2 , , wn 的物品,能否从n件物品中挑选若干件恰好装满背包,即使w1 +w2 + + wn=T,要求找出所有满足上述条件的解。例如:当T=10,各件物品的体积1,8,4,3,5,2时,可找到下列4组解:(1,4,3,2) (1,4,5) (8,2) (3,5,2)。提示:可利用回溯法的设计思想来解决背包问题。首先将物品排成一列,然后顺序选取物品装入背包,假设已选取了前i 件物品之后背包还没有装满,则继续选取第i+1件物品,若该件物品“太大”不能装入,则弃之而继续选取下一件,直至背包装满为止。但如果在剩余的物品中找不到合适的物品以填满背包,则说明“刚刚”装入背包的那件物品“不合适”,应将它取出“弃之一边”,继续再从“它之后”的物品中选取,如此重复,直至求得满足条件的解,或者无解。5. MML命令解释说明:MML命令又称人机交互语言,作用就是客户端通过发送有意义的命令字符串来获取服务器的服务。它的格式有很多种,它的格式有很多种,我们要支持下面两种:1) 命令字:参数1=参数1值,参数2=参数2值,.,.,.有一个命令字和很多的参数块,每个参数块中有很多的参数,参数之间用逗号隔开参数值的类型有两种,整型和字符串。2) 命令字:.,.;命令字:.,.;命令字:.,.有很多的命令字,每两个命令字之间用分号隔开,每个命令字可以有很多的参数块,每个参数块的内容同格式1);基本要求:1) 提取所有的参数及其值,其中值为用双/单引号括住的字符串值或者整型值;2) 打印有多少个命令字和参数块个数以及参数的个数;3) 遇到非法输入要报警;附加要求:1) 对非法输入报告其类型,如字符串没有引号等等;2) 限定参数必须为合法的标识符(即字母或下划线开始的、由字母数字下划线组成的字符串),对非法的标识符要报错;补充:如果程序的健壮性很强,即遇到精心设计的测试用例不会死机,可以提示输入错,则加分。【实验要求】1. 要求所编写的程序应:a) 必须带命令行参数;b) 必须通过命令行参数指定输入、输出文件的文件名,练习对文件的操作。2. 撰写实验报告。【检查期限】1 上机内容检查时间:第35次上机时,以第5次上机为截止时间;2 报告上交截止时间:第4次上机后的第一次课的上课前截止。实验三 二叉树及其应用(2次上机)【上机时间】第57次【实验目的】树是一种应用极为广泛的数据结构之一,是本课程的重点。树是一种1:N的非线性结构。本章首先以二叉树这种特殊、简单的树为原型,讨论数据元素(结点)之间的1:N(N=0,1,2)关系的表示(顺序映像完全二叉树的顺序存储;链式映像二叉链表、三叉链表);重点介绍二叉树的各种遍历算法(先序、中序、后序、层次遍历);并以这些遍历算法为基础,进一步讨论二叉树的其他各种问题的求解算法。然后,展开对一般树的表示(双亲表示法、孩子表示法、孩子-兄弟表示法)和操作算法的讨论;强调树(森林)的孩子-兄弟表示法及其相关应用;并将表示树(森林)的孩子-兄弟链映射到表示二叉树的二叉链,从而获得树(森林)与二叉树的相互转换。本实验以二叉树的链式表示、建立和应用为基础,旨在让学生深入了解二叉树的存储表示特征以及遍历次序与二叉树的存储结构之间的关系,进一步掌握利用遍历思想解决二叉树中相关问题的方法。本实验的另一个目的是让学生通过思考、上机实践与分析总结,理解计算机进行算术表达式解析、计算的可能方法,初步涉及一些编译技术,增加自己今后学习编译原理的兴趣,并奠定一些学习的基础。【实验内容】本实验由以下环节组成:1) 存储结构以二叉链表或三叉链表作为二叉树的存储结构;2) 二叉树的创建(链式存储)以某一种遍历的次序录入二叉树的元素,写出相应的二/三叉链表的创建算法,并上机实现该算法;二叉树的输入次序可以有如下几种方法,你可以选择其中之一来作为二/三叉链表创建程序的输入:ABCEDFGH图 1(1) 添加虚结点补足成完全二叉树,对补足虚结点后的二叉树按层次遍历次序输入。如图1的二叉树输入次序为: A, B, C, F, D, E, F, F, F, G, F, F, H也可以通过添加虚结点,为每一实在结点补足其孩子,再对补足虚结点后的二叉树按层次遍历的次序输入。如图1的二叉树输入次序为: A, B, C, F, D, E, F, G, F, F, H, F, F, F, F, F, F进一步改进,可以在输入列表中忽略出现在列表尾部的虚结点,即: A, B, C, F, D, E, F, G, F, F, H(2) 通过添加虚结点,将二叉树中的每一实在结点补足成度为2的结点,对补足虚结点后的二叉树按先序遍历的次序输入。如图1的二叉树输入次序为: A, B, F, D, G, F, F, F, C, E, F, H, F, F, F, F, F(3) 依次输入二叉树的中序和后序遍历的结果。如图1的二叉树输入次序为: 中序:B, G, D, A, E, H, C, F 后序:G, D, B, H, E, F, C, A(4) 依次输入二叉树的中序和先序遍历的结果。如图1的二叉树输入次序为: 中序:B, G, D, A, E, H, C, F 先序:A, B, D, G, C, E, H, F3) 二叉树的遍历对所建的二叉树进行验证:按初始输入元素采用的遍历方法遍历该二叉树,看遍历的结果是否与初始输入一致;4) 二叉树的应用:线索二叉树的创建基于二叉树遍历思想的其它问题的求解:扩展二叉树的存储结构,增加表示直接后继线索的链域,给出创建给定二叉树的后序线索化二叉树的程序;5) 线索二叉树的遍历编写在后序线索化树上的遍历算法。6) 二叉树创建的特例表达式树在2)基础上,设计并实现为输入表达式(仅考虑运算符为双目运算符的情况)创建表达式树的程序:A) 先实现输入为合法的波兰式;B) 进一步考虑输入为中缀表达式(需要分析优先级和结合性)C) 考虑输入为合法的逆波兰式;7) 二叉树遍历的特例表达式树针对用6)创建的表达式树,用3)遍历(先序/中序/后序)该树,比较它与实际的波兰式、中缀式和逆波兰式之间的区别;8) 二叉树的应用表达式求值与转换基于3)的后序遍历或基于5),A)完成给定表达式树的表达式求值运算;B)输出表达式的另一种表示方法(如波兰式、逆波兰式或中缀表达式)。 【实验要求】1. 每一学生必须完成以下内容:a) 1)至5)b) 6)中的A)、B)、C)之一c) 7)以及8)中的A)、B) 之一。2. 其余部分可以根据自己的实际情况酌情处理。【检查期限】1 上机内容检查时间:第68次上机时,以第8次上机为截止时间;2 报告上交截止时间:第7次上机后的第1次课上课前截止。实验四 图及其应用(23次上机)【上机时间】第810次【实验目的】图是另一种应用极为广泛的数据结构,是本课程的重点。图是一种M:N的非线性结构。在图的学习中,首先要解决如何表示图中顶点之间的关系。在教材中给出了邻接矩阵、邻接表、邻接多重表、十字链表四种存储结构,不同的存储结构有不同的应用范围(要求熟练掌握前两种存储结构)。其次,必须理解深度优先搜索和广度优先搜索的特征,熟练写出它们在ADT Graph以及在不同存储结构下的算法实现,并基于它们进一步掌握生成森林(树)的构造、连通分量的确定、关节点的查找等算法。再次,要能理解最小生成树的普里姆和克鲁斯卡尔算法,分析它们各自的特征以及时空特性。最后,掌握有向无环图的应用,重点掌握拓扑排序(入度),理解如何利用拓扑排序进行关键路径的求解;理解在网中如何求从某源点到其它顶点以及任意两个顶点之间的最短路径(可以结合最小生成树理解)。本实验是图的基础实验,旨在让学生熟练掌握图的存储表示特征,各类图的创建、遍历方法以及基于遍历的算法应用。【实验题目】1、 图的存储结构的定义和图的创建图的种类有:有向图、无向图、有向网、无向网。图的存储结构可采用:邻接矩阵、邻接表。要求:分别给出邻接矩阵和邻接表在某一种图上的创建算法2、 图的遍历:非递归的深度优先搜索算法、广度优先搜索算法。3、 图的深度遍历的应用:求无向连通图中的关节点(教材P177-178,算法7.10和7.11)4、 图的广度遍历的应用:给定图G,输出从顶点v0到其余每个顶点的最短路径,要求输出各路径中的顶点信息。【实验要求】1. 每一学生必须完成上述所有题2. 进一步熟悉静态链的应用:习题集7.25,以及依据此存储结构的3和4的实现本项内容可以根据自己的实际情况酌情处理,但是必须在实验报告中附上自己对静态链的体会(可以结合在树中的作业练习等来阐明)。【检查期限】1 上机内容检查时间:第910次上机时,以第10次上机为截止时间;2 报告上交截止时间:第10次上机后的第1次课上课前截止。附录 Makefile 、GCC、pkg-config 使用说明1. GCC的使用2. pkg-config的作用3. Makefile的作用此教程还不够完整,很多地方比较简陋,打算以后再不断完整它,现在来说用这个教程的内容已经能够开发程序了。1GCC的使用通常所说的GCC是GUN Compiler Collection的简称,除了编译程序之外,它还含其他相关工具,所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代码。GCC是Linux平台下最常用的编译程序,它是Linux平台编译器的事实标准。同时,在Linux平台下的嵌入式开发领域,GCC也是用得最普遍的一种编译器。GCC之所以被广泛采用,是因为它能支持各种不同的目标体系结构。例如,它既支持基于宿主的开发(简单讲就是要为某平台编译程序,就在该平台上编译),也支持交叉编译(即在A平台上编译的程序是供平台B使用的)。目前,GCC支持的体系结构有四十余种,常见的有X86系列、Arm、 PowerPC等。同时,GCC还能运行在不同的操作系统上,如Linux、Solaris、Windows等。除了上面讲的之外,GCC除了支持C语言外,还支持多种其他语言,例如C+、Ada、Java、Objective-C、FORTRAN、Pascal等。程序的编译过程对于GUN编译器来说,程序的编译要经历预处理、编译、汇编、连接四个阶段。从功能上分,预处理、编译、汇编是三个不同的阶段,但GCC的实际操作上,它可以把这三个步骤合并为一个步骤来执行。在预处理阶段,输入的是C语言的源文件,通常为*.c。它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、 #include和#define命令。该阶段会生成一个中间文件*.i,但实际工作中通常不用专门生成这种文件,因为基本上用不到;若非要生成这种文件不可,可以利用下面的示例命令:gcc -E main.c -o main.i在编译阶段,输入的是中间文件*.i,编译后生成汇编语言文件*.s 。这个阶段对应的GCC命令如下所示:gcc -S main.i -o main.s在汇编阶段,将输入的汇编文件*.s转换成机器语言*.o。这个阶段对应的GCC命令如下所示:gcc -c main.s -o main.0最后,在连接阶段将输入的机器代码文件*.s(与其它的机器代码文件和库文件)汇集成一个可执行的二进制代码文件。这一步骤,可以利用下面的示例命令完成:gcc main.o -o mainGCC常用模式这里介绍GCC追常用的两种模式:编译模式和编译连接模式。下面以一个例子来说明各种模式的使用方法。为简单起见,假设我们全部的源代码都在一个文件main.c中,要想把这个源文件直接编译成可执行程序,可以使用以下命令:gcc -o main main.c这里main.c是源文件,生成的可执行代码存放在一个名为main 的文件中(该文件是机器代码并且可执行)。-o 是生成可执行文件的输出选项。如果我们只想让源文件生成目标文件(给文件虽然也是机器代码但不可执行),可以使用标记-c ,详细命令如下所示:gcc -c main.c默认情况下,生成的目标文件被命名为main.o,但我们也可以为输出文件指定名称,如下所示:gcc -c main.c -o m.o上面这条命令将编译后的目标文件命名为m.o,而不是默认的main.o。迄今为止,我们谈论的程序仅涉及到一个源文件;现实中,一个程序的源代码通常包含在多个源文件之中,这该怎么办?没关系,即使这样,用GCC处理起来也并不复杂,见下例:gcc -o main 1.c 2.c 3.c需要注意的是,要生成可执行程序时,一个程序无论有有一个源文件还是多个源文件,所有被编译和连接的源文件中必须有且仅有一个main函数,因为main 函数是该程序的入口点(换句话说,当系统调用该程序时,首先将控制权授予程序的main函数)。但如果仅仅是把源文件编译成目标文件的时候,因为不会进行连接,所以main函数不是必需的。常用选项许多情况下,头文件和源文件会单独存放在不同的目录中。例如,假设存放源文件的子目录名为./src,而包含文件则放在层次的其他目录下,如./inc。当我们在./src 目录下进行编译工作时,如何告诉GCC到哪里找头文件呢?方法如下所示:gcc test.c I./inc -o test上面的命令告诉GCC包含文件存放在./inc 目录下,在当前目录的上一级。如果在编译时需要的包含文件存放在多个目录下,可以使用多个-I 来指定各个目录:gcc test.c I./inc1 I././inc2 -o test这里指出了另一个包含子目录inc2,较之前目录它还要在再上两级才能找到。另外,我们还可以在编译命令行中定义符号常量。为此,我们可以简单的在命令行中使用-D选项即可,如下例所示:gcc -DTEST_CONFIGURATION test.c -o test上面的命令与在源文件中加入下列命令是等效的:#define TEST_CONFIGURATION在编译命令行中定义符号常量的好处是,不必修改源文件就能改变由符号常量控制的行为。 此外,还有一个比较重要的选项,就是指定编译时需要用到的库,用法如下:gcc -lcurses main.c 这个例子的意思是使用curses库编译程序。使用-L选项时后面可以放库的路径,编译器就会在指定的目录里寻找所需的库。警告功能当GCC 在编译过程中检查出错误的话,它就会中止编译;但检测到警告时却能继续编译生成可执行程序,因为警告只是针对程序结构的诊断信息,它不能说明程序一定有错误,而是存在风险,或者可能存在错误。虽然GCC提供了非常丰富的警告,但前提是你已经启用了它们,否则它不会报告这些检测到的警告。在众多的警告选项之中,最常用的就是-Wall选项。该选项能发现程序中一系列的常见错误警告,该选项用法举例如下:gcc -Wall main.c -o main2pkg-config的作用(此部分教程还不完整,待添加新内容)在使用GCC编译和链接源代码时,往往需要指定库的头文件地址和库文件的位置。即使用-I和-L选项(或者-i和-l)。但是,当编写程序时,往往会需要涉及到多个程序库,比如编写GTK+程序时,需要用到的库有pango、cairo、gobject、gdk等,涉及到的头文件也很多,一一指定是非常麻烦的。pkg-config简单来说,就是一个负责替你指定所用到的头文件和库的地址给编译器的工具,一般来说,最常用的是两个功能,一是指定编译程序时需要用到的头文件地址给编译器,使用方法:gcc E main.c o main.i pkg-config cflags gtk+-2.0这条命令的意思是,假定main.c是一个GTK+程序,则pkg-config cflags gtk+-2.0这条命令会把这个程序里所用的头文件地址告诉给gcc。注意,gcc使用-E选项,表示只对main.c做预处理,因为没有指定库函数的地址,不能加载库函数,即不能可执行生成模块。另外一个重要的功能是告诉编译器库的位置,使用方法:Gcc o main main.i pkg-config libs gtk+-.20这条命令的意思是,把main.i所可能用到的库的目录传给编译器,这样就能编译连接生成可知性文件了。关于pkg-config 就介绍到这里,其他内容以后再作补充。3Makefile的作用3.1 Makefile简介Makefile是GNU Make的输入文件,用来指示make怎样自动编译源代码。这个文 件里主要是有关哪些文件(target目的文件)是从哪些别的 文件(dependencies依靠文件)中产生的,用什么命令来进行 这个产生过程。有了这些信息, make 会检查硬盘上的文件,如果 目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。(目的文件不一定是最后的可执行档,它可以是任何一个文件。)Makefile 一般被叫做“Makefile”或“makefile”。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定,它会寻 找“makefile”或“Makefile”,因此使用这两个名字是最简单 的。一个 makefile 主要含有一系列的规则,如下:: .(tab)<command>(tab)<command>.例如,考虑以下的 makefile :-myprog : foo.o bar.o gcc foo.o bar.o -o myprogfoo.o : foo.c foo.h bar.h gcc -c foo.c -o foo.obar.o : bar.c bar.h gcc -c bar.c -o bar.o-这是一个非常基本的 makefile make 从最上面开始,把上 面第一个目的,myprog,做为它的主要目标(一个它需要保 证其总是最新的最终目标)。给出的规则说明只要文件myprog 比文件foo.o或bar.o中的任何一个旧,下一行的命令将 会被执行。但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟 上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc -o foo.o foo.c' 将会执行,从而更新 文件 foo.o 。接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。现在, make 回到myprog的规则。如果刚才两个规则中的任 何一个被执行,myprog 就需要重建(因为其中一个 .o 档就会比 myprog新),因此连接命令将被执行。3.2 编写 make 规则 (Rules)最简单的编写规则的方法是一个一个的查 看源码文件,把它们的目标文件做为目的,而源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被 包括的文件所包括的文件然后你会发现要对越来越多的文件 进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸 色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的 电脑显示器,停止编程。到低有没有些容易点儿的方法呢? 当然有!向编译器要!在编译每一个源码文件的时候,它实在应 该知道应该包括什么样的 header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的文件输出一个规则,把目标文件 做为目的,而这个文件和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(<', >')和双引号("')所包围的文件。其实我们可以 相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc,那些用角括 号包围的 header 档将不会被包括。(这会节省一些编译时间) 由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让 make 使用它的隐含的规则。3.3 Makefile 变量上面提到 makefiles 里主要包含一些规则。它们包含的其它的东西是变量定义。makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make 的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如: i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。 ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使用一个不同的编译器,你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。 iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall -O -g);如果你把这组选项存 入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。要设定一个变量,你只要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用 这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在 下面,我们把前面的 makefile 利用变量重写一遍:-OBJS = foo.o bar.oCC = gccCFLAGS = -Wall -O -gmyprog : $(OBJS) $(CC) $(OBJS) -o myprogfoo.o : foo.c foo.h bar.h $(CC) $(CFLAGS) -c foo.c -o foo.obar.o : bar.c bar.h $(CC) $(CFLAGS) -c bar.c -o bar.o-还有一些设定好的内部变量,它们根据每一个规则内容定义。三个 比较有用的变量是 $, $< 和 $ (这些变量不需要括号括住)。 $ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第 一个依靠文件,而 $ 扩展成整个依靠的列表(除掉了里面所有重 复的文件名)。利用这些变量,我们可以把上面的 makefile 写成:-OBJS = foo.o bar.oCC = gccCFLAGS = -Wall -O -gmyprog : $(OBJS) $(CC) $ -o $foo.o : foo.c foo.h bar.h $(CC) $(CFLAGS) -c $< -o $bar.o : bar.c bar.h $(CC) $(CFLAGS) -c $< -o $-你可以用变量做许多其它的事情,特别是当你把它们和函数混合 使用的时候。如果需要更进一步的了解,请参考 GNU Make 手册。另外,配合使用pkg-config,你可以在把代码所用的头文件和库函数位置放在变量里。比如,在编写GTK+程序时,可以这样设置一个变量:GTK_FLAGS=$(shell pkg-config -libs -cflags "gtk+-2.0") 然后在规则里使用它:gcc main.c -o simple $(GTK_FLAGS)3.4 隐含规则 (Implicit Rules)请注意,在上面的例子里,几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和相关文件里产生 .o 文件,这是一个标准的步 骤。其实 make 已经知道怎么做它有一些叫做隐含规则的内 置的规则,这些规则告诉它当你没有给出某些命令的时候,应该 怎么办。 如果你把生成 foo.o 和 bar.o 的命令从它们的规则中删除, make 将会查找它的隐含规则,然后会找到一个适当的命令。它的命令会 使用一些变量,因此你可以按照你的想法来设定它:它使用变量 CC 做为编译器(象我们在前面的例子),并且传递变量 CFLAGS (给 C 编译器,C+ 编译器用 CXXFLAGS ),CPPFLAGS ( C 预 处理器旗标), TARGET_ARCH (现在不用考虑这个),然后它加 入旗标 '-c' ,后面跟变量 $< (第一个依靠名),然后是旗 标 '-o' 跟变量 $ (目的文件名)。一个编译的具体命令将 会是:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $ 当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开关输出的码可以直接用在一个 makefile 里。3.5 假象目的 (Phony Targets)假设你的一个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文件,但这两个文件是相互独立的如果一 个文件需要重建,并不影响另一个。你可以使用“假象目的”来 达到这种效果。一个假象目的跟一个正常的目的几乎是一样的, 只是这个目的文件是不存在的。因此, make 总是会假设它需要 被生成,当把它的依赖文件更新后,就会执行它的规则里的命令 行。 如果在我们的 makefile 开始处输入:all : exec1 exec2 其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 'all' 做为它的主要目的,每次执行时都会尝试把 'all' 更新。但既然这行规则里没有哪个命令来作用在一个叫 'all' 的 实际文件(事实上 all 并不会在磁碟上实际产生),所以这个规 则并不真的改变 'all' 的状态。可既然这个文件并不存在,所以 make 会尝试更新 all 规则,因此就检查它的依靠 exec1, exec2 是否需要更新,如果需要,就把它们更新,从而达到我们的目的。 假象目的也可以用来描述一组非预设的动作。例如,你想把所有由 make 产生的文件删除,你可以在 makefile 里设立这样一个规则:clean : rm *.o rm myprog 前提是没有其它的规则依靠这个 'veryclean' 目的,它将永远 不会被执行。但是,如果你明确的使用命令 'make veryclean' , make 会把这个目的做为它的主要目标,执行那些 rm 命令。 如果你的硬盘上存在一个叫 clean 文件,会发生什么事?这 时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是 最新的了(所有的依靠文件都已经是最新的了),所以既使用户明 确命令 make 重新产生它,也不会有任何事情发生。解决方法是标 明所有的假象目的(用 .PHONY),这就告诉 make 不用检查它们 是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目 的需要被更新。在 makefile 里加入下面这行包含上面规则的规则:.PHONY : clean就可以了。注意,这是一个特殊的 make 规则,make 知道 .PHONY 是一个特殊目的,当然你可