🌈个人主页:羽晨同学
💫个人格言:"成为自己未来的主人~"
继承的概念和定义
继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行拓展,增加功能,这样可以产生新的类,叫做派生类,继承呈现了面向对象的程序设计的层次结构,继承是类设计层次的复用。
cpp
class Person
{
public:
Person()
{}
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
cout<<_tel<<endl;
}
string _name = "peter";//姓名
protected:
int _age = 18;
//父类定义本质,不想被子类继承
private:
int _tel = 110;
};
//继承的父类的成员
class Student :public Person
{
public:
void Func()
{
//子类用不了(不可见)
//cout << _tel << endl;
cout << _name << endl;
cout << _age << endl;
}
protected:
int _stuid;//学号
};
class Teacher :public Person
{
protected:
int _jobid;//工号
};
在这个代码当中,我们简单的使用继承创建了两个对象。
所以,我们看到的是Person是父类,也被叫做基类,Student是子类,也称作派生类。
继承管理和访问限定符
继承基类成员访问方式的变化。
在这其中,如果我们使用public继承和类成员是public成员或者protected成员,是可以直接使用成员,其他的就不可以,例如:
cpp
class Student :public Person
{
public:
void Func()
{
//子类用不了(不可见)
cout << _tel << endl;
cout << _name << endl;
cout << _age << endl;
}
protected:
int _stuid;//学号
};
在这里,我们的_tel在Person中时private修饰的,所以派生类中不能进行调用。
但是,父类的私有成员变量是可以父类的成员函数来间接调用的,例如:
cpp
class Person
{
public:
Person()
{}
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
cout<<_tel<<endl;
}
string _name = "peter";//姓名
protected:
int _age = 18;
//父类定义本质,不想被子类继承
private:
int _tel = 110;
};
在实际使用中一般使用的都是public继承,几乎很少使用protected/private继承
派生类和基类对象的赋值转换
我们假如让p=s,这个是可以实现的,但这个实现的逻辑并不是类型转换,而是一种特殊的语法规则,不会产生任何的中间变量,我们可以看下面的代码实现逻辑。
cpp
int main()
{
Student s;
Person p;
//跟下面机制不一样
//特殊语法规则:不是类型转换,中间没有产生临时变量
p = s;
Person* ptr = &s;
Person& ref = s;
ptr->_name += 'x';
ref._name += 'y';
s._name += 'z';
return 0;
}
- 基类对象不能赋值给派生类对象。
- 派生类对象可以赋值给基类的对象/基类的指针/基类的引用,这里我们可以叫做切片或者切割。
那类型转换下的情况是什么吗?
这个就是我们经常说到的,截断和提升。
继承中的作用域
基类和派生类都有独立的作用域。
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫做隐藏,也叫做重定义(在子类成员函数中,可以使用基类:基类成员显示访问)
如果是成员函数之间的隐藏,只要函数名相同就能构成隐藏。
在实际中,最好不要在继承体系里面定义同名的成员。
cpp
#include<iostream>
using namespace std;
//student的_num和Prson的_num构成隐藏关系,可以看出代码虽然可以运行,但是非常容易混淆
class Person
{
protected:
string _name = "小李子";
int _num = 111;
};
class Student :public Person
{
public:
void Print()
{
cout << "姓名" << _name << endl;
cout << "学号" << _num << endl;//隐藏,重定义
cout << "学号" << Person::_num << endl;
}
protected:
int _num = 999;
};
int main()
{
Person p;
Student s;
s.Print();
return 0;
}
你看,这里面就涉及到了隐藏和重定义。
派生类的默认成员函数
6个默认成员函数
- 初始化和清理
- 构造函数主要完成初始化工作
- 析构函数主要完成清理工作
- 拷贝复制
- 拷贝构造是使用同类对象初始化创建对象
- 赋值重载主要是把一个对象赋值给另一个对象
- 取地址重载
- 主要是普通函数和const对象取地址,这两个很少会自己实现
在子类中,父类的那部分调用父类的什么函数,这个就叫做复用。
构造:
构造的时候,我们要先父后子,因为子类中关于父类成员的部分会用到父类的构造,
析构:
析构的时候,我们要先子后父,因为子类的析构可能会用到父类的析构函数。
cpp
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;
delete[] _str;
}
protected:
string _name;
char* _str = new char[10] {'x', 'y', 'z'};
};
class Student :public Person
{
public:
//父类构造显示调用,可以保证先父后子
Student(const char*name="",int x=0,const char*address="")
:_x(x)
,_address(address)
,_name(Person::_name+'x')
,Person(name)
{}
Student(const Student&st)
:Person(st)
,_x(st._x)
,_address(st._address)
{}
Student& operator=(const Student& st)
{
if (this != &st)
{
Person::operator=(st);
_x = st._x;
_address = st._address;
}
return *this;
}
//由于多态,析构函数的名字会被统一处理成destructor()
//父类析构不能显示调用,因为显示调用不能保证先子后父
~Student()
{
//析构函数会构成隐藏,所以这里要指定类域
cout << "Student()" << endl;
cout << _str << endl;
}
protected:
int _x = 1;
string _address = "sdasa";
string _name;
};
好了,本次的文章就到这里了,我们下次再见。