C++ 第四周 第二部分

对象和类

面对对象编程(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 类型的变量——称为对象或实例。

访问控制 

       关键字 privatepublic 也是新的,他们描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员。因此,公有成员函数是程序和对象的私有成员之间的桥梁,提供了程序和对象之间的接口

       防止程序之间访问数据被称为数据隐藏,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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DDsoup

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值