继承

继承是面向对象复用的重要手段。通过继承定义一个类,继承是类型之间的关系建模,共享公有的东西,实现各自本质不同的东西。

一、继承定义的格式


二、继承关系&访问限定符


实现一个简单的继承关系


继承是一种复用手段,在继承关系里基类的成员类的成员派生类的成员,由此达到复用的目的。

三、三种继承关系下基类成员的在派生类的访问关系变化

继承方式基类的public成员基类的protected成员基类的private成员继承引起的访问控制关系变化概括
public继承仍为public成员仍为protected成员不可见基类的非私有成员在子类的访问属性都不变
protected继承变为protected成员变为protected成员不可见基类的非私有成员都成为子类的私有成员
private继承变为private成员变为private成员不可见基类中的非私有成员都成为子类的私有成员

不可见的含义:在子类中存在,但不可以访问(在派生类(子类)中,类内、类外都不可以访问)

父类中private在子类中都不可见,父类中public继承访问限定符不变,父类中protected继承访问限定符变为protected

总结:

(1)基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类内能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的

(2)public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。

(3)protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-a的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景使用的都是public继承

(4)不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)

(5)使用关键字class时默认的继承方式是private继承,使用struct时默认的继承方式是public,不过最好显示的写出继承方式

(6)在实际运用中一般使用的是public继承


四、继承与转换——赋值兼容规则——public继承

1、子类对象可以赋值给父类对象/父类的指针/父类的引用(切割、切片)


2、父类对象不可以赋值给子类对象

3、父类的指针/引用可以指向子类对象

4、子类的指针/引用不能指向父类对象(可以通过强制类型转换)

class Person
{
public:
	void Display()
	{
		cout << _name << endl;
	}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	int _num; // 学号
};
void Test()
{
	Person p;
	Student s;
	// 1.子类对象可以赋值给父类对象(切割 /切片)
	p = s;
	// 2.父类对象不能赋值给子类对象
	//s = p;
	// 3.父类的指针/引用可以指向子类对象
	Person* p1 = &s;
	Person& p2 = s;
	// 4.子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
	Student* r1 = (Student*)& p;
	Student& r2 = (Student&)p;
	// 这里会发生什么?
	r1->_num = 10;
	r2._num = 20;
}

面试题

1、实现一个不能被继承的类。

把父类的构造函数定义成private

注意:私有成员在子类中是不可见的

          子类的构造函数是合成的

class A
{
public:
	static A* GetAObj()//类外可以访问A的构造函数
	{
		return new A;
	}
private:
	A()
	{}
protected:
	int _a;
};

2、实现一个只能在栈上生成对象的类

class AA
{
public:
	static AA GetAAObj()
	{
		return AA();
	}
private:
	AA(int a = 0)
		:_a(a)
	{}
	int _a;
};

3、实现一个只能在堆上生成对象的类

class AA
{
public:
	static AA* GetAAObj()
	{
		//return *(new AA);
		return new AA;//出了作用域对象还在
	}
private:
	AA(int a = 0)
		:_a(a)
	{}
	int _a;
};

类的防拷贝:

1、只声明不定义

2、声明称private(防止别人去定义)

五、继承体系中的作用域

1、在继承体系中基类和派生类是两个不同的作用域

2、子类和父类中有同名成员(函数名相同的成员函数、相同名字的成员变量),子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中可以使用 基类::基类成员 的访问)——隐藏——重定义

3、注意在实际中的继承体系里最好不要定义同名的成员

class Person
{
public:
	Person(const char* name = "", int id = 0)
		:_name(name)
		, _id(id)
	{}
protected:
	string _name;//姓名
	int _id;//身份证号
};
class Student : public Person
{
public:
	Student(const char* name, int id, int stunum)
		:Person(name,id)
		, _stunum(stunum)
	{}
	void print()
	{
		cout << "身份证号:" << Person::_id<< endl;
		//存在同名成员的情况下,访问父类成员:s.Person::_id=10;
		cout << "学号" << _stunum << endl;
	}
protected:
	int _stunum;//学号
};

面试题

1、下列哪个说法正确(BC)

class Person
{
public:
	void f()
	{
		cout << "Person::f()" << endl;
	}
	void f1(int i)
	{
		cout << "Person::f1()" << endl;
	}
};
class Student : public Person
{
public:
	void f()
	{
		cout << "Student::f()" << endl;
	}
	void f1(int i)
	{
		cout << "Student::f1()" << endl;
	}
};
int main()
{
	Student s;
	s.f1();
	system("pause");
	return 0;
}

A、两个f1构成重载(同一作用域)

B、两个f1构成隐藏

C、代码编不过

D、以上选项都不对

2、下列哪个说法正确()

class Student : public Person
{
public:
	void f()
	{
		cout << "Student::f()" << endl;
	}
	void f1(int i)
	{
		cout << "Student::f1()" << endl;
	}
	void f2()
	{
		cout << _stunum << endl;
		cout << "Student::f2()" << endl;
	}
	void f3()
	{
		_stunum = 3;
		cout << "Student::f3()" << endl;
	}
	void f4()
	{
		this->f();
		cout << "Student::f4()" << endl;
	}
	int _stunum;
};
int main()
{
	Student *p = NULL;
	//1、p->f1() C 
	//2、p->f2() D 对空指针进行了解引用
	//3、p->f3() B 对空指针进行了解引用
	//4、p->f4() C 没有去指针指向的内容,传过去并没有解引用
	system("pause");
	return 0;
}

A、代码编不过

B、可以编过,但是程序会崩溃

C、可以编过,正常输出

D、以上选项都不对

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

子类不显示定义成员成员函数,编译器会合成留个对应的成员函数。

不显示定义,会调用父类缺省值。

子类的构造函数不是定义的而是合成的,子类最好显示调用父类的构造函数

构造函数的调用:先调用父类的,在调用子类的

析构函数的调用:先调用子类的,在调用父类的

派生类中要显示调用基类的构造函数,但不需要显示调用基类的析构函数

class Person
{
public:
	Person(int data = 0)
		:_data(data)
	{
		cout << "Person()" << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}

protected:
	int _data;
};
class Student : public Person
{
public:
	Student(int data = 0,int num = 0)
		:Person(data)//派生类中初始化父类成员:调用父类构造函数
		,_num(num)
	{
		cout << "Student()" << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}

protected://这里为什么用protected?防止之后再被继承
	int _num;
};
int main()
{
	cout << "基类的构造函数";
	Person p(2);
	cout << "派生类的构造函数";
	Student s(3, 4);
	cout << "派生类的析构函数";
	s.~Student();
	cout << "基类的析构函数";
	p.~Person();
	system("pause");
	return 0;
}


七、单继承&多继承&菱形继承

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

多继承:有两个或以上直接的父类


菱形继承


菱形继承存在二义性(不明确)和数据冗余(浪费空间)的问题

解决二义性:指定作用域

菱形继承对象模型

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;
	system("pause");
	return 0;
}


八、虚继承——解决菱形继承的二义性和数据冗余

菱形虚拟继承对象模型


解决二义性时使用的是偏移量,而不是指针的直接指向


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值