第十五章 面向对象程序设计


前言

c++Primer 第十五章学习心得


一、概述

OOP的核心思想是:数据抽象、继承和动态绑定
基本特征是:封装、继承和多态

通过抽象可以将接口与实现分离,动态绑定可以忽略类型区别,使用统一方式管理对象

1、继承

在类名后加":继承方式 基类,其他基类"实现,对于重载的虚函数,除了在基类、派生类函数前加virtual还可以在派生类中参数列表后加override显式表明重载。

2、动态绑定

参数类型为基类的指针或者引用,在函数运行时,由实参(可以为派生类)的类型决定调用的函数类型。

二、定义基类和派生类

1、定义基类

在基类中定义公用的成员,包括变量和函数;还需要定义virtual虚函数,一定包括虚析构函数
除了构造函数外,所有函数都可以是虚函数,调用时发生动态绑定。
protected用于派生类访问基类私有成员
基类中的静态成员,在所有继承体系中只存在该成员的唯一定义。

通过在类名后使用关键字final,可以将类设置为不可继承

2、定义派生类

派生类中的虚函数 参数列表后加override显式表明重载,前面可不加virtual,因为虚函数在派生类中一定是虚函数。
派生类在指针或者引用时可以被隐式转换为基类,因此可以用基类指针或者引用去绑定派生类。但不存在基类到派生类的隐式转换。对于基类中的方法,当传入的参数为派生类时,只对共有部分进行处理,忽略派生类自己的成员。

派生类初始化时执行构造函数,首先通过基类构造函数初始化共有成员,然后根据声明顺序初始化自己的成员。

三、虚函数

(1)所有的虚函数都必须被定义,属于动态绑定。
(2)派生类中虚函数的参数一定和基类相同,返回基类本身的引用或指针时可以在派生类能够转换到基类时,可以将基类改为派生类,否则返回类型必须相同。
(3)最好使用override帮助编译器发现定义虚函数时的错误,例如参数列表不同。
(4)可以使用final将函数设置为不可覆盖。
(5)虚函数可以使用默认实参,但最好基类和派生类一致。当基类指针指向派生类,执行虚函数时的默认实参仍为基类的默认。
(6)可以通过作用域运算符"::"确定虚函数的类型

四、抽象基类

用于继承的基类虚函数,本身不希望执行功能,可以通过声明时参数列表后 “= 0” 表明为纯虚函数,无须在基类中定义,不过可以在类外定义,只是无意义。
含有纯虚函数的类是抽象基类,抽象基类不能直接创建对象,只能通过继承将纯虚函数覆盖后才能创建对象。

五、访问控制与继承

基类中的访问说明符决定派生类内部是否能访问该成员。
派生类的访问说明符决定派生类对象能从外部否访问该成员。protected继承相当于内部成员最高为protected

class F{
public:
	int f_pub;
protected:
	int f_pro;
private:
	int f_pri;
};

class S_pub:public F{//内部能够访问f_pub,f_pro,不能访问f_pri
public:
	void f(){cout<<f_pub<<f_pro;}
};

class S_pri:private F{//内部能够访问f_pub,f_pro,不能访问f_pri
public:
	void f(){cout<<f_pub<<f_pro;}
};

S_pub s_pub;
s_pub.f();//正确
//pub继承,只允许从外部访问继承自父类的public成员
cout<<s_pub.f_pub;


S_pri s_pri;
s_pri.f();//正确
cout<<s_pri.f_pub<<s_pub.f_pub;//pri和pro继承,均无法从外部访问

当发生派生类向基类转换时,由派生类的继承方式决定访问控制。

1、友元与继承

友元关系不能继承,因此基类的友元不能访问派生类的特有成员,派生类的友元可以访问被继承的成员,与继承方式无关。友元本身也也不能被继承。
S继承自F,如果是public继承,那么F的友元fun(F&)可以在fun(s)中访问s的公有对象。如果是protected或者private继承,那么无法访问,实际是由于类型转换导致。

