对象和类
面对对象编程(OOP)是一种特殊的,设计程序的概念性方法,C++ 通过一些特性改进了C语言,下面是最重要的OOP特性:
(1)抽象
(2)封装和数据隐藏
(3)多态
(4)继承
(5)代码的可重用性
抽象和类
抽象是通往用户定义类型的捷径,在C++ 中,用户定义类型指的是实现抽象接口的类设计。
类是一种将抽象转换为用户定义类型的C++ 工具,它将数据表示和操作数据的方法组合成一个整洁的包 。
接下来定义类,一般来说,类规范由两部分组成:
类声明:数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述工有接口。
类方法定义:描述如何实现类成员函数。
简单来说,类声明提供了类的蓝图,而方法定义则提供了细节。
什么是接口
接口是一个共享框架,供两个系统交互时使用。程序接口将你的意图转换为存储在计算机中的具体信息。对于类,我们说公共接口。这里,公众(public)是使用类的程序,交互系统由类对象组成,而接口由编写类的人提供的方法组成。接口让程序员能够编写与类对象交互的代码,从而让程序能够使用类对象。
通常,C++ 程序员将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源代码文件中。
下面是一个示例:
// stock00.h -- Stock class interface
// version 00
#ifndef STOCK00_H_
#define STOCK00_H_
#include<string>
class Stock // class declaration
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_pot() {
total_val = shares * share_val;
}
public:
void acquire(const std::string &co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
#endif
首先,关键字 class 指出这些代码定义了一个类设计(不同于在模板参数中,在这里,关键字class 和 typename 不是同义词,不能使用 typename 代替 class)。
其中 Stock 是这个新类的类型名。该声明让我们能够声明 Stock 类型的变量——称为对象或实例。
访问控制
关键字 private 和 public 也是新的,他们描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员。因此,公有成员函数是程序和对象的私有成员之间的桥梁,提供了程序和对象之间的接口。
防止程序之间访问数据被称为数据隐藏,C++ 还提供了第三个访问控制关键字 protected ,后面章节介绍类继承时将讨论该关键字。
类设计尽可能将公有接口与实现细节分开。公有接口表示设计的抽象组件。将实现细节放在一起并将他们与抽象分开被称为封装。数据隐藏(将数据放在类的私有部分中)是一种封装。
控制对成员的访问:公有还是私有
无论类成员是数据成员还是成员函数,都可以在类的公有部分或私有部分声明它。数据项通常放在私有部分,组成类接口的成员函数放在公有部分;否则,就无法从程序中调用这些函数。
另外,不必在类声明中使用关键字private,因为这是类对象的默认访问控制:
class World
{
float mass;
char name[20];
public:
void tell();
};
类与结构的唯一区别是,结构的默认访问类型是 public,而类为 private。 C++ 程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象。
实现类成员函数
还需要创建类描述的第二部分:为那些由类声明中的原型表示的成员函数提供代码。成员函数定义与常规函数定义非常相似,他们由有函数头和函数体,也可以有返回类型的参数。但是他们还有两个非常特殊的特征:
(1)定义成员函数时,使用作用域解析运算符(: :)来标识函数所属的类;
(2)类方法可以访问类的 private 组件 。
例如:
void Stock :: update(double price)
这种表示法意味着我们定义的 update()函数是Stock 类的成员;还意味着可以将另一个类的成员函数也命名为update(),两者不冲突。标识符 update()具有类作用域。Stock的其他成员函数不必使用作用域解析运算符就可以使用 update()方法。
定义位于类声明中的函数都将自动成为内联函数
方法使用哪个对象
下面介绍使用对象时最主要的一个方面:如何将类应用于对象。下面代码使用了一个对象的Shares 成员:
Shares += num ;
是哪个对象呢?
首先,看看如何创建对象。最简单的方式是声明类变量:
Stock kate,joe ;
这将创建两个Stock 对象,一个为 kate, 一个为 joe。
接下来,看看如何使用对象的成员函数。和使用结构成员一样,通过成员运算符:
kate.show();
joe.show();
注意:调用成员函数时,它将被用来调用它的对象的数据成员。
使用类
直接上示例:
// stock00.h -- Stock class interface
// version 00
#ifndef STOCK00_H_
#define STOCK00_H_
#include<string>
class Stock // class declaration
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_pot() {
total_val = shares * share_val;
}
public:
void acquire(const std::string &co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
// 各个函数的解释忽略
#endif
// compile with stock.cpp
#include<iostream>
#include "stock00.h"
int main()
{
Stock fluffy;
fluffy.acquire("Hello",1,2);
fluffy.show();
fluffy.buy(666,666.6);
fluffy.show();
return 0;
}
类的构造函数和析构函数
声明和定义构造函数
现在需要创建 Stock 的构造函数。由于需要为 Stock 对象提供三个值,因此要为构造函数提供三个参数。注意:构造函数没有返回类型。原型位于类声明的公有部分。
使用构造函数
C++ 提供了两种使用构造函数来初始化对象的方式。第一种方式是显式地调用构造函数:
Stock food = Stock("Hello World",250,1.25);
另一种是隐式地调用构造函数:
Stock food = ("Hello World",250,1.25);
构造函数的使用方式不同于其他类方法。一般来说,使用对象来调用方法:
stock1.show();
但无法使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。因此构造函数被用来创建对象,而不能通过对象来调用。
析构函数
用构造函数创建对象后,程序负责跟踪对象,直到其过期为止。对象过期时,程序将自动调析构函数。让他来完成清理工作,和构造函数一样,析构函数的名称也很特殊:在类名前加上 ~。因此, Stock 类的析构函数是 ~ Stock();这么写的原因是析构函数实际上没有需要完成的任务,只需让编译器生成一个什么都不做的隐式析构函数即可。
这是其原型:
~Stock();
Stock::~Stock();
{
}
为了让你知道析构函数何时被调用,这么写:
Stock::~Stock()
{
cout << "Bye, " << company << "!\n";
}
析构函数的调用由编译器决定。
this 指针
对于 Stock 类,到目前为止,每个类成员函数都只涉及一个对象,即调用它的对象。但有时候方法可能涉及到两个对象,在这种情况下需要使用 C++ 的 this 指针。
虽然 Stock 类声明可以显示数据,但他缺乏分析能力。要让程序知道存储的数据,最直接的方式是让方法返回一个值。为此,通常使用内联代码,如下例所示:
class Stock
{
private:
double total_val;
public:
double total() const
{
return total_val;
}
};
还有另一种方法,运用 this 指针的方法 :定义一个成员函数,它查看两个 Stock 对象,并返回某个对象的引用。
首先,如何将两个要比较的对象提供给成员函数呢?例如,将该方法命名为 topval () ,则函数调用 stock1. topval () 将访问 stock1对象的数据,而 stock.topval () 将访问 stock1 对象的数据。如果希望该方法对两个对象进行比较,则必须将第二个对象作为参数传递给它。出于效率方面考虑,可以按引用来传递参数,也就是说, topval () 方法使用一个类型为 const Stock & 的参数。
其次,如何将方法的答案传回给调用程序呢?最直接的方法是让方法返回一个引用,因此用于比较的方法的原型如下:
const Stock & topval(const Stock & s) const;
// 该函数隐式地访问一个对象, 而显示地访问另一个对象, 并返回其中一个对象的引用.
// 括号中的 const 表明, 该函数不会修改显示地访问的对象;
// 而括号后的 const 表明, 该函数不会修改被隐式访问的对象.
注意:
const Stock & Stock::topval(const Stock & s) const
{
if(s.total_val > total_val)
{
return s;
// s 是其中一个对象的别名
}
else
{
return ?????
// 另一个对象的别名不存在
}
}
这时,应使用被称为 this 的指针,this 指针指向用来调用成员函数的对象(this 被作为隐藏参数传递给方法)。
#include<iostream>
// stock20.h
#ifndef STOCK_H_
#define STOCK_H_
#include<string>
class Stock
{
private:
std::string company;
int shares;
double share_val;
double total_val;
void set_tot() {
total_val = shares * share_val;
}
public:
Stock();
Stock(const std::string & co, long n = 0, double pr = 0.0);
~Stock();
const Stock & topval(const Stock & s) const;
};
#endif
// stock20.cpp
Stock::Stock()
{
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::Stock(const std::string & co, long n, double pr)
{
company = co;
if(n < 0)
{
std::cout << "Number of shares can't be nagetive;"
<< company << " shares set to 0.\n";
shares = 0;
}
else
{
shares = n;
share_val = pr;
set_tot();
}
}
Stock::~Stock()
{
}
const Stock & Stock::topval(const Stock & s) const
{
if(s.total_val > total_val)
{
return s;
}
else
{
return *this;
}
}
对象数组
其声明和标准数组相同。其他的方面就是相当于多个对象同时初始化。
类作用域省略
抽象数据类型
前面的 Stock 类非常具体,然鹅常常通过定义类来表示更通用的概念。 下面介绍栈的特征,
首先,栈存储了多个数据项;其次,栈由可对它执行的操作来描述。
(1)可创建空栈
(2)可将数据项添加到栈顶
(3)可从栈顶删除数据项
(4)可查看栈是否填满
(5)可查看栈是否为空
可以将上述描述转换成一个类声明,其中公有成员函数提供了表示栈操作的接口,而私有数据成员负责存储栈数据。(私有部分必须表明数据存储的方式)
一、栈的定义:
一种可以实现“先进后出(后进先出)”的存储结构
生活中的例子:玩具枪的子弹夹,后进来的子弹先射出。
二、分类:
静态栈:使用数组
动态栈:链表
三、算法:
出栈:push
压栈:pop
栈是否为空:empty
栈的大小:size
访问栈顶:top
四、应用:
(1)函数中调用其它函数,其它函数运行完之后返回结果给上一级的函数。
(2)中断
(3)表达式求值
(4)内存分配
(5)缓冲处理
(6)迷宫
五、基本操作的实现程序:
#include <iostream>
using namespace std;
// 结点结构体
struct Node {
int data;
Node *next;
};
// 栈
struct Stack {
Node *pTop; // 顶部指针
Node *pBottom; // 底部指针
};
void Init(Stack *pS); // 初始化
void CreateStack(Stack *pS); // 建栈
void Travers(Stack *pS); // 遍历栈
void Push(Stack *pS, int val); // 压栈
bool Pop(Stack *pS); // 出栈:把栈顶的结点删掉
bool getTop(Stack *pS, int &val); // 获取栈顶元素:但不删除栈顶结点
bool isEmpty(Stack *pS); // 判断栈是否为空
int getSize(Stack *pS); // 获取栈的长度
int main(int argc, const char * argv[]) {
Stack s; // 声明对象,等价于 struct Stack s
int val, choose, len; // val存储值,choose存储用户的选择
bool finished = false;
while(!finished) {
cout << "1:初始化栈:" << endl;
cout << "2:建栈(以-1结束输入):" << endl;
cout << "3:遍历栈:" << endl;
cout << "4:压栈:" << endl;
cout << "5:出栈:" << endl;
cout << "6:取栈顶的元素值:" << endl;
cout << "7:栈是否为空:" << endl;
cout << "8:获取栈的长度:" << endl;
cout << "9:退出:" << endl;
cout << "请输入你的选择[1-9]:" << endl;
cin >> choose;
switch(choose) {
case 1:
Init(&s); // 初始化栈
break;
case 2:
CreateStack(&s); // 建栈
break;
case 3:
cout << "栈中的元素为:" << endl;
Travers(&s); // 遍历栈
break;
case 4:
cout << "请输入要压入栈中的元素值:" << endl;
cin >> val;
Push(&s, val);
break;
case 5:
if(Pop(&s)) // 出栈
cout << "出栈成功!" << endl;
else
cout << "出栈失败!" << endl;
break;
case 6:
if(getTop(&s, val))
cout << "栈顶元素的值为:" << val << endl;
else
cout << "栈为空!" << endl;
break;
case 7:
if(isEmpty(&s))
cout << "栈为空!" << endl;
else
cout << "栈不空" << endl;
break;
case 8:
len = getSize(&s);
cout << "栈的长度为:" << len << endl;
break;
case 9:
finished = true;
break;
default:
cout << "输入选择错误,请重新输入!" << endl;
}
}
return 0;
}
// 初始化
void Init(Stack *pS) {
pS->pTop = new Node();
if(NULL == pS->pTop) {
cerr << "动态内存分配失败!" << endl;
exit(1);
}
pS->pBottom = pS->pTop; // 顶部指针和底部指针指向同一个位置
pS->pTop->next = NULL; // pS->pBottom->next = NULL;
}
// 建栈
void CreateStack(Stack *pS) {
int val;
cout << "请输入各个元素值:" << endl;
while(cin >> val && val != -1)
Push(pS, val);
}
// 压栈
void Push(Stack *pS, int val) {
Node *newNode = new Node(); // 新建结点
if(NULL == newNode) {
cerr << "动态内存分配失败!" << endl;
exit(1);
}
newNode->data = val;
newNode->next = pS->pTop;
pS->pTop = newNode; // 顶端指针往上移
}
// 遍历栈
void Travers(Stack *pS) {
Node *p = pS->pTop;
while(p != pS->pBottom) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
// 判断栈是否为空:通过比较栈顶和栈尾指针是否相等,如相等,说明为空
bool isEmpty(Stack *pS) {
if(pS->pTop == pS->pBottom)
return true;
return false;
}
// 出栈:把栈顶的结点删掉
bool Pop(Stack *pS) {
if(isEmpty(pS))
return false;
Node *r = pS->pTop; // 暂存顶指针
pS->pTop = r->next; // 栈顶指针往下移一个结点
delete r; // 释放空间
r = NULL; // 指向空
return true;
}
// 获取栈顶元素:但不删除栈顶结点
bool getTop(Stack *pS, int &val) {
if(isEmpty(pS)) // 栈空,返回0
return false;
val = pS->pTop->data;
return true; // 否则返回栈顶的元素值
}
// 获取栈的长度
int getSize(Stack *pS) {
int len = 0;
Node *p = pS->pTop;
while(p != pS->pBottom) {
len++;
p = p->next;
}
return len;
}