【C++入门】面向对象编程的基石——【类与对象】基础概念篇

⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///


WARNING : DETECTING HIGH ENERGY

🌊 🌉 🌊 心手合一 · 水到渠成

|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| >>> ACCESS TERMINAL <<< ||
| 🦾 作者主页 | 🔥 C语言核心 |
| 💾 编程百度 | 📡 代码仓库 |


Running Process: 100% | Latency: 0ms


索引与导读

一、什么是面向对象编程?

面向对象编程 简称OOP,是一种程序设计思想

  • 将现实世界中的事物抽象为对象(Object) ,并将数据(属性)处理数据的方法(行为) 封装在一起

如果说过程化编程(如 C 语言) 是按照第一步、第二步、第三步 的步骤来解决问题,那么OOP就是通过 指挥不同的对象协作 来解决问题

OOP的三大特性

  1. 封装 (Encapsulation)

    封装是将数据和操作数据的方法绑定在一起,并隐藏对象的内部实现细节。

    • 做法: 使用访问限定符(如 private, public)。

    • 好处: 安全性。外部代码不能随便修改对象内部的敏感数据,只能通过预留的接口(方法)进行交互。

  2. 继承 (Inheritance)

    继承允许一个类(子类)获取另一个类(父类)的属性和方法。

    • 做法: class Student : public Person { ... };

    • 好处: 代码复用。你不需要为每一个新类重写相同的代码。例如,"学生"和"老师"都可以继承"人"这个类的基本属性。

  3. 多态 (Polymorphism)

    多态是指同一个接口,在不同的对象上表现出不同的行为。

    • 做法: 通过函数重写或虚函数实现。

    • 好处: 灵活性。你可以写一个通用的函数,比如"让动物叫",传入一只狗它会汪汪,传入一只猫它会喵喵


二、类的概念与定义

2.1)什么是类?

类(Class)面向对象编程(OOP) 中的核心概念,它是一种用户定义的数据类型,用于封装数据(属性)操作数据的方法(函数) ,是创建对象的蓝图或模板


2.2)类的基本结构与命名规范

1)关键字与标识符

classC++ 的保留字,用于声明一个新的类型。Stack 是用户自定义的类名,建议采用大驼峰命名法

2)分号的必要性

C++ 中,类定义的末尾必须有一个分号 ;

3)成员变量与成员函数

  • 成员变量: 描述对象的"状态"。
  • 成员函数: 描述对象的"行为"
cpp 复制代码
//定义一个动态的栈
class Stack {
	//成员函数
	void Init() {}

	void Push(int x) {}

	//成员变量
	int a;
	int top;
	int capacity;
};
  • 如何去调用这个类里面的功能
cpp 复制代码
int main() {
	Stack s1;
	Stack s2;

	s1.Init();
	s2.Push(1);
	s2.Push(2);
	s2.Push(3);

	return 0;
}

2.3)struct 与 class 的演进与区别

C 语言的struct vs C++ 中的 struct:

  • class成员默认访问权限 private(私有)

  • struct成员默认访问权限 public(公有)

C++兼容C中的struct的用法,同时也将struct升级为类


2.4)成员函数的 Inline 特性

定义在类里面的成员函数默认认为 inline

什么是 Inline(内联)? 内联函数告诉编译器在调用该函数的地方直接展开代码,以减少函数调用的开销(如压栈、跳转、返回)

🔗Lucy的空间骇客裂缝:inline函数

自动内联的条件: 只要函数体直接写在类声明内部,编译器就会将其视为内联


二、访问限定符

C++ 一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用

1.1)三大访问限定符


访问限定符
public
private
protected

C++ 有一个后门friend 关键字,可以让特定的外部函数或类突破 private防线 (但要慎用,会破坏封装性)


1.2)限定符的作用域:管辖范围

访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到}即类结束

  • 代码示例
cpp 复制代码
class Hacker {
public:  // --- public 的管辖范围开始 ---
    void attack() {}
    void defend() {}

private: // --- public 结束,private 开始 ---
    int ip_address;
    int port;

public:  // --- private 结束,新的 public 开始 ---
    void disconnect() {}
};       // --- 作用域结束

虽然 C++ 允许你在类里写无数个 public/private 块,但行业规范通常建议将同类型的权限归并在一起。通常的顺序是:先写 public(方便阅读接口),再写 private(隐藏数据)


1.3)行业通用的"黄金法则"

一般成员变量都会被限制为 private/protected,需要给别人使用的成员函数会放为 public

  • 如果把成员变量设为 public,任何人都可以在外部随意修改它:
cpp 复制代码
// 错误示范
obj.age = -100; // 外部可以直接把年龄设为负数,逻辑崩坏
  • 如果设为 private,并通过 public 的函数来访问,你就可以把关:
