Unix/Linux C++应用开发-C++预处理程序

C++应用程序开发中离不开预处理程序。预处理程序实际上是从C语言中继承而来。预处理程序在应用程序正式编译之前预先完成。C++编译器有专门内置的预处理器。本章将会就预处理相关的指令作详细介绍。

15.1 C++预处理简介

预处理指令并不属于C++语言中的语法关键字范畴。所有预处理指令定义都不需要以分号为结尾。预处理指令大多都以#符号开头。许多高级语言都不包含预处理器的实现,但C++作为一门优秀语言,包含了多种编程风格。它很好地吸收了C语言中的特色——预处理指令。

预处理指令由C++编译器中的预处理器模块处理。该预处理器可以识别应用程序中的#号开头的预处理指令,并在编译之前对其作出相应的处理。所有的预处理指令需要注意的是不能以分号为结尾,这是因为预处理指令不是C++语句。而配合预处理指令开头的#号用来区分普通应用程序语句与预处理指令语句。需要注意的是,预处理指令以#号开始,并且#号之前不允许有任何的空白字符。下面可以通过一个简单的预处理指令定义实例,了解基本预处理在应用程序中使用情况。

#define PI 3.1415926                                    //定义宏为PI,对应值为常量3.1415926

int main()

{

         doubler;                                                 //定义double型变量,表示圆的半径

         cout<<”Pleaseinput r:”<<endl;           //提示从键盘输入圆半径

         cin>>r;                                                     //输入圆半径变量

         cout<<”Circlearea:”<<PI*r*r<<endl; //打印输出圆面积计算表达式的值

         return0;
}

上述实例中使用宏预处理指令,定义PI值应用于圆面积的计算。预处理指令define用于定义PI之类的特殊符号,用以表示特定处理的数值。这样就可以避免在应用程序中使用时每次都需要编写一遍。这样,当需要修改对应的值时,只需要在预处理指令定义处修改一次。

上述#define预处理指令通过预处理器在应用程序编译之前先处理,即编译器编译程序前已经确定预处理中的指令处理,编译程序中调用时再自行展开即可。下面将会就预处理程序提供的各类处理指令作详细讲述。

15.2  C++预处理指令

预处理程序通过预处理器的内置功能对相应的指令进行预先处理。预处理指令提供了一系列的指令。通过归纳,这些提供的预处理指令分为文件包含、条件编译、布局控制与宏定义四种。下面将会就这四类指令作详细介绍。

15.2.1  预处理文件包含指令

C++预处理包含指令为#include。通常大型的软件应用程序不是由孤立的源文件组成。大部分都是以多文件并结合相应的代码管理最终协调运行。#include指令的出现,可以允许开发者包含使用C++标准库中提供的函数接口与类方法接口文件。另外,开发者也可以包含使用用户自定义库或者组件代码文件供实际模块程序使用。

#include预处理指令是在应用程序包含处将被包含的文件代码展开。大型软件系统的代码文件会分成多个。一般函数的声明、一些公用的常量、枚举、结构体、类的类型的定义等比较稳定。同时,有y经常有一些不需要经常修改的接口定义。这时,会将其放入后缀为.h的头文件中实现。而函数的定义,类中方法成员等具体实现放入源代码文件.cpp中。这样,头文件中包含了稳定不经常修改的代码,并且提供了接口的声明。在具体的包含源文件中可以包含该头文件定义实现具体接口或者直接调用,使得程序编译速度提高的同时又方便程序的管理。#include指令在C++应用程序中通常主要包含两类文件的包含。

q 一类是标准库提供的头文件。通常标准库对外提供的代码接口仅仅通过其头文件对外开放使用,具体源代码实现一般不需要开放,也不需要开发者关心C++中标准库头文件为无任何后缀的代码文件。该文件中主要声明了供应用程序使用的方法接口,应用的开发者只需要将其头文件通过#include命令进行包含,另外也需要在程序编译时连接这些库。

q 第二类是用户自定义实现的库。一般参照标准库实现的方式,将用户的应用程序进行划分。同样提供的库代码也分为头文件和源文件两个部分,如用户程序中自定义的组件、功能库等。这类库的使用也是可以通过包含其头文件,连接到相应库上就可以在具体应用中操作

在具体应用中,这两类#include指令可以包含多种形式,如下所示。

#include <iostream>                                                        //标准库IO处理头文件

#include <string>                                                     //标准库字符串string处理头文件

#include “MyString.h”                                             //自定义字符串类头文件

#include “../test/MyString.h”                                   //包含对应目录下的头文件

从上述几行代码中可以看出,包含标准库头文件与包含普通用户自定义头文件区别在于#include指令后的包含形式。一对尖括号表示所要包含的头文件在实际应用中需要从标准库提供的路径开始搜索查找起;引号之间包含的头文件则为用户自定义头文件,使用时需要在用户的工作目录下搜索。默认情况下,在用户当前工作目录下查找。另外当在不同目录下时,也可以直接指定包含该目录下的头文件。

