继承与派生

一、继承:

使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作

也可以在原有类的基础上增添新的属性和操作

从一个类派生出另一个类时,原始类称为基类,继承类称为派生类

派生类继承基类所有的成员变量和函数

派生类可扩展自己的功能


二、继承中的访问权限

public > protected > private

派生类

public :  公有继承

基类 public    成员:在派生类中还是 public    属性,在派生类的内部和外部都可以访问

基类 protected 成员:在派生类中还是 protected 属性,在派生类的内部可以访问, 外部不可以访问

基类 private   成员:在派生类中还是 private   属性,在派生类的内部

protected: 保护继承

基类 public    成员:在派生类中还是 protected 属性,在派生类的内部可以访问, 外部不可以访问

基类 protected 成员:在派生类中还是 protected 属性,在派生类的内部可以访问, 外部不可以访问

基类 private   成员:在派生类中还是 private   属性,在派生类的内部和外部都不可以访问

private: 私有继承

基类 public    成员:在派生类中还是 private   属性,在派生类的内部可以访问, 外部不可以访问

基类 protected 成员:在派生类中还是 private   属性,在派生类的内部可以访问, 外部不可以访问

基类 private   成员:在派生类中还是 private   属性,在派生类的内部和外部都不可以访问


三、类型兼容性原则

1、子类对象可以被当作父类对象使用

void func1()
{
	// Parent p;
	Child p;
	p.setAB(1,2);
	p.printAB();
}

2、父类指针可以直接指向子类对象

void func2()
{
	// c1 是子类对象的指针
	Child *c1 = new Child;
	c1->setAB(1,2);
	c1->setC(3);


	// p 是基类的指针   c1 是派生类的指针
	// p 是父类的指针   c1 是子类的指针,指向子类的对象
	// 父类指针可以直接指向子类对象, p可以直接指向一个子类的对象
	// 所以可以直接将 c1 赋值给 p
	// 将子类对象当作父类对象来使用
	Parent *p;


	p = c1;
	// p 是 Parent 类型的指针,所以只能使用 Parent 中的内容
	p->printAB();




	Parent *p2 = new Child;
}


3、子类对象可以直接初始化父类对象

4、子类对象可以对父类对象赋值

5、父类引用可以直接引用子类对象

void func4()
{
	Child c;
	c.setAB(1,2);
	c.setC(3);

	Parent &p1 = c;     //父类引用可以直接引用子类对象

	// 子类对象可以直接初始化父类对象
	Parent p = c; // Parent(c) ===>  Parent(const Parent &obj)  ====>  Parent(&c) ==> Parent(const Parent *const obj)
	test(&p);


	// 子类对象可以对父类对象赋值
	Child c2;
	c2.setAB(5,6);
	c2.setC(10);

	// Parent &operator=(const Parent &obj)
	// 父类引用可以直接引用子类对象
	// p.operator=(c2);
	p = c2;
	test(p);
}

四、继承中的构造和析构

派生类的组成:基类成员 +  派生类自己的成员

原则:谁的成员谁自己去初始化,分工明确, 

基  类:基类自己初始化(调用基类的构造函数)  ----->  在对象初始化列表中显示调用基类的构造函数

派生类:派生类自己初始化(调用派生类的构造函数)

构造:先构造基类、再构造组合对象、再构造派生类自己

析构:与构造顺序相反

class Child :public Parent
{
public:
	Child() : Parent(1,2)
	{
		c = 0;
		cout << "Child 构造函数被调用" << endl;
	}
	~Child()
	{
		cout << "Child 析构函数被调用" << endl;
	}
	void printC()
	{
		printf ("c = %d\n", c);
	}
private:
	int c;
};

class Child :public Parent
{
public:
	Child() : Parent(1,2), o1(1,2), o2(3,4)
	{
		c = 0;
		cout << "Child 构造函数被调用" << endl;
	}
	~Child()
	{
		cout << "Child 析构函数被调用" << endl;
	}
	void printC()
	{
		printf ("c = %d\n", c);
	}
private:
	int c;

