注意:保护和私有在类中没有区别,但是在继承中有区别,private在继承的子类不可见,protect在继承的子类可见
记忆方法:先看基类的修饰符是private,那都是不可见的。如果不是,那就用继承的修饰和基类的修饰比较谁小取谁----->public>protect>private
cpp
#include <iostream>
using namespace std;
class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student :public Person
{
public:
int _No;
};
int main()
{
Person p;
Student s;
//子类和父类之间赋值兼容规则
//1、子类对象可以赋值父类对象/指针/引用
p = s;
Person* p1 = &s;
Person& p1 = s;
return 0;
}
注意:父类不可以赋值给子类,有一种情况就是父类指向子类,再赋值给另一个子类
不同作用域(隐藏(重定义))
cpp
class Person
{
protected:
string _sex;
int _age=111;
};
class Student :public Person
{
public:
void f()
{
cout << _age << endl;
}
public:
int _age=99;
};
int main()
{
Student s;
s.f();
}
面向对象的三大特性
封装(类的封装)、继承、多态
实际上面向对象不只三大特性
派生类的默认成员函数
注意:如果是自定义类型就会调用自己的默认构造函数(struct class)
如何在子类中初始化父类部分呢?
cppclass Person { public: Person(const char* name) :_name(name) { cout << "Person()" << _name<< endl; } ~Person() { cout << "~Person()" << endl; } private: string _name; }; class Student : public Person { public: Student(const char* name,int age)//所以要加const这个块空间是只读 :Person(name), _stuid(age) { } protected: int _stuid; }; int main() { Student a("joker",1);//传入常量字符串 return 0; }
父类继承部分一定要调用父类去完成,如果不显示调用的话也会自动调用父类的构造函数
假设1:
默认构造函数包括无参、全缺省、系统默认生成的
如果是系统调用的默认构造函数内置类型是不会处理的,只有自定义类型才会处理
假设2:
如何完成子类拷贝构造呢?
什么是拷贝构造?
1、拷贝构造的参数只有一个,该参数的类型是对象类型的引用,为什么要引用?因为防止无限的调用拷贝构造去递归,一般用const修饰
2、要区分深拷贝和浅拷贝的问题,系统默认生成的拷贝函数的内存存储方式是字节序存储所以要注意成员变量类型的,防止释放两次同一个空间
复习完成
cpp
class Person
{
public:
Person(const char*name="")
:_name(name)
{
cout <<_name << "Person()" << endl;
}
Person(const Person& p1)
:_name(p1._name)
{
cout << "Person(const Person& p1)" << endl;
}
private:
string _name;
};
class Student :Person
{
public:
Student(const char*name,int id)
:Person(name),//要以父类名()的方式调用父类构造函数初始化子类中的父类部分
_stuid(id)
{
cout << "student()" << endl;
}
Student(const Student& s1)
:Person(s1)//要以父类名()的方式调用父类的拷贝构造函数 复制子类中的父类部分
{
_stuid = s1._stuid;
cout << "Student(const Student& s1)" << endl;
}
private:
int _stuid;
};
int main()
{
//Person p1("aaa");
Student s1("bbb",9);//初始化
Student s2(s1);//拷贝构造
//Student s3 ("gg",30);//初始化
//s1 = s3;//s1.operator=(s3)
return 0;
}
注意:只有显示的用父类(子类对象)的显示调用方式才可以调用父类的拷贝构造,系统不会自动调用父类的拷贝构造这个和初始化构造不一样
子类的operator=:
cpp
class Person
{
public:
//初始化
Person(const char*name="")
:_name(name)
{
cout <<_name << "Person()" << endl;
}
//拷贝
Person(const Person& p1)
:_name(p1._name)
{
cout << "Person(const Person& p1)" << endl;
}
//父类的operator=函数
Person& operator=(const Person& s1)
{
if (this != &s1)
{
_name = s1._name;
cout << "Person& operator=(const Person& s1)" << endl;
}
return *this;
}
private:
string _name;//string(const char*s1)
};
class Student :Person
{
public:
Student(const char*name,int id)
:Person(name),
_stuid(id)
{
cout << "student()" << endl;
}
Student(const Student& s1)
:Person(s1)//要以父类名()的方式调用父类的拷贝初始化子类中的父类部分
{
_stuid = s1._stuid;
cout << "Student(const Student& s1)" << endl;
}
//operator=
Student& operator=(const Student& s1)
{
if (this != &s1)//防止赋值时,两个对象时一个对象,浪费时间
{
Person::operator=(s1);//显示调用父类的operator=,将父类的部分赋值
_stuid = s1._stuid;
cout << "Student& operator=(const Student& s1)" << endl;
}
return *this;//返回子类的对象
}
private:
int _stuid;
};
int main()
{
//Person p1("aaa");
Student s1("bbb",9);//初始化
Student s2(s1);//拷贝构造
//Student s3 ("gg",30);//初始化
//s1 = s3;//s1.operator=(s3)
return 0;
}
子类的析构函数
注意:
1、子类的初始化时先父后子,所以析构就是先子后父,类似于stack
2、虽然父类的析构和子类的析构名字不相同,但是子类和父类的析构会被统一的处理成destructor构成隐藏
3、系统已经实现好当子类析构完成了,就会自动调用父类的析构
面试题
如何构建一个不可以继承的类
cpp
#include <iostream>
using namespace std;
class A
{
private: //将父类的初始化私有化
A()
{
}
}
class B:public A
{
}
int main()
{
B b;//初始化不了对象
return 0;
}
友元关系不能被继承(了解就好)
会报错,要是想用只可以手动添加
静态成员在父类中被继承会发生什么?
cpp
class Person
{
public:
Person()
{
_count++;
}
public:
string _name;
static int _count;
};
int Person::_count = 0;//静态成员必须初始化
class Student:public Person
{
public:
private:
int id;
};
int main()
{
Person p1;
Student s1;
p1._name = "jack";
s1._name = "rose";
p1._count = 1;
s1._count = 2;
cout << Person::_count << endl;//静态成员属于对象也属于类所以可以Person::_count
return 0;
}
static 成员属于对象也是属于类的,子类继承下来的静态成员和父类是一样的它们调用同一个静态成员变量
多继承
什么叫单继承:只有一个父类
所以多继承:就是有多个父类
多继承会造成很多问题的,所以后来的语言是没有多继承的,多继承是一个大坑
为什么是大坑?
因为多继承的一种特殊的继承菱形继承,会造成数据冗余和二义性
cpp
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; // 主修课程
};
void Test()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";//菱形继承会导致二义性不知道是那个的_name
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
菱形继承:
菱形继承原理
experiment:
cpp
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;
return 0;
}
进入调试看内存
这是内存模型,存在数据冗余和二义性
但是我们要看虚继承的内存模型
cpp
using namespace std;
class A
{
public:
int _a;
};
class B:virtual public A//添加了virtual
{
public:
int _b;
};
class C :virtual 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;
return 0;
}
进入调试内存
接下来看看这些是什么?
好像是地址
再次进入内存
存在偏移量,存放偏移量的地方又叫虚基表
为什么要设计出来呢?
因为菱形继承会出现数据冗余和二义性,所以virtual继承是为了解决二义性和数据冗余
菱形继承:
虚继承:
为什么虚继承会比菱形继承还大呢?
是因为多了两个指针
我相信很多同学会问不是解决数据冗余吗?为什么越来越大了
因为基类太小了
菱形继承:
虚继承:
少了一倍
这就是虚继承的基本概念