P25-c++类继承-02基类和派生类关系详细梳理,以及例子演示!

1一个筒单的基类

从一个类派生出另一个类时,原始类称为基类,继承类称为派生类
为说明继承,首先需要一个基类
Webtown俱乐部决定跟踪乒兵球会会员。作为俱乐部的首席程序员,需要设计一个简单的 TableTennisplayer
类,如程序清单13.1和13.2所示。
程序清单13.1 TableTennisPlayer.h

/*
	author:梦悦foundation
	公众号:梦悦foundation
	可以在公众号获得源码和详细的图文笔记
*/
// TableTennisPlayer.h -- a table-tennis base class
#ifndef TABTENN0_H_
#define TABTENN0_H_
#include <string>
using std::string;
// simple base class
class TableTennisPlayer
{
    
    
private:
	string firstname;
	string lastname;
	bool hasTable;
public:
	TableTennisPlayer (const string & fn = "none",
					   const string & ln = "none", bool ht = false);
	void Name() const;
	bool HasTable() const {
    
     return hasTable; };
	void ResetTable(bool v) {
    
     hasTable = v; };
};
#endif

程序清单13.2 TableTennisPlayer.cpp

/*
	author:梦悦foundation
	公众号:梦悦foundation
	可以在公众号获得源码和详细的图文笔记
*/
	//TableTennisPlayer.cpp -- simple base-class methods
#include "TableTennisPlayer.h"
#include <iostream>

TableTennisPlayer::TableTennisPlayer (const string & fn, 
	const string & ln, bool ht) : firstname(fn),
		lastname(ln), hasTable(ht) {
    
    }

	
void TableTennisPlayer::Name() const
{
    
    
	std::cout << lastname << ", " << firstname;
}

TableTennisPlayer类只是记录会员的姓名以及是否有球桌。
有两点需要说明。首先,这个类使用标准 string类来存储姓名,相比于使用字符数组,这更方便、更灵活、更安全,而与第12章的 String类相比,这更专业。其次,构造函数使用了第12章介绍的成员初始化列表语法,但也可以像下面这样做:

TableTennisPlayer::TableTennisPlayer(const string & fn, const string & In, bool ht)
{
    
    
	firstname = fn;
	lastname = ln;
	hasTable = ht;
}

这将首先为 firstname调用 string的默认构造函数,再调用 string的赋值运算符将 firstname设置为fn, 但初始化列表语法可减少ー个步骤,它直接使用 string的复制构造函数将 firstname初始化为fn。 -----对这句话存在疑问????
上面这段话,我还是有点疑问的,测试一下,是不是这么个步骤!
这里自己定义了一个 String 类,然后仿照上面 定义了一个 UseString类,这个类里面有一个firstname成员变量,它的构造函数也和上面形式是一样的。为了方便观察,讲很多信息打印出来了。如形参的地址等,好用来分析。

class UseString {
    
    
private:
	int i;
	String firstname;
public:
	UseString(const String &firstname) {
    
    
		cout << "UseString(const String &firstname), this's address:" << this << ", this->firstname address:" << &(this->firstname) << ", &firstname:" << &firstname << endl; 
		this->firstname = firstname;
	}
	UseString(const UseString & u) {
    
    
		cout << "UseString(const UseString & u)" <<endl;
	}
#if 0
	UseString(const char *u) {
    
    
		cout << "UseString(const char* u)" <<endl;
	}
#endif	
};

1. Demo.cpp

  /*
	 author:梦悦foundation
	 公众号:梦悦foundation
	 可以在公众号获得源码和详细的图文笔记
 */
 
#include <iostream>
#include <typeinfo>
#include <string>
#include <cstring>
 
using namespace std;

