1、面向对象编程的特性:

抽象、封装和数据隐藏、多态、继承、代码的可重用性

面向对象编程(Object-Oriented Programming, OOP)是一种软件开发范式,其核心思想是将现实世界中的事物抽象为对象,对象之间通过消息传递来完成任务。面向对象编程具有以下主要特性:

  1. 抽象(Abstraction):抽象是将复杂的现实世界问题简化为程序设计中的对象模型。通过抽象,可以忽略对象的具体细节,只关注对象的重要特征和行为,从而更容易理解和处理问题。

  2. 封装和数据隐藏(Encapsulation and Data Hiding):封装是将对象的属性(数据)和行为(方法)打包在一起,形成一个相对独立的单元。封装可以隐藏对象的内部实现细节,只向外界提供有限的访问接口,从而增强了代码的安全性和可维护性。

  3. 多态(Polymorphism):多态是指相同的消息可以被不同类型的对象接收和处理,实现了同一接口的多种不同表现形式。多态分为编译时多态(静态多态,如函数重载)和运行时多态(动态多态,如虚函数)两种形式,可以提高代码的灵活性和可扩展性。

  4. 继承(Inheritance):继承是指一个类(子类)可以继承另一个类(父类)的属性和方法。通过继承,子类可以重用父类的代码,并在此基础上进行扩展和修改,减少了代码的重复性,提高了代码的可维护性和扩展性。

  5. 代码的可重用性(Code Reusability):面向对象编程提倡将问题分解为多个独立的对象,并通过组合、继承等方式组合这些对象来解决问题。这种模块化的设计使得代码更容易重用,可以在不同的项目中使用相同的对象或对象集合来实现相似的功能,提高了开发效率和代码的可维护性。

2、过程性编程

过程性编程是一种以过程或函数为中心的编程范式,程序主要由一系列按照顺序执行的函数或过程组成。在过程性编程中,主要考虑的是问题的解决步骤和数据的处理方式,而不是将数据和行为封装在对象中。

下面是过程性编程的一般步骤以及对数据的处理方式:

  1. 定义问题解决步骤:在过程性编程中,首先要明确问题的解决步骤,确定程序的主要流程和执行顺序。通常会将问题分解为一系列逻辑上相关的步骤或子任务,并按照顺序编写对应的函数或过程来实现这些步骤。

  2. 表示数据:其次,需要考虑如何表示数据以及数据之间的关系。在过程性编程中,数据通常以基本数据类型或结构体的形式进行表示。数据之间的关系通过函数参数的传递来实现。

  3. 文件存储和读取:在程序设计中,用户可能希望将数据存储在文件中,以便稍后读取或持久化数据。在过程性编程中,可以使用文件操作函数来实现数据的存储和读取。例如,使用文件输入输出流来将数据写入文件或从文件中读取数据。

在面向对象编程中,除了考虑数据的表示方式外,还需要考虑如何使用数据和数据之间的关系。具体来说,面向对象编程需要关注以下几个方面:

  1. 数据和行为的封装:面向对象编程将数据和操作数据的行为封装在对象中,通过定义类来描述对象的属性和方法。这样可以更好地组织和管理代码,提高代码的可维护性和可重用性。

  2. 数据的使用:面向对象编程强调对象之间的交互和协作,通过定义对象之间的关系和接口来实现数据的共享和交换。对象可以通过方法调用来访问和修改数据,从而实现对数据的有效使用。

  3. 数据的持久化:面向对象编程通常使用对象关系映射(ORM)或序列化技术来实现数据的持久化。这样可以将对象保存到数据库中或将对象序列化为文件,以便稍后读取或恢复对象状态。

3、类是用户定义类型的定义,类规范由两个部分组成:

类是面向对象编程的基本构建块,它是一种用户自定义的数据类型,用于描述具有相似属性和行为的对象集合。一个类的规范由两个主要部分组成:

  1. 类声明(Class Declaration):类声明部分描述了类的成员变量(数据成员)和成员函数(方法),但并不提供实现细节。在类声明中,可以定义公有、私有和保护的成员,以及静态成员、常量成员等。类声明通常放在头文件中,用于向外界提供类的接口。

    • 数据成员(Member Variables):描述了类的对象的属性或状态。数据成员可以是任何合法的数据类型,包括基本数据类型、数组、结构体、类对象等。

    • 成员函数(Member Functions):描述了类的对象的行为或操作。成员函数定义了对象可以执行的操作,包括访问和修改数据成员、执行特定的计算或处理等。成员函数可以分为公有成员函数、私有成员函数和保护成员函数等,以控制对类的接口的访问权限。

  2. 类方法定义(Class Method Definition):类方法定义部分实现了类声明中的成员函数,即描述了如何实现类的接口。类方法定义通常放在源文件中,包含了成员函数的具体实现代码。在类方法定义中,可以通过函数体来实现类的成员函数,包括对数据成员的操作、具体的计算逻辑等。

    • 成员函数实现(Member Function Implementation):在类方法定义中,编写了成员函数的具体实现代码。在成员函数实现中,可以通过成员函数的参数和数据成员来操作和处理数据,实现类的特定功能。

    • 成员函数内联(Member Function Inline):可以选择将成员函数的定义直接放在类声明中,并在函数声明前加上 inline 关键字,使得成员函数变成内联函数。内联函数的定义在类声明中,可以提高函数调用的效率,但会增加代码的长度。

