C++基础复习—— 继承

这篇是继承与派生

C++基础复习—— 继承

1.继承与派生

C++继承的几个特点: (1) 一个派生类可以有一个或多个基类,只有一个基类时,称为单继承;有多个基类时称为多继承。 (2) 继承关系可以是多级的。 (3) 不允许循环继承。 (4) 基类中能够被继承的部分只能是公有成员和保护成员,私有成员不能被继承。

单继承 说明: (1)在派生类定义中,继承方式只限定紧跟其后的那个基类。如果不显示给出继承方式系统默认私 有继承。 (2)派生方式有三个关键字,public private protected除此之外,缺省的方式默认私有继承。

派生类实现方式:吸收 改造(主要通过同名覆盖实现) 添加

多继承定义格式:class 派生类名:继承方式1 基类名1,继承方式2 基类名2,··· (继承方式缺省默认私有继承)

基类与派生类的关系: 1)派生类是基类的具体化 2)派生类是基类定义的延续 3)派生类是基类的组合

2.派生类的访问控制

公有继承:

      基类        派生类       派生类内    派生类外
     public  ->   public       可使用     不可使用
     private ->   不可访问     不可使用    不可使用
     protected ->protected     可使用     可通过成员函数访问

私有继承: 基类 派生类 派生类内 派生类外 public -> private 可使用 不可直接使用 protected-> private 可以使用 不可直接使用 private -> 不可访问 不能直接使用 不能直接使用(只能通过调用基类函数成员

3.派生类的构造函数和析构函数

派生类的构造函数:在派生类对象的成员中,从基类继承来的成员被封装为基类的子对象,他们的初始化由派生类的构造函数隐含调用基类构造函数进行初始化;派生类新增数据成员则在派生类自己定义的构造函数中进行初始化。对象在使用之前必须初始化,对派生类对象初始化之时,需要对该类数据成员赋初值。由于派生类数据成员是由所有基类成员与派生类新成员共同组成的,所以这里要注意参数是否与所有要初始化的参数相匹配。

  • 派生类构造函数的调用规则

1)单继承的构造函数调用顺序 调用基类构造函数->调用内嵌成员对象的构造函数->调用顺序取决于他们在类中定义的顺序->派生类自己的构造函数。

例题(包括子对象条件下)

class Base1  //基类
{
public:
	Base1(int i) {
		a = i;
		cout << "constructing Base1 a=" << a << endl;
	}
private:
	int a;
};
class Base2   //子对象f所属类
{
public:
	Base2(int i) {
		b = i;
		cout << "constructing Base2 b=" << b << endl;
	}
private:
	int b;
};
class Base3   //子对象g所属类
{
public:
	Base3(int i) {
		c = i;
		cout << "constructing Base3 c=" << c << endl;
	}
private:
	int c;
};
class Derivedclass :public Base1  //派生类
{
public:
	Derivedclass(int i, int j, int k, int m);
private:
	int d;
	Base2 f;   //子对象f
	Base3 g;  //子对象g
};
Derivedclass::Derivedclass(int i, int j, int k, int m) :Base1(i), g(j), f(k)
{
	d = m;
	cout << "constructing Derivedclass d=" << d << endl;
}
int main()
{
	Derivedclass x(5, 6, 7, 8);
	return 0;
}

下面是调试结果: constructing Base1 a=5 constructing Base2 b=7 constructing Base3 c=6 constructing Derivedclass d=8 *注意:子对象f和g的调用顺序取决于他们在派生类中被说明的顺序,与他们在成员初始化列表中的 顺序无关。 好了看完这个例子我们来总结一下派生类构造函数格式: 派生类名(参数总表):基类名1(参数表1),···,基类名m(参数表m),成员对象名1(成员对象参数表1),···成员对象n(成员对象参数表n) {

·······

};

2)多继承的构造函数调用顺序 多继承下派生类的构造函数必须同时负责该派生类所有基类构造函数的调用。构造函数的调用顺序是: 先调用所有基类的构造函数,再调用派生类的构造函数。处于同一层次的各基类构造函数的调用次序 取决于定义派生类所指定的基类顺序,与派生类构造函数中所定义的初始化列表顺序无关。 *对于同一个基类不允许直接继承两次 我们来感受一下

例题:

class Base1
{
public:
	Base1(int i)
	{
		a = i;
		cout << "constructing Base1 a=" << a << endl;
	}
private:
	int a;
};
class Base2
{
public:
	Base2(int i)
	{
		b = i;
		cout << "constructing Base2 b=" << b << endl;
	}
private:
	int b;
};
class Derivedclass :public Base1, public Base2
{
public:
	Derivedclass(int i, int j, int k);
private:
	int d;
};
Derivedclass::Derivedclass(int i, int j, int k) :Base2(i), Base1(j)
{
	d = k;
	cout << "constructing Derivedclass d=" << d << endl;
}
int main()
{
	Derivedclass x(5, 6, 7);
	return 0;
}

