C++面向对象编程学习

news2024/11/27 16:29:42

C++面向对象编程学习

  • 前言
  • 一、C++面向对象编程
  • 二、知识点学习
    • 1. 定义一个类
      • 1.1 使用struct定义
      • 1.2 使用class定义
      • 1.3 struct和class的区别
    • 2. 类的定义方式
      • 2.1 单文件定义(Inline Definition)
      • 2.2 分离定义(Separate Definition)
      • 2.3 头文件守卫
    • 3. 访问修饰符
    • 4. 构造函数与析构函数
      • 4.1 构造函数(Constructor)
      • 4.2 析构函数(Destructor)
    • 5. this指针
    • 6. 虚函数
    • 7. 继承
    • 8. 其余内容
  • 总结


前言

注意:本文只适合有C++基础的朋友食用!另本文章目的在于记录学习C++面向编程学习的过程,会引用到其他人的文章。
推荐文章:【C++核心】一文理解C++面向对象(超级详细!)(作者:数据知道)


一、C++面向对象编程

先由一段C++面向对象编程的典例的代码引入学习:

#include <iostream>
#include <string>

// 基类:Animal
class Animal {
public: // 访问权限
    // 构造函数
    Animal(std::string name) : name_(name) {}

    // 虚析构函数
    virtual ~Animal() {}

    // 虚函数,用于发声
    virtual void makeSound() const = 0; // 纯虚函数

    // 打印动物的名字
    void printName() const {
        std::cout << "My name is " << name_ << std::endl;
    }

protected:
    std::string name_; // 动物的名字
};

// 派生类(子类):Dog
class Dog : public Animal {
public:
    // 构造函数
    Dog(std::string name) : Animal(name) {}

    // 实现基类的纯虚函数
    void makeSound() const override {
        std::cout << name_ << " says: Bark!" << std::endl;
    }
};

// 派生类:Cat
class Cat : public Animal {
public:
    // 构造函数
    Cat(std::string name) : Animal(name) {}

    // 实现基类的纯虚函数
    void makeSound() const override {
        std::cout << name_ << " says: Meow!" << std::endl;
    }
};

// 函数模板,用于处理任何类型的Animal
template <typename AnimalType>
void makeAnimalSound(const AnimalType& animal) {
    animal.makeSound();
}

int main() {
    Dog dog("Rex");
    Cat cat("Whiskers");

    dog.printName();
    dog.makeSound();

    cat.printName();
    cat.makeSound();

    // 使用模板函数
    makeAnimalSound(dog);
    makeAnimalSound(cat);

    return 0;
}

二、知识点学习

1. 定义一个类

在C++中,structclass关键字都可以用来定义一个类,但它们在默认的访问权限上有所不同。以下是使用structclass定义同一个类的例子:

1.1 使用struct定义

struct MyStruct {
    int publicData; // 默认为public

private:
    int privateData; // 私有成员

public:
    MyStruct(int value) : privateData(value) {}

    int getPrivateData() const {
        return privateData;
    }

    void setPrivateData(int value) {
        privateData = value;
    }

private:
    void privateMethod() {
        // 私有方法
    }
};

1.2 使用class定义

class MyClass {
    int publicData; // 默认为private

private:
    int privateData; // 私有成员

public:
    MyClass(int value) : privateData(value) {}

    int getPrivateData() const {
        return privateData;
    }

    void setPrivateData(int value) {
        privateData = value;
    }

private:
    void privateMethod() {
        // 私有方法
    }
};

1.3 struct和class的区别

  • struct中,如果没有指定访问修饰符,成员默认是public的。
  • class中,如果没有指定访问修饰符,成员默认是private的。

从C++11起,你可以使用struct来定义一个简单的数据结构(没有或很少的行为),而class通常用于定义具有封装、继承和多态特性的对象(强调一个类型是具有封装、继承和多态的对象)。然而,这只是一个编程习惯上的区分,技术上你可以使用任何一个关键字来定义任何类型的类。