2 、using改变个别成员可访问性

继承方式:
	using 类名::成员名;

可以无视继承方式对可访问成员权限的控制,修改权限

六、继承中的类作用域

继承关系中,派生类的作用域嵌套在基类的作用域之内,派生类中解析成员时未找到,会到基类作用域中解析。也就是说,可以在派生类中重新定义某些成员,虽然与基类的成员同名,但在派生类对象中使用时,会覆盖基类的成员。
如果需要使用被覆盖的成员,可以借助作用域运算符来显式调用。
除了虚函数,最好不要重用其他基类中的成员。
名字查找通常先于类型查找,对于非虚函数的重用,表现形式为形参列表不同,此时派生类的函数同样会覆盖基类函数,导致无法调用。

struct Base{
	int memfcn();
};
struct Derived:Base{
	int memfcn(int );
};
Base b;
Derived d;
d.memfcn();//错误,名字匹配成功,基类成员被覆盖,但由于参数不一致,导致调用失败
d.Base::memfcn();//正确
d.memfcn(10);//正确

可以使用基类的指针或者引用,从而动态绑定使用的基类函数

调用函数的顺序,首先是确定静态类型(即指针引用类型,此时不可在基类指针中调用子类的对象),然后是根据名字在作用域中查询(若在子类中采用重载,会隐藏外层基类的同名成员,必须在子类中对虚函数进行重写才可以继续使用父类的其他重载同名函数),其次是查看是否是虚函数决定执行版本。

Base * bptr = &d;
bptr->memfcn();//正确调用基类函数

当基类存在众多重载函数时,可以在派生类中使用 using 类名::成员名 将同名的重载函数全部加入派生类作用域,再对需要的函数进行覆盖。例如基类中存在两函数fcn()和fcn(int),在派生类中只重载了fcn(int),如果使用派生类对象调用fcn(),会报错,因为作用域中只存在fcn(int),同名覆盖了基类中的函数。使用using Base::fcn;能够将两个函数均加入作用域,再重载fcn(int)后,能够使用基类中的fcn()

七、构造函数与拷贝控制

1、虚析构函数

析构函数必须是虚函数,从而确保动态绑定时运行正确的析构函数版本,避免内存泄漏。
在拷贝控制中,对于基类而言并不一定需要拷贝构造和赋值构造函数。
特别地是,即使通过=default显式使用了合成版本,派生类中也不会存在合成移动函数。因此如果需要在派生类中使用移动操作,需要在基类中首先显式地定义该成员。

2、合成拷贝控制与继承

派生类中的拷贝控制通过调用基类的拷贝控制实现,类似一个栈。
(1)如果基类通过delete将默认的构造函数删除或者设置为不可访问,那么派生类则将该函数定义为删除
(2)如果基类的析构函数被删除或者私有,那么派生类中合成的默认和拷贝构造函数将会是被删除的
本质上如果派生类定义拷贝控制,首先需要在基类中显式定义。不过,派生类中,构造和析构是仅处理派生类自己分配的资源,其他是派生类处理整个对象。

3、派生类的拷贝控制成员

简而言之,类似构造函数,使用基类的成员放在参数列表后控制共有部分,在函数体中对派生类特有成员进行控制。

4、继承构造函数

可以在派生类中使用using 基类::基类构造函数名来继承构造函数,且该方法定义的构造函数与基类的访问级别一致,与using的位置无关。另外,如果含有默认实参,则会生成多个构造函数,每个函数中会有一个默认实参被省略。

八、容器与继承

在容器中存放继承体系中的对象时,通常使用间接存储——智能指针,因为容器中的元素类型必须一致。通过智能指针,可以动态绑定虚函数,根据指向的对象完成不同的操作,更加灵活。


总结

拷贝控制、继承还是需要进一步理解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值