面向对象编程思想和细节处理


1、前言


为了避免打着C++的旗号写出C风格的代码,我们很有必要了解C++的面向对象编程思想。只有真正掌握了这个,才能使用C++正确的编程,而不至于总是和指针,数组打交道,从而失去了C++自身的优势。C++面向对象编程主要有三个特性,数据抽象,多态,继承。当我们尝试对某个事物进行封装成一个类的时候,其实就是数据抽象了。当一个类是另一个类的基类,就是继承。如果在运行时才能确定函数的实参是继承层次中哪个类,就叫做多态。下面我们详细了解这些。



2、概述


我们先通过一个例子来综合描述上面提到的三个特性。

首先是基类

#include <string>
#include <iostream>

class Bird
{
public:
	Bird();
	virtual ~Bird();
	std::string getName()//获取鸟的名字
	{
		return "bird";
	}
	virtual int flyHeight()//获取飞行高度
	{
		return height;
	}
	void printlnFlyHeight()//打印飞行信息
	{
		std::cout << "name:" << getName() << ",flyHeight:" << flyHeight()<<std::endl;
	}
private:
	int height = 10;
};

Bird::Bird()
{
}

Bird::~Bird()
{
}

我们需要关注的是virtual修饰符修饰的flyHeight方法以及printlnFlyHeight方法

其次是子类:

#include "Brid.h"

class Sparrow:public Bird
{
public:
	Sparrow();
	virtual ~Sparrow();
	virtual int flyHeight()
	{
		return height;
	}
private:
	int height = 8;
};

Sparrow::Sparrow()
{
}

Sparrow::~Sparrow()
{
}

子类重写了Bird的flyHeight方法。

最后调用。

#include "Sparrow.h"

int main()
{
	Bird bird;
	Sparrow sparrow;

	std::cout << "Bird:";
	bird.printlnFlyHeight();
	std::cout << "Sparrow:";
	sparrow.printlnFlyHeight();
	system("pause");
	return 0;
}

打印结果:





分析上面的例子,首先我们在封装一个类的时候就使用到了数据抽象的特性,其次flyHeight方法在基类和子类中都有所不同,这个就是多态。并且Sparrow是继承自Brid的,所以体现了继承。在main函数中,对printlnFlyheight方法的调用就是先了动态绑定过程。动态绑定指的是,编译的时候并不知道确切的类型,只有运行的时候知道。可以支撑这个特性的就是多态了。接下来会介绍跟多的详细内容。


3、定义基类和派生类

关于基类和派生类,它们之间即使相互联系的又是相互分离的,这种关系是很微妙的,需要具体掌握。


3.1、基类的定义

基类和一般的类一样,有自己的数据成员和成员函数,除此之外,可以通过修饰符,对成员进行更多的限制和使用。比如如下代码:

#include <string>
#include <iostream>

class Bird
{
public:
	Bird(const std::string type = "bird", const int height = 10) :type(type), height(height){};
	virtual ~Bird();
	std::string getName()
	{
		return "bird";
	}
	virtual int flyHeight()
	{
		return height;
	}
	void printlnFlyHeight()
	{
		std::cout << "name:" << getName() << ",flyHeight:" << flyHeight() << std::endl;
	}
private:
	int height = 10;
protected:
	std::string type;

};

Bird::Bird()
{
}

Bird::~Bird()
{
}

基类拥有自己的成员,比如type......默认情况下,基类的成员函数是非虚函数,如果要声明一个函数为虚函数,必须使用virtual修饰符。它的作用是,告诉子类,这个函数期望子类自己去重写。如果一个函数希望子类和父类拥有相同的行为,就不能使用虚函数,这样就不会发生动态绑定。同时,可以给成员添加访问标志,标识成员的可访问区域,比如private,public,protected。它们的区别如下:

private:这些成员是不可以被子类继承的,除了自身和友元对象之外,没有其它对象可以访问它们。

public:这些成员是公开的,可以被所有的对象访问,可以被所有的子类继承。

protected:这些成员是受限制的,首先它们是可以被子类继承的,其次,除了自身和友元对象之外,任何对象都不能访问它们,包括子类。


class默认的继承是private,struct默认的继承是public。

现在假设有Sparrow,是Bird的子类,Brid有一个protected修饰的成员type,在Sparrow中含有一个getType方法,下面通过函数说明一下protected的作用:


public void getType(const Bird &bird,const Sparrow &sparrow)
{
cout<<bird.type;
cout<<sparrow.type;
}

由于type是可以继承但是不能访问的,所以sparrow是由type成员的,但是bird.type是错误的,type只能继承,不能被任何除了自身和友元对象之外的任何对象进行访问。



3.2 派生类的细节说明


首先看例子:

#include "Brid.h"

class Sparrow:public Bird
{
public:
	Sparrow();
	virtual ~Sparrow();
	virtual int flyHeight()
	{
		return height;
	}
private:
	int height = 8;


};

Sparrow::Sparrow()
{
}

Sparrow::~Sparrow()
{
}

继承一个类,使用类名:访问标识符 基类的格式,比如class Sparrow:public Bird。访问标识的意思是这样的

如果是private,表示继承自父类的所有成员在子类中都是私有的。
如果是protected,表示继承自父类的所有成员在子类中都是受保护的。
如果是public,表示继承自父类的所有成员在子类中的访问标识不变。

如果要继承多个父类,用逗号分隔,比如class Sparrow:public Brid,public Animal。

子类除了包含自身的成员之外,也包含父类的除了private之外的所有成员。

子类可以重写父类中的虚函数,但不是必须这样做。一个函数在父类中被定义为虚函数,子类除了重定义它之外,并不能改变它虚函数的特性。虚函数用virtual声明,默认情况下,成员函数都是非虚函数。如果一个成员函数被声明为虚函数,则说明它希望该函数被子类重定义。如果不希望被子类重定义,就不要声明为虚函数。

派生类又可以作为其它类的基类,没有限制。但是作为基类的类必须已定义,不能继承一个未定义的类作为基类。

友元关系不能继承,基类和子类的友元关系是不能公用的。

static成员也不能被继承,静态成员只与类相关和类的对象无关。


3.3 virtual虚函数

C++是可以动态绑定的,意思是,编译阶段可以不需要知道具体的类型,但是运行的时候就可以知道具体的类型。如果希望一个成员函数是可以动态绑定的,那么需要满足两个条件。

①、成员函数必须是virtual声明的。
②、调用成员函数必须是基类的引用或指针。

virtual不可用于构造函数和static成员中。



4、构造函数和复制控制

派生类可以继承基类的非static成员,除此之外,友元关系,构造函数以及复制控制是不能继承的。当一个派生类进行构造复制赋值撤销的时候,也会伴随着基类的构造复制赋值撤销。如果派生类不定义自己的构造函数和复制构造函数或者析构函数等等,默认使用合成版本。



4.1 复制控制和继承


如果派生类重定义了复制控制的部分,那么它必须负责基类的成员的复制控制部分。比如:

class Sparrow:public Brid
{
public :
Sparrow(const Sparrow &sparrow):Brid(sparrow){}
/......
}

上面的Sparrow由于自己定义了复制构造函数,所以必须承担起基类的复制构造。


比如赋值操作符重载:

Sparrow & Sparrow::operator=(const Sparrow &sparrow)
{
if(this!=&sparow)
Bird::operator=(sparrow);

return this;
}

如果是析构函数,则不用负责基类的析构函数的调用。因为编译器总是显示的调用各类的析构函数来释放所有资源。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值