c++ 编程常用技术images.china-pub.com/ebook4970001-4975000/4974298/ch01.pdf · 2016. 8. 5. ·...

25
1 C++ 编程常用技术 我们通过固定格式和固定词汇的“语言”来影响他人,让他人为我们做事情。语言有很 多种,包括汉语、英语、法语、韩语等,虽然它们的词汇和格式都不一样,但是可以达到同 样的目的,我们可以选择任意一种语言去与他人交流。同样,我们也可以通过“语言”来影 响计算机,让计算机为我们做事情,这样的语言就叫作编程语言。 C 语言是 1972 年由美国贝尔实验室的 D.M.Ritchie 设计成功的,它是为计算机专业人员 设计的,大多数系统软件和许多应用软件都是用 C 语言编写的。但是随着软件规模的增大, C 语言编写程序渐渐显得有些吃力了。C++ 也是由美国贝尔实验室的 Bjarne Stroustrup 士及其同事于 20 世纪 80 年代初在 C 语言的基础上开发成功的。C++ 保留了 C 语言原有的所 有优点,与 C 语言兼容,并且增加了面向对象的机制。用 C 语言写的程序基本上可以不加修 改地用于 C++ 开发工具。从 C++ 的名字可以看出它是 C 的超集。C++ 既可用于面向过程的 结构化程序设计,又可用于面向对象的程序设计,是一种功能强大的混合型程序设计语言。 本章主要讲述 C++ 中的常用技术,让读者可迅速地、由浅入深地熟悉这门语言。 1.1 第一个 C++ 程序 刚开始接触一门编程语言,一般会从写一个输出 Hello world 的程序开始。 【例 1.1用程序输出 Hello world#include<iostream> using namespace std; int main() { Chapter 1

Upload: others

Post on 05-Feb-2021

12 views

Category:

Documents


0 download

