一:继承和派生
0、默认构造函数即不带参数的构造函数或者是系统自动生成的构造函数。每一个类的构造函数可以有多个,但是析构函数只能有一个。
1、采用公用public继承方式,则基类的公有成员变量和成员函数的属性继承到派生类后不发生变化。例如book类的公有的setprice和settitle成员函数继承到codingbook类后,这两个成员变量的属性仍将是public属性。如果在继承过程中不指名继承方式时,编译器系统会默认继承方式为private或protected属性。
2、派生类在继承了父类的所有成员函数和成员变量后,是不能直接访问基类的私有成员函数和成员变量的。
3、从基类派生出派生类,派生类继承基类的继承方式有三种:public、protected和private。在未指定的情况下编译器会默认继承方式为protected或private方式。
1) public继承方式
- 基类中所有public成员在派生类中为public属性,可以访问;
- 基类中所有protected成员在派生类中为protected属性,不可以访问;
- 基类中所有private成员在派生类中不可访问。
2) protected继承方式
- 基类中的所有public成员在派生类中为protected属性,不可以访问;
- 基类中的所有protected成员在派生类中为protected属性,不可以访问。
- 基类中的所有private成员在派生类中仍然不可访问。
3) private继承方式
- 基类中的所有public成员在派生类中均为private属性;不可以访问;
- 基类中的所有protected成员在派生类中均为private属性;不可以访问;
- 基类中的所有private成员在派生类中均不可访问。
4、使用using声明可以改变基类成员在派生类中的访问属性。我们知道基类的公有成员经过公有继承,在派生类中其属性为public的,但是通过using 声明,我们可以将其改为private或protected属性。
5、如果派生类中新增一个成员变量,该成员变量与基类中的成员变量同名,则新增的成员变量就会遮蔽从基类中继承过来的成员变量。同理,如果派生类中新增的成员函数与基类中的成员函数同名,则该新增的成员函数就会遮蔽从基类中继承过来的成员函数。
- #include<iostream>
- #include<stdio.h>
- //c++继承和派生
- using namespace std;
- class A{
- public:
- A(){
- a=3;
- c=100;
- d=12;
- }
- int c;
- void Display(){
- cout<<"A Display"<<endl;
- }
- private:
- int a;
- protected:
- int d;
- };
- class B:public A{
- public:
- B(){c=917;b=4;}
- int c;
- void Display(){
- cout<<"B Display"<<endl;
- }
- private:
- int b;
- };
- int main(){
- B*p=new B;
- cout<<p->c<<endl;
- p->Display();
- p->A::Display(); //如果不想被遮蔽的书写方式
- system("pause");
- return 0;
- }
从上例中,我们可以看出被遮蔽了的基类的成员变量或成员函数并非是没有继承过来,而仅仅是被派生类的同名成员变量和成员函数给遮蔽了,调用的时候需要用到类名加上域解析操作符。
6、假设类C继承自类B,类B继承自类A。那么类C中的除了能够继承B类的成员函数和成员变量外,同样也能继承B类继承自A类的所有成员。换言之,类C可以继承来自类A的所有成员。因此继承既可以是直接继承,也可以是间接继承。
7、继承机制下的构造函数:派生类同样有构造函数。当我们创建一个派生类对象的时候,基类构造函数将会被自动调用,用于初始化派生类从基类中继承过来的成员变量。而派生类中新增的成员变量则需要重新定义构造函数用于初始化了。无论是对象指针的方式或者是直接创建对象的方式都会先调用基类的构造函数,然后调用派生类的构造函数。
当我们创建派生类对象时,先由派生类构造函数调用基类构造函数,然后再执行派生类构造函数函数体中的内容,也就是说先执行基类构造函数,然后再去执行派生类构造函数。如果继承关系有好几层的话,例如A类派生出B类,B类派生出C类,则创建C类对象时,构造函数的执行顺序则为A的构造函数,其次是B的构造函数,最后是C类的构造函数。构造函数的调用顺序是按照继承的层次,自顶向下,从基类再到派生类的。
8、派生类和基类构造函数的调用规则:基类默认构造函数-----》基类带参构造函数-----》派生类默认构造函数-------》派生类带参构造函数
9、派生类构造函数可以自动调用基类的默认构造函数,但是前提是默认构造函数必须存在。通常情况下,默认构造函数系统会自动生成的,但是如果在基类中,我们自己定义了一个带参数的构造函数,这个时候,系统是不会为基类自动生成默认构造函数的,这个时候派生类则无法自动调用基类的默认构造函数了,因为基类根本就不存在默认构造函数。遇到这种情况有两种解决方案:其一,在基类中定义一个默认构造函数(不带参数的构造函数),例如上一节中的例2;其二,派生类中的每一个构造函数都显式的调用基类中的带参构造函数,显示调用方法:。
我们还建议在设计类的时候为每一个类设计一个默认构造函数,毕竟默认构造函数并不会妨碍构造函数的显式调用。通常我们还会遇到这样一种情况,派生类中并未显式定义构造函数,这个时候派生类中只有系统自动生成的默认构造函数,如此一来,如果我们不为基类设计一个默认构造函数,则程序就会编译出错。这种错误很玄妙,如果不小心还真是难以发现。为了避免这种情况的发生,我们建议为每一个类设计一个默认构造函数。
总的来说,在创建派生类对象时,必须显式或隐式地调用基类的某一个构造函数,这一点非常重要。当然被调用的基类的构造函数可以是带参构造函数,也可以是默认构造函数。
10、创建派生类对象时构造函数的调用顺序是按照继承顺序,先执行基类构造函数,然后再执行派生类的构造函数。但是对于析构函数,其调用顺序是正好相反的,即先执行派生类的构造函数,然后再执行基类的构造函数。构造函数的执行顺序是按照继承顺序自顶向下的,从基类到派生类,而析构函数的执行顺序是按照继承顺序自下向上,从派生类到基类。因为每一个类中最多只能有一个析构函数,因此调用的时候并不会出现二义性,因此析构函数不需要显式的调用。
11、c++多继承:
在C++中一个派生类中允许有两个及以上的基类,我们称这种情况为多继承。单继承中派生类是对基类的特例化,例如前面中编程类书籍是书籍中的特例。而多继承中,派生类是所有基类的一种组合。在多继承中,派生类继承了所有基类中的所有成员变量和成员函数,这些继承过来的成员变量及成员函数其访问规则与单继承是相同的。使用多继承可以描述事物之间的组合关系,但是如此一来也可能会增加命名冲突的可能性,冲突可能很有可能发生在基类与基类之间,基类与派生类之间。命名冲突是必须要解决的问题。为了解决命名冲突问题我们只能采用域解析操作符来区分具体所调用的类中的成员函数,例如:。
12、c++虚基类(虚继承):
在多继承时很容易产生命名冲突问题,如果我们很小心地将所有类中的成员变量及成员函数都命名为不同的名字时,命名冲突依然有可能发生,比如非常经典的菱形继承层次。类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自A派生B然后派生D这一路,另一份来自A派生C然后派生D这一条路。
例1:
- class A
- {
- public:
- void setx(int a){x = a;}
- int getx(){return x;}
- private:
- int x;
- };
- class B: public A
- {
- public:
- void sety(int a){y = a;}
- int gety(){return y;}
- private:
- int y;
- };
- class C: public A
- {
- public:
- void setz(int a){z = a;}
- int getz(){return z;}
- private:
- int z;
- };
- class D: public B, public C
- {
- //......
- };
本例即为典型的菱形继承结构,类A中的成员变量及成员函数继承到类D中均会产生两份,这样的命名冲突非常的棘手,通过域解析操作符已经无法分清具体的变量了。为此,C++提供了虚继承这一方式解决命名冲突问题。虚继承只需要在继承属性前加上virtual关键字。
例2:
- #include <iostream>
- using namespace std;
- class A
- {
- public:
- void setx(int a){x = a;}
- int getx(){return x;}
- private:
- int x;
- };
- class B: virtual public A
- {
- public:
- void sety(int a){y = a;}
- int gety(){return y;}
- private:
- int y;
- };
- class C: virtual public A
- {
- public:
- void setz(int a){z = a;}
- int getz(){return z;}
- private:
- int z;
- };
- class D: public B, public C
- {
- //......
- };
- int main()
- {
- D test;
- test.setx(10);
- cout<<test.getx()<<endl;
- return 0;
- }
在本例中,类B和类C都是继承类A都是虚继承,如此操作之后,类D只会得到一份来自类A的数据。在本例的主函数中,定义了类D的对象test,然后通过该对象调用从类A间接继承来的setx和getx成员函数,因为B和C继承自类A采用的是虚继承,故通过D调用setx和getx不会有命名冲突问题,因为D类只得到了一份A的数据。