赞
踩
系统学习C++,本章将记录类继承、类多态的相关概念
方便自己日后复习,错误的地方希望积极指正
往期文章:
C++基础从0到1入门编程(一)
C++基础从0到1入门编程(二)
C++基础从0到1入门编程(三)
C++基础从0到1入门编程(四)
C++基础从0到1入门编程(五)
参考视频:
1.黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难
2.系统化学习C++
继承:一个类从另一个类获取成员变量和成员函数的过程
语法:
class 派生类名: [继承方式] 基类名
{
派生类新增加的成员
}
被继承的类称为基类或父类
;继承的类称为派生类或子类
使用继承的场景
(1)新创建的类和现有的类相似,只是多出若干成员变量或成员函数,可以使用继承
#include <iostream> using namespace std; class CallComers { public: string name_; string tel_; CallComers() { name_ = "Big"; tel_ = "123"; } void sing() { cout << "I am a bird\n"; } void setname(const string& name) { name_ = name; } void settel(const string& tel) { tel_ = tel; } }; class CGirl : public CallComers { public: int bh_; CGirl() { bh_ = 8; } void show() { cout << bh_ << ' ' << name_ << ' ' << tel_ << endl; } }; int main() { CGirl g; g.name_ = "Small"; g.show(); // 8 Small 123 }
(2)当需要创建多个类时,如果拥有很多相似的成员变量或成员函数,可以将这些类提取出来,定义为基类,然后从基类继承
class Sort
{
int data[30];
void print()
};
class BubbleSort :public Sort
{
void sort();
};
class ShellSort :public Sort
{
void sort();
};
类成员访问权限:public - > protected - > private
public成员在类外可以访问,private成员只能在类的成员函数中访问
不考虑继承关系,protected成员和private成员一样,类外不能访问。当存在继承关系,基类的protected成员可以在派生类中访问,而基类的private成员不能在派生类中访问
继承方式:public、protected、private。如果不写,默认为private
(1)基类成员在派生类中的访问权限不得高于继承方式中指定的权限。继承方式中的public、protected、private是用来指明基类成员在派生类中的最高访问权限的
(2)不管继承方式如何,基类中的private成员在派生类中始终不能使用,不能在派生类的成员函数中访问或调用
(3)如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为public 或protected;只有那些不希望在派生类中使用的成员才声明为private
(4)如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected
由于private和protected继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,在实际开发中,一般使用public
(5)在派生类中,可以通过基类的公有成员函数间接访问基类的私有成员
(6)使用 using 关键字可以改变基类成员在派生类中的访问权限
using只能改变基类中public和protected成员的访问权限,不能改变private成员的访问权限,因为基类中的private成员在派生类中是不可见的,根本不能使用
#include <iostream> using namespace std; class A { public: int a_ = 10; protected: int b_ = 20; private: int c_ = 30; }; class B :public A { public: using A::b_; // using A::c_; // 报错 private: using A::a_; }; int main() { B b; // b.a_ = 10; b.b_ = 21; }
如果手工调用派生类的析构函数,也会调用基类的析构函数
memset(p, 0, sizeof(B));
*((int*)p + 2) = 123;
#include <iostream> #include <cstring> using namespace std; void* operator new(size_t size) { void* ptr = malloc(size); cout << ptr << ' ' << size << endl; return ptr; } void operator delete(void* ptr) { if (ptr == 0) return; free(ptr); cout << "Free\n"; } class A { public: int a_ = 10; protected: int b_ = 20; private: int c_ = 30; public: A() { cout << this << endl; cout << &a_ << endl; cout << &b_ << endl; cout << &c_ << endl; } void func() { cout << a_ <<' ' << b_ << ' ' << c_ << endl; } }; class B:public A { public: int d_ = 40; B() { cout << this << endl; cout << &a_ << endl; cout << &b_ << endl; cout << &d_ << endl; } void func1() { cout << d_ << endl; } }; int main() { cout << sizeof(A) << endl; // 12 cout << sizeof(B) << endl; // 16 B* p = new B; p->func(); p->func1(); // memset(p, 0, sizeof(B)); // p->func(); p->func1(); *((int*)p + 2) = 123; p->func(); p->func1(); delete p; }
派生类构造函数的要点:
(1)创建派生类对象,程序先调用基类构造函数,然后再调用派生类构造函数
(2)没有指定基类构造函数,将使用基类的默认构造函数
(3)可以用初始化列表指明要使用的基类构造函数
(4)基类构造函数负责初始化被继承的数据成员;派生类构造函数用于初始化新增的数据成员
(5)派生类的构造函数总是调用一个基类构造函数,包括拷贝构造函数
#include <iostream> using namespace std; class A { public: int a_; private: int b_; public: A() : a_(0), b_(0) { cout << "Base class MorenGouzao\n"; } A(int a, int b) : a_(a), b_(b) { cout << "Base class Gouzao\n"; } A(const A &a) : a_(a.a_), b_(a.b_) { cout << "Base class KaoBeiGouzao\n"; } void showA() { cout << a_ << ' ' << b_ << endl; } }; class B:public A { public: int c_; B() : c_(0), A() { cout << "Moren GouzaoB()\n"; } B(int a,int b, int c) : A(a,b), c_(c) { cout << "B(int a,int b, int c)\n"; } B(const A& a, int c) : A(a), c_(c) { cout << "B(const A& a, int c)\n"; } void showB() { cout << c_ << endl; } }; int main() { B b1; b1.showA(); // 0 0 b1.showB(); // 0 B b2(1, 2, 3); b2.showA(); // 1 2 b2.showB(); // 3 A a(10, 20); B b3(a, 20); b3.showA(); // 10 20 b3.showB(); // 20 }
如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,通过派生类对象或者在派生类的成员函数中使用该成员时,将使用派生类新增的成员,而不是基类的
#include <iostream> using namespace std; class A { public: int a_ = 30; void func() { cout << "A func()\n"; } }; class B:public A { public: int a_ = 80; void func() { cout << "B func()\n"; } }; int main() { B b; cout << b.a_ << endl; // 80 b.func(); // B func() }
Tip:基类的成员函数和派生类的成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数(因为作用域)
在成员名前面加类名和域解析符可以访问对象的成员
如果不存在继承关系,类名和域解析符可以省略不写
#include <iostream> using namespace std; class A { public: int a_ = 30; void func() { cout << "A func()\n"; } void func(int a) { cout << "A func(int a)\n"; } }; class B:public A { public: int a_ = 80; //void func() { cout << "B func()\n"; } }; int main() { B b; cout << b.a_ << endl; // 80 b.func(); // B func() b.func(1); // 把派生类的func()注释掉可以运行 }
当存在继承关系时,基类的作用域嵌套在派生类的作用域中。如果成员在派生类的作用域中已经找到,就不会在基类作用域中继续查找;如果没有找到,则继续在基类作用域中查找
在成员的前面加上类名和域解析符,就可以直接使用该作用域的成员
#include <iostream> using namespace std; class A { public: int a_ = 10; void func() { cout << "A func()\n"; } }; class B : public A { public: int a_ = 20; void func() { cout << "B func()\n"; } }; class C : public B { public: int a_ = 30; void func() { cout << "C func()\n"; } void show() { cout << C::a_ << ' ' << B::a_ << ' ' << B::A::a_ << endl; } }; int main() { C c; cout << c.C::a_ << endl; // 30 cout << c.B::a_ << endl; // 20 cout << c.B::A::a_ << endl;// 10 c.C::func(); // C func() c.B::func(); // B func() c.B::A::func();// A func() c.show(); // 30 20 10 }
派生类和基类之间的特殊关系
(1)如果继承方式是公有的,派生类对象可以使用基类成员
(2)派生类对象赋值给基类对象(包括私有成员),会舍弃非基类的成员
(3)基类指针可以在不显示转换的情况下指向派生类对象
(4)基类引用可以在不显示转换的情况下指向派生类对象
Tip:
(1)基类指针或引用只能调用基类的方法,不能调用派生类的方法
(2)用派生类构造基类
(3)如果函数形参是基类,实参可以用派生类
(4)C++要求指针和引用类型与赋给的类型匹配,这一规则对继承来说是例外。但是,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针(没有价值)
#include <iostream> using namespace std; class A { public: int a_ = 0; private: int b_ = 0; public: void show() { cout << a_ << ' ' << b_ << endl; } void setb(int b) { b_ = b; } }; class B:public A { public: int c_ = 0; void show() { cout << a_ << ' ' << c_ << endl; } }; int main() { B b; A* a = &b; b.a_ = 10; b.setb(20); b.c_ = 30; b.show(); // 10 30 a->a_ = 11; a->setb(12); a->show(); // 11 12 }
多继承语法:
class B : public A, public C
{
int a_ = 10;
}
菱形继承:
虚继承可以解决菱形继承的二义性
、数据冗余
问题
有了多继承,就存在菱形继承,有了菱形继承就有虚继承,变得更复杂
不提倡使用多继承,只有在比较简单和不出现二义性的情况下才使用多继承,能用单一继承解决的问题就不要用多继承
多继承:
#include <iostream> using namespace std; class A1 { public: int a_ = 10; }; class A2 { public: int a_ = 20; }; class B :public A1, public A2 { public: int a_ = 30; }; int main() { B b; cout << b.a_ << endl; // 30 cout << b.A1::a_ << endl;// 10 cout << b.A2::a_ << endl;// 20 }
菱形继承:
#include <iostream> using namespace std; class A { public: int a_ = 10; }; class B : virtual public A {}; class C : virtual public A {}; class DD : public B, public C {}; int main() { DD d; d.a_ = 20; cout << d.a_ << endl; // 20 cout << &d.B::a_ << ' ' << &d.C::a_ << endl; // 0x47d1ffc90 0x47d1ffc90 }
基类指针只能调用基类的成员函数,不能调用派生类的成员函数。
如果在基类的成员函数前加virtual关键字,把它声名为虚函数,基类指针就可以调用派生类中同名的成员函数,通过派生类中的成员函数,就可以访问派生对象的成员变量
有了虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据,基类指针表现出了多种形式,这种现象称为多态
基类引用也可以使用多态
Tip:
(1)只需要在函数声名的时候加上virtual关键字,函数定义时不能加
(2)派生类中重定义虚函数,函数特征要相同
(3)基类中定义了虚函数时,如果派生类没有重定义该函数,那么将使用基类的虚函数
(4)在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和域解析符
(5)如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数
有两方面好处:(1)普通函数效率更高(2)指出不要重新定义该函数
#include <iostream> using namespace std; class CAllComers{ public: int bh_ = 0; virtual void show() { cout << "CAllComers::show() " << bh_ << endl; } virtual void show(int a) { cout << "CAllComers::show(int a) " << bh_ << endl; } }; class CGirl : public CAllComers { public: int age_ = 0; void show() { cout << "CGirl::show() " << bh_ << ' ' << age_ << endl; } void show(int a) { cout << "CGirl::show(int a) " << bh_ << ' ' << age_ << endl; } }; int main() { CAllComers a; a.bh_ = 3; // 创建基类对象并对成员赋值 CGirl g; g.bh_ = 8; g.age_ = 23;// 创建派生类对象并对成员赋值 CAllComers* p;// 声明基类指针 // p = &a; // p->show(); //CAllComers::show() 3 // 让基类指针指向基类对象,并调用虚函数 // p->show(5); p = &g; p->show(); // CGirl::show() 8 23 // 让基类指针指向派生类对象,并调用虚函数 p->show(5); p->CAllComers::show(5); }
(1)基类的虚函数实现基本功能
(2)派生类重新定义虚函数,扩展功能、提升功能
(3)实现个性化功能
#include <iostream> using namespace std; class Hero { public: int viability; int attack; virtual void skill1() { cout << "One skill1" << endl; } virtual void skill2() { cout << "Two skill2" << endl; } virtual void uskill() { cout << "uskill" << endl; } }; class XS : public Hero { public: void skill1() { cout << "XS skill1" << endl; } void skill2() { cout << "XS skill2" << endl; } void uskill() { cout << "XS uskill" << endl; } }; class HX : public Hero { public: void skill1() { cout << "HX skill1" << endl; } void skill2() { cout << "HX skill2" << endl; } void uskill() { cout << "HX uskill" << endl; } }; int main() { int id = 0; cout << "Please input hero: " << endl; cin >> id; // 创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数 Hero* ptr = nullptr; if (id == 1) { ptr = new XS; } else if (id == 2) { ptr = new HX; } if (ptr != nullptr) { ptr->skill1(); ptr->skill2(); ptr->uskill(); delete ptr; } }
类的普通成员函数地址是静态的,在编译阶段指定
(1)如果基类中有虚函数,对象的内存模型中有一个虚函数表,表中存放了基类的函数名和地址
(2)如果派生类中重定义了基类的虚函数,创建派生对象时,将用派生类的函数取代虚函数表中基类的函数
C++中的多态分为两种:静态多态与动态多态
静态多态:编译时的多态,在编译时期就已经确定要执行了的函数地址了;主要有函数重载和函数模板
动态多态:动态绑定,在运行时才去确定对象类型和正确选择需要调用的函数,一般用于解决基类指针或引用派生类对象调用类中重写的方法(函数)时出现的问题
调用普通成员函数的效率比调用虚函数的效率更高,所以如果不考虑多态,不要把普通成员函数设置为虚函数
(1)构造函数不能继承,创建派生类对象,先执行基类构造函数,再执行派生类构造函数
(2)析构函数不能继承,销毁派生类对象时,先执行派生类析构函数,再执行基类析构函数
(3)派生类析构函数执行完,会自动调用基类的析构函数
(4)如果手工的调用派生类的析构函数,也会自动调用基类函数
析构派生类
:
1.析构派生类对象时,会自动调用基类的析构函数。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉
2.析构函数可以手工调用,如果对象中有堆内存
delete ptr;
ptr = nullptr;
3.用基类指针指向派生类对象时,delete基类指针调用的时基类的析构函数,不是派生类的,如果希望调用派生类的析构函数,就要把基类的析构函数设置为虚函数
4.C++编译器对虚析构函数做了特别的处理
5.对于基类,即使它不需要析构函数,也应该提供一个空虚析构函数,不然析构派生类对象不执行
6.赋值运算符函数不能继承,派生类继承的函数的特征标与基类完全相同,但赋值运算符函数的特征标随类而异,它包含了一个类型为其所属类的形参
7.友元函数不是类成员,不能继承
#include <iostream> using namespace std; class AA { public: AA() { cout << "Base Gouzao\n"; } virtual void func() { cout << "Base func()\n"; } virtual ~AA() { cout << "Base Xigou\n"; } }; class BB : public AA { public: BB() { cout << "Pai Gouzao\n"; } void func() { cout << "Pai func()\n"; } ~BB() { cout << "Pai Xigou\n"; } }; int main() { AA *a = new BB; a->func(); delete a; }
纯虚函数是一种特殊的虚函数,在某些情况,基类中不能对虚函数给出有意义的实现,把它声名为纯虚函数
语法:virtual 返回值类型 函数名(参数列表) = 0
纯虚函数在基类中为派生类保留一个函数的名字,以便派生类进行重定义,如果在基类中没有保留函数名字,则无法支持多态性
含有纯虚函数的类被称为抽象类,
不能实例化对象
,可以创建指针和引用
派生类必须重定义抽象类中的纯虚函数,否则也属于抽象类(不能实例化对象)
基类的纯虚析构函数也需要实现,为啥声名析构函数为纯虚析构函数?
有时候,想使一个类成为抽象类,刚好没有任何纯虚析构函数,在想要成为抽象类中声名一个纯虚析构函数
#include <iostream> using namespace std; class AA { public: AA() { cout << "Base Gouzao\n"; } virtual void func() = 0;// { cout << "Base func()\n"; } virtual ~AA() { cout << "Base Xigou\n"; } }; class BB : public AA { public: BB() { cout << "Pai Gouzao\n"; } void func() { cout << "Pai func()\n"; } ~BB() { cout << "Pai Xigou\n"; } }; int main() { AA *a = new BB; a->func(); delete a; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。