4、内联函数:

其定义位于类声明中的函数自动成为内联函数,也可以在类声明外定义内联函数,但要加上关键字inline

  1. 类构造函数
    • 类构造函数(Constructor)是一种特殊的成员函数,用于在创建新对象时初始化对象的数据成员。构造函数的名称与类名相同,没有返回类型(包括void),因为它们自动返回类的实例。构造函数可以有参数,用于向对象传递初始值。
    • 构造函数的作用是确保对象在创建时具有合适的初始状态,通常用于初始化对象的数据成员、分配资源等操作。构造函数在对象创建时自动调用,不能手动调用。如果没有显式定义构造函数,编译器会自动生成默认构造函数(不带参数的构造函数)。
    • 构造函数的参数名不能与类的成员变量名相同,但可以与函数参数名相同,因为构造函数参数是局部变量,不会与类的成员变量冲突。
  2. 析构函数
    • 类析构函数(Destructor)是类的一种特殊成员函数,用于在对象销毁时执行必要的清理工作,例如释放动态分配的内存、关闭文件等。析构函数的名称与类名相同,前面加上波浪号“~”,没有参数和返回类型。
    • 析构函数在对象销毁时自动调用,通常用于清理对象使用的资源,防止内存泄漏和资源泄漏。如果不显式定义析构函数,编译器会生成默认析构函数,它不执行任何操作。
    • 析构函数通常在对象的生命周期结束时调用,例如对象超出作用域、delete操作符释放对象等。
  3. this指针
    • this指针是C++中的一个隐含的指针,它指向当前对象的地址。this指针可以在成员函数中使用,用于访问当前对象的数据成员和成员函数。
    • 当成员函数涉及到两个对象进行操作时,需要使用this指针来区分不同的对象,确保对正确的对象进行操作。
    • this指针是一个常量指针,不能被修改,因此它可以被声明为常量成员函数的隐含参数。在常量成员函数中,this指针指向的对象是常量对象,因此不能修改对象的数据成员。
  4. 类如何实现抽象、封装和数据隐藏
    • 抽象:类通过提供公有接口和实现细节的隐藏来实现抽象。类的成员函数定义了类的公有接口,外部代码只能通过这些公有接口来访问类的功能,而不需要了解类的内部实现细节。
    • 封装:封装通过将数据成员和成员函数打包在一起,形成一个相对独立的单元。类的数据成员可以被声明为私有的,只能通过类的成员函数来访问,从而隐藏了数据的具体实现细节,提高了代码的安全性和可维护性。
    • 数据隐藏:数据隐藏通过将类的数据成员声明为私有的,只能通过类的成员函数来访问,从而避免了外部代码直接访问和修改数据成员。外部代码只能通过类的公有接口来间接访问数据成员,实现了数据的隐藏和保护。
  5. 默认构造函数:默认构造函数是没有参数或所有参数都有默认值的构造函数。当类没有显式定义构造函数时,编译器会自动生成默认构造函数。默认构造函数用于创建对象时不需要提供任何参数,可以在声明对象时不进行初始化。
  6. 运算符重载:运算符重载是C++中一种形式的多态。它允许程序员重新定义运算符的含义,使得同一个运算符能够用于不同类型的操作数,以实现更加灵活的运算。通过运算符重载,可以定义自定义类型的运算符行为,提高代码的可读性和可维护性。
  7. 友元函数:友元函数是指被声明为类的友元的非成员函数。通过友元函数,可以赋予该函数与类的成员函数相同的访问权限,使其能够访问类的私有成员。类可以将其他函数声明为友元函数,从而授权这些函数访问类的私有数据,但友元函数本身并不是类的成员函数。
  8. 转换函数:转换函数是一种特殊的成员函数,用于实现类型转换。它允许将对象从一种类型转换为另一种类型。转换函数必须是类的成员函数,不能指定返回类型,也不能有参数。
  9. 复制构造函数:复制构造函数用于将一个对象的值复制到新创建的对象中。它是一种特殊的构造函数,用于在对象复制或传递时调用。复制构造函数通常以引用方式接受参数,并通过参数初始化新对象的数据成员。
  10. 默认复制构造函数:默认复制构造函数是由编译器自动生成的复制构造函数。它逐个将对象的成员赋值给新对象,包括静态成员。这种赋值方式也称为浅复制,适用于简单的数据类型或指针。
  11. 显式复制构造函数:显式复制构造函数也称为深度复制构造函数。它是由程序员显式定义的复制构造函数,用于处理特定的复制需求,例如在对象包含动态分配内存或其他资源时。显式复制构造函数可以实现深度复制,确保新对象拥有独立的资源副本。
  12. 类继承:类继承是面向对象编程中的重要概念,它允许从已有的类派生出新的类,新类称为派生类,原有类称为基类或父类。派生类继承了基类的特征和行为,并可以在此基础上添加新的特性或行为。派生类可以直接访问基类的公有成员和保护成员,但不能直接访问基类的私有成员。要访问基类的私有成员,必须通过基类的公有或保护成员函数进行间接访问。
  13. C++的三种继承方式
  • 公有继承(Public Inheritance):派生类继承了基类的公有成员和保护成员,基类的私有成员在派生类中不可访问。公有继承体现了"is-a"关系,即派生类对象也是基类对象。
  • 保护继承(Protected Inheritance):派生类继承了基类的保护成员,基类的公有成员和私有成员在派生类中不可访问。保护继承对外部对象隐藏了基类的公有成员,但仍然可以被派生类访问。
  • 私有继承(Private Inheritance):派生类继承了基类的私有成员,基类的公有成员和保护成员在派生类中不可访问。私有继承完全隐藏了基类的接口和实现细节,只能通过派生类的成员函数来访问基类的成员。
  1. 多态和公有继承
  • 多态(Polymorphism)是面向对象编程中的一个重要概念,指同一个方法在不同对象中的行为不同。多态可以通过函数重载、运算符重载和虚函数等方式实现。
  • 公有继承可以实现多态性,尤其是通过虚函数(Virtual Function)实现的动态多态性。在公有继承中,基类中的虚函数可以被派生类重新定义,从而实现不同对象对同一方法的不同行为。当通过基类的指针或引用调用虚函数时,程序会根据指针或引用所指向的对象的实际类型来确定调用哪个版本的虚函数,从而实现多态性。
  • 使用虚函数是实现多态性的一种常见方式,因为它允许在运行时动态地确定函数的调用版本,而不是在编译时静态地绑定函数的调用版本。这样可以实现更加灵活和可扩展的代码设计,提高代码的可维护性和可扩展性。
  1. 派生类不能从基类继承的内容
  • 派生类不能继承基类的构造函数、析构函数、赋值运算符重载函数以及友元函数。
  • 构造函数和析构函数不是普通的成员函数,它们是用于创建和销毁对象的特殊函数,在派生类中不能直接继承。
  • 赋值运算符重载函数和友元函数是与类关联的函数,不是类的成员函数,因此也不能继承。
  1. 派生类是否需要定义构造函数
  • 是的,每个类都必须至少有一个构造函数。即使派生类没有添加任何数据成员,也需要定义一个构造函数,用于初始化基类的部分。
  1. 派生类何时需要定义赋值运算符
  • 如果派生类具有指针成员,并且在构造函数中使用了动态内存分配,那么通常需要定义赋值运算符重载函数。
  • 当派生类对象被赋值给另一个派生类对象时,需要确保指针成员的正确拷贝和资源释放,此时定义赋值运算符重载函数可以保证对象赋值的正确性。
  1. 按引用传递对象和按值传递对象的效率比较
  • 按引用传递对象比按值传递对象的效率更高。这是因为按引用传递对象不会复制整个对象,而是传递对象的引用(地址),从而节省了内存和时间。
  • 另外,按引用传递对象可以确保函数调用从虚函数中受益,因为传递的是对象的引用,而不是对象的副本。这可以避免对象切片(Object Slicing)的问题,提高了程序的健壮性和可扩展性。
  1. 使用explicit关闭隐式转换
  • 在C++中,explicit关键字用于修饰单参数的构造函数,以防止隐式类型转换。当构造函数被声明为explicit时,它只能用于显式地创建对象,不能被用于隐式类型转换。
  • 通过使用explicit关键字,可以避免意外的类型转换,提高代码的清晰度和安全性。
  1. 关键字template<class Type>用于定义模板
  • template<class Type>是C++中定义模板的关键字,它用于声明一个模板类或模板函数。通过模板,可以编写通用的代码,使得代码具有更好的复用性和灵活性。
  • 模板类和模板函数可以根据不同的类型参数生成不同的实例,从而实现对不同数据类型的支持。
  1. 虚基类的概念
  • 虚基类用于解决多重继承中的菱形继承问题。当一个类同时从两个不同的路径继承同一个基类时,如果这两个基类中含有相同的成员,派生类将会包含两份相同的成员,导致二义性。
  • 为了避免这种二义性,可以将这个基类声明为虚基类。虚基类的成员在派生类中只有一份拷贝,从而消除了二义性。在继承体系中,最终派生类对虚基类的构造负责,保证了基类成员的唯一性。