#include包含指令在应用程序中可以多次包含。同时,包含的头文件中又可以包含其它的文件,即多重包含。预处理过程不会针对同一个头文件在展开时检查多次包含情况。此时,C++预处理指令提供了条件编译指令于设定编译时的条件。

15.2.2  预处理条件编译指令

对上一小节头文件被多次包含的情形,条件编译指令提供了很好的解决方法。#ifndef指令针对多次包含同一头文件情况作出限制,如下所示。

#ifndef      MYSTRING_H_

#define     MYSTRING_H_

class MyString

{

//类接口声明定义

};

#endif

上述实例中,将前面自定义封装字符串实例头文件运用条件编译指令限制多次被包含的情形。一旦#ifndef后的特殊符号已经被定义,则程序处理时可以忽略随后的代码内容不再包含处理。在上例中MyString类封装定义的头文件中加上条件编译指令,在使用MyString头文件时包含该头文件。每次包含使用时需要检测#ifndef后定义的特殊符号。如果该符号没有被定义,则随后定义该特殊符号,紧接着包含该头文件使用。如果已经定义了特殊符号,则会忽略随后的头文件代码处理。

#ifndef指令以#endif标注为结尾,其中则放入头文件代码定义的内容。多次包含同一文件时,检测到事先已经定义过的特殊符号MYSTRING_H_。随后就忽略从#ifndef#endif之间的定义从而避免重复包含。

除了#ifdef#endif指令,预处理条件编译指令还包含#if#ifndef#else#elif 4种。使用这些条件编译指令,可以使得应用程序中某部分代码在一定的条件下被忽略或者被处理。所以,条件编译指令可以用于编写在多种操作系统上运行的程序代码。下面将会详细的介绍条件编译指令的应用情况。

1.#if指令

#if指令通常配合#elif#else以及结束标识#endif灵活地控制是否需要编译其中的代码块。#if指令随后的表达式如果为非零,则对随后的代码进行编译。下面通过一个实际应用了解该指令运用情况。

#if     expression1

         …                        //代码块

#elif expression2

         …                        //代码块

#else

         …                        //代码块

#endif

上述代码小片段中首先判断#if指令后的条件表达式结果是否为真。例如,该指令后的表达式已经定义过则表明该条件表达式判断为真。随后#if后的代码块将会被包含入应用程序进行编译。如果#if指令后的条件表达式没有被定义,则表明该条件表达式判断结果为假。随后进入下一个判断即#elif。同样,如果elif指令后的条件表达式为真,则编译之后的代码块。直到前面条件表达式判断都为假的时候,则执行默认编译指令#else随后的代码块编译。

2.#ifdef

#ifdef指令不同之处在于其随后的标识符。#ifdef指令判断随后定义的标识符是否已经通过#define语句定义。如果已经定义,则将随后的代码块纳入程序编译的范围之内;否则,随后的代码块将会被忽略。上述定义实例方式使用#ifdef编译指令则如下所示。

#ifdef        identifier1

         …                        //代码块

#elif           identifier2

         …                        //代码块

#else

         …                        //代码块

#endif

#ifdef#if指令使用格式相同,同样是以#endif为结束标识。期间可以根据选择控制的判断情况将对应的代码块纳入应用程序编译范围中。与#if指令不同的是,#ifdef指令则是根据随后的标识符号是否定义来判断,从而将后续的代码块纳入应用程序中进行编译。上述实例中首先判断#ifdef指令后的标识符identifier1之前有没有定义。如果已经定义,则将随后的代码块纳入编译范围并直接转移至#endif之后。如果没有定义,则向下继续进行判断。最后如果前面判断条件都不成立,则将#else后的代码块直接纳入应用程序编译范围中。

#ifdef编译指令相反的是,#ifndef编译指令则表明随后的标识符号如果没有被定义则将随后包含的代码块加入应用程序编译范围。如果之前标识符号已经被定义过则将忽略随后包含的代码块。#ifndef条件编译指令最常见得应用是解决同一头文件被多次包含的情况。一旦#ifndef随后的标识符号没有被定义过,则证明该头文件在本应用程序中首次被包含使用,需要纳入应用程序编译中。而如果随后的标识符号已经被定义过,即证明其头文件可能在之前已经被包含。

15.2.3  预处理指令:常量符号定义

C++语言继承了C语言中宏的使用概念,#define预处理指令也称为宏定义语句。它主要用于在应用程序中定义符号常量。C++#define定义常量符号方式与本身该语言提供的const功能类似。通常,提倡使用const关键字定义常量,并加入应用程序使用。理由之一就是#define属于预处理器处理,不是语言提供的支持。很多时候,宏的复杂定义使用容易出错,而编译器不能很好的作出提示。但是#define宏指令的使用不仅仅针对常量符号,它有着更广阔的应用空间。在很多情况下,应用#define指令处理可以简化应用程序,提高程序的处理效率。

