?7.1 程序的模块化结构
7.2 模块的组装
7.2.1 文件包含与头文件的使用
7.2.2 模块间的连接
7.2.4 条件编译
宏定义第七章 实用程序设计技巧
7.1 程序的模块化结构程序设计是一种工程性的工作,通过对任务进行分析和功能模块分解,将大任务分解为若干子任务,子任务分别进行设计之后,再进行组合,合并为功能强大而复杂的一个整体。
7.1.1 软件工程的思想系统设计分四方面内容:
体系结构设计
模块设计
数据结构与算法设计
用户界面设计
7.1.2 模块设计在设计好软件的体系结构后,已经在宏观上明确了各个模块应具有什么功能,应放在体系结构的哪个位置。保持“功能独立”是模块化设计的基本原则。
因为“功能独立”的模块可以降低开发、测试、维护等阶段的代价。
功能独立并不意味着模块之间保持绝对的孤立。
一个系统要完成某项任务,需要各个模块相互配合才能实现,此时模块之间就要进行信息交流。
在设计一个模块时不仅要考虑“这个模块就该提供什么样的功能”,还要考虑“这个模块应该怎样与其它模块交流信息”。
7.1 程序的模块化结构
7.1 程序的模块化结构模块化设计时通常是将一个大型的程序自上向下的进行功能分解,分成若干个子模块,每个模块对应了一个功能,有自己的界面,有相关的操作,完成独立的功能。各个模块可以分别由不同的人员编写和调试,最后将不同的模块组装成一个完整的程序。
评价模块设计优劣的三个特征因素:
信息隐藏、内聚与耦合、封闭 --开放性在 C语言中,用函数实现功能模块的定义,程序的功能可以通过函数之间的调用实现。
一个完整工程项目的 C程序可能由多个源程序文件组成,每一个文件中又可以包含多个函数。
7.1 程序的模块化结构程序文件 1
main函数文件 2
函数 f1
函数 f2
文件 3
函数 g1
函数 g2
函数 g3
7.1 程序的模块化结构
7.1.3 使用模块化方法开发程序的好处
模块可以独立于解决方案的其他部分进行单独的编写和测试,对于大型项目各个模块的开发可以同步进行
模块是解决方案的一小部分,单独测试起来更加容易
经过仔细的测试之后,不需要重新测试就可以将模块直接应用于新的问题解决方案中
使用模块通常可缩短程序的长度,使程序更具可读性
模块的使用促使采用抽象的概念,从而允许程序员把细节“隐藏”于模块之中,这使我们能够向使用系统库函数一样使用模块,而无须考虑具体的细节
7.2 模块的组装在用模块化方法开发程序时,一个完整工程项目的 C程序通常会由多个源程序文件组成,每一个文件中又可以包含多个函数。
模块的组装既涉及到多个源文件的连接问题,也涉及到实现具体模块的函数之间的连接调用关系。
多个源程序文件之间的连接可用 #include命令一、编译预处理命令
1、编译预处理命令:在程序编译之前对源程序进行的工作,它不属于程序中的可执行语句,不占用程序的运行时间。
2、预处理命令格式预处理命令均以,#,打头,单独占一行,行末不加分号预处理命令可以出现在程序的任何位置,其作用域从出现点到所在源程序的末尾,一般将预处理命令放在程序的起始位置3、预处理命令主要有,宏定义、文件包含、条件编译
7.2.1 文件包含与头文件的使用
2、文件包含格式
① #include <文件名 >
< >表示直接到指定的标准包含文件目录中寻找包含文件,对于 #include <stdio.h>,如果 bc31在 c盘上,就是到 c:\bc31\include目录中去寻找 stdio.h这个文件通常使用系统提供的标准头文件时用 < >
二、文件包含
1、文件包含,一个源文件可以将另一个源文件的全部内容包含进来,文件包含是一个编译预处理命令。
7.2.1 文件包含与头文件的使用使用 #include <stdio.h>,就是将此头文件中的全部内容包含到现有文件中。使用了文件包含命令,就可以使用系统库函数功能,不必自己亲手编写。
② #include,文件名,
使用时系统先在当前目录下 (即 bc31的 bin目录 )寻找被包含文件,如果找不到,系统再到标准包含文件目录下寻找 (即 bc31的 include目录 )
通常使用用户自己编写的文件时用,,
使用,,时,文件名可以包含有文件路径,这时系统将到指定的文件目录下去找被包含的文件例,# include,d:\ teach\ include\ compute,h”
7.2.1 文件包含与头文件的使用
3、说明
(1) 文件包含的作用就是在编译预处理时将被包含文件的全部内容复制并插入到 #include命令处
(2) 一个 include命令只能指定一个被包含文件,
如果有 n个被包含文件则需要用 n个 include命令,
且一个命令占一行
(3) 使用文件包含时,在被包含文件中绝对不能含有
main函数
(4) 文件包含可以嵌套使用
(5) 被包含文件中的全局变量在其所在的文件中有效
7.2.1 文件包含与头文件的使用例 文件 file1.cpp
#include <stdio.h>
#include,file2.cpp”
#include,file3.cpp”
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d\n”,s1,s2) ;
}
文件 file2.cpp
int max (int a,int b)
{ return(a>b? a,b ) ; }
文件 file3.cpp
int min (int a,int b)
{ return(a<b? a,b ) ; }
文件 stdio.h的内容
int min (int a,int b)
{ return(a<b? a,b ) ; }
int max (int a,int b)
{ return(a>b? a,b ) ; }
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y ) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d \n”,s1,s2) ;
}
注,文件 file1.cpp,file2.cpp,
file3.cpp都在目录 c:\bc31\bin 下文件 stdio.h的内容
int min (int a,int b)
{ return(a<b? a,b ) ; }
int max (int a,int b)
{ return(a>b? a,b ) ; }
例 文件 file1.cpp
#include <stdio.h>
#include,file3.cpp”
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d\n”,s1,s2) ;
}
文件 file2.cpp
int max (int a,int b)
{ return(a>b? a,b ) ; }
文件 file3.cpp
#include,fill2.cpp”
int min (int a,int b)
{ return(a<b? a,b ) ; }
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y ) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d \n”,s1,s2) ;
}
7.2.2 模块间的连接模块函数间的连接可以分为短暂连接和长久连接。
短暂连接 是指只有在模块函数被调用时通过函数参数的传递或函数返回值和其他模块发生的连接关系,
这是模块连接所采用最普遍的连接方式,也叫做临时连接,当函数调用结束后连接关系就消失了。
长久连接 是通过全局变量或静态存储的变量和其他模块之间产生的连接关系,这种连接是长期存在的,
当函数一次调用结束后连接仍然存在,只有当整个程序运行全部结束时连接关系才取消。
模块间的短暂连接以三种形式存在:
普通参数,函数调用时主调函数以实际参数赋给函数的形式参数
返回值,函数调用结束后,返回指定的值给主调函数
指针参数,指针形式的参数使得函数调用时,函数通过指针直接访问主调函数中变量的内存单元,以此取得变量值或将函数处理结果放到指定的内存单元中
7.2.2 模块间的连接
模块间的长久连接以如下两种形式表现:
全局变量,全局变量又称为外部变量,是定义在函数外部的变量,可以被若干个函数模块所访问,每一个程序模块访问全局变量改变其值后,全局变量的值就发生了永久的改变,函数调用结束后这种值的改变仍然生效。
static静态存储类,函数内部用 static声明的静态变量是存储在系统的静态存储区的,其生存期较长,不随函数的调用结束而释放,而是在整个程序运行期间一直都保持有效。
7.2.2 模块间的连接
7.2.4 条件编译
1、条件编译,
对源程序中的一部分内容在满足一定条件时才进行编译,即对一部分内容指定编译的条件
2、条件编译的形式
(1) #ifdef 标识符程序段 1
#else
程序段 2
#endif
作用,
指定的标识符已经被 #define命令定义过,则在程序编译阶段只编译程序段 1,否则只编译程序段 2
注,#else部分可以没有
(2) #ifndef 标识符程序段 1
#else
程序段 2
#endif
(3) #if 表达式程序段 1
#else
程序段 2
#endif
作用,
当表达式的值为真时编译程序段 1
否则编译程序段 2
作用,
若指定的标识符没有被 #define
命令定义过,则编译程序段 1,
否则编译程序段 2
7.2.4 条件编译宏定义一,不带参数的宏定义
1,格式,#define 标识符 字符串
2,说明,
(1) 标识符也称宏名,一般用大写字母表示
(2) 预处理时将程序中所有的宏名用宏体替换,该过程称“宏展开” ; 但在程序中用,,括起来的字符串中,
即使有的字符串与宏名相同,也不进行替换#define SIZE 20
void main ( )
{ int x ;
x = SIZE+15 ;
printf(,SIZE=%d \n”,SIZE ) ;
}
称为宏体输出结果,
SIZE=35
称为宏名宏定义
(3) 宏定义只是一种简单的字符替代,不进行语法检查若将 #define SIZE 20 中 20的 零 写成英文字母 ‘ o’,
程序中的 x = SIZE+15 ; 会替换为 x = 2o+15;
这时才会发现错误
(4)每条宏命令要单独占一行,且行末不加分号
(5) #define命令出现在函数的外部,宏名的有效范围为定义命令之后到本文件结束
(6) 可以用 #undef 命令终止宏定义的作用域
(7) 宏定义可以嵌套使用例 #define L 10
#define W 20
#define S L*W
(8) 宏定义与变量定义不同,
它只作字符替换,不分配内存空间
3,使用宏替换的优点,提高程序的可读性,易于修改宏定义二,带参数的宏定义
1,格式,#define 宏名 ( 形参表 ) 字符串
2,说明
(1) 宏定义时宏名与括号之间没有空格,
若有空格则会把空格后的所有字符都看成是宏体
(2) 带参数的宏在替换时,不仅宏名被宏体替换,
同时形参被实参替换
#define PI 3.14159
#define S(r) PI*r*r
void main ( )
{ float a,area ;
a = 3.6 ;
area = S(a);
printf(,%f \n”,area) ;
}
宏替换,
area = 3.14159*a*a ;
宏定义
(3) 建议带运算符的宏体和形参要用 ( ) 括起来
void main ( )
{ float a,b,area ;
a = 3.6 ;
b = 1.2 ;
area = S(a+b);
printf(,%f \n”,area) ;
}
#define PI 3.14159
#define S(r) PI *r *r
宏替换,
area = 3.14159*a+b*a+b ;
宏替换,
area = 3.14159*(a+b)*(a+b) ;
#define S(r) PI *(r)*(r)
宏定义例,
① #define SQUARE(x) x*x
② #define SQUARE(x) (x)*(x)
③ #define SQUARE(x) ((x)*(x))
若 a=2.7/SQUARE(3.0)
宏展开,
① a=2.7/3.0*3.0
② a=2.7/(3.0)*(3.0)
③ a=2.7/((3.0)*(3.0))
若 a=SQUARE(n+1)
宏展开,
① a=n+1*n+1
② a=(n+1)*(n+1)
③ a=((n+1)*(n+1))
出错 出错宏定义
3,带参数的宏与函数的区别
(1) 函数调用时,先求出实参表达式的值,再代入形参带参数的宏定义只是进行简单的字符替换
(2) 函数调用是在程序运行时处理,分配临时的内存单元宏展开是在编译时进行的,在展开时不分配内存单元
(3) 对函数的形参和实参都要定义类型,且要求一致宏不存在类型问题,宏名无类型,其参数也无类型
(4) 调用函数只可得到一个返回值,
使用宏可以设法得到几个结果
(5) 函数调用不会使源程序变长,宏展开会使源程序增长
(6) 函数调用占用运行时间宏展开不占运行时间,只占编译时间
7.2 模块的组装
7.2.1 文件包含与头文件的使用
7.2.2 模块间的连接
7.2.4 条件编译
宏定义第七章 实用程序设计技巧
7.1 程序的模块化结构程序设计是一种工程性的工作,通过对任务进行分析和功能模块分解,将大任务分解为若干子任务,子任务分别进行设计之后,再进行组合,合并为功能强大而复杂的一个整体。
7.1.1 软件工程的思想系统设计分四方面内容:
体系结构设计
模块设计
数据结构与算法设计
用户界面设计
7.1.2 模块设计在设计好软件的体系结构后,已经在宏观上明确了各个模块应具有什么功能,应放在体系结构的哪个位置。保持“功能独立”是模块化设计的基本原则。
因为“功能独立”的模块可以降低开发、测试、维护等阶段的代价。
功能独立并不意味着模块之间保持绝对的孤立。
一个系统要完成某项任务,需要各个模块相互配合才能实现,此时模块之间就要进行信息交流。
在设计一个模块时不仅要考虑“这个模块就该提供什么样的功能”,还要考虑“这个模块应该怎样与其它模块交流信息”。
7.1 程序的模块化结构
7.1 程序的模块化结构模块化设计时通常是将一个大型的程序自上向下的进行功能分解,分成若干个子模块,每个模块对应了一个功能,有自己的界面,有相关的操作,完成独立的功能。各个模块可以分别由不同的人员编写和调试,最后将不同的模块组装成一个完整的程序。
评价模块设计优劣的三个特征因素:
信息隐藏、内聚与耦合、封闭 --开放性在 C语言中,用函数实现功能模块的定义,程序的功能可以通过函数之间的调用实现。
一个完整工程项目的 C程序可能由多个源程序文件组成,每一个文件中又可以包含多个函数。
7.1 程序的模块化结构程序文件 1
main函数文件 2
函数 f1
函数 f2
文件 3
函数 g1
函数 g2
函数 g3
7.1 程序的模块化结构
7.1.3 使用模块化方法开发程序的好处
模块可以独立于解决方案的其他部分进行单独的编写和测试,对于大型项目各个模块的开发可以同步进行
模块是解决方案的一小部分,单独测试起来更加容易
经过仔细的测试之后,不需要重新测试就可以将模块直接应用于新的问题解决方案中
使用模块通常可缩短程序的长度,使程序更具可读性
模块的使用促使采用抽象的概念,从而允许程序员把细节“隐藏”于模块之中,这使我们能够向使用系统库函数一样使用模块,而无须考虑具体的细节
7.2 模块的组装在用模块化方法开发程序时,一个完整工程项目的 C程序通常会由多个源程序文件组成,每一个文件中又可以包含多个函数。
模块的组装既涉及到多个源文件的连接问题,也涉及到实现具体模块的函数之间的连接调用关系。
多个源程序文件之间的连接可用 #include命令一、编译预处理命令
1、编译预处理命令:在程序编译之前对源程序进行的工作,它不属于程序中的可执行语句,不占用程序的运行时间。
2、预处理命令格式预处理命令均以,#,打头,单独占一行,行末不加分号预处理命令可以出现在程序的任何位置,其作用域从出现点到所在源程序的末尾,一般将预处理命令放在程序的起始位置3、预处理命令主要有,宏定义、文件包含、条件编译
7.2.1 文件包含与头文件的使用
2、文件包含格式
① #include <文件名 >
< >表示直接到指定的标准包含文件目录中寻找包含文件,对于 #include <stdio.h>,如果 bc31在 c盘上,就是到 c:\bc31\include目录中去寻找 stdio.h这个文件通常使用系统提供的标准头文件时用 < >
二、文件包含
1、文件包含,一个源文件可以将另一个源文件的全部内容包含进来,文件包含是一个编译预处理命令。
7.2.1 文件包含与头文件的使用使用 #include <stdio.h>,就是将此头文件中的全部内容包含到现有文件中。使用了文件包含命令,就可以使用系统库函数功能,不必自己亲手编写。
② #include,文件名,
使用时系统先在当前目录下 (即 bc31的 bin目录 )寻找被包含文件,如果找不到,系统再到标准包含文件目录下寻找 (即 bc31的 include目录 )
通常使用用户自己编写的文件时用,,
使用,,时,文件名可以包含有文件路径,这时系统将到指定的文件目录下去找被包含的文件例,# include,d:\ teach\ include\ compute,h”
7.2.1 文件包含与头文件的使用
3、说明
(1) 文件包含的作用就是在编译预处理时将被包含文件的全部内容复制并插入到 #include命令处
(2) 一个 include命令只能指定一个被包含文件,
如果有 n个被包含文件则需要用 n个 include命令,
且一个命令占一行
(3) 使用文件包含时,在被包含文件中绝对不能含有
main函数
(4) 文件包含可以嵌套使用
(5) 被包含文件中的全局变量在其所在的文件中有效
7.2.1 文件包含与头文件的使用例 文件 file1.cpp
#include <stdio.h>
#include,file2.cpp”
#include,file3.cpp”
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d\n”,s1,s2) ;
}
文件 file2.cpp
int max (int a,int b)
{ return(a>b? a,b ) ; }
文件 file3.cpp
int min (int a,int b)
{ return(a<b? a,b ) ; }
文件 stdio.h的内容
int min (int a,int b)
{ return(a<b? a,b ) ; }
int max (int a,int b)
{ return(a>b? a,b ) ; }
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y ) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d \n”,s1,s2) ;
}
注,文件 file1.cpp,file2.cpp,
file3.cpp都在目录 c:\bc31\bin 下文件 stdio.h的内容
int min (int a,int b)
{ return(a<b? a,b ) ; }
int max (int a,int b)
{ return(a>b? a,b ) ; }
例 文件 file1.cpp
#include <stdio.h>
#include,file3.cpp”
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d\n”,s1,s2) ;
}
文件 file2.cpp
int max (int a,int b)
{ return(a>b? a,b ) ; }
文件 file3.cpp
#include,fill2.cpp”
int min (int a,int b)
{ return(a<b? a,b ) ; }
void main ( )
{ int x,y,s1,s2 ;
scanf(“%d%d”,&x,&y ) ;
s1 = max ( x,y ) ;
s2 = min ( x,y ) ;
printf(“%d,%d \n”,s1,s2) ;
}
7.2.2 模块间的连接模块函数间的连接可以分为短暂连接和长久连接。
短暂连接 是指只有在模块函数被调用时通过函数参数的传递或函数返回值和其他模块发生的连接关系,
这是模块连接所采用最普遍的连接方式,也叫做临时连接,当函数调用结束后连接关系就消失了。
长久连接 是通过全局变量或静态存储的变量和其他模块之间产生的连接关系,这种连接是长期存在的,
当函数一次调用结束后连接仍然存在,只有当整个程序运行全部结束时连接关系才取消。
模块间的短暂连接以三种形式存在:
普通参数,函数调用时主调函数以实际参数赋给函数的形式参数
返回值,函数调用结束后,返回指定的值给主调函数
指针参数,指针形式的参数使得函数调用时,函数通过指针直接访问主调函数中变量的内存单元,以此取得变量值或将函数处理结果放到指定的内存单元中
7.2.2 模块间的连接
模块间的长久连接以如下两种形式表现:
全局变量,全局变量又称为外部变量,是定义在函数外部的变量,可以被若干个函数模块所访问,每一个程序模块访问全局变量改变其值后,全局变量的值就发生了永久的改变,函数调用结束后这种值的改变仍然生效。
static静态存储类,函数内部用 static声明的静态变量是存储在系统的静态存储区的,其生存期较长,不随函数的调用结束而释放,而是在整个程序运行期间一直都保持有效。
7.2.2 模块间的连接
7.2.4 条件编译
1、条件编译,
对源程序中的一部分内容在满足一定条件时才进行编译,即对一部分内容指定编译的条件
2、条件编译的形式
(1) #ifdef 标识符程序段 1
#else
程序段 2
#endif
作用,
指定的标识符已经被 #define命令定义过,则在程序编译阶段只编译程序段 1,否则只编译程序段 2
注,#else部分可以没有
(2) #ifndef 标识符程序段 1
#else
程序段 2
#endif
(3) #if 表达式程序段 1
#else
程序段 2
#endif
作用,
当表达式的值为真时编译程序段 1
否则编译程序段 2
作用,
若指定的标识符没有被 #define
命令定义过,则编译程序段 1,
否则编译程序段 2
7.2.4 条件编译宏定义一,不带参数的宏定义
1,格式,#define 标识符 字符串
2,说明,
(1) 标识符也称宏名,一般用大写字母表示
(2) 预处理时将程序中所有的宏名用宏体替换,该过程称“宏展开” ; 但在程序中用,,括起来的字符串中,
即使有的字符串与宏名相同,也不进行替换#define SIZE 20
void main ( )
{ int x ;
x = SIZE+15 ;
printf(,SIZE=%d \n”,SIZE ) ;
}
称为宏体输出结果,
SIZE=35
称为宏名宏定义
(3) 宏定义只是一种简单的字符替代,不进行语法检查若将 #define SIZE 20 中 20的 零 写成英文字母 ‘ o’,
程序中的 x = SIZE+15 ; 会替换为 x = 2o+15;
这时才会发现错误
(4)每条宏命令要单独占一行,且行末不加分号
(5) #define命令出现在函数的外部,宏名的有效范围为定义命令之后到本文件结束
(6) 可以用 #undef 命令终止宏定义的作用域
(7) 宏定义可以嵌套使用例 #define L 10
#define W 20
#define S L*W
(8) 宏定义与变量定义不同,
它只作字符替换,不分配内存空间
3,使用宏替换的优点,提高程序的可读性,易于修改宏定义二,带参数的宏定义
1,格式,#define 宏名 ( 形参表 ) 字符串
2,说明
(1) 宏定义时宏名与括号之间没有空格,
若有空格则会把空格后的所有字符都看成是宏体
(2) 带参数的宏在替换时,不仅宏名被宏体替换,
同时形参被实参替换
#define PI 3.14159
#define S(r) PI*r*r
void main ( )
{ float a,area ;
a = 3.6 ;
area = S(a);
printf(,%f \n”,area) ;
}
宏替换,
area = 3.14159*a*a ;
宏定义
(3) 建议带运算符的宏体和形参要用 ( ) 括起来
void main ( )
{ float a,b,area ;
a = 3.6 ;
b = 1.2 ;
area = S(a+b);
printf(,%f \n”,area) ;
}
#define PI 3.14159
#define S(r) PI *r *r
宏替换,
area = 3.14159*a+b*a+b ;
宏替换,
area = 3.14159*(a+b)*(a+b) ;
#define S(r) PI *(r)*(r)
宏定义例,
① #define SQUARE(x) x*x
② #define SQUARE(x) (x)*(x)
③ #define SQUARE(x) ((x)*(x))
若 a=2.7/SQUARE(3.0)
宏展开,
① a=2.7/3.0*3.0
② a=2.7/(3.0)*(3.0)
③ a=2.7/((3.0)*(3.0))
若 a=SQUARE(n+1)
宏展开,
① a=n+1*n+1
② a=(n+1)*(n+1)
③ a=((n+1)*(n+1))
出错 出错宏定义
3,带参数的宏与函数的区别
(1) 函数调用时,先求出实参表达式的值,再代入形参带参数的宏定义只是进行简单的字符替换
(2) 函数调用是在程序运行时处理,分配临时的内存单元宏展开是在编译时进行的,在展开时不分配内存单元
(3) 对函数的形参和实参都要定义类型,且要求一致宏不存在类型问题,宏名无类型,其参数也无类型
(4) 调用函数只可得到一个返回值,
使用宏可以设法得到几个结果
(5) 函数调用不会使源程序变长,宏展开会使源程序增长
(6) 函数调用占用运行时间宏展开不占运行时间,只占编译时间