essential c++ 第五章

1.面向对象编程

主要特征:继承、多态

void loan_check_in(libmat &mat){
	mat.check_in();
	if(mat.is_late())
		mat.assess_fine();
	if(mat.waiting_list())
		mat.notify_available();
} 
  • 继承

继承体系最根本的就是抽象基类,抽象基类:带有纯虚函数,抽象类不可实例化,不可定义对象。

在面向对象的程序中,间接利用指向抽象基类的指针或引用来操作每一个类

  • 多态

让基类的指针或引用能够指向任何一个派生类的对象

  • 动态绑定

编译器在编译时就依据mat所属的类决定执行哪一个check_in函数,由于在程序执行之前就已经解析出应该调用哪个函数,所以这种方式是静态绑定

 在面向对象的编程中,编译器无法得知应该调用哪个check_in函数,每次loan_check_in执行时,只能在执行中根据mat所指的实际对象来确定应该调用哪个loan_check_in,解析操作是在运行时才进行,所以叫动态绑定

多态和动态绑定只有在使用指针和引用的时候才能发挥作用

2.面向对象的编程思维

我们通过libmat派生出book 再从book中派生出audiobook

默认情况下,成员函数都是在编译时静态进行,若要其在运行时动态进行,需要virtual关键字,

同时定义非成员函数,注意其中的形参是引用

#include 
#include 
#include 
using namespace std;


class LibMat {        //抽象基类,注意其中的析构函数和成员函数为虚函数,virtual表示其编译在运行时动态进行
public:
	LibMat(){
		cout << "LibMat::LibMat() default constructor!\n";
	}
	
	virtual ~LibMat(){
		cout << "LibMat::~LibMat() destructor!\n";
	}

	virtual void print() const {
		cout << "LibMat::print() -- I am a LibMat object!\n";
	}
};

class Book : public LibMat {    //Book的派生类
public:
	Book( const string &title, const string &author )
		: _title( title ), _author( author ){
		cout << "Book::Book( " << _title
			 << ", " << _author << " )  constructor\n";
	}

	~Book(){
		cout << "Book::~Book() destructor!\n";
	}

	virtual void print() const {
		cout << "Book::print() -- I am a Book object!\n"
			 << "My title is: " << _title << '\n'
			 << "My author is: " << _author << endl;
	}

	const string& title() const { return _title; }
	const string& author() const { return _author; }

protected:
	string _title;
	string _author;
};

class AudioBook : public Book {   //AudioBook派生类,继承了Book,注意构造函数调用Book的构造函数,在构造时,调用其基类Book的构造函数
public:
	AudioBook( const string &title, 
		       const string &author, const string &narrator )
		: Book( title, author ), _narrator( narrator ){
		cout << "AudioBook::AudioBook( " << _title
			 << ", " << _author 
			 << ", " << _narrator
			 << " )  constructor\n";
	}

	~AudioBook(){
		cout << "AudioBook::~AudioBook() destructor!\n";
	}

	virtual void print() const {
		cout << "AudioBook::print() -- I am a AudioBook object!\n"
			 << "My title is: " << _title << '\n'
			 << "My author is: " << _author << '\n'
			 << "My narrator is: " << _narrator << endl;
	}

	const string& narrator() const { return _narrator; }

protected:
	string _narrator;
};

void print( const LibMat &mat )
{
	cout << "in global print(): about to print mat.print()\n";
	mat.print();
}

int main()
{

	// objects are in local blocks to force destruction
	{   
        cout << "\n" << "Creating a LibMat object to print()\n";
  	    LibMat m;
	    print( m );
	}

	{
        cout << "\n" << "Creating a Book object to print()\n";
	    Book b( "The Castle", "Franz Kafka" );
	    print( b );
	}
	
	{
        cout << "\n" << "Creating a AudioBook object to print()\n";
	    AudioBook ab( "Man Without Qualities", "Robert Musil", "Kenneth Meyer" );
	    print( ab );
	}

	return 0; // unnecessary but quiets vc++
}

程序结果为:

Creating a LibMat object to print()
LibMat::LibMat() default constructor!
in global print(): about to print mat.print()
LibMat::print() -- I am a LibMat object!
LibMat::~LibMat() destructor!

Creating a Book object to print()
LibMat::LibMat() default constructor!
Book::Book( The Castle, Franz Kafka )  constructor
in global print(): about to print mat.print()
Book::print() -- I am a Book object!
My title is: The Castle
My author is: Franz Kafka
Book::~Book() destructor!
LibMat::~LibMat() destructor!

