继承是面向对象复用的重要手段。通过继承定义一个类,继承是类型之间的关系建模,共享公有的东西,实现各自本质不同的东西。
一、继承定义的格式
二、继承关系&访问限定符
实现一个简单的继承关系
继承是一种复用手段,在继承关系里基类的成员类的成员派生类的成员,由此达到复用的目的。
三、三种继承关系下基类成员的在派生类的访问关系变化
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概括 |
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性都不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都成为子类的私有成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都成为子类的私有成员 |
不可见的含义:在子类中存在,但不可以访问(在派生类(子类)中,类内、类外都不可以访问)
父类中private在子类中都不可见,父类中public继承访问限定符不变,父类中protected继承访问限定符变为protected
总结:
(1)基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类内能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
(2)public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
(3)protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-a的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景使用的都是public继承
(4)不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)
(5)使用关键字class时默认的继承方式是private继承,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
(6)在实际运用中一般使用的是public继承
四、继承与转换——赋值兼容规则——public继承
1、子类对象可以赋值给父类对象/父类的指针/父类的引用(切割、切片)
2、父类对象不可以赋值给子类对象
3、父类的指针/引用可以指向子类对象
4、子类的指针/引用不能指向父类对象(可以通过强制类型转换)
class Person
{
public:
void Display()
{
cout << _name << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
int _num; // 学号
};
void Test()
{
Person p;
Student s;
// 1.子类对象可以赋值给父类对象(切割 /切片)
p = s;
// 2.父类对象不能赋值给子类对象
//s = p;
// 3.父类的指针/引用可以指向子类对象
Person* p1 = &s;
Person& p2 = s;
// 4.子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
Student* r1 = (Student*)& p;
Student& r2 = (Student&)p;
// 这里会发生什么?
r1->_num = 10;
r2._num = 20;
}
面试题
1、实现一个不能被继承的类。
把父类的构造函数定义成private
注意:私有成员在子类中是不可见的
子类的构造函数是合成的
class A
{
public:
static A* GetAObj()//类外可以访问A的构造函数
{
return new A;
}
private:
A()
{}
protected:
int _a;
};
2、实现一个只能在栈上生成对象的类
class AA
{
public:
static AA GetAAObj()
{
return AA();
}
private:
AA(int a = 0)
:_a(a)
{}
int _a;
};
3、实现一个只能在堆上生成对象的类
class AA
{
public:
static AA* GetAAObj()
{
//return *(new AA);
return new AA;//出了作用域对象还在
}
private:
AA(int a = 0)
:_a(a)
{}
int _a;
};
类的防拷贝:
1、只声明不定义
2、声明称private(防止别人去定义)
五、继承体系中的作用域
1、在继承体系中基类和派生类是两个不同的作用域
2、子类和父类中有同名成员(函数名相同的成员函数、相同名字的成员变量),子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中可以使用 基类::基类成员 的访问)——隐藏——重定义
3、注意在实际中的继承体系里最好不要定义同名的成员
class Person
{
public:
Person(const char* name = "", int id = 0)
:_name(name)
, _id(id)
{}
protected:
string _name;//姓名
int _id;//身份证号
};
class Student : public Person
{
public:
Student(const char* name, int id, int stunum)
:Person(name,id)
, _stunum(stunum)
{}
void print()
{
cout << "身份证号:" << Person::_id<< endl;
//存在同名成员的情况下,访问父类成员:s.Person::_id=10;
cout << "学号" << _stunum << endl;
}
protected:
int _stunum;//学号
};
面试题
1、下列哪个说法正确(BC)
class Person
{
public:
void f()
{
cout << "Person::f()" << endl;
}
void f1(int i)
{
cout << "Person::f1()" << endl;
}
};
class Student : public Person
{
public:
void f()
{
cout << "Student::f()" << endl;
}
void f1(int i)
{
cout << "Student::f1()" << endl;
}
};
int main()
{
Student s;
s.f1();
system("pause");
return 0;
}
A、两个f1构成重载(同一作用域)
B、两个f1构成隐藏
C、代码编不过
D、以上选项都不对
2、下列哪个说法正确()
class Student : public Person
{
public:
void f()
{
cout << "Student::f()" << endl;
}
void f1(int i)
{
cout << "Student::f1()" << endl;
}
void f2()
{
cout << _stunum << endl;
cout << "Student::f2()" << endl;
}
void f3()
{
_stunum = 3;
cout << "Student::f3()" << endl;
}
void f4()
{
this->f();
cout << "Student::f4()" << endl;
}
int _stunum;
};
int main()
{
Student *p = NULL;
//1、p->f1() C
//2、p->f2() D 对空指针进行了解引用
//3、p->f3() B 对空指针进行了解引用
//4、p->f4() C 没有去指针指向的内容,传过去并没有解引用
system("pause");
return 0;
}
A、代码编不过
B、可以编过,但是程序会崩溃
C、可以编过,正常输出
D、以上选项都不对
六、派生类的默认成员函数
子类不显示定义成员成员函数,编译器会合成留个对应的成员函数。
不显示定义,会调用父类缺省值。
子类的构造函数不是定义的而是合成的,子类最好显示调用父类的构造函数
构造函数的调用:先调用父类的,在调用子类的
析构函数的调用:先调用子类的,在调用父类的
派生类中要显示调用基类的构造函数,但不需要显示调用基类的析构函数
class Person
{
public:
Person(int data = 0)
:_data(data)
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
int _data;
};
class Student : public Person
{
public:
Student(int data = 0,int num = 0)
:Person(data)//派生类中初始化父类成员:调用父类构造函数
,_num(num)
{
cout << "Student()" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
protected://这里为什么用protected?防止之后再被继承
int _num;
};
int main()
{
cout << "基类的构造函数";
Person p(2);
cout << "派生类的构造函数";
Student s(3, 4);
cout << "派生类的析构函数";
s.~Student();
cout << "基类的析构函数";
p.~Person();
system("pause");
return 0;
}
七、单继承&多继承&菱形继承
单继承:只有一个直接的父类
多继承:有两个或以上直接的父类
菱形继承
菱形继承存在二义性(不明确)和数据冗余(浪费空间)的问题
解决二义性:指定作用域
菱形继承对象模型
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;
system("pause");
return 0;
}
八、虚继承——解决菱形继承的二义性和数据冗余
菱形虚拟继承对象模型
解决二义性时使用的是偏移量,而不是指针的直接指向