cpp 复制代码
// 正确示范
void setAge(int a) {
    if (a < 0 || a > 150) {
        cout << "非法数据!" << endl;
        return;
    }
    _age = a;
}
  • C++ 中,我们通常提供 Getxxx()Setxxx() 接口来控制对私有数据的读写 后面我们会讲

三、类域

:: 作用域操作符

类定义了一个新的作用域,类的所有成员都在类的作用域中。

在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域

cpp 复制代码
class MyClass {
public:
    void printMessage(); // 成员函数声明
private:
    int data;
};

// 在类体外使用 :: 操作符定义成员函数
void MyClass::printMessage() {
    std::cout << "This is defined outside the class." << std::endl;
}

:: 明确指定了后续的成员属于哪个类,从而进入了该类的作用域,是连接成员声明(在类内)与成员定义(在类外)的标准语法

  • 代码示例
cpp 复制代码
class Stack {
public:
    void Init(); // 成员函数声明
private:
    int* _array;
    int _capacity;
    int _top;
};

错误写法

cpp 复制代码
void  Init() {
		_array = nullptr;
		_top = _capacity = 0;
}

编译器错误:找不到_array的声明

正确写法

cpp 复制代码
void Stack::Init() {
    _array = nullptr;
    _top = _capacity = 0;
}

正确:编译器知道在 Stack 类的作用域内查找 _array


四、类的实例化

cpp 复制代码
#include <iostream>
using namespace std;

class Date
{
public:
    // 初始化日期
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    // 打印日期
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    // 这里只是声明,定义对象时才分配内存
    int _year;
    int _month;
    int _day;
};

int main()
{
    // Date类实例化出对象d1和d2
    Date d1;
    Date d2;

    d1.Init(2024, 3, 31);
    d1.Print();

    d2.Init(2024, 7, 5);
    d2.Print();

    return 0;
}

五、类的对象大小

5.1)成员变量与成员函数的大小存储

对象的大小只包含成员变量的大小,不包含成员函数的大小

  • 成员变量

    • 类实例化出的每个对象(如 d1d2),都需要有自己独立的数据空间
  • 成员函数(不存储在对象中)

    1. 函数被编译后是一堆指令,这些指令存储在公共的代码段
    2. 对象内部无法直接存储指令代码
    3. 即便是存储函数的指针(地址),也是极大的浪费

当你实例化 100个对象 时,就会有 100个重复的指针 指向同一个函数地址

因此,C++ 设计者决定不在对象中存储成员函数的指针


5.2)编译器如何找到函数

  • 普通成员函数

    • 普通成员函数(非虚函数)的地址在编译和链接阶段就已经确定了
    • 编译器在生成汇编代码时,直接将其翻译成call地址的指令
    • 程序运行时,不需要去对象里找地址,而是直接跳转到那个固定的代码段地址去执行
  • virtual虚函数

    如果有虚函数,对象内部确实会增加一个隐含的指针(虚表指针 vptr ),用来在运行时寻找正确的函数
    后续我们会讲到


六、C++类实例化对象的 内存对齐的规则

结构体内存对齐规则

  1. 起始位置: 第一个成员在与结构体偏移量(offset)为 0 的地址处
  2. 成员对齐: 其他成员变量要对齐到该成员的 对齐数 的整数倍地址处
    • 对齐数 = 编译器默认的一个对齐数(VS中默认为 8,Linux/gcc 默认为成员大小本身)与该成员大小的 较小值
  3. 总大小计算: 结构体的总大小必须是 最大对齐数 的整数倍
    • 最大对齐数 = 所有成员变量(包括嵌套结构体成员)中最大的那个对齐数
  4. 嵌套结构体: 如果嵌套了结构体,嵌套的结构体要对齐到 它自己内部最大对齐数 的整数倍处。结构体的整体大小则是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

代码示例

cpp 复制代码
#include <iostream>
using namespace std;

// 场景 A:含有成员变量和成员函数
class A {
public:
    void Print() {
        cout << _ch << endl;
    }
private:
    char _ch; // 1 字节
    int _i;   // 4 字节
};

// 场景 B:仅含有成员函数,没有成员变量
class B {
public:
    void Print() {
        // ...
    }
};

// 场景 C:完全的空类
class C {};

int main() {
    A a;
    B b;
    C c;

    cout << "Size of A (char + int): " << sizeof(a) << endl;
    cout << "Size of B (only function): " << sizeof(b) << endl;
    cout << "Size of C (empty class): " << sizeof(c) << endl;

    return 0;
}

类 A 的大小分析

