C++ 继承

目录

模版收尾

继承

赋值兼容转换

 继承的作用域

同名变量

同名成员函数

派生类的成员构造函数

不写构造函数

写构造函数

不写拷贝构造函数

手动写拷贝构造

运算符重载

析构函数


模版收尾

模版的声明和定义不能分离,否则会报错.

写下面三个文件:

Stack.h

#pragma once
#include
using namespace std;
template 
T Add(const T& left, const T& right);



Stack.cpp

#include"Stack.h"
template 
T Add(const T& left, const T& right)
{
	cout << 1 << endl;
	return left + right;

}
Test.cpp



#include"Stack.h"
int main()
{
	Add(1,2);
	fun();
	return 0;
}

运行结果:

C++ 继承_第1张图片

 如上图所示,模版的声明和定义分离会出现链接错误。

如果我们正常函数的定义和分离就不会出错,比如下图fun函数:
C++ 继承_第2张图片

这是刚开始我们把代码加载进内存,运行之后先执行预处理,预处理把"Stack.h"展开。

展开之后就会在Stack.cpp里面找,但是Stack.cpp里面并没有对模版进行实例化,也就找不到地址:

C++ 继承_第3张图片

为什么Add预处理阶段找不到地址呢?这是因为在Stack.cpp也就是定义里面Add没有被实例化:C++ 继承_第4张图片

Add是模版,没有实例化,在预编译阶段找不到地址,但是fun我们一开始就定义了是void类型,所以不管是预处理,编译,链接阶段都可以找到它的地址。

解决方案:

1.显示实例化:

C++ 继承_第5张图片缺点:换类型之后还要重新显示实例化,假设我此时这样写:

Add(1.0, 2.0);

 就会报错:

1>Test.obj : error LNK2019: 无法解析的外部符号 "double __cdecl Add(double const &,double const &)" (??$Add@N@@YANABN0@Z),函数 _main 中引用了该符号

 需要再加个类型显示实例化:

template
double Add(const double& left, const double& right);

运行:

1
1

但是这样太麻烦了,干脆不要声明定义分离了,把Stack.cpp删掉,定义全部放到Stack.h里面:

C++ 继承_第6张图片

继承

继承就是一种复用:

C++ 继承_第7张图片

 继承格式:C++ 继承_第8张图片

 C++ 继承_第9张图片C++ 继承_第10张图片

举例:



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:

C++ 继承_第11张图片

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

Student a;
Person&  b = a;

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

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

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

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

原因:

C++ 继承_第12张图片

 继承的作用域

同名变量

如下代码,父类和子类都有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

同名成员函数

如果父子类有两个同名成员函数,选哪个:C++ 继承_第13张图片


// 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)" <

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

改成如下:


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)" <

不写拷贝构造函数

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

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

手动写拷贝构造

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

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

C++ 继承_第14张图片

运算符重载


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

	}

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

C++ 继承_第15张图片

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

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

	}

C++ 继承_第16张图片

析构函数

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


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

C++ 继承_第17张图片

 演示:

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

C++ 继承_第18张图片

静态变量的继承

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

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

菱形继承

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._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++ 继承_第19张图片

 解析:
C++ 继承_第20张图片

所以选C

你可能感兴趣的:(c++,java,开发语言)