class String
{
    
    
private:
	char * str;			  // pointer to string
	int len;				  // length of string
	static int num_strings; // number of objects
	static const int CINLIM = 80;  // cin input limit
	public:
	// constructors and other methods
	String(const char * s); // constructor
	String(); 			  // default constructor
	String(const String &); // copy constructor
	~String();			  // destructor
	int length () const {
    
     return len; }
	// overloaded operator methods	
	String & operator=(const String &);
	String & operator=(const char *);
	char & operator[](int i);
	const char & operator[](int i) const;
	// overloaded operator friends
	friend bool operator<(const String &st, const String &st2);
	friend bool operator>(const String &st1, const String &st2);
	friend bool operator==(const String &st, const String &st2);
	friend ostream & operator<<(ostream & os, const String & st);
	friend istream & operator>>(istream & is, String & st);
	// static function
	static int HowMany();
};

// initializing static class member

int String::num_strings = 0;

// static method
int String::HowMany()
{
    
    
	 return num_strings;
}

// class methods
String::String(const char * s) 	// construct String from C string
{
    
    
	cout << "String(const char * s)"  << ", s:" << s  << ", this's address:" << this << endl;
	len = std::strlen(s);			// set size
	str = new char[len + 1];		// allot storage
	std::strcpy(str, s);			// initialize pointer
	num_strings++; 				// set object count
}

String::String()					// default constructor
{
    
    
	cout << "String()" << ", this's address:" << this << endl;
	len = 4;
	str = new char[1];
	str[0] = '\0'; 				// default string
	num_strings++;
}

String::String(const String & st)
{
    
    
	cout << "String(const String &st)" <<endl;
	num_strings++; 			// handle static member update
	len = st.len;				// same length
	str = new char [len + 1];	// allot space
	std::strcpy(str, st.str);	// copy string to new location
}

String::~String()					   // necessary destructor
{
    
    
	cout << "~String()" << ", this's address:" << this << endl;
	--num_strings; 				   // required
	delete [] str; 				   // required
}

// overloaded operator methods    

	 // assign a String to a String
String & String::operator=(const String & st)
{
    
    
	
	cout << "operator=" << ", this's address:" << this << ", &st:" << &st << ", st.str:" << st.str << endl;
	if (this == &st)
	 return *this;
	delete [] str;
	len = st.len;
	str = new char[len + 1];
	std::strcpy(str, st.str);
	return *this;
}

	 // assign a C string to a String
String & String::operator=(const char * s)
{
    
    
	delete [] str;
	len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str, s);
	return *this;
}

	 // read-write char access for non-const String
char & String::operator[](int i)
{
    
    
	return str[i];
}

	 // read-only char access for const String
const char & String::operator[](int i) const
{
    
    
	return str[i];
}

// overloaded operator friends

bool operator<(const String &st1, const String &st2)
{
    
    
	return (std::strcmp(st1.str, st2.str) < 0);
}

bool operator>(const String &st1, const String &st2)
{
    
    
	return st2 < st1;
}

bool operator==(const String &st1, const String &st2)
{
    
    
	return (std::strcmp(st1.str, st2.str) == 0);
}

	 // simple String output
ostream & operator<<(ostream & os, const String & st)
{
    
    
	os << st.str;
	return os; 
}

	 // quick and dirty String input
istream & operator>>(istream & is, String & st)
{
    
    
	char temp[String::CINLIM];
	is.get(temp, String::CINLIM);
	if (is)
	 st = temp;
	while (is && is.get() != '\n')
	 continue;
	return is; 
}

class UseString {
    
    
private:
	int i;
	String firstname;
public:
	UseString(const String &firstname) {
    
    
		cout << "UseString(const String &firstname), this's address:" << this << ", this->firstname address:" << &(this->firstname) << ", &firstname:" << &firstname << endl; 
		this->firstname = firstname;
	}
	UseString(const UseString & u) {
    
    
		cout << "UseString(const UseString & u)" <<endl;
	}
#if 0
	UseString(const char *u) {
    
    
		cout << "UseString(const char* u)" <<endl;
	}
#endif	
};
int main()
{
    
    
	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	UseString u1("梦悦foundation");
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
	return 0;
}


2. 看看哪个效率更高?

1. 第一种 ,不用初始化列表的方式

	UseString(const String &firstname) {
    
    
		cout << "UseString(const String &firstname), this's address:" << this << ", this->firstname address:" << &(this->firstname) << ", &firstname:" << &firstname << endl; 
		this->firstname = firstname;
	}