	Object o1;
	Object o2;
};

五、继承中的同名成员和同名函数

1、当基类成员变量和派生类成员变量重名的时候,默认使用的是派生类的成员变量

2、使用基类的同名成员变量,要使用域解析符指明所属的类


关于继承中的重载问题:

重载只能发生在一个类之中,派生类不能重载基类的同名函数

当派生类重载基类的函数的时候,会将基类的所有的同名函数屏蔽掉,不能再使用


重定义:派生类有和基类 成员函数原型一样的函数,叫做函数重定义

class Parent
{
public:
	void  print()
	{
		printf ("a = %d, b = %d\n", a, b);
	}

	void func()
	{
		printf ("基类: 无参\n");
	}

	void func(int a)
	{
		printf ("基类: 有一个参数\n");
	}
public:
	int a;
	int b;
};

class Child : public Parent
{
public:
	// 派生类的同名成员函数会屏蔽基类的同名成员函数
	// 函数原型一样, 叫函数重定义
	void print()
	{
		printf ("a = %d, c = %d\n", a, c);
	}

	void func(int a, int b)
	{
		printf ("派生类: 有两个参数\n");
	}
public:
	int a;
	int c;
};

六、派生类中关键字 static 的使用

类的静态变量是所有派生类所共享的

静态变量存放在数据区,测算类的大小时,不包括静态变量


七、多继承

多重继承的语法:class 新类:基类列表(逗号分隔开)

多个父类的构造顺序和在继承时父类的声明顺序有关

基类指针指向派生类对象的时候,指针会根据基类数据成员在派生类中的

存储位置不同,做不同的偏移

#include <iostream>

using namespace std;

class Parent1
{
public:
	Parent1 (int a, int b)
	{
		this->a = a;
		this->b = b;
		cout << "Parent1 构造函数被调用" << endl;
	}

	void  print1()
	{
		printf ("a = %d, b = %d\n", a, b);
	}
private:
	int a;
	int b;
};

class Parent2
{
public:
	Parent2 (int a, int b)
	{
		this->c = a;
		this->d = b;
		cout << "Parent2 构造函数被调用" << endl;
	}

	void  print2()
	{
		printf ("c = %d, d = %d\n", c, d);
	}
private:
	int c;
	int d;
};

// 多重继承的语法:class 新类:基类列表(逗号分隔开)
// 多个父类的构造顺序和在继承时父类的声明顺序有关
class C:public Parent1, public Parent2
{
public:
	C(int e):Parent1(1,2), Parent2(3,4)
	{
		this->e = e;
		cout << "C 构造函数被调用" << endl;
	}
	void  printc()
	{
		printf ("e = %d\n", e);
	}
private:
	int e;
};

void func1(Parent1 &p)
{
	p.print1();
}

void func2(Parent2 &p)
{
	p.print2();
}

int main()
{
	cout << "sizeof C : " << sizeof C << endl;
	C c(10);

	func1(c);
	func2(c);

	// 基类指针指向派生类对象的时候,指针会根据基类数据成员在派生类中的
	// 存储位置不同,做不同的偏移 
	Parent1 *p1 = &c;
	Parent2 *p2 = &c;

	printf ("&C = %p\n", &c);
	printf ("p1 = %p\n", p1);
	printf ("p2 = %p\n", p2);

    return 0;
}

八、多继承的二义性

#include <iostream>

using namespace std;

class Parent1
{
public:
	Parent1 (int a, int b)
	{
		this->a = a;
		this->b = b;
		cout << "Parent1 构造函数被调用" << endl;
	}

	void  print1()
	{
		printf ("a = %d, b = %d\n", a, b);
	}

public:
	int a;
	int b;
};

class Parent2
{
public:
	Parent2 (int a, int b)
	{
		this->a = a;
		this->d = b;
		cout << "Parent2 构造函数被调用" << endl;
	}

