详解C++中继承的基本内容

有些类与类之间存在特殊的关系,有共性也有特性,比如动物类可以细分为猫,狗等。下级别的成员除了拥有上一级的共性,还有自己的特性,这个时候就可以考虑继承的技术,减少重复代码。

一、继承中的对象模型

1.1 子类继承父类中的成员变量

子类从父类继承的成员变量,是属于子类呢还是属于父类呢?我们定义如下示例:

class father{
public:
	int f_a;
protected:
	int f_b;
private:
	int f_c;
};
class son : public father{	// 从son是father的子类
public:
	int s_a;
};
son S1;
cout << "son所占的内存为:" << sizeof(S1) << endl;

输出结果为:

son所占的内存为:16

因此可以看出,父类中的所有变量都被子类给继承了下来,都属于子类的一部分。虽然父类中 private 访问权限的成员不能被子类访问,但是仍然属于子类的一部分。同理,在子类继承父类时,除了继承父类中所有的成员变量,也同时继承了除了父类构造函数外的所有成员函数,这样便可以有效节省代码量,提高代码复用效率。至于子类与父类构造函数之间的关系,将在后文进行解释。

1.2 子类与父类构造函数的原则

上面提到,子类可以继承父类中所有的成员变量和成员方法,但不继承父类的构造函数。因此在创建子类对象时,为了初始化从父类继承来的成员变量,系统需要调用父类的构造方法。我们都知道,任何一个类都要有构造函数,那么子类的构造函数和父类的构造函数之间的关系是怎样的?

  1. 如果子类没有定义构造函数,则调用父类的无参构造函数;

  2. 如果子类定义了构造函数(不管是无参还是有参),在创建子类对象时首先执行父类的无参构造函数,然后执行自己的构造函数;

    这两种情况下,不管子类是否定义了新的构造函数,只要没有显示的调用父类中的构造函数,都只会调用父类中的无参构造函数。

  3. 如果父类中自己定义了有参构造函数,那么编译器便不会再提供默认无参构造函数,子类便只能在构造函数中显示的调用父类的构造函数来对父类成员进行初始化。

仅仅通过文字会比较抽象,下面我们通过几个具体的例子来解释一下上面的构造原则:

class father{
public:
	string f_a;
    // 与默认构造函数一样,都是无参构造函数
	father(){
		cout << "我是父类的无参构造函数" << endl;
	}
    // 自定义的有参构造函数
    father(string f_a){
        this->f_a = f_a;
        cout << "我是父类的有参构造函数" << endl;
    }
};
class son : public father{
public:
	string f_a;
    // 子类中的构造函数,在没有显示调用父类构造函数的情况下,默认调用父类中的无参构造函数
	son(string f_a){
		cout << "我是子类的构造函数" << endl;
	}
};
son S1("abc"); // 如果此时创建对象,那么将会调用父类中的无参构造函数,然后调用子类的构造函数

输出结果如下:

我是父类的无参构造函数	//调用父类的无参构造函数
我是子类的构造函数	// 调用子类的构造函数

假如父类中只定义了有参构造函数,在创建子类对象时编译器便找不到父类中的无参构造函数,于是便会报错。解决这类问题的办法就是在子类构造函数中显示地调用父类的有参构造函数来对父类的成员初始化。

class father{
public:
	string f_a;
    // 自定义的有参构造函数
    father(string f_a){
        this->f_a = f_a;
        cout << "我是父类的有参构造函数" << endl;
    }
};
class son : public father{
public:
	string f_a;
    // 子类中的构造函数,由于父类中没有提供无参构造函数,导致出错
	//son(string f_a){
	//	cout << "我是子类的构造函数" << endl;
	//}
    son(string f_a):father("father"){	// 显示调用父类中的有参构造函数
		cout << "我是子类的构造函数" << endl;
	}
};
son S1("abc"); // 如果此时创建对象,那么将会调用父类中的无参构造函数,然后调用子类的构造函数

输出结果如下:

我是父类的有参构造函数	//显示调用父类的有参构造函数
我是子类的构造函数	// 调用子类的构造函数

二、继承中的构造和析构顺序

在上面一节中也简单提到了继承过程中的构造顺序,原则是:

  1. 父类先执行构造函数,子类然后执行构造函数
  2. 子类先执行析构函数,父类然后执行析构函数

下面通过一个简单的例子来简要说明一下:

class father{
public:
	string f_a;
    father(){
        cout << "我是父类的构造函数" << endl;
    }
	~father(){
        cout << "我是父类的析构函数" << endl;
    }
};
class son : public father{
public:
	string f_a;
	son(){
        cout << "我是子类的构造函数" << endl;
    }
	~son(){
        cout << "我是子类的析构函数" << endl;
    }
};

输出结果如下:

我是父类的构造函数
我是子类的构造函数
我是子类的析构函数
我是父类的析构函数

二、同名成员的处理

当子类与父类中出现同名的成员,如何通过子类对象访问到子类或者父类中的同名数据呢?首先我们先理解一下为什么子类和父类中会出现同名的成员变量和成员函数。这是因为变量和函数都有它的作用域,在同一个作用域中不能出现两个重名的变量,但是在不同的作用域中可以出现重名。这就相当于每个学校都有一个校长,但是在同一所学校内只能有一个校长。由于父类和子类是两个不同的作用域,所以可以出现重名的变量或者函数。

class father{
public:
	string f_a;
	int f_b;
	father(){	// 父类中的无参构造函数,初始化父类成员
		f_a = "father";
		f_b = 10;
	}
    void show(){
        cout << " 我是father!" << endl;
    }
};
class son : public father{
public:
	string f_a;
	son(){	// 子类中的无参构造函数,初始化子类成员
		f_a = "son";
	}
    void show(){
        cout << " 我是son!" << endl;
    }
};
son S1;
cout << "f_b = " << S1.f_b << endl;	// 子类对象可以像访问自己的成员一样访问从父类继承下来的成员
cout << "f_a = " << S1.f_a << endl; // 但是,从父类继承了一个变量 f_a , 子类自己也有一个 f_a , 那么访问的是哪个呢?

输出结果为:

f_b = 10
f_a = son	// 访问的是子类中的成员

通过上述实例可以看出,当子类和父类中有同名的成员时,子类对象优先访问子类中的成员,若想访问父类中的成员,应该指定父类成员的作用域:

cout << "f_a = " << S1.father::f_a << endl; // 给 f_a 加上一个父类的作用域

输出结果为:

f_a = father	// 访问到了父类中的同名成员

对成员函数的访问也是一样,比如:

S1.show();	// 访问子类中的show
S1.father::show();	// 访问父类中的show

输出结果为:

我是son!
我是father!
posted @ 2021-07-06 15:09  ZhiboZhao  阅读(1484)  评论(1编辑  收藏  举报