C++ 三大特性之封装,继承,多态

1 从C到C++

在嵌入式的软件开发过程中大多是使用C这种面向过程的语言来开发,从效率上来说C语言已经非常高了,使用在硬件资源比较紧张的嵌入式系统来说是最好不过的选择了,随着硬件性能的提升,以及硬件价格的下降,C++逐渐应用于嵌入式系统中了,C++语言在编译器开优化的情况下,代码效率已经接近C语言了,并且提供了更高级的语言特性。(C语言也可以实现C++的特性LINUX内核就是使用了很多面向对象的思想)

2 封装(Encapsulation)

C语言也可以实现封装,使用struct关键字,把函数放进结构体中是从C到C++的根本改变(C语言使用函数指针)。将数据连同函数捆绑在一起的能力可以用于创建新的数据类型,这称之为封装(encapsulation),封装既是针对数据(属性)也是针对函数(行为)。

封装就是对现实世界的一种抽象。

事实上面向对象编程可以总结为一句话,“向对象发送消息”,实际上要做的所有事情就是创建一束对象并且给它们发送消息。

封装为什么好处呢?

  • 内聚
  • 增强安全性和简化编程
  • 代码重用

隐藏实现:
在C语言中,struct与其它的数据结构一样,没有任何的规则,程序员可以struct中做他想做的任何事情,没有途径来强制任何的特殊行为。
C++语言中引入了三个新的关键字,用于在结构中设置访问的边界,public,private,protected。

  • public
    其后声明的所有成员可以被所有人访问。
  • private
    除去该类型的创建者和类的内部成员函数外任何人都不能访问。
  • protected
    在继承结构中可以访问protected成员,但是不能访问private成员。

在C++中使用class 关键字,它和C语言中的struct每一个方面都是一样的,除了class的成员默认是private的,而struct的成默认是public的。

如果一个函数被声明为friend时,就意味着它不这个类的成员函数,却可以修改该类的私有成员,且必须被列在该类的定义当中,这是一个特权函数,它突破了原有的访问控制权限。

3 继承(Inheritance)

C++中通过创建新的类来重用代码,而不是从头创建它们。继承是对抽象的一种升华。在编程实践中将公共特性部分抽出来放在父类里,了类则拥有特性化的有别于其它子类的特性。(is-a关系)
在这里插入图片描述

子类的访问权限:

继承方式 基类的public成员 基类的protected成员 基类的private成员
public 仍为public 仍为protected not access
protected 变为protected 仍为protected not access
private 变为private 变为private not access

组合( composition)

组合是另外一种代码重用的方式。无论是组合还是继承都能把子对象放在新类型中。组合通常是在希望新类内部具有已存在类的功能时使用,而不是希望已经存在类作为它的接口,嵌入一个对象用以实现新类的功能,而新类的用户看到的是新定义的接口而不是来自老类的接口,为此在新类的内部嵌入已存在类的private对象。(has-a关系)

在这里插入图片描述
继承与静态成员

  • 静态成员变量所有子类都只有一份
  • 静态成员变量在类外初始化
  • 一般使用静态成员函数访问静态成员变量
  • 静态成员函数没有this指针,不是任何对象的组成部分。

4 多态 (polymorphism)

多态是通过虚函数实现的。
多态是运行时动态绑定。

动态绑定的条件:

  • 第一:只有指定为虚函数的成员函数才能进行动态绑定
  • 第二:必须通过基类类型的引用或指针进行函数调用

基类类型的引用或指针可以引用基类类型对象,也可以引用派生类型对象。

引用或指针的静态类型与动态类型可以不同,这是C++支持多态的基础。通过基类引用或指针调用基类中定义的函数时,并不能知道要执行的函数对象的确切类型,执行函数的对象有可能是基类类型也可能是派生类型的。

class Base
{
public:
virtual void func(){}
};

class DevidedA :public Base
{
public:
virtual void func(){}
};
class DevidedB :public Base
{
public:
virtual void func(){}
};
/
Base *b = new DevidedB ();
b->func(); //调用 DevidedB::func();

4.1 纯虚函数