main函数

int main()
{
    
    
	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	UseString u1("梦悦foundation");
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
	return 0;
}

运行结果:

meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$ g++ -o Demo Demo.cpp
meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$ ./Demo
---------------开始--->公众号:梦悦foundation---------------
String(const char * s), s:梦悦foundation, this's address:0x7fff94bc0900
String(), this's address:0x7fff94bc0918
UseString(const String &firstname), this's address:0x7fff94bc0910, this->firstname address:0x7fff94bc0918, &firstname:0x7fff94bc0900
operator=, this's address:0x7fff94bc0918, &st:0x7fff94bc0900, st.str:梦悦foundation
~String(), this's address:0x7fff94bc0900
---------------结束--->公众号:梦悦foundation---------------
~String(), this's address:0x7fff94bc0918
meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$

首先 UseString u1("梦悦foundation"); 这句话会导致 相匹配的原型的 构造函数被调用, 下面图里的第二种显示调用构造函数会调用 赋值运算符。 因为他既不属于已有对象的复制,也不属于需要创建临时对象的范畴。
在这里插入图片描述
紧接着 发现 形参不匹配 ,实参是 "梦悦foundation", 但是形参是 const String &firstname,所以会调用 String 类与这个字符串相匹配的 构造函数 进行转换,生成一个临时对象。这里匹配的构造函数类型是
String::String(const char * s) // construct String from C string 所以可以看到最先打印的是这一行

String(const char * s), s:梦悦foundation, this's address:0x7fff94bc0900

这个临时String 的对象地址是 0x7fff94bc0900
接着开始构造 u1 这个对象,构造他之前,因为 里面有一个 String对象,所以先调用String构造函数
u1->String 地址是 0x7fff94bc0918

String(), this's address:0x7fff94bc0918

现在执行到 UseString类的构造函数 UseString(const String &firstname)
从打印能看出来 这个u1的地址是 0x7fff94bc0910, String对象的地址和上面的一样是0x7fff94bc0918, 而且 传递进来的形参地址firstname 而最开始那个临时对象地址是一样的 0x7fff94bc0900

UseString(const String &firstname), this's address:0x7fff94bc0910, this->firstname address:0x7fff94bc0918, &firstname:0x7fff94bc0900

接着就要执行 this->firstname = firstname; 这会导致赋值运算符被调用 (this->firstname.operator=(firstname);) 所以下面的打印 this的地址 确实是 0x7fff94bc0918,然后形参是之前的临时对象(梦悦foundation转换的String类临时对象)

operator=, this's address:0x7fff94bc0918, &st:0x7fff94bc0900, st.str:梦悦foundation

之后u1的构造函数执行完毕,这个临时对象就被析构掉了

~String(), this's address:0x7fff94bc0900

这里联想到一个问题 如果 main函数写成这样有什么影响吗?
Demo1.cpp

	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	UseString u1 = "梦悦foundation";
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
	return 0;

这会导致 一个编译错误
因为 这会让编译器想把字符串 "梦悦foundation" 转换为 UseString 类,但是 UseString 并没有一个与之相匹配的 构造函数, 本来以为 UseString(const String &firstname) 这个是可以的,因为他是能接受 字符串作为参数的,但是应该他不接受两次转换,即字符串还得转换为 String类型。肯定是直接转换为 UseString类型,

meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$ g++ -o Demo1 Demo1.cpp
Demo1.cpp: In function ‘int main():
Demo1.cpp:188:17: error: conversion from ‘const char [17]’ to non-scalar type ‘UseString’ requested
  UseString u1 = "梦悦foundation";
                 ^~~~~~~~~~~~~~~~~~
meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$

即便我们换成 c++标准的 string 也是编译报错。
这样还是会报错的。

	UseString(const string &firstname) {
    
    
		cout << "UseString(const String &firstname), this's address:" << this << ", this->firstname address:" << &(this->firstname) << ", &firstname:" << &firstname << endl; 
	}

2. 使用TableTennisPlayer这个类