本小节主要讲述#define指令针对常量符号使用情况。更多宏的应用将会在后续章节介绍。下面通过一些基本的常量符号定义实例应用来了解#define指令的基本操作情况。

#define PI         3.14                                     //定义常量符号PI,表示圆周率

double area;                                          //定义浮点型变量,表示圆面积

area = PI*r*r;                                          //根据已经定义的PI常量计算圆面积

#define MAXLEN 100                          //定义常量符号MAXLEN,表示数组定义最大长度

char array[MAXLEN];                           //定义长度为MAXLEN长度的字符数组array

for(int i = 0;i < MAXLEN;i++)               //根据数组最大长度作出相应循环处理

{

         …

}

上述代码中,首先根据#define指令定义常量符号PI表示圆面积计算的圆周率。由于圆周率在现实计算应用中是保持不变的常量值,采用PI常量符号表示可以保持代码可读性。随后在应用程序中就可以应用PI常量符号,使用时直接展开使用对应的常量值替换即可。第二种使用方式应用于数组长度的表示。由于数组定义时,通常必须静态地确定其大小,从而让编译器静态分配内存空间。采用常量符号表示后,将通用的数组大小定义直接使用该符号来表示。例如,应用程序中处理数据需要的缓冲区定长设定,则可以采用#define预先定义常量符号表示其具体缓冲区大小。这样,在各处使用的缓冲区可以保持一致不易出错,同时修改也更加方便。

#define指令不仅可以定义常量符号,还可以定义常量表达式,即#define指令后常量符号对应的值可以是直接的常量数值,也可以是计算结果最终为常量数值的表达式。如下实例中,则定义常量表达式对应的符号。

#define symbol1 10

#define symbol2 100

#define symbol3 (symbol1*symbol2)

如上定义中symbol3直接为前两个常量符号计算结果的表示,并在应用程序中展开使用。下一节将会对宏定义带参数处理等更复杂的情形作详细的介绍。

15.3  宏定义

由于宏定义处理为C语言提供了一个功能强大的处理工具,C++语言中保留并支持了这一工具。这一方面是为了兼容更多现有的C程序,另一方面也是为了吸收了C语言相关处理的优点。前面小节已经就宏定义中常量符号的基本应用情况作了简单介绍,下面将会介绍宏定义处理中的复杂应用情形。

15.3.1  更多表达式宏定义

前面讲述#define指令时已经介绍过,宏定义的常量符号仅仅为其中一种应用。#define语句之后不仅仅可以定义对应的常量符号表示形式,另外也可以包含相应的表达式。表达式不仅可以是简单的常量表达式,也可以是为了实现某种基本功能而较为复杂的表达式。下面通过几个宏定义符号表示表达式情形的应用,了解宏定义中表达式表示操作的方式。

大型的生产系统中都会针对不同的业务模块处理制定相应的错误代码。这些代码用于标识不同的错误情况,便于程序出错时统一检查。错误代码通常可以根据需要划分为不同的段,每个段都有一个对应的起始错误号。以下代码首先定义ERROR_BASE符号表示框架文件中错误代码段的基数,随后根据不同的错误形式定义对应的常量符号表示。该常量符号所对应的即为常量表达式计算结果。上述实例中主要用于递增表示不同的错误类型代码。

#define ERROR_BASE 100                                                              //宏定义错误代码中应用

#define E_SYSNOTIFY_STARTUP ERROR_BASE+1

#define EAPPFRM_SYSINIT ERROR_BASE+2

同时宏定义中可以使用有意义的标识符号来表示相应的运算符号。以下代码则为定义清晰的符号表示对应的运算符,参与实际应用程序处理。这里主要定义了三类逻辑运算符,在应用程序中可以直接使用表示符号,并在对应处直接展开所表示的运算符。

#define AND &&                                                                                  //宏定义标识符表示运算符参与运算

#define OR ||

#define EQUAL ==

if(value1 AND value2)

{

         …               //代码块

}

if(value == 10 OR value == 100)

{

         …               //代码块

}

以下代码则直接将移位运算采用宏定义标识符号表示。<<10表示将操作数左移10位,实例中定义符号LEFT_DEVIATION_10表示该操作,随后在应用程序中value2LEFT_DEVIATION_10该语句运行时直接通过展开预定义左移10位的操作符参与运算,则表示将value2左移10位并给value1赋值。

#define LEFT_DEVIATION_10 <<10                                                        //宏定义标识符表示移位运算

value1 = value2 LEFT_DEVIATION_10;