函数定义为纯虚函数说明,此函数为后代类型提供了可以覆盖的接口,但是这个类中是不会调用,此外用户不能创建此类的对象。此类为抽象基类。

class Q_WIDGETS_EXPORT QAbstractButton : public QWidget
{
    Q_OBJECT
    ....
protected:
    virtual void paintEvent(QPaintEvent *e) = 0;
    ...
 };

void QRadioButton::paintEvent(QPaintEvent *)
{
    QStylePainter p(this);
    QStyleOptionButton opt;
    initStyleOption(&opt);
    p.drawControl(QStyle::CE_RadioButton, opt);
}

QAbstractButton 的所有派生类 QCheckBox, QPushButton, QRadioButton, QToolButton都要实现irtual void paintEvent(QPaintEvent *e)接口。

5 覆盖(Overriding)

  • 覆盖与重写是一个意思
  • 覆盖是借助于virtual关键字实现的
  • 覆盖是子类覆盖父类
  • 覆盖时子类与父类的函数名,形参,返回值是一样一样的

摘自Qt源码的 focusOutEvent

void QAbstractButton::focusOutEvent(QFocusEvent *e)
{
    Q_D(QAbstractButton);
    if (e->reason() != Qt::PopupFocusReason)
        d->down = false;
    QWidget::focusOutEvent(e);//显式调用
}

在QAbstractButton的focusOutEvent方法里做特殊化的处理,再调用父类的focusOutEvent做通用化的处理。

6 重载(Overload)

静态的多态

6.1 函数

出现在相同的作用域中(同一个类中,同一个文件中)的两个函数如果具有相同的名字且形参表不同见为函数的重载(overload function)。编译器将根据所传递的实参类型来判断调用的是哪一个函数。函数不能仅仅基于不同的返回类型而实现重载。

错误的例子:

Record lookup(const Account&);
bool lookup(const Account&);
Record lookup(const Account&acct);
Record lookup(const Account&);
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&);
Record lookup(Phone);
Record lookup(const Phone);

正确的例子:

Record lookup(Phone&);
Record lookup(const Phone&);
Record lookup(Name);
Record lookup(Address);

6.2 操作符

通过操作符的重载,程序能够针对类 类型的操作数定义不同的操作符版本。重载操作符是具有特殊名称的函数,保留字operator后接需要定义的操作符符号,像其它函数一样,重载操作符具有返回类型和形参列表。
格式:

Object operator + (const Objcet&,const Object&);

内置类型的操作符,不能重载(不能重载int类型的“+”操作符)

重载一元操作符如果作为成员函数就没有形参(显式),如果作为非成员函数就有一个形参,同样的,重载二元操作符定义的成员时有一个形参,定义为非成员函数时有两个形参。

操作符定义为非成员函数时通常必须将它们设置为所操作类的友元(friend)

7 重定义(Redefining)

子类与基类的成员同名时将屏蔽对基类成员的直接访问。

7.1 成员变量

class Base
{
public:
	Base():mem(0){}
protected:
int mem;
};

class Derived :public Base
{
public:
	Derived():mem(0){} //初始化Derived::mem;
	int getMem()
	{
		return mem;//返回 Derived::mem;
	}
	int getBaseMem()
	{
		return Base::mem;//返回 Base::mem;
	}
protected:
int mem;
}

7.2 成员函数

在基类和子类中使用同一名字的成员函数,其行为与数据成一样:在子类作用域中子类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽。

class Base
{
public:
	Base();
	int func();
};

class Derived :public Base
{
public:
	Derived();
 	int func(int);
}

Base b;
Derived d;

b.func();
d.func(2);
d.Base::func();
d.func(); //error
  • 不同的作用域
  • 若重新定义了基类中的一个重载函数,则在派生类中,基类中该名字函数(即其他所有重载版本)都会被自动隐藏,包括同名的虚函数
  • 对函数的返回值、形参列表无要求

8 有用参考

《C++编程思想》
《C++ Primer》
https://blog.csdn.net/lms1008611/article/details/81515727 (图)
https://www.cnblogs.com/DannyShi/p/4593735.html
https://blog.csdn.net/haoel/article/details/1948051/

猜你喜欢

转载自blog.csdn.net/amwha/article/details/87520004