[程序设计语言用c++语言编程陈家骏]
一、过程抽象---函数
1.程序中调用的所有函数都要有定义,如果函数定义在其他文件(如c++标准库),或定义与本源文件使用点之后,在调用之前需对被调函数进行声明,可采用函数原型声明表示:
<返回值类型> <函数名>(<形式参数表>);
也可采用c语言形式函数声明:
extern <返回值类型> <函数名>(<形式参数表>);
二、变量的局部性
1.c++中,根据变量的定义位置,把变量分为全局变量和局部变量
2.c++把变量生存期分为:
静态生存期变量:其内存空间从程序开始执行时就进行分配,直到程序结束才收回,全局变量具有静态生存期
自动生存期变量:其内存空间在程序执行到定义它们的复合语句时才分配,定义它们的复合语句结束时,空间被收回,局部变量和函数参数一般具有自动生存期
动态生存期变量:内存空间用new操作分配,用delete操作收回,动态变量具有动态生存期
定义局部变量时,可以加上存储类型修饰符auto,static或register来指出生存期。局部变量默认是auto。
定义为auto存储类的局部变量具有自动生存期。
定义为static存储类的局部变量具有静态生存期。其作用是能在函数调用获得上一次调用结束时该局部变量的值,即static存储类使得某些局部变量的值能在函数调用多次之间得以保留,如果在一个static存储类的局部变量中给出了初始化,则该初始化只在函数第一次调用时进行,以后调用中不再进行初始化,它的值是上一次函数结束时保存的值,这种变量能受函数封装保护。对应static存储类的局部变量,其作用域为定义它的函数,生存期为整个程序运行期间,在包含static存储类局部变量的函数被调用前,该变量虽然存在,但不可访问。
定义为register存储类的局部变量也具有自动生存期,并建议编译器把相应的局部变量空间分配在cpu的寄存器中,提高对局部变量的访问效率,只是建议,也有可能分配在内存中,视cpu寄存器使用情况而定
当一个程序运行时,操作系统将为其分配一块内存空间,包括四个部分:
静态数据区:用于全局变量、static存储类的局部变量以及常量的内存分配
代码区:用于存放程序的指令,对应c++,代码区存放的是所有函数代码
栈区:用于存储auto存储类的局部变量、函数的形参及函数调用时有关信息(函数返回地址等)的内存分配
堆区:用于动态变量的内存分配
静态数据区和代码区的大小事固定的,而栈区和堆区大小不断变化,OS对其最大值有一定限制。在C++中,如果定义一个变量时没有对其初始化,对于全局变量和static存储类的局部变量,编译程序将隐式地自动把它们按位模式初始化为0;对应其它变量,编译程序不会对它们进行初始化。对变量进行显示初始化是一种良好的习惯。
3.为了便于管理、组织、理解c++程序,通常对构成c++程序的各个逻辑单位(全局函数、全局常量、全局变量/对象、类等)的定义进行分组,分别把它们放在若干源文件中,以避免对程序一点修改导致对整个程序的重新编译和便于多人合作开发一个程序。分组即模块化,基本原则是:模块内部内聚性最大、模块之间耦合度最小。模块是一个可单独编译的程序单位,一般包含两个部分:接口和实现;c++中接口一般于.h文件中存放;实现于.cpp文件中存放。
4.#include <文件名>:在系统指定的目录下寻找指定文件
#include "文件名":现在包含#include命令的源文件所在目录下查找指定文件,然后再在系统指定目录下查找。
5.c++把标示符作用域分成:
局部作用域:在函数定义或复合语句中,从标示符定义点开始到函数定义或复合语句结束之间的程序段。局部常量名、局部变量名/对象以及函数的形参名具有局部作用域,静态作用域机制:外层标示符作用域应该是从其潜在的作用域中扣除内层同名标示符作用域所得到的作用域,如:
void f()
{
……
int x;//外层x的定义
…x…;//外层x
while(…x…)//外层x
{
…x…;//外层的x
double x;//内层x的定义
…x…;//内层x
}
…x…;//外层x
}
全局作用域:构成C++程序的所有源文件。
文件作用域:指单独的源文件,在全局标示符的定义中加上static修饰符,该全局标示符就成了具有文件作用域的标示符,只能在定义它们的源文件中使用。在函数外用const定义的常量名具有文件作用域。
函数作用域:指整个函数定义所构成的程序段。语句标号是唯一具有函数作用域的标示符,可以在定义它们的函数体中任何地方访问它们。函数作用域与局部作用域区别是:a.函数作用域为整个函数,而局部作用域只是从定义点开始到函数定义或复合语句结束;b.函数体中,语句标号只能定义一次,即使是内层的复合语句中,也不能与外层相同的语句标号;c.C++把语句标号作为特殊的标示符看待,与其他类的标示符属于不同的范畴,语句标号的作用域可以撤同名的其他标示符作用域重叠,如:
void f(int x)
{
goto x;//ok
goto y;//ok
x: y=x;//ok
y: y=x+1;//ok
}
函数原型作用域:指用于函数声明的函数原型,其中形参名的作用域从函数原型开始到函数原型结束
类作用域:
名空间作用域:对于不是定义在某个名空间中的全局程序实体,可以把它们看成是定义在一个全局的无名空间中,可以把文件作用域的程序实体定义为一个局部与各个源文件的无名空间。
//file.h
namespace A
{
extern int x;
void f();
void g();
}
//file.cpp
namespace A
{
int x=1;
void f()
{
…x…;//ok
}
void g()
{
…x…;//ok
f();//ok
}
}
#include "file1.h"
int main()
{
…x…;//error
f();//error;
…A::x…;//ok
…A::f()…;//ok
…A::g()…;//ok
}
或
int main()
{
using namespace A;
f();
g();
}
或
int main()
{
using A::f;
f();
A::g();
}
三、带默认值的形式参数
a.有默认值的形参应处于形参表的右部
b.对参数默认值指定只在函数声明处有意义,在函数定义时指定默认值没有意义,除非声明定义同时进行
c.在不同源文件中,对同一个函数的声明可以对它的同一个参数指定不同的默认值,在同一个源文件中,对同一个函数的声明只能对它的每一个参数指定一次默认值
四、内联函数和宏:解决函数运行带来的执行效率降低的问题
1.宏定义(4种):
#define <宏名> <文字串>:字符串及注释中的相应值不会被替换,如#define PI 3.14;
#define <宏名>(<参数表>) <文字串>:出现<宏名>的地方用文字串代替,<文字串>中参数(相当于形参)将被替换成<宏名>提供的参数(相当于实参),如果一行写不下,可在本行最后用续行符"/"后跟一个回车转到下一行。如:
#define max(a,b) (((a)>(b))?(a):(b))//该行有无分号有时会影响结果正确性
int main()
{
int x,y;
max(x,y);//替换成x>y?x:y
}
#define <宏名>:告知编译程序<宏名>已被定义,不做文本替换,实现条件编译
#undef <宏名>:取消某个宏明定义,其后的<宏名>不再进行替换和不再有意义
2.宏定义缺陷:
a.有时会重复计算,如:max(x+1,y+2);则被编译程序替换为:(((x+1)>(y+2))?(x+1):(y+2))
b.不进行类型检查和转换
c.在c++编译结果中,所有宏定义已不存在,会给一些软件工具(如调试程序)在源程序和目标程序之间进行交叉定位带来困难
3.内联函数:在函数返回类型之前加上inline,建议编译程序把该函数的函数体展开到调用点,形式上属于函数,效果上具有宏定义的高效,只是建议,视函数体的具体实现而定,有些函数即使加上inline,编译程序也不把它作为内联函数,如递归函数一般不能作为内联函数。内联函数具有文件作用域,在调用内联函数时一定要见到内联函数的定义,仅仅对其声明是不行的。常把内联函数定义放在头文件中,其他源文件需要时用#include包含进来即可
五、条件编译作用:
a.由编译程序根据不同的环境对程序中相应的与环境有关的代码进行编译,避免代码重复,与环境无关的代码只写一次。
b.不会重复包含某个.h文件,如在该.h文件头加入
#ifndef MODUAL1
#define MODUAL1
……
#endif
则编译程序只对第一次包含的内容进行编译。
c.编译程序调试,实际上断言可以达到类似效果,C++标准库提供了宏assert来表示断言,在头文件(cassert或assert.h中定义),该宏要求一个类型为int的表达式作为其参数,如果表达式值为0,会显示相应的表达式、断言所在源文件名及断言所在的行号等诊断信息,然后调用abort终止程序运行;当表达式不为0时,程序继续运行
条件编译命令的格式1:
#ifdef <宏名> / ifndef <宏名>
<程序段1>
[#else
<程序段2>
#endif
条件编译命令的格式2:
#if <常量表达式1> / #ifdef <宏名> / #ifndef <宏名>
<程序段1>
#elif <常量表达式2>
<程序段2>
[#else
<程序段n+1>]
#endif
其中常量表达式为整形常量表达式,其操作数只能是符号常量、字面常量或"define(<宏名>)",对应define(<宏名>),当宏名有定义时,其值为1,否则为0
六、标准库
C语言标准库主要包含了一些常用的函数定义和宏定义,它们的头文件名种通常包含".h",C++标准库也提供相应的头文件,把C标准库文件名的".h"去掉,在文件名前加上c,很多C++实现为了保证与c兼容,通常提供2套头文件。C++标准库的程序实体是在名空间std中定义的,在C++程序中使用C标准库功能时,如果包含的是相应的C++头文件,应该通过名空间std来使用这些功能。