继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的用。
class Person//
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter";
int _age = 18;
};
//Student是子类也叫派生类 public为继承方式也称继承权限 Person叫做基类
class Student : public Person
{
//public继承方式,基类中public修饰的成员在子类的访问权限也是public,可以通过子类的对象在类外访问
//基类中的protected在子类可以被访问,但在类外面不行,在子类的权限是protected
//基类的private成员变量在子类不可以被访问
protected:
int _stuid;
};
//Teacher是子类也叫派生类 public为继承方式 Person叫做基类
class Teacher : public Person
{
protected:
int _jobid;
};
继承方式有三种:
public继承;
protected继承;
private继承;
继承基类成员访问方式的变化
类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员
基类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员
基类的privat成员 在派生类中不可见 在派生类中不可见 在派生类中不可见
子类以public方式继承父类
1.public继承方式下基类中public和protected成员在子类的权限一致;
2.成员想在类外被访问可以设置为public
3.成员不想类外被访问,但是子类中需要被访问,可以设置为protected
4.成员不想再类类外和子类中被直接访问,则设置为private,
子类以protected方式继承父类
1.基类中为public修饰的成员变量在子类中为protected。
2.基类中为protected修饰的成员变量在子类中的权限任然为protected;
3.基类中private修饰的成员变量在子类中不可见,及不能被直接访问。
子类以private方式继承父类
1.如果以private继承方式:父类中public修饰的成员在子类的访问权限是不能被访问的
2.private可以在子类中访问子类中访问,但是它的子类不可以直接访问
3.private继承方式:父类中protected修饰的成员在子类的访问权限是private
总结:
1.基类private成员在派生类中都是不可见的。指基类的私有成员被继承到了派生类对象中,但是语法上限制派生类对象不论在类里面还是类外面都不能去访问
2.public继承方式下基类中public和protected修饰的成员在子类中的权限没有发生变化
3.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected,因此保护成员限定符是因继承才出现的
4.如果成员不想在类外直接被访问,也不想在子类中被直接访问,可以将其设置为private。
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter";
int _age = 18;
};
class Student : Person//class默认的继承方式是私有的也就是private
{
protected:
int _stuid;
};
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
**加粗样式**protected:
string _name = "peter";
int _age = 18;
};
struct Student : Person//struct默认的继承方式是公有的的也就是public
{
protected:
int _stuid;
};
总结:
1.struct和class都定义类,struct成员的默认权限是共有的为了兼容C语言,二class的默认权限是私有的
2.模板参数列表中只能用class来定义模板参数而不能用struct来定义
3.在继承中class默认的继承方式是private而struct默认的继承方式是public
派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。(这里相当于将属于父类的那一部分赋值过去)基类对象不能赋值给派生类对象
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;
};
int main()
{
Student s;
Person p;
p= s;
//s = b;//这里就会报错基类不能切给子类
return 0;
}
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才行。
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;
};
int main()
{
Student s;
Person b;
Person* pp = &s;//子类对昂可以赋值给父类指针
//也可以将父类强制转换后再赋值
return 0;
}
使用基类的引用去引用子类的对象,但是反过来不可
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;
};
int main()
{
Student s;
Person b;
Person& pp2 = s;
return 0;
}
在继承体系中基类和派生类都有独立的作用域。我们在基类和子类中分别定义一个函数名相同参数不同,这两个函数不构成重载,因为函数需要两个函数在同一个作用域内。
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问(子类对象访问与基类同名成员变量是只能访问到子类自己的),这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。如果就想要访问,可以在成员函数强加基类的作用域限定符,也就是显示访问。
#include
using namespace std;
class Person
{
public:
protected:
string _name = "peter";
int _age = 18;
};
class Student : public Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
int _stuid;
int _age = 20;
};
int main()
{
Student s;
s.Print();
return 0;
}
这里可以看到子类和父类的都有相同的_age但是调用的却是子类中的,也就印证了隐藏。因此注意在实际中在继承体系里面最好不要定义同名的成员。
派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。子类对象在构造是分两步去构造,第一步将基类继承下来的成员初始化需要调用基类的构造函数,第二步将自己增加的成员进行初始化,而所有的初始化都是在初始化列表位置完成的,需要在子类的初始化列表中调用基类的构造函数,而当不定义基类的构造函数时,会生成默认的构造函数,子类在调用时会自动调用默认的构造函数,但定义了构造函数之后就需要我们手动调用,因为编译器只会调用无参的构造函数。
需要注意的是:
1.不能在子类的初始化列表中初始化基类里的成员对象
class Person
{
public:
int _age;
};
class Student : public Person
{
public:
Student()
:_age(10),//这里是错误的编译器会报错
_stuid(10)
{
protected:
int _stuid;
};
2.基类没有显示定义构造函数那么子类可以定义也可以不定义
3.基类定义了构造函数,但基类的构造函数是无参的或者是全缺省的构造函数此时子类的构造函数可以定义也可以不用定义
4.基类显示定义了构造函数,但基类的构造函数不是无参的或者全缺省的构造函数,此时子类必须要定义自己的构造函数,并且需要在其子类的构造函数初始化列表位置显示调用基类的构造函数,目的就是为了完成从基类中继承下来的成员的初始化。
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
派生类的operator=必须要调用基类的operator=完成基类的复制
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
派生类对象初始化先调用基类构造再调派生类构造.
派生类对象析构清理先调用派生类析构再调基类的析构,在析构子类对象时调用子类的析构函数,当执行完子类的析构函数后再子类的析构函数的最后编译器会加汇编指令去调用基类的析构函数。
在基类中的友元类和友元函数不能被子类所继承
当基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
而这里的多继承就会衍生出一个菱形继承的问题
例如我们这里设置一个基类:
class Person
{
public:
string _name = "peter";
int _age;
};
//Student是子类也叫派生类 public为继承方式 Person叫做基类
class Student : public Person
{
protected:
int _stuid;
};
//Teacher是子类也叫派生类 public为继承方式 Person叫做基类
class Teacher : public Person
{
protected:
int _jobid;
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
Assistant a;
a._name = "peter";//在编译器中这里就会报错,因为编译器不知道要
//要解决这个问题则需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "bai";
a.Teacher::_name = "lin";
return 0;
}
这里顶层的基类person中成员在下面的Student Teacher 都会被继承。如果直接进行访问就会出现访问不明确即为二义性。
这里解决菱形继承的的方式有两种
第一种就是上面代码所演示的那样,让访问明确化,但这样无法真正解决问题,人眼有代码冗余问题,和浪费空间,二义性问题依然存在,只是我们指定编译器访问而已
第二种就是采用菱形虚拟继承,这个可以从根本解决问题,让顶层基类的成员只存在一份。
菱形虚拟继承需要在继承顶层基类的子类出加上 virtual
class Person
{
public:
//protected:
/*string _name = "peter";
int _age = 18;*/
string _name = "peter";
int _age=2;
};
class Student : virtual public Person
{
protected:
int _stuid;
};
class Teacher :virtual public Person
{
protected:
int _jobid;
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
Assistant a;
//这样就不会产生二发生二义性
a._name = "bai";
a._age = 10;
return 0;
}