1、第四章 功能(过程)抽象函数,主讲人:侯海良通信与控制工程系,本章内容,基于过程抽象的程序设计 子程序的概念 C+的函数 变量的局部性和变量的生存期 标识符的作用域 递归函数 内联函数 函数名重载 条件编译程序调试与多环境程序编制 标准库函数,基于过程抽象的程序设计,人们在设计一个复杂的程序时,经常会用到功能分解和复合两种手段: 功能分解:在进行程序设计时,首先把程序的功能分解成若干子功能,每个子功能又可以分解成若干子功能,等等,从而形成了一种自顶向下(top-down)、逐步精化(step-wise)的设计过程。 功能复合:把已有的(子)功能逐步组合成更大的(子)功能,从而形成一种自底向上(
2、bottom-up)的设计过程。 过程抽象:一个功能的使用者只需要知道相应功能是什么(what to do),而不必知道它是如何做(how to do)的。,子程序,子程序是取了名的一段程序代码,在程序中通过名字来使用(调用)它们。 子程序的作用: 减少重复代码,节省劳动力 实现过程抽象(功能抽象) 封装和信息隐藏的作用,子程序之间的数据传递,一个子程序所需要的数据往往要从调用者(也是一个子程序)那里获得,计算结果也需要返回给调用者。 子程序之间的数据传递方式可以通过: 全局变量:所有子程序都能访问到的变量。(不好) 参数:形式参数(形参)和实在参数(实参)。 值传递:把实参的值复制一份给形参
3、。 地址或引用传递:把实参的地址传给形参。 返回值机制:返回计算结果。,C+函数,函数是C+提供的用于实现子程序的语言成分。 函数的定义:() 描述了函数返回值的类型, 可以为任意的C+数据类型。 当返回值类型为void时,它表示函数没有返回值。 用于标识函数的名字,用标识符表示。 描述函数的形式参数,由零个、一个或多个形参说明(用逗号隔开)构成,形参说明的格式为:, 为一个,用于实现相应函数的功能。 函数体内可以包含return语句,格式为: return ; return; 当函数体执行到return语句时,函数立即返回到调用者。如果有返回值,则把返回值带回给调用者。 如果return中的
4、的类型与函数 不一致,则进行隐式类型转换,基本原则为:把转成 。 注意:在函数体中不能用goto语句转出函数体。,例1:用函数实现阶乘,int factorial(int n) /求n的阶乘 int i,f=1;for (i=2; i=n; i+)f *= i;return f; ,double power(double x, int n) /求x的n次幂 if (x = 0) return 0;double product=1.0;if (n = 0)while (n 0) product *= x;n-;elsewhile (n 0) product /= x;n+;return prod
5、uct; ,例2:编写求xn的函数,函数main,每个C+程序都要定义一个名字为main的函数,C+程序的执行是从main开始的。对于函数main,其返回值类型为int,例如: int main() . return -1;return 0; 一般情况下,返回0表示程序正常结束;返回负数(如1)表示程序非正常结束。,函数的调用,对于定义的一个函数,必须要调用它,它的函数体才会执行。 除了函数main外,程序中对其它函数的调用都是从main开始的。main一般是由操作系统来调用。 函数调用的格式如下: () 由零个、一个或多个表达式构成(逗号分割) 实参的个数和类型应与相应函数的形参相同。类型如
6、果不同,编译器会试图进行隐式转换,转换规则是把实参类型转换成形参类型 。 注意:不能用goto语句从函数外转入函数体,函数调用的例子, int main() int x;cout x;cout “Factorial of “ x “ is “ factorial(x) /调用阶乘函数 endl;return 0; , int main() double a;int b;cout a b;cout a “的“ b “次方是:“ power(a,b) endl;return 0; ,函数调用的执行过程,计算实参的值(对于多个实参,C+没有规定计算次序); 把实参分别传递给被调用函数的形参; 执行函
7、数体; 函数体中执行return语句返回函数调用点,调用点获得返回值(如果有返回值)并执行调用之后的操作。 可以把有返回值的函数调用作为操作数放在表达式中参加运算 :x+power(x,y)*z,函数声明,程序中调用的所有函数都要有定义。 如果函数定义在其它文件(如:C+的标准库)中或定义在本源文件中使用点之后,则在调用前需要对被调用的函数进行声明。 函数声明的格式如下:(); /函数原型 或 extern (); 在函数声明中,中可以只列出形参的类型而不写形参名,/file2.cpp int g(int i) /定义 extern int x,y; /声明int z; /定义z = x +
8、y;return z+i; ,/file1.cpp int x=0; /定义 int main() /定义 extern void f(); /声明extern int g(int); /声明extern int y; /声明y = x + 2;f(); /调用y = g(x); /调用return 0; int y=0; /定义 void f() /定义 x = y + 1; ,函数声明的作用是什么?,例5:用函数实现求小于n的所有素数。,#include #include using namespace std; bool is_prime(int n);/函数声明 void print_
9、prime(int n, int count);/函数声明 int main() int i,n,count=1;cout n; /从键盘输入一个正整数if (n 2) return -1;cout 2 “,“; /输出第一个素数for (i=3; in; i+=2) if (is_prime(i) count+;print_prime(i,count);cout endl;return 0; ,bool is_prime(int n) int i,j,k=sqrt(n);for (i=2, j=k; i=j; i+)if (n%i = 0) return false;return true;
10、 void print_prime(int n, int count) cout n ,;if (count % 6 = 0) cout endl; ,函数的参数传递,C+提供了两种参数传递机制: 值传递 把实参的值赋值给形参。 地址或引用传递 把实参的地址赋值给形参。 C+默认的参数传递方式是值传递。,值传递,在函数调用时,采用类似变量初始化的形式把实参的值传给形参。 函数执行过程中,通过形参获得实参的值, 函数体中对形参值的改变不会影响相应实参的值。,值参数传递的例子,/函数main调用函数power计算ab #include using namespace std; double pow
11、er(double x, int n); int main() double a=3.0,c;int b=4;c = power(a,b);cout a “,“ b “,“ c endl;return 0; ,double power(double x, int n) if (x = 0) return 0;double product=1.0;if (n = 0)while (n 0) product *= x;n-;elsewhile (n 0) product /= x;n+;return product; ,执行main时,产生三个变量(分配内存空间)a、b和c:a: 3.0 b: 4
12、 c: ? 调用power函数时,又产生三个个变量x、n和product,然后分别用a、b以及1.0对它们初始化:a: 3.0 b: 4 c: ?x: 3.0 n: 4 product: 1.0 函数power中的循环结束后(函数返回前):a: 3.0 b: 4 c: ?x: 3.0 n: 0 product: 81.0 函数power返回后:a: 3.0 b: 4 c: 81.0,变量的局部性,在C+中,根据变量的定义位置,把变量分成:局部变量和全局变量。 局部变量是指在复合语句中定义的变量,它们只能在定义它们的复合语句(包括内层的复合语句)中使用。 全局变量是指在函数外部定义的变量,它们一
13、般能被程序中的所有函数使用(静态的全局变量除外)。,局部变量和全局变量的例子,int x=0; /全局变量 void f() int y=0; /局部变量x+; /OKy+; /OKa+; /Error ,int main() int a=0; /局部变量f();a+; /OKx+; /OKy+; /Error while (x10) int b=0; /局部变量a+; /OKb+; /OKx+; /OKb+; /Errorreturn 0; ,变量的生存期(存储分配),把程序运行时一个变量占有内存空间的时间段称为该变量的生存期。 静态:从程序开始执行时就进行内存空间分配,直到程序结束才收回它
14、们的空间。全局变量具有静态生存期 。 自动:内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时,它们的空间将被收回。局部变量和函数的参数一般具有自动生存期。 动态 :内存空间在程序中显式地用new操作或malloc库函数分配、用delete操作或free库函数收回。动态变量具有动态生存期。 具有静态生存期的变量,如果没有显式初始化,系统将把它们初始化成0。,存储类修饰符,在定义局部变量时,可以为它们加上存储类修饰符来显式地指出它们的生存期。 auto:使局部变量具有自动生存期。局部变量的默认存储类为auto。 static:使局部变量具有静态生存期。它
15、只在函数第一次调用时进行初始化,以后调用中不再进行初始化,它的值为上一次函数调用结束时的值。 register:使局部变量也具有自动生存期,由编译程序根据CPU寄存器的使用情况来决定是否存放在寄存器中。,void f() auto int x=0; /auto一般不写static int y=1;register int z=0;x+; y+; z+;cout x y z endl; int main() f();z+;f();return 0; ,=调用f时,输出:x=1,y=2,z=1,=调用f时,输出:x=1,y=3,z=1,Static 实例,程序实体在内存中的安排,程序运行时各种数据
16、在内存中的分配: 静态数据区用于全局变量、static存会储类的局部变量以及常量的内存分配 。(该区变量未初始化时会默认为0) 代码区用于存放程序的指令,对C+程序而言,代码区存放的是所有函数代码; 栈区用于auto存储类的局部变量、函数的形式参数以及函数调用时有关信息(如:函数返回地址等)的内存分配; 堆区用于动态变量的内存分配。,例7:编写一个能够产生随机数的函数 usingned int random() static usigned int seed=1;seed=(25173*seed+13849)%65536;return seed; ,C+程序的多模块结构,逻辑上,一个C+程序由
17、一些全局函数(区别于类定义中的成员函数)、全局常量、全局变量/对象以及类的定义构成,其中必须有且仅有一个名字为main的全局函数。函数内部可以包含形参、局部常量、局部变量/对象的定义以及语句。 物理上,可以按某种规则对构成C+程序的各个逻辑单位(全局函数、全局常量、全局变量/对象、类等)的定义进行分组,分别把它们放在若干个源文件中,构成程序模块。 程序模块是为了便于从物理上对程序进行组织、管理和理解,便于多人合作开发一个程序。 程序模块是可单独编译的程序单位。,C+模块的构成,一个C+模块一般包含两个部分: 接口(.h文件 ): 给出在本模块中定义的、提供给其它模块使用的一些程序实体(如:函数
18、、全局变量等)的声明; 实现(.cpp文件): 模块的实现给出了模块中的程序实体的定义。,在模块A中要用到模块B中定义的程序实体时,可以在A的.cpp文件中用文件包含命令(#include)把B的.h文件包含进来。 文件包含命令是一种编译预处理命令,其格式为:#include 或 #include “文件名“ 注意:(1) #include 表示在系统指定的目录下寻找指定文件 (2)#include “文件名“表示在源文件所在的目录下寻找指定文件 include命令的含义是:在编译前,用命令中的文件名所指定的文件内容替换该命令。,/file1.h extern int x; /全局变量x的声明
19、 extern double y; /全局变量y的声明 int f(); /全局函数f的声明/file1.cpp int x=1; /全局变量x的定义 double y=2.0; /全局变量y的定义 int f() /全局函数f的定义 int m; /局部变量m的定义m += x; /语句return m; ,/file2.h void g(); /全局函数g的声明/file2.cpp #include “file1.h“ /把文件file1.h中的内容包含进来 void g() /全局函数g的定义 double z; /局部变量z的定义z = y+10; /语句,/main.cpp #inc
20、lude “file1.h“ /把文件file1.h中的内容包含进来 #include “file2.h“ /把文件file2.h中的内容包含进来 int main() /全局函数main的定义 double r; /局部变量r的定义r = x+y*f(); /语句g(); /语句,标识符的作用域,在程序常量、变量、函数、类等的命名中,要区分它们需要命不同的名字,这样程序才能区分它们。但这会带来不便。如两个程序员写函数的局部变量,可能会出现命名相同。 为了对程序中的实体的名字进行管理,引进了标识符的作用域的概念。 一个定义了的标识符的有效范围(能被访问的程序段)称为该标识符的作用域。 在不同的
21、作用域中,可以用相同的标识符来标识不同的程序实体。,C+标识符的作用域,C+把标识符的作用域分成若干类,其中包括: 局部作用域 全局作用域 文件作用域 函数作用域 函数原型作用域 类作用域 名空间作用域,局部作用域,在函数定义或复合语句中、从标识符的定义点开始到函数定义或复合语句结束之间的程序段。 C+中的局部常量名、局部变量名/对象名以及函数的形参名具有局部作用域。,例:void f(int y) y; /OKx; /Errorint x;x; / OKdouble x; /Error,重名char y; /Error,重名,void g(double y) / OK double x; /
22、OKf(1); / OK,void f(int n) x+; /Error int x=0;x+;n+;. void g() x+; /Errorn+; /Error int main() int x=0;int n;cin n;f(n);,如果在一个标识符的局部作用域中包含内层复合语句,并且在该内层复合语句中定义了一个同名的不同实体,则外层定义的标识符的作用域应该是从其潜在作用域中扣除内层同名标识符的作用域之后所得到的作用域。 void f() int x; /外层x的定义. x . /外层的xwhile ( . x .) /外层的x . x . /外层的x,double x; /内层x的定
23、义. x . /内层的x. x . /外层的x ,全局作用域,在函数级定义的标识符具有全局作用域。 全局变量名/对象名、全局函数名和全局类名的作用域一般具有全局作用域,它们在整个程序中可用。 如果在某个局部作用域中定义了与某个全局标识符同名的标识符,则该全局标识符的作用域应扣掉与之同名的局部标识符的作用域。 在局部标识符的作用域中若要使用与其同名的全局标识符,则需要用全局域选择符(:)对全局标识符进行修饰(受限)。,double x; /外层x的定义 void f() int x; /内层x的定义. x . /内层的x:x . /外层的x ,作用域的综合实例,文件作用域,在全局标识符的定义中加
24、上static修饰符,则该全局标识符就成了具有文件作用域的标识符,它们只能在定义它们的源文件(模块)中使用。 C+中的关键词static有两个不同的含义。 在局部变量的定义中,static修饰符用于指定局部变量采用静态存储分配; 而在全局标识符的定义中,static修饰符用于把全局标识符的作用域改变为文件作用域。 一般情况下,具有全局作用域的标识符主要用于标识被程序各个模块共享的程序实体,而具有文件作用域的标识符用于标识在一个模块内部共享的程序实体。,/file1.cpp static int y; /文件作用域 static void f() /文件作用域 /file2.cpp extern
25、 int y; extern void f(); void g() . y . /Errorf(); /Error ,函数作用域,语句标号是唯一具有函数作用域的标识符,它们在定义它们的函数体中的任何地方都可以访问。 函数作用域与局部作用域的区别是: 函数作用域包括整个函数,而局部作用域是从定义点开始到函数定义或复合语句结束。 在函数体中,一个语句标号只能定义一次,即使是在内层的复合语句中,也不能再定义与外层相同的语句标号。,void f() goto L; /OKL: . L: . /Errorgoto L; /OK void g() goto L; /Error,名空间作用域,对于一个多文件
26、构成的程序,有时会面临一个问题:在一个源文件中要用到两个分别在另外两个源文件中定义的不同全局程序实体(如:全局函数),而这两个全局程序实体的名字相同。 例 C+提供了名空间(namespace)设施来解决上述的名冲突问题。 在一个名空间中定义的全局标识符,其作用域为该名空间。 当在一个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受限。,/file1.cppvoid f() ,/file2.cppvoid f() ,int main() f(); /file1中还是file2中的?f(); /file1中还是file2中的? ,/main.cpp,/模块1 name
27、space A int x=1;void f() ,/模块2 namespace B int x=0;void f() ,. A:x . /A中的x A:f(); /A中的f . B:x . /B中的x B:f(); /B中的f,using namespace A; . x . /A中的x f(); /A中的f . B:x . /B中的x B:f(); /B中的f,using A:f; . A:x . /A中的x f(); /A中的f . B:x . /B中的x B:f(); /B中的f,/模块3,1、,2、,3、,递归函数,函数的调用是可以嵌套的。 void h() void g() h()
28、; void f() g();. ,直接递归 void f() . f() . ,间接递归 extern void g(); void f() . g() . void g() . f() .,如果一个函数在其函数体中直接或间接地调用了自己,则该函数称为递归函数。,递归函数的作用,在程序设计中经常需要实现重复性的操作。循环为实现重复操作提供了一种途径。 实现重复操作的另一个途径是采用递归函数。 “分而治之”(Divide and Conquer)设计方法: 把一个问题分解成若干个子问题,而每个子问题的性质与原问题相同,只是在规模上比原问题要小。每个子问题的求解过程可以采用与原问题相同的方式来进
29、行。 递归函数为上述设计方法提供了一种自然、简洁的实现机制,例:求第n个fibonacci 数(递归解法),int fib(int n) if (n = 1)return 0; else if (n = 2) return 1;else return fib(n-2)+fib(n-1); ,递归函数的执行过程,/用递归函数求n! int f(int n) if (n = 0) return 1;else return n*f(n-1); ,递归条件和结束条件,在定义递归函数时,一定要对两种情况给出描述: 递归条件。指出何时进行递归调用,它描述了问题求解的一般情况,包括:分解和综合过程。 结束条
30、件。指出何时不需递归调用,它描述了问题求解的特殊情况或基本情况,例:解汉诺塔问题,汉诺塔问题:有A,B,C三个柱子,柱子A上穿有n个大小不同的圆盘,大盘在下,小盘在上。现要把柱子A上的所有圆盘移到柱子B上,要求每次只能移动一个圆盘,且大盘不能放在小盘上,移动时可借助柱子C。编写一个C+函数给出移动步骤,如:n=3时,移动步骤为:1:AB, 2:AC, 1:BC, 3:AB, 1:CA, 2:CB, 1:AB。,A B C,当n=1时,只要把1个圆盘从A移至B就可以了 cout B“ endl; 把n-1个圆盘从柱子C移到柱子B。 上面的子问题1和3与原问题相同,只是盘子的个数少了一个以及移动的
31、位置不同;子问题2是移动一个盘子的简单问题。,#include using namespace std; void hanoi(char x,char y,char z,int n) /把n个圆盘从x表示的柱子移至y所表示的柱子。 if (n = 1)cout “1: “ x “ y endl; /把第1个盘子从x表示的柱子移至y所表示的柱子。else hanoi(x,z,y,n-1); /把n-1个圆盘从x表示的柱子移至z所表示的柱子。cout n “: “ x “ y endl; /把第n个圆盘从x表示的柱子移至y所表示的柱子。hanoi(z,y,x,n-1); /把n-1个圆盘从z表示的
32、柱子移至y所表示的柱子。 ,递归与循环的选择,对于一些递归定义的问题,用递归函数来解决会显得比较自然和简洁,而用循环来解决这样的问题,有时会很复杂,不易设计和理解。 在实现数据的操作上,它们有一点不同: 循环是在同一组变量上进行重复操作(循环常常又称为迭代) 递归则是在不同的变量组(属于递归函数的不同实例)上进行重复操作。 递归的缺陷: 由于递归表达的重复操作是通过函数调用来实现的,而函数调用是需要开销的; 栈空间的大小也会限制递归的深度。 递归算法有时会出现重复计算。,函数名重载,对于一些功能相同、参数类型或个数不同的函数,有时给它们取相同的名字会带来使用上的方便。例如,把下面的函数: vo
33、id print_int(int i) void print_double(double d) void print_char(char c) void print_A(A a) /A为自定义类型 定义为: void print(int i) void print(double d) void print(char c) void print(A a) 上述的函数定义形式称为函数名重载。,对重载函数调用的绑定,确定对重载函数的调用对应着哪一个函数的过程称为绑定(又称定联、联编、捆绑)。例如: print(1.0)将调用void print(double d) 对重载函数调用的绑定在编译时刻由编
34、译程序根据实参与形参的匹配情况来决定。从形参个数与实参个数相同的重载函数中按下面的规则依次选择: 精确匹配 提升匹配 标准转换匹配 自定义转换匹配 匹配失败,精确匹配,类型相同 对实参进行“微小”的类型转换: 数组变量名-数组首地址 函数名-函数首地址 例如,对于下面的重载函数定义:void print(int);void print(double);void print(char); 下面的函数调用:print(1); 绑定到函数:void print(int);print(1.0); 绑定到函数:void print(double);print(a); 绑定到函数:void print(c
35、har);,提升匹配,先对实参进行下面的类型提升,然后进行精确匹配: 按整型提升规则提升实参类型 把float类型实参提升到double 把double类型实参提升到long double 例如,对于下述的重载函数: void print(int); void print(double); 根据提升匹配,下面的函数调用: print(a); 绑定到函数:void print(int); print(1.0f); 绑定到函数:void print(double);,标准转换匹配,任何算术类型可以互相转换 枚举类型可以转换成任何算术类型 零可以转换成任何算术类型或指针类型 任何类型的指针可以转换成
36、void * 派生类指针可以转换成基类指针 以上转换规则优先级相同。,例如,对于下述的重载函数: void print(char); void print(char *); 根据标准转换匹配,下面的函数调用: print(1); 绑定到函数:void print(char);,绑定失败,如果不存在匹配或存在多个匹配,则绑定失败 例如,对于下述的重载函数: void print(char); void print(double); 根据标准转换匹配,下面的函数调用将会绑定失败: print(1); 因为根据标准转换,1(属于int型)既可以转成char,又可以转成double 解决办法是: 对实
37、参进行显式类型转换,如, print (char)1) 或 print (double)1) 增加额外的重载,如, 增加一个重载函数定义: void print(int);,带缺省值的形式参数,在C+中允许在声明函数时,为函数的某些参数指定默认值。如果调用这些函数时没有提供相应的实参,则相应的形参采用指定的默认值。 例如,对于下面的函数声明: void print(int value, int base=10); 下面的调用: print(28); /28传给value;10传给base print(32,2); /28传给value;2传给base,在指定函数参数的默认值时,应注意下面几点:
38、 有默认值的形参应处于形参表的右部。例如: void f(int a, int b=1, int c=0); /OK void f(int a, int b=1, int c); /Error 对参数默认值的指定只在函数声明(包括定义性声明)处有意义。 在不同的源文件中,对同一个函数的声明可以对它的同一个参数指定不同的默认值; 在同一个源文件中,对同一个函数的声明只能对它的每一个参数指定一次默认值。,无名参数,在C+中允许在函数声明和定义时将参数名省略,这些参数称为无名参数。 函数声明时允许使用无名参数void print(int, int); 函数定义时使用无名参数void point(in
39、t a, int) cout a endl; 注意:函数定义时使用无名参数,则该参数在函数中不能使用,形同虚设。,解决小函数的低效问题,由于函数调用是需要开销的,特别是对一些小函数的频繁调用将使程序的效率有很大的降低。 C+提供了两种解决上述问题的办法: 宏定义 内联函数,宏定义,在C+中,利用一种编译预处理命令:宏定义,用它可以实现类似函数的功能: #define () 例如: #define max(a,b) (a)(b)?(a):(b) 在编译之前,将对宏的使用进行文字替换! 例如:编译前将把 cout (y)?(x):(y);,宏定义的不足之处,需要加上很多的括号。例如: #defin
40、e max(a,b) ab?a:b 10+max(x,y)+z 将被替换成: 10+xy?x:y+z 有时会出现重复计算。 例如: #define max(a,b) (a)(b)?(a):(b) max(x+1,y*2)将被替换成: (x+1)(y*2)?(x+1):(y*2) 不进行参数类型检查和转换。 不利于一些工具对程序的处理。,内联函数,内联函数是指在函数定义时,在函数返回类型之前加上一个关键词inline,例如: inline int max(int a, int b) return ab?a:b; 内联函数的作用是建议编译程序把该函数的函数体展开到调用点,以提高函数调用的效率。 内
41、联函数形式上属于函数,它遵循函数的一些规定,如:参数类型检查与转换。 使用内联函数时应注意以下几点: 编译程序对内联函数的限制。 内联函数名具有文件作用域。,编译预处理命令,C+程序中可以写一些供编译程序使用的命令:编译预处理命令。 编译预处理命令不是C+程序所要完成的功能,而是用于对编译过程给出指导,其功能由编译预处理系统来完成。 编译预处理命令主要有: 文件包含命令(#include) 宏定义(#define)命令 条件编译命令,条件编译,编译程序根据不同的情况来选择需编译的程序代码。例如,编译程序将根据宏名ABC是否被定义,来选择需要编译的代码(或):/必须编译的代码 #ifdef AB
42、C/如果宏名ABC有定义,编译之 #else/如果宏名ABC没有定义,编译之 #endif/必须编译的代码 用于条件编译的宏名ABC在哪里定义?,在程序中定义宏/必须编译的代码 #define ABC #ifdef ABC/如果宏名ABC有定义,编译之 #else/如果宏名ABC没有定义,编译之 #endif/必须编译的代码 缺点:要修改程序!,在编译环境中定义宏 命令行 cl . -D ABC . Visual C+ 6.0的集成开发环境中,选择“Project|Settings”菜单,在“Project Settings”对话框的“C/C+”选项卡中,选择“Category”中的“Prep
43、rocessor”,然后在“Preprocessor definitions”中添加要定义的宏名:ABC。,条件编译命令的常用格式,#ifdef / #ifndef #else #endif上述条件编译命令的含义是:如果有定义(#ifdef)或无定义(#ifndef),则编译,否则编译,其中,#else分支可以省略。 可以在程序中用#define定义,也可以在编译器的选项中给出。,条件编译命令的另一种格式,#if / #ifdef / #ifndef #elif #elif #else #endif上述条件编译命令的含义是:如果的值为非零(#if)或有定义(#ifdef)或无定义(#ifnde
44、f),则编译,否则,如果为非零值,则编译,.,否则如果有#else,则编译,否则什么都不编译。 中只能包含字面常量或用#define定义的常量。,条件编译的作用,条件编译的作用: 基于多环境的程序编制 程序调试 ,基于多环境的程序编制,#ifdef UNIX/适合于UNIX环境的代码 #elif WINDOWS/适合于WINDOWS环境的代码 #else/适合于其它环境的代码 #endif/适合于各种环境的公共代码,程序调试,加入调试信息 #ifdef DEBUG/调试信息,主要由输出操作构成 #endif 问题:写起来比较麻烦!,利用标准库中定义的宏(assert)调试 #include /
45、或 assert(x = 1); /断言,x!=1 会终止程序的运行 assert的定义大致如下: #ifdef NDEBUG #define assert(exp) (void)0) #else #define assert(exp) (exp)?(void)0:) #endif ,C+标准库函数,为了方便程序设计,C+语言的每个实现往往会提供一个标准库,其中定义了一些语言本身没有提供的功能: 常用的数学函数 字符串处理函数以及 输入/输出,等等 在C+标准库中,根据功能对定义的程序实体进行了分类,把每一类程序实体的声明分别放在一个头文件中。 在C+中,把从C语言保留下来的库函数, 重新定义
46、在名空间std中; 对相应的头文件进了重新命名:*.h - c*,一些标准数学函数(cmath或math.h),int abs( int n ); /int型的绝对值 long labs( long n ); /long int型的绝对值 double fabs( double x ); /double型的绝对值 double sin( double x ); /正弦函数 double cos( double x ); /余弦函数 double tan( double x ); /正切函数 double asin( double x ); /反正弦函数 double acos( double
47、x ); /反余弦函数 double atan( double x ); /反正切函数 double ceil( double x ); /不小于x的最小整数(返回值为以/ / double表示的整型数) double floor( double x ); /不大于x的最大整数(返回值为以/ double表示的整型数) double log( double x ); /自然对数 double log10( double x ); /以10为底的对数 double sqrt( double x ); /平方根 double pow( double x, double y ); /x的y次幂,作业二,思考教材后面的习题。 上交的作业 (1)已知函数poly是递归方法计算x的n阶勒德让多项式的值。数学函数如下:1 n=0polyn(x)=x n=1(2n-1)*x*polyn-1(x)-(n-1)*polyn-2(x)/n n1 (2)水仙花数指一个 n 位数 ( n3 ),它的每个位上的数字的 n 次幂之和等于它本身。如:153 =13+53+33 用函数实现求出所有的3位水仙花数 (3) 教材119面12题,