2. 类的定义方式

在C++中,类的定义通常有两种主要方式:一种是在单个文件中定义类的全部内容,另一种是将类的声明(通常称为头文件)和定义(通常称为源文件)分开。

  • 单文件定义:通常用于小型项目或简单的类,其中类的实现非常简短,或者为了减少编译依赖。
  • 分离定义:通常用于大型项目或复杂的类,其中需要隐藏实现细节,提高代码的可维护性和可读性。

2.1 单文件定义(Inline Definition)

在这种方式中,类的定义被直接放在头文件中,通常是在.h.hpp扩展名的文件中。这种方式被称为内联定义或头文件中的定义。

示例:

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    void function() {
        // 实现细节
    }
private:
    int data;
};

#endif // MYCLASS_H

在这种方式中,每次包含头文件时,类的定义都会被包含到使用它的每个源文件中。这可能会导致代码重复,但可以减少编译依赖和提高编译速度。

2.2 分离定义(Separate Definition)

就是类的声明放在头文件(.h.hpp),类的(定义)实现放在源文件(.cpp)。

头文件( MyClass.h ):

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    void function();
private:
    int data;
};

#endif // MYCLASS_H

源文件( MyClass.cpp ):

// MyClass.cpp
#include "MyClass.h"

void MyClass::function() {
    // 实现细节
    data = 42; // 访问私有成员
}

在这种方式中,类的实现被隐藏在源文件中,只有声明被包含在其他文件中。这样做的好处是增加了模块化,减少了重复代码,并且使得单个类的定义更容易维护。

2.3 头文件守卫

这段代码是C++预处理器指令,用于防止头文件被多次包含,这是一种常见的头文件保护技术。这种技术被称为“包含保护”或“头文件守卫”。下面是每个指令的解释:

  1. #ifndef MYCLASS_H - 这个指令检查一个宏(在这个例子中是MYCLASS_H)是否还未定义。#ifndef是“if not defined”的缩写。如果MYCLASS_H没有定义,那么预处理器会继续处理下面的代码。

  2. #define MYCLASS_H - 这个指令定义了MYCLASS_H宏。这样,如果这个头文件被包含多次,预处理器会记住MYCLASS_H已经被定义了,并且跳过第二次及后续的包含。

  3. #endif // MYCLASS_H - 这个指令标记了预处理条件块的结束。#endif指示预处理器停止处理条件块。注释// MYCLASS_H是为了提供额外的信息,说明这个#endif与哪个#ifndef配对。这是一个编程习惯,有助于在阅读代码时快速识别匹配的指令。

包含保护的目的是防止头文件的内容被编译多次,这可能发生在多个源文件都包含了同一个头文件的情况下。如果没有包含保护,每次头文件被包含时,它的代码都会被重新编译,这不仅会导致编译错误(如重复定义),还可能降低编译效率。

例如,如果你有以下头文件和源文件结构:

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
    // 类的定义
};

#endif // MYCLASS_H

// main.cpp
#include "MyClass.h"
#include "MyClass.h" // 第二次包含相同的头文件

int main() {
    MyClass myObject;
    // 使用myObject
}

在没有包含保护的情况下,MyClass的定义会被包含两次,导致编译器报错,因为它尝试定义同一个类两次。有了包含保护,预处理器会识别出第二次包含是重复的,并忽略它,从而避免编译错误。

请注意,#endif指令前的注释// MYCLASS_H是可选的,但它有助于在阅读代码时快速定位匹配的#ifndef指令。

3. 访问修饰符

(1)public--------公共的,类外部可以访问;
(2)protected—保护的,类外部不可以访问;
(3)private-------私有的,类外部不能访问;

