在C++语言中,我们使用类定义自己的数据类型。通过定义新的类型来反映待解决问题中的各种概念,可以使我们更容易编写,调试,和修改程序。
类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。累得接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及类所需的各种私有函数。
封装实现了类的接口和实现的分离。封装后的类隐藏了他的实现细节,也就是说,类的用户只能使用接口而无法访问实现的部分。
类要想实现数据抽象个封装,需要首先定义一个抽象数据类型。在抽象数据类型中,由类的设计者负责考虑类的实现过程,使用该类的程序员则只需要抽象的思考类型做了什么,而无需了解类型的工作细节。
1、定义抽象数据类型:
(1)设计sales_data类:
我们的最终目的是令sales_data支持与sales_items类完全一样的操作集合。sales_items有一个名为isbn的成员函数,并且支持+-×/等运算符。
注意:我们将在14章学习如何自定义运算符。现在我们先为这些运算定义普通函数形式。
综上,sales_data的接口因该包含以下操作:
『』一个isbn成员函数,用于返回isbn编号
『』一个combine成员函数,用于将一个sales_data对象加到另一个对象上。
『』一个名为add的函数,执行两个sales_data对象的加法
『』一个read函数,将数据从istream读入到sales_data对象中
『』一个print函数,将sales_data对象的值输出到ostream
关键概念:不同的编程角色
程序员把运行程序的人称作用户(user)。类似的,类的设计者也是为其用户设计并实现一个类的人:显然类的用户是程序员,而非应用程序的最终使用者。
使用改进sales_data类:
在考虑如何实现类之前,首先来看看如何使用上面这些接口函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Sales_data total; //保存当前求和结果的变量 if (read(cin,total)) //读入第一笔交易 { Sales_data trans; //保存下一条交易数据的变量 while (read(cin,trans)) //读入剩余交易 { if (total.isbn()==trans.isbn()) //检查isbn total.combine(trans); //更新变量total当前的值 else { print(cout,total)<<endl; //输出结果 total=trans; //处理下一本书 } } print(cout,total)<<endl; //输出最后一条交易 } else //没有输入任何信息 { cerr<< "no data?!" <<endl; //通知用户 } |
-
#include <iostream>
-
#include<string>
-
#include<vector>
-
using namespace std;
-
/*
-
C++ 中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。
-
而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
-
-
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
-
1:使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。(最本质的区别)
-
2:class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)。
-
3:class 可以使用模板,而 struct 不能。
-
*/
-
-
//利用class定义类
-
class Student
-
{
-
Student(string name, int age, int score);//构造函数
-
-
string m_name;
-
int m_age;
-
int m_score;//定义三个变量
-
-
void showname()//定义一个函数
-
{
-
cout<<m_name<<"的年龄是:"<<m_age<<",得分为:"<<m_score<<endl;
-
}//类内定义的函数,编译器会优先视为内联函数
-
public:
-
private:
-
protected://三种形式
-
};
-
Student::Student(string name, int age, int score):m_name(name), m_age(age), m_score(score){ }
-
//成员初始化列表,将name赋给m_name,改变类内变量
-
-
-
//利用strcut定义类
-
struct Students
-
{
-
Students(string name, int age, int score);//构造函数
-
-
string m_name;
-
int m_age;
-
int m_score;//定义三个变量,默认public
-
-
void shownames()//定义一个函数
-
{
-
cout<<m_name<<"的年龄是:"<<m_age<<",得分为:"<<m_score<<endl;
-
}//类内类外定义皆是在同文件中,也可定义在.h文件中。
-
};
-
Students::Students(string name, int age, int score):m_name(name), m_age(age), m_score(score){ }
-
//列表初始化,覆盖类的内部变量
-
-
/*
-
总结
-
1:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的
-
2:当你觉得你要做的更像是一种数据结构的话,那么用struct,如果你要做的更像是一种对象的话,那么用class。
-
3:然而对于访问控制,应该在程序里明确的指出,而不是依靠默认,这是一个良好的习惯,也让你的代码更具可读性。
-
*/
-
-
int main(int argc, char** argv)
-
{
-
Student stu1("小明", 18, 3);//报错:虽然已声明,但是不可访问。成员没有限定public,默认都是private,外部不可访问。
-
Students stu2("小明", 18, 3);//正常
-
stu2.shownames();
-
return 0;
-
}
2、定义改进的sales_data类:
改进之后的类的数据成员将与之前保持一致。
类将包含两个成员函数:combine和isbn。此外,我们还将赋予sales_data另外一个成员函数用于返回售出书记的平均价格,这个函数被命名为avg_price。因为avg_price的目的并非通用,所以它应该属于类的实现的一部分,而非接口的一部分。
成员函数的声明必须在类的内部,它的定义则能在类的内部也可以在外部。作为接口组成部分的非成员函数,例如:add,read,print等,他们的定义和声明都在类的外部:
1
2
3
4
5
6
7
8
9
|
struct Sales_data { std::string isbn() const { return bookno;} Sales_data & combine( const Sales_data&); double avg_price() const ; std::string bookno; unsigned units_sold= 0 ; double revenue = 0.0 ; }; |
1
2
3
|
Sales_data add( const Sales_data &, const Sales_data &); std::ostream &print(std::ostream&, const Sales_data &); std::istream &read(std::istream&, Sales_data &); |
注意定义在类内部的函数是隐式的inline函数。
首先介绍,isbn函数,它的参数列表为空,返回值是一个string对象,成员函数体也是一个块。在isbn中快只有一条return语句,用于返回sales_data对象的bookno数据成员。关于isbn函数一件有意思的事情是:它是如何获得bookno数据成员的那。
在成员内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类的直接访问都被看作this的隐式引用,也就是当isbn使用bookno时,他隐式的使用this指向的成员,就像我们书写了this->bookno一样。
引入const成员函数:
isbn函数的另外一个关键之处是紧随参数列表之后的const关键字,这里,const的作用是修改隐式this指针类型。
默认情况下,this的类型是指向类类型非常量版本的常量指针。例如在sales_data成员函数中,this的类型是sales_data *const。尽管this是隐式的,但它仍然需要遵循初始化规则,意味着我们不能把this绑定到一个常量对象上。这一情况也就使我们不能吧this绑定到一个常量对象上。使我们不能在一个常量对象上调用普通的成员函数。
在C++中,把const关键字放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数。
常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
类作用域和成员函数:
类本身就是一个作用域,类的成员函数的定义嵌套在类的作用域之内,因此,isbn中用到的名字bookno其实就是定义在sales_data内的数据成员。
在类的外部定义成员函数:
像其他函数一样,在类的外部定义成员函数时,成员函数的定义必须与他的生命匹配。也就是说,返回类型,参数列表,函数名都与类内部的声明保持一致。如果成员被声明称常量成员函数,那么他的定义也必须在参数列表后明确的制定const属性。同时,类外部定义的成员的名字必须包含它所属的类名。
作用域运算符::一旦编译器看到这个函数名,就能理解剩余的代码是位于类的作用域内的。