CH15 面向对象程序设计

Posted Emma_U

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CH15 面向对象程序设计相关的知识,希望对你有一定的参考价值。

面向对象程序设计是基于三个基本概念的:数据抽象、继承和多态。

第7章介绍了数据抽象的知识,简单来说,C++通过定义自己的数据类型来实现数据抽象。

数据抽象是一种依赖于接口和实现分离的编程技术:类的设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。

 封装是一项将低层次的元素组合起来形成新的、高层次实体的技术!函数和类都是封装的具体形式。其中类代表若干成员的聚集。大多数(设计良好的)类类型隐藏了实现该类型的成员。

一个简单的PersonInfo类,包含类成员、成员函数、构造函数

 1 #include <string>
 2 
 3 using namespace std;
 4 class PersonInfo {
 5 public:
 6     PersonInfo();
 7     PersonInfo(string& name):strname(name),age(0){}
 8     string getName() { return strname; }
 9     int getAge() { return age; }
10 
11 private:
12     string strname;
13     int age;
14 };

2.访问控制符 

程序所有部分均可访问public成员

只有在类内部才能访问类的private成员,即类的private成员对外是不可见的。

3.成员函数可以被重载,上面的PersonInfo类就定义了两个版本的构造函数,被重载的成员函数之间的参数数量和类型不能完全相同,因为重载的成员函数被调用时会根据参数数量和类型进行匹配。重载构造函数的的原理和调用和普通成员函数的重载是一样的。

4.类的声明和定义

一个源文件中一个类只能定义一次。

可以只声明一个类,而先不定义它,一般,这种情况是有关联的类

class StrBlobPtr;
class StrBlob {
    friend class StrBlobPtr;
public:
    StrBlob();
    StrBlob(initializer_list<string>il){}
    //其它成员函数
    StrBlobPtr begin();
    StrBlobPtr end();
private:
    //私有成员
};

但是只声明没有定义的类,不能使用,即:不能定义该类的对象,因为生命之后,定义之前,StrBlobPtr是不完全的类,并不知道这个类有哪些成员。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。,如上面的begin()和end()函数。

 

5.类对象

定义一个类后,就可以定义该类的对象,可以认为对象是类的实体,类通过对象实现一系列操作。定义对象时,会为其分配内存空间,定义类型时并没有进行存储分配。

//两种定义对象的方式都可以,一般是第一种
PersonInfo a;
class PersonInfo b;

6.this指针