TRANSCRIPT

  • 第 1 章

    C++ 编程常用技术

    我们通过固定格式和固定词汇的“语言”来影响他人,让他人为我们做事情。语言有很

    多种,包括汉语、英语、法语、韩语等,虽然它们的词汇和格式都不一样,但是可以达到同

    样的目的,我们可以选择任意一种语言去与他人交流。同样,我们也可以通过“语言”来影

    响计算机,让计算机为我们做事情,这样的语言就叫作编程语言。

    C 语言是 1972 年由美国贝尔实验室的 D.M.Ritchie 设计成功的,它是为计算机专业人员设计的,大多数系统软件和许多应用软件都是用 C 语言编写的。但是随着软件规模的增大,用 C 语言编写程序渐渐显得有些吃力了。C++ 也是由美国贝尔实验室的 Bjarne Stroustrup 博士及其同事于 20 世纪 80 年代初在 C 语言的基础上开发成功的。C++ 保留了 C 语言原有的所有优点,与 C 语言兼容,并且增加了面向对象的机制。用 C 语言写的程序基本上可以不加修改地用于 C++ 开发工具。从 C++ 的名字可以看出它是 C 的超集。C++ 既可用于面向过程的结构化程序设计,又可用于面向对象的程序设计,是一种功能强大的混合型程序设计语言。

    本章主要讲述 C++ 中的常用技术,让读者可迅速地、由浅入深地熟悉这门语言。

    1.1 第一个 C++ 程序刚开始接触一门编程语言,一般会从写一个输出 Hello world 的程序开始。【例 1.1】 用程序输出 Hello world。

    #include

    using namespace std;

    int main()

    {

    Chapter 1

  • 2   后台开发:核心技术与应用实践

    cout

  • 第 1 章 C++ 编程常用技术   3

    定义的元素。这条语句在使用标准函数库的 C++ 程序中频繁出现,本书中大部分例子的代码中也将用到它,需要注意的是,最好不要在头文件中使用命名空间,否则容易造成命名冲突。

    继续看程序的第三行:“ int main()”,这是主函数 (main function) 的起始声明。主函数是所有 C++ 程序的运行的起始点。不管它是在代码的开头、结尾还是中间,此函数中的代码总是在程序开始运行时第一个被执行。main 后面跟了一对圆括号 (),表示它是一个函数。C++中所有函数都跟有一对圆括号 (),括号中可以有一些输入参数。如例 1.1 中显示,主函数(main function) 的内容紧跟在它的声明之后,由花括号 {} 括起来。

    程序的第四行:“ cout

  • 4   后台开发:核心技术与应用实践

    int min(int a,int b){ // 这里的 min 就是函数名,a、b 是形参, // 返回值是一个 int 整型 if(a(1e-5))a=b;

    return a;

    }

    int main(){

    int a=1,b=2,c=3;

    cout

  • 第 1 章 C++ 编程常用技术   5

    return 0;

    }

    程序的执行结果是:

    1

    100

    1.1

    这里分别需要比较 3 个整数、3 个长整数和 2 个浮点数,并获得各组中的最小值。例 1.3中分别定义了 3 个函数,而且函数名都是一样的,不过参数个数不一样或者参数类型不一样,这就是使用了函数重载来实现功能。

    在使用函数重载时,同名函数的功能应当相同或相近,不要用同一函数名去实现几个完

    全不相干的功能,这样虽然程序能运行,但是可读性不好,会让人觉得莫名其妙。

    3. 函数模板函数模板,实际上是建立一个通用函数,其函数类型和形参不具体指定,而用一个虚拟

    的类型来代表,这个通用函数就是函数模板。凡是函数体相同的函数都可以用这个模板来代

    替,而不用定义多个函数,实际使用时只需在模板中定义一次就可以了。在调用函数时,系

    统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。

    定义函数模板的一般格式是:

    template

    下面的程序说明了函数模板的使用方法。

    【例 1.4】 函数模板使用举例。

    #include

    using namespace std;

    template

    T min(T a,T b,T c){

    if(a>b)a=b;

    if(a>c)a=c;

    return a;

    }

    int main(){

    int a=1,b=2,c=3;

    cout

  • 6   后台开发:核心技术与应用实践

    例 1.4 中定义了一个函数模板,用来获得 3 个数中的最小者。若传入 3 个整型的,函数就将虚拟类型 T 转成 int 去执行;若传入 3 个长整型的,函数就将 T 转化成 long long 去执行。这样就可以不用定义类型不同的函数了,只需一个函数模板即可搞定。

    在编写函数模板时,可以先写一个函数,然后把其中的变量类型都替换成虚拟类型即可。

    可以看到,用函数模板比函数重载更方便,但是它只适用于函数个数相同而类型不同的情况。

    1.3 数组

    1. 数组的定义数组是相同类型数据的集合。引入数组就不需要在程序中定义大量的变量,大大减少

    程序中变量的数量,使程序精炼,而且数组含义清楚,使用方便,明确地反映了数据间的联

    系。许多好的算法都与数组有关。熟练地利用数组,可以大大地提高编程的效率,加强程序

    的可读性。例 1.5 展示了数组的使用方法。【例 1.5】 一维数组与二维数组使用举例。

    #include

    using namespace std;

    int main(){

    int a[10]={1,2,3,4,5,6,7,8,9,10};

    // 数组 a, 类型为 int 整型,有 10 个元素,是一个一维数组 int i,j;

    for(i=0;i

  • 第 1 章 C++ 编程常用技术   7

    a[1] 的地址就是 2004,a[2] 的地址就是 2008,a[3] 的地址就是 2012……如此类推。这里的“地址”,大家可以简单理解为在内存中的一个标识,详细内容会在本章的 1.4 节中进行介绍。

    2. 字符数组字符数组,就是一个用来存放字符数据的数组,示例如下:

    char str[10]="Book";

    其中,str 就是一个字符数组,并且 str[0]='B',str[1]='o',str[2]='o',str[3]='k'。C++ 中用 '\0' 来标识一个字符串的结束,这里,str[4]~str[9] 都是 '\0'。通常用 strlen() 函数来计算一个字符串的长度,故 strlen(str) 的值是 4。与 strlen() 函数比较容易混淆的是 sizeof() 函数,这里 sizeof(str) 的值是 10。

    strlen 与 sizeof 的区别如下所示:(1)strlen() 是函数,在运行时才能计算。参数必须是字符型指针(char*),且必须是

    以 '\0' 结尾的。当数组名作为参数传入时,实际上数组已经退化为指针了。它的功能是返回字符串的长度。

    (2)sizeof() 是运算符,而不是一个函数,在编译时就计算好了,用于计算数据空间的字节数。因此,sizeof 不能用来返回动态分配的内存空间的大小。sizeof 常用于返回类型和静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。

    当参数分别如下时,sizeof 返回的值表示的含义如下所述。1)数组——编译时分配的数组空间大小,如:

    char a[10]="hello";

    因为 char 占 1Byte,所以 sizeof(a) 的值是 10*1=10Byte。2)指针——存储该指针所用的空间大小,如:

    char *str="I am from China."

    因为 str 存储的是一个字符指针,所以 sizeof(str) 是指针所占的空间,即是 4Byte。3)类型——该类型所占的空间大小,如:

    int b=10;

    因为在 32 位的机器上,int 类型占 4Byte,所以 sizeof(b) 的值是 4Byte。4)对象——对象的实际占用空间大小,如:

    class Class_Sample{

    int a,b;

    int func();

    }Class_a;

    两个 int 类型的值是 8Byte,所以 sizeof(Class_a) 的值是 8Byte。5)函数——函数的返回类型所占的空间大小,且函数的返回类型不能是 void。

  • 8   后台开发:核心技术与应用实践

    1.4 指针

    1. 指针的概念为了理解什么是指针,必须先弄清楚数据在内存中是如何存储的,又是如何读取的。如

    果在程序中定义了一个变量,在编译时就给这个变量分配内存单元。系统根据程序中定义

    的变量类型,来分配一定长度的空间。例如,C++ 编译系统在 32 位机器上为整型变量分配4Byte,为单精度浮点型变量分配 4Byte,为字符型变量分配 1Byte。内存区的每一个字节有一个编号,这个编号就是地址,如表 1-1 所示。

    表 1-1 用户数据、变量、地址直接的对应关系地址 用户数据 变量名

    … … …

    2000 3 变量 i2004 6 变量 j2008 9 变量 k2012 10 变量 l

    … … …

    表 1-1 中展示了用户数据、变量、地址直接的对应关系。假设有变量 i,存的数据是 3,那它在内存中的地址就是 2000。

    请务必弄清楚一个内存单元的地址与内存单元的内容这两个概念的区别。其实程序经过

    编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。这种按变

    量地址存取变量值的方式称为直接存取方式,或直接访问方式。还可以采用另一种称为间接

    存取(间接访问)的方式,在程序中定义一种特殊的变量,专门用来存放地址。

    由于通过地址能找到所需的变量单元,因此可以说,地址指向该变量单元。因此将地址

    形象化地称为“指针”,一个变量的地址称为该变量的指针。如果有一个变量是专门用来存

    放另一变量地址(即指针)的,则它称为指针变量。指针变量的值(即指针变量中存放的值)

    是地址(即指针)。

    指针也是一种变量,普通的变量存放的是实际的数据,而指针变量包含的是内存中的一

    块地址,这块地址指向某个变量或者函数。指针的内容包括:指针的类型、指针所指向的类

    型、指针的值以及指针本身所占的内存区。例 1.6 展示了指针的使用。【例 1.6】 指针使用举例。

    #include

    using namespace std;

    int main(){

    int p1=1; // p1 是一个普通的整型变量 int *p2; // p2 是一个指针,指向一个整型变量 p2=&p1; // 把 p1 的地址赋值给 p2,p2 也就指向了 p1 cout

  • 第 1 章 C++ 编程常用技术   9

    p1=2; // 那么 *p2 的值也是 2 cout

  • 10   后台开发:核心技术与应用实践

    int *p[3];

    int a[3][4];

    for(i=0;i[]>*。

    3. 字符串与指针字符串是 C++ 中最经常用到的操作对象之一。用字符数组和字符指针变量都可以实现字

    符串的存储和运算。例 1.7 展示了字符数组和字符指针是如何使用的。【例 1.7】 字符数组、字符指针、字符指针数组、字符串变量应用举例。

    #include

    #include

    using namespace std;

    int main(){

    char str[] = "I am a programmer." ; // str 是一个字符数组 char * str1="abc"; // str1 是一个字符指针变量,可以指向一个字符串 char * str2[]={"hello world","good bye"};

    // str2 是一个字符指针数组,可以存多个字符串 string str3 = "I am a programmer, too.";

    // str3 是一个字符串变量 cout

  • 第 1 章 C++ 编程常用技术   11

    (1)字符串指针变量本身是一个变量,用于存放字符串的首地址。可以改变 str1 使它指向不同的字符串,但不能改变 str1 所指的字符串常量。因为定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,所以 abc 会被当成常量,并且被放到程序的常量区,不能被修改。

    (2)字符串本身是存放在以该首地址为首的一块连续的内存空间中,并以 '\0' 作为字符串的结束标志。

    (3)字符数组是由于若干个数组元素组成的,每个元素中存放字符串的一个字符。在定义一个字符数组时,编译后就会分配一个内存单元,每个元素都有确定的地址。

    4. 函数与指针函数指针是指向函数的指针变量。所以,函数指针首先是个指针变量,而且这个变量指

    向一个函数。C++ 在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,就可以用该指针变量调用函数了。

    函数指针的声明方法是:

    返回值类型 (* 指针变量名 )([ 形参列表 ]);

    其中,返回值类型说明函数的返回值类型,(* 指针变量名)这句的括号不能省略。例如:

    int func(int a); // 声明一个函数int (*f) (int a); // 声明一个函数指针f=&func;

    将 func 函数的首地址赋值给函数指针,这里也等价于 f=&func ;赋值时函数不带括号,也不带参数,函数名就代表了函数的首地址。例 1.8 展示了函数指针调用函数的方法。

    【例 1.8】 函数指针使用范例。

    #include

    using namespace std;

    int Mmin(int x,int y){

    if(xy)return x;

    return y;

    }

    int main(){

    int (*f)(int x,int y);

    int a=10,b=20;

    f=Mmin; // 把 Mmin 函数的入口地址赋给 f cout

  • 12   后台开发:核心技术与应用实践

    程序的执行结果是:

    10

    20

    例 1.8 中 定 义 了 一 个 函 数 指 针 f, 两 个 函 数 Mmin 和 Mmax, 先 后 把 f 指 向 Mmin 和Mmax 函数,执行比较两个数,分别得出较小值和较大值。

    1.5 引用

    1. 引用是什么对于习惯使用 C 语言进行开发的朋友们,在看到 C++ 中出现的 & 符号后,可能会犯迷

    糊,虽然在 C 语言中这个符号代表取地址符,但是在 C++ 中它却有着不一样的用途,代表着引用的意思。掌握 C++ 的 & 符号,有利于增强代码质量和提高代码执行效率。

    引用是一种变量类型,它用于为一个变量起一个别名。

    引用的声明方法是:

    类型标识符 & 引用名 = 目标变量名 ;

    假设有一个变量 a,想给它起一个别名 r,可以这样写:

    int a;

    int &r=a;

    定义引用 r,它是变量 a 的引用,即别名。经过这样的声明后,a 和 r 的作用都一样,都代表着同一变量。a 和 r 占用内存的同一个存储单元,即具有同一地址。在声明一个引用变量时,必须同时使之初始化,即声明它代表哪个变量。函数执行期间,不可以将其再作为其

    他变量的引用。例 1.9 说明了引用的使用方法。【例 1.9】 引用的使用举例。

    #include

    using namespace std;

    int main(){

    int a=2;

    int &r=a;

    a=a+4;

    cout

  • 第 1 章 C++ 编程常用技术   13

    例 1.9 中展示了引用与变量的关系。r 是 a 的引用,a 变了,r 的值也跟着变;r 变了,a的值也跟着变。

    2. 引用作为参数引用一个重要的作用就是作为函数的参数。

    【例 1.10】 引用作为函数的参数举例。

    #include

    using namespace std;

    void Mmin1(int a,int b){

    int temp;

    if(a>b){

    temp=a;

    a=b;

    b=temp;

    }

    }

    void Mmin2(int &a,int &b){ // 引用作为函数的参数 int temp;

    if(a>b){

    temp=a;

    a=b;

    b=temp;

    }

    }

    int main(){

    int a=30,b=20;

    Mmin1(a,b); // cout

  • 14   后台开发:核心技术与应用实践

    较大时,用引用比用一般变量传递参数的效率更高,所占空间更少。

    使用指针作为函数的参数虽然也能达到与使用引用同样的效果,但是在被调函数中同样

    要给形参分配存储单元,且需要重复使用“ * 指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参,

    这些都不太方便。

    综上所述,引用是个有效率的选择。

    3. 常引用如果既要提高程序的效率,又要使传递给函数的数据不在函数中被改变,就应该使用常

    引用。常引用的声明方式是:

    const 类型标识符 & 引用名 = 目标变量名 ;

    用这种方式声明的引用,不能通过引用对目标变量的值进行修改,在程序中使引用的目

    标成为 const 类型,从而保证了引用的安全性,如下所示:

    int a;

    const int &r=a;

    r=1; // 错误a=1; // 正确

    假设有如下函数声明:

    string func1();

    void func2(string &s);

    那么下面的表达式都是非法的:

    func2(func1);

    func2("hello");

    原因在于 func1() 和 "hello" 都将产生一个临时对象,而在 C++ 中,这些临时对象都是const 类型的。因此,上面的表达式就是试图将一个 const 类型的对象转化为非 const 类型,这是非法的。

    引用型参数应该在能被定义成 const 的情况下,尽量定义为 const。

    1.6 结构体、公用体、枚举

    1.6.1 结构体、共用体、枚举的概念

    1. 结构体的声明方法结构体的声明方法如下所示:

    struct 结构名 { 数据类型 成员名 ;

  • 第 1 章 C++ 编程常用技术   15

    数据类型 成员名 ; ...

    };

    成员表由若干成员组成,每个成员都是该结构的一个组成部分,对每个成员也必须做类

    型声明。例 1.11 说明了结构体的使用方法。【例 1.11】 结构体使用范例。

    #include

    #include

    using namespace std;

    struct student{ // 声明一个结构体类型 student int num;

    char name[20];

    int age;

    }; // 最后有一个分号int main(){

    struct student stu1; // 定义一个 student 类型的变量 stu1 student stu2; // 定义时也可以不用 struct stu1.num=1; // 单独对 st1 的 num 元素赋值 char temp[20]="Xiao ming";

    strncpy(stu1.name,temp,strlen(temp));

    stu1.age=10;

    cout

  • 16   后台开发:核心技术与应用实践

    量,以达到节省空间的目的。但同一时间只能储存其中一个成员变量的值。

    共用体的声明方式为:

    union 共用体类型名 { 数据类型 成员名 ; 数据类型 成员名 ; ...

    } 变量名 ;

    可以使用 union 判断系统是 big endian(大端)还是 little endian(小端)。【例 1.12】 判断系统是 big endian 还是 little endian。

    #include

    using namespace std;

    union TEST{

    short a;

    char b[sizeof(short)];

    };

    int main(){

    TEST test;

    test.a=0x0102;// 不能引用共用体变量,只能引用共用体变量中的成员。 if(test.b[0]==0x01&&test.b[1]==0x02){

    cout

  • 第 1 章 C++ 编程常用技术   17

    址是怎么样分布的。例如 0x0000 是 1Byte 的存储单元,0x12 单元一共占了 8bit,也就是1byte,即占用了 1Byte。

    目前,几乎所有网络协议都是采用 big endian 的方式来传输数据的,当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序(big endian)后再进行传播。

    3. 枚举在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期只有 7

    天,一年只有 12 个月,一个班每周有 6 门课程等。如果把这些量说明为整型,字符型或其他类型显然是不妥当的。为此,C 语言提供了一种称为“枚举”的类型,枚举类型在 C++ 中也同样适用。在“枚举”类型的定义中列举出所有可能的取值,用来说明该“枚举”类型的

    变量取值不能超过定义的范围。应该说明的是,枚举类型是一种基本数据类型,而不是一种

    构造类型,因为它不能再分解为任何其他基本类型。

    枚举的声明方式为:

    enum 枚举类型名 { 枚举常量表列 };

    如同结构和共用体一样,枚举变量也可用不同的方式说明,即先定义后说明,同时定义

    说明或直接说明。

    设有变量 a, b, c 是枚举类型 weekday,可采用下述任一种方式:

    enum weekday{ sun,mou,tue,wed,thu,fri,sat };

    enum weekday a,b,c;

    或者为:

    enum weekday{ sun,mou,tue,wed,thu,fri,sat }a,b,c;

    或者为:

    enum { sun,mou,tue,wed,thu,fri,sat }a,b,c;

    枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。

    例如对枚举 weekday 的元素再作以下赋值,都是错误的:

    sun=5;

    mon=2;

    sun=mon;

    只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量,如下面的语句是正

    确的:

    a=sum;

    b=mon;

    而下面的语句则是错误的:

  • 18   后台开发:核心技术与应用实践

    a=0;

    b=1;

    如一定要把数值赋予枚举变量,则必须用强制类型转换,如:

    a=(enum weekday)2;

    其意义是将顺序号为 2 的枚举元素赋予枚举变量 a,相当于:

    a=tue;

    还应该说明的是枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。

    【例 1.13】 enum 使用范例。

    #include

    using namespace std;

    int main(){

    enum weather{sunny,cloudy,rainy,windy};

    /* 其中 sunny=0,cloudy=1,rainy=2,windy=3,

    默认地,第一个枚举子被赋值为 0*/

    enum fruits{apple=3,orange,banana=7,bear};

    /* 也可以显式地赋值,接下来的枚举子取值是前面一个枚举子的取值 +1,即 orange=4,bear=8*/

    cout

  • 第 1 章 C++ 编程常用技术   19

    其中,long 类型在 32 位机器上只占 4Byte,其他类型在 32 位机器和 64 位机器都是占同样的大小空间。先来看 union 占用内存单元字节数的计算方法。

    【例 1.14】 union 的字节数计算。

    #include

    using namespace std;

    union A{

    int a[5];

    char b;

    double c;

    };

    int main(){

    cout

  • 20   后台开发:核心技术与应用实践

    (即结构中占用最大空间的类型所占用的字节数 sizeof (double)=8)的倍数,所以需要填充4Byte,以满足结构的大小为 sizeof(double)=8 的倍数,即 24。

    再来看一个混合结构体的大小计算。

    【例 1.16】 一个混合结构体大小的计算。

    #include

    using namespace std;

    typedef union{

    long i;

    int k[5];

    char c;

    } UDATE;

    struct data{

    int cat;

    UDATE cow;

    double dog;

    }too;

    UDATE temp;

    int main(){

    cout

  • 第 1 章 C++ 编程常用技术   21

    带参数的宏定义的声明格式如下所示:

    #define 宏 ( 参数表列 ) 宏

    例:#def ine A(x) x使用宏定义中,要注意以下问题。

    (1)在简单宏定义的使用中,当替换文本所表示的字符串是一个表达式时,需要加上括号,否则容易引起误解和误用。

    【例 1.17】 简单宏定义不加括号容易引起误用。

    #include

    #define N 2+9

    using namespace std;

    int main(){

    int a=N*N;

    cout

  • 22   后台开发:核心技术与应用实践

    8

    表面上看,给的参数是 2+2,所得的结果应该为 4*4=16,但该程序的实际结果为 8。宏定义中要遵循先替换后计算的原则,在上面的程序里,2+2 即为宏 area 中的参数,应该由它来替换宏定义中的 x,即替换成 2+2*2+2=8 了。那如果遵循(1)中的解决办法,把 2+2 括起来,即把宏体中的 x 括起来,是否可以解决呢? #def ine area(x) (x)*(x),对于 area(2+2),替换为 (2+2)*(2+2)=16,可以解决,但是对于 area(2+2)/area(2+2) 又会怎么样呢,有人一看到这道题马上给出结果 1,因为分子分母一样,那么这样就又错了。遵循先替换再计算的规则,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2) 即 4*4/4*4 按照乘除运算规则,结果为 16/4*4=4*4=16。解决这类问题的方法是在整个宏体上再加一个括号,即 #def ine area(x) ((x)*(x)),不要觉得这没必要,没有它是不行的。

    要想能够真正使用好宏定义,在读别人的程序时,一定要记住先将程序中对宏的使用全

    部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展开后再进行相应的

    计算,就不会求错运行结果。

    如果是自己在编程时使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号

    时,加上括号表现出优先级,如果是带参数的宏定义,则要给宏体中的每个参数加上括号,

    并在整个宏体上再加一个括号。

    2. do...while(0) 的妙用大家都知道,do{…}while(condition) 可以表示循环,但你有没有遇到在一些宏定义中可

    以不用循环的地方,也用到了 do{…}while,比如有这样的宏:

    #define Foo(x) do{\

    statement one;\

    statement two;\

    }while(0) // 这里没有分号

    粗看会觉得很奇怪,既然循环里面只执行了一次,那要这个看似多余的 do...while(0) 有什么意义呢?再来看这样的宏:

    #define Foo(x) {\

    statement one;\

    statement two;\

    }

    这两个看似一样的宏,其实是不一样的。前者定义的宏是一个非复合语句,而后者却是

    一个复合语句。假如有这样的使用场景:

    if(conditon)

    Foo(x);

    else

    ...;

    因为宏在预处理的时候会直接被展开,采用第 2 种写法,会变成:

  • 第 1 章 C++ 编程常用技术   23

    if(condition)

    statement one;

    statement two;

    else

    ...///

    这样会导致 else 语句孤立而出现编译错误。加了 do{...}while(0),就使得宏展开后,仍然保留初始的语义,从而保证程序的正确性。

    3. 条件编译一般情况下,源程序中所有行的语句都参加编译。但是有时程序员希望其中一部分内容

    只在满足一定条件时才进行编译,也就是对一部分内容指定编译的条件,这就用到了“条件

    编译”。

    条件编译命令最常见的形式为:

    #ifdef 标识符 程序段 1 #else

    程序段 2 #endif

    它的作用是:当标识符已经被定义过(一般是用 #def ine 命令定义),则对程序段 1 进行编译,否则编译程序段 2。其中 #else 部分也可以没有,即:

    #ifdef 标识符 程序段 1 #endif

    下面这样的形式则是当指定的表达式值为真(非零)时就编译程序段 1,否则编译程序段 2。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。

    #if 表达式 程序段 1#else

    程序段 2#endif

    这里的“程序段”可以是语句组,也可以是命令行。

    有时候程序中的某些调试代码,只需要在调试的时候被编译,而不希望在程序的正式发

    行版中被编译,你可能会看到类似例 1.19 这样的代码段。【例 1.19】 调试代码巧用条件编译。

    #include

    using namespace std;

    #define _DEBUG_

    int main(){

    int x=10;

  • 24   后台开发:核心技术与应用实践

    #ifdef _DEBUG_

    cout

  • 第 1 章 C++ 编程常用技术   25

    【例 1.20】 __cplusplus 的使用方法。

    #include

    int main() {

    #define TO_LITERAL(text) TO_LITERAL_(text)

    #define TO_LITERAL_(text) #text

    #ifndef __cplusplus

    /* this translation unit is being treated as a C one */

    printf("a C program\n");

    #else

    /*this translation unit is being treated as a C++ one*/

    printf("a C++ program\n__cplusplus expands to \""

    TO_LITERAL(__cplusplus) "\"\n");

    #endif

    return 0;

    }

    程序的执行结果是:

    a C++ program

    __cplusplus expands to "1"

    例 1.20 中程序的意思是:如果没有定义 __cplusplus,那么当前源代码就会被当作 C源代码处理;如果定义了 __cplusplus,那么当前源代码会被当中 C++ 源代码处理,并且输出 __cplusplus 宏被展开后的字符串。

    1.8 本章小结

    本章抛砖引玉地讲述了 C++ 中的常用技术,通过简单的例子,读者可轻易学会其使用方法。读者在实际编程时,简单的知识点直接查阅本章即可。

    学习 C++,既要会利用 C++ 进行面向过程的结构化程序设计,也要会利用 C++ 进行面向对象的程序设计。接下来的第 2 章,我们将学习面向对象的 C++。