程序清单13.3使用了TableTennisPlayer这个类
程序清单13.3 UseTableTennisPlayer.cpp


/*
	author:梦悦foundation
	公众号:梦悦foundation
	可以在公众号获得源码和详细的图文笔记
*/


#include <iostream>
#include "TableTennisPlayer.h"
using std::cout;
using std::endl;

int main ( void )
{
    
    

	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	
    TableTennisPlayer player1("san", "zhang", true);
    TableTennisPlayer player2("si", "li", false);
    player1.Name();
    if (player1.HasTable())
        cout << ": has a table.\n";
    else
        cout << ": hasn't a table.\n";
    player2.Name();
    if (player2.HasTable())
        cout << ": has a table";
    else
        cout << ": hasn't a table.\n";
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;

    // std::cin.get();
    return 0;
}

编译运行的结果:

meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$ g++ -o UseTableTennisPlayer UseTableTennisPlayer.cpp TableTennisPlayer.cpp
meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$ ./UseTableTennisPlayer
---------------开始--->公众号:梦悦foundation---------------
zhang, san: has a table.
li, si: hasn't a table.
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$

注意到该程序实例化对象时将C-风格字符串作为参数

    TableTennisPlayer player1("san", "zhang", true);
    TableTennisPlayer player2("si", "li", false);

但构造函数的形参类型被声明为const string &
这导致类型不匹配,但与第12章创建的 String类一样,string类有一个将 const char*作为参数的构造函数,使用C-风格字符串初始化 string对象时,将自动调用这个构造函数。
总之,可将 string对象或C-风格字符串作为构造函数 TableTennisPlayer的参数:将前者作
为参数时,将调用接受 const string&作为参数的 string构造函数,而将后者作为参数时,将调用接受 const char*作为参数的 string构造函数。

3.派生一个类

Webtown俱乐部的一些成员曾经参加过当地的乒乓球锦标赛,需要这样一个类,它能包括成员在比赛中的比分。与其从零开始,不如从 TableTennis class类派生出一个类。首先将 RatedPlayer类声明为从TableTennis Class类派生而来

//RatedPlayerderives from the TableTennisPlayer base class
class RatedPlayer : public TableTennisPlayer

冒号指出 RatedPlayer类的 基类是 TableTennisplayer类。
上述特殊的声明头表明 TableTennisPlayer是
个公有基类,这被称为公有派生。
派生类对象包含基类对象。使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问(稍后将介绍保护成员)。
上述代码完成了哪些工作呢? RatedPlayer,对象将具有以下特征:

  • 派生类对象存储了基类的数据成员(派生类继承了基类的实现)
  • 派生类对象可以使用基类的方法(派生类继承了基类的接口)。
    因此, RatedPlayer对象可以存储运动员的姓名及其是否有球桌。另外, RatedPlayer对象还可以使用 TableTennisPlayer类的Name()、 hasTable()和 ResetTablet()方法(参见图13.1)。
    在这里插入图片描述
    需要在继承特性中添加什么呢?
  • 派生类需要自己的构造函数。
  • 派生类可以根据需要添加额外的数据成员和成员函数。
    在这个例子中,派生类需要另ー个数据成员来存储比分,还应包含检索比分的方法和重置比分的方法。
    因此,类声明与下面类似:
// simple derived class
class RatedPlayer : public TableTennisPlayer
{
    
    
private:
    unsigned int rating;
public:
    RatedPlayer (unsigned int r = 0, const string & fn = "none",
                 const string & ln = "none", bool ht = false);
    RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
    unsigned int Rating() const {
    
     return rating; }
    void ResetRating (unsigned int r) {
    
    rating = r;}
};

构造函数必须给新成员(如果有的话)和继承的成员提供数据。
在第一个 RatedPlayer构造函数中,每个成员对应一个形参;而第二个 RatedPlayer构造函数使用一个 TableTennisPlayer参数,该参数包括firstname、 lastname和 hasTable。

1.构造函数:访问权限的考虑

