类
类的继承¶
继承类对基类成员的访问权限只和基类中的访问说明符有关
- public: 可以被外部和继承类访问
- protected: 不可以被外部访问,可以被继承类和友元访问
- private: 不可以被外部、继承类和友元访问
以哪种形式继承,只影响继承类的用户对基类成员的访问权限,继承类的成员函数内部对基类的public
和protected
成员都可以访问
- public继承: 基类的
public
成员和protected
成员在派生类中保持原有的访问属性 - protected继承: 基类的
public
成员和protected
成员都变成protected
的属性 - private继承: 基类的
public
和protected
成员都变成private
属性
如下代码,只有public
继承的PubDerived才可以调用基类的public
函数pub()
#include <iostream>
using namespace std;
class Base {
public:
void pub() { cout << "Base Public\n"; }
protected:
void pro() { cout << "Base Protected\n"; }
private:
void pri() { cout << "Base Private\n"; }
};
class PubDerived : public Base {};
class ProDerived : protected Base {};
class PriDerived : private Base {};
int main() {
PubDerived pub;
pub.pub();
ProDerived pro;
PriDerived pri;
}
字节对齐¶
- 32位下4字节对齐,64位下8字节对齐
- 结构体的总大小为结构体最宽基本类型成员大小的整数倍
如以下结构体
类型转换与继承¶
对于基类和派生类,有一个重要规则: 可以将基类的指针或者引用绑定到派生类对象上
对于一个变量或表达式,存在静态类型和动态类型,静态类型在编译期就已经确定,动态类型则在运行时才知道,而基类的指针或引用的动态类型可能与静态类型不一致,也只有指针或引用可以,基类的变量永远是基类的类型,不能更改
派生类可以向基类转化,而基类永远不可能向派生类转换,因为基类只是派生类的一部分,即使是指针和引用也不行,因为编译器编译期间只能通过静态类型来进行检查,如下,即使第二行的基类指针已经和派生类绑定在一起,也无法转换成派生类指针
Derived derived;
Base* basePtr = &derived;
Derived* derivedPtr = basePtr; // 出错
虚函数¶
以上所述动静态类型和虚函数关系很大,因为当虚函数通过指针或引用被调用时,直到运行时才知道具体的调用结果,所以所有的虚函数都必须有定义,因为编译器无法确定一个虚函数是否会被使用
通过指针或引用调用虚函数时,被调用的是与绑定到指针或引用上的动态类型相匹配的一个
因此基类指针可以调用子类的虚函数
c++11中可以使用override
关键字来说明派生类中的虚函数,同时还可以把某个函数指定为final
,则之后任何尝试覆盖该函数的操作都将引发错误
作用域 可以通过作用域来强迫调用基类的函数版本而不是动态绑定的虚函数
Base* ptr = new PubDerived();
ptr->Base::virtualFunction(); // 调用的仍然是基类的虚函数
虚析构函数
- 当基类包含
virtual
成员函数时,表示该基类可能会用作多态,即基类指针或引用绑定派生类对象,这时就要定义一个虚析构函数,来保证在析构时调用的是子析构函数 - 子析构函数会后调用父析构函数,相应,子构造函数会先调用父构造函数,以下代码执行结果可见
class Base {
public:
Base() { cout << "Base Constructor\n"; }
virtual ~Base() { cout << "Base Destrution\n"; }
};
class PubDerived : public Base {
public:
PubDerived() { cout << "PubDerived Constructor\n"; }
~PubDerived() { cout << "PubDerived Destructoin\n"; }
};
int main() {
Base* ptr = new PubDerived();
delete ptr;
}
执行结果为
Base Constructor
PubDerived Constructor
PubDerived Destructoin
Base Destrution
C++拷贝构造¶
一个构造函数第一个参数是自身类型的引用,且任何额外参数都有默认值,则这个构造函数是拷贝构造函数
class Foo {
public:
Foo(); // 默认构造函数
Foo(const Foo&); // 拷贝构造
};
拷贝构造函数必须是引用,可以不定义成const,但这个参数应用中几乎都是const的
如果没有给一个类定义拷贝构造函数,编译器会定义一个
出现情况
- 用另一个对象来定义变量
- 用=来定义变量
- 经一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
多态¶
指相同的对象收到不同的消息或者不同的对象收到相同的消息时产生的不同的实现动作
- 编译时多态性(静态多态):就是在编译期确定的一种多态,在C++中主要体现在函数模板
- 重载是多态性中最简单的形式,它是指用同一名字表示不同的函数或运算符,从而使C++具有更大的灵活性和扩展性,它分为运算符重载和重载函数两种
- 运行时多态性(动态多态):通过虚函数实现。具体来说,声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法
只有在通过指针或引用调用虚函数的时候,才会在运行时解析该调用,对象的动态类型才会与静态类型不一致
#include<iostream>
using namespace std;
//基类对象
class Base {
public:
//有virtual关键字,运行时多态
virtual void f(float x) {
cout<<"Base::f(float)"<< x <<endl;
}
//无viratul关键字,不会发生运行时多态
void g(float x) {
cout<<"Base::g(float)"<< x <<endl;
}
void h(float x) {
cout<<"Base::h(float)"<< x <<endl;
}
};
class Derived : public Base {
public:
virtual void f(float x) {
cout<<"Derived::f(float)"<< x <<endl; //多态、覆盖
}
//子类与父类的函数同名,无virtual关键字,则为隐藏
void g(int x) {
cout<<"Derived::g(int)"<< x <<endl; //隐藏
}
void h(float x) {
cout<<"Derived::h(float)"<< x <<endl; //隐藏
}
};
int main(void) {
Derived d; //子类
Base base = d; // Base::f(float)3.14
base.f(3.14f);
Base *pb = &d; //基类指针指向子类
Derived *pd = &d; //子类指针指向自己
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14 调用子类,多态
pd->f(3.14f); // Derived::f(float) 3.14 调用子类
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14 无多态,调用自己的
pd->g(3.14f); // Derived::g(int) 3 无多态,调用自己的
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 无多态,调用自己的
pd->h(3.14f); // Derived::h(float) 3.14 无多态,调用自己的
return 0;
}