在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;//通知用户
}

 

  1. #include <iostream>  

  2. #include<string>  

  3. #include<vector>  

  4. using namespace std;  

  5. /* 

  6. C++ 中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。 

  7. 而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。 

  8.  

  9. C++中的 struct 和 class 基本是通用的,唯有几个细节不同: 

  10. 1:使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。(最本质的区别) 

  11. 2:class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)。 

  12. 3:class 可以使用模板,而 struct 不能。 

  13. */  

  14.   

  15. //利用class定义类  

  16. class Student  

  17. {  

  18.     Student(string name, int age, int score);//构造函数  

  19.   

  20.     string m_name;  

  21.     int m_age;  

  22.     int m_score;//定义三个变量  

  23.   

  24.     void showname()//定义一个函数  

  25.     {  

  26.         cout<<m_name<<"的年龄是:"<<m_age<<",得分为:"<<m_score<<endl;  

  27.     }//类内定义的函数,编译器会优先视为内联函数  

  28. public:  

  29. private:  

  30. protected://三种形式  

  31. };  

  32. Student::Student(string name, int age, int score):m_name(name), m_age(age), m_score(score){ }  

  33. //成员初始化列表,将name赋给m_name,改变类内变量  

  34.   

  35.   

  36. //利用strcut定义类  

  37. struct Students  

  38. {  

  39.     Students(string name, int age, int score);//构造函数  

  40.       

  41.     string m_name;  

  42.     int m_age;  

  43.     int m_score;//定义三个变量,默认public  

  44.   

  45.     void shownames()//定义一个函数  

  46.     {  

  47.         cout<<m_name<<"的年龄是:"<<m_age<<",得分为:"<<m_score<<endl;  

  48.     }//类内类外定义皆是在同文件中,也可定义在.h文件中。  

  49. };  

  50. Students::Students(string name, int age, int score):m_name(name), m_age(age), m_score(score){ }  

  51. //列表初始化,覆盖类的内部变量  

  52.   

  53. /* 

  54. 总结 

  55. 1:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的 

  56. 2:当你觉得你要做的更像是一种数据结构的话,那么用struct,如果你要做的更像是一种对象的话,那么用class。  

  57. 3:然而对于访问控制,应该在程序里明确的指出,而不是依靠默认,这是一个良好的习惯,也让你的代码更具可读性。  

  58. */  

  59.   

  60. int main(int argc, char** argv)  

  61. {  

  62.     Student stu1("小明", 18, 3);//报错:虽然已声明,但是不可访问。成员没有限定public,默认都是private,外部不可访问。  

  63.     Students stu2("小明", 18, 3);//正常  

  64.     stu2.shownames();  

  65.     return 0;  

  66. }  

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属性。同时,类外部定义的成员的名字必须包含它所属的类名。

 

 

作用域运算符::一旦编译器看到这个函数名,就能理解剩余的代码是位于类的作用域内的。