C++ 继承

目录

继承

赋值兼容转换

 继承的作用域

同名变量

同名成员函数

派生类的成员构造函数

不写构造函数

写构造函数

不写拷贝构造函数

手动写拷贝构造

运算符重载

析构函数


继承

继承就是一种复用:

 继承格式:

 

举例:



class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18;
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
//Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
	//以看到变量的复用。调用Print可以看到成员函数的复用。
class Student : public Person
{
protected:
	int _stuid; // 学号
};
class Teacher : public Person
{
protected:
	int _jobid; // 工号
};
int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}
name:peter
age:18
name:peter
age:18

如上图,teach类和student类都继承了person类的成员函数和成员变量

如果继承方式不写,class默认是私有,struct默认是公有:



class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18;
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
//Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
	//以看到变量的复用。调用Print可以看到成员函数的复用。
class Student :  Person
{
protected:
	int _stuid; // 学号
};
class Teacher : Person
{
protected:
	int _jobid; // 工号
};
int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

赋值兼容转换

不同类型的相近对象之间赋值的时候会进行隐式类型转化,隐式类型转换会产生临时变量。

如:

double d = 2.2;
	
	int& r = d;
	

 报错原因:

int 和double是相近类型,因为都表示大小,把double d赋值给int i,那么r对int d取地址实际上不是对d取地址,而是对临时变量取地址。

解决方法:

临时变量具有常性,加个const就行了:

const int& r = d;

那子类可不可以给父类呢?


class Person{};
class Student   : public Person{};
int main()
{
	Student a;
	Person  b = a;
	return 0;
}

没有报错可以的,因为父子之间是一种很亲近的关系。

在public继承下,父子之间是一种 is a关系:

student is a person ,teacher is a person:

那既然是很亲近的关系,会进行隐式类型转换,那么肯定也会产生临时对象:

Student a;
Person&  b = a;

 没加const没有报错。难道自定义类型不会隐式类型转换吗?

下面代码可证明自定义类型也可以隐式类型转换:

string c = "xxxx";
string& d = "xxxx";

string c = "xxxx";
const string& d = "xxxx";

原因:

 继承的作用域

同名变量

如下代码,父类和子类都有number变量,此时访问子类,打印出来的是子类的number:

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111;//身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << _num << endl;

	}
protected:
	int _num = 999; // 学号
};
void Test()
{
	Student s1;
	s1.Print();
};
int main()
{
	Test();
	return 0;
}

 姓名:小李子
 身份证号:999

 如果想访问父类的number,需要突破作用域:

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111;//身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl;
	
	}
protected:
	int _num = 999; // 学号
};
void Test()
{
	Student s1;
	s1.Print();
};
int main()
{
	Test();
	return 0;
}

 姓名:小李子
 身份证号:111

同名成员函数

如果父子类有两个同名成员函数,选哪个:


// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
	B b;
	b.fun(10);
};
int main()
{
	Test();
	
	return 0;
}

答案:B
//会调用子类同名成员函数,隐藏父类同名成员函数
运行结果:func(int i)->10

如果我现在不带参:

void Test()
{
	B b;
	b.fun();
};

 假设我就是不想带参,那就调父类嘛,突破一下作用域:

void Test()
{
	B b;
	b.A::fun();
};
运行结果:func()

派生类的成员构造函数

不写构造函数

自定义类型,如果我们不写,编译器会自动生成构造函数

派生类如果不写会调用父类的构造函数:

class Person
{
public:

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

protected:
	string _name; // 姓名
};


class Student : public Person
{
public:

protected:
	int _num; //学号
};


int main()
{
Student s1;

	return 0;
}

写构造函数


class Person
{
public:
	Person(const char* name = "peter")
		: _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 age)
		:_name()
	    ,_age(age)
	{
		cout << "student const(student& s)" <<endl;
	}

protected:
	int _age; //学号
};
void Test()
{
	Student s1("张三",18);

}
int main()
{
	Test();	
	return 0;
}


这是因为派生类初始化父类成员变量必须把父类当成一个对象,而不是直接去初始化父类的成员变量,如果派生类不写构造函数会默认调用父类构造函数,如果派生类想写构造函数必须显示调用父类构造函数。

改成如下:


class Person
{
public:
	Person(const char* name = "peter")
		: _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 age)
		:Person(name)
	    ,_age(age)
	{
		cout << "student const(student& s)" <<endl;
	}

protected:
	int _age; //学号
};
void Test()
{
	Student s1("张三",18);

}
int main()
{
	Test();	
	return 0;
}


不写拷贝构造函数

子类拷贝默认调用父类的拷贝构造:

	Student s1("张三",18);
	Student s2(s1);

手动写拷贝构造

派生类写拷贝构造要先显示调用父类拷贝构造

Student(const Student& s)
	
		:Person(s)//切片,把父类的成员变量切出来去给父类拷贝构造
		,_age(s._age)
		{
		cout << "Student (const student&)" << endl;
		}

运算符重载


	Student& operator=(const Student& s)
	{
		if (&s != this)
		{
			 operator=(s);
			_age = s._age;
		}
		cout << "student operator =(const student& s)" << endl;
		return *this;

	}

会栈溢出,一值调子类的operator:

 这是因为子类隐藏了父类,要想调父类的必须要显示调用:

Student& operator=(const Student& s)
	{
		if (&s != this)
		{
			 Person::operator=(s);
			_age = s._age;
		}
		cout << "student operator =(const student& s)" << endl;
		return *this;

	}

析构函数

子类调用析构不用显示调用父类析构,系统会自动按照先子后父调用析构


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

 演示:

在快结束时按fn+f11会调用析构,如下图显示:程序结束时先调用子类析构,再调用父类析构:

静态变量的继承

如图,子类继承父类的静态变量,我们把地址打印出来看看,发现是同一个地址,也就是并没有继承,而是直接拿来用了。

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
class Graduate : public Student
	
{
protected:
string _seminarCourse; // 研究科目
};
int main()
{

	cout << " 人数 :" << &Person::_count << endl;

	cout << " 人数 :" << &Person::_count << endl;
	return 0;
}

菱形继承

如下图,子类和父类中都有_name成员,我们现在访问_name成员会有二义性,编译器不知道我们要访问父类还是子类的_name。

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
	
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
int main()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";
	
	return 0;
}

 解决方法

1:指定:

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
	/*
	虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
	Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
	方去使用。
	虚拟继承解决数据冗余和二义性的原理
	为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
	员的模型。*/
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
int main()
{
	
	Assistant a;
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
	return 0;
}

2.虚继承

class Student :virtual  public Person
class Teacher : virtual public Person

class Assistant : virtual public Student, public Teacher

题目

 解析:

所以选C

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙鹏宇.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值