以下代码主要演示了宏定义中操作复杂表达式的情形。通常闰年的计算方式可以通过以下的表达式计算来判断相应的年份是否为闰年。由于闰年的计算算法为每四年一闰,其中百年不闰四百年再闰。即闰年年份应该为除尽4并且不能被100除尽;另外,能够被400除尽也表示闰年。上述实例采用宏定义常量符号LEAP_YEAR,表示随后复杂的表达式。符号LEAP_YEAR在应用程序中可以直接用来判断对应表达式计算结果真假,根据判断结果来表示是否为闰年年份。

#define LEAP_YEAR year % 4 == 0 && year %100 != 0 || year % 400 == 0

if(LEAP_YEAR)                                     //如果是闰年则执行if结构中的代码块

{

         …               //代码块

}

15.3.2  带参数的宏定义

上述实例中第四个采用宏定义表示闰年的实现方式有一个不足之处。year必须是事先定义的常量符号,这就限制了操作时候的灵活性。而宏定义中常量符号表示的表达式可以通过增加接受外部程序参数传入的方式,类似于函数的使用方式。这样就让宏定义具有更强的功能。

判断年份是否闰年的基本算法前面已经介绍过,下面将会通过定义带参数year的宏定义来实现。

#define LEAP_YEAR(year)  year % 4 ==0 && year % 100!=0 || year% 400 ==0

if(LEAP_YEAR(year))

{

         …                                 //代码块

}

上述实现方式中,将year作为宏定义符号LEAP_YEAR的参数,并使用圆括号包含。随后,依然是闰年判断计算表达式定义。应用程序实际使用中只需要使用符号LEAP_YEAR同时带有相应的实参表示其年份即可。由于宏定义的符号在预处理程序中是进行直接的替换,所以事先year并不需要声明其类型,只需要在实际应用中直接给出值即可。实际应用程序判断中,直接将该符号表示的表达式展开,采用传入的year参数来带入该表达式进行计算判断。

这种带参数的宏定义方式使得宏具有可操作更多应用的能力。与函数相比,其优势在于不需要事先声明该参数。但是,同时带来一个缺点,即程序在编译过程中缺少了编译器层的函数参数类型检查。一旦出现错误,就比较的隐蔽,不容易察觉。

下面将会通过一些带参数宏定义实例,帮助读者更多地了解宏定义带参数应用情况,实例如下。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter1501_01.hchapter1501_01.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter1501

* 源文件chapter1501_01.cppchapter1501_01.h

* 宏定义使用演示

*/

//带参数宏定义头文件chapter1501_01.h

#ifndef MACROPARAMETER_H_

#define MACROPARAMETER_H_

#include <iostream>

using namespace std;

 

#define add(a,b) a+b                                                                                   //加法计算宏定义

#define cube(x) x*x*x                                                                      //立方计算宏定义

#define max(a,b) (((a)>(b))?(a):(b))                                                 //大小比较计算宏定义

#define LEAP_YEAR(year) year%4==0 &&year%100!=0\                  //闰年计算宏定义

        ||year%400==0

#endif

//带参数宏定义源文件chapter1501_01.cpp

#include "chapter1501_01.h"

 

int main()

{

         inta,b,year;                                                                                  //定义3个整型变量

         a =6;                                                                                             //整型变量a赋初值6

         b =7;                                                                                             //整型变量b赋初值7

         year= 2008;                                                                                //整型变量year赋初值2008

         cout<<"a+b:"<<add(a,b)<<endl;                                                       //调用加法计算宏,打印输出其结果

         cout<<"acube:"<<cube(a)<<endl;                                          //调用立方计算宏,打印输出其结果

         cout<<"maxa and b:"<<max(a,b)<<endl;                             //调用大小比较计算宏,打印输出其结果

         if(LEAP_YEAR(year))                                                                //调用闰年计算宏,判断输入年份是否闰年

         {

                   cout<<year<<"is a leap year!"<<endl;                          //如果闰年则打印闰年信息

         }

         else

                   cout<<year<<"is not a leap year!"<<endl;                            //否则打印不闰年信息

         return0;

}

本实例通过宏定义的方式,将闰年计算公式定义为宏常量,在应用程序中演示宏常量的一般使用方法。程序主要在主函数中完成,程序具体剖析见程序注释与后面讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter1501_01.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter1501_01.o

CC=g++

 

chapter1501_01: $(OBJECTS)

         $(CC)$(OBJECTS) -g -o chapter1501_01

clean:

         rm-f chapter1501_01 core $(OBJECTS)

submit:

         cp-f -r chapter1501_01 ../bin

         cp-f -r *.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录。通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make

g++    -c -ochapter1501_01.o chapter1501_01.cpp

g++ chapter1501_01.o -g -o chapter1501_01

[developer@localhost src]$ make submit

cp -f -r chapter1501_01 ../bin

cp -f -r *.h ../include

[developer@localhostsrc]$ cd ../bin

[developer@localhostbin]$ ./chapter1501_01

a+b:13

a cube:216

max a and b:7

2008 is a leap year!