派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。
例如,RatedPlayer构造函数不能直接设置继承的成员( firstname、 lastname和 hasTable),而必须使用基类的公有方法来访问私有的基类成员。
具体地说,派生类构造函数必须使用基类构造函数。
创建派生类对象时,程序首先创建基类对象。
从概念上说,这意味着基类对象应当在程序进入派生类构造函数之前被创建。
C++使用成员初始化列表语法来完成这种工作。例如,下面是第一个 RatedPlayer构造函数的代码

// RatedPlayer methods
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
     const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
    
    
    rating = r;
}

其中: TableTennisPlayer(fn,ln,ht)是成员初始化列表。它是可执行的代码,调用TableTennisPlayer构造函数。例如,假设程序包含如下声明:

RatedPlayer rplayer1(1140, " Mallory", "Duck", true );

则 RatedPlayer构造函数将把实参“ Mallory”、“Duck”和true赋给形参fn、ln和ht,然后将这些参数作为实参传递给 TableTennisPlayer构造函数,后者将创建一个嵌套 TableTennisPlayer对象,并将数据 “ Mallory”、“Duck”和true存储在该对象中。然后,程序进入 RatedPlayer构造函数体,完成 RatedPlayer对象的创建,并将参数r的值(即1140)赋给 rating成员

如果省略成员初始化列表,情况将如何呢?

RatedPlayer::RatedPlayer(unsigned int r, const string & fn
const string &ln, bool ht)// what if no initializer list?
{
    
    
	rating = r;
}

必须首先创建基类对象,如果不调用基类构造函数,程序将使用默认的基类构造函数,因此上述代码与下面等效

RatedPlayer::RatedPlayer(unsigned int r, const string & fn
const string &ln, bool ht)//: TableTennisPlayer()
{
    
    
	rating = r;
}

除非要使用默认构造函数,否则应显式调用正确的基类构造函数。
下面来看第二个构造函数的代码:

RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
    : TableTennisPlayer(tp), rating(r)
{
    
    
}

这里也将 TableTennisPlayer 的信息传递给了 TableTennisPlayer构造函数:

TableTennisPlayer(tp)

由于的类型为 TableTennisPlayer &, 因此将调用基类的复制构造函数。基类没有定义复制构造函数但第12章介绍过,如果需要使用复制构造函数但又没有定义,编译器将自动生成一个。在这种情况下执行成员复制的隐式复制构造函数是合适的,因为这个类没有使用动态内存分配( string成员确实使角了动态内存分配,但本书前面说过,成员复制将使用 string类的复制构造函数来复制 string成员)

有关派生类构造函数的要点如下:

  • 首先创建基类对象;
  • 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
  • 派生类构造函数应初始化派生类新增的数据成员
    这个例子没有提供显式构造函数,因此将使用隐式构造函数。
    释放对象的顺序与创建对象的顺序相反, 即首先执行派生类的析构函数,然后自动调用基类的析构函数。

注意:创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。
派生类的构造函数总是调用一个基类构造函数。可以使用初始化器列表语法指明要使用的基类构造函数,否则将使用默认的基类构造函数。
派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。

1. 成员初始化列表

派生类构造函数可以使用初始化器列表机制将值传递给基类构造函数。请看下面的例子:

derived::derived(type1 x, type2 y): base(x, y)//initializer list

其中 derived是派生类,base是基类,x和y是基类构造函数使用的变量。例如,如果派生类构造函数接收到参数10和12,则这种机制将把10和12传递给被定义为接受这些类型的参数的基类构造函数。
除虚基类外(参见第14章), **类只能将值传递回相邻的基类,但后者可以使用相同的机制将信息传递给相邻的基类,依此类推。**如果没有在成员初始化列表中提供基类构造函数,程序将使用默认的基类构造函数。
成员初始化列表只能用于构造函数。

4. 使用派生类

要使用派生类,程序必须要能够访问基类声明。程序清单13.4将这两种类的声明置于同一个头文件中也可以将每个类放在独立的头文件中,但由于这两个类是相关的,所以把其类声明放在一起更合适。
程序清单13.4 RatedPlayer.h

/*
	author:梦悦foundation
	公众号:梦悦foundation
	可以在公众号获得源码和详细的图文笔记
*/