调试结果如下: constructing Base1 a=6 constructing Base2 b=5 constructing Derivedclass d=7 由于在声明派生类时,Base1在Base2之前,所以不关派生类构造函数参数列表的顺序如何,都要先 构造Base1,就是这个道理。

3)派生类的析构函数

  • 析构函数调用规则如下:

首先调用派生类的析构函数(清理派生类新增成员)->如果派生类有子对象,再调用子对象类的析构 函数->再调用普通基类的析构函数(清理从基类继承来的基类子对象)->最后调用虚基类的析构函数 例题:

class Base1
{
public:
	Base1(int i)
	{
		a = i;
		cout << "constructing Base1 a=" << a << endl;
	}
	~Base1()
	{
		cout << "destructing Base1" << endl;
	}
private:
	int a;
};
class Base2
{
public:
	Base2(int i)
	{
		b = i;
		cout << "constructing Base2 b=" << b << endl;
	}
	~Base2()
	{
		cout << "destructing Base2" << endl;
	}
private:
	int b;
};
class Base3
{
public:
	Base3(int i)
	{
		c = i;
		cout << "constructing Base3 c=" << c <<endl;
	}
	~Base3()
	{
		cout << "destructing Base3" << endl;
	}
private:
	int c;
};

class Derivedclass :public Base1
{
public:
	Derivedclass(int i, int j, int k, int m);
	~Derivedclass();
private:
	int d;
	Base2 f;
	Base3 g;
};
Derivedclass::Derivedclass(int i, int j, int k, int m) :Base1(i), g(j), f(k)
{
	d = m;
	cout << "constructing Derivedclass d=" << d << endl;
}
Derivedclass::~Derivedclass()
{
	cout << "destructing Derivedclass" << endl;
}
int main()
{
	Derivedclass x(5, 6, 7, 8);
	return 0;
}

调试结果如下: constructing Base1 a=5 constructing Base2 b=7 constructing Base3 c=6 constructing Derivedclass d=8 destructing Derivedclass destructing Base3 destructing Base2 destructing Base1

4.多继承

多继承语法: class 派生类名: 访问区分符 基类1的类名,···, 访问区分符 基类n的类名

*多继承中的二义性及其解决 引例:

class A
{
public:
	void fun() { cout<<"调用A类成员函数" << endl; }
};
class B
{
public:
	void fun() { cout << "调用B类成员函数" << endl; }
};
class C
{
public:
	void funC() {
		cout << "调用C类成员函数" << endl;
	}
};
class D :public B, public A, public C
{
public:
	void fun() {
		cout << "调用D类成员函数" << endl;
	}
};
int main()
{
	D d;
	d.fun();
	d.funC();
	return 0;
}

调试结果如下: 调用D类成员函数 调用C类成员函数 我们发现,派生类的成员函数覆盖了基类中所有与之重名的成员函数,这一规则对于数据成员同样 适用。 由此来看二义性的产生:如果只是基类与基类有同名函数成员,存在二义性,系统不能自行判断他们 派生类C的对象访问是的成员中的哪一个。

二义性的解决办法: 1)成员名限定:通过类作用域分辨符明确限定出现歧义的成员是继承自哪一个基类成员 2)成员重定义:在派生类中新增一个与基类成员相同的成员,由于同名覆盖,程序自动选择派生类 新增的成员。

多继承中构造函数和析构函数调用顺序 多继承情况下,基类及派生类的构造函数是按照以下顺序被调用的: (1)按基类被列出的顺序逐一调用基类构造函数 (2)如果该派生类存在对象成员,则调用成员对象的构造函数 (3)如果存在多个对象成员,按他们被列出的顺序逐一调用 (4)最后调用派生类构造函数 而析构函数调用顺序与构造函数的调用顺序正好相反 例题:

class HairColor
{
private:
	int color;
public:
	HairColor(int color)
	{
		cout << "Constructor of class HairColor called" << endl;
		this->color = color;
	}
	~HairColor()
	{
		cout << "Destructor of class HairColor called" << endl;
	}
};
class Horse
{
public:
	Horse()
	{
		cout << "Constructor of class Horse called" << endl;
	}
	~Horse()
	{
		cout << "Destructor of class Horse called" << endl;
	}
};
class Donkey
{
public:
	Donkey()
	{
		cout << "Constructor of class Donkey called" << endl;
	}
	~Donkey()
	{
		cout << "Destructor of class Donkey called" << endl;
	}
};
class Mule :public Horse, public Donkey
{
private:
	HairColor hcInstance;
public:
	Mule(int color) :hcInstance(color)
	{
		cout << "Constructor of class Mule called" << endl;
	}
	~Mule()
	{
		cout << "Destructor of class Mule called" << endl;
	}
};
int main()
{
	Mule muleInstance(100);
	cout << "Program over" << endl;
	return 0;
}