Creating a AudioBook object to print()
LibMat::LibMat() default constructor!
Book::Book( Man Without Qualities, Robert Musil )  constructor
AudioBook::AudioBook( Man Without Qualities, Robert Musil, Kenneth Meyer )  constructor
in global print(): about to print mat.print()
AudioBook::print() -- I am a AudioBook object!
My title is: Man Without Qualities
My author is: Robert Musil
My narrator is: Kenneth Meyer
AudioBook::~AudioBook() destructor!
Book::~Book() destructor!
LibMat::~LibMat() destructor!
  • 第一部分

非成员函数的实参为抽象基类LibMat的对象,程序调用了抽象基类的虚函数print,mat.print在运行时被解析为LibMat::print(),同时也会调用抽象基类的构造函数和析构函数。

  • 第二部分

1.此时传入非成员函数的实参时派生类的Book的对象,mat.print所进行的虚拟调用有效,程序调用了Book::print(),而不是LibMat的成员函数。

2.同时基类和派生类的构造函数和析构函数均会被执行,调用了基类的默认构造函数。

3.Book中的print函数覆盖了基类中的同名函数,注意覆盖和重载的区别

4.同时关键字protected声明的成员可以被派生类访问,除了派生类外,都不能访问。

  • 第三部分

1.AudioBook中的构造函数,需要调用其基类Book中的构造函数,来对继承了成员变量进行初始化

2.程序运行先后调用了LibMat、Book、AudioBook的构造函数,同时print被解析为AudioBook::print,析构函数也会被执行,但是顺序和构造函数相反。

3.不带继承的多态

4.定义一个抽象基类

定义抽象类第一是要找到所有子类的共同操作,第二是找出哪些操作和类型有关。

虚函数:操作与类型有关的函数,即在不同类中的同名函数的功能不同

普通成员函数:具有共通的操作行为

class num_sequence {
public:

    virtual ~num_sequence(){};

    virtual num_sequence *clone() const = 0;    //纯虚函数

    virtual unsigned int elem( int pos ) const = 0;   //纯虚函数
    virtual const char*  what_am_i() const = 0;     //纯虚函数
    static  int max_elems(){ return _max_elems; }

    virtual ostream& print( ostream &os = cout ) const = 0;  //纯虚函数
	
  
protected:
	
    virtual void gen_elems( int pos ) const = 0;
    bool check_integrity( int pos, int size ) const; 
    const static int _max_elems = 1024;	
};

每个虚函数,要么有定义,要么就被声明为纯虚函数

  • 任何类如果有一个既以上的纯虚函数,由于其接口不完整(纯虚函数没有函数定义,不完整),程序无法为其产生任何对象,只能做派生类的子对象使用,同时派生类必须为纯虚函数提供定义
  • 对于构造函数:在本例中,没有任何非静态变量需要初始化,所以构造函数没有意义。
  • 析构函数:基类中有一个或多个虚函数,其析构函数要声明为virtual。

例如,当我使用

num_sequence *ps =new Fibonacci(12);
delete ps;

Fibonacci为其一个派生类,在delete时,我应该调用的是Fibonacci的析构函数,而不是num_sequence的,将析构函数定义为虚函数,可以使解析操作在运行时进行。同时,析构函数最好不要声明为纯虚函数

5.定义派生类

派生类必须为从基类继承来的每个纯虚函数提供定义,还要声明派生类的专属成员变量。

class Fibonacci : public num_sequence {
public:
   Fibonacci( int beg_pos = 1, int len = 1 )
   {  set_position( beg_pos ); set_length( len ); }
		
   virtual unsigned int elem( int pos ) const;

   virtual const char* what_am_i() const { return "Fibonacci"; }
   virtual ostream&    print( ostream &os = cout ) const; 

   virtual int length()  const { return _length;  }
   virtual int beg_pos() const { return _beg_pos; }


protected:
   virtual void gen_elems( int pos ) const;
   static vector< unsigned int > _elems;
   
   int _length; 	
   int _beg_pos; 
};
  • 在这个派生类中,length() 、beg_pos()两个函数不是虚函数,在基类中也没有这两个函数,没有覆盖的对象,因为他们不是基类提供的接口的一员,所以无法通过基类的指针或引用来访问他们。
