【两篇概况C++】二、继承、重载、多态、封装

本文详细介绍了C++中的继承机制,包括公有、保护和私有继承,以及多继承。此外,还涵盖了重载运算符、函数和多态的概念,以及数据抽象和封装的重要性,以及抽象类的使用。

1、C++ 继承

面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。

当创建一个类时,您不需要重新编写新的数据成员和成员函数,

只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类

基类 & 派生类

一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>

using namespace std;

class Base {
public:
	void setWidth(int _width) {
		width = _width;
	}
	void setHeight(int _height) {
		height = _height;
	}

protected:
	int width;
	int height;
};

class son :public Base {
public:
	int getArea() {
		return width * height;
	}
};

int main() {
	son jack;
	jack.setHeight(10);
	jack.setWidth(20);

	cout << "area: " << jack.getArea() << endl;

}

访问控制和继承

派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。

我们可以根据访问权限总结出不同的访问类型,如下所示:

访问publicprotectedprivate
同一个类yesyesyes
派生类yesyesno
外部的类yesnono

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

继承类型

当一个类派生自基类,该基类可以被继承为 public、protected private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。

我们几乎不使用 protected private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

多继承

多继承即一个子类可以有多个父类,它继承了多个父类的特性。

C++ 类可以从多个类继承成员,语法如下:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};

2、重载运算符和重载函数

C++ 允许在同一作用域中的某个函数运算符指定多个定义,分别称为函数重载运算符重载

重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。

当您调用一个重载函数重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策

C++ 中的函数重载

在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数

C++ 中的运算符重载

您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

3、多态

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

class Shape {
protected:
	int width, height;
public:
	Shape(int _width,int _height)
		:width(_width),height(_height){}
	 int area() {
		cout << "Parent class area :" << endl;
		return 0;
	}
};

class son_one :public Shape {
public:
	son_one(int _width, int _height)
		:Shape(_width,_height){}
	int area() {
		cout << "son_one class area :" << endl;
		return (width * height / 2);
	}
};
class son_two :public Shape {
public:
	son_two(int _width, int _height)
		:Shape(_width, _height) {}
	int area() {
		cout << "son_two class area :" << endl;
		return (width * height);
	}
	};
int main()
{
	Shape* shape;
	son_one A(10,20);
	son_two B(3,6);

	shape = &A;
	shape->area();

	shape = &B;
	shape->area();

}

结果打印都是父类地area

导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。

但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual

class Shape {
protected:
	int width, height;
public:
	Shape(int _width,int _height)
		:width(_width),height(_height){}
	virtual int area() {
		cout << "Parent class area :" << endl;
		return 0;
	}
};

此时,编译器看的是指针的内容,而不是它的类型。因此,由于 son1和 son2 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

4、数据抽象

数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。

数据抽象是一种依赖于接口和实现分离的编程(设计)技术。

让我们举一个现实生活中的真实例子,比如一台电视机,您可以打开和关闭、切换频道、调整音量、添加外部组件(如喇叭、录像机、DVD 播放器),但是您不知道它的内部实现细节,也就是说,您并不知道它是如何通过缆线接收信号,如何转换信号,并最终显示在屏幕上。

因此,我们可以说电视把它的内部实现和外部接口分离开了,您无需知道它的内部实现原理,直接通过它的外部接口(比如电源按钮、遥控器、声量控制器)就可以操控电视。

现在,让我们言归正传,就 C++ 编程而言,C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。

在 C++ 中,我们使用来定义我们自己的抽象数据类型(ADT)。您可以使用类 iostreamcout 对象来输出数据到标准输出,如下所示:

实例

#include <iostream>

using namespace std;

int main( ) { cout << "Hello C++" <<endl; return 0; }

在这里,您不需要理解 cout 是如何在用户的屏幕上显示文本。您只需要知道公共接口即可,cout 的底层实现可以自由改变。

访问标签强制抽象

在 C++ 中,我们使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:

  • 使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由它的公共成员来定义的。
  • 使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。

访问标签出现的频率没有限制。每个访问标签指定了紧随其后的成员定义的访问级别。指定的访问级别会一直有效,直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。

数据抽象的好处

数据抽象有两个重要的优势:

  • 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
  • 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。

如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变会导致哪些影响。如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>

using namespace std;

class Add {
public:
	Add(int _total)
		:total(_total){}
	//对外接口
	void addNum(int number) {
		total += number;
	}

