第七章-类基础

7.1 定义抽象数据类型

抽象数据类型的最大特点是其具有很高的封装性,我们无法直接访问其内部的数据,甚至我们不清楚其内部都有哪些类型的数据,我们仅使用其提供的各种接口(api)来对其数据进行访问和操作。

C++中的类就是一种抽象的数据类型,类的基本思想就是数据抽象和封装。

仅由一组数据组成的结构体并不是一种抽象的数据类型,因为我们能直接访问其内部数据,而不是通过接口访问。如下的Sales_data

如果想要将书的交易记录Sales_data定义为一个抽象数据类型,我们需要提供一组操作(接口),每次交易只调用这些操作,这样就可以将数据封装起来。

(1)设计Sale_data类

书的交易记录Sale_data包含的数据有:书的标号 bookNo(定义为 string类对象),书的销量 units_sold(定义为 unsigned类型)和交易总价 revenue(定义为 double类型),使用这三个数据类型就可以完整的描述每单的交易了;除了对每单交易的描述外,还应提供哪些操作呢?

我们可以设计了这样一些操作:

     a.每单交易的录入read()和打印该单交易print()

     b.获取每单交易的参数:获取书编号getBookNo(),获取销量getUnitsSold(),获取销售总价getRevenue()

     c.实现同一本书的多单交易的汇总统计(将一单交易加到另一单上)combine()

代码如下:

先编写头文件Sales_data  ,将Saes_data类定义在头文件里  //注意,头文件名需要与类名一致,这是规范各文件名中类定义一致,非语法强制要求。

Sales_data类定义
#ifndef SALES_DATA_H
#define SALES_DATA_H  //定义头文件时必须加保护,防止其他.cpp重复引入此头文件。


#include <iostream>
#include <string>



using namespace std;

//统计每单书的交易记录
struct Sales_data {

	//数据成员
	string bookNo;          //书的编号
	unsigned units_sold = 0;     //销量
	double revenue = 0;           //销售总价

	//成员函数   必须在类或结构体内声明,可以在外面定义,但需要在函数名前加 类名::(Sales_data::)
	string getBookNo();
	unsigned getUnitsSold();
	double getRevenue();
	void read();
	void print();
	void combine(Sales_data next);
};

string Sales_data::getBookNo() {
	return bookNo;
}
unsigned Sales_data::getUnitsSold() {
	return units_sold;
}
double Sales_data::getRevenue() {
	return revenue;
}
void Sales_data::read() {
	cout << "请输入一条交易" << endl;
	cout << "书编号:";
	cin >> bookNo;
	cout << "销量:";
	cin >> units_sold;
	cout << "销售总价";
	cin >> revenue;
}
void Sales_data::print() {
	cout << "编号:" << bookNo << "  卖出" << units_sold << "本" << "  总价:" << revenue << endl;
}
void Sales_data::combine(Sales_data next) {
	this->units_sold += next.units_sold;
	this->revenue += next.revenue;
}

#endif // !SALES_DATA_H
主程序(输入一批交易)
 int main() {

	Sales_data toutal, next;
	int k=1;
	toutal.read();
	while (k!=-1)
	{
		next.read();
		if (toutal.getBookNo() == next.getBookNo()) {
			toutal.combine(next);
		}
		else {
			toutal.print();
			toutal = next;
		}
		cout << "是否停止录入? ";
		cin >> k;
	}
	toutal.print();
	return 0;
}

通过以上,定义了描述每单交易的必要参数,以及每单交易和多单交易之前可能会用的的操作,这样,Sale_data类基本就设计好。但还需要打磨一下设计的细节:

需要先介绍两个概念:

引入this指针:

     通过对象.成员函数调用时,形参表里会隐式的传入一个指向该对象的常量指针this,实际上在成员函数内使用对象的数据成员时,是隐式的使用this.数据成员.

    this指针始终都指向调用对象,所以this都是常量指针 (type * const类型)

    如toutal.getBookNo();将隐式传入 this常量指针,存放的是toutal对象的地址(Sales_data *const this=&toutal;)。