本实例中首先将带参数的宏定义放入头文件代码中,供需要应用的应用程序直接包含使用。头文件中包含了4个带参数宏的定义。从本实例中看出宏可以带有多个参数定义,参数之间采用逗号分隔,类似于函数中的参数表定义方式。

带参数宏定义中参数表放入圆括号中,圆括号表示的参数表必须与定义的标识符号之间没有空格。否则,会被认为是后面的表达式定义。宏定义中常量符号与对应的表达式之间采用空格隔开,后面表达式定义之间为了统一代码风格可以使用空格。宏定义中凡是常量标识符空格后的任何表达式定义都视为所要表示的对象。

上述代码中总共有4个带参数宏定义实例,分别为add(两数加法操作符号)、cube(单数x立方计算符号)、max(两数大小比较符号)以及闰年计算的常量符号定义。4个实例中分别从单参数宏定义到多参数宏定义。另外也演示了同一个宏定义中,一旦需要换行的情形可以使用“\”符号。下面将会分析4个实例中宏定义情况,以及应用中应该注意的基本考虑点的说明。

1.add(a,b)

第一个宏定义中表示两数加法计算,add(a,b)表示宏定义的标识符。其中,ab为宏标识符的参数,所要表示的表达式为a+b。最终在应用程序中调用出展开的形式即为传入的实参的a+b的表达式计算结果。当然,带参数的宏定义与函数区别在于参数使用上。宏定义中形参是不需要定义和分配内存空间的。只要在实参定义时,说明其基本类型然后分别去替换即可。主应用程序中首先定义两个整型变量ab,并且赋上初值分别为67。调用处直接add(a,b)后,在其调用点展开的表达式为a+b,根据实参的值直接计算出结果。

宏中的参数替换,实参不仅仅可以为变量或者直接的常量数值,往往还可以为对应表达式的替换。例如,此时调用add(a+1,b+1),则应用程序替换中将会替换为a+1+b+1,最后直接计算其表达式结果。

2.cube(a)

第二个实例中为单参数宏定义,表示参数x的立方计算结果。主程序中根据传入的实参直接替换参数并计算其结果,调用处cube(a)其展开表达式为a*a*a6的立方计算。此时需要注意的是,一旦在调用中采用表达式替换比如cube(a+1)时,原先开发者的本意是计算a+1后的立方计算。但是由于宏定义中的参数是直接替换计算,表达式为x*x*x传入的实参a值为6,替换后即为6+1*6+1*6+1,得不到预期的结果。

因此在定义带参数的宏时,最好将后面的表达式中参数采用括号标注。如果要达到上述计算的正确预期结果,宏定义中表达式应该为(x)*(x)*(x)。此时调用处展开的表达式为(a+1)*(a+1)*(a+1)即计算得到预期表达式a+1的立方结果。

3.cube(a+1)

第三个参数宏定义中则注意了表达式中替换参数的情况,将需要替换的参数在表达式中都采用括号单独括起来参与运算。该宏定义主要用于计算两数比较大小的结果,替换的表达式中应用条件运算符计算。此时应用程序调用中如果为cube(a+1)则能够计算得出预期结果。

4.闰年计算

最后一个带参数的宏定义为前面讲述过的闰年情况。参数year为需要检查的是否闰年的年份数。通过实参替换计算得出结果,从而判断是否为闰年年份。在宏定义中可以看出,代码书写中如果需要换行表示的话可以直接使用“\”表示即可。预处理程序遇到该符号会自动将下一行的代码定义连接到本行形成整体的表达式。上述实例中根据输入实参为2008年份数,判断其展开的表达式计算是否为真。如果为真,则表示2008为闰年年份。

15.3.3  ###操作符的宏

预处理中“#”与“##”操作符有着特别的含义。“#”操作符加入带参数的宏定义中用于放置在表达式参数前,预处理见到#操作符后会将随后的参数生成对应的参数字符串。下面通过使用实例演示“#”操作符。

#define string(str) #str

cout<<string(teststring)<<endl;

上述代码实例中首先定义带参数的宏,符号为string其中参数为str。随后的表达式中“#”操作符后直接为参数。由于“#”操作符表示在应用中直接将参数展开为对应的字符串。随后的代码中采用cout输出对象打印调用宏定义符号的结果。此时传入的参数为teststring,“#”操作符作用即为转换对应的参数teststring为相应的字符串。此时打印输出的结果为字符串teststring。“#”操作符的作用即为将宏定义中的参数转换为对应的字符串。

操作符“##”其作用则是将两个参数符号连接起来,操作符的前后面都可以是对应的参数。下面依然通过一个宏定义实例来了解“##”操作符的基本使用情况,实例代码如下所示。

#define TEST(n) value ## n

#define PRINT_TEST(n) printf(“value” #n“=%d\n”,value ## n)

int TEST(1) = 100;

int TEST(2) = 200;

PRINT_TEST(1);

PRINT_TEST(2);

