第8章 C、C++程序开发基础
【学习目标】
1.知识目标
了解软件开发过程。
掌握C语言基本语法。
掌握程序调试概念。
掌握编译相关概念。
掌握GCC、Makefile、GDB的用途。
2.技能目标
安装C/C++的开发工具。
编写简单的C/C++程序。
调试程序。
编译程序。
【认证考点】
了解软件开发过程。
认识编译相关概念、工具。
掌握GCC、Makefile、GDB的用途。
能够编写简单的C/C++程序。
能够编译、运行C/C++程序。
基本读懂C/C++程序。
掌握程序调试的基本知识和技巧。
项目引导:简易计算器
【项目描述】
该项目将实现两个数据加减乘除的简易计算器,任意输入两个数,求出它们的和、差、积、商,简易计算器运行界面如图8-1-1所示。完成该项目需要几个过程,分别为软件开发流程、安装与配置程序语言的开发环境、设计程序流程图、编写程序代码、调试代码、编译以及运行,经过上述过程才能完成该项目。
知识储备
8.1软件开发流程简介
软件开发流程即建立软件设计思路和方法。它的一般过程分为需求分析、概要设计、详细设计、编码、测试及软件交付等一系列操作,以满足用户的需求、解决用户的问题。
1.需求分析
软件开发项目,在项目启动前期,首先要明确用户的需求,在充分沟通后,项目双方对需求达成一致,最后形成需求分析。
首先,初步了解用户需求,然后用相关的工具软件列出要开发的系统的大功能模块,每个大功能模块包含哪些小功能模块。有些需求具有比较明确的界面时,可以初步定义少量的相关界面。接着,深入了解和分析需求,根据自己的经验用Word等工具写出一份系统的《功能需求文档》。该文档要清楚地列出系统的功能模块,并且列出相关的界面及其功能。最后,向用户再次确认需求。
2.概要设计
对软件系统进行概要设计,即系统设计。概要设计需要对软件系统的设计进行考虑,包括系统的基本处理流程、组织结构、模块划分、功能分配、接口设计、运行设计、数据结构设计和出错处理设计等,为软件的详细设计提供基础。
3.详细设计
在概要设计的基础上,开发者需要进行软件系统的详细设计。在详细设计中,描述实现具体模块所涉及的主要算法、数据结构、类的层次结构及调用关系,需要说明软件系统各个层次中的每一个程序的设计思路,要保证软件的需求完全分配到整个软件结构中,应当能够根据《软件系统详细设计报告》进行编码和测试。
4.编码
在软件编码阶段,开发者根据《软件系统详细设计报告》中对数据结构、算法和模块实现等的设计要求,开始具体的程序编写工作,分别实现各模块的功能,从而实现目标系统的功能、性能、接口、界面等。在规范化的研发流程中,编码工作量在整个项目流程里最多不超过1/2,通常为1/3。设计过程完成得好,编码效率就高,要注意的是,编码时不同模块之间的进度协调和协作是最需要小心的,也许一个小模块的问题就可能影响了整体进度,让很多程序员因此被迫停下工作,这种问题在很多研发过程中都出现过。
5.测试
测试编写好的系统,交给用户使用,用户使用时确认每个功能。软件测试有多种,按照测试执行方式区分,可以分为内部测试和外部测试;按照测试范围区分,可以分为模块测试和整体联调;按照测试条件区分,可以分为正常操作情况测试和异常情况测试;按照测试的输入范围区分,可以分为全覆盖测试和抽样测试。
8.2 C语言语法基础
C语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发UNIX操作系统而设计的。C语言最开始是于1972年,它是一种高级语言,应用很广泛,即可用于嵌入式开发也也用于应用程序的开发,其中UNIX操作系统、C编译器、及UNIX应用程序都是用C语言编写的。
C程序主要包括以下部分:预处理器指令、变量、结构与表达式、函数、注释。
1.预处理器指令
预处理器是一个程序,用来处理源程序中的预处理指令。 一个C语言程序在编译之前一般都要经过预处理。
预处理器指令以“#”开头,它可以出现在任何位置。预处理语句须一行结束如要换行,则须用“\”来连接两行内容。 预处理主要有宏定义、宏函数、文件包含、条件编译。
(1)包含
文件包含是指一个文件包含另一个文件的内容,代码如下
#include "文件名"
//或
#include <文件名>(2)宏指令
宏相当于文本的替换操作 ,定义在函数的外面,如定义PI内容替换为3.12,代码如下:
#define 标识符文本
#define PI 3.12(3)宏函数
宏函数只是文本,只是相当于做了内容替换的操作,注意参数是没有数据类型。代码如下:
//带参数的宏定义
#define S(a,b) a*b(4)条件编译指令
在代码中设置编译条件根据编译条件进行代码的编译并运行。在编译文件的时候传入一个参数,根据参数就可以对代码进行有选择的编译。代码如下:
#ifdef 标识符
程序段1
#else
程序段2
#endif或
#ifdef
程序段1
#endif2.变量
C语言中变量是程序可操作的存储区的名称。C语言中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
C语言中的变量定义语法如下:
type variable_list;type 必须是一个有效的C语言数据类型,可以是char、w_char、int、float、double或任何用户自定义的对象,variable_list 可以由一个或多个标识符名称组成,多个标识符之间用逗号分隔。
下面列出几个有效的声明:
int i,j,k;
char c,ch;
float f,salary;
double d;(1)变量的命名规则
变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为C语言的大小写是敏感的。
(2)变量的数据类型
C语言中的数据类型可分为基本类型、构造类型、空类型、指针类型和用户自定义类型。基本类型包括整型、实型和字符型,基本类型的数据是不可分解的;构造类型的值是由若干成分按某种方式组成的,它的成分可能是基本类型,也可能是其他类型的,C语言中的构造类型有数组、结构体、共用体、位段和枚举等类型;用户自定义类型是为已经存在的数据类型重新命名;指针类型是表示数据在内存中的地址的一种特殊数据类型形式。
①整型数据。整型数据有短整型(shortint或short)、基本整型(int)、长整型(longint或long)和无符号整型(unsigned)。若不指定变量为无符号型,则变量默认为有符号型(signed)。整形数据如表8-2-1所示。
②实型变量。C语言中的实型变量分为单精度类型(float)和双精度类型(double)。对于每一个实型变量也都应该先定义后使用,一般在定义变量的同时进行初始化。
③字符变量。字符型变量用来存放字符数据,同时只能存放一个字符。在C语言中,字符变量用关键字char进行定义,在定义的同时一般进行初始化,其基本定义格式与前面的整型变量定义相同。
④数组。C语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合,数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
声明一个数组,需要指定元素的类型和元素的数量,语法如下:
typearray Name[arraySize];初始化数组,可以逐个初始化数组,也可以使用一个初始化语句,代码所示:
double balance[5]={1000.0,2.0,3.4,7.0,50.0};大括号{}之间的值的数目不能大于数组声明时在方括号[]中指定的元素数目。
访问数组元素。数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。代码如下:
double salary=balance[2];⑤枚举。枚举是C语言中的一种基本数据类型,它可以让数据更简洁,更易读。枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};【实例8-1】定义一星期有7天,代码如下:
enum DAY
{
MON=1,TUE,WED,THU,FRI,SAT,SUN
};⑥结构体。数组允许定义可存储相同类型数据项的变量,结构体是C编程中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。
结构体的定义,使用 struct 语句。struct语句定义了一个包含多个成员的新的数据类型,struct语句的格式如下:
struct tag{
member-list
member-list
member-list
...}variable-list;tag 是结构体标签;member-list 是标准的变量定义,比如inti或者floatf,或者其他有效的变量定义;variable-list 结构变量,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量。
【实例8-2】声明Book结构的方式,代码如下:
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
}book;3.程序基本结构
C语言程序的结构有顺序结构、选择结构和循环结构。
(1)选择结构
选择结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句和条件为假时要执行的语句。C语言把任何非零和非空的值假定为 true,把零或 null 假定为 false。
①选择结构类型。选择结构有if、if…else、嵌套if、switch、嵌套switch语句等类型,见表8-2-2。
②三元运算符“?:运算符”。三元运算符“?:”,可以用来替代 if...else 语句。它的一般形式如下:
Exp1?Exp2:Exp3;其中,Exp1、Exp2和Exp3是表达式。“?”表达式的值是由Exp1决定的。如果Exp1为真,则计算Exp2的值,结果即为整个“?”表达式的值。如果Exp1为假,则计算Exp3的值,结果即为整个“?”表达式的值。
【实例8-3】输入一个数字来判断它是否为奇数或偶数,代码如下:
#include <stdio.h>
int main() {
int num;
printf("输入一个数字 : ");
scanf("%d",&num);
(num%2==0)?printf("偶数"):printf("奇数");
}(2)循环结构
有的时候,可能需要多次执行同一块代码,C语言提供了执行路径更为复杂的多种控制结构。循环语句允许我们多次执行一个语句或语句组。
① 循环类型。C 语言提供了以下几种循环类型,见表8-2-3所示。
② 循环控制语句。循环控制语句改变你代码的执行顺序,通过它你可以实现代码的跳转,见表8-2-4所示。
【实例8-4】循环输出1···10的值,代码如下:
#include <stdio.h>
int main () {
for( int i=1;i<10 i++; ) {
printf("i=%d",i);
}
}4.函数
函数是一组一起执行一个任务的语句。每个C程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数,通常是根据每个函数执行一个特定的任务来定义函数。
(1)定义函数
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体,C语言中的函数定义的一般形式如下:
return_type function_name( parameter list ){
body of the function
}return_type 返回类型是函数返回值的数据类型,有些函数执行所需的操作而不返回值,return_type设置返回类型为 void。function_name函数名称是函数的实际名称。parameter list参数,参数就像是占位符,当函数被调用时,向参数传递一个值,这个值被称为实际参数,参数列表包括函数参数的类型、顺序、数量,参数是可选的,也就是说,函数可能不包含参数。函数主体包含一组定义函数执行任务的语句。
【实例8-5】定义max函数,函数有两个参数num1和num2,会返回这两个数中较大的那个数,代码如下:
int max(int num1, int num2) {
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}(2)调用函数
当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。
【实例8-6】调用max函数,代码如下:
int main () {
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
/* 调用函数来获取最大值 */
ret = max(a, b);
printf( "Max value is : %d\n", ret );
return 0;
}(3)函数参数
如果函数要使用参数,则必须声明接受参数值的变量,这些变量称为函数的形式参数。当调用函数时,有两种向函数传递参数的方式。见表8-2-5所示。
5.注释
C 语言有两种注释方式,“// ”表示单行注释,这种注释可以单独占一行。“/* */ ”表示多行注释,可以单行或多行。格式如下:
//单行注释
/*
多行注释
多行注释
多行注释
*/项目实施
采用C语言来实现简易计算器这个项目,首先要确定项目的需求,这个项目比较简单,从功能上主要实现两个数的加减乘除。
需要完成的任务:
- 安装与配置C开发环境
- 编写简易计算器程序代码
- 调试、编译、运行程序
8.3任务1:安装与配置开发环境
C语言和C++语言是国际上广泛流行的计算机高级程序设计语言,也是世界上最受欢迎的语言之一,它具有强大的功能,许多著名的软件都是用C语言或C++语言来编写的。这里主要介绍C语言。
C语言的开发过程一般要经过编辑、编译、链接、运行四个步骤。编写的代码称为源代码或源文件,编辑是指输入、修改源代码的过程。在这个过程中还要对源代码进行排版,使之美观、易读、有层次,并加上一些说明性的文字注释,帮助程序员理解代码的含义。经过编辑的源代码保存后生成后缀名为“.c”的文件。编译是指把源代码转换为可执行文件,这个转换的过程需要借助编译器,经过编译,把源代码转换为以“.obj”为后缀名的目标文件,但目标文件是机器代码,是不能够直接执行的,它需要有其他文件或者其他函数库辅助,才能最终生成以“.exe”为后缀名的可执行文件,这个过程称为链接。执行“.exe”文件并生成结果的过程称为运行。
8.3.1开发工具介绍及安装
C语言常用的集成开发环境有VisualC++、VisualC++.NET、TurboC以及BorlandC++Builder等。
1.开发工具介绍
VisualC++ 6.0,简称VC 6.0,是微软于1998年推出的一款C++编译器,包含标准版、专业版与企业版 ,缺点是对Windows系统不同版本的兼容性较差。Visual C++.NET是Visual C++ 6.0的后续版本,是一套完整的开发工具集,可在.NET平台下调用Framework的类库,功能强大,其中包含VisualC++开发组件。TurboC是美国Borland公司的产品,目前常用的版本是TurboC 2.0。BorlandC++Builder是由Borland公司继Delphi之后推出的一款高性能集成开发环境,具有可视化的开发环境。
2.开发工具的安装
下面以VisualC++ 6.0为例介绍开发工具的安装。VisualC++ 6.0是一个功能强大的可视化软件开发工具,它将程序的代码编辑、编译、链接和调试等功能集于一身。接下来介绍VisualC++ 6.0的安装过程。
(1)下载VisualC++ 6.0
登录其官方网站,下载该软件。
(2)VisualC++的安装过程
【步骤1】解压安装包文件。VisualC++ 6.0安装包文件可以直接解压。
【步骤2】安装。VisualC++ 6.0的安装比较简单,根据安装向导,选择软件的安装路径,直到安装完成,安装过程需1~2分钟。
8.3.2使用开发工具
1.启动VisualC++ 6.0
双击图标,启动VisualC++ 6.0。
2.使用VisualC++ 6.0
(1)部分工具栏按钮介绍
VisualC++ 6.0集成开发环境提供了许多有用的工具栏按钮,工具栏中包含如下按钮。
(2)创建C源程序
在VisualC++ 6.0界面上方的菜单栏中选择“文件”→“新建”,或者使用快捷键“Ctrl+N”,弹出新建对话框,选择“文件”→“C++SourceFile”,在对话框的右边,设置文件的保存路径及文件名。注意:C语言文件名的后缀为“.c”。以简易计算器程序为例,文件取名为computer.c,如图8-3-1所示。
(3)编写代码
在编辑区编写程序代码,编写一个简单打印输出的C源程序,如图8-3-2所示。
(4)编译程序
C语言是高级程序语言,在计算机中不能直接执行,需要编译。如computer.c源文件写好了,要进行编译,选择菜单“组建”→“编译”,如图8-3-3所示。
单击“编译”后,会出现如图8-3-4所示的对话框,询问是否创建一个默认项目环境,选择“是”,编译结果如图8-3-5所示。
(5)程序运行
运行C源程序,单击
8.4任务2:编写简易计算器程序代码
编写简易计算器程序,首先需要了解数据类型、定义变量、输出语句、输入语句、控制语句等知识。编写程序代码前,先设计程序流程图。
8.4.1设计程序流程图
程序流程图又称程序框图,是用统一规定的标准符号描述程序运行具体步骤的图形表示,通过对输入、输出数据和处理过程的详细分析,将计算机的主要运行步骤和内容标识出来。程序流程图是进行程序设计的基本依据,因此它的质量直接关系到程序设计的质量。
1.程序流程图的构成
程序流程图由处理框、判断框、起止框、连接点、流程线、注释框、相应的算法等构成。
2.程序流程图的三种基本结构
任何复杂的算法都可以由顺序结构、选择(分支)结构和循环结构这三种基本结构组成,因此,构造一个算法时,也是以这三种基本结构为“建筑单元”,遵守三种基本结构的规范。基本结构之间可以并列、相互包含,但不允许交叉,不允许从一个结构直接转到另一个结构的内部。因为整个算法都是由三种基本结构组成的,就像用模块构建的一样,所以结构清晰,易于验证正确性,易于纠错。这种方法就是结构化方法,遵循这种方法的程序设计,就是结构化程序设计。相应地,只要规定好三种基本结构的流程图的画法,就可以画出任何算法的流程图。
(1)顺序结构如图8-4-1所示。
(2)选择(分支)结构。这种结构是对某个给定条件进行判断,条件为真或假时分别执行不同的程序。其基本形式如图8-4-2所示。
(3)循环结构。循环结构有两种基本形式:while型循环和do-while型循环。while型循环的执行序列为:当条件为真时,反复执行循环体,一旦条件为假,跳出循环,执行循环后的语句。do-while型循环的执行序列为:首先执行循环体,再判断条件,条件为真时,一直循环执行循环体,一旦条件为假,结束循环,执行循环后的语句。循环结构如8-4-3所示。
3.程序流程图的作用及优点
程序流程图是人们对解决问题的方法、思路或算法的一种描述。
流程图的优点:
(1)采用简单、规范的符号,画法简单。
(2)结构清晰,逻辑性强。
(3)便于描述,容易理解。
4.简易计算器的程序流程图
简易计算器程序的编程思路,首先定义计算的两个数为变量,确定其数据类型为浮点数,可以采用float或double定义两个变量。接着打印输出计算器的服务界面,采用printf()函数打印输出,用户在服务界面选择功能菜单(1表示加法,2表示减法,3表示乘法,4表示除法),功能菜单用一个整数变量来表示,设定该整数变量值的范围为1~4,超过该范围则提示输入出错。接下来根据用户选择的功能实现不同的运算,这是一个判断性问题,有多个选择,采用条件分支语句switch(表达式)完成,如果表达式值为1,则实现两个操作数相加;如果表达式值为2,则实现两个操作数相减;如果表达式值为3,则实现两个操作数相乘;如果表达式值为4,则实现两个操作数相除。注意除法运算中,“0”不能作为被除数,所以在除法运算中需要对第二个操作数进行判定,采用if语句实现。简易计算器程序流程图如图8-4-4所示。
8.4.2编写代码
根据程序流程图编写程序代码,源代码文件取名为computer.c,代码如下:
#include<stdio.h>
void main(){
//定义两个操作符和功能选项
double Lopter,Ropter,sum=0;
int server;
//打印服务界面
printf("\n----------------简易计算器-------------------\n");
printf("\n1加法运算2减法运算\n");
printf("\n3乘法运算4除法运算\n");
printf("\n------------------------------------------\n");
printf("请选择你要的服务(1、2、3、4):");
scanf("%d",&server);
if(server>4||server<1)
printf("输入有误!\n");
else
{
printf("\n请输入两个操作数:");
scanf("%lf%lf",&Lopter,&Ropter);
switch(server)
{
case1:
sum=Lopter+Ropter;
break;
case2:
sum=Lopter-Ropter;
break;
case3:
sum=Lopter*Ropter;
break;
//如果功能为4,则需要判断被除数不能为0
default:if(Ropter==0)
{
printf("被除数不能为零!\n");
}
else
{
sum=Lopter/Ropter;
}
}
printf("\n两个数的运算结果为:%8.3lf\n",sum);
}
}【代码解析】
1.功能界面
运用printf函数,打印出功能界面,代码如下:
//打印服务界面
printf("\n--------------------简易计算器-------------------\n");
printf("\n1加法运算2减法运算\n");
printf("\n3乘法运算4除法运算\n");
printf("\n----------------------------------------------\n");
printf("请选择你要的服务(1、2、3、4):");功能界面,运行结果如图8-4-5所示。
2.输出结果保留小数点后3位
代码printf("\n两个数的运算结果为:%8.3lf\n",sum)中%8.3表示输出结果保留小数点后3位。在输出程序中会四舍五入,但存储在变量里的结果并没有丢失精度。
3.if-else与switch语句的比较
switch语句与if-else进行对比,if-else比switch语句的条件控制更强大,if-else可以根据各种关系和逻辑运算的结果进行流程控制;而switch语句只能进行“==”的判断。
8.5任务3:编译、调试、运行程序
计算机高级语言按程序的执行方式可以分为编译型和解释型两种。
因为编译型语言是一次性地将源程序编译成机器码,所以可以脱离开发环境独立运行,通常运行效率较高。但由于编译型语言的程序被编译成特定平台上的机器码,因此编译生成的可执行性程序通常无法移植到其他平台上运行;如果需要移植,则必须将源代码复制到特定平台上,针对特定平台进行修改,至少也需要采用特定平台上的编译器进行重新编译。现有的C、C++、FORTRAN、Pascal等高级语言都属于编译型语言。
每次执行解释型语言的程序都需要进行一次编译,因此解释型语言的运行效率通常较低,而且不能脱离解释器独立运行。但解释型语言有一个优势:跨平台比较容易,只提供特定平台的解释器即可,每个特定平台上的解释器负责将源程序解释成特定平台的机器指令。解释型语言可以方便地实现源程序级的移植,但以牺牲程序执行效率为代价。现有的Ruby、Python等语言都属于解释型语言。
无论C还是C++语言,首先要把源文件编译成中间目标文件,在Windows下中间目标文件就是“.obj”文件,UNIX下中间目标文件是“.o”文件,即ObjectFile文件,这个动作叫做编译。然后再把大量的ObjectFile合成执行文件,这个动作叫做链接。
编译时,编译器需要语法正确、函数与变量的声明正确。对于后者,通常需要告诉编译器头文件的所在位置,只要所有语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应一个中间目标文件。
链接的主要是函数和全局变量,所以,可以使用这些中间目标文件来链接应用程序。链接器并不处理函数所在的源文件,只处理函数的中间目标文件,大多数时候,由于源文件太多,导致编译生成的中间目标文件太多,而在链接时需要明确地指出中间目标文件名,这对于编译而言很不方便,所以,要给中间目标文件打包,在Windows下这种包称为“库文件”,也就是“.lib”文件,在UNIX下,是归档文件,也就是“.a”文件。
8.5.1GCC编译
GCC是GNU编译器集,是C、C++、Objective-C、Fortran、Ada、Go等编程语言的编译器和库的集合。许多开源项目包括GNU工具和Linux内核都是用GCC编译的。
1.安装GCC
在Ubuntu系统中安装GCC,首先需要在Ubuntu系统上添加新存储库和安装软件包,这时须用具有sudo权限的用户身份登录系统,默认Ubuntu系统中存储库包含一个名为“build-essential”的元包,它包含GCC编译器以及编译软件所需的许多库和其他实用程序。在Ubuntu上安装GCC编译器过程如下:
(1)更新包列表
sudo apt update(2)安装build-essential软件包
sudo apt install build-essential(3)验证GCC编译器是否已成功安装
gcc --version输入命令后,显示GCC版本的信息,则说明GCC安装成功,如图8-5-1所示。
2.GCC编译过程
GCC的编译过程为:预处理→编译→汇编→链接。
- 预处理:预处理器将展开源文件中的宏。
- 编译:GCC将C文件编译成汇编文件。
- 汇编:AS将汇编文件编译成机器码。
- 链接:将目标文件和外部符号进行链接,得到一个可执行二进制文件。
3.GCC常用选项
- -c表示编译源文件不链接,生成目标文件(.o文件)。
- -S表示只编译不汇编,生成汇编代码。
- -E表示进行预编译,不做其他处理。
- -ofile表示输出目标文件。
- -g表示在目标文件中产生调试信息,用于GDB调试。
- -D<宏定义>编译时将宏定义传入。
- -Wall打开所有类型的警告。
4.GCC编译运行C程序
(1)运用Vim编程C程序
在Vim工具编写comuter.c程序,运用“vi”命令,编写C程序。如编写computer.c文件,命令如下:
vi computer.cVim工具打开以后默认进入的是命令模式,编写程序代码,也可以把写好的C程序代码复制到编辑区域里,如图8-5-2所示。
程序编辑好以后,按ESC键退出编辑模式,输入“:wq”即可保存修改的程序。
(2)编译C语言程序
语法:
$gcc[programName].c -o program Name.exe(或programName)//-o表示输出为指定文件类型,将C语言程序编译为*.exe文件。
编译“computer.c”程序为computer可执行exe文件,如下图8-5-3所示。
(3)运行C语言程序
编译成功后,就可以运行程序,运行的命令语法如下:
./programName运行computer.exe文件,如图8-5-4所示。
8.5.2Make和Makefile编译工具
在软件的工程中,源文件是不计其数的,其按照类型、功能、模块分别放在若干个目录中,哪些文件需要编译,哪些文件需要后编译,哪些文件需要重新编译,甚至进行更复杂的操作,这时就需要系统编译工具了。
在Linux和UNIX中,有一个强大的命令叫做Make,可以用它来管理多模块程序的编译和链接,直至生成可执行文件。Make命令读取一个说明文件,称为Makefile,Makefile文件中描述了整个软件工程的编译规则和各个文件之间的依赖关系。Makefile就像一个Shell脚本,其中可以执行操作系统的命令,它带来的好处就是能够实现“自动化编译”,一旦写好代码,只要一个Make命令,就可以完成自动编译,极大地提高了软件开发的效率。
1.Make命令
Make是一个命令,是一个解释Makefile中指令的命令,一般大多数IDE都有这个命令,使用Make命令可以使重新编译的次数达到最小化。执行Make命令时,需要一个Makefile文件,以告诉Make命令怎样编译和链接程序。
(1)Make命令的语法
make [选项][目标][宏定义]选项含义如下:
-d:显示调试信息(debug)。
-f:指定Makefile文件。
-h:显示所有选项的简要说明(help)。
-n:非执行模式,输出所有执行命令,但并不执行。
-s:沉默模式,在执行之前不输出相应的命令行信息。
可以使用-h选项来了解更多的选项。
(2)Make命令工作方式
在默认方式下,就是只能输入Make命令。Make命令会在当前目录下找名字为Makefile或makefile的文件。
2.Makefile文件
什么是Makefile?很多Windows程序员都不知道这个文件,因为Windows的IDE可以做相应的工作。在UNIX下编译软件,就要自己写Makefile文件了,会不会写Makefile文件,从一个侧面说明了一个程序员是否具备完成大型工程的能力。
(1)为什么用Makefile文件
Makefile文件关系到整个工程的编译规则。在软件开发中,Make通常被视为一种软件构建工具。该工具主要经由读取一种名为makefile或Makefile的文件来实现软件的自动化建构。它会通过被称之为“target”概念来检查相关文件之间的依赖关系,这种依赖关系的检查系统非常简单,主要通过对比文件的修改时间来实现。在大多数情况下,Makefile主要用它来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。
(2)Makefile文件的编写规则
当Make命令不带选项运行时,它从Makefile文件中读取指定规则,当指定的规则不同于其他文件时,就要运行带有-f选项的Make命令了。
make.fray.Makefile make –f make.fray.MakefileMakefile文件的编写规则如下:
target...:prerequisites...
command
...
...target是一个目标文件,可以是ObjectFile,也可以是执行文件,还可以是一个标签(Label)。prerequisites是要生成的那个target所需要的文件或目标。command是make需要执行的命令,它可以是任意的Shell命令。重要提示:命令列表中的每个命令必须要以<Tab>字符开始。
这是一个文件的依赖关系,也就是说target表示的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。或者说,prerequisites中如果有一个以上的文件比target文件新,command所定义的命令就会被执行。这就是Makefile文件的规则,也就是Makefile文件最核心的内容。
【实例8-7】Make、Makefile、GCC综合实例
【步骤1】建立一个放置头文件的库函数address.h以备调用,代码如下:
vim address.h//建立头文件库函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//放置一些常用的C语言头文件
void func();//【步骤2】建立子函数,源文件取名address.c,代码如下:
vim address.c
#include "address.h"//调用头文件库函数
void func()
{
printf("hi hello world\n");
}【步骤3】建立主函数main.c,代码如下:
vim main.c
#include "address.h"//同名文件夹,故使用双引号,直接调用
int main()
{
printf("hello\n");
func();//调用函数
return0;
}【步骤4】建立makefile文件,并进行编译处理,代码如下:
//在终端上输入并建立makefile文件,将所有的编译命令放入其中,必须用vim编译器
vim makefile
//address为最终执行的文件名,冒号后是生成的所有可执行文件(.o文件)
address:main.o address.o
gcc main.o address.o -o address
main.o:
gcc -c main.c -o main.o
address.o:address.c address.h
gcc -c -o address.oaddress.caddress.c为实现功能函数,address.h为头文件的库函数,main.c为主函数,makefile为放置所有编译命令的函数
【步骤5】完成上述工作后,在终端中执行下列操作,代码如下:
[root@localhost01]#make clean //清空所有.o文件
rm –rf *.o
[root@localhost01]#ls //查看所有文件
//可见文件夹中没有.o文件
address address.c address.h func.c main.c makefile
[root@localhost01]#make //输入make进行编译
gcc -c main.c –o main.o
gcc –c –o address.o address.c
gcc main.o address.o -o address //成功编译出.o可执行文件
[root@localhost01]#ls
address address.c address.h address.o func.c main.c main.o makefile //查看,编译成功
[root@localhost01]# ./address //直接运行(./加上要编译文件的名字)
hello
hi hello world//运行成功8.5.3调试程序
任何一个程序员都无法宣称他编写的程序是完全正确的。几乎每一个稍微复杂一点的程序必须经过反复的调试、修改,最终才能完成。程序的调试在编程中是一项重要的技术。下面以VisualC++6.0工具调试C程序为例来介绍程序的调试过程。
1.程序的常见错误
程序中常见的错误有编译错误、运行时错误和逻辑错误。
编译错误是指程序在编译过程中出现的错误,由于编写代码不正确而产生非法使用或丢失关键字、遗漏某些必要的标点符号、函数调用缺少参数或传递不匹配的参数等错误。如for循环关键字写错,代码如下:
fore(int i=0;i<10;i++); //关键字fore书写错误运行错误是指应用程序在运行期间执行了非法操作或某些操作失败,如打开的文件未找到、磁盘空间不足、网络连接断开、除法中除数为0等。
逻辑错误是指应用程序未按照预期的方式运行时所产生的错误。一般来讲,逻辑错误不是语法层面的错误,应用程序可以执行,但是得不到正确的结果。
2.调试的类别
调试的方法分为静态调试和动态调试。
程序的静态调试就是在程序编写完以后,由人代替计算机,对程序进行仔细检查,主要检查程序中的语法规则和逻辑结构的正确性。实践证明,有很大一部分错误可以通过静态调试来发现。通过静态调试,可以大大缩短上机调试的时间,提高上机调试的效率。
动态调试就是上机调试,它贯穿在编译、链接和运行的整个过程,根据程序编译、链接和运行时计算机给出的错误信息进行程序调试。程序调试中最常用的方法是初步动态调试,即通过分段隔离、设置断点、跟踪打印进行程序的调试。
(1)分段隔离
如果程序代码量比较大,可以把程序进行分段隔离,在运行前把一个函数或一段代码进行注释,再运行程序。
(2)设置断点
断点是调试应用程序时经常使用的一种方法,调试前必须在代码中插入断点。
在VisualC++6.0工具中,使用F9键设置断点,断点设置在怀疑有问题的函数处,红点即断点,按F10键单步执行程序进行调试。
(3)跟踪打印
有时可能需要查看程序内部一些变量的值,但是又不希望中断程序的执行。例如,调试一个网络协议栈,另一个程序可能在接收数据包,想查看数据包的格式,但如果中断程序的执行,会导致后续的数据包丢失。这种情况下,一般的做法就是在源代码里加一些日志记录代码,这样可以将一些变量的值记录下来,以便后续分析。如果日志在产品发布以后还需要,在源代码里加入这些日志代码是一个好办法,但是如果只是想临时查看一些变量的值,就需要通过监视断点来实现跟踪打印。比如VisualC++6.0工具的监视断点就做到在不修改程序源代码的前提下,在调试器窗口中打印一些变量的值。
8.5.4运行程序
程序调试、编译成功后,就可以运行程序了。在VisualC++6.0工具中运行简易计算器项目,单击“!”按钮,编译、链接成功,如图8-5-5所示。
编译、链接成功后,生成了“.exe”执行文件,弹出运行窗口,如图8-5-6所示,输入两个数,就可以进行加减乘除的计算了。
本章小结
本章以简易计算器项目为引导,介绍了软件开发过程、C/C++程序开发工具安装及应用、程序流程图设计、程序编写以及程序编译、调试及运行,重点介绍了编译命令Make和Makefile文件的应用。通过本章的学习,读者应能读懂程序代码,并可以编译、调试和运行代码。
本章习题
一、单项选择题
1.C语言源程序文件经过C编译程序编译、链接之后生成的文件后缀名为( )。
A.c
B.obj
C.exe
D.bas
2.下列数据中,不合法的C语言实型数据是( )。
A. 0.123
B.345e3
C.3.1e2.5
D.789.000
3.以下程序输出结果为( )。
#include <stdio.h>
void main()
{int x=10,y=10;
printf("%d%d\n",x--,y--)
}
A.1010
B.99
C.910
D.109
4.设有如下定义:
int x=10,y=3,z;
则语句
printf(“%d\n”,z=(x%y%,x/y);
的输出结果是( )。
A.1
B.0
C.4
D.3
5.GCC将C文件编译成( )。
A.目标文件
B.汇编文件
C.源文件
D.可执行文件
6.Make命令是( )。
A.翻译工具
B.汇编工具
C.编译工具
D.执行工具
二、多项选择题
1.下列( )是程序调试的方法。
A.设置断点
B.分段隔离
C.跟踪打印
D.注释
2.调试的方法分为( )。
A.静态调试
B.动态调试
C.分段调试
D.响应式调试
三、判断题
1.预处理命令前面必须加一个“#”号。( )
2.continue不是结束本次循环,而是终止整个循环的执行。( )
3.函数的实参传递到形参有两种方式,分别为值传递和地址传递。( )
学员评价