目录
一、结构化编程与面向对象编程
1.1结构化编程
传统的程序设计方法可以概括为“程序=算法+数据结构”,即将程序视为一系列处理数据的过程。这种方法的核心关注点在于数据处理的过程,因此被称为面向过程的设计。其显著特点是数据与程序的分离,即数据本身与对数据进行操作的程序是分开的。
结构化程序设计的核心思想是采用自顶向下、逐步细化的设计策略,以及单一入口和单一出口的控制结构。其理念是将复杂的程序分解为一系列小型、易于管理的任务。如果某个任务仍然显得庞大,那么它将进一步被分解为更小的子任务。这个分解过程将持续进行,直至程序被划分为一系列小型的、易于编码的模块。
当问题的复杂性超越了个人能够清晰把握各个子程序间调用关系的能力时,自顶向下的设计方法可能会显得力不从心。
结构化程序设计曾是应对复杂问题的有效工具,然而后面这种方法开始显现出一系列局限性:
-
程序的管理变得困难;
-
数据修改面临挑战;
-
程序的可重用性不足;
-
用户需求在系统分析阶段难以精确界定,导致系统交付使用时出现诸多问题,而依赖系统开发各阶段成果的控制方式,无法灵活适应不断变化的需求。
面向过程程序设计的根本缺陷在于数据与数据处理的分离。在这种模式下,数据往往被多个模块共享,这增加了数据混乱和错误的风险。
1.2面向对象程序设计
面向对象的视角将系统视为一组通过交互作用来实现特定功能的对象集合。每个对象都拥有自己的方法来管理其内部数据,确保只有对象自身的代码能够操作其内部数据。在这种模式下,程序由多个对象及其相互间的交互构成。
对象是算法与数据的封装体,一旦封装完成,外部环境的变化不会对其内部产生影响。其核心理念是将数据处理方法与数据本身紧密结合,形成一个独立的实体。在这个实体中,数据不再属于整个程序,而是专属于对象,且数据处理方法由对象自身提供,外部无法直接修改对象内部的数据。
面向对象是一种认识事物的方法,它以对象为中心,模拟自然界处理事物的方式。在面向对象程序设计中,数据及其操作方法被整合为一个独立的整体——对象。同类对象通过抽象共性,可以形成类。
在类中,数据通常只能通过该类提供的方法进行操作,这些方法构成了类与外部世界的接口。这种封装确保了数据的安全性和一致性,同时也促进了代码的模块化和可维护性。
1.3面向对象三要素
面向对象程序设计(Object-Oriented Programming,简称OOP)的核心要素包括对象、类和继承。
对象:
-
概念上:在问题空间中,对象是对客观世界实体的抽象,它可以是任何具体或抽象的事物,如人、物、事件、规则或概念等。
-
实现上:对象是一个封装体,它将一系列数据及其处理过程(即操作或方法)作为一个整体来管理。
类:
-
类是生成对象的模板,它定义了对象的基本结构和行为。类包含了创建对象所需的状态描述和方法定义。对象是根据特定类创建的实例,每个实例都继承了类的数据结构和操作代码,但各自拥有独立的状态(主要体现在内部数据的差异)。
通过这种方式,面向对象程序设计提供了一种模块化和可重用的编程方法,使得代码更加清晰、易于维护和扩展。
类和对象的概念在面向对象编程中,类似于面向过程语言中的数据类型和变量。例如,可以将“动物类”比作数据类型,而具体的“某个动物对象”则相当于该数据类型的变量。
**什么是继承**:
在开发新型号电视机时,有两种设计策略:
A. 从零开始,重新设计整个产品。
B. 基于现有型号进行改进,即继承现有设计并加以优化。
C++语言通过继承机制支持代码重用,允许开发者不仅重用具有特定功能的现有类,还可以通过继承创建新的类。新类继承了父类的属性,并可以添加额外的特性。这种由已有类派生出的新类被称为派生类或子类,而原有的类则被称为父类或基类。
在现实世界中,存在着整体与部分的关系,以及一般与特殊的关系。继承机制正是将这种一般与特殊的关系模型化。例如:
- “汽车类”作为一般概念,而各类具体的汽车则是其特殊化的实例。
- “昆虫类”代表一般性的昆虫概念,而各类具体的昆虫则是其特殊化的体现。
通过继承,面向对象编程能够更好地模拟现实世界的复杂关系,提高代码的可维护性和扩展性。
1.4面向对象三大特性
面向对象编程的三大核心特性是封装、继承和多态。
封装:
封装是面向对象编程的基础,它涉及将数据和操作数据的方法捆绑在一起,形成一个独立的实体,即对象。这种机制隐藏了对象的内部状态和实现细节,只通过公共接口与外部交互,从而提高了代码的安全性和可维护性。
继承:
继承是面向对象编程的关键特性,它允许新创建的类(子类)继承现有类(父类)的属性和方法。这种机制不仅促进了代码的重用,还支持了类的层次化组织,使得程序结构更加清晰和模块化。
多态:
多态是对封装和继承的补充,它指的是同一个名称具有多种不同的含义,或者同一个接口有多种实现方式。多态性体现在,即使消息相同,不同类型的对象接收到该消息后,会根据自身的类型执行相应的操作,从而表现出不同的行为。
例如,函数重载就是多态性的一种简单体现:
void f(char, float);
void f(int, int);
void f(int, float);
当调用函数 f
时,根据传递的参数类型和数量的不同,实际上会调用不同的函数实现,这种现象称为多态。这种机制使得代码更加灵活,能够处理更复杂的情况,同时保持接口的一致性。
二、类的声明定义与应用
2.1 类的定义格式:
类是对现实世界中客观事物的抽象表示,它将具有共同属性的多个事物归为一类。类的具体实例被称为对象。类是一种复杂的数据类型,它将不同类型的数据及其相关操作封装成一个整体。类体现了对数据的抽象性、隐藏性和封装性,确保了数据的安全性和操作的一致性。
类对象的行为由其内部的数据结构和相关操作共同决定,而外部行为则通过操作接口来实现。用户主要关注的是操作接口所提供的服务,这些服务定义了对象可以执行的操作。
类定义通常包含两个主要部分:
A. **说明部分**(“做什么”):
- **数据成员**:定义对象的属性,包括名称和类型。
- **成员函数**(方法):描述对象可以执行的操作。
B. **实现部分**(“怎么做”):
- **成员函数的定义和实现**:详细说明每个成员函数如何执行其操作。
通过这种方式,类不仅定义了对象的结构,还提供了操作这些结构的方法,使得对象能够以一种有组织和可预测的方式与外部世界交互。
类定义的一般形式:
//声明部分
class<类名>
{
public :
公有数据及成员函数
protected:
保护数据及成员函数
private:
私有数据及成员函数
};
//实现部分
<各成员函数的实现>
简单的类常将说明部分和实现部分合并在一起:
//定义类的关键字 class
class CPPclassName { //自定义的类名
public:
void init(int nNumA, int nNumB) {
m_X = nNumA;
m_Y = nNumB;
}
int getx() { return m_X; }//成员函数
int gety() { return m_Y; }
private:
int m_X, m_Y; //数据成员 成员变量
};
在面向对象编程中,关键字 class
用于定义一个类,而 CPPclassName
则是开发者自定义的类名,通常以大写字母开头,以便与对象名区分开来。关键字 public
、private
和 protected
是访问权限控制符,用于规定类成员的访问权限。这些控制符可以以任意顺序出现,且次数不限。
A. 公有(public)成员:
公有成员构成了类的接口,它们不仅可以在类的成员函数内部访问,还可以在程序的其他部分被访问。这些成员提供了类与外部世界交互的途径。
B. 私有(private)成员:
私有成员是类内部隐藏的数据,它们对外部是不可见的,也无法被访问。即使是派生类的函数也不能访问这些私有成员。通常,数据成员会被定义为私有,以保护它们不被外部直接操作。
C. 保护(protected)成员:
保护成员与私有成员类似,外部无法访问,但派生类的函数可以访问这些成员。这种访问权限为类的继承和扩展提供了便利。
注:在这里,“访问”指的是能够读取或修改数据,或者能够调用函数。
例如,getx()
和 gety()
是成员函数,用于访问私有数据成员 x
和 y
。这些函数提供了访问私有数据的接口,确保了数据的安全性和封装性。
2.2 定义对象
对象是类的具体实例,每个对象都属于某个特定的类。在C++编程中,定义一个类相当于创建了一种新的数据类型。一旦类被定义,就可以使用这个类来声明新的变量,即对象。对象的声明格式如下:
<类名> <对象名表>;
例如,定义一个名为 `CPPclassName` 的类的对象可以这样写:
CPPclassName objA, objB, objC[10], *pobjD;
在这个例子中:
- `objA` 和 `objB` 是两个独立的对象。
- `objC[10]` 是一个包含10个对象的数组。
- `pobjD` 是指向 `CPPclassName` 类对象的指针。
对象的成员包括数据成员和成员函数,这与它们所属的类成员相同。当创建对象时,每个对象的数据成员都有自己独立的存储空间,用于存储各自的数据。然而,成员函数的代码是共享的,即所有对象共用同一份函数代码。这种设计有助于节省内存空间,并确保所有对象的行为一致。
2.3 对象及成员的引用
当定义好对象后,我们需要访问对象中的成员,对象访问成员的方法与结构变量访问成员变量的方法相同。
访问一般对象的成员:
<对象名>.<数据成员名>
<对象名>.<成员函数名>(<参数表>)
访问指向对象的指针的成员:
<对象指针名>-><数据成员名>
<对象指针名>-><成员函数名>(<参数表>)
代码示例:
#include <iostream>
#include <stdlib.h>
using std::wcout;
using std::endl;
class CPPclassName {
public:
void init(int nNumA, int nNumB) {
m_X = nNumA;
m_Y = nNumB;
}
int getx() {
return m_X;
}
int gety() {
return m_Y;
}
private:
int m_X, m_Y;
};
int main() {
CPPclassName objA, objB, objC;
objA.init(10, 20);
objB.init(100, 200);
wcout << objA.getx() << ", " << objA.gety() << endl;
wcout << objB.getx() << ", " << objB.gety() << endl;
objC.m_X = 25;//这里会报错,为什么?
objC.m_Y = 250;//这里会报错,为什么?
wcout<<objC. getx ()<<", " << objC.gety() << endl;
system("pause");
return 0;
}
会报错的地方是因为试图访问对象的私有成员。
2.4 定义成员函数
定义类中的成员函数可以采用以下三种方式:
a.成员函数的定义及实现在类体中完成;
b.成员函数的定义及实现在类体外完成;
c.成员函数的定义及实现与类体在不同的文件中完成。
下边举例说明这三种情况
这里设计了一个日期类,能够判断是不是闰年
class CTdate{
public:
void set(int m, int d, int y) {
m_Month = m;
m_Day = d;
m_Year = y;
}
int Isleapyear() {
return (m_Year % 4 == 0 && m_Year % 100 != 0) || (m_Year % 400 == 0);
}
void print() {
cout << m_Month << "/" << m_Day << "/" << m_Year << endl;
}
private:
int m_Month; int m_Day; int m_Year;
};
在类体外定义成员函数时须按下述格式:
<函数类型> <类名>::<成员函数名>(<参数表>) {
<函数体>
}
void CTdate::set(int m,int d, int y){
m Month=m; mDay=d; mYear=y;
}
其中,作用域运算符“::”是用来标识某个成员函数是属于哪个类的,下面就是一个在类外部定义与实现成员函数的方法
class CTdate {
public:
void set(int m, int d, int y);
int Isleapyear();
void print();
private:
int m_Month;
int m_Day;
int m_Year;
};
//下面实现在其他文件cpp写也可以,上面的定义也可以放在其他头文件里面
void CTdate::set(int m, int d, int y) {
m_Month = m;
m_Day = d;
m_Year = y;
}
int CTdate::Isleapyear() {
return (m_Year % 4 == 0 && m_Year % 100 != 0) || (m_Year % 400 == 0);
}
void CTdate::print() {
cout << m_Month << "/"
<< m_Day << "/"
<< m_Year << endl;
}
成员函数的定义及实现与类体,在不同的文件中完成也可以。
2.5调用成员函数
一个对象要表现其行为,就要调用它的成员函数。前面已经介绍了调用成员函数的方法。下面举例说明常用的几种调用方式用成员访问符调用
CTdate objA;
objA.set(4, 24, 2024);
objA.print();
CTdate objA;
objA.set(7, 25, 2023);
objA.print();
CTdate* pobjA;
pobjA = &objA; //指向 CTdate 类对象的指针
pobjA->print();
CTdate objA;
objA.set(7, 23, 2016);
objA.print();
CTdate& ref = objA; //ref是 objA 对象的引用
ref.print();
2.6 this 指针
成员函数必须使用对象来调用。一个类的所有对象调用的成员函数都是同一个代码段,例如:
void CTdate::set(int m,int d,int y){
m_Month = m;
m_Day = d;
m_Year =y;
}
成员函数如何识别m_Month、m_Day和m_Year 是属于哪个调用对象呢?成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针,通过this指针保证了每个对象可以拥有不同的数据成员,处理这些成员的代码可以被所有对象共享。
当对象s调用s.set(3,15,2024)时,实际上传递了4个参数,除了接收传递的3个参数外,还接收到正在调用成员函数的对象s的地址,这个地址放入隐含的形参this指针中。等同于执行this=&s;语句。所以对成员函数内数据成员的访问都隐含地加上了 this 指针。
因此 m_Month=m;等价于 this->m_Month=m;
故而set()函数还可以表示成:
void CTdate::set(int m, int d, int y){
this-> m Month =m;
this-> m Day=d ;
this->m Year =y;
}
因此,无论哪个对象调用成员函数,该函数都能通过接收的参数(包括显式的实际参数和隐含的对象地址)准确识别当前操作的对象。编译器引入的 `this` 指针在此起到了关键作用,它帮助成员函数记住当前正在访问的对象,确保能够正确地访问和操作对象的数据成员。
需要注意的是:
1. 一个类对象所占用的内存空间大小,完全由其数据成员所占用的数据空间总和决定。
2. 类的成员函数本身并不占用对象的内存空间,这意味着所有对象共享同一份成员函数代码,从而节省了内存资源。
2.6 程序结构
类的作用域,简称类域,指的是在类定义的类体内部,该类的成员仅限于该类所属的类域内。在类域内,一个类的任何成员都可以访问同一类的其他成员。对于类作用域外的访问,程序员可以通过控制访问权限来限制,例如将成员定义为私有或保护,以限制外界的访问。类域可以被包含在文件域中,因此类域的范围小于文件域;同时,类域内又可以包含函数域,因此类域的范围又大于函数域。类域位于文件域和函数域之间,形成了一个介于两者之间的作用域层次。
对象的存储类别决定了其生命周期。对象的生存期是指对象从创建到被销毁的整个存在时间,即对象的寿命。根据生命周期的不同,对象可以分为以下三种类型:
1. **局部对象**:定义在函数体内或程序块内的对象,其作用域和生命周期都是局部的。
2. **全局对象**:定义在文件中的对象,其作用域覆盖整个程序,生命周期是全局的。
3. **静态对象**:分为内部静态对象和外部静态对象。两者的生命周期都是全局的,但作用域不同:内部静态对象的作用域限于定义它的函数体或程序块内;外部静态对象的作用域限于定义它的文件。
这些不同类型的对象在程序中扮演着不同的角色,程序员可以根据需要选择合适的对象类型来管理内存和控制程序的执行流程。
类名允许与其他变量名或函数名同名,可通过下面方法实现正确的访问
A. 如果一个非类型名隐藏了类型名,则类型名通过加前缀class访问:
class Sample{
//...
}
void func(intSample){//形参屏蔽了类名
class Sample a; //类名前加class
Sample++; //形参自增运算
//...
}
如果一个类型名隐藏了一个非类型名,则用一般作用域规则访问
int S=0;
void func() {
class S a; //类S屏蔽了全局变量S定义类对象a
::S = 3; //引用全局变量前加作用域符
}
int g = S; //全局变量S给变量g初始化
定义一个类就是实现对创建一个对象的数据结构的描述,在类中,一些成员是保护的,被有效地屏蔽,以防外界的干扰和误操作。另一些成员是公共的,作为接口提供外界使用。
另外在C++中,类与结构体基本一致,结构体可以有成员函数,也可以继承,多态,也可以有构造函数与析构函数。他们仅有如下区别:
默认访问区别:
类定义中默认情况下的成员访问级别是private。
结构体定义中默认情况下的成员访问级别是public。