C++类定义
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
C++ 继承
当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
C++ 数据抽象
数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
数据抽象是一种依赖于接口和实现分离的编程(设计)技术。
就C++编程而言,C++类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,即外界实际上并不清楚类的内部实现。
在C++中,使用类来定义抽象数据类型(ADT)。
访问标签强制抽象
在C++中,我们使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:
- 使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由他的公共成员来定义的。
- 使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。
访问标签出现的频率没有限制。每个访问标签指定了紧随其后的成员定义的访问级别。指定的访问级别会一直有效,直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。
数据抽象的好处
数据抽象的两个重要的优势:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变的会导致哪些影响。如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。
数据抽象的实例
C++程序中,任何带有公有和私有成员的类都可以作为数据抽象的实例。
设计策略
抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可。
C++数据封装
所有的C++程序都有以下两个基本要素:
- 程序语句(代码):这是程序中执行动作的部分,它们被称为函数。
- 程序数据:数据是程序的信息,会受到程序函数的影响。
封装是面向对象编程中把数据和操作数据的函数绑定在一起的概率,这样能避免受到外界干扰和误用,从而确保了安全。数据封装引申出了另一个重要的OOP概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
C++通过创建类来支持封装和数据隐藏(public、protected、private)。我们已经知道,类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。默认情况下,在类中定义的所有项目都是私有的。例如:
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
变量length、breadth和height都是私有的(private)。这意味着它们只能被Box类中的其他成员访问,而不被程序中其他部分访问。这是实现封装的一种方式。
为了使类中的成员变成公有的(即,程序中的其他部分也能访问),必须在这些成员前使用public关键字进行声明。所有定义在public标识符后边的变量或函数可以被程序中所有其他的函数访问。
把一个类定义为另一个类的友元类,会暴露实行细节,从而降低了封装性。理想的做法是尽可能地对外隐藏每个类的实现细节。
数据封装的实例
C++程序中,任何带有公有和私有成员的类都可以作为数据封装和数据抽象的实例。
设计策略
通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。这通常应用于数据成员,但它同样适用于所有成员,包括虚函数。
C++接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。
C++接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关数据分离开的概率。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用“= 0”来指定的,
设计抽象类(通常称为ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
因此,如果一个ABC的子类需要被实例化,则必须实现每个虚函数,这也意味着C++支持使用ABC声明接口。如果没有在派生类中重载存虚函数,就尝试实例化该类的对象,会导致编译错误。
可用于实例化对象的类被称为具体类。
设计策略
面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。
外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。
这个架构也使得新的应用程序可以很容易地被添加到系统中,即使是在系统被定义之后依然可以如此。
C++ 动态内存
C++程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:程序中未使用的内存,在程序运行时可用于动态分配内存。
大多数情况下,无法预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。在C++中,可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即new运算符。
如果您不在需要动态分配的内存空间,可以使用delete运算符,删除之前由运算符分配的内存。
new 和 delete 运算符
下面是使用new运算符来为任意的数据类型动态分配内存的通用语法:
new data-type
在这里,data-type可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。让我们先看下内置的数据类型。例如,我们可以定义一个指向double类型的指针,然后请求内存,该内存在执行时被分配。我们可以按照下面的语句使用new运算符来完成这点:
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
如果自由存储区已被用完,可能无法成功分配内存。所以建议检查new运算符是否返回NULL指针,并采取以下适当的操作:
double* pvalue = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
malloc()函数在C语言中就出现了,在C++中仍然存在,但建议尽量不要使用malloc()函数。new与malloc()函数相比,其主要的优点是,new不只是分配了内存,它还创建了对象。
在任何时候,当您觉得某个已经动态分配内存的变量不再需要使用时,您可以使用delete操作符释放它所占用的内存,如下所示:
delete pvalue; // 释放 pvalue 所指向的内存
数组的动态内存分配
假设我们要为一个字符数组(一个有20个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存
要删除我们刚才创建的数组,语句如下:
delete [] pvalue; // 删除 pvalue 所指向的数组
下面是new操作符的通用语法,可以为多维数组分配内存,如下所示:
//一维数组
// 动态分配,数组长度为 m
int *array=new int [m];
//释放内存
delete [] array;
//二维数组
int **array
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n] ;
}
//释放
for( int i=0; i<m; i++ )
{
delete [] arrar[i];
}
delete [] array;
C++预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是C++语句,所以它们不会以分号(;)结尾。
我们已经看到,之前所有的实例中都有#include指令。这个宏用于把头文件包含到源文件中。
#define预处理
#define预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define macro-name replacement-text
当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为replace-text。
参数宏
使用#define来定义一个带有参数的宏,如下所示:
#include <iostream>
using namespace std;
#define MIN(a,b) (a<b ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cout <<"较小的值为:" << MIN(i, j) << endl;
return 0;
}
条件编译
有几个指令可以用来有选择地对部分源代码进行编译。这个过程称为条件编译。
条件预处理器的结构与if选择结构很像。
#ifndef NULL
#define NULL 0
#endif
可以只在调试时进行编译,调试开关可以使用一个宏来实现,如下所示:
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。您可以使用 #if 0 语句注释掉程序的一部分,如下所示:
#if 0
不进行编译的代码
#endif
# 和 ## 运算符
# 和 ##预处理运算符在C++和ANSI/ISO C中都是可用的。
# 运算符会把replacement-text令牌转换为用引号引起来的字符串。
## 运算符用于连接两个令牌。
参考链接:http://www.runoob.com/cplusplus/cpp-tutorial.html