num_sequence *ps =new Fibonacci(12);
ps->what_am_i();  //通过虚函数机制,调用了Fibonacci::what_am_i()

ps->max_elems();  //调用继承来的num_sequence::max_elems()

ps->length();   //错误,length()不是num_sequence中的一员

ps->beg_pos();   //错误,beg_pos()不是num_sequence中的一员

delete ps       //通过虚函数机制调用Fibonacci的析构函数

解决方法:1.在基类中加上两个纯虚函数length() 、beg_pos(),这样派生类中的就自动变成虚拟函数,不需要在指定关键字virtual

                     2.将length() 、beg_pos()移到基类中,派生类中就变成了了继承而来的非虚拟成员函数

  • 在类之外对虚拟函数进行定义,不需要指明关键字virtual
inline int num_sequence::
elem( int pos ) const 
{
    if(! check_integrity(pos))
        return 0;
    if(pos>_elem.size())
        Fibonacci::gen_elems(pos);
    return _elems[pos-1];   
}
  1. 在这段程序中,check_integrity(pos)是从基类中继承来的普通成员函数,继承来的public和protected成员,在派生类中也可以被视为其自身拥有的成员,基类中的public 成员在派生类中也是public
  2. 而对于gen_elems函数,因为其为虚拟函数,在基类和派生类中都有,所以我们要指定使用哪个类中的函数,不必等到运行时才解析出想调用Fibonacci中的函数。

 

如果在基类中又一个普通成员函数check_integrity,在派生类中也有一个check_integrity,如下程序:

class num_sequence {
public:
protected:

    bool check_integrity( int pos, int size ) const; 
};


class Fibonacci : public num_sequence {
public:
protected:
    bool check_integrity( int pos, int size ) const; 

};


inline int num_sequence::
elem( int pos ) const 
{
    if(! check_integrity(pos))   //此处调用的是Fibonacci的check_integrity
        return 0;  
}

每当派生类的某个成员和基类的成员重名的时候,便会遮挡住基类的那份成员,也就是派生类对该名称的任何使用,都会被解析为该派生类的那个成员,而不是继承来的成员

带来的问题是:在基类中check_integrity并未被视作虚函数,所以,每次通过基类的指针或引用来调用check_integrity,解析出来的都是基类的那个函数,并未考虑这个指针或引用指向的对象是什么。

void Fibonacci::example(){
    num_sequence *ps=new Fibonacci(12,8);

    ps->elem(1024);  //通过虚拟机制动态解析为Fibonacci::elem()
    
    ps->check_integrity(pos);   //静态解析为num_sequence::check_integrity
    
}

所以在基类和派生类中提供同名的非虚拟函数,或许基类中所有的函数都应该被声明为virtual

6.运用继承体系

7.基类应该多么抽象

引用永远无法代表空对象,指针可以指向null。

成员变量中,如果有引用,那么必须在构造函数中的成员初始化列表中将其初始化,如果是指针,则没这个要求

class num_sequence{
	protected:
		num_sequence(int len,int bp,vector&re):_length(len),_beg(bp),_relems(re){
		}
		int _length;
		int _beg;
		vector &_relems;
}; 

8.初始化,析构,复制

如果基类中存在实际的成员变量,必须为基类提供初始化,较好的方式是为基类提供构造函数。

num_sequence含有纯虚函数,是一个抽象基类,无法为其定义任何对象,num_sequence的作用是为每个派生类对象的子对象。所以,我们将基类中的构造函数声明为protected。

  • 派生类的构造函数,不仅必须为派生类的成员变量进行初始化,也要为基类的提供初始化。
inline Fibonacci::Fibonacci(int len,int beg_pos):num_sequence(len,beg_pos,_elems){}
  • num_sequence 提供默认构造函数,这里,我们必须把_relems改为指针
num_sequence::num_sequence(int len=1,int bp=1,vector *pe=0):_length(len),_beg_pos(bp),_pelems(pe){}

默认构造函数,虽然有形参,但是均有默认值,调用时不需要传入实参。

如果派生类没有指定调用基类的构造函数,编译器就会自动调用基类的默认构造函数。

9.在派生类中定义虚函数

在定义派生类的时候,必须决定,是将虚函数覆盖掉,还是继承虚函数,如果继承了纯虚函数,那么派生类也是抽象类。

你可能感兴趣的:(essential,c++)