1、什么是继承
1.1、概念引入
我们在定义两个类student和teacher时,学生和老师都有共同属性——姓名,年龄.....学生特有学号,教师特有教工号,但是如果我们在student类定义sting name,int age,string id三个成员变量;给teacher类定义也sting name,int age,string tid三个成员变量。我们可以看到,这两个类的成员变量有两个重合的——name和age。
class student
{
private:
string _name = "zhangsan";
int _age = 0;
string _id = "1";
};
class teacher
{
private:
string _name = "zhangsan";
int _age = 0;
string _tid = "1";
};
这只是两个类,如果后面再引入校长、班长、教导主任等等,那我们重合的地方会很多很多。C++这门语言,非常忌讳重复的代码,所以这个时候,我们引入继承这个概念。我们可以封装一个类Person,student和teacher归根结底也是一个人,我们在Person类里面把他们共有的成员放进去——name,age;然后我们在定义student和teacher类时,直接继承Person类的成员,再在student,teacher类里面,定义自己特有的成员就可以了。
1.2、继承的概念
1.3、继承的用法
下面的代码就是我们定义一个Person类,把重复的属性都放在Person类里面,并在student和teacher类里面定义自己特有的成员变量。
class Person
{
protected:
string _name = "zhangsan";
int _age = 0;
};
class student:public Person
{
private:
string _id = "1";
};
class teacher :public Person
{
private:
string _tid = "1";
};
我们把Person类叫基类,俗称父类;把student和teacher这两个类都叫派生类,俗称子类。我们看到Person里面堆成员变量的访问限定词跟以前的不一样:protected。我们知道,如果访问限定词是public,代表在类里面外面都可以访问这个成员,如果是private代表只能在类里面对该成员访问,类外面不可以。而我们用继承地目的就是为了在派生类里面用基类的成员,所以如果我们访问限定符用private的话岂不是在派生类里面不能访问了吗,但如果用public的话,类外面也可以随意访问,如果在类外面不小心把数据给修改了呢,所以为了安全起见,这里再引入一个访问限定词:protected,它表示该成员只能在类里面和子类使用。
我们看到这里:
class student:public Person
student类和teacher类后面都有:public Person
public代表继承方式,后面跟的内容表示继承的哪个类。
所以,类的继承可以这么表示
1.4、继承关系和访问限定符
类成员
/
继承方式
|
public
继承
|
protected
继承
|
private
继承
|
基类的
public
成员
|
派生类的public
成员
|
派生类的
protected成员
|
派生类的
private成员
|
基类的
protected
成员
|
派生类的
protected成员
|
派生类的
protected成员
|
派生类的
private成员
|
基类的
private
成
员
|
在派生类中不可见
|
在派生类中不可见
| 在派生类中不可见 |
2、基类和派生类对象复制转换
![](https://i-blog.csdnimg.cn/blog_migrate/9b2d40839848df51f9c0f4b4dc4c947f.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/effdaae349534f0c97b49c42d16a19de.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/7a95cff9fc13061845e663ca951f9d73.jpeg)
class Person
{
protected:
string _name; // 姓名
int _age; // 年龄
};
class Student : public Person
{
public:
int _id; // 学号
};
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
//sobj = pobj; err
}
3、继承中的作用域
class Person
{
public:
void fun()
{
cout << _name << endl;
}
protected:
string _name = "zhangsan";
int _age = 0;
};
class student:public Person
{
public:
void fun()
{
cout << _id << endl;
}
private:
string _id = "1";
};
int main()
{
student s;
s.fun();//基类派生类都有fun(),子类会屏蔽掉父类的同名函数;
s.Person::fun();//显示调用父类fun();
return 0;
}
class Person
{
public:
void fun()
{
cout << _name << endl;
}
public://为了演示隐藏,方便在类外面调用同名成员变量,这里用public
string _name = "zhangsan";
int _age = 0;
int _num = 11111;//身份证号
};
class student:public Person
{
public:
void fun()
{
cout << _id << endl;
}
public://为了演示隐藏,方便在类外面调用同名成员变量,这里用public
string _id = "1";
string _num = "222222222222";//学籍号
};
void test1()
{
student s;
cout << s._num << endl;
cout << s.Person::_num << endl;
}
int main()
{
test1();
return 0;
}
4、派生类的默认成员函数
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 num)
: Person(name)//显示调用基类构造
, _num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
: Person(s)//切片,显示调用父类拷贝构造
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
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; //学号
};
void test()
{
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 17);
s1 = s3;
}
int main()
{
test();
return 0;
}
5、静态成员
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; // 研究科目
};
void TestPerson()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout << " 人数 :" << Person::_count << endl;
Student::_count = 0;
cout << " 人数 :" << Person::_count << endl;
}
int main()
{
TestPerson();
return 0;
}
6、多继承
如果一个类,继承多个类,这种场景就叫多继承。
class Student
{
protected:
int _num; //学号
};
class Teacher
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
这个助教Assistant同时继承student和teacher,即同时可以使用student和teacher两个类的成员。
但是多继承可能引发一个非常严重的后果——菱形继承
上图所表达的就是菱形继承。菱形继承会导致一个现象,B继承A,拥有A的成员,C继承A,拥有A的成员,D继承B,C拥有B,C的成员,则,D会有两份A的成员,这会导致数据冗余和二异性。
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";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
int main()
{
Test();
return 0;
}
virtual虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和 Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
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";
}
虽然我们用虚继承解决了菱形继承的问题,但是日常我们如果要用多继承的话,尽量避免菱形继承,菱形继承底层结构复杂,没有必要去给自己找麻烦。
最后补充一点,友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。