成员函数有一个隐形形参,指向该类对象的一个隐形形参,即this指针,this的形参是由编译器定义的,与成员的当前对象绑定在一起

 1 class Screen {
 2 public:
 3     typedef string::size_type pos;//此处类为自己定义了一个局部类型,使用typedef为string::size_tyoe 定义了个别名
 4     //为类做了一个更好的抽象,
 5     Screen() = default;
 6     Screen(pos ht, pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}//define constructor and initizalize
 7     char get()const { return contents[cursor]; }
 8     inline char get(pos ht, pos wd)const;//declare operator member function
 9     Screen &move(pos r, pos c);//Note:此处返回的是引用,该引用指向执行操作的那个对象
10     //add some function to set the char in the position pointed by the cursor
11     //和move()一样,set成员的返回值是调用set对象的引用,返回引用的函数是左值的
12     Screen &set(char);
13     Screen &set(pos, pos, char);
14 private:
15     pos cursor = 0;
16     pos height = 0, width = 0;
17     string contents;
18 };
19 
20 //define move()
21 //勒种一些规模较小是函数适合被声明为内联函数,类内部的默认自动是inlien 的。
22 inline Screen &Screen::move(pos r,pos c)
23 {//函数返回调用自己的对象,使用this 来访问该对象
24     pos row = r*width;//cacculate the position of the row
25     cursor = row + c;//move the cursou to the pointed column in the row
26     return *this;//return the object
27 
28 }
29 
30 char Screen::get(pos r, pos c)const
31 {
32     pos row = r*width;
33     return contents[row + c];
34 }
35 inline Screen& Screen::set(char c)
36 {
37     contents[cursor] = c;
38     return *this;//返回的是this指向的对象,将this对象作为左值返回
39 }
40 //重载的set()函数
41 inline Screen& Screen::set(pos r, pos col, char ch)
42 {
43     contents[r*width + col] = ch;
44     return *this;
45 }
46 int main()
47 {
48     
49     Screen myScreen;
50     
51     myScreen.move(4, 0).set(\'*\');//这句相当于下面两句,
52     myScreen.move(4, 0);
53     myScreen.set(\'*\');//
54     system("pause");
55     return 0;
56 }

Note: set()成员不能定义为const 的,因为set()返回的是调用set()对象的引用,返回的是左值,而左值必须是可以修改值的,不能是const 的

下面我们定义一个函数从const 成员返回*this

 在普通的const成员函数,this的类型是一个指向类类型的const指针。可以改变this所指向的值,但不能改变this所保存的地址。const成员函数中,this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。

    不能从const成员函数返回指向类对象的普通引用。const成 员函数只能返回*this作为一个 const引用

定义一个display 成员打印出Screen的内容,只是显示Screen的内容不需要改变,因此将const定义为一个const成员,此时this 是一个指向const的指针,*this 就是const对象 ,所以display的返回类型应该是const Screen&,但是,如 如果令display返回一个const引用,则我们不能把display嵌入到一组动作的序列中

此时若用display返回的对象调用set()成员,就会发生错误,,上面说了set成员为什么不能设为常量、

所以下面调用是错误的

myScreen.dipaly(cout).set(\'#\');

Note:一个const成员函数如果以引用方式返回*this,那么它的返回类型将是常量引用。所以定义一个非const的display成员

此处注意,定义了一个小的私有成员,do_play(),看似没简化操作,实则,这样的小程序段是非常有用的,关于这样公共代码使用私有功能函数的建议,课本P248有详细说明

class Screen {
public:
    //其它以定义的public成员
    const Screen &dipaly(ostream& os)const { do_display(os); return *this; }
    Screen& dispaly(ostream&os) { do_display(os); return *this; }
private:
    //其它以定义的prvate成员
    void do_display (ostream &os)const { os << contents; }
};

7.类作用域

名字查找与类的作用域

类的定义分两步

1.编译成员的声明

2.直到类全部可见后才编译函数体

编译器处理完类中的全部声明后才会处理成员函数的定义

 OOP概述

1.OOP核心思想是数据抽象,继承和动态绑定,使用数据抽象可以将类的接口与实现分离,使用继承可以定义相似的类型并对其相似关系建模;使用动态绑定,可以在一定程度上忽略相似类型的区别,以统一方式使用它们的对象

2.定义基类和派生类

其中Quote类是基类,Bulk_quote类是派生类。派生类的构造函数必须重新写过,不能继承。(因为毕竟两个类的类名都不一样,不可能构造函数继承)只继承其他的成员函数和成员变量。

派生类可以覆盖基类的虚函数,但是也可以选择不覆盖(即直接使用父类的函数版本)

每个类控制它自己的成员的初始化过程,派生类初始化成员时,对于从父类继承来的成员必须使用父类的构造函数进行初始化,对于自己新增加的成员,调用自己的构造函数初始化

Note:基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此

 1 #include <string>
 2 //#include <cstddef>
 3 #include <iostream>
 4 using namespace std;
 5 //基类
 6 class Quote {
 7 public:
 8     //将默认构造函数声明为default,则要求编译器为嗯合成默认构造函数
 9     Quote() = default;
10     //重载constructor
11     Quote(const string &book,double sales_price):bookNo(book),price(sales_price){}
12     string isbn()const { return bookNo; }
13     //返回某种书的销售总额
14     //派生类会根据不同情形重写该函数,所以定义为虚函数
15     virtual double net_price(size_t n)const { return n*price; }
16     virtual ~Quote() = default;
17 private:
18     string bookNo;
19 protected:
20     double price = 0.0;
21 };
22 //派生类,从基类Quote那里继承了isbn()和bookNo和price,重新定义了net_price()函数
23 //新增加了min_qty 和discount 成员
24 class Bulk_quote:public Quote {
25 public:
26     Bulk_quote() = default;
27     Bulk_quote(const string&, double, size_t, double);
28     double net_price(size_t)const override;
29 private:
30     size_t min_qty = 0;//可以使用指定折扣的最低数量
31     double discount = 0.0;//折扣
32 };
33 Bulk_quote::Bulk_quote(const string& book, double p, size_t qty,double disc):
34     Quote(book,p),min_qty(qty),discount(disc)
35 {
36   //注意初始化列表中对成员bookNo 和price的初始化,不是像之前那样bookNo(book),price(p)
37  //进行初始化,而是调用基类的构造函数进行初始化
38 }
39 double Bulk_quote::net_price(size_t cnt)const
40 {
41     if (cnt >= min_qty)
42         return cnt*price*(1 - discount);
43     else
44         return cnt*price;
45 }
46 //dynamic binding
47 double print_total(ostream &os, const Quote &item, size_t n)
48 {
49     //根据传入的item形参的对象类型调用net_price()
50     double ret = item.net_price(n);
51     os << "ISBN: " << item.isbn() <<\'\\t\'<< "#sold: " << n << " total due: "
52         << ret << endl;
53     return ret;
54 }
55 int main()
56 {
57     Quote item("978-7-121-31092-8", 65);//调用基类构造函数
58     Bulk_quote bulk("978-7-121-31092-8", 65, 20, 0.2);//call the constructor of sub class
59     //bookA.net_price(20);
60     //Quote item;//基类对象
61     //Bulk_quote bulk;//派生类对象
62     
63     Quote *p =&item;
64     p->net_price(40);//dynamic binding
65 //    Quote base;
66 //    Bulk_quote subcls;
67     print_total(cout, item, 20);
68     print_total(cout, bulk, 20);
69     system("pause");
70     return 0;
71 }

继承与静态成员

如果基类有一个静态成员,那么基类和所有派生类(包括派生类的派生类)都共同拥有这仅有的一个静态成员。并且,对该成员的访问控制与非静态成员的访问控制方式一样,即,如果是private的,则派生类无权访问,,如果是可访问的,则既可以通过基类使用它,也可通过派生类使用它。

     

class Base {
public:
    static void statmem();//static member
};
class Derived :public Base {
    void f(const Derived&);
};

void Derived::f(const Derived& derived_obj)
{
    Base::statmem();//correct:difine in Base
    Derived::statmem();//correct: Derived derived it from Base
    derived_obj.statmem();//correct:visit it throgh the object of Derived
    statmem();//correct: visit it through object of pointed by this 
}

类型转换与继承

可以将基类的指针或引用绑定到派生类对象上(上面Quote例子中的63,64行):当使用基类引用(或指针)时,实际上我们并不清楚该引用(指针)所绑定的真实类型,该对象可能是基类的对象,也可能是派生类对象。

静态类型在编译时总是已知的,而动态类型的对象直到运行时才可知。

3.虚函数

我们必须为每一个虚函数都提供定义,不管它是否被用到了,因为编译器无法确定到底会使用哪个虚函数。
OOP的核心思想就是多态(polymorphism):具有继承关系的多个类型称为多态类型。

派生类中的虚函数,当在派生类中覆盖了基类的某个虚函数,可以使用virtual关键字指明,即使不这么做,C++也是默认virtual的。

一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。

同样,派生类中虚函数的返回类型也必须与基类函数匹配,当类的虚函数返回类型是类本身的指针或引用时例外。

只有虚函数才能被覆盖

 

基类的虚函数默认实参最好与派生类的虚函数默认实参一致。

 

因为如果通过基类的引用或指针调用函数,则将使用基类的默认实参版本,但是该函数的实现是动态绑定的,即可能是基类的函数也可能是派生类的函数。

 

 

struct B {
    virtual void f1(int)const;
    virtual void f2();
    void f3();
};
struct D :B {
    void f1(int)const;//correct:f1matched with f1 in class  B
    void f2(int)override;//incorrect:theris no f2(int) in class B
    void f3()override;//incorrect:f3 is not a virtual function
    void f4()override;//incorrect:there is no function named f4 in class B
};

4.抽象基类

纯虚函数:纯虚函数不需要定义,在声明时写上=0即可,智能出现在虚函数声明处的语句。

含有(或未经覆盖而直接继承)纯虚函数的类为抽象类,抽象基类负责定义借口,而后续其它类可以覆盖接口。Note:不能创建抽象基类的对象

如将之前的net_price()定义为虚函数

 1 class Dis_quote :public Quote{
 2 public:
 3     Dis_quote() = default;
 4     Dis_quote(const string& book, double p, size_t qty, double disc) :
 5         Quote(book, p), quanty(qty), discount(disc)
 6     {
 7 
 8     }
 9 
10     double net_price(size_t)const = 0;//pure virtual
11 protected:
12     size_t quanty = 0;
13     double discount = 0.0;
14 };
Disc_quote discounted;//incorrect :can not define the object for abstract base class

Disc_quote 的派生类必须定义自己的net_price(),否则仍是抽象基类

现在可以重新定义Bulk_quote,让它继承Disc_quote而不是直接继承Quote

class Bulk_quote :public Disc_quote {
    Bulk_quote() = default;
    Bulk_quote(const string& book,double p,size_t qty,double disc):
        Disc_quote(book,p,qty,disc){}
    //overeide net_price() in class base
    double net_price(size_t)const override;
};

访问控制与继承

对于访问控制,记住基类的私有成员,不管派生类的继承方式是什么,都是不可见的。派生类可以改变基类中的成员的访问权限,也只限于可访问的成员。

6. 继承中的类作用域

当存在继承关系时,派生类的作用域嵌套在其基类中的作用域内,如果一个名字在派生类的作用域内是无法解析的,则编译器将继续在外层的基类作用域寻找该名字,并且是在编译时进行名字查找;当派生类

的成员名字与基类成员名字冲时,此时定义在内层作用域(派生类)的名字将隐藏定义在外层作用域(基类)的名字

 1 #include <iostream>
 2 
 3 
 4 struct Base {
 5     Base():mem(0){}
 6 protected:
 7     int mem;
 8 
 9 };
10 struct Derived :Base {
11     Derived(int i) :mem(i) {}//用i初始化mem,
12                              //Base::mem 进行默认初始化
13     int get_mem() { return mem;}//返回Derived::mem
14 protected:
15     int mem;//与基类成员名形同,会隐藏基类中的mem
16 };
17 int main()
18 {
19     Derived d(42);
20     std::cout << d.get_mem() << std::endl;//打印结果42,说明调用get_mem()
21                                           //对mem的解析结果是定义在Derived中的
22     system("pause");
23     return 0;
24 
25 }

 

                                                                                                                                                                                                                                                                                                                                                    

 

 

 

 

 

以上是关于CH15 面向对象程序设计的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段9——JS中的面向对象编程

Java面向对象程序设计第14章3-8和第15章6

《面向对象程序设计(java)》第七周学习总结

王艳 201771010127《面向对象程序设计(java)》第七周学习总结

周强 201771010141 《面向对象程序设计(java)》第七周学习总结

狄慧201771010104《面向对象程序设计(java)》第七周学习总结