上述实例中首先定义宏TEST,带有参数n。随后表达式为value##n,此时程序中展开为valuen。其中,n为实参替换。第二个宏定义中同样参数为n。随后的表达式为一个pingtf打印输出的语句。该语句中首先为字符串value,紧跟着#n将实参n转化为字符串。双引号后为设定输出的格式为整型,最后将valuen连为一个新的字符串。

从上述的实例演示来看,“#”操作符用于将宏定义参数替换成对应的常量字符串,在实际应用中根据需要应用。“##”操作符则主要用于将两个符号连接起来,该操作符的前后都可以为宏参数。实际应用程序中展开会将“##”前后的参数符号连接起来创建一个新的符号用于处理。

15.4  小结

C++应用程序中,预处理指令应用的掌握非常重要。良好的可扩展性强的应用程序中,往往都少不了一定数量的预处理指令的使用。初学者刚开始接触这部分知识时可能暂无法深入领会,对于本章学习应该多写应用实例来体会预处理指令在应用程序中的作用,掌握预处理指令的一般使用方式。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第1章 计算机与C++编程简介 1.1 简介 1.2 什么是计算机 1.3 计算机组成 1.4 操作系统的变革 I.5 个人计算、分布式计算与客户/a匠务器计算 l. 6 机器语言、汇编语言和高级语言 1.7 C语言与C++的历史 1.8 C++标准库 1.9 Java、Internet与万维网 1.10 其他高级语言 1.11 结构化编程 1.12 典型C++环境基础 1.13 C++与本书的一般说明 1.14 C++编程简介 1.15 简单程序:打印一行文本 1.16 简单程序:两个整数相加 1.17 内存的概念 1.18 算术运算 1.19 判断:相等与关系运算符 1.20 新型头文件与名字空间 1.21 有关对象的思考 小结 术语 自测练习 自测练习答案 练习 第2章 控制结构 2.1 简介 2.2 算法 2.3 伪代码 2.4 控制结构 2.5 if选择结构 2.6 if/e1se选择结构 2.7 while重复结构 2.8 构造算法:实例研究1(计数器控制重复) 2.9 构造算法与自上而下逐步完善:实例研究2(标记控制重复) 2.10 构造算法与自上而下逐步完善:实例研究3(嵌套控制结构) 2.11 赋值运算符 2.12 自增和自减运算符 2.13 计数器控制循环的要点 2.14 for重复结构 2.15 for结构使用举例 2.16 switch多项选择结构 2.17 do/while重复结构 2.18 break和continue语句 2.19 逻辑运算符 2.20 混淆相等(.==)与赋值(=)运算符 2.21 结构化编程小结 2.22 有关对象的思考:确定问题中的对象 小结 术语 自测练习 自测练习答案 练习 第3章 函数 3.1 简介 3.2 C++程序组件 3.3 数学函数库 3.4 函数 3.5 函数定义 3.6 函数原型 3.7 头文件 3.8 随机数产生器 3.9 案例:机会游戏与enum简介 3.10 存储类 3.11 作用域规则 3.12 递归 3.13 使用递归举例:Fibonacci数列 3.14 递归与迭代 3.15 带空参数表的函数 3.16 内联函数 3.17 引用与引用参数 3.18 默认参数 3.19 一元作用域运算符 3.20 函数重载 3.21 函数模板 3.22 有关对象的思考:确定对象属性 小结 术语 自测练习 自测练习答案 练习 第4章 数组 4.1 简介 4.2 数组 4.3 声明数组 4.4 使用数组的举例 4.5 将数组传递给函数 4.6 排序数组 4.7 实例研究:用数组计算平均值、中数和模 4.8 查找数组:线性查找与折半查找 4.9 多下标数组 4.10 有关对象的思考:确定类的行为 小结 术语 自测练习 自测练习答案 练习 递归练习 第5章 指针与字符串 5.1 简介 5.2 指针变量的声明与初始化 5.3 指针运算符 5.4 按引用调用函数 5.5 指针与常量限定符 5.6 按引用调用的冒泡排序 5.7 指针表达式与指针算法 5.8 指针与数组的关系 5.9 指针数组 5.10 实例研究:洗牌与发牌 5.11 函数指针 5.12 字符与字符串处理简介 5.12.1 字符与字符串基础 5.12.2 字符串处理库的字符串操作函数 5.13 有关对象的思考:对象间的交互 小结 术语 自测练习 自测练习答案 练习 特殊小节:建立自己的计算机 更多的指针练习 字符串操作练习 特殊小节:高级字符串操作练习 复杂字符串操作练习 第6章 类与数据抽象(一) 6.1 简介 6.2 结构定义 6.3 访问结构成员 6.4 用struct实现用户自定义类型Time 6.5 用类实现Time抽象数据类型 6.6 类范围与访问类成员 6.7 接口与实现方法的分离 6.8 控制对成员的访问 6.9 访问函数与工具函数 6.10 初始化类对象:构造函数 6.11 在构造函数中使用默认参数 6.12 使用析构函数 6.13 何时调用构造函数与析构函数 6.14 使用数据成员和成员函数 6.15 微妙的陷阱:返回对Private数据成员的引用 6.16 通过默认的成员复制进行赋值 6.17 软件复用性 6.18 有关对象的思考:编写电梯模拟程序的类 小结 术语 自测练习 自测练习答案 练习 第7章 类与数据抽象(二) 7. 1 简介 7.2 const(常量)对象与const成员函数 7.3 复合:把对象作为类成员 7.4 友元函数与友元类 7.5 使用this指针 7.6 动态内存分配与new和delete运算符 7.7 static类成员 7.8 数据抽象与信息隐藏 7.8.1 范例:数组抽象数据类型 7.8.2 范例:字符串抽象数据类型 7.8.3 范例:队列抽象数据类型 7.9 容器类与迭代 7.10 代理类 7.11 有关对象的思考:在电梯模拟程序中使用复合和动态对象管理 小结 术语 自测练习 自测练习答案 练习 第8章 运算符重载 8.1 简介 8.2 运算符重载的基础 8.3 运算符重载的限制 8.4 用作类成员与友元函数的运算符函数 8.5 重载流插入与流读取运算符 8.6 重载一元运算符 8.7 重载二元运算符 8.8 实例研究:Array类 8.9 类型之间的转换 8.10 实例研究:String类 8.11 重载十十与—— 8.12 实例研究:Date类 小结 术语 自测练习 自测练习答案 练习 第9章 继承 9.1 简介 9.2 继承:基类和派生类 9.3 Protected成员 9.4 把基类指针强制转换为派生类指针 9.5 使用成员函数 9.6 在派生类中重定义基类成员 9.7 Public、Protected和Private继承 9.8 直接基类和间接基类 9.9 在派生类中使用构造函数和析构函数 9.10 将派生类对象隐式转换为基类对象 9.11 关于继承的软件工程 9.12 复合与继承的比较 9.13 对象的“使用”关系和“知道”关系 9.14 实例研究:类Point、CircIe和Cylinder 9.15 多重继承 小结 术语 自测练习 自测练习答案 练习 第10章 虚函数和多态性 10.1 简介 10.2 类型域和switch语句 10.3 虚函数 10.4 抽象基类和具体类 10.5 多态性 10.6 实例研究:利用多态性的工资单系统 10.7 新类和动态关联 10.8 虚析构函数 10.9 实例研究:继承接口和实现 10.10 多态、虚函数和动态关联 小结 术语 自测练习 自测练习答案 练习 第11章 C++输入/输出流 11.1 简介 11.2 流 11.2.1 iosbeam类库的头文件 11.2.2 输入/输出流类和对象 11.3 输出流 11.3.1 流插入运算符 11.3. 2 连续使用流插入/流读取运算符 11.3.3 输出char类型的变量 11.3.4 用成员函数put输出字符和put函数的连续调用 11.4 输入流 11.4.1 流读取运算符 11.4.2 成员函数get和getline 11.4.3 istream类中的其他成员函数(Peek、Putback和ignore) 11.4.4 类型安全的I/0 11.5 成员函数read、gcount和write的无格式输人/输出 11.6 流操纵算子 11.6.1 整数流的基数:流操纵算子dec、oct、hex和setbase 11.6.2 设置浮点数精度(Precision、setprecision) 11.6.3 设置域宽(setw、width) 11.6.4 用户自定义的流操纵算子 11.7 流格式状态 11.7.1 格式状态标志 11.7.2 尾数零和十进制小数点(ios:showpoint) 11.7.3 对齐(ios::left、ios::right、ios::internal) 11.7.4 设置填充字符(fill、setfill) 11.7.5 整数流的基数:(ios::dec、ios::oct、ios::hex、ios::showbase) 11. 7.6 浮点数和科学记数法(ios::scientific、ios::fixed) 11.7.7 大/小写控制(ios:uppercase) 11.7.8 设置及清除格式标志(flags、setiosflags、resetiosflags) 11.8 流错误状态 11.9 把输出流连到输入流上 小结 术语 自测练习 自测练习答案 练习 第12章 模板 12.1 简介 12.2 函数模板 12.3 重载模板函数 12.4 类模板 12.5 类模板与非类型参数 12.6 模板与继承 12.7 模板与友元 12.8 模板与static成员 小结 术语 自测练习 自测练习答案 练习 第13章 异常处理 13.1 简介 13.2 何时使用异常处理 13.3 其他错误处理方法 13.4 C十十异常处理基础:try、throw、catch 13.5 简单异常处理例子:除数为o 13.6 抛出异常 13.7 捕获异常 13.8 再抛出异常 13.9 异常指定 13.10 处理意外异常 13.11 堆栈解退 13.12 构造函数、析构函数与异常处理 13.13 异常与继承 13.14 处理new故障 13.15 auto_ptr类与动态内存分配 13.16 标准库异常层次 小结 术语 自测练习 自测练习答案 练习 第14章 文件处理 14.1 简介 14.2 数据的层次 14.3 文件和流 14.4 建立顺序访问文件 14.5 读取顺序访问文件中的数据 14.6 更新顺序访问文件 14.7 随机访问文件 14.8 建立随机访问文件 14.9 向随机访问文件中随机地写入数据 14.10 从随机访问文件中顺序地读取数据 14.11 实例研究:事务处理程序 14.12 对象的输入/输出 小结 术语 自测练习 自测练习答案 练习 第15章 数据结构 15.1 简介 15.2 自引用类 15.3 动态内存分配 15.4 链表 15.5 堆栈 15.6 队列 15.7 树 小结 术语 自测练习 自测练习答案 练习 特殊小节:建立自己的编译器 第16章 位、字符、字符串和结构 16.1 简介 16.2 结构的定义 16.3 结构的初始化 16.4 函数和结构 16.5 类型定义:typedef 16. 6 范例:高效的洗牌和发牌模拟程序 16.7 位运算符 16.8 位段 16.9 字符处理库 16.10 字符串转换函数 16.11 字符串处理库中的查找函数 16.12 字符串处理库中的内存函数 16.13 字符串处理库中的其他函数 小结 术语 自测练习 自测练习答案 练习 第17章 预处理器 17.1 简介 17.2 预处理指令#include 17.3 预处理指令#define:符号常量 17.4 预处理指令#define:宏 17.5 条件编译 17.6 预处理指令#error和#pragma 17.7 运算符#和## 17.8 行号 17.9 预定义的符号常量 17.10 断言(宏assert) 小结 术语 自测练习 自测练习答案 练习 第18章 C语言遗留代码问题 18.1 简介 18.2 UNIX和DOS系统中的输入/输出重定向 18.3 变长参数表 18.4 使用命令行参数 18.5 对编译多个源文件程序的说明 18. 6 用edt和atexit终止程序的执行 18.7 volatile类型限定符 18.8 整数和浮点数常量的后缀 18.9 信号处理 18.10 动态内存分配:函数calloc和realloc 18.11 无条件转移:goto语句 18.12 联合体 18.13 连接指定 小结 术语 自测练习 自测练习答案 练习 第19章 string类与字符串流处理 19.1 简介 19.2 string的赋值与连接 19.3 比较string 19.4 子串 19.5 交换string 19.6 string的特性 19.7 寻找string中的字符 19.8 替换string中的字符 19.9 在string中插入字符 19.10 转换成C语言式char 字符串 19.11 迭代器 19.12 字符串流处理 小结 术语 自测练习 自测练习答案 练习 第20章 标准模板库(STL) 20.1 标准模板库(STL)简介 20.1.1 容器简介 20.1.2 迭代器简介 20.1.3 算法简介 20.2 顺序容器 20.2.1 vector顺序容器 20.2.2 1ist顺序容器 20.2.3 deque顺序容器 20.3 关联容器 20.3.1 multiset关联容器 20. 3.2 set关联容器 20.3.3 mdtimap关联容器 20.3.4 map关联容器 20.4 容器适配器 20.4.1 stack适配器 20.4.2 queue适配器 20.4.3 Priority_queue适配器 20.5 算法 20.5.1 fill、fill_n、generate与generate_n 20.5.2 equal、mismatch和1exicographical_compare 20.5.3 remove、remove_if、 remove_copy和remove_copy_if 20.5.4 replace、replace_if、replace_copy和replace_copy_if 20.5.5 数学算法 20.5.6 基本查找与排序算法 20.5.7 swap、iter_swap和swap_ranges 20.5.8 copy—backward、 merge、 unique和reverse 20.5.9 inplace_merge、 unique—copy和reverse—copy 20.5.10 集合操作 20.5.11 1ower—bound、 upper—bound和equal_range 20.5.12 堆排序 20.5.13 min和max 20.5.14 本章未介绍的算法 20.6 bitset类 20.7 函数对象 小结 术语 自测练习 自测练习答案 练习 Internet和World Wide Web中的STL资源 STL文献 第21章 ANSI/ISO C++标准语言补充 21.1 简介 21.2 bool数据类型 21.3 static—cast运算符 21.4 const—cast运算符 21.5 reinterpret—cast运算符 21.6 名字空间 21.7 运行时类型信息(RTTI) 21.8 运算符关键字 21.9 explicit构造函数 21.10 mutable类成员 21.11 类成员指针(.和—>) 21.12 多重继承与virtual基类 21.13 结束语 小结 术语 自测练习 自测练习答案 练习 附录A 运算符的优先级与结台律 附录B ASCII字符集 附录C 数值系统 附录D 有关C++的Internet与Web资源 参考文献 【媒体评论】

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值