针对类 A 包含一个 char 成员和一个 int 成员的情况,其内存分配逻辑如下:

  • 成员分析 :类 A 有一个 char (1字节) 和一个 int (4字节)。成员函数 Print 不占对象内存。
  • 对齐过程
    1. _ch 放在偏移量 0 处。
    2. _iint,大小为 4。默认对齐数是 8,取较小值 4 。因此 _i 必须对齐到 4 的倍数地址。
    3. 偏移量 1, 2, 3 不满足要求,被填充(Padding),_i 从偏移量 4 开始存储,占用 4, 5, 6, 7。
  • 总大小计算 :当前占用 0 到 7 共 8 个字节。最大对齐数是 4(int 的大小),8 是 4 的倍数。
  • 结果sizeof(a)8

类 B与类 C 的大小分析

B 虽然有函数,但没有成员变量;类 C 既没有函数也没有变量。

  • 计算逻辑
    • 对于类 B :成员函数存储在代码段,不占用对象空间。逻辑上它和类 C 一样,都是"空"的。
    • 关键点 :如果空类的大小为 0,当我们声明 C c1, c2; 时,这两个对象就会拥有相同的内存地址。为了区分不同的对象,编译器会给空类实例分配一个占位符
  • 结果 :在绝大多数编译器(如 VS, GCC)中,sizeof(b)sizeof(c) 均为 1

注意:这 1 个字节不存储任何有效数据,仅用于标识对象的存在


七、空指针访问规则

  • 下⾯程序编译运⾏结果是()
cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    void Print()
    {
        cout << "A::Print()" << endl;
    }

private:
    int _a;
};

int main()
{
    // 1. 定义一个空指针 p
    A* p = nullptr;
    
    // 2. 试图通过空指针调用成员函数
    p->Print();
    
    return 0;
}

只要Print函数内部不访问对象的成员变量(不解引用 this),空指针调用成员函数是合法的

7.1)绝对禁区:解引用

  • 形式: *ptrptr->memberVariable

  • 结果: ❌ 崩溃(导致未定义行为UB)


7.2)调用普通成员函数

调用成员函数时,ptr 会作为 this 指针传给函数

  • 情况 A:函数内访问了成员变量

    • 代码试图通过this->variable访问内存。由于thisnullptr,这等同于解引用。

    • 结果:❌ 崩溃。

  • 情况 B:函数内没有访问任何成员变量

    • 函数体内不需要解引用this指针。虽然在C++标准中这属于未定义行为(UB),但在大多数编译器实现下,程序能正常运行并输出结果。

    • 结果:⚠️ 通常能运行(但属于 UB,不推荐依赖)


7.3)调用虚函数

虚函数的调用依赖于虚函数表指针(vptr ,而 vptr 存储在对象内存的头部

  • 形式: ptr->virtualFunc()

  • 原理: 程序需要先去ptr指向的内存里找vptr,因为 ptr 是空的,找不到内存

  • 结果: ❌ 崩溃


7.4)静态成员

静态成员函数静态成员变量 属于而不属于对象

  • 形式: ptr->staticFunc()

  • 原理: 编译器会将其优化为 ClassName::staticFunc(),根本不会去解引用 ptr

  • 结果: ✅ 安全,正常运行。


💻结尾--- 核心连接协议

警告: 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠


【📡】 建立深度链接: 关注本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。

【⚡】 能量过载分发: 执行点赞操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。

【💾】 离线缓存核心: 将本页加入收藏。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。

【💬】 协议加密解密:评论区留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。

【🛰️】 信号频率投票: 通过投票发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。



相关推荐
马士兵教育2 小时前
Java还有前景吗?Java+AI大模型学习路线及项目?
java·人工智能·python·学习·机器学习
搬砖魁首2 小时前
基础能力系列 - 多线程2 - 条件变量
c++·rust·条件变量·原子类型·线程同步互斥
chase_my_dream2 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
snow@li2 小时前
Java:理解 Gradle / 后端项目的管家 / 打包SpringBoot 应用 / 完成编译、下载依赖、运行测试、打包 JAR/WAR / 速查表
java
牛油果子哥q2 小时前
【C++ STL string 】C++ STL string 终极精讲:底层原理、内存机制、全套API、深浅拷贝、易错坑点与工程实战规范
数据库·c++
云烟成雨TD2 小时前
Spring AI 1.x 系列【57】动态工具发现:Tool Search Tool
java·人工智能·spring
zfoo-framework3 小时前
[修改代码使用]codex官方app中使用中转(不需要cc-switch) 1.config.toml 2.sk方式登录
java
逍遥德3 小时前
MQTT教程详解-05.SpringBoot集成mqtt client 性能分析
java·spring boot·spring·mt
云烟成雨TD3 小时前
Spring AI 1.x 系列【54】Retry 机制分析
java·人工智能·spring
weixin_523185323 小时前
Collections.unmodifiableMap详解:真的不可修改吗?
java·linux·前端