解析:

  1. public(公共的)

    • 类外部可以访问:类的公共成员在定义类的外部是可见的,可以直接通过类的实例来访问。
    • 示例
      class MyClass {
      public:
          int publicData; // 公共成员,可以直接访问
          void publicMethod() {} // 公共成员函数,可以直接调用
      };
      
      MyClass obj;
      obj.publicData = 10; // 直接访问公共成员变量
      obj.publicMethod(); // 直接调用公共成员函数
      
  2. protected(保护的)

    • 类外部不可以访问:类的受保护成员在定义类的外部是不可见的,不能直接通过类的实例来访问。
    • 但可以在派生类中访问:如果一个类派生自具有受保护成员的基类,那么这些受保护的成员在派生类中是可见的。
    • 示例
      class Base {
      protected:
          int protectedData; // 受保护成员,不能直接访问
      };
      
      class Derived : public Base {
      public:
          void accessBaseMembers() {
              protectedData = 20; // 在派生类中可以访问基类的受保护成员
          }
      };
      
      Derived obj;
      // obj.protectedData = 20; // 错误:在类外部不能直接访问受保护成员
      obj.accessBaseMembers(); // 正确:通过派生类的公共接口访问基类的受保护成员
      
  3. private(私有的)

    • 类外部不能访问:类的私有成员在定义类的外部是不可见的,不能直接通过类的实例来访问。
    • 类内部可以访问:私有成员只能在定义它们的类内部被访问,包括类的成员函数和友元类或友元函数。
    • 示例
      class MyClass {
      private:
          int privateData; // 私有成员,不能直接访问
          void privateMethod() {} // 私有成员函数,不能直接调用
      
      public:
          void publicMethod() {
              privateData = 30; // 在类内部可以访问私有成员
              privateMethod(); // 在类内部可以调用私有成员函数
          }
      };
      
      MyClass obj;
      // obj.privateData = 30; // 错误:在类外部不能直接访问私有成员
      // obj.privateMethod(); // 错误:在类外部不能直接调用私有成员函数
      obj.publicMethod(); // 正确:通过公共成员函数间接访问私有成员
      

4. 构造函数与析构函数

在C++中,构造函数和析构函数是特殊的成员函数,它们分别用于初始化对象和清理对象使用资源。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。语法格式如下:
构造函数语法:类名(){}

析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。语法格式如下:
析构函数语法: ~类名(){}

4.1 构造函数(Constructor)

构造函数是一种特殊的成员函数,用于创建类的对象时初始化对象。它具有与类相同的名称,且没有返回类型,也不能被声明为constvolatilestatic。构造函数可以有参数,也可以没有参数(无参数构造函数),可以有多个构造函数(构造函数重载),以适应不同的初始化需求。

特点

  • 没有返回值,也不抛出异常(除了析构函数外)。
  • 可以有0个或多个参数。
  • 可以有默认参数。
  • 可以被重载以提供不同的初始化方式。

示例

class MyClass {
public:
    // 无参数的构造函数
    MyClass() {
        // 初始化代码
    }

    // 带参数的构造函数
    MyClass(int value) {
        // 使用参数value进行初始化
    }

    // 带多个参数的构造函数
    MyClass(int value, const std::string& name) {
        // 使用参数value和name进行初始化
    }
};

4.2 析构函数(Destructor)

析构函数是一种特殊的成员函数,用于对象生命周期结束时执行清理工作。它用于释放对象在生命周期中分配的资源,如动态分配的内存、文件句柄、网络连接等。析构函数的名称是在类名前加上波浪号(~),且析构函数不能有参数,不能有返回值,也不能被重载。

特点

  • 没有返回值。
  • 不能带有参数。
  • 不能被重载。
  • 一个类只能有一个析构函数。

示例

class MyClass {
public:
    // 构造函数
    MyClass() {
        // 分配资源,如动态内存
    }

    // 析构函数
    ~MyClass() {
        // 释放资源,如动态内存
    }
};

在上述示例中,当使用new创建MyClass对象时,会调用构造函数进行初始化。当对象不再需要时,比如超出作用域或者使用delete时,会调用析构函数进行清理。

5. this指针

