C语言提供了结构struct数据类型, 用于组织数据。显然, 一个完整的复数是由实部和虚部两部分组成, 可以用结构把密切相关的数据包装成一个集装箱。下面是复数结构类型的定义:
struct COMPLEX { |
定义了COMPLEX类型以后, 就可以把它作为数据类型直接使用了。下面定义了三个复数类型的变量:
COMPLEX a, b, c;
这三个变量都是结构变量, 它们既可以作为一个整体来使用, 也可以操作其中的数据成员。例如:
a = b; // 把结构变量b的16字节全部内容赋值给a, 使a与b相同
c.rpart = a.rpart + b.rpart; // 把a的实部与b的实部相加, 结果存入c的实部
下面是完整的程序:
#include <iostream> struct COMPLEX { int main() { |
假设用户输入的第一复数 3+4i, 第二个复数是 2-5i, 则程序的运行结果如下:
a = ? 3 4 |
在这个版本里, 采用了结构体来表示复数数据类型, 整个复数结构共占用16字节内存空间, 每个复数包含实部rpart和虚部ipart两个成员, 它们都是实数, 分别占8字节内存空间, 数据的结构非常清晰。例如复数 3+4i, 在内存的存储结构如下图所示:
|
|
下面介绍用工程的方法来书写程序。在工程中, 我们经常使用头文件, 用于书写类型和函数的声明, 而代码部分一般写入程序文件中。例如:
COMPLEX.H
struct COMPLEX { |
在程序文件中需要用到有关的声明时, 使用包含预处理命令即可。例如:
MAIN.CPP
#include <iostream> #include "complex.h" int main() { |
包含命令在编译之前, 把指定文件的内容取到该命令处, 然后再进行编译。因此, main.cpp在编译之前, 经过预处理以后将变成下面的样子:
...... using namespace std; struct COMPLEX { int main() { | 文件iostream的内容
文件complex.h的内容 |
包含命令有两种格式:
#include <文件名>
#include "文件名"
在#include命令中指定被包含的文件名时, 可以使用尖括号, 也可以使用双引号。两者的区别在于: 使用双引号时, 编译器将首先在用户文件夹中查找指定的文件, 如果找不到, 再到系统的文件夹中查找; 而使用尖括号时, 编译器只到系统的文件夹中查找文件。所以, 当包含系统的头文件时, 最好使用尖括号, 而包含用户自定义的头文件时, 必须用双引号。
包含命令可以嵌套使用, 假设有如下两个头文件:
HEAD1.H | HEAD2.H | |||
|
|
使用包含命令时, 预处理过程如下图所示:
PROG1.CPP | |||||||
| → |
| → |
|
当程序较复杂, 文件较多时, 一不小心, 就有可能发生重复包含的问题, 例如:
PROG2.CPP |
|
| |||||
| → |
| → |
|
我们看到, 这里的复数结构定义了两次, 产生了重复定义的问题。
为了解决重复包含问题, 需要使用宏定义命令和条件编译命令。宏定义命令的一般形式为:
#define 宏名字 字符串
宏名字是一个标识符, 它主要作用是在编译之前, 将程序中的所有宏名字替换成后面的字符串, 然后再进行编译, 例如:
PROG2.CPP |
| |||
| → |
|
有时候, 出于特殊需要, 可以只定义宏名字, 并不作替换, 则可以省去宏名字后的字符串:
#define 宏名字
条件编译命令的一般形式为:
#if 条件1 |
若条件1成立, 则编译器将对代码段1进行编译, 并忽略其余的代码段;
若条件1不成立, 则编译器忽略代码段1, 测试条件2, 若条件2成立, 则编译器将对代码段2进行编译, 并忽略其余的代码段;
若条件2不成立, 则编译器忽略代码段2, 测试条件3, 若条件3成立, 则编译器将对代码段3进行编译, 并忽略其余的代码段;
...(以下依此类推)...
若条件n-1不成立, 则编译器忽略代码段n-1, 测试条件n, 若条件n成立, 则编译器将对代码段n进行编译, 并忽略其余的代码段;
若从条件1到条件n都不成立, 则代码段1到代码段n都被忽略, 编译器将编译代码段n+1。
因此, 这n+1段代码中, 编译器根据情况只选择其中的一段代码进行编译, 即只有一段代码有效。
可以通过defined测试是否定义过某个宏名字, 例如:
#if defined 宏名字 | 如果定义了宏名字, 则编译代码段1, 忽略代码段2; 否则忽略代码段1, 编译代码段2。 |
条件编译命令还有另外两种特殊的格式, 专用于判断是否定义过某个宏名字:
#ifdef 宏名字 | 如果定义了宏名字, 则编译代码段1, 忽略代码段2; 否则忽略代码段1, 编译代码段2。 |
#ifndef 宏名字 | 如果没有定义宏名字, 则编译代码段1, 忽略代码段2; 否则忽略代码段1, 编译代码段2。 |
对于重复包含问题, 利用宏定义和条件编译命令, 可以使某一段代码只在第一次出现时有效, 以后既使重复出现, 也会被编译器忽略掉。常见的写法是在头文件里使用下面的框架, 把代码段置于#ifndef/#define和#endif命令之间:
#ifndef 宏名字 ...// 数据类型和函数原型的声明 #endif |
当头文件第一次被包含时, 显然这个宏名字未定义, 于是#ifndef和#endif之间的内容有效。进入这段代码, 它首先定义这个宏名字, 然后是我们写下的数据类型和函数原型的声明。当以后再次包含该头文件时, 由于这个宏名字已经定义过, 所以#ifndef和#endif之间的内容无效, 被编译器忽略。
用这个方法改写后的头文件如下:
COMPLEX.H
#ifndef _COMPLEX_H_ struct COMPLEX { #endif |