	void  print2()
	{
		printf ("c = %d, d = %d\n", a, d);
	}

public:
	int a;
	int d;
};

class C:public Parent1, public Parent2
{
public:
	C(int e):Parent1(1,2), Parent2(3,4)
	{
		this->e = e;
		cout << "C 构造函数被调用" << endl;
	}
	void  printc()
	{
		printf ("e = %d\n", e);
	}
private:
	int e;
};

int main()
{
	C c(10);
	//c.a = 18;   编译器不知道该调用哪个基类的  a
	c.Parent1::a = 10;
	c.Parent2::a = 12;

	return 0;
}


多继承的二义性—钻石型结构

#include <iostream>

using namespace std;

class Parent
{
public:
	Parent (int a)
	{
		this->a = a;
		cout << "Parent  构造函数被调用" << endl;
	}
public:
	int a;
};


class Parent1 :public Parent
{
public:
	Parent1 (int b):Parent(1)
	{
		this->b = b;
		cout << "Parent1 构造函数被调用" << endl;
	}

	void  print1()
	{
		printf ("b = %d\n", b);
		cout << "Parent1  构造函数被调用" << endl;
	}

public:
	int b;
};

class Parent2 :public Parent
{
public:
	Parent2 (int b) :Parent(3)
	{
		this->d = b;
		cout << "Parent2 构造函数被调用" << endl;
	}

	void  print2()
	{
		printf ("d = %d\n", d);
	}

public:
	int d;
};

class C:public Parent1, public Parent2
{
public:
	C(int e):Parent1(2), Parent2(4)
	{
		this->e = e;
		cout << "C 构造函数被调用" << endl;
	}
	void  printc()
	{
		printf ("e = %d\n", e);
	}
private:
	int e;
};

int main()
{
	C c(10);

	c.Parent1::a = 10;

	return 0;
}

九、虚继承

如果一个派生类从多个基类产生,而这些基类又有一个共同的基类

,则在对该基类中声明的名字进行访问时,可能产生二义性,要使

这个公共基类在派生类中只产生一个子对象,必须对这个基类声明

为虚继承,使这个基类成为虚基类。


虚继承:解决多继承带来的二义性问题

#include <iostream>

using namespace std;

// 虚继承:解决多继承带来的二义性的问题


// 公共基类
class Parent
{
public:
	Parent (int a = 0)
	{
		this->a = a;
		cout << "Parent  构造函数被调用" << endl;
	}
public:
	int a;
};

// 虚继承自 公共基类
class Parent1 :virtual public Parent
{
public:
	Parent1 (int b):Parent(1)
	{
		this->b = b;
		cout << "Parent1 构造函数被调用" << endl;
	}
	void  print1()
	{
		printf ("b = %d\n", b);
		cout << "Parent1  构造函数被调用" << endl;
	}

public:
	int b;
};

class Parent2 :virtual public Parent                   
{
public:
	Parent2 (int b) :Parent(3)
	{
		this->d = b;
		cout << "Parent2 构造函数被调用" << endl;
	}
	void  print2()
	{
		printf ("d = %d\n", d);
	}
public:
	int d;
};

// 派生类
class C:public Parent1, public Parent2
{
public:
	C(int e):Parent1(2), Parent2(4)
	{
		this->e = e;
		cout << "C 构造函数被调用" << endl;
	}
	void  printc()
	{
		printf ("e = %d\n", e);
	}
public:
	int e;
};

int main()
{
	C c(1);

	Parent1 p1(1);
	cout << "sizeof p1 = " << sizeof p1 << endl;
	cout << "sizeof p2 = " << sizeof Parent2 << endl;
	cout << "sizeof c  = " << sizeof C << endl;

	c.a = 1;
	c.b = 2;
	c.d = 3;
	c.e = 4;
	
	return 0;
}

虚继承会有虚基类指针   vbptr->b->vbptr->d->e->a;






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值