C++继承

目录

一、继承的概念

二、继承的格式

三、基类和派生类的对象赋值兼容转换

四、隐藏

 五、派生类的默认成员函数

六、菱形继承

菱形继承的对象模型


一、继承的概念

        继承是指,在原有类的基础上扩展出新的功能,产生新的类,叫做派生类

二、继承的格式

       

        继承方式和访问限定符不同时,继承基类成员的变化      

         总结:

        1、当以 public 方式继承时,基类中的 public 成员还是派生类的 public 成员,protected 成员还是 protected 成员,private  在派生类中不可见。(以 public 方式继承时,与其在基类中限定符一样,除 private 是不可见)

        2、当以 protected 方式继承时,基类的 public 和 protected 成员在派生类中都是 protected 成员,基类中的 private 成员依旧是不可见。(基类中比 protected 等级低的在派生类中都是 protected成员,除 private 不可见)

        3、当以 private方式继承时,基类的 public 和 protected 成员在派生类中都是 private成员,基类中的 private 成员依旧是不可见。(基类中比 private等级低的在派生类中都是 private成员,除 private 不可见)

三、基类和派生类的对象赋值兼容转换

        派生类可以赋值给基类的 对象/指针/引用。这种方式也叫做切割或切片。

class Person
{
protected:
	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
private:
	string _id;// 学号
};

      

        在进行赋值时,父类只拿到子类中属于父类的那部分,而丢弃了只属于子类中的部分,就像切割了一样,因此这种赋值也叫做切割或切片。(这个赋值是天然的,并不会产生临时变量

Student s("la", "女", 17, "10201");
Person& rp = s;

         在赋值给父类的引用时,rp 改变了成员变量,s 中的成员变量也会跟着改变(因为赋值只是把子类 s 中父类的地址给 rp 即可)

四、隐藏

        如果基类和派生类中有同名的成员,就构成隐藏,子类将屏蔽父类的同名成员。

class Person
{
public:
	Person(string name = "lw", string sex = "男", int age = 18)
		:_name(name),
		_sex(sex),
		_age(age)
	{}

	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	Student(string name, string sex, int age, string id)
		:Person(name, sex, age),
		_id(id),
		_age(age)
	{}

	string _id;// 学号
	int _age;
};


int main()
{
	Student s("la", "女", 17, "10201");
	s._age = 100;

	return 0;
}

        上述代码有同名成员变量,将 _age 改为 100,默认改的是子类中的 _age 。

        可以指定使用 父类 的作用域,来访问 父类中的 _age。

Student s("la", "女", 17, "10201");
s.Person::_age = 100;

        同样,成员函数也可以构成隐藏。

class Person
{
public:
	Person(string name = "lw", string sex = "男", int age = 18)
		:_name(name),
		_sex(sex),
		_age(age)
	{}

	void func()
	{
		cout << "Person::func()" << endl;
	}
public:
	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	Student(string name, string sex, int age, string id)
		:Person(name, sex, age),
		_id(id),
		_age(age)
	{}

	void func(int x)
	{
		cout << "Student::func(int x)" << endl;
	}
	string _id;// 学号
	int _age;
};

  上述代码中的 func 函数就是构成隐藏,只要是同名成员函数就构成隐藏。与返回值和参数无关

 五、派生类的默认成员函数

    1、构造函数

        派生类的成员分为:

        ①、派生类的成员:自定义类型和内置类型分别处理

        ②、父类的成员:调用父类的构造函数

class Person
{
public:
	Person(string name = "lw", string sex = "男", int age = 18)
		:_name(name),
		_sex(sex),
		_age(age)
	{
		std::cout << "Person::Person()" << endl;
	}

	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	Student(string id = "2024593")
		:_id(id)
	{
		std::cout << "Student::Student()" << endl;
	}

	string _id;// 学号
};

int main()
{
	Student s;

	return 0;
}

        运行结果: 

             

         从上述代码可以看出,子类会先调用父类的构造函数,再调用自己的构造函数。

      如果父类没有默认构造函数可以使用 Person(...) 在初始化列表显示调用。(如 四 隐藏中的代码)

    2、拷贝构造: 

class Person
{
public:
	Person(string name = "lw", string sex = "男", int age = 18)
		:_name(name),
		_sex(sex),
		_age(age)
	{
		cout << "Person::Person()" << endl;
	}

	Person(const Person& p)
		:_name(p._name),
		_sex(p._sex),
		_age(p._age)
	{
		cout << "Person::Person(const Person& p)" << endl;
	}

	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	Student(string id = "2024593")
		:_id(id)
	{
		cout << "Student::Student()" << endl;
	}

	Student(const Student& s)
		:Person(s), // 父类调用父类的拷贝构造
		_id(s._id)
	{
		cout << "Student::Student(const Student& s)" << endl;
	}

	string _id;// 学号
};

int main()
{
	Student s;

	Student s_copy = s;

	return 0;
}

        3、赋值重载

#include <iostream>

using namespace std;

class Person
{
public:
	Person(string name = "lw", string sex = "男", int age = 18)
		:_name(name),
		_sex(sex),
		_age(age)
	{
		cout << "Person::Person()" << endl;
	}

	Person(const Person& p)
		:_name(p._name),
		_sex(p._sex),
		_age(p._age)
	{
		cout << "Person::Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		if(&p != this) // 如果不是自己给自己赋值
		{
			_name = p._name;
			_sex = p._sex;
			_age = p._age;
			cout << "operator=(const Person& p)" << endl;
		}

		return *this;
	}

	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	Student(string id = "2024593")
		:_id(id)
	{
		cout << "Student::Student()" << endl;
	}

	Student(const Student& s)
		:Person(s), // 父类调用父类的拷贝构造
		_id(s._id)
	{
		cout << "Student::Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if(&s != this) // 如果不是自己给自己赋值
		{
			Person::operator=(s); // 显示调用父类的赋值重载
			_id = s._id;
			cout << "operator=(const Student& s)" << endl;
		}

		return *this;
	}

	string _id;// 学号
};

int main()
{
	Student s;
	s._age = 100;

	Student s_copy;
	s_copy = s;

	return 0;
}

        4、析构函数

        子类析构时会自动调用父类的析构函数,不需要显示自己写。

        原因是为了保证析构顺序:先子后父。

        构造:先父后子;子类构造可能会使用父类的成员

        析构:先子后父;子类析构可能需要访问父类的成员

class Person
{
public:
	Person(string name = "lw", string sex = "男", int age = 18)
		:_name(name),
		_sex(sex),
		_age(age)
	{
		cout << "Person::Person()" << endl;
	}

	Person(const Person& p)
		:_name(p._name),
		_sex(p._sex),
		_age(p._age)
	{
		cout << "Person::Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		if(&p != this) // 如果不是自己给自己赋值
		{
			_name = p._name;
			_sex = p._sex;
			_age = p._age;
			cout << "operator=(const Person& p)" << endl;
		}

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}

	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	Student(string id = "2024593")
		:_id(id)
	{
		cout << "Student::Student()" << endl;
	}

	Student(const Student& s)
		:Person(s), // 父类调用父类的拷贝构造
		_id(s._id)
	{
		cout << "Student::Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if(&s != this) // 如果不是自己给自己赋值
		{
			Person::operator=(s);
			_id = s._id;
			cout << "operator=(const Student& s)" << endl;
		}

		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}

	string _id;// 学号
};

int main()
{
	Student s;

	return 0;
}

        子类的析构函数和父类的析构函数构成重载,因为由于多态原因,析构函数被特殊处理,函数名都会被处理成 destruction()。

        总结:派生类这些默认成员函数的规则与以前类似,不同的是分为了子类部分和父类部分,父类部分就调用父类的默认成员函数。

六、友元与静态成员函数

        1、父类的友元无法继承

        2、父类的静态成员在整个继承体系中只有一个

                (例:static int count; // 则父类和子类都有count,它们是同一个)

class Person
{
public:

	string _name; // 姓名
	string _sex;  // 性别
	int _age; // 年龄

	static int count;
};

int Person::count = 1;

class Student : public Person
{
public:

	string _id;// 学号
};

int main()
{
	Person p;
	Student s;

	cout << &p.count << endl;
	cout << &s.count << endl;
	return 0;
}

        同一个地址,说明是同一个 count。

六、菱形继承

        单继承:一个子类只有一个直接父类 


         多继承:一个子类有多个直接父类


菱形继承:多继承的两个父类继承了同一个类 

        菱形继承出现的问题:在 Assistant 类中,Person 类会有两份。

        会出现数据冗余与二义性的问题。

class Person
{
public:

	string _name; // 姓名
};

class Student : public Person
{
public:

	string _sid;// 学号
};

class Teacher : public Person
{
public:

	string _tid;// 教工号
};

class Assistant : public Teacher, public Student
{
public:
	string _major;// 专业
};

int main()
{
	Assistant a;
	a._name = "la";

	return 0;
}

  

        虽然我们可以用指定类域的方式解决二义性问题(如:Student::_name),但有很多情况我们不需要两份 Person,会造成空间的浪费。

        我们可以增加虚继承来解决数据冗余和二义性的问题。

        虚继承的方式:就是在有二义性的地方( Student 和 Teacher )继承方式前加 virtual

class Person
{
public:

	string _name; // 姓名
};

class Student : virtual public Person
{
public:

	string _sid;// 学号
};

class Teacher : virtual public Person
{
public:

	string _tid;// 教工号
};

class Assistant : public Teacher, public Student
{
public:
	string _major;// 专业
};

int main()
{
	Assistant a;
	a._name = "la";

	return 0;
}

         监视窗口:

        虚拟继承原理:原本 Student 中和 Teacher 中各有一个类 Person,但使用虚拟继承后,Student 和 Teacher 有一份公共的类 Person。Student 中和 Teacher 中不保存类 Person ,而是保存 Person 类的偏移量的地址。

        公共的类 Person 一般是在最后面(但没规定,也可以不在)

         

菱形继承的对象模型

       在VS环境下

class A
{
public:
	int _a;
};

class B :public A
{
public:
	int _b;
};

class C :public A
{
public:
	int _c;
};

class D :public B, public C
{
public:
	int _d;
};


int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

         没有使用虚拟继承时的 监视内存窗口:


 虚拟继承后 (环境为VS,X86): 

class A
{
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

class C : virtual public A
{
public:
	int _c;
};

class D :public B, public C
{
public:
	int _d;
};


int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

         内存窗口:         

        等于在虚拟继承中, D中的 B 和 C 都存的是 A 偏移量的地址,形成了偏移量表,所有的 D 对象都有一份偏移量表,用来找公共的 A。

        再定义一个 D dd;

        它们有相同的偏移量表。

        同理,在 B 和C 对象中也是存了偏移量表和公共的 A。

        它们的模型是一样的。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值