在C++中,this 指针是一个特殊的指针,它在每个非静态成员函数内部可用。this 指针指向调用成员函数的对象本身,它允许成员函数访问对象的数据成员和成员函数。
作用

  1. 访问对象的成员:成员函数经常需要访问调用它的那个对象的成员变量和其他成员函数。this 指针使得成员函数能够区分不同的对象,即使它们拥有相同的成员名称。

  2. 链式调用this 指针可以用来返回对象本身,从而实现链式调用。

  3. 区分成员变量和局部变量:当成员函数中的局部变量与类的成员变量同名时,this 指针可以用来区分它们。

示例

class MyClass {
    int value;

public:
    MyClass(int val) : value(val) {}

    int getValue() const {
        return value; // 使用this指针访问成员变量
    }

    MyClass& set(int val) { // 成员函数返回对象的引用
        this->value = val;
        return *this; // 使用this指针返回对象本身
    }

    void printValue() const {
        int localValue = 10; // 局部变量
        int* memVarPtr = &this->value; // 使用this指针区分成员变量和局部变量
        int* localVarPtr = &localValue;
        // 输出指针地址以区分成员变量和局部变量
        std::cout << "Member variable value: " << *memVarPtr << std::endl;
        std::cout << "Local variable value: " << *localVarPtr << std::endl;
    }
};

int main() {
    MyClass obj(100);
    obj.printValue(); // 输出对象的成员变量和局部变量的值
    obj.set(200).set(300).set(400); // 链式调用
    std::cout << "Final value: " << obj.getValue() << std::endl; // 输出最终值
    return 0;
}

注意事项

  • this 指针在静态成员函数中不可用,因为静态成员函数不依赖于具体的对象实例。
  • this 指针在普通成员函数中是隐式定义的,不需要声明。
  • this 指针通常不需要手动管理,但了解其存在和作用对于编写更清晰和更有效的代码是有帮助的。
  • 在异常情况下,如果成员函数被非法调用(例如,在一个未初始化的对象上),this 指针可能不是一个有效的指针,因此在处理异常和错误时需要小心。

6. 虚函数

语法格式:

virtual ReturnType FunctionName(Parameters) {
    // 函数实现
}

这里的virtual关键字告诉编译器这个函数是虚函数,可以被子类重写。

当通过基类指针或引用调用虚函数时,如果对象实际是派生类的实例,那么将调用派生类中的版本,这就是多态。如果对象是基类实例,则调用基类中的版本。

例如:

class Base {
public:
    virtual void show() {
        cout << "Base class show()" << endl;
    }
};

class Derived : public Base {
public:
    void show() override { // C++11中引入了override关键字来明确指出函数重写了基类中的虚函数
        cout << "Derived class show()" << endl;
    }
};

int main() {
    Base* b;
    Derived d;

    b = &d; // 基类指针指向派生类对象
    b->show(); // 输出Derived class show(),展示了多态性

    return 0;
}

在这个例子中,尽管b是基类Base的指针,但由于它指向的是派生类Derived的对象,所以调用的实际上是Derived类中的show方法。

7. 继承

语法:class A : public B;
MyFrame::MyFrame() 表示MyFrame()是MyFrame的构造函数。
继承的主要优点包括:

  1. 代码重用:可以减少代码冗余,提高开发效率。
  2. 扩展性:可以基于现有的类进行扩展,增加新的功能。
  3. 维护性:修改基类会影响到所有派生类,简化了维护工作。

在C++中,继承是通过在类定义时使用:和继承类型关键字(如public, protected, private)来实现的。例如:

class Base {
public:
    void show() {
        cout << "Base class show()" << endl;
    }
};

class Derived : public Base { // 使用public继承
public:
    void display() {
        cout << "Derived class display()" << endl;
    }
};

int main() {
    Derived d;
    d.show(); // 继承自Base类的show方法
    d.display(); // Derived类特有的display方法

    return 0;
}

在这个例子中,Derived类通过public关键字继承了Base类。这意味着Derived类的对象可以访问Base类中所有公有的成员。

