c++ 继承

📖基础知识

1,什么是继承

  • 继承就是用户从现有的类(基类)创建一个新的类(派生类)。
  • 派生类继承了基类的所有功能,并且可以拥有自己的其他功能。

举个例子。
一个父类Animal,两个子类cat和dog。
在这里插入图片描述
以下是代码实现。

class Animal
{
public:
	void eat()
	{
	cout<<"Animal can eat"<<endl;
	}

	void sleep()
	{
		cout << "Animal can sleep" << endl;
	}

	string name;
	string clocur;

protected:
	int age;
};

class dog :public Animal
{
public:
	void dog_speak()
	{
		cout << "wang-wang-wang" << endl;
	}
};

class cat :public Animal
{
public:
	void cat_speak()
	{
		cout << "mi-mi-mi" << endl;
	}
};

int main()
{
	cat c;
	c.clocur = "red";
	c.name = "cat";
	//c.age = 3;这样会报错,因为age是protected类型的,不能被继承
	c.eat();//调父类的函数
	c.cat_speak();//调子类的函数
}

在上面的示例中,Animal类是基类,而dog和cat类则是从Animal派生出来的。

  • 派生的类与类的声明一起出现,后跟冒号,关键字public和派生该类的基类名称。
  • 由于cat和dog是从Person派生的,因此可以从它们访问Person的所有成员数据和成员函数。

在这里插入图片描述

有图可以看出,我们实例化的cat类的对象c继承了Animal的成员。但是我们不能继承被保护的成员“age”。“age”虽然在c中可见,但是不可用。也就是不可修改。

2,继承的访问说明符

  • 从基类创建派生类时,可以使用不同的访问说明符来继承基类的数据成员。

  • 这些可以是public, protected 或 private.。

  • 在上面的实例中,基类Animal被dog公开地继承了。

还是刚才的例子,我门这次实现private和protected继承

class Animal
{
	//....
};

class dog :private Animal//private继承
{
	//....
};

class cat :private Animal//protected继承
{
	//....
};

我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。

保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。

私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

3,继承的成员函数重写

  • 假设基类和派生类的成员函数具有相同的名称和参数。

  • 如果创建派生类的对象并尝试访问该成员函数,则只能调用派生类中的成员函数。

  • 派生类的成员函数将覆盖基类的成员函数。

class Animal
{
public:
	//animal类的名字的默认缺省值是dongwu
	string name="dongwu";

	void speak()
	{
		cout << "animal can speak" << endl;
	}

protected:
	int age;
};

class dog :public Animal
{
public:
	//dog类的名字的默认缺省值是gou
	string name="gou";

	void speak()
	{
		cout << "dog can speak" << endl;
	}
protected:
	int age;
};

int main()
{
	dog d;
	cout << d.name << endl;
	d.speak();
	return 0;
}

运行结果
在这里插入图片描述


在上述代码中,基类和派生类有同名的函数speak,和相同的成员name,我创建了派生类的对象d,并且想要输出d(派生类)的speak()和name。结果是输出了派生类的值,没有输出基类的值,也就是说派生类对基类形成了“重写”。(也叫隐藏)

📖继承的访问权限

1,访问控制和继承

派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:

在这里插入图片描述

  • 一个派生类继承了所有的基类方法,但下列情况除外:

  • 1,基类的构造函数、析构函数和拷贝构造函数。

  • 2,基类的重载运算符。

  • 3,基类的友元函数。

接下来我们用代码证明上述的结论。

 class Person
{
public:
	//构造函数
	Person(const char* name)
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	//拷贝构造函数
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	//运算符重载
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	//析构函数
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	//构造函数
	Student(const char* name = "", int num = 0)
		:_num(num)
		, Person(name)
	{
		cout << "Student(const char* name = "", int num = 0)" << endl;
	}

	//拷贝构造函数
	Student(const Student& s)
		:Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	// 运算符重载  s1 = s3
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}

		cout << "Student& operator=(const Student& s)" << endl;

		return *this;
	}

	// 父子类的析构函数构成隐藏关系
	// 原因:下一节多态的需要,析构函数名统一会被处理成destructor()

	// 为了保证析构顺序,先子后父,
	// 子类析构函数完成后会自动调用父类析构函数,所以不需要我们显示调用
	//析构函数
	~Student()
	{
		//Person::~Person();

		cout << "~Student()" << endl;
	}// -》自动调用父类析构函数
