1-02 C++起步: 用结构组织数据 —— 数据封装初步

C语言提供了结构struct数据类型, 用于组织数据。显然, 一个完整的复数是由实部和虚部两部分组成, 可以用结构把密切相关的数据包装成一个集装箱。下面是复数结构类型的定义:

struct COMPLEX {
    double rpart, ipart;
};

定义了COMPLEX类型以后, 就可以把它作为数据类型直接使用了。下面定义了三个复数类型的变量:
COMPLEX a, b, c;

这三个变量都是结构变量, 它们既可以作为一个整体来使用, 也可以操作其中的数据成员。例如:
    a = b; // 把结构变量b的16字节全部内容赋值给a, 使a与b相同
    c.rpart = a.rpart + b.rpart; // 把a的实部与b的实部相加, 结果存入c的实部

下面是完整的程序:

#include <iostream>
using namespace std;

struct COMPLEX {
    double rpart, ipart;
};

int main() {
    COMPLEX a, b, c;
    cout << "a = ? "; cin >> a.rpart >> a.ipart; // 输入第一个复数
    cout << "b = ? "; cin >> b.rpart >> b.ipart; // 输入第二个复数
    c.rpart = a.rpart + b.rpart;
    c.ipart = a.ipart + b.ipart; // 求和
    cout << "c = (" << c.rpart << ", " << c.ipart << ")/n"; // 输出结果
    return 0;
}

假设用户输入的第一复数 3+4i, 第二个复数是 2-5i, 则程序的运行结果如下:

a = ? 3 4
b = ? 2 -5
c = (5, -1)

在这个版本里, 采用了结构体来表示复数数据类型, 整个复数结构共占用16字节内存空间, 每个复数包含实部rpart和虚部ipart两个成员, 它们都是实数, 分别占8字节内存空间, 数据的结构非常清晰。例如复数 3+4i, 在内存的存储结构如下图所示:

 

COMPLEX

rpart

ipart

3

4


下面介绍用工程的方法来书写程序。在工程中, 我们经常使用头文件, 用于书写类型和函数的声明, 而代码部分一般写入程序文件中。例如:

COMPLEX.H

struct COMPLEX {
    double rpart, ipart;
};

在程序文件中需要用到有关的声明时, 使用包含预处理命令即可。例如:

MAIN.CPP

#include <iostream>
using namespace std;

#include "complex.h"

int main() {
    COMPLEX a, b, c;
    cout << "a = ? "; cin >> a.rpart >> a.ipart; // 输入第一个复数
    cout << "b = ? "; cin >> b.rpart >> b.ipart; // 输入第二个复数
    c.rpart = a.rpart + b.rpart;
    c.ipart = a.ipart + b.ipart; // 求和
    cout << "c = (" << c.rpart << ", " << c.ipart << ")/n"; // 输出结果
    return 0;
}

包含命令在编译之前, 把指定文件的内容取到该命令处, 然后再进行编译。因此, main.cpp在编译之前, 经过预处理以后将变成下面的样子:

......

using namespace std;

struct COMPLEX {
    double rpart, ipart;
};

int main() {
    COMPLEX a, b, c;
    cout << "a = ? "; cin >> a.rpart >> a.ipart; // 输入第一个复数
    cout << "b = ? "; cin >> b.rpart >> b.ipart; // 输入第二个复数
    c.rpart = a.rpart + b.rpart;
    c.ipart = a.ipart + b.ipart; // 求和
    cout << "c = (" << c.rpart << ", " << c.ipart << ")/n"; // 输出结果
    return 0;
}

文件iostream的内容

 

文件complex.h的内容
 
 

 
 
 
 
 
 
 
 
 

包含命令有两种格式:

#include <文件名>

#include "文件名"

在#include命令中指定被包含的文件名时, 可以使用尖括号, 也可以使用双引号。两者的区别在于: 使用双引号时, 编译器将首先在用户文件夹中查找指定的文件, 如果找不到, 再到系统的文件夹中查找; 而使用尖括号时, 编译器只到系统的文件夹中查找文件。所以, 当包含系统的头文件时, 最好使用尖括号, 而包含用户自定义的头文件时, 必须用双引号。

