继承的概念及定义
概念(继承的本质就是复用)
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
Person p;
p.Print();
Student s1;
s1.Print();Teacher t1;
t1.Print();
定义
父类是Person类,也叫基类,子类是Student,也叫派生类
继承关系与访问限制符
继承关系和访问限制符所用的关键字都是一样的,分别是public,protected,private
不同的继承方式和不同访问限制符所继承的用法是不一样的:
总的来说,就是基类中的private成员不管用什么方式都不可见,也意味着语法上是限制使用的(子类里面和外面都不能用),而类里面的protected和public成员要看继承的方式,谁小就用谁;
因此基类的private成员咋派生类中不可见,若基类成员不想在类外直接被访问,但需要在
派生类中能访问,就定义为protected。关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承
![](https://img-blog.csdnimg.cn/direct/e5925819200e40f59da01c017ce90194.png)
//子类对象可以赋值给父类的对象/指针/引用
Person p1 = s1;
Person* pp1 = &s1;//中间没有生成临时对象,因为临时对象具有常性,要用const修饰,所以是切割
Person& rp = s1;//可以把父类的指针强制类型转换为子类的指针
Person* pp = &s1;
Student* s = (Student*) pp;
隐藏/重定义
子类的默认构造函数
要显示的调用父类
1.子类的构造必须调用父类默认构造来初始化父类的成员,如果没有显示调用,那么编译器会自动调用父类的默认构造,如果没有默认构造,就要在子类的初始化列表中显示的调用父类构造
2.子类构造先调用父类的构造函数来构造父类,后构造子类
3.子类的拷贝构造必须显示的调用父类的拷贝构造,如果没有显示的调用,那么编译器会自动调用父类的默认构造来构造父类的成员,达不到拷贝构造的要求
4.子类的赋值重载必须显示的调用父类的赋值重载。
编译器自动调用父类
5.子类的析构函数会自动调用父类的析构来保证先子后父的顺序,子类可以访问父类成员,而父类不会访问子类成员,因此要先析构子类,后析构父类。
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= "zhangsan", int num=0)
:Person(name)
,_num(num)
{
cout << "Student(const char* name= ""zhangsan"", int num=0)" << endl;
}
Student(const Student& s)
:Person(s)
{
cout << "Student(const Student& s)" << endl;
_num = s._num;
}
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s);
//必须要指明是父类,因为构成隐藏,那么子类会优先调用子类的赋值函数,造成栈溢出
_num = s._num;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
![](https://img-blog.csdnimg.cn/direct/599ab0c43d404d6d9c331c821852d8ce.png)
继承与友元
子类不能继承父类的友元,除非父类的友元称为子类的友元。
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
void main()
{
Person p;
Student s;
Display(p, s);
}
继承与静态成员
静态成员同时属于父类和子类,在派生类中不会在拷贝一份,只继承使用权
class Person
{
public :
Person () {++ _count ;}
protected :
string _name ; // 姓名
public :
static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person
{
protected :
int _stuNum ; // 学号
};
如果要统计个数,可以直接在父类的构造中计数,因为构造子类要先调用父类的构造函数来构造父类成员。
菱形继承
![](https://img-blog.csdnimg.cn/direct/012ac6a1031742fdbb77ec0e94eb9d56.png)
一个类可以继承多个类,因此就会造成菱形继承。
菱形继承:菱形继承是多继承的一种特殊情况。
// 这样会有二义性无法明确知道访问的是哪一个Assistant a ;a . _name = "peter" ;// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a . Student::_name = "xxx" ;a . Teacher::_name = "yyy" ;
如何解决呢?虚继承
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
Assistant a ;
a._name = "peter";
}
在实际中最后不要使用多继承。