	//对外接口
	int getTotal() {
		return total;
	}
private:
	int total;
};
int main()
{
	Add obj(10);

	obj.addNum(20);
	obj.addNum(70);
	cout << obj.getTotal() << endl;

}

C++ 数据封装

所有的 C++ 程序都有以下两个基本要素:

  • 程序语句(代码):这是程序中执行动作的部分,它们被称为函数。
  • 程序数据:数据是程序的信息,会受到程序函数的影响。

封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏

数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。

C++ 通过创建来支持封装和数据隐藏(public、protected、private)。我们已经知道,类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。默认情况下,在类中定义的所有项目都是私有的。例如:

class Box { public: double getVolume(void) { return length * breadth * height; } private: double length; // 长度 double breadth; // 宽度 double height; // 高度 };

变量 length、breadth 和 height 都是私有的(private)。这意味着它们只能被 Box 类中的其他成员访问,而不能被程序中其他部分访问。这是实现封装的一种方式。

为了使类中的成员变成公有的(即,程序中的其他部分也能访问),必须在这些成员前使用 public 关键字进行声明。所有定义在 public 标识符后边的变量或函数可以被程序中所有其他的函数访问。

把一个类定义为另一个类的友元类,会暴露实现细节,从而降低了封装性。理想的做法是尽可能地对外隐藏每个类的实现细节。

C++ 接口(抽象类)

接口描述了类的行为和功能,而不需要完成类的特定实现。

C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。

如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。

### 封装 封装的原理是隐藏实现细节,将函数和数据包围起来,对数据的访问只能通过可信任的对象和类进行,对不可信的进行信息隐藏。在面向对象编程中,把客观事物封装成抽象的类,类可以把自己的数据和方法只让可信的类或者对象操作,从而使代码模块化,让代码和功能独立[^2]。 使用方法:定义类,将数据成员设为私有(private),通过公有(public)的成员函数来访问和修改这些数据成员。 示例代码如下: ```cpp #include <iostream> class Rectangle { private: double length; double width; public: // 构造函数 Rectangle(double l, double w) : length(l), width(w) {} // 获取面积 double getArea() { return length * width; } }; int main() { Rectangle rect(5, 3); std::cout << "Area: " << rect.getArea() << std::endl; return 0; } ``` ### 继承 继承的原理是可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展,其过程是从一般到特殊。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”“父类”或“超类”。在C++中,一个子类可以继承多个基类,继承概念的实现方式有实现继承、接口继承和可视继承[^2]。 使用方法:使用`class 子类名 : 继承方式 基类名`的语法来实现继承。 示例代码如下: ```cpp #include <iostream> // 基类 class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生类 class Rectangle : public Shape { public: int getArea() { return (width * height); } }; int main() { Rectangle rect; rect.setWidth(5); rect.setHeight(3); std::cout << "Area: " << rect.getArea() << std::endl; return 0; } ``` ### 多态 多态的原理是同一接口可以有不同实现的能力,C++通过函数重载、运算符重载、虚函数和纯虚函数实现多态,可分为静态多态和动态多态。静态多态通过在同一作用域中定义多个同名但参数不同的函数(重载)在编译时实现;动态多态通过派生类重新定义基类中的虚函数(重写)在运行时实现[^3]。 使用方法:静态多态通过函数重载实现,动态多态通过虚函数和重写实现。 示例代码如下: ```cpp #include <iostream> // 基类 class Shape { public: virtual int area() { std::cout << "Parent class area: " << std::endl; return 0; } }; // 派生类 class Rectangle : public Shape { public: int area() { std::cout << "Rectangle class area: " << std::endl; return (width * height); } Rectangle(int w, int h) : width(w), height(h) {} private: int width; int height; }; int main() { Shape* shape; Rectangle rect(5, 3); shape = &rect; shape->area(); return 0; } ``` ### 重载 重载的原理属于静态多态(编译时多态),在同一作用域中定义多个同名但参数不同的函数,编译器根据调用时的参数类型和数量来确定调用哪个函数[^3]。 使用方法:在同一作用域中定义多个同名函数,但参数列表不同。 示例代码如下: ```cpp #include <iostream> int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int main() { std::cout << "Integer addition: " << add(2, 3) << std::endl; std::cout << "Double addition: " << add(2.5, 3.5) << std::endl; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值