包含命令可以嵌套使用, 假设有如下两个头文件:

HEAD1.H

 

HEAD2.H

struct COMPLEX {
    double rpart, ipart;
};

 

#include "head1.h"
void Input(COMPLEX &x);
void Output(COMPLEX x);

使用包含命令时, 预处理过程如下图所示:

PROG1.CPP

#include "head2.h"
 
 

 

int main() {
    .....
    return 0;
}

#include "head1.h"
 
 
void Input(COMPLEX &x);
void Output(COMPLEX x);

int main() {
    .....
    return 0;
}

struct COMPLEX {
    double rpart, ipart;
};
void Input(COMPLEX &x);
void Output(COMPLEX x);

int main() {
    .....
    return 0;
}

当程序较复杂, 文件较多时, 一不小心, 就有可能发生重复包含的问题, 例如:

PROG2.CPP

 

 

 

 

#include "head1.h"
 
 

#include "head2.h"
 
 

 

int main() {
    .....
    return 0;
}

struct COMPLEX {
    double rpart, ipart;
};

#include "head1.h"
 
 
void Input(COMPLEX &x);
void Output(COMPLEX x);

int main() {
    .....
    return 0;
}

struct COMPLEX {
    double rpart, ipart;
};

struct COMPLEX {
    double rpart, ipart;
};
void Input(COMPLEX &x);
void Output(COMPLEX x);

int main() {
    .....
    return 0;
}

我们看到, 这里的复数结构定义了两次, 产生了重复定义的问题。

为了解决重复包含问题, 需要使用宏定义命令和条件编译命令。宏定义命令的一般形式为:

#define 宏名字 字符串

宏名字是一个标识符, 它主要作用是在编译之前, 将程序中的所有宏名字替换成后面的字符串, 然后再进行编译, 例如:

PROG2.CPP

 

 

#define pi 3.14159

int main() {
    .....
    y = sin(x*pi/180);
    .....
   return 0;
}

 

int main() {
    .....
    y = sin(x*3.14159/180);
    .....
   return 0;
}

有时候, 出于特殊需要, 可以只定义宏名字, 并不作替换, 则可以省去宏名字后的字符串:
    #define 宏名字

条件编译命令的一般形式为:

#if 条件1
    ...// 代码段1
#elif 条件2
    ...// 代码段2

#elif 条件n
    ...// 代码段n
#else
    ...// 代码段n+1
#endif

若条件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
#else
    ... // 代码段2
#endif

如果定义了宏名字, 则编译代码段1, 忽略代码段2;

否则忽略代码段1, 编译代码段2。

条件编译命令还有另外两种特殊的格式, 专用于判断是否定义过某个宏名字:

#ifdef 宏名字
    ...// 代码段1
#else
    ...// 代码段2
#endif

如果定义了宏名字, 则编译代码段1, 忽略代码段2;

否则忽略代码段1, 编译代码段2。

#ifndef 宏名字
    ...// 代码段1
#else
    ...// 代码段2
#endif

如果没有定义宏名字, 则编译代码段1, 忽略代码段2;

否则忽略代码段1, 编译代码段2。

对于重复包含问题, 利用宏定义和条件编译命令, 可以使某一段代码只在第一次出现时有效, 以后既使重复出现, 也会被编译器忽略掉。常见的写法是在头文件里使用下面的框架, 把代码段置于#ifndef/#define和#endif命令之间:

#ifndef 宏名字
#define 宏名字

...// 数据类型和函数原型的声明

#endif

当头文件第一次被包含时, 显然这个宏名字未定义, 于是#ifndef和#endif之间的内容有效。进入这段代码, 它首先定义这个宏名字, 然后是我们写下的数据类型和函数原型的声明。当以后再次包含该头文件时, 由于这个宏名字已经定义过, 所以#ifndef和#endif之间的内容无效, 被编译器忽略。

用这个方法改写后的头文件如下:

COMPLEX.H

#ifndef _COMPLEX_H_
#define _COMPLEX_H_

struct COMPLEX {
    double rpart, ipart;
};

#endif

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值