继承类型关键字决定了基类成员在派生类中的访问级别:

  • public继承:基类中的公有成员和保护成员在派生类中仍然是公有的和保护的。
  • protected继承:基类中的公有成员和保护成员在派生类中都变成保护的。
  • private继承:基类中的公有成员和保护成员在派生类中都变成私有的。

8. 其余内容

推荐文章:【C++核心】一文理解C++面向对象(超级详细!)(作者:数据知道)


总结

本来学的C++没有学到面向对象编程这块,应为主要用来刷算法题,但由于最近一个项目学习需要到此方面知识,故来学习一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文2500字从0到1实现压测自动化!

大家好&#xff0c;我是小码哥&#xff0c;最近工作有点忙&#xff0c;一直在实现压测自动化的功能&#xff0c;今天来分享一下实现思路 我所在的业务线现在项目比较少了&#xff0c;所以最近一个月我都没有做业务测试&#xff0c;需求开发完后RD直接走免测就上线&#xff0c;…

利用Spring Boot实现信息化教学平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理信息化在线教学平台的相关信息成为必然。开…

Ansible自动化运维管理工具

一、Ansible 1.1、自动化运维管理工具有哪些&#xff1f; 工具架构语言使用情况Ansible无clientpython 协议用ssh95%puppetC/Sruby 协议用http基本不用chefC/Sruby 协议用http基本不用saltstackC/Spython 协议用ssh5% 1.2、Ansible简介 Ansible是一个基于Py…

深度学习 简易环境安装(不含Anaconda)

在Windows上安装深度学习环境而不使用Anaconda&#xff0c;下面是一个基于pip的安装指南&#xff1a; 1. 安装Python 确保你已经安装了Python。可以从Python官网下载Python&#xff0c;并在安装时勾选“Add Python to PATH”选项。 注意&#xff0c;Python 不要安装最新版的…

期权懂|期权止损策略如何平衡风险与收益?

本期让我懂 你就懂的期权懂带大家来了解&#xff0c;期权止损策略如何平衡风险与收益&#xff1f;有兴趣的朋友可以看一下。期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 期权止损策略如何平衡风险与收益&#xff1f; 期权止损…

如何写一个视频编码器演示篇

先前写过《视频编码原理简介》&#xff0c;有朋友问光代码和文字不太真切&#xff0c;能否补充几张图片&#xff0c;今天我们演示一下&#xff1a; 这是第一帧画面&#xff1a;P1&#xff08;我们的参考帧&#xff09; 这是第二帧画面&#xff1a;P2&#xff08;需要编码的帧&…

计算机网络—静态路由

1.0 网络拓扑结构 星型拓扑结构是一个中心&#xff0c;多个分节点。它结构简单&#xff0c;连接方便&#xff0c;管理和维护都相对容易&#xff0c;而且扩展性强。网络延迟时间较小&#xff0c;传输误差低。中心无故障&#xff0c;一般网络没问题。中心故障&#xff0c;网络就出…

MIT-OC Electrochemical Energy Systems 1-2

一、等效电路模型 L2 电化学能量转换 1. 电化学能量转换与原电池 原电池可以将不同形式的能量&#xff08;化学能、太阳能、机械压力等&#xff09;转化为电能和热能。本文档讨论了一些原电池的示例及其等效电路模型。电压源&#xff1a;特性&#xff1a;电压源的特点是提供…

从网络请求到Excel:自动化数据抓取和保存的完整指南

背景介绍 在投资和财经领域&#xff0c;论坛一直是投资者们讨论和分享信息的重要平台&#xff0c;而东方财富股吧作为中国最大的财经论坛之一&#xff0c;聚集了大量投资者实时交流股票信息。对于投资者来说&#xff0c;自动化地采集这些发帖信息&#xff0c;并进行分析&#…

ionic Capacitor 生成 Android 应用

