文章目录
1.继承的定义和概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
teacher类、student类继承person类
class person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter";
int _age=18;
};
class student:public person
{
private:
int _suid;
};
class teacher :public person
{
private:
int _juid;
};
int main()
{
student s;
teacher t;
s.print();
t.print();
return 0;
}
1.2继承基类访问方式的变化
总结:
- 1.基类中的private成员在派生类中无论以那种基础方式都不可见;这里并不是不继承,只是语法限制了派生类去访问。
- 2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。
- 3.继承中,权限是取较小。
- 4.使用class关键字时默认的继承方式是private,使用struct关键字时默认的继承方式public。
2.基类和派生类对象赋值转换
1.派生类对象可以赋值给基类的对象、指针或引用。这个操作叫做切片。 2.基类对象不能复制给派生类对象。 3.基类的指针和引用可以通过强制类型转换赋值给派生类的指针和引用。这也是多态的原理。
void test()
{
student s;
teacher t;
person p = s;
person* ptrp = &s;
person& yp = s;
cout << "&s:" << &s << endl;
cout << "ptrp:" << ptrp << endl;
cout << "&yp:" << &yp << endl;
}
可以看到ptrp、yp和s指向的同一块区域。只是可以访问的成员不同。
3.继承中的作用域
1.在继承体系中基类和派生类都有独立的作用域 2.隐藏(重定义):子类和父类中有同名成员,子类成员将屏蔽父类对同名成员直接访问。 3.成员函数的隐藏:只需要函数名相同就构成隐藏。(注意:重载的条件是同名函数在同一个作用域。)
class person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
public:
string _name = "peter";
int _age=18;
int _num = 10;
};
class student:public person
{
public:
int _suid;
int _num = 100;
};
void test()
{
student s;
s._num +=10;
cout << "_num:" << s._num << endl;
}
输出:_num:110;可以看出调用的是派生类的同名成员。
如果想要调用父类的成员,需要指定作用域。
void test()
{
student s;
s.person::_num +=10;
cout << "_num:" << s.person::_num << endl;
}
输出:_num:20
隐藏
class A
{
public:
void f(){ cout<<"A::f()"<<endl; }
int a;
};
class B : public A
{
public:
void f(int a){cout<<"B::f()"<<endl;}
int a;
};
int main()
{
B b;
b.f();
return 0;
}
会发生编译报错。A中的函数f()被B中的同名函数隐藏。而想要调用B中的f函数需要传递一个int类型的参数:f(int)。
4.派生类的默认成员函数
4.1派生类的默认成员函数调用规则
调用规则:继承父类的部分调用父类的构造函数,剩下的按照一般类处理。
class person
{
public:
person(int age=10,string name="petet")
:_age(age),_name(name)
{
cout << "person()" << endl;
}
~person()
{
cout << "~person()" << endl;
}
protected:
string _name;
int _age;
};
class student:public person
{
public:
int _suid;
int _num;
};
student s;
4.2无法自动调用基类默认函数
比如基类不存在默认的构造函数(比如基类的构造函数不是全缺省),这时就需要显示的调用构造函数。
class person
{
public:
person(int age=10,string name="petet")
:_age(age),_name(name)
{
cout << "person()" << endl;
}
person(const person& p)
:_name(p._name),_age(p._age)
{
cout << "person(const person& p)" << endl;
}
person& operator=(const person& p)
{
if (this != &p)
{
_name = p._name;
_age = p._age;
cout << "operator=(const person&p)" << endl;
}
return *this;
}
~person()
{
cout << "~person()" << endl;
}
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name;
int _age;
};
class student:public person
{
public:
student(int age = 18, const string name="",int suid=0,int num=1)
//初始化列表显示调用person的构造函数
:person(age,name),_suid(suid),_num(num)
{
cout << "student()" << endl;
}
student& operator=(const student&p)
{
if (this != &p)
{
//赋值构造函数的两次切片:1.this的切片2.p的切片
//this-->person(this);p->person(p);
//需要指出作用域显示调用:防止同名函数的隐藏。
this->person::operator=(p);
_suid = p._suid;
_num = p._num;
cout << "operator=(const student& p)" << endl;
}
return *this;
}
student(const student&p)
//进行了一次切片
:person(p),_suid(p._suid),_num(p._num)
{
cout << "student(const student&p)" << endl;
}
~student()
{
cout << "~student()" << endl;
}
int _suid;
int _num;
}
student s;
person p(s);
4.3函数名统一的析构函数
4…3.1父子类的析构函数构成隐藏函数
当我们显示的调用基类的析构函数时,编译无法通过。
原因:
父子类的析构函数构成隐藏函数---->为了满足多态的需要,析构函数名称会统一被处理destructor();
如果想要调用,就需要显示的调用。
4.2编译器对析构函数的特殊处理
上面程序可以发现,编译器会多调用一次基类的析构函数。
原因:
为了保证析构的顺序(构造的顺序与析构的顺序相反);在创建子类时要先创建父类,因此需要先析构子类再析父类。
因此C++编译器进行了特殊处理,调用派生类析构后会自动调用基类的析构函数。
满足先创建父类再创建子类,先析构子类再析构父类。
4.4设计一个不能被继承的类
方法:将基类的构造函数私有化
class A
{
public:
//可以在类外访问
A createA()
{
return A();
}
private:
//派生类无法调用
A()
{}
}
class B:public A
{}
但是要调用成员函数需要对象调用,而创建对象需要调用构造函数,而构造函数是私有的。因此就无法生成对象。
为了无对象访问成员函数,可以将成员函数设置为静态成员函数
class A
{
public:
//可以在类外访问
static A createA()
{
return A();
}
private:
//派生类无法调用
A()
{}
}
5.继承与静态成员函数
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
统计一共调用了多少次基类的构造函数
class A
{
public:
A()
{
a++;
}
static int a;
};
int A:: a = 0;
class B :public A
{};
void test() {
A aa;
B bb;
cout << "aa statit a :" << aa.a << " &a" << &aa.a << endl;
cout << "bb statit a :" << bb.a << " &a" << &bb.a << endl;
}
子类和基类对象共享同一个静态变量。
6.菱形继承和菱形虚拟继承
6.1菱形继承的概念
我们可以同时从多个类继承。但是多继承是饱受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。需要加上作用域。而菱形继承是多继承的一种特殊情况。
6.2菱形继承出现的问题
class a
{
public:
char ch[20000];
int _num;
};
class a1:public a
{};
class a2 :public a
{};
class a_son :public a1, public a2
{};
1.二义性
访问继承的成员时,
void test()
{
a_son aa;
cout<<aa._num<<endl;
}
解决方法:指定作用域。
2.数据冗余
在这里a_son会继承两份a的数据。造成数据的冗余
void testa()
{
a_son aa;
cout << sizeof(aa)<< endl;
}
6.3虚继承
使用虚继承可以解决数据冗余和二义性问题
class a
{
public:
char ch[20000];
int _num;
};
class a1:virtual public a
{};
class a2 :virtual public a
{};
class a_son :public a1, public a2
{};
6.4虚继承模型和虚基表
6.4.1菱形继承模型
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;
};
void testa()
{
D dd;
dd.B::a = 1;
dd.C::a = 2;
dd.b = 2;
dd.c = 3;
dd.d = 4;
}
比较经典的一个菱形继承类就是iostream类
6.4.2虚继承模型
class A
{
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;
};
void testa()
{
D dd;
dd.a = 0;
dd.B::a = 1;
dd.C::a = 2;
dd.b = 3;
dd.c = 4;
dd.d = 5;
}
对比菱形继承的模型,原本菱形基础中存放各基类a的位置,存放了一个“随机值”。我们取随机值的地址。
我们把指向偏移量的指针叫做虚基表指针。
存储模型
6.4.3继承模型题目
class A{ public: int _b1; };
class B { public: int _b2; };
class C : public A, public B
{
public: int _d;
};
int main() {
C d;
A* p1 = &d;
B* p2 = &d;
C* p3 = &d;
cout << "p1:" << p1 << endl;
cout << "p2:" << p2 << endl;
cout << "p3:" << p3 << endl;
return 0;
}
结果为p1==p3!=p2。
分析p1和p2虽然都是其父类,但在子类内存模型中,其位置不同,所以p1和p2所指子类的位置也不相同,因此p1!=p2;
由于p1对象是第一个被继承的父类类型,所有其地址与子类对象的地址p3所指位置都为子类对象的起始位置,因此p1==p3,