protected:
	int _num; //学号
	
};

 /*子类构造函数原则:
 a、调用父类构造函数初始化继承自父类成员
 b、自己再初始化自己的成员 -- 规则参考普通类
 析构、拷贝构造、复制重载也类似*/
int main()
{
	Student s1("李四", 1);
	cout << "------" << endl;
	Student s2(s1);
	cout << "------" << endl;
	Student s3("王五", 2);
	s1 = s3;

	return 0;
}

运行结果如下图
在这里插入图片描述
由图可以看出,
创建派生类对象s1,先后调用了父类的构造函数,子类的构造函数
拷贝构造派生类对象s2,先后调用了父类的拷贝构造函数,子类的拷贝构造函数
赋值函数也就是运算符重载,先后调用了父类的operator=,子类的operator

我们可以总结结论
子类构造函数原则:
a、调用父类构造函数初始化继承自父类成员
b、自己再初始化自己的成员 – 规则参考普通类
析构、拷贝构造、复制重载也类似

  • 这也就证明了最开始给出的结论,派生类可以继承基类的所有方法,但是构造函数,析构函数,拷贝构造函数,重载不可以被继承,这些函数还要再派生类中重新实现。(因为不能继承,所以必须再写一次)

📖多重继承

1,多继承

多继承即一个子类可以有多个父类,它继承了多个父类的特性。

C++ 类可以从多个类继承成员,语法如下:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,{
<派生类类体>
};

我们来看个例子

class animal
{
	//...
};
class dog
{
	//...
};

//erha继承了2个父类
class erha :public animal, public dog
{
	//...
};

多继承是C++独有的一种继承方式,这种继承方式是c++的一个大坑!!!如果不是特殊情况,最好不要使用多继承!!!
接下来我举个多继承大坑的例子

class base1
{
public:
    void someFunction()
    {
        //.... ... ....
    }
};
class base2
{
    void someFunction()
    {
       // .... ... ....
    }
};
class A : public base1, public base2
{

};

int main()
{
    A a;

    a.someFunction() // 错误!  
}

上述的代码不能正常运行,因为a是派生类A的实例化对象,A继承的两个基类有同名的函数someFunction,a去调用a.someFunction()时,编译器不知道要调用那个函数,所以报错了。

  • 要想解决这个问题,可以在调用函数时写清楚要调用那个函数所类。

2,多层继承

在C ++编程中,不仅可以从基类派生一个类,还可以从派生类派生一个类。这种继承形式称为多层继承。

class A
{
	//...
};

class B:public A
{
	//...
};

class C :public B
{
	//...
};

这里,类B派生自基类A,类C派生自派生类B。


然后我们实际运行一个程序
以下是代码

class A
{
public:
	void test()
	{
		cout << "这是A的test函数" << endl;
	}
};

class B:public A
{
	//...
};

class C :public B
{
	//...
};

int main()
{
	C c;
	c.test();
	return 0;
}

以下是运行结果
在这里插入图片描述

在这个程序中,C类是从B类派生出来的(B类是从基类A派生出来的)。

C类的c对象在main()函数中定义。

调用test()函数时,将执行类A中的test()。 这是因为在C类和B类中没有test()函数。

编译器首先在类C中查找test()函数。由于该函数在该类中不存在,因此它将在类B中查找该函数(因为C是从B派生的)。

这个test()函数在B类中也不存在,所以编译器在A类中寻找它(因为B是从A派生出来的)。

如果C中存在test()函数,则编译器将覆盖类A的test()(因为成员函数覆盖)。

📖继承和组合

我先举一个例子,让大家看看什么是组合


//这是组合
class A
{
	//...
};

class B
{
	A a;
};


//这是继承
class C
{

};

class D :public C
{
	//...
};

继承和组合都完成了类的复用

  1. 组合是一种黑盒测试,A对B是不透明的,A保持着他的封装

  2. 继承是一种白盒测试(透明),C对D是透明的,在一定程度上破坏了父类的封装性。

  3. 如果要比较谁更好,结果是组合更好。因为程序追求的是“高内聚,低耦合”,组合的封装性更强,程序间的耦合度就越低,更符合程序的要求。

📖虚函数

讲虚函数之前,我们先来看一个例子

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;
}

以下是内存的存储结构
在这里插入图片描述

改变成虚函数后的代码

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;
}

以下是内存存储结构图
在这里插入图片描述

然后我们画一下这几个类的继承关系
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值