官方文档 https://ionic.nodejs.cn/developing/android/ https://capacitorjs.com/docs/getting-started 1、创建新的 Capacitor 应用程序 空目录下面 npm init capacitor/app2、install Capacitor npm install npm start在这里插入图片描述 3、生成dist目录 npm run buil…

ChatGPT 现已登陆 Windows 平台

今天&#xff0c;OpenAI 宣布其人工智能聊天机器人平台 ChatGPT 已开始预览专用 Windows 应用程序。OpenAI 表示&#xff0c;该应用目前仅适用于 ChatGPT Plus、Team、Enterprise 和 Edu 用户&#xff0c;是一个早期版本&#xff0c;将在今年晚些时候推出"完整体验"。…

二、PyCharm基本设置

PyCharm基本设置 前言一、设置中文汉化二、设置代码字体颜色三、设置鼠标滚轮调整字体大小四、修改 PyCharm 运行内存4.1 方式一4.1 方式二 五、显示 PyCharm 运行时内存六、设置代码模版配置的参数有&#xff1a; 七、PyCharm设置背景图总结 前言 为了让我们的 PyCharm 更好用…

Axure中继器实现时间读取和修改

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;中继器实现时间读取和修改 主要内容&#xff1a;中继器内不同时间格式的向外读取&#xff0c;和向内赋值&#xff0c;实现中继器时间的修改 应用场…

口电气设备:认证这道坎,如何跨越才能通全球?

今年国内各行各业都非常卷&#xff0c;出口成了一些公司的选择&#xff0c;但出口的电气设备往往不能跳过各种认证&#xff0c;这是市场准入的一部分&#xff0c;必须通过认证以满足当地的电气设备规范&#xff0c;比如出口欧盟需要CE认证 出口美国需要UL认证&#xff0c;加拿大…

ajax 读取文件

DOMException: Failed to read the responseXML property from XMLHttpRequest: The value is only accessible if the objects responseType is or document (was blob). at XMLHttpRequest.r ( $.ajax({ url: 未来之窗_服务, method: GET, …

展会亮点回顾|HMS汽车工业通信解决方案

2024 汽车测试及质量监控博览会&#xff08;中国&#xff09;&#xff08;Testing Expo China – Automotive&#xff09;于 8 月 28 日至 30 日在上海世博展览馆顺利举行。作为汽车测试技术领域的顶级盛会&#xff0c;来自全球的行业领袖和技术专家齐聚一堂&#xff0c;共同探…

基于Spring Boot、Vue和MyBatis的前后端分离座位管理系统:增删改查功能入门指南

在项目开发和工作实践中&#xff0c;创作灵感往往来自于对日常经验的总结与反思。通过记录技术难点和解决方案&#xff0c;不仅可以加深对问题的理解&#xff0c;还能为后续项目的优化提供参考。与此同时&#xff0c;撰写技术笔记、分享职场心得&#xff0c;不仅是对自己成长的…

Print Appearances Page 打印外观页面

Print Appearances 页面提供了设计时工具&#xff0c;用于自定义在打印网格控件时用于绘制 View 元素的外观。它提供对 BaseView.AppearancePrint 集合的设计时访问。此页面如下图所示。 当前在列表框中选择的 AppearanceObject 的外观设置在属性网格中列出。所有更改都会立即反…

OpenWRT 和 Padavan 路由器配置网络打印机 实现远程打印

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 之前有给大家介绍过 Armbian 安装 CUPS 作为打印服务器&#xff0c;像是 N1 盒子、玩客云&#xff0c;甚至是随身 WiFi 都可以通过 CUPS 来进行打印。但是有些朋友不想专门为打印机添置一个设备&#xff0…

前端实现鼠标可拖动弹框

目录 一、使用原生JS实现 1.HTML结构 2.CSS样式 3.使用JavaScript实现弹框的可拖动功能 二、使用Vue实现 分享一下前端常见功能“可拖动弹框”的实现方式&#xff0c;分别用原生JS和Vue实现。 一、使用原生JS实现 1.HTML结构 首先创建一个弹框的HTML结构&#xff0c;例如…