#ifndef TABTENN1_H_
#define TABTENN1_H_
#include <string>
#include <iostream>
using std::string;
using std::cout;
using std::endl;

// simple base class
class TableTennisPlayer
{
    
    
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer (const string & fn = "none",
                       const string & ln = "none", bool ht = false);
	TableTennisPlayer(const TableTennisPlayer & t);
    void Name() const;
    bool HasTable() const {
    
     return hasTable; };
    void ResetTable(bool v) {
    
     hasTable = v; };
};

// simple derived class
class RatedPlayer : public TableTennisPlayer
{
    
    
private:
    unsigned int rating;
public:
    RatedPlayer (unsigned int r = 0, const string & fn = "none",
                 const string & ln = "none", bool ht = false);
    RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
    unsigned int Rating() const {
    
     return rating; }
    void ResetRating (unsigned int r) {
    
    rating = r;}
};

#endif

程序清单13.5是这两个类的方法定义。同样,也可以使用不同的文件,但将定义放在一起更简单。
程序清单13.5 RatedPlayer.cpp

/*
	author:梦悦foundation
	公众号:梦悦foundation
	可以在公众号获得源码和详细的图文笔记
*/

#include "RatedPlayer.h"
#include <iostream>
using std::cout;
using std::endl;

TableTennisPlayer::TableTennisPlayer (const string & fn, 
    const string & ln, bool ht) : firstname(fn),
	    lastname(ln), hasTable(ht) {
    
    }
    
TableTennisPlayer::TableTennisPlayer(const TableTennisPlayer &t)
{
    
    
	cout << "TableTennisPlayer(const TableTennisPlayer &t)" << endl;
}


void TableTennisPlayer::Name() const
{
    
    
    std::cout << lastname << ", " << firstname;
}

// RatedPlayer methods
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
     const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
    
    
    rating = r;
}

RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
    : TableTennisPlayer(tp), rating(r)
{
    
    
}

程序清单13.6创建了 TableTennisPlayer类和 RatedPlayer类的对象。
请注意这两个类对象是如何使用
TableTennisPlayer类的Name()和 HasTable()方法的。
程序清单13.6 UseRatedPlayer.cpp

/*
	author:梦悦foundation
	公众号:梦悦foundation
	可以在公众号获得源码和详细的图文笔记
*/


#include <iostream>
#include "RatedPlayer.h"
using std::cout;
using std::endl;

int main ( void )
{
    
    

	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	TableTennisPlayer player1("san", "Zhang", false);
	RatedPlayer rplayer1(1140, "si", "Li", true);
	rplayer1.Name();		  // derived object uses base method
	if (rplayer1.HasTable())
		cout << ": has a table.\n";
	else
		cout << ": hasn't a table.\n";
	player1.Name(); 		  // base object uses base method
	if (player1.HasTable())
		cout << ": has a table";
	else
		cout << ": hasn't a table.\n";
	cout << "Name: ";
	rplayer1.Name();
	cout << "; Rating: " << rplayer1.Rating() << endl;
// initialize RatedPlayer using TableTennisPlayer object
	RatedPlayer rplayer2(1212, player1);
	cout << "Name: ";
	rplayer2.Name();
	cout << "; Rating: " << rplayer2.Rating() << endl;
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
    // std::cin.get();
    return 0;
}


编译运行的结果:

meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$ ./UseRatedPlayer
---------------开始--->公众号:梦悦foundation---------------
Li, si: has a table.
Zhang, san: hasn't a table.
Name: Li, si; Rating: 1140
Name: Zhang, san; Rating: 1212
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/class_inherit/02$

5. 派生类和基类之间的特殊关系

派生类与基类之间有一些特殊关系。
其中之一是派生类对象可以使用基类的方法,条件是方法不是私有的:

	RatedPlayer rplayer1(1140, "si", "Li", true);
	rplayer1.Name();		  // derived object uses base method

另外两个重要的关系是:

  1. 基类指针可以在不进行显式类型转换的情况下指向派生类对象;
  2. 基类引用可以在不进行显式类型转换的情况下引用派生类对象:
