继承的定义
继承的本质是一种复用。规定Person类为基类,Student类为派生类 。
继承方式分为public继承,protected继承,private继承。一般使用public继承,对成员的限制++private > protected > public++.
public继承,基类的private成员只能在基类使用,protected成员只能在基类和派生类中使用,public成员能在任意地方使用。
protect继承,基类的private成员只能在基类使用,protected成员只能在基类和派生类中使用,public成员只能在基类和派生类中使用。
private继承,基类的private成员只能在基类使用,protected成员只能在基类中使用,public成员只能在基类中使用。
cpp
//基类/父类
class Person
{
public:
//身份认证
void identity()
{
cout << "void identity()" << endl;
}
void func()
{
_age++;
}
protected:
string _name = "张三";
string _address;
string _tel;
private:
int _age = 18;
};
//子类/派生类
class Student : public Person
{
public:
//学习
void study()
{
cout << "void study()" << endl;
}
//不能在派生类声明时直接在派生类中来改变基类的成员变量
//_name = "李四";
void Set_name()
{
_name = "李四";
}
protected:
int _stuid;//学号
};
//不显示写继承方式 默认为private继承
//class Teacher : Person
class Teacher : public Person
{
public:
void teach()
{
cout << "void teach()" << endl;
//派生类中无法访问基类的private成员
//age++;
}
protected:
int _work_num;//工号
};
int main()
{
Person p;
p.identity();//基类调用成员函数
Student s;
s.identity();//派生类调用基类的成员函数
Teacher t;
t.identity();
s.func();//调用基类的函数来改变基类的private变量
s.Set_name();//调用成员函数可以该改变基类中的potected变量
return 0;
}
继承类模板
在复用容器中的函数时会报错找不到标识符push_back()。
++因为lzk::stack<int> s实例化了stack<int>和vector<int>,但是没有实例化vector<int>::push_back(x)。++
这里的问题本质上时编译器对模板的两阶段查找规则
第一阶段(模板定义阶段)模板定义时自动触发(无需实例化)
编译器会检查所有不依赖模板参数 T 的名称(即非依赖名称,如直接写的 push_back)。
如果名称未在当前作用域或可见基类中声明,直接报错(即使基类模板实例化后可能有该成员)。
第二阶段(模板实例化阶段)实例化模板时触发(如 main() 中使用)
检查所有依赖 T 的名称(如 vector<T>::push_back)。
此时基类模板(如 vector<int>)已实例化,可以确认成员是否存在。
cpp
//基类为类模板
namespace lzk
{
template<class T>
class stack : public vector<T>
{
public:
void push(const T& x)
{
//报错找不到标识符
//push_back();
//lzk::stack<int> s;时实例化了stack<int> 和vector<int>
//但是没有实例化vector<int>::push_back(x)
//这里的问题本质上时编译器对模板的两阶段查找规则
/*第一阶段(模板定义阶段)
编译器会检查所有不依赖模板参数 T 的名称(即非依赖名称,如直接写的 push_back)。
如果名称未在当前作用域或可见基类中声明,直接报错(即使基类模板实例化后可能有该成员)。
这就是你遇到的 push_back 报错的根本原因。
第二阶段(模板实例化阶段)
检查所有依赖 T 的名称(如 vector<T>::push_back)。
此时基类模板(如 vector<int>)已实例化,可以确认成员是否存在。*/
//阶段1:模板定义时自动触发(无需实例化)。
//阶段2:实例化模板时触发(如 main() 中使用)。
vector<int>::push_back(x);
}
void pop()
{
vector<int>::pop_back();
}
const T& top()
{
return vector<int>::back();
}
bool empty()
{
return vector<int>::empty();
}
};
}
int main()
{
lzk::stack<int> s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
s.push_back(4);
while (!s.empty())
{
cout << s.top() << endl;
s.pop();
}
return 0;
}
派生类和基类之前的转换
++public继承的派生类对象可以赋值给基类的对象和指针和引用,但是基类对象不能赋值给派生类对象。++
cpp
//派生类对象和基类对象的转换
class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student : public Person
{
public:
int _num;
};
int main()
{
Student s;
//派生类对象可以赋值给基类的指针引用
Person* p = &s;
Person& rp = s;
//派生类对象赋值给基类对象,通过基类的拷贝构造完成
Person pojb = s;
return 0;
}
隐藏
我们知道不同的类,有不同的类域,他们是相互独立的,++如果基类和派生类中存在同名成员变量或同名成员函数,派生类成员将屏蔽基类对同名成员的直接访问++,即隐藏。
cpp
class Person
{
protected:
string _name = "李四";
string _sex = "男";
int _age = 18;
public:
void func()
{
cout << "func()" << endl;
}
};
class Student : public Person
{
public:
void print()
{
//将基类中的_age隐藏了
cout << _age << endl;
}
int _num = 22;
int _age = 19;
void func(int i)
{
cout << "void func(int i)" << endl;
}
};
int main()
{
Student s;
s.print();
//通过派生类对象调用基类隐藏函数,需要指定类域
s.func(1);
s.Person::func();
return 0;
}
派生类的默认成员函数
构造函数的调用顺序是,++先调用基类的构造函数,再调用派生类的构造函数;先析构派生类对象,再析构基类对象++。
如果显示调用基类的析构,有两个问题
1.在编译过后,编译器会将基类和派生类的析构函数名称改为destructor,那么基类和派生类的析构函数就会构成隐藏关系,则需要指定类域调用
2.调用过后发现基类析构了两次,编译器为了析构的顺序是先派生类再基类,会在调用派生类的析构后再调用基类析构,如果有动态资源就会报错。
cpp
//派生类的默认成员函数
//基类
class Person
{
public:
Person(const string& name = "张三")
:_name(name)
{
cout << "Person(const string& name = 张三)" << 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;
_name = "";
}
protected:
string _name;
};
//派生类
class Student : public Person
{
public:
Student(const string& n, int age, const string& name)
:Person(name)
, _n(n)
, _age(age)
{
cout << "Student(const string& n, int age, const string& name)" << endl;
}
Student(const Student& s)
:Person(s)//显示调用基类的拷贝构造
,_n(s._n)
,_age(s._age)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
_n = s._n;
_age = s._age;
Person::operator=(s);//显示调用基类的赋值重载
}
return *this;
}
~Student()
{
//如果显示调用基类的析构,有两个问题
//1.
//在编译过后,编译器会将基类和派生类的析构函数名称改为destructor(涉及到多态)
//那么基类和派生类的析构函数就会构成隐藏关系
//则需要指定类域调用
//2.
//调用过后发现基类析构了两次
//编译器为了析构的顺序是先派生类再基类
//会在调用派生类的析构后再调用基类析构
//如果有动态资源就会报错
//Person::~Person();
cout << "~student()" << endl;
}
private:
string _n = "李四";
int _age = 19;
};
int main()
{
Student s("李好", 19,"张斌");
Student s1(s);
//Student s3("李一", 18,"李二" );
//s1 = s3;
return 0;
}
不能被继承的类
实现一个不能被继承的类有两种方法
一是将基类的构造函数列为私有成员。
二是在基类的名字后面加final关键字。
cpp
//实现一个不能被继承的类
//c++11
class Base final//加关键字
{
public:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
private:
//c++98
//将构造函数设为私有
//Base()
//{};
};
//报错
//class Student : public Base
继承中的友元和静态成员
++友元关系不能被继承++,基类的友元函数不能访问派生类的成员变量,除非给派生类也声明友元。
在基类中定义了静态成员,则在++派生类中使用的也是这个成员++,不会再开辟新的空间。
cpp
//继承和友元
//前置声明
class Student;
class Person
{
public:
friend void print(const Person& p, const Student& s);
protected:
int _age = 18;
};
class Student : public Person
{
protected:
string _name = "李思思";
friend void print(const Person& p, const Student& s);
};
void print(const Person& p, const Student& s)
{
cout << p._age << endl;
cout << s._name << endl;
}
int main()
{
Student s;
Person p;
print(p,s);
return 0;
}
//静态成员
class Person
{
public:
string _name;
static int _count;
};
//静态成员在类外定义
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum;
};
int main()
{
Person p;
Student s;
//基类和派生类公用一个静态变量
cout << &p._count << endl;
cout << &s._count << endl;
//基类和派生类非静态成员的地址是不一样的
cout << &p._name << endl;
cout << &s._name << endl;
//通过指定类域可以访问静态成员
cout << &Person::_count << endl;
cout << &Student::_count << endl;
return 0;
}
继承模型
单继承:一个派生类继承一个基类。
多继承:一个派生类继承多个基类。
菱形继承:诸如两个派生类继承同一个基类后,再被同一个派生类继承的情况。
菱形继承会产生数据冗余和二义性的问题,为了解决这个问题就有了虚继承,虚继承就是在继承方式前面加上virtual关键字。那么派生类对象就可以访问基类公开成员了。
cpp
//单继承 多继承 菱形继承
class Person
{
public:
string _name = "李思思";
};
class Student : public Person
{
public:
int _nun;
};
class Teacher : public Person
{
public:
int _worknum;
};
class Assistant : public Student, public Teacher
{
protected:
string _course; // 主修课程
};
int main()
{
//对_name的访问不明确
//student类和teacher类中都有_name
Assistant a;
//a._name = "lisisi";
//指定类域可以访问,解决二义性问题,但是存在数据冗余
a.Student::_name = "lisisi";
a.Teacher::_name = "lss";
}
//虚继承
//解决数据冗余和二义性
class Person
{
public:
string _name = "李思思";
};
//加关键字virtaul
class Student : virtual public Person
{
public:
int _nun;
};
class Teacher : virtual public Person
{
public:
int _worknum;
};
class Assistant : public Student, public Teacher
{
protected:
string _course; // 主修课程
};
int main()
{
Assistant a;
a._name = "李思思";
return 0;
}
**任何足够先进的科技都与魔法无异,但魔法背后的真相永远是严谨的代码逻辑。愿我们既能享受创造的浪漫,也能保持对技术的敬畏之心 !**🚀