前言
继承的本质就是使代码可以被复用,被继承的类称为父类或基类,继承其他类的类称为子类或派生类。
一、使用语法
class Person
{
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :public Person
{
protected:
int _id; //学号
string _speciality; //专业
};
- Person: 父类或基类
- Student: 子类或派生类
- public: 继承方式
继承的语法很简单,就是在你写完一个类后加上冒号(:),冒号后面跟上你要继承的类名,冒号左边的类就是子类,冒号右边的类就是父类,此时在子类和父类中间的public就是继承方式。
此外,继承方式也是可以省略不写的,如下:
class Student : Person
{
protected:
int _id; //学号
string _speciality; //专业
};
对于用class关键字修饰的类来说,如果不写继承方式,默认的继承方式就为private,而在C++中类不仅可以用class修饰,也可以用struct修饰,所以如果是继承自struct修饰的类,并且不显示指定继承方式的话,那么默认的继承方式就是public。
二、继承方式
首先明确C++ 继承中的三种继承方式:
- public: 公有继承
- protected: 保护继承
- private: 私有继承
继承方式大小关系:public > protected > private
无论使用何种继承方式,父类的成员都会被继承到子类,只是根据继承方式与父类内部成员的访问限定符决定该部分成员能否在子类被访问和其在子类内部的访问限定符,举个例子:
class Person
{
public:
Person(int age = 0, string sex = "", string name = "")
:_age(age)
,_sex(sex)
,_name(name)
{
cout << "Person()" << endl;
}
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :private Person
{
public:
Student(int age = 0, string sex = "", string name = "", int id = 0, string speciality = "")
:Person(age,sex,name)
,_id(id)
,_speciality(speciality)
{
cout << "Student()" << endl;
}
void Print()
{
cout << _age << _sex << _name << endl;
}
protected:
int _id; //学号
string _speciality; //专业
};
此时的继承为private继承,因为父类中的成员的访问限定符都要大于我们的继承方式,所以父类中的成员我们在子类中都是可以使用的,而因为是private继承,继承到的成员的访问限定符都是private修饰的,所以这些继承到的成员只能在子类的内部使用,而不能在类外使用。
总的来说,一般建议继承方式都用public继承,因为如果protected、private继承下来的成员只能在子类的内部使用,扩展维护性不强,另外如果一个类要被另外的类继承,那么它的成员建议是protected、private修饰的,因为如果使用private修饰的话,就算子类继承了父类的成员,在子类内部也是无法访问父类的private成员的,这样算哪门子的继承?!
三、父类和子类的赋值转换
子类对象可以直接赋值给父类的对象、父类的指针、父类的引用,父类和子类的赋值转换有一个形象的叫法叫做切片或者切割,画图表示图下:
就是把子类和父类相同的成员给赋值过去,然而父类对象并不能直接赋值给子类对象,原因也很简单,子类有的成员父类并不完全拥有,所以不能完成赋值。
四、继承中的作用域
父类和子类有不同的作用域,如果父类和子类中有同名的元素,子类会优先使用自己内部的成员,而不访问父类的同名成员,这种情况叫做隐藏。
举个例子:
父类和子类都有_age这个同名成员,现在给它们赋不同的值,父类的_age为20,子类的_age为30,那么此时调用子类内部的Print()(打印函数)会调用那个_age呢?
class Person
{
public:
Person(int age = 0, string sex = "", string name = "")
:_age(age)
,_sex(sex)
,_name(name)
{
//cout << "Person()" << endl;
}
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :private Person
{
public:
Student(int age1 = 0, string sex = "", string name = "", int age2 = 0, string speciality = "")
:Person(age1,sex,name)
,_age(age2)
,_speciality(speciality)
{
//cout << "Student()" << endl;
}
void Print()
{
cout << _age << endl;
}
protected:
int _age; //学号
string _speciality; //专业
};
int main()
{
Person p;
Student s(20, "男", "张三", 30, "计算机");
s.Print();
return 0;
}
运行结果:
可以看到,运行的是子类本身的_age,也表明了父类和子类有相同的成员,会隐藏父类的同名成员,而只访问本身的内部成员。
另外如果是成员函数的隐藏,那么只需要满足函数名相同即可构成,举个例子:
class Person
{
public:
Person(int age = 0, string sex = "", string name = "")
:_age(age)
,_sex(sex)
,_name(name)
{
//cout << "Person()" << endl;
}
void Print()
{
cout << "我是父类的打印函数" << endl;
}
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :private Person
{
public:
Student(int age1 = 0, string sex = "", string name = "", int age2 = 0, string speciality = "")
:Person(age1,sex,name)
,_age(age2)
,_speciality(speciality)
{
//cout << "Student()" << endl;
}
void Print()
{
cout << "我是子类的打印函数" << endl;
}
protected:
int _age; //学号
string _speciality; //专业
};
int main()
{
Person p;
Student s(20, "男", "张三", 30, "计算机");
s.Print();
return 0;
}
运行结果:
总的来说关于父类和子类中有同名的成员,子类会隐藏自父类中继承的同名成员,访问遵循就近原则。
五、子类的默认成员函数
子类的默认成员和普通类的默认成员是一致的,都是由构造函数、拷贝构造函数、析构函数、赋值运算符重载、取地址、const修饰的取地址所组成的。
但是毕竟子类继承了父类的成员,那么父类的成员在子类构造和析构时自然也要进行操作,具体操作规则如下:
- 构造函数: 子类对象在进行构造时,需要先调用父类的构造函数,去构造父类的成员,然后再调用子类自己的构造函数来构造自己的成员,如果父类内部没有实现默认的构造函数,那么子类在构造时就要在初始化列表显示的调用父类的构造函数对其进行构造,如下:
class Person
{
public:
Person(int age = 0, string sex = "", string name = "")
:_age(age)
,_sex(sex)
,_name(name)
{
cout << "Person()" << endl;
}
void Print()
{
cout << "我是父类的打印函数" << endl;
}
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :private Person
{
public:
Student(int age1 = 0, string sex = "", string name = "", int age2 = 0, string speciality = "")
:Person(age1,sex,name)
,_age(age2)
,_speciality(speciality)
{
cout << "Student()" << endl;
}
void Print()
{
cout << "我是子类的打印函数" << endl;
}
protected:
int _age; //学号
string _speciality; //专业
};
int main()
{
Student s(20, "男", "张三", 30, "计算机");
return 0;
}
运行结果:
可以清楚的看到在实例化是个Student(子类)对象时,先调用了父类的构造函数,然后再调用了子类自己的构造函数,如果父类没有默认构造函数,而此时子类的构造函数的初始化列表也没有显示的调用父类的构造函数,则会出现如下的情况:
- 析构函数: 子类对象在进行析构时,执行顺序是和构造函数反过来的,即先调用子类自己的析构函数,再调用父类的析构函数,这里析构函数就不用我们自己显示的调用了,编译器会在对子类自身进行析构之后,自动的调用父类的析构函数,如下:
class Person
{
public:
Person(int age = 0, string sex = "", string name = "")
:_age(age)
,_sex(sex)
,_name(name)
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
void Print()
{
cout << "我是父类的打印函数" << endl;
}
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :private Person
{
public:
Student(int age1 = 0, string sex = "", string name = "", int age2 = 0, string speciality = "")
:_age(age2)
,_speciality(speciality)
{
cout << "Student()" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
void Print()
{
cout << "我是子类的打印函数" << endl;
}
protected:
int _age; //学号
string _speciality; //专业
};
int main()
{
Student s(20, "男", "张三", 30, "计算机");
return 0;
}
运行结果:
结果正好是和构造反着来的,其实也很好理解,如果先析构父类,那么此时子类继承的一些父类的成员进行访问时就会出错了,所以先析构子类是最好的。
- 拷贝构造和赋值运算符重载: 这两个函数也是先调用父类的拷贝构造对父类的成员进行构造,赋值也是先调用父类的赋值对父类的成员进行赋值。
六、继承与友元
友元关系是不能继承的,也就是说父类的友元,不能访问子类的protected成员和private成员。
举例:
class Person
{
friend class f_class;
public:
Person(int age = 0, string sex = "", string name = "")
:_age(age)
,_sex(sex)
,_name(name)
{
//cout << "Person()" << endl;
}
~Person()
{
//cout << "~Person()" << endl;
}
void Print()
{
cout << "我是父类的打印函数" << endl;
}
protected:
int _age; //年龄
string _sex; //性别
string _name; //姓名
};
class Student :private Person
{
public:
Student(int age1 = 0, string sex = "", string name = "", int age2 = 0, string speciality = "")
:_age(age2)
,_speciality(speciality)
{
//cout << "Student()" << endl;
}
~Student()
{
//cout << "~Student()" << endl;
}
void Print()
{
cout << "我是子类的打印函数" << endl;
}
protected:
int _age; //学号
string _speciality; //专业
};
class f_class
{
public:
void f_print()
{
Person().Print();
Student()._age;
}
};
int main()
{
//Student s(20, "男", "张三", 30, "计算机");
f_class f;
f.f_print();
return 0;
}
运行结果:
可以看到,虽然Student是继承Person这个类的,而f_class作为父类的友元类,它是不能访问Student的protected成员的。
七、继承与静态成员
父类里有一个static成员,那么无论多少个类继承了该类,整个继承体系里面都只有一份这个static成员,即继承该父类的子类都共享这一个static成员。
总结
温故而知新。