3.1继承概念
面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。我们已经讲解了类和对象,了解了面向对象程序设计的两个重要特征一数据抽象与封装,已经能够设计出基于对象的程序,这是面向对象程序设计的基础。
要较好地进行面向对象程序设计,还必须了解面向对象程序设计另外两个重要特 征——继承性和多态性。本章主要介绍有关继承的知识,多态性将在后续章节中讲解。
继承性是面向对象程序设计最重要的特征,可以说,如果没有掌握继承性,就等于没有掌握类和对象的精华,就是没有掌握面向对象程序设计的真谛。
3.1.1类之间的关系
has-A,uses-A 和 is-A
has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
is-A 机制称为“继承”。关系具有传递性,不具有对称性。
3.1.2继承关系举例
万事万物中皆有继承,是重要的现象
两个案例:1)植物继承图;2)程序员继承图
3.1.3 继承相关概念
3.1.4 派生类的定义
注意:C++中的继承方式(public、private、protected)会影响子类的对外访问属性。
3.1.5 继承重要说明
1、子类拥有父类的所有成员变量和成员函数
2、子类可以拥有父类没有的方法和属性
3、子类就是一种特殊的父类
4、子类对象可以当作父类对象使用
3.2派生类的访问控制
派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生过程中是可以调整的。
3.2.1单个类的访问控制
1、类成员访问级别(public、private、protected)
2、思考:类成员的访问级别只有public和private是否足够?
3.2.2不同的继承方式会改变继承成员的访问属性
1)C++中的继承方式会影响子类的对外访问属性
public继承:父类成员在子类中保持原有访问级别
private继承:父类成员在子类中变为private成员
protected继承:父类中public成员会变成protected
父类中protected成员仍然为protected
父类中private成员仍然为private
2)private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员。
3)C++中子类对外访问属性表
#include <iostream>
using namespace std;
class Parent
{
public:
int b;
void print()
{
a = 0;
b = 0;
cout << "a" << a << endl;
cout << "b" << b << endl;
}
private:
int a;
};
class Child:public Parent
{
public:
private:
int c;
};
void main()
{
Child c1;
c1.b = 3;//class Child:public Parent public b能够访问
c1.c;//class Child private
c1.a;//class Child:public Parent private==>private
cout << "hello..." << endl;
system("pause");
return;
}
| 父类成员访问级别 | |||
继 承 方 式 |
| public | proteced | private |
public | public | proteced | private | |
proteced | proteced | proteced | private | |
private | private | private | Private |
4)继承中的访问控制
//public 修饰的成员变量 方法 在类的内部 类的外部都能使用
//protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
//派生类访问控制的结论
//1 protected 关键字 修饰的成员变量 和成员函数 ,是为了在家族中使用 ,是为了继承
//2 项目开发中 一般情况下 是 public
//3
#include <iostream>
using namespace std;
class Parent
{
public:
int a;//老爹的名字
protected:
int b;//老爹的银行卡密码
private:
int c;//老爹的情人
};
//保护继承
class Child :protected Parent
{
public:
void useVar()
{
a = 0;//public 修饰的成员变量 方法 在类的内部 类的外部都能使用
b = 0;//protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c = 0;//private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
}
};
int main01()
{
Child c1;
//c1.a = 10;//protected继承下来后,a public =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c1.b = 10;//protected继承下来后,b protected =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c1.c = 10;//protected继承下来后,c private =>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
system("pause");
return 0;
}
//私有继承
class Child1:private Parent
{
public:
void useVar()
{
a = 0;//ok private继承下来后,a =>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
b = 0;//ok private继承下来后,b=>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
//c = 0;//err
}
};
int main02()
{
Child c1;
//c1.a = 10;//err protected继承下来后,a public =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c1.b = 10;//err protected继承下来后,b protected =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c1.c = 10;//err protected继承下来后,c private =>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
system("pause");
return 0;
}
class Child2:public Parent
{
public:
void useVar()
{
a = 0; //ok public继承下来后,a public =>public 修饰的成员变量 方法 在类的内部 类的外部都能使用
b = 0; //ok public继承下来后,b protected =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c = 0; //err protected继承下来后,c private =>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
}
};
int main()
{
Child c1;
//c1.a = 10;//err protected继承下来后,a public =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c1.b = 10;//err protected继承下来后,b protected =>protected 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//c1.c = 10;//err protected继承下来后,c private =>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
system("pause");
return 0;
}
3.2.3“三看”原则
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问
1)看调用语句,这句话写在子类的内部、外部
2)看子类如何从父类继承(public、private、protected)
3)看父类中的访问级别(public、private、protected)
3.2.3派生类类成员访问级别设置的原则
思考:如何恰当的使用public,protected和private为成员声明访问级别?
1、需要被外界访问的成员直接设置为public
2、只能在当前类中访问的成员设置为private
3、只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
3.2.4综合训练
练习:
public继承不会改变父类对外访问属性;
private继承会改变父类对外访问属性为private;
protected继承会部分改变父类对外访问属性。
结论:一般情况下class B : public A
//dm03_派生类访问控制综合训练
//类的继承方式对子类对外访问属性影响
#include <cstdlib>
#include <iostream>
using namespace std;
class A
{
private:
int a;
protected:
int b;
public:
int c;
A()
{
a = 0;
b = 0;
c = 0;
}
void set()
{
this->a = a;
this->b = b;
this->c = c;
}
};
//公用继承
class B:public A
{
public:
void print()
{
//cout<<"a = "<< a; //err //public继承下来后,a private=>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
cout << "b = " << b; //ok //public继承下来后,b protected=>protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
cout << "c = " << c; //ok //public继承下来后,b public=>public 修饰的成员变量 方法 在类的内部 类的外部都能使用
}
};
//保护继承
class C:protected A
{
public:
void print()
{
//cout<<"a = "<< a; //err //protected继承下来后,a private=>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
cout << "b = " << b; // ok //protected继承下来后,b protected=>protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
cout << "c = " << endl; //ok //protected继承下来后,c public=>protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
}
};
//私有继承
class D : private A
{
public:
void print()
{
//cout<<"a = "<<a; //err //err //private继承下来后,a private=>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
cout << "b = " << b << endl; //ok // ok //private继承下来后,b protected=>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
cout << "c = " << c << endl; // ok //private继承下来后,c public=>private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
}
};
int main()
{
A aa;
B bb;
C cc;
D dd;
aa.c = 100; //ok
bb.c = 100; // ok
//cc.c = 100; // err 保护的
//dd.c = 100; // err
//aa.set(1, 2, 3); //err
//bb.set(10, 20, 30); //err
//cc.set(40, 50, 60); // err
//dd.set(70, 80, 90); //err
bb.print(); //ok
cc.print(); //ok
dd.print(); //
system("pause");
return 0;
}
//类的继承方式对子类对外访问属性影响
#include <cstdlib> #include <iostream>
using namespace std;
class A { private: int a; protected: int b; public: int c;
A() { a = 0; b = 0; c = 0; }
void set(int a, int b, int c) { this->a = a; this->b = b; this->c = c; } };
class B : public A { public: void print() { //cout<<"a = "<<a; //err cout<<"b = "<<b; cout<<"c = "<<endl; } };
class C : protected A { public: void print() { //cout<<"a = "<<a; //err cout<<"b = "<<b; cout<<"c = "<<endl; } };
class D : private A { public: void print() { //cout<<"a = "<<a; //err cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; } };
int main_01(int argc, char *argv[]) { A aa; B bb; C cc; D dd;
aa.c = 100; //ok bb.c = 100; //ok //cc.c = 100; //err 类的外部是什么含义 //dd.c = 100; //err
aa.set(1, 2, 3); bb.set(10, 20, 30); //cc.set(40, 50, 60); //ee //dd.set(70, 80, 90); //ee
bb.print(); cc.print(); dd.print();
system("pause"); return 0; } |
3.3继承中的构造和析构
3.3.1类型兼容性原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
类型兼容规则是多态性的重要基础之一。
总结:子类就是特殊的父类 (base *p = &child;)
#include <cstdlib> #include <iostream>
using namespace std;
/* 子类对象可以当作父类对象使用 子类对象可以直接赋值给父类对象 子类对象可以直接初始化父类对象 父类指针可以直接指向子类对象 父类引用可以直接引用子类对象 */ //子类就是特殊的父类 class Parent03 { protected: const char* name; public: Parent03() { name = "Parent03"; }
void print() { cout<<"Name: "<<name<<endl; } };
class Child03 : public Parent03 { protected: int i; public: Child03(int i) { this->name = "Child2"; this->i = i; } };
int main() { Child03 child03(1000); //分别定义父类对象 父类指针 父类引用 child Parent03 parent = child03; Parent03* pp = &child03; Parent03& rp = child03;
parent.print(); pp->print(); rp.print(); system("pause"); return 0; } |
3.3.2继承中的对象模型
类在C++编译器的内部可以理解为结构体
子类是由父类成员叠加子类新成员得到的
3.3.3继承中的构造析构调用原则
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
//dm05_继承中构造和析构
#include <iostream>
using namespace std;
//结论
//先 调用父类构造函数 在调用 子类构造函数
//析构的顺序 和构造相反
/*
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
*/
class Parent
{
public:
Parent(int a, int b)
{
this->a = a;
this->b = b;
cout << "父类构造函数..." << endl;
}
void printP(int a, int b)
{
this->a = a;
this->b = b;
cout << "我是爹..." << endl;
}
~Parent()
{
cout << "析构函数..." << endl;
}
private:
int a;
int b;
};
class child:public Parent
{
public:
//3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
child(int a, int b, int c) : Parent(a, b)//初始化列表,否则会报错
{
this->c = c;
cout << "子类的构造函数" << endl;
}
void printC()
{
cout << "子类的析构" << endl;
}
~child()
{
cout << "子类的析构" << endl;
}
private:
int c;
};
//搭建一个舞台 观看一个对象的生命周期
void playObj()
{
//Parent(1, 2);
child c1(1, 2, 3);
}
//父类构造函数...
//子类的构造函数
//子类的析构
//析构函数...
//hello...
//请按任意键继续. . .
//1、子类对象在创建时会首先调用父类的构造函数
//2、父类构造函数执行结束后,执行子类的构造函数
//4、析构函数调用的先后顺序与构造函数相反 析构的时候:先析构子类的 在析构父类的
void main()
{
playObj();
cout << "hello..." << endl;
system("pause");
return;
}
继承中构造和析构
问题:如何初始化父类成员?父类与子类的构造函数有什么关系
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化
在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理
#include <cstdlib> #include <iostream> using namespace std;
class Parent04 { public: Parent04(const char* s) { cout<<"Parent04()"<<" "<<s<<endl; }
~Parent04() { cout<<"~Parent04()"<<endl; } };
class Child04 : public Parent04 { public: Child04() : Parent04("Parameter from Child!") { cout<<"Child04()"<<endl; }
~Child04() { cout<<"~Child04()"<<endl; } };
void run04() { Child04 child; }
int main_04(int argc, char *argv[]) { run04();
system("pause"); return 0; } |
//dm04_类型兼容性原则
#include <iostream>
using namespace std;
class Parent
{
public:
void printP()
{
cout << "我是爹..." << endl;
}
Parent()
{
cout << "parent构造函数" << endl;
}
Parent(const Parent &obj)
{
cout << "copy构造函数" << endl;
}
private:
int a;
};
class child : public Parent
{
public:
void printC()
{
cout << "我是儿子" << endl;
}
protected:
private:
int c;
};
/*
兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
*/
//C++编译器 是不会报错的 .....
void howToPrint(Parent *base)
{
base->printP(); //父类的 成员函数
}
void howToPrint2(Parent &base)
{
base.printP(); //父类的 成员函数
}
void main()
{
//兼容规则中所指的替代包括以下情况:
Parent p1;
p1.printP();
child c1;
c1.printC();
c1.printP(); //1.子类对象可以当作父类对象使用,调用父类的成员函数
//赋值兼容性原则
//1-1 基类指针 (引用) 指向 子类对象
Parent *p = NULL;
p = &c1;
p->printP();
//1-2 指针做函数参数
howToPrint(&p1);
howToPrint(&c1);
//1-3引用做函数参数
howToPrint2(p1);
howToPrint2(c1);
//第二层含义
//可以让子类对象 初始化 父类对象
//子类就是一种特殊的父类
Parent p3 = c1;
cout << "hello..." << endl;
system("pause");
return;
}
3.3.4继承与组合混搭情况下,构造和析构调用原则
原则: 先构造父类,再构造成员变量、最后构造自己
先析构自己,在析构成员变量、最后析构父类
//先构造的对象,后释放
//dm06_继承和组合混搭下的构造和析构
#include <iostream>
using namespace std;
class Object
{
public:
Object(int a, int b)
{
this->a = a;
this->b = b;
cout << "object构造函数 执行 " << "a" << a << " b " << b << endl;
}
~Object()
{
cout << "object析构函数 \n" << "a" << a << " b " << b << endl;
}
protected:
int a;
int b;
};
class Parent:public Object
{
public:
Parent(char *p) :Object(1, 2)
{
m_p = p;
cout << "父类构造函数..." << m_p << endl;
}
~Parent()
{
cout << "父类析构函数..." << m_p << endl;
}
void printP(int a, int b)
{
cout << "我是爹..." << endl;
}
protected:
char *m_p;
};
class child:public Parent
{
public:
child(char *p) :Parent(p), obj1(3, 4), obj2(5, 6)
{
this->myp = p;
cout << "子类的构造函数" << myp << endl;
}
~child()
{
cout << "子类的析构" << myp << endl;
}
void printC()
{
cout << "我是儿子" << endl;
}
protected:
char *myp;
Object obj1;
Object obj2;
};
//搭建一个舞台
void playObj()
{
child c1("继承测试");
}
//原则: 先构造父类,再构造成员变量、最后构造自己
//先析构自己,在析构成员变量、最后析构父类
//先构造的对象,后释放
//object构造函数 执行 a1 b 2
//父类构造函数...继承测试
//object构造函数 执行 a3 b 4
//object构造函数 执行 a5 b 6
//子类的构造函数继承测试
//子类的析构继承测试
//object析构函数
//a5 b 6
//object析构函数
//a3 b 4
//父类析构函数...继承测试
//object析构函数
//a1 b 2
//hello...
//请按任意键继续. . .
void main()
{
playObj();
cout << "hello..." << endl;
system("pause");
return;
}
3.3.5继承中的同名成员变量处理方法
1、当子类成员变量与父类成员变量同名时
2、子类依然从父类继承同名成员
3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
4、同名成员存储在内存中的不同位置
总结:同名成员变量和成员函数通过作用域分辨符进行区分
//dm07_继承中的同名成员变量和函数
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
public:
void get()
{
cout << "b " << b << endl;
}
void print()
{
cout << "AAAAA " << endl;
}
protected:
private:
};
class B : public A
{
public:
int b;//同名成员变量
int c;
public:
void get_child()
{
cout << "b " << b << endl;
}
void print()//同名成员函数
{
cout << "BBBB " << endl;
}
protected:
private:
};
//同名成员函数
void main01()
{
B b1;
b1.print();//派生类屏蔽基类同名成员函数 调用自身的成员函数
b1.A::print();//派生类对象调用基类版本同名成员函数
b1.B::print(); //默认情况
//BBBB
// AAAAA
// BBBB
// 请按任意键继续. . .
system("pause");
}
//同名成员变量
//同名成员变量
void main()
{
B b1;
b1.b = 1; //派生类的同名成员屏蔽基类的同名成员
b1.get_child();//
b1.A::b = 100; //修改父类的b
b1.B::b = 200; //修改子类的b 默认情况是B
b1.get();//子类对象 可以调用父类 派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员
cout << "hello..." << endl;
system("pause");
return;
}
3.3.6派生类中的static关键字
继承和static关键字在一起会产生什么现象哪?
理论知识
Ø 基类定义的静态成员,将被所有派生类共享
Ø 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制)
Ø 派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问 对象名 . 成员
总结:
1> static函数也遵守3个访问原则
2> static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)
3> 构造函数默认为private
//dm08_继承中的static关键字
#include <iostream>
using namespace std;
//单例
class A
{
public:
A()//构造函数默认为private
{
cout << "A的构造函数" << endl;
}
public:
static int a;
int b;
public:
void get()
{
cout<<"b "<<b<<endl;
}
void print()
{
cout<<"AAAAA "<<endl;
}
protected:
private:
};
int A::a = 100; //这句话 不是简单的变量赋值 更重要的是 要告诉C++编译器 你要给我分配内存 ,我再继承类中 用到了a 不然会报错..
class B : private A
{
public:
int b;
int c;
public:
void get_child()
{
cout<<"b "<<b<<endl;
cout<<a<<endl;
}
void print()
{
cout<<"BBBB "<<endl;
}
protected:
private:
};
//1 static关键字 遵守 派生类的访问控制规则
//2 不是简单的变量赋值 更重要的是 要告诉C++编译器 你要给我分配内存 ,我再继承类中 用到了a 不然会报错..
//3 A类中添加构造函数
//A类的构造函数中 A的构造函数是私有的构造函数 ...
//被别的类继承要小心....
//单例场景 .... UML
void main01()
{
A a1;
a1.print();
B b1;
b1.get_child();
system("pause");
}
//static函数也遵守3个访问原则
void main()
{
B b1;
//b1.a = 200;//private继承下来,无法访问a
system("pause");
}
3.4多继承
3.4.1多继承的应用
多继承概念
Ø 一个类有多个直接基类的继承关系称为多继承
Ø 多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
Ø 类 C 可以根据访问控制同时继承类 A 和类B 的成员,并添加
自己的成员
多继承的派生类构造和访问
Ø 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
Ø 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
Ø 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
多继承简单应用
//dm09_多继承语法
#include <iostream>
using namespace std;
class Base1
{
public:
Base1(int b1)
{
this->b1 = b1;
}
void printB1()
{
cout << "b1:" << b1 << endl;
}
protected:
private:
int b1;
};
class Base2
{
public:
Base2(int b2)
{
this->b2 = b2;
}
void printB2()
{
cout << "b2:" << b2 << endl;
}
protected:
private:
int b2;
};
class Child:public Base1,public Base2
{
public:
//初始化列表
Child(int b1, int b2, int c) :Base1(b1), Base2(b2)
{
this->c = c;
cout << "多继承" << endl;
}
void printC()
{
cout << "c:" << c << endl;
}
private:
int c;
};
void main()
{
Child b1(1, 2, 3);
b1.printC();
b1.printB1();
b1.printB2();
cout << "hello..." << endl;
system("pause");
return;
}
3.4.2虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
总结:
Ø 如果一个派生类从多个基类派生,而这些基类又有一个共同
的基类,则在对该基类中声明的名字进行访问时,可能产生
二义性
Ø 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处
汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象
Ø 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类
声明为虚继承,使这个基类成为虚基类。
Ø 虚继承声明使用关键字 virtual
实验:注意增加virtual关键字后,构造函数调用的次数。
3.5继承总结
Ø 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
Ø 单继承的派生类只有一个基类。多继承的派生类有多个基类。
Ø 派生类对基类成员的访问由继承方式和成员性质决定。
Ø 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
Ø C++提供虚继承机制,防止类继承关系中成员访问的二义性。
Ø 多继承提供了软件重用的强大功能,也增加了程序的复杂性。
//dm10_多继承的二义性
#include <iostream>
using namespace std;
class A
{
public:
int a;
A(int a)
{
this->a = a;
cout << "A类的构造函数" << endl;
}
private:
};
class B1 :virtual public A
{
public:
int b1;
B1(int b1) :A(0)
{
this->b1 = b1;
cout << "B1类的构造函数" << endl;
}
private:
};
class B2 :virtual public A
{
public:
int b2;
B2(int b2) :A(0)
{
this->b2 = b2;
cout << "B2类的构造函数" << endl;
}
private:
};
class C:public B1,public B2
{
public:
int c;
C(int c = 0) :A(0), B1(0), B2(0)
{
this->c = c;
cout << "c类的构造函数" << endl;
}
void printC()
{
cout << "a" << a << " " << "b1" << b1 << " " << "b2" << b2 << "c" << c << endl;
}
private:
};
void main()
{
C c1;
c1.b1 = 100;
c1.b2 = 200;
c1.c = 300;
//建立C类对象时候,A的构造函数被调用两次:一次由B1调用 另一次由B2调用 以初始化C类的对象中所包含的两个B类的子对象
//这样在调用A类时候,c++编译器不知道从哪一条路径来的 就会产生二义性
//c1.a = 500;
//解决方案1
//c1.B1::a = 500;
//c1.B2::a = 200;
//解决方案2
//进行虚继承
c1.printC();
cout << "hello..." << endl;
system("pause");
return;
}
//dm11_多继承原理抛砖
#include <iostream>
using namespace std;
class B
{
public:
B()
{
cout << "B构造函数执行\n";
}
int b;
protected:
private:
};
class B1 : virtual public B //12
{
public:
int b1;
};
class B2 : public B //8
{
public:
int b2;
};
class C : public B1, public B2
{
public:
int c;
};
void main()
{
cout << sizeof(B) << endl; //4
cout << sizeof(B1) << endl; //12 //加上virtual以后 , C++编译器会在给变量偷偷增加属性
cout << sizeof(B2) << endl; //8
system("pause");
}
void main1101()
{
C c1;
c1.b1 = 100;
c1.b2 = 200;
c1.c = 300;
//c1.b = 500; //继承的二义性 和 虚继承解决方案
//c1.B1::b = 500;
//c1.B2::b = 500;
cout << "hello..." << endl;
system("pause");
return;
}
class D1
{
public:
int k;
protected:
private:
};
class D2
{
public:
int k;
protected:
private:
};
class E : public D1, public D2
{
public:
protected:
private:
};
void main1202()
{
E e1;
e1.D1::k = 100;
e1.D2::k = 200;
system("pause");
}
4、多态
问题引出(赋值兼容性原则遇上函数重写)
面向对象新需求
C++提供的多态解决方案
多态案例
多态工程意义
面向对象三大概念、三种境界(封装、继承、多态)
多态成立条件
总结条件、看代码的时候要看出多态
//dm13_类型兼容性原则遇上函数重写
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(int a)
{
this->a = a;
cout << "Parent a" << a << endl;
}
virtual void print() //子类的和父类的函数名字一样
{
cout << "Parent 打印 a:" << a << endl;
}
private:
int a;
};
class Child:public Parent
{
public:
Child(int b) :Parent(10)
{
this->b = b;
cout << "Child b" << b << endl;
}
virtual void print() //virtual 父类写了virtual,子类可写 可不写 一般都会写,让人觉得显目,知道这里要用多态了
{
cout << "Child 打印 b:" << b << endl;
}
private:
int b;
};
void howtoPrint(Parent *base)
{
base->print();
}
void howtoPrint01(Parent &base)
{
base.print();
}
void main()
{
Parent *base = NULL;
Parent p1(20);
Child c1(30);
//1.子类对象可以当作父类对象使用,调用父类的成员函数
base = &p1;
base->print();//执行父类的打印函数
base = &c1;
base->print();//不加virtual关键字 调用父类的 加了之后调用子类的
//面向对象的新需求
//赋值兼容性原则
//1-1 基类指针 (引用) 指向 子类对象
{
Parent &base1 = p1;
base1.print();
Parent &base2 = c1;//c1的别名 不加virtual 还是调用父类的打印函数
base2.print();
}
//1-2 指针做函数参数
{
howtoPrint(&p1);
howtoPrint(&c1);
howtoPrint01(p1);
howtoPrint01(c1);
}
cout << "hello..." << endl;
system("pause");
return;
}
//加virtual关键字
//Parent a20
//Parent a10
//Child b30
//Parent 打印 a : 20
//Child 打印 b : 30
//Parent 打印 a : 20
//Child 打印 b : 30
//Parent 打印 a : 20
//Child 打印 b : 30
//Parent 打印 a : 20
//Child 打印 b : 30
//hello...
//请按任意键继续. . .
//不加virtual关键字
//Parent a20
//Parent a10
//Child b30
//Parent 打印 a : 20
//Parent 打印 a : 10
//Parent 打印 a : 20
//Parent 打印 a : 10
//Parent 打印 a : 20
//Parent 打印 a : 10
//Parent 打印 a : 20
//Parent 打印 a : 10
//hello...
//请按任意键继续. . .
4.1多态
4.1.1问题引出
如果子类定义了与父类中原型相同的函数会发生什么?
函数重写 在子类中定义与父类中原型相同的函数 函数重写只发生在父类与子类之间 |
class Parent { public: void print() { cout<<"Parent:print() do..."<<endl; } };
class Child : public Parent { public: void print() { cout<<"Child:print() do..."<<endl; } }; int main01() { run00();
/* Child child; Parent *p = NULL; p = &child; child.print(); child.Parent::print(); */
system("pause"); return 0; }
|
父类中被重写的函数依然会继承给子类 默认情况下子类中重写的函数将隐藏父类中的函数 通过作用域分辨符::可以访问到父类中被隐藏的函数 |
/* C/C++是静态编译型语言 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象 */ /* 1、在编译此函数的时,编译器不可能知道指针 p 究竟指向了什么。 2、编译器没有理由报错。 3、于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。 */
//面向对象新需求 //如果我传一个父类对象,执行父类的print函数 //如果我传一个子类对象,执行子类的printf函数
//现象产生的原因 //赋值兼容性原则遇上函数重写 出现的一个现象 //1 没有理由报错 //2 对被调用函数来讲,在编译器编译期间,我就确定了,这个函数的参数是p,是Parent类型的。。。 //3静态链编
//工程开发中如何判断是不是多态存在?
/* 在同一个类里面能实现函数重载 继承的情况下,发生重写 重载不一定; 重写的定义 静态联编 重载是 动态联编 */ #include <iostream>
using namespace std;
class Parent { public: void print() { cout<<"Parent:print() do..."<<endl; } };
class Child : public Parent { public: void print() { cout<<"Child:print() do..."<<endl; } };
/* 1、在编译此函数的时,编译器不可能知道指针 p 究竟指向了什么。 2、编译器没有理由报错。 3、于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。 */
void howToPrint(Parent* p) { p->print(); }
void run00() { Child child; Parent* pp = &child; Parent& rp = child;
//child.print();
//通过指针 //pp->print(); //通过引用 //rp.print();
howToPrint(&child); } int main01() { run00();
/* Child child; Parent *p = NULL; p = &child; child.print(); child.Parent::print(); */
system("pause"); return 0; }
|
4.1.2面向对象新需求
编译器的做法不是我们期望的
根据实际的对象类型来判断重写函数的调用
如果父类指针指向的是父类对象则调用父类中定义的函数
如果父类指针指向的是子类对象则调用子类中定义的重写函数
4.1.3解决方案
Ø C++中通过virtual关键字对多态进行支持
Ø 使用virtual声明的函数被重写后即可展现多态特性
4.1.4多态实例
//dm14_多态案例
//多态的思想
//面向对象3大概念
//封装: 突破c函数的概念....用类做函数参数的时候,可以使用对象的属性 和对象的方法
//继承: A B 代码复用
//多态 : 可以使用未来...
//多态很重要
//实现多态的三个条件
//C语言 间接赋值 是指针存在的最大意义
//是c语言的特有的现象 (1 定义两个变量 2 建立关联 3 *p在被调用函数中去间接的修改实参的值)
//实现多态的三个条件
//1 要有继承
//2 要有虚函数重写
//3 用父类指针(父类引用)指向子类对象....
#include <iostream>
using namespace std;
class HeroFighter
{
public:
virtual int power()//C++会对这个函数特殊处理
{
return 10;
}
private:
};
class EnemyFighter
{
public:
int attack()
{
return 15;
}
};
class AdvHeroFighter:public HeroFighter//1 要有继承
{
public:
virtual int power()//2 要有虚函数重写
{
return 20;
}
private:
};
//多态威力
//1 PlayObj给对象搭建舞台 看成一个框架
void PlayObj(HeroFighter *hf, EnemyFighter *ef)
{
//不写virtual关键字 是静态联编 C++编译器根据HeroFighter类型,去执行 这个类型的power函数 在编译器编译阶段就已经决定了函数的调用
//动态联编: 迟绑定: //在运行的时候,根据具体对象(具体的类型),执行不同对象的函数 ,表现成多态.
if (hf->power() > ef->attack())//hf->power()函数调用会有多态发生
{
printf("主角win\n");
}
else
{
printf("主角挂掉\n");
}
}
//未来在增加 框架依然适用
class AdvAdvHeroFighter : public HeroFighter
{
public:
virtual int power()
{
return 30;
}
};
void main()
{
HeroFighter hf;
AdvHeroFighter Advhf;
EnemyFighter ef;
//3 用父类指针(父类引用)指向子类对象....
PlayObj(&hf, &ef); //主角挂掉
PlayObj(&Advhf, &ef);//主角win
AdvAdvHeroFighter advadvhf;
PlayObj(&advadvhf, &ef); //主角挂掉
cout << "hello..." << endl;
system("pause");
return;
}
4.1.5多态工程意义
//面向对象3大概念
/*
封装
突破了C语言函数的概念。。
继承
代码复用 。。。。我复用原来写好的代码。。。
多态
多态可以使用未来。。。。。80年代写了一个框架。。。。。。90人写的代码
多态是我们软件行业追寻的一个目标。。。
//写了一个框架,可以调用后来人,写的代码的能力
*/
4.1.6多态成立的条件
//间接赋值成立的3个条件
//1 定义两个变量。。。
//2 建立关联 。。。。
//3 *p
//多态成立的三个条件
//1 要有继承
//2 要有函数重写。。。C 虚函数
//3 要有父类指针(父类引用)指向子类对象
//多态是设计模式的基础,多态是框架的基础
4.1.7多态的理论基础
01静态联编和动态联编
1、联编是指一个程序模块、代码之间互相关联的过程。
2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。
重载函数使用静态联编。
3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。
4、理论联系实际
1、C++与C相同,是静态编译型语言 2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。 3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象 从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。 |
虚析构函数 若不加virtual关键字 析构时候只会析构父类,这样就会造成内存泄漏
//dm15_虚析构函数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//虚析构函数
class A
{
public:
A()
{
p = new char[20];
strcpy(p, "obja");
printf("A()\n");
}
virtual ~A()
{
delete[] p;
printf("~A()\n");
}
protected:
private:
char *p;
};
class B : public A
{
public:
B()
{
p = new char[20];
strcpy(p, "objb");
printf("B()\n");
}
virtual ~B()
{
delete[] p;
printf("~B()\n");
}
protected:
private:
char *p;
};
class C : public B
{
public:
C()
{
p = new char[20];
strcpy(p, "objc");
printf("C()\n");
}
virtual ~C()
{
delete[] p;
printf("~C()\n");
}
protected:
private:
char *p;
};
//只执行了 父类的析构函数
//向通过父类指针 把 所有的子类对象的析构函数 都执行一遍
//向通过父类指针 释放所有的子类资源
void howtodelete(A *base)
{
delete base; //这句话不会表现成多态 这种属性
}
/*
void howtodelete(B *base)
{
delete base; //这句话不会表现成多态 这种属性
}
*/
void main()
{
C *myC = new C; //new delete匹配
//delete myC; //直接通过子类对象释放资源 不需要写virtual
howtodelete(myC);//加virtual后,析构掉所有的
cout << "hello..." << endl;
system("pause");
return;
}
//dm16_重载重写重定义
#include <iostream>
using namespace std;
//重写 重载 重定义
//重写发生在2个类之间
//重载必须在一个类之间
//重写分为2类
//1 虚函数重写 将发生多态
//2 非虚函数重写 (重定义)
class Parent
{
public:
void abc()
{
printf("abc");
}
//这个三个函数都是重载关系
virtual void func()
{
cout << "func() do..." << endl;
}
virtual void func(int i)
{
cout << "func() do..." << i << endl;
}
virtual void func(int i, int j)
{
cout << "func() do..." << i << " " << j << endl;
}
virtual void func(int i, int j, int m, int n)
{
cout << "func() do..." << i << " " << j << endl;
}
protected:
private:
};
class Child : public Parent
{
public:
void abc()
{
printf("child abc");
}
/*
void abc(int a)
{
printf("child abc");
}
*/
virtual void func(int i, int j)
{
cout << "func(int i, int j) do..." << i << " " << j << endl;
}
virtual void func(int i, int j, int k)
{
cout << "func(int i, int j) do.." << endl;
}
protected:
private:
};
//重载重写和重定义
void main()
{
//: error C2661: “Child::func”: 没有重载函数接受 0 个参数
Child c1;
//c1.func();
//子类无法重载父类的函数,父类同名函数将被名称覆盖
c1.Parent::func();
//1 C++编译器 看到func名字 ,因子类中func名字已经存在了(名称覆盖).所以c++编译器不会去找父类的4个参数的func函数
//2 c++编译器只会在子类中,查找func函数,找到了两个func,一个是2个参数的,一个是3个参数的.
//3 C++编译器开始报错..... error C2661: “Child::func”: 没有重载函数接受 4 个参数
//4 若想调用父类的func,只能加上父类的域名..这样去调用..
//c1.func(1, 3, 4, 5);
c1.Parent::func(1, 3, 4, 5);
//c1.func();
//func函数的名字,在子类中发生了名称覆盖;子类的函数的名字,占用了父类的函数的名字的位置
//因为子类中已经有了func名字的重载形式。。。。
//编译器开始在子类中找func函数。。。。但是没有0个参数的func函数
cout << "hello..." << endl;
system("pause");
return;
}
面试题1:请谈谈你对多态的理解
多态的实现效果 多态:同样的调用语句有多种不同的表现形态; 多态实现的三个条件 有继承、有virtual重写、有父类指针(引用)指向子类对象。 多态的C++实现 virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用 动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。 多态的重要意义 设计模式的基础 是框架的基石。 实现多态的理论基础 函数指针做函数参数 C函数指针是C++至高无上的荣耀。C函数指针一般有两种用法(正、反)。 多态原理探究 与面试官展开讨论 |
面试题2:谈谈C++编译器是如何实现多态
c++编译器多态实现原理
面试题3:谈谈你对重写,重载理解
函数重载
必须在同一个类中进行
子类无法重载父类的函数,父类同名函数将被名称覆盖
重载是在编译期间根据参数类型和个数决定函数调用
函数重写
必须发生于父类与子类之间
并且父类与子类中的函数必须有完全相同的原型
使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
多态是在运行期间根据具体对象的类型决定函数调用
#include <cstdlib> #include <iostream>
using namespace std;
class Parent01 { public: Parent01() { cout<<"Parent01:printf()..do"<<endl; } public: virtual void func() { cout<<"Parent01:void func()"<<endl; }
virtual void func(int i) { cout<<"Parent:void func(int i)"<<endl; }
virtual void func(int i, int j) { cout<<"Parent:void func(int i, int j)"<<endl; } };
class Child01 : public Parent01 {
public:
//此处2个参数,和子类func函数是什么关系 void func(int i, int j) { cout<<"Child:void func(int i, int j)"<<" "<<i + j<<endl; }
//此处3个参数的,和子类func函数是什么关系 void func(int i, int j, int k) { cout<<"Child:void func(int i, int j, int k)"<<" "<<i + j + k<<endl; } };
void run01(Parent01* p) { p->func(1, 2); }
int main() { Parent01 p;
p.func(); p.func(1); p.func(1, 2);
Child01 c; //c.func(); //问题1 c.Parent01::func(); c.func(1, 2);
run01(&p); run01(&c);
system("pause"); return 0; }
//问题1:child对象继承父类对象的func,请问这句话能运行吗?why //c.func(); //因为名称覆盖,C++编译器不会去父类中寻找0个参数的func函数,只会在子类中找func函数。
//1子类里面的func无法重载父类里面的func //2当父类和子类有相同的函数名、变量名出现,发生名称覆盖(子类的函数名,覆盖了父类的函数名。) //3//c.Parent::func(); //问题2 子类的两个func和父类里的三个func函数是什么关系? |
4.3多态原理探究
理论知识:
Ø 当类中声明虚函数时,编译器会在类中生成一个虚函数表
Ø 虚函数表是一个存储类成员函数指针的数据结构
Ø 虚函数表是由编译器自动生成与维护的
Ø virtual成员函数会被编译器放入虚函数表中
Ø 当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行howToPrint(Parent *base)函数是,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可。)
Ø VPTR一般作为类对象的第一个成员
4.3.1 多态的实现原理
C++中多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表 虚函数表是一个存储类成员函数指针的数据结构 虚函数表是由编译器自动生成与维护的 virtual成员函数会被编译器放入虚函数表中 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针) |
说明1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
说明2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数说明3 :C++编译器,执行HowToPrint函数,不需要区分是子类对象还是父类对象
|
所以编译器不需要知道是子类对象还是父类对象 给我们程序员造成的假象 c++编译器能识别子类对象或者父类对象
//dm01_多态原理探究
#include <iostream>
using namespace std;
//多态成立的三个条件
//1.要有继承
//2.要有方法的重写 -虚函数重写
//3.父类指针(引用)指向子类对象
class Parent
{
public:
Parent(int a = 0)
{
this->a = a;
}
virtual void print() //1 动手脚 写virtal关键字 会特殊处理 //虚函数表
{
cout << "我是爹" << endl;
}
virtual void print2() //1 动手脚 写virtal关键字 会特殊处理 //虚函数表
{
cout << "我是爹" << endl;
}
private:
int a;
};
class Child:public Parent
{
public:
//启动初始化列表
Child(int a = 0, int b = 0) :Parent(a)
{
this->b = b;
}
virtual void print()
{
cout << "我是儿子" << endl;
}
private:
int b;
};
void HowToPlay(Parent *base)
{
base->print();
//有多态发生 //2 动手脚
//效果:传来子类对象 指向子类的打印函数 传来父类的对象 执行父类的打印函数
//存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针) 父类对象和子类对象 都会有各自的vptr指针
//当类中声明虚函数时,编译器会在类中生成一个虚函数表
//虚函数表是一个存储类成员函数指针的数据结构
//虚函数表是由编译器自动生成与维护的
//virtual成员函数会被编译器放入虚函数表中
//当编译器调用相应对象的虚函数 编译器就会根据对象的vptr指针 在所指的虚函数表中查找打印函数 并调用执行
}
void main()
{
Parent p1; //3 动手脚 提前布局
//用类定义对象的时候 C++编译器会在对象中添加一个vptr指针
Child c1; //子类里面也有一个vptr指针
HowToPlay(&p1);
HowToPlay(&c1);
cout << "hello..." << endl;
system("pause");
return;
}
4.3.2如何证明vptr指针的存在
//dm02_证明vptr指针的存在
#include <iostream>
using namespace std;
class Parent1//4
{
public:
Parent1(int a = 0)
{
this->a = a;
}
void print()
{
cout << "我是爹" << endl;
}
private:
int a;
};
class Parent2//4 ==》实际上却是8 说明存在一个指针大小 (32位下指针都是4个字节长度)
{
public:
Parent2(int a = 0)
{
this->a = a;
}
virtual void print()
{
cout << "我是爹" << endl;
}
private:
int a;
};
void main()
{
//加上virtual关键字 c++编译器会增加一个指向虚函数表的指针 。
printf("sizeof(Parent):%d sizeof(Parent2):%d \n", sizeof(Parent1), sizeof(Parent2));
cout << "hello..." << endl;
system("pause");
return;
}
4.3.3构造函数中能调用虚函数,实现多态吗
1)对象中的VPTR指针什么时候被初始化?
对象在创建的时,由编译器对VPTR指针进行初始化 只有当对象的构造完全结束后VPTR的指向才最终确定 父类对象的VPTR指向父类虚函数表 子类对象的VPTR指向子类虚函数表
|
2)分析过程 画图分析
|
//dm03_vptr指针的分步初始化
#include <iostream>
using namespace std;
//构造函数中调用虚函数能发生多态吗?
class Parent
{
public:
Parent(int a = 0)
{
this->a = a;
print();//构造函数中调用虚函数
//这里调用父类的虚函数
}
virtual void print()
{
cout << "我是爹" << endl;
}
private:
int a;
};
class Child : public Parent
{
public:
Child(int a = 0, int b = 0) :Parent(a)
{
this->b = b;
print();
}
virtual void print()
{
cout << "我是儿子" << endl;
}
private:
int b;
};
void HowToPlay(Parent *base)
{
base->print(); //有多态发生 //2 动手脚
}
void main()
{
Child c1; //定义一个子类对象 ,在这个过程中,在父类构造函数中调用虚函数print 能发生多态吗?
//c1.print();
//1.要初始化 c1.vptr指针 初始化过程是分步的
//2 当执行父类的构造函数的时候 c1的vptr指向父类的虚函数表 当父类的构造函数运行完毕后 会把c1的vptr指针指向子类的虚函数表
//3 子类对象vptr指针分步完成的
//==》 子类对象构造的时候 在父类的构造函数中调用虚函数 产生不了多态现象 原因就是子类对象vptr指针初始化时分步的
cout << "hello..." << endl;
system("pause");
return;
}
面试题4:是否可类的每个成员函数都声明为虚函数,为什么。 c++编译器多态实现原理
面试题5:构造函数中调用虚函数能实现多态吗?为什么? c++编译器多态实现原理
面试题6:虚函数表指针(VPTR)被编译器初始化的过程,你是如何理解的?
c++编译器多态实现原理
面试题7:父类的构造函数中调用虚函数,能发生多态吗? c++编译器多态实现原理
面试题8:为什么要定义虚析构函数?
在什么情况下应当声明虚函数
Ø 构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
Ø 析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
铁律1:指针是一种数据类型
1)指针也是一种变量,占有内存空间,用来保存内存地址
测试指针变量占有内存空间大小
2)*p操作内存
在指针声明时,*号表示所声明的变量为指针
在指针使用时,*号表示操作指针所指向的内存空间中的值
*p相当于通过地址(p变量的值)找到一块内存;然后操作内存
*p放在等号的左边赋值(给内存赋值)
*p放在等号的右边取值(从内存获取值)
3)指针变量和它指向的内存块是两个不同的概念
//含义1 给p赋值p=0x1111; 只会改变指针变量值,不会改变所指的内容;p = p +1; //p++
//含义2 给*p赋值*p='a'; 不会改变指针变量的值,只会改变所指的内存块的值
//含义3 =左边*p 表示给内存赋值, =右边*p 表示取值含义不同切结!
//含义4 =左边char *p
//含义5 保证所指的内存块能修改
4)指针是一种数据类型,是指它指向的内存空间的数据类型
含义1:指针步长(p++),根据所致内存空间的数据类型来确定
p++=è(unsigned char )p+sizeof(a);
结论:指针的步长,根据所指内存空间类型来定。
注意: 建立指针指向谁,就把把谁的地址赋值给指针。图和代码和二为一。
不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系)。
什么时候子类的步长与父类步长一样: 当子类继承父类后 不添加任何属性和方法 那么就不会出错
//dm04_父类指针和子类指针的步长
#include <iostream>
using namespace std;
//结论:
//多态是用父类指针指向子类对象 和 父类步长++,是两个不同的概念
class Parent
{
public:
Parent(int a = 0)
{
this->a = a;
}
virtual void print()
{
cout << "我是爹" << endl;
}
private:
int a;
};
//成功 ,一次偶然的成功 ,必然的失败更可怕
class Child : public Parent
{
public:
Child(int b = 0) :Parent(0)
{
this->b = b;
}
virtual void print()
{
cout << "我是儿子" << endl;
}
private:
int b; //开始父类与子类 内存大小一样 但是当子类添加一个int b 这两个类的内存大小就不一样
};
void HowToPlay(Parent *base)
{
base->print(); //有多态发生 //2 动手脚
}
void main()
{
Child c1; //定义一个子类对象 ,在这个过程中,在父类构造函数中调用虚函数print 能发生多态吗?
//c1.print();
Parent *pP = NULL;
Child *pC = NULL;
Child array[] = { Child(1), Child(2), Child(3) };
pP = array;//让父类的指针指向子类的对象
pC = array;
pP->print();
pC->print(); //多态发生
//多态时候 用父类指针指向子类对象 和 父类步长++ 是两个不同的概念
//当添加一个int b 后 两个指针的步长不一样
pP++;
pC++;
pC->print(); //多态发生
pP->print();//父类的步长不对劲 pP++ 后的位置不是下一个数组的入口地址位置 编译器就无法根据对象去用vptr指针去寻址要执行的虚函数
//但是子类步长ok pC++后的位置是下一个数组的入口地址位置
pP++;
pC++;
pP->print();
pC->print(); //多态发生
cout << "hello..." << endl;
system("pause");
return;
}
5、纯虚函数和抽象类
5.1基本概念
5.2抽象类案例
//dm05_纯虚函数抽象类语法基础
#include <iostream>
using namespace std;
//面向抽象类编程(面向一套预先定义好的接口编程)
//一个具有纯虚函数的基类称为抽象类
//1.有继承
class Figure//抽象类
{
public:
//阅读一个统一的界面(接口),让子类使用,让子类必须去实现
virtual void getArea() = 0; //纯虚函数 2.多态条件 有虚函数重写
private:
};
class Circle:public Figure
{
public:
Circle(int a)
{
this->a = a;
}
virtual void getArea()
{
cout << "圆形的面积:" << 3.14*a*a << endl;
}
private:
int a;
};
class Tri : public Figure
{
public:
Tri(int a, int b)
{
this->a = a;
this->b = b;
}
virtual void getArea()
{
cout << "三角形的面积: " << a*b / 2 << endl;;
}
private:
int a;
int b;
};
class Square : public Figure
{
public:
Square(int a, int b)
{
this->a = a;
this->b = b;
}
virtual void getArea()
{
cout << "四边形的面积: " << a*b << endl;;
}
private:
int a;
int b;
};
//搭建一个舞台 看对象完整的生命周期
void objplay(Figure *base)
{
base->getArea();//发生多态 3 父类指针(引用)指向子类对象
}
void main()
{
//Figure f;//抽象类不能被实例化
Figure *base = NULL;//抽象类不能被实例化
Circle c1(10);
Tri tr1(10,10);
Square s1(10,10);
//面向抽象类编程(面向一套预先定义好的接口编程)
objplay(&c1);
objplay(&tr1);
objplay(&s1);
cout << "hello..." << endl;
system("pause");
return;
}
5.3抽象类在多继承中的应用
C++中没有Java中的接口概念,抽象类可以模拟Java中的接口类。(接口和协议)
5.3.1有关多继承的说明
工程上的多继承
被实际开发经验抛弃的多继承
工程开发中真正意义上的多继承是几乎不被使用的
多重继承带来的代码复杂性远多于其带来的便利
多重继承对代码维护性上的影响是灾难性的
在设计方法上,任何多继承都可以用单继承代替
多继承中的二义性和多继承不能解决的问题
//dm06_多继承的二义性
#include <iostream>
using namespace std;
class B
{
public:
int b;
protected:
private:
};
class B1 : virtual public B
{
public:
int b1;
protected:
private:
};
class B2 : virtual public B
{
public:
int b2;
protected:
private:
};
class C : public B1, public B2
{
public:
int c;
protected:
private:
};
//多继承暂时解决不了的
class D
{
public:
int b;
private:
};
class E :virtual public B, virtual public D
{
public:
int e;
private:
};
void main()
{
C myc;
myc.c = 10;
//多继承的二义性 error C2385: 对“b”的访问不明确 通过添加virtual能够解决
myc.b = 100;
E e1;
e1.e = 20;
//多继承的二义性 解决不了的问题
e1.b = 10;
cout << "hello..." << endl;
system("pause");
return;
}
5.3.2多继承的应用场景
C++中是否有Java中的接口概念? |
绝大多数面向对象语言都不支持多继承 绝大多数面向对象语言都支持接口的概念 C++中没有接口的概念 C++中可以使用纯虚函数实现接口 接口类中只有函数原型定义,没有任何数据的定义。 class Interface { public: virtual void func1() = 0; virtual void func2(int i) = 0; virtual void func3(int i) = 0; }; |
实际工程经验证明 多重继承接口不会带来二义性和复杂性等问题 多重继承可以通过精心设计用单继承和接口来代替 接口类只是一个功能说明,而不是功能实现。 子类需要根据功能说明定义功能实现。 |
#include "iostream" using namespace std;
/* C++中没有接口的概念 C++中可以使用纯虚函数实现接口 接口类中只有函数原型定义,没有任何数据的定义。 */
class Interface1 { public: virtual void print() = 0; virtual int add(int a, int b) = 0; };
class Interface2 { public: virtual void print() = 0; virtual int add(int a, int b) = 0; virtual int minus(int a, int b) = 0; };
class parent { public: int a; }; class Child : public parent, public Interface1, public Interface2 { public: void print() { cout<<"Child::print"<<endl; }
int add(int a, int b) { return a + b; }
int minus(int a, int b) { return a - b; } };
int main() { Child c;
c.print();
cout<<c.add(3, 5)<<endl; cout<<c.minus(4, 6)<<endl;
Interface1* i1 = &c; Interface2* i2 = &c;
cout<<i1->add(7, 8)<<endl; cout<<i2->add(7, 8)<<endl; system("pause"); } |
//dm07_抽象类在多继承中的应用
#include <iostream>
using namespace std;
class Interface1
{
public:
virtual int add(int a,int b) = 0;
virtual void print() = 0;
private:
};
class Interface2
{
public:
virtual int mult(int a, int b) = 0;
virtual void print() = 0;
};
class Parent
{
public:
int getA()
{
a = 0;
return a;
}
private:
int a;
};
class Child:public Interface1,public Interface2,public Parent
{
public:
virtual int add(int a, int b)
{
cout << "Child:add" << endl;
return a+b;
}
virtual void print()
{
cout << "Child:print" << endl;
}
virtual int mult(int a, int b)
{
cout << "Child:mult" << endl;
return a * b;
}
private:
};
void main()
{
Child c1;
c1.print();
Interface1 *it1 = &c1;
it1->add(1, 2);
Interface2 *it2 = &c1;
it2->mult(3, 6);
cout << "hello..." << endl;
system("pause");
return;
}
5.4抽象类知识点强化
/*
编写一个C++程序, 计算程序员( programmer )工资
1要求能计算出初级程序员( junior_programmer ) 中级程序员 ( mid_programmer )高级程序员( adv_programmer)的工资
2要求利用抽象类统一界面,方便程序的扩展, 比如:新增, 计算架构师 (architect ) 的工资
*/
//dm08_面向抽象类编程计算程序员工资
#include <iostream>
using namespace std;
/*
编写一个C++程序, 计算程序员( programmer )工资
1 要求能计算出初级程序员( junior_programmer ) 中级程序员 ( mid_programmer )高级程序员( adv_programmer)的工资
2 要求利用抽象类统一界面,方便程序的扩展, 比如:新增, 计算 架构师 (architect ) 的工资
*/
class Programmer
{
public:
virtual void getSal() = 0;
private:
};
class junior_programmer:public Programmer
{
public:
junior_programmer(char *name, char *job, int sal)
{
this->name = name;
this->job = job;
this->sal = sal;
}
virtual void getSal()
{
cout << name << " " << job << " : " << sal << endl;
}
private:
char *name;
char *job;
int sal;
};
class mid_programmer :public Programmer
{
public:
mid_programmer(char *name, char *job, int sal)
{
this->name = name;
this->job = job;
this->sal = sal;
}
virtual void getSal()
{
cout << name << " " << job << " : " << sal << endl;
}
private:
char *name;
char *job;
int sal;
};
//1多态的条件 要有继承
class adv_programmer :public Programmer
{
public:
adv_programmer(char *name, char *job, int sal)
{
this->name = name;
this->job = job;
this->sal = sal;
}
virtual void getSal()//2多态的条件 要有虚函数重写
{
cout << name << " " << job << " : " << sal << endl;
}
private:
char *name;
char *job;
int sal;
};
//3.父类指针(引用)指向子类对象 多态的条件
void CalProSal(Programmer *base)
{
base->getSal();
}
//系统扩展
class architect :public Programmer
{
public:
architect(char *name, char *job, int sal)
{
this->name = name;
this->job = job;
this->sal = sal;
}
virtual void getSal()//2多态的条件 要有虚函数重写
{
cout << name << " " << job << " : " << sal << endl;
}
private:
char *name;
char *job;
int sal;
};
void main()
{
junior_programmer jp("小王", "初级", 4000);
mid_programmer mp("小张", "中级", 8600);
adv_programmer ap("小李", "高级", 15000);
CalProSal(&jp);
CalProSal(&mp);
CalProSal(&ap);
//系统扩展
architect ar("传智高水平学员", "架构师", 24000);
CalProSal(&ar);
cout << "hello..." << endl;
system("pause");
return;
}