RatedPlayer rplayer1(1140, "si", "Li", true);
TableTennisPlayer & rt= rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name(); //invoke Name() with reference
pt->Name(); // invoke Name() with pointer

然而,基类指针或引用只能用于调用基类方法,因此,不能使用rt或者pt来调用派生类的ResetRating方法。

通常,C++要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说是例外。然而,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针

TableTennisPlayer player("si", "Li", true);
RatedPlayer &rr = player; // NOT ALLOWED
RatedPlayer *pr = player; //NOT ALLOWED

爸爸可以拿儿子的钱,但是儿子不能拿爸爸的钱
上述规则是有道理的。
例如,如果允许基类引用隐式地引用派生类对象,则可以使用基类引用为派生类对象调用基类的方法。因为派生类继承了基类的方法,所以这样做不会出现问题。
如果可以将基类对象赋给派生类引用,将发生什么情况呢?派生类引用能够为基对象调用派生类方法,这样做将出现问题。
例如,将RatedPlayer::Rating()方法用于 TableTennisPlayer 对象是没有意义的,因TableTennisPlayer 对象没有 rating成员。

基类指针或者引用可以指向 派生类的 原因是因为 派生类 继承了基类,所以怎么使用这个指针或者引用都可以。但是,派生类不能指向基类,是因为 派生类里面有的东西,基类里面没有

如果基类引用和指针可以指向派生类对象,将出现一些很有趣的结果。其中之一是基类引用定义的函数或指针参数可用于基类对象或派生类对象。例如,在下面的函数中:

void Show(const TableTennisPlayer & rt)
{
    
    
	using std::cout;
	cout << "Name: ";
	rt.Name();
	cout << "\nTable: ";
	if (rt.Hastable())
		cout << "yes \n";
	else
	cout << "no\n";
	
}

形参rt是一个基类引用,它可以指向基类对象或派生类对象,所以可以在Show)中使用 TableTennisPlayer 参数或 Ratedplayer参数:

TableTennisPlayer player1("Tara", " Boomdea", false);
RatedPlayer rplayerl(1140, "Mallory", " Duck", true);
Show(player1); // works with TableTennisPlayer argument
Show(rplayer1); // works with RatedPlayer argument

对于形参为指向基类的指针的函数,也存在相似的关系。它可以使用基类对象的地址或派生类对象的地址作为实参

void Wohs(const TableTennisPlayer *pt); //function with pointer parameter
TableTennisPlayer player("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Wohs(&player1); // works with TableTennisPlayer argument
wohs(&rplayer1); // works with RatedPlayer argument

引用兼容性属性也让您能够将基类对象初始化为派生类对象,尽管不那么直接。假设有这样的代码

RatedPlayer olaf1(1840, "Olaf", "Loaf", true); //派生类
TableTennisPlayer olaf2(olaf1); //派生类 olaf1 赋值给 基类 olaf2

要初始化olaf2,匹配的构造函数的原型如下:

Tabletennisplayer(const RatedPlayer &); // doesn!t exist

类定义中没有这样的构造函数,但存在隐式复制构造函数:

// implicit copy constructor
Tabletennisplayer(const TableTennisPlayer &);

形参是基类引用,因此它可以引用派生类。这样,将olaf2初始化为 olaf1时,将要使用该构造函数, 它复制 firstname、 lastname和 Hastable成员。换句话来说,它将olaf2初始化为在 RatedPlayer对象中的 TableTennisPlayer olaf1对象

同样,也可以将派生对象赋给基类对象

RatedPlayer olaf1(1840, "Olaf", "Loaf ", true);
TableTennisPlayer winner;
winner = olaf1; // assign derived to base object 派生类赋值给基类

在这种情况下,程序将使用隐式重载赋值运算符:

TableTennisPlayer & operator=(const TableTennisPlayer & ) const;

基类引用指向的也是派生类对象,因此olaf1的基类部分被复制给 winner。

猜你喜欢

转载自blog.csdn.net/sgy1993/article/details/113827623