引入const成员函数

   如果调用对象是一个常量对象时,默认的指针类型是不能指向一个常量对象的,所以需要指定this指针为指向常量的指针,只需要在定义和声明成员函数时,在形参列表后添加关键字const,用以修饰this为指向常量的指针常量。

  使用const的成员函数称为常量成员函数无论是常量对象还是非常量对象都可以调用它,但它只能读取调用对象的数据成员,无权修改调用对象。  (常量对象以及其引用或指针只能调用它的常量成员函数)

  

知道以上两个概念后,我们可以将getBookNo()等成员函数定义为常量成员函数,如    string Sales_data::getBookNo() const{...}

因为getBookNo()只读取对象的数据成员,无修改对象的操作。

 

类的设计者负责思考描述一个类事物需要哪些参数,然后将这些参数进行封装,并设计一些相关操作,从而得到这类事务的一个模板;如描述人类,需要性别,姓名,年龄等特征参数,然后我们可以设计一个走路的操作,统计他一分钟走多远等等,这样就设计了一个简单的human模板,可以通过此模板创建一个个具体的人类对象张三或者李四。

而类的User(即类的调用者),不应该去过多思考类的实现过程,甚至不需要了解它都有哪些数据成员和成员函数,我们仅需思考这个类型可以做些什么?然后直接用其提供的API(成员函数)。

 

7.1.4构造函数

每个类都定义了它的对象的初始化方式,通过一个或者几个特殊的成员函数来控制其对象的初始化过程,这些成员函数叫构造函数

构造函数的任务是初始化对象的数据成员。

构造函数没有返回类型,函数名同类名。

构造函数在创建对象时被调用。

由编译器定义的构造函数叫默认构造函数,当且仅当类没有定义构造函数时,编译器才会自动生成默认构造函数。

当我们已经定义了类的构造函数但又需要默认的构造函数时,可以设置 类名()=default; 来要求编译器生成默认构造函数。

如果对象被创建时,某个数据成员没有传入初始值,编译器将用内置的初始值对数据成员进行初始化,如果也没有内置数据成员,则数值型数据默认为0,字符型数据默认为NULL;字符串默认为空字符串"".

struct Sale_data{

       string bookNo="000";          //内置的初始值;

       ......

};

 

7.1.5拷贝、赋值和析构

用一个对象给另一个对象赋值或者初始化另一个对象时,其实是对象的数据成员之前的赋值或初始化.

 

7.2 控制访问与封装

|控制访问| 

定义类的数据成员或声明其成员函数时,通过在前面加访问说明符号public或者private来控制外部调用代码的访问权限。

public定义的成员,在整个程序内都可被访问,无论是类的成员函数还是通过对象.数据成员对象.成员函数,都可以访问,将类的接口定义为public。

private定义的成员,仅能被类的成员函数访问,不能被外部代码访问(不能被对象.的方式直接访问),通过将非对外接口的成员定义为private,限制了外部代码的访问权限,实现了类的封装(隐藏了类的实现细节)。

访问说明符的用法:在需要声明为public或private的成员前面加public: 或private: 冒号后面的成员将定义为public或者private。

struct Sales_data {

	//数据成员
private:
	string bookNo;                  //书的编号
	unsigned units_sold = 0;        //销量
	  double revenue = 0;           //销售总价

	//成员函数   必须在类或结构体内声明,可以在外面定义,但需要在函数名前加 类名::(Sales_data::)
public:
	string getBookNo();
	unsigned getUnitsSold();
	double getRevenue();
	istream & read(istream &is);
	ostream &print(ostream &os)const;
	void combine(const Sales_data &next);
};

 

使用struct和class定义类的方式一样,唯一区别是两者的默认访问权限不一样。

对于没有访问说明符的成员,struct默认为public,calss默认为private。所以,如果希望所有成员都是public时,用struct方便些,反之,如果希望所有成员是private时,用class方便些。

 

7.2.1 友元

如果想给其他类或者函数访问其私有成员的权限,可以通过关键字friend,将这些类或者函数声明为本类的友元(最好在类开始或者结束位置统一声明友元).

友元声明并非函数声明,这些非成员函数还是需要在类外声明一下。

class sales_data{

friend void print();

friend void uui();

};

void print();

void uui();

 

类封装的优点:

a.可以防止用户误操作造成数据破坏

b.因用户面向接口编程,所以只要接口不变,类的设计者就可以自由修改封装的成员,而不用修改用户代码。

 

posted @ 2022-07-02 17:48  newloser  阅读(183)  评论(0)    收藏  举报