继承
1. 概念及定义
1.1 概念
继承机制是面向对象程序设计使代码可以复用的重要手段,它允许程序员在 保持原有类特性的基础上进行扩展,增加功能,产生的新类,叫派生类。继承呈现了面向对象程序设计的层次结构,继承是类设计层次的复用
1.2 定义
class Base
{};
class Derived : public Base//派生类以public方式继承基类
{};
- 注意:class默认继承方式为private,struct默认继承方式为public
- 注意:在派生类中不可见指基类private成员被继承到了派生类中,但语法上限制派生类对象在类内或类外都无法访问它。
2. 基类和派生类对象赋值转换
- 派生类对象可以赋值给基类对象,基类指针,基类引用
- 基类对象不可赋值给派生类对象
通过上面的模型,可以发现,若派生类对象赋值给基类对象,当派生类对象访问自己的那部分数据的时候,就会发生访问越界。 - 基类指针可以通过强制转换赋值给派生类指针。但必须是,基类的指针指向派生类的对象时才是安全的
3.继承中的作用域
1.在继承体系中,基类和派生类都有独立的作用域
2.派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员直接访问,这种情况叫隐藏,也叫重定义
3.如果是成员函数的隐藏,只要函数名相同就构成隐藏
//A中的fun与B中的fun没有形成重载,因为不在同一作用域
//A中的fun与B中的fun形成隐藏,因为函数名相同
class A
{
public:
void fun()
{}
};
class B
{
public:
void fun(int)
{}
};
4.派生类中的默认成员函数
1.派生类的构造函数必须调用基类的构造函数,完成基类部分的初始化。如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
2.派生类的拷贝构造函数必须调用基类的拷贝构造函数,完成基类部分的拷贝初始化
3.派生类的operator=必须调用基类的operator完成基类的复制
4.派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员
用下面代码测试派生类中构造与析构函数调用顺序
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B : public A
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
void Test()
{
B b;
}
int main()
{
Test();
return 0;
}
5.继承与友元
友元关系不能被继承
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
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);
}
派生类没有继承基类的友元关系,所以上面代码会发生崩溃
6.继承与静态成员
基类定义了static静态成员,则整个继承体系中只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
class A
{
public:
A()
{
cout << count << endl;
}
public:
static int count;
};
int A::count = 0;
class B : public A
{
public:
B()
{
count++;
cout << count << endl;
}
};
class C : public A
{
public:
C()
{
count++;
cout << count << endl;
}
};
int main()
{
A a;
B b;
C c;
return 0;
}
静态成员 count 只有一个,派生类B,C,都在使用同一个count
7. 复杂的菱形继承及菱形虚拟继承
单继承:一个派生类只有一个直接基类
多继承:一个派生类有两个及以上的直接基类
菱形继承:菱形继承是多继承的一种特殊情况
菱形继承会产生二义性和数据冗余的问题
class A
{
public:
void fun()
{
cout << "A()" << endl;
}
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()
{
B b;
D d;
//d.fun();编译报错,出现模糊调用问题
d.B::fun();
d.C::fun();
return 0;
}
此时d对象模型为:
利用虚拟继承可以解决二义性和数据冗余问题
class A
{
public:
void fun()
{
cout << "A()" << endl;
}
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual 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;
return 0;
}
此时d对象的模型为:
我们发现,sizeof(菱形继承的d对象) 结果为20(32位机器,vs2013),sizeof(菱形虚拟继承的d对象)结果为24(32位机器,vs2013),因为d对象开辟一块空间存放_a。
总结:通过内存窗口发现,虚拟继承的对象的前四个字节被修改为虚基表指针,指向虚基表,虚基表中存放偏移量