调试结果如下: Constructor of class Horse called Constructor of class Donkey called Constructor of class HairColor called Constructor of class Mule called Program over Destructor of class Mule called Destructor of class HairColor called Destructor of class Donkey called Destructor of class Horse called

5.虚基类

(1)多重派生的基类拷贝 引例:

class A
{
public:
  int x;
  A(int a){x=a;}
};
class B:public A
{
public:
  int y;
  B(int a,int b):A(b){y=a;}
};
class C:public A
{
public:
  int z;
  C(int a,int b):A(b)
  {
    z=a;
  }
};
class D:public B,public C
{
public:
  int m;
  D(int a,int b,int d,int e,int f):B(a,b),C(d,e)
  {
    m=f;
  }
  void Print()
  {
    cout<<"x="<<B::x<<'\t'<<"y="<<y<<endl;
    cout<<"x="<<C::x<<'\t'<<"z="<<z<<endl;
    cout<<"m="<<m<<endl;
  }
}
int main()
{
  D d1(100,200,300,400,500);
  d1.Print();
  return 0;
}

调试结果如下: x=200 y=100 x=400 z=300 m=500

虚基类的定义 定义格式: class <派生类名>:virtual <虚基类名> or class <派生类名>: virtual <虚基类名>

例题:定义虚基类,使派生类中只有基类的一个拷贝

class A
{
public:
  int x;
  A(int a=0)
  {
    x=a;
  }
};
class B:virtual public A
{
public:
  int y;
  B(int a,int b):A(b)
  {
    y=a;
  }
};
class C:public virtual A
{
public:
  int z;
  C(int a,int b):A(b)
  {
    z=a;
  }
};
class D:public B,public C
{
public:
  int m;
  D(int a,int b,int d,int e,int f):B(a,b),C(d,e){m=f;}
  void Print()
  {
    cout<<"x="<<x<<'\t'<<"y="<<y<<endl;
    cout<<"x="<<x<<"\t"<<"z="<<z<<endl;
    cout<<"m="<<m<<endl;
  }
};
int main()
{
  D d1(100,200,300,400,500);
  d1.Print();
  d1.x=400;
  d1.Print();
  return 0;
}

调试结果如下: x=0 y=100 x=0 z=300 m=500 x=400 y=100 x=400 z=300 m=500 有虚基类时,多继承方式下构造函数的调用顺序 例题

class base1
{
public:
  base1(){cout<<"constructing base1"<<endl;}
};
class base2
{
public:
  base2(){cout<<"constructing base2"<<endl;}
};
class derived1:public base2,virtual public base1
{
public:
  derived1(){cout<<"constructing derived1"<<endl;}
};
class derived2:public base2,virtual base1
{
public:
  derived2(){cout<<"constructing derived2"<<endl;}
};
class Derived3:public derived1,virtual public derived2
{
public:
  Derived3(){cout<<"constructing derived3"<<endl;}
};

int main()
{
  Derived3 obj;
  return 0;
}

调试结果如下 constructing base1 constructing base2 constructing derived2 constructing base2 constructing derived1 constructing derived3 *注意,虚基类的构造函数只调用一次

*虚基类的构造与析构 C++建立对象时所使用的派生类为最远派生类。对于虚基类而言,由于最远派生类对象中只有一个 公共虚基类子对象,为了初始化该公共子对象,最远派生类的构造函数要调用该公共基类的构造函数 ,而且只能被调用一次。 分为三种情况: 1)虚基类没有构造函数。程序自动调用系统缺省构造函数来初始化派生类对象中的虚基类子对象 2)虚基类中定义了缺省构造函数。程序自动调用自定义的缺省构造函数和析构函数 3)虚基类定义了带参的构造函数···比较复杂 textbook P196 *C++同时规定,在初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数 先于非虚基类的构造函数执行,虚基类的析构顺序与构造顺序完全相反。最先析构的是最远派生类自 身,最后析构的是虚基类

赋值兼容规则: 1)派生类的对象可以赋值给基类对象 2)派生类的对象可以初始化基类引用 3)派生类对象的地址可以赋给指向基类的指针(指向基类的指针也可以指向派生类 4)通过基类对象名,指针只能使用从基类继承的成员 示例

class B0
{
public:
  void display()
  {
    cout<<"B0::display()"<<endl;
  }
};
class B1:public B0
{
public:
  void display()
  {
    cout<<"B1::display"<<endl;
  }
};
class D1:public B1
{
  void display()
  {
    cout<<"D1::display"<<endl;
  }
};
void fun(B0 *ptr)
{
  ptr->display();
}
int main()
{
  B0 b0;
  B1 b1;
  D1 d1;
  B0 *p;
  p=&b0;
  fun(p);
  p=&b1;
  fun(p);
  p=&d1;
  fun(p);
  return 0;
}

调试结果如下 B0::display() B0::display() B0::display()


goodnight 2018.11.22 23:10

自认为是幻象波普星的来客
Built with Hugo
主题 StackJimmy 设计