概述
以下内容为大学期间学习C++语言总结的知识:
《C++》基础入门_01——数据存储,表示形式,基本运算
《C++》基础入门_02——面向对象的总体概括
《C++》基础入门_03——程序的开发过程
《C++》基础入门_04——四大语句
《C++》基础入门_05——函数详解篇
《C++》基础入门_06——面向对象的详述
《C++》基础入门_07——数据的共享保护:const
《C++》基础入门_08——利用数组实现对批量数据的处理
《C++》基础入门_09——指针和引用的讲解
《C++》基础入门_10——用户自定义数据类型详细篇
《C++》基础入门_11——友元的讲解
《C++》基础入门_12——类模板
《C++》基础入门_13——运算符的重载
《C++》基础入门_14——继承与派生
《C++》基础入门_15——多态性
《C++》基础入门_16——输入输出流详讲
《C++》基础入门_17——对象串行化
《C++》基础入门_18——异常处理
《C++》基础入门_19——命名空间
《C++》基础入门_20——文件操作
《C++》基础入门_21——在函数中返回数组的常用方法
文章目录
- 一、多态性
- 1.1 概念
- 1.2 从系统的实现分类
- 1.2.1 静态多态性
- 1.2.2 动态多态性
- 1. 虚函数
- 2. 虚函数的使用
- 二、静态关联与动态关联
- 2.1 静态关联
- 2.2 动态关联
- 2.3 虚函数动态绑定的实现原理
- 三、虚析构函数
- 四、纯虚函数与抽象类
- 4.1 纯虚函数
- 4.2 抽象类(抽象基类)
- 五、多态类型与非多态类型
- 六、运行时类型识别
- 6.1 运行时类型识别的方式
- 6.1.1 用dynamic_cast做类型转换
- 6.1.2 用typeid直接获取类型信息
一、多态性
1.1 概念
- 多态性是面向对象程序设计的一个重要特征。若一种语言只支持类,而不支持多态,则不能称为面向对象语言的,只能说是基于对象的。
多态,即一种事物对种形态。 - 面向对象方法中对多态的表示:向不同对象发送同一个消息,不同的对象在接受时产生的行为不同。即每个对象有自己的方法去响应共同的消息。
- C++中多态性的表现形式之一:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。
- 多态性是一个接口,多种方法。
1.2 从系统的实现分类
1.2.1 静态多态性
- 特点:在程序编译过程时系统决定调用哪个函数,因此静态多态性又称为编译时的多态性。
- 实现方式:通过函数重载实现的。包括函数重载和运算符重载。
- 优缺点:函数调用速度快,效率高,但缺乏灵活性。
#include <iostream>
#include <string>
using namespace std;
//声明类point
class Point {
public:
Point(int a=0,int b=0):x(a),y(b) {}
void setPoint(int,int);
int getX() const { return x; }//常成员函数:为了保证不修改数据成员的值,只能引用
int getY() const { return y; }
friend ostream &operator << (ostream &, const Point &);
protected:
int x, y;
};
void Point::setPoint(int a,int b) {
x = a;
y = b;
}
//重载流运算符
ostream & operator <<(ostream & output, const Point & p) {
output << "[" << p.x << "," << p.y << "]" << endl;
return output;
}
//声明point类的公有派生类Circle类:表示对一个圆的抽象
class Circle :public Point {
public:
Circle(int a = 0, int b = 0, int c = 0):Point(a,b) {//給基类传参只能用初始化器的形式
r = c;
}
void setR(int);
int getR() const { return r; }
double area() const { return 3.14*r*r; } //获得圆的表面积
friend ostream & operator <<(ostream &,const Circle &);
protected:
int r;
};
void Circle::setR(int a) {
r= a;
}
ostream &operator <<(ostream &output, const Circle &c) {
output << "圆心:[" << c.x << "," << c.y << "],半径:r=" << c.r << endl;
return output;
}
//声明Circle的公有派生类cylinder类,代表一个圆柱的抽象
class Cylinder :public Circle {
public :
Cylinder(int a = 0, int b = 0, int c = 0, int h = 0):Circle(a,b,c) {
height = h;
}
void setH(int h) { height = h; };
int getH() { return height; }
double area()const { //获得圆柱的表面积
return 2 * Circle::area()+2*3.14*r*height;
}
double volume() const { //获得圆柱的体积
return Circle::area()*height;
}
friend ostream & operator <<(ostream &, const Cylinder &);
private:
int height;
};
ostream & operator <<(ostream &output, const Cylinder &c) {
output << "圆心:[" << c.x << "," << c.y << "],半径:r=" << c.r << ",高度: h=" << c.height << endl;
return output;
}
void main() {
Circle c(2, 2, 1);
cout << "Circle message:" << endl;
cout << c;
cout << "圆面积: s=" << c.area() << endl;
c.setPoint(1,1);
c.setR(2);
cout << endl;
cout << "Circle message:" << endl;
cout << c;
cout << "圆面积: s=" << c.area() << endl;
Point &p = c;//派生类对象可以代替基类对象向基类对象的引用初始化或赋值。p不能认为是c的别名,它只是c中基类一部分的别名,得到了c的起始地址,与c中基类部分共享同一段存储单元。
cout << endl;
cout<< "Point message:" << endl;
cout << p;
Cylinder cy(3, 3, 3, 1);
cout << endl;
cout << "Cylinder message:" << endl;
cout << cy;
cout << "圆柱表面积:" << cy.area() << endl;
cout << "圆柱的体积:" << cy.volume() << endl;
cy.setPoint(4, 4);
cy.setR(4);
cy.setH(2);
Point &p1 = cy;
cout << endl;
cout << "Point message:" << endl;
cout << p1 << endl;
Circle &p2 = cy;
cout << "Circle message:" << endl;
cout << p2<<endl;
}
结果:
1.2.2 动态多态性
- 特点:不在编译时确定调用哪个函数,而是程序运行过程中才动态确定操作所针对的对象。它又称为运行时的多态。
- 实现方式:虚函数实现
1. 虚函数
- 人们提出能否用一个调用形式来调用基类和派生类同名函数。在程序中不是通过不同对象的名字去访问不同派生类中的同名函数,而是通过指针调用他们,只需要在调用前临时给指针变量赋予不同的值即可。
- 虚函数就是在在基类声明函数是虚拟的,并不是实际存在的函数,然后在派生类中才正式定义此函数。
- 作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
- 虚函数是动态绑定的基础。
- 是非静态的成员函数。
- 在类的声明中,在函数原型之前写virtual。
- virtual 只用来说明类声明中的原型,不能用在函数实现时。
- 具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。
- 本质:不是重载声明而是覆盖。
- 调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定调用哪个函数
2. 虚函数的使用
- 在基类中用virtual声明成员函数为虚函数。
- 在派生类中重新定义此函数,函数名,函数类型,函数参数个数和类型都必须和基类的虚函数相同。
- 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
- 通过指针调用虚函数,此时调用的是指针变量指向对象的同名函数。
-
不使用虚函数:
#include <iostream> #include <string> using namespace std; //声明类point class Student { public: Student(int, string, int); void display(); protected: int sno; string name; int age; }; Student::Student(int s, string n, int a) { sno = s; name = n; age = a; } void Student::display() { cout << "student :: massage:" << endl; cout << "sno: " <<sno<< endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << endl; } class Student1 :public Student { public: Student1(int, string, int, char); void display(); private: char sex; }; Student1::Student1(int s, string n, int a, char se):Student(s,n,a) { sex = se; } void Student1::display() { cout << "student1 :: massage:" << endl; cout << "sno: " << sno << endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << "sex: " << sex << endl; cout << endl; } int main() { Student s1(111, "Tom", 18); Student1 s2(222, "Rain", 21,'f'); Student *p = &s1; p->display(); p = &s2; p->display(); return 0; }
不使用虚函数时的输出信息:
基类指针调用的是基类的display方法
-
使用虚函数
#include <iostream> #include <string> using namespace std; //声明类point class Student { public: Student(int, string, int); virtual void display(); protected: int sno; string name; int age; }; Student::Student(int s, string n, int a) { sno = s; name = n; age = a; } void Student::display() { cout << "student :: massage:" << endl; cout << "sno: " <<sno<< endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << endl; } class Student1 :public Student { public: Student1(int, string, int, char); void display(); private: char sex; }; Student1::Student1(int s, string n, int a, char se):Student(s,n,a) { sex = se; } void Student1::display() { cout << "student1 :: massage:" << endl; cout << "sno: " << sno << endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << "sex: " << sex << endl; cout << endl; } int main() { Student s1(111, "Tom", 18); Student1 s2(222, "Rain", 21,'f'); Student *p = &s1; p->display(); p = &s2; p->display(); return 0; }
使用虚函数后的输出信息:
基类指针调用的是子对象的display方法
二、静态关联与动态关联
确定调用的具体对象的过程称为关联。
关联是把一个标识符和一个存储地址联系起来。
2.1 静态关联
- 函数重载和通过对象名调用的虚函数,在程序编译过程时就可以确定调用的虚函数属于哪个类,此过程称为静态关联。
- 运行前进行关联的,又称早期关联。
2.2 动态关联
- 不能在编译时确定调用哪个函数,而是程序运行过程中才把类和虚函数绑定在一起的,它称为动态关联。
- 在编译以后的运行阶段进行的,又称为滞后关联。
2.3 虚函数动态绑定的实现原理
- 动态选择被执行的函数
- 函数的调用,需要通过函数代码的入口地址
- 把函数入口地址作为变量,在不同情况下赋予不同的值,
通过该变量调用函数,就可动态选择被执行的函数
- 虚表
- 每个多态类有一个虚表(virtual table)
- 虚表中有当前类的各个虚函数的入口地址
- 每个对象有一个指向当前类的虚表的指针(虚指针vptr)
- 动态绑定的实现
- 构造函数中为对象的虚指针赋值
- 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
- 通过该入口地址调用虚函数
三、虚析构函数
-
当派生类的对象从内存中撤销时一般先调用派生类的析构函数,在调用基类的析构函数。
-
构造函数不能是虚函数,析构函数可以是虚函数
-
如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一种情况:系统只会执行基类的析构函数,而不会执行派生类的析构函数。
#include <iostream> #include <string> using namespace std; //声明类point class Student { public: Student(int, string, int); ~Student() { cout << " ~Student()"<<endl; } virtual void display(); protected: int sno; string name; int age; }; Student::Student(int s, string n, int a) { sno = s; name = n; age = a; } void Student::display() { cout << "student :: massage:" << endl; cout << "sno: " <<sno<< endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << endl; } class Student1 :public Student { public: Student1(int, string, int, char); ~Student1() { cout << " ~Student1()" << endl; } void display(); private: char sex; }; Student1::Student1(int s, string n, int a, char se):Student(s,n,a) { sex = se; } void Student1::display() { cout << "student1 :: massage:" << endl; cout << "sno: " << sno << endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << "sex: " << sex << endl; cout << endl; } int main() { Student *p = new Student1(111,"yj",18,'f'); delete p; return 0; /* 执行结果: ~Student() */ }
-
为什么需要虚析构函数?
- 可能通过基类指针删除派生类对象;
- 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
-
使用方法:把析构函数声明为虚析构函数
#include <iostream> #include <string> using namespace std; //声明类point class Student { public: Student(int, string, int); virtual ~Student() { cout << " ~Student()"<<endl; } virtual void display(); protected: int sno; string name; int age; }; Student::Student(int s, string n, int a) { sno = s; name = n; age = a; } void Student::display() { cout << "student :: massage:" << endl; cout << "sno: " <<sno<< endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << endl; } class Student1 :public Student { public: Student1(int, string, int, char); ~Student1() { cout << " ~Student1()" << endl; } void display(); private: char sex; }; Student1::Student1(int s, string n, int a, char se):Student(s,n,a) { sex = se; } void Student1::display() { cout << "student1 :: massage:" << endl; cout << "sno: " << sno << endl; cout << "sname: " << name << endl; cout << "age: " << age << endl; cout << "sex: " << sex << endl; cout << endl; } int main() { Student *p = new Student1(111,"yj",18,'f'); delete p; return 0; /* 执行结果: ~Student1() ~Student() */ }
-
最好把所有的基类析构函数声明为虚函数。
四、纯虚函数与抽象类
4.1 纯虚函数
- 纯虚函数是在声明虚函数时被初始化为“0”的函数。
- 纯虚函数的一般形式:
virtual 函数类型 函数名 (参数表 )=0 ;- 纯虚函数无函数体;
- 最后面的0并不代表返回值为0,他只是形式上的作用,告诉编译系统这是纯虚函数。
- 这是一个声明语句,要有“;”分号。
- 纯虚函数只是函数名而不具备函数功能,不能被调用。
- 纯虚函数作用:在基类中为其派生类保留一个函数名,以便派生类需要时进行定义。
4.2 抽象类(抽象基类)
- 定义一些类不用于生成对象,而是作为基类去建立派生类,它为一个类族提供公共接口。
- 带有纯虚函数的类称为抽象类。
- 因为纯虚函数不能被调用,所以抽象类不能建立对象,但是可以定义指向抽象类数据的指针变量。此指针指向抽象类的具体类。
- 抽象类的派生类若对基类的所有纯虚函数都进行了定义,则派生类称为了具体类,不在是抽象类,可以建立对象。若没有对所有纯虚函数进行定义,则派生类任然是抽象类,不能建立对象。
- 作用:
- 抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
- 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
#include <iostream>
#include <string>
using namespace std;
class Base1 { // 基类 Base1定义
public:
virtual void display() const = 0; // 纯虚函数
};
class Base2:public Base1 { // 公有派生类 Base2 定义
public:
void display() const { // 覆盖基类的虚函数
cout << "Base2::display()" << endl;
}
};
class Derived : public Base2 { // 公有派生类 Derived 定义
public:
void display() const {// 覆盖基类的虚函数
cout << "Derived :: display()" << endl;
}
};
void fun(Base1 * ptr) {
ptr -> display() ; // 对象指针-> 成员名
}
int main() {
Base2 b2; // 定义Base2类对象
Derived d; // 定义Derived 类对象
fun(&b2 ); // 用Base2对象的指针调用fun函数
fun(&d); // 用 Derived 对象的指针调用 fun 函数
return 0;
/*
Base2::display()
Derived :: display()
*/
}
五、多态类型与非多态类型
- 有虚函数的类类型称为多态类型,其它类型皆为非多态类型。
- 二者的差异
- 语言层面的差异
多态类型支持运行时类型识别
多态类型对象占用额外的空间 - 设计原则上的差异
• 多态类型
多态类型的析构函数一般应为虚函数
• 非多态类型- 非多态类型不宜作为公共基类
• 由于没有利用动态多态性,一般可以用组合,而无需用共有继承;
• 如果继承,则由于析构函数不是虚函数,删除对象时所执行操作与指针类型有关,易引起混乱。 - 把不需被继承的类型设定为非多态类型
• 由于成员函数都是静态绑定,调用速度较快;
• 对象占用空间较小。
- 非多态类型不宜作为公共基类
- 语言层面的差异
六、运行时类型识别
- 运行时类型识别
- 允许在运行时通过基类指针(或引用)辨别对象所属的具体派生类;
- 只对多态类型适用;
- 比虚函数动态绑定的开销更大,因此应仅对虚函数无法解决的问题使用。
6.1 运行时类型识别的方式
6.1.1 用dynamic_cast做类型转换
-
语法形式
dynamic_cast<目的类型>(表达式)
-
功能
- 将基类指针转换为派生类指针,将基类引用转换为派生类引用;
- 转换是有条件的
- 如果指针(或引用)所指对象的实际类型与转换的目的类型兼容,则转换成功进行;
- 否则如执行的是指针类型的转换,则得到空指针;
- 如果执行的是引用类型的转换,则抛出异常。
#include <iostream> #include <string> using namespace std; class Base{ public: virtual void fun1(){ cout << "Base::fun1()" << endl; } virtual ~Base() { } }; class Derived1 : public Base { public: virtual void fun1() { cout << "Derived1::fun1()" << endl; } virtual void fun2() { cout << "Derived1::fun2()" << endl; } }; class Derived2 : public Derived1 { public: virtual void fun1() { cout << "Derived2::fun1()" << endl; } virtual void fun2() { cout << "Derived2::fun2()" << endl; } }; void fun(Base *b) { b->fun1(); //尝试将b转换为Derived1指针 Derived1 *d = dynamic_cast<Derived1 *>(b); //判断转换是否成功 if (d != 0) { cout << "将Base转换为Derived1指针成功。"<<endl; d->fun2(); } else { cout << "将Base转换为Derived1指针失败!" << endl; } } int main() { Base b; cout << "传入Base的对象:" << endl; fun(&b); cout << endl; Derived1 d1; cout << "传入Derived1的对象:" << endl; fun(&d1); cout<< endl; Derived2 d2; cout << "传入Derived2的对象:" << endl; fun(&d2); return 0; }
结果:
6.1.2 用typeid直接获取类型信息
- 语法形式
typeid ( 表达式 )
typeid ( 类型说明符 )
- 功能
- 获得表达式或类型说明符的类型信息
- 表达式有多态类型时,会被求值,并得到动态类型信息;
- 否则,表达式不被求值,只能得到静态的类型信息。
- 类型信息用type_info对象表示
- type_info是typeinfo头文件中声明的类;
- typeid的结果是type_info类型的常引用;
- 可以用type_info的重载的“==”、“!=”操作符比较两类型的异同;
- type_info的name成员函数返回类型名称,类型为const char *。
- 获得表达式或类型说明符的类型信息
#include <iostream>
#include <string>
using namespace std;
class Base{
public:
virtual ~Base() { }
};
class Derived : public Base {
};
void fun(Base *b) {
//得到表示b和*b类型信息的对象
const type_info &info1 = typeid(b);
const type_info &info2 = typeid(*b);
cout << "typeid(b): " << info1.name() << endl;
cout << "typeid(*b): " << info2.name() << endl;
if (info2 == typeid(Base)) //判断*b是否为Base类型
cout << "A base class!" << endl;
}
int main() {
Base b;
fun(&b);
Derived d;
fun(&d);
return 0;
/*
typeid(b): class Base *
typeid(*b): class Base
A base class!
typeid(b): class Base *
typeid(*b): class Derived
*/
}
最后
以上就是心灵美身影为你收集整理的《C++》基础入门_15——多态性的全部内容,希望文章能够帮你解决《C++》基础入门_15——多态性所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复