C++2(类与对象上篇)

作为 C++ 初学者,刚接触类与对象时总觉得云里雾里:为啥要抛弃熟悉的 C 语言结构体?类到底是个啥?对象和类又是什么关系?这篇笔记我会用最接地气的语言、最易懂的例子,把类与对象的核心知识点掰开揉碎讲清楚,既是学习记录,也是复习神器。
📚 目录
1.🤔 先唠唠:面向过程 vs 面向对象(为啥 C++ 更香?)
2.💡 类的由来:结构体的 "升级版"(C++ 对 C 的温柔改造)
3.✍️ 类的定义:手把手教你写第一个类(避坑指南)
4.🔒 封装的秘密:访问限定符(给代码 "上锁")
5.📌 类的作用域:别让代码 "迷路"
6.🧱 类的实例化:把设计图变成实物(类≠对象!)
7.📏 算大小:类的对象占多少内存?(新手必踩坑)
8.🕵️ this 指针:隐藏的 "对象身份证"(核心中的核心)
9.🆚 实战对比:C 语言栈 vs C++ 栈(看封装的威力)
10. 核心总结:必背的 5 个关键点

1. 🤔 先唠唠:面向过程 vs 面向对象(为啥 C++ 更香?)

刚学 C++ 时,我总问自己:"C 语言好好的,为啥要学类和对象?" 直到用 C 写了个洗衣机模拟程序,才懂了差距 ------

1.1 面向过程(C 语言):一步一步 "指挥"

核心是 "按步骤做事",就像手动洗衣服:拿盆 → 放水 → 放衣服 → 倒洗衣粉 → 搓洗 → 换水 → 拧干 → 晾衣服每一步都要我们写代码 "指挥",哪怕只是想换个洗衣粉,都要改一堆步骤代码,麻烦又容易错。

新手体验:用 C 写程序,就像亲手做一桌满汉全席,从买菜、切菜到炒菜全要自己来,少一步都不行。

1.2 面向对象(C++):找 "对象" 帮忙

核心是 "找对象干活",就像用洗衣机洗衣服:我们只需要找 3 个 "对象"------ 人、洗衣机、衣服。人只需要按洗衣机的 "启动键"(接口),不用管洗衣机内部怎么搓、怎么甩干,洗衣机自己会搞定。

体验:用 C++ 写程序,就像点外卖,告诉商家(对象)要啥菜(调用接口),不用管后厨怎么做,坐等结果就行~

核心差异表

2. 💡 类的由来:结构体的 "升级版"(C++ 对 C 的温柔改造)

C++ 是 C 的超集,所以先从熟悉的结构体入手~C 语言的结构体只能装变量,比如定义一个栈:

c 复制代码
// C语言结构体:只有变量,没函数
typedef int DataType;
struct Stack
{
    DataType* _array; // 数组
    size_t _capacity; // 容量
    size_t _size;     // 元素个数
};
// 操作栈的函数只能写在外面
void StackInit(struct Stack* ps)
{
    // 初始化逻辑
}

但 C++ 给结构体加了 "buff"------ 能在里面写函数了

cpp 复制代码
// C++结构体:既能装变量,也能装函数
typedef int DataType;
struct Stack
{
    // 直接在结构体里写初始化函数
    void Init(size_t capacity)
    {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (_array == nullptr)
        {
            perror("malloc失败"); // 新手提示:一定要检查内存申请!
            return;
        }
        _capacity = capacity;
        _size = 0;
    }
    // 成员变量
    DataType* _array;
    size_t _capacity;
    size_t _size;
};

不过 C++ 更推荐用class代替struct定义类 ------ 这才是面向对象的核心载体~
3. ✍️ 类的定义:手把手教你写第一个类(避坑指南)

3.1 基本语法(必记)

cpp 复制代码
class 类名
{
    // 成员:变量(属性)+ 函数(方法)
}; // 踩坑点:分号千万别漏!漏了编译器报错能懵半天

class:定义类的关键字,记死!
类名:比如Person、Date,首字母大写(编程规范);
成员:变量叫 "成员变量"(比如人的姓名、年龄),函数叫 "成员函数"(比如人的吃饭、走路)。

3.2 两种定义方式

方式 1:声明 + 定义全写在类里(适合简单函数)

cpp 复制代码
class Person
{
public: // 访问限定符,后面会讲
    // 成员函数:显示信息(新手提示:类里写函数,编译器可能当成内联函数)
    void ShowInfo()
    {
        cout << "姓名:" << _name << " 年龄:" << _age << endl;
    }
    // 成员变量:加下划线区分形参(新手必备技巧!)
    string _name; // 姓名
    int _age;     // 年龄
};

点评:写起来快,适合练手,但函数多了会乱,实际项目少用。

方式 2:声明放.h,定义放.cpp(实战推荐)

这是公司里最常用的写法,越早习惯越好!

第一步:新建person.h(头文件,只写声明)

cpp 复制代码
// person.h
#include <string>
using namespace std; // 新手提示:头文件里尽量少用using,这里为了简化
class Person
{
public:
    // 只声明,不写具体逻辑
    void ShowInfo();
    // 成员变量
    string _name;
    int _age;
};

第二步:新建person.cpp(源文件,写定义)

cpp 复制代码
// person.cpp
#include "person.h"
#include <iostream>
// 新手重点:必须加「类名::」,告诉编译器这是Person类的函数
void Person::ShowInfo()
{
    cout << "姓名:" << _name << " 年龄:" << _age << endl;
}

优势:代码结构清晰,后期改逻辑只动.cpp,不用动.h,不容易出错。

3.3 必避坑:成员变量命名
刚学的时候我总犯这错:形参和成员变量重名,导致赋值无效!

cpp 复制代码
// 反例:坑哭新手
class Date
{
public:
    void Init(int year)
    {
        year = year; // 编译器分不清哪个是形参,哪个是成员变量!
    }
private:
    int year;
};

正确写法(推荐):给成员变量加前缀 _

cpp 复制代码
class Date
{
public:
    void Init(int year)
    {
        _year = year; // 一眼分清!
    }
private:
    int _year; // 加下划线
};

4. 🔒 封装的秘密:访问限定符(给代码 "上锁")

封装是面向对象的三大特性之一 (另外两个是继承、多态,下篇讲),可以理解为:"把不想让人碰的东西锁起来,只留能用的接口 "。
4.1 三个访问限定符

重点:
1.权限从限定符出现的位置开始,到下一个限定符结束;
2.class默认private(不写限定符,成员全锁起来),struct默认public(兼容 C 语言);
3.这些限定符只在编译时有用,代码运行后,内存里没有 "锁" 的区别~

4.2 封装的本质

封装不是 "藏起来不让看",而是 "管理起来更安全"。比如手机:我们只需要用屏幕、按键(public接口),不用碰内部的电池、主板(private成员)------ 就算想换电池,也得通过官方维修点(指定接口),不会自己拆坏。

代码演示

cpp 复制代码
class Phone
{
public:
    // 对外接口:开机、打电话
    void PowerOn() { cout << "手机开机" << endl; }
    void Call(string number) { cout << "拨打:" << number << endl; }
private:
    // 内部成员:电池、主板(类外碰不到)
    int _battery; // 电池电量
    string _board; // 主板型号
};

int main()
{
    Phone myPhone;
    myPhone.PowerOn(); // 能调用,public
    myPhone.Call("10086"); // 能调用,public
    // myPhone._battery = 100; // 编译报错!private,类外不能访问
    return 0;
}

4.3 面试题:struct 和 class 的区别
默认权限:struct默认public,class默认private;
兼容性:struct能当 C 的结构体用,class不行;
继承时:struct默认公有继承,class默认私有继承(下篇讲)。

5. 📌 类的作用域:别让代码 "迷路"

可以把类的作用域理解为 "类的专属地盘"------ 类里的成员,只有在这个 "地盘" 里才能直接用,出了地盘就得 "报家门"。

cpp 复制代码
class Person
{
public:
    // 声明:在Person的地盘里,直接写函数名就行
    void PrintInfo();
private:
    string _name = "小明";
    int _age = 18;
};

// 定义:出了Person的地盘,必须加「Person::」报家门
void Person::PrintInfo()
{
    // 函数里在Person地盘内,直接用成员变量
    cout << _name << " " << _age << endl;
}

提醒如果漏写Person::,编译器会以为这是个全局函数,找不到_name和_age,直接报错!

6. 🧱 类的实例化:把设计图变成实物(类≠对象!)

这是新手最容易混淆的点:类只是 "设计图",对象才是 "按图做出来的实物"。
6.1 核心区别(比喻)
类:建筑设计图(只画了房子长啥样,没有实际房子,不占空间);
对象:按设计图盖的房子(有实际空间,能住人,占内存)。

cpp 复制代码
class Person
{
public:
    string _name;
    int _age;
};

int main()
{
    // 错误:类没有内存,不能直接赋值!
    // Person._age = 20; 
    
    // 正确:实例化对象(盖房子),对象才有内存
    Person p1;
    p1._age = 20; // 给对象的成员变量赋值
    return 0;
}

6.2 一个类能造多个对象(示例)

cpp 复制代码
void Test()
{
    // 实例化2个Person对象,各自有独立的内存
    Person p1; // 小明
    p1._name = "小明";
    p1._age = 18;
    
    Person p2; // 小红
    p2._name = "小红";
    p2._age = 17;
}

理解:就像一张设计图,能盖 10 栋、100 栋房子,每栋房子都有自己的家具(成员变量),但建造方法(成员函数)是一样的。

7. 📏 算大小:类的对象占多少内存?(必踩坑)

新手第一次算类对象大小,总会疑惑:"类里有函数有变量,为啥大小只算变量?"

7.1 存储规则(秒懂)

C++ 编译器很聪明,为了节省内存
成员变量:每个对象独立存(因为每个对象的属性不一样,比如小明 18 岁,小红 17 岁);
成员函数:所有对象共享(因为吃饭、走路的方法都一样,没必要每个对象存一份)。
所以:对象大小 = 成员变量总大小 + 内存对齐补齐的字节(和结构体对齐规则一样)。

cpp 复制代码
// 类1:有成员变量+成员函数
class A1 {
public:
    void f1() {} // 函数不占对象内存
private:
    int _a; // int占4字节,无对齐
};

// 类2:只有成员函数
class A2 {
public:
    void f2() {}
};

// 类3:空类(啥都没有)
class A3 {};

int main()
{
    cout << sizeof(A1) << endl; // 输出4(只有int _a的大小)
    cout << sizeof(A2) << endl; // 输出1(编译器给的标识字节,区分不同对象)
    cout << sizeof(A3) << endl; // 输出1(空类也需要1字节标识)
    return 0;
}

7.3 结构体对齐规则(复习)
VS 编译器默认对齐数是 8,规则记这 4 点:
1.第一个成员从偏移量 0 的位置开始;
2.其他成员对齐到「对齐数」的整数倍(对齐数 = min (编译器默认值,成员大小));
3.总大小是最大对齐数的整数倍;
4.嵌套结构体时,嵌套的部分对齐到自己的最大对齐数,整体大小是所有最大对齐数的整数倍。

cpp 复制代码
class A {
private:
    char _a;   // 1字节
    int _b;    // 4字节
    double _c; // 8字节
};
// 计算:_a(1) + 补齐3字节 + _b(4) + _c(8) = 16字节(最大对齐数8,16是8的倍数)
cout << sizeof(A) << endl; // 输出16

8. 🕵️ this 指针:隐藏的 "对象身份证"(核心中的核心)

新手理解 this 指针的关键:"成员函数怎么知道操作的是哪个对象?"

8.1 this 指针的由来(场景)

看这段代码,d1 和 d2 都调用 Init 函数,为啥 d1 的年份是 2022,d2 是 2023?

cpp 复制代码
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year, _month, _day;
};

int main()
{
    Date d1, d2;
    d1.Init(2022, 1, 1);
    d2.Init(2023, 1, 1);
    return 0;
}

答案:编译器给每个非静态成员函数加了个隐藏的 this 指针参数,指向当前调用函数的对象

8.2 编译器的 "小动作"(可视化)

你写的代码:

cpp 复制代码
void Date::Init(int year, int month, int day)
{
    _year = year;
}

编译器实际处理的代码:

cpp 复制代码
// 偷偷加了this指针参数
void Date::Init(Date* this, int year, int month, int day)
{
    this->_year = year; // 偷偷用this访问成员变量
}

你调用的代码:

cpp 复制代码
d1.Init(2022,1,1);

编译器实际调用的代码:

cpp 复制代码
Date::Init(&d1, 2022,1,1); // 偷偷传对象地址给this

8.3 this 指针的特性(必记)

1.类型:类名* const(比如Date* const)------this 指针的指向不能改(不能给 this 赋值);

2.作用域:只能在成员函数内部用;

3.存储位置:是形参,存在栈区(编译器优化后可能用寄存器传递);

4.新手易错点:this 可以为空,但访问成员变量会崩溃!

8.4 实战:this 为空的两种情况

情况 1:this 为空,不访问成员变量→正常运行

cpp 复制代码
class A
{
public:
    void Print()
    {
        cout << "我只是打印一句话,不碰成员变量" << endl;
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr; // p是空指针
    p->Print();     // 正常运行:不用访问成员变量,无需解引用this
    return 0;
}

情况 2:this 为空,访问成员变量→崩溃

cpp 复制代码
class A
{
public:
    void PrintA()
    {
        cout << _a << endl; // 本质是this->_a,this为空,解引用崩溃
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr;
    p->PrintA(); // 运行崩溃:空指针解引用(新手必记!)
    return 0;
}

9. 🆚 实战对比:C 语言栈 vs C++ 栈(看封装的威力)

对比着写一遍,就能深刻理解 C++ 的优势!

9.1 C 语言实现栈(体验:麻烦又易错)

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int DataType;
struct Stack
{
    DataType* _array;
    size_t _capacity;
    size_t _size;
};

// 所有函数都要传Stack*,还要手动检查是否为空
void StackInit(struct Stack* ps)
{
    if (ps == NULL) return; // 新手容易忘写这行!
    ps->_array = (DataType*)malloc(sizeof(DataType)*4);
    ps->_capacity = 4;
    ps->_size = 0;
}

void StackPush(struct Stack* ps, DataType x)
{
    if (ps == NULL) return;
    // 扩容逻辑(新手容易写错)
    if (ps->_size == ps->_capacity)
    {
        ps->_capacity *= 2;
        ps->_array = (DataType*)realloc(ps->_array, ps->_capacity*sizeof(DataType));
    }
    ps->_array[ps->_size++] = x;
}

int main()
{
    struct Stack s;
    StackInit(&s); // 必须传地址,新手容易漏&
    StackPush(&s, 1);
    StackPush(&s, 2);
    return 0;
}

9.2 C++ 实现栈(体验:简洁又安全)

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

typedef int DataType;
class Stack
{
public:
    // 不用传Stack*,编译器自动传this
    void Init()
    {
        _array = (DataType*)malloc(sizeof(DataType)*4);
        _capacity = 4;
        _size = 0;
    }

    void Push(DataType x)
    {
        // 扩容逻辑,直接用成员变量,不用ps->_
        if (_size == _capacity)
        {
            _capacity *= 2;
            _array = (DataType*)realloc(_array, _capacity*sizeof(DataType));
        }
        _array[_size++] = x;
    }

private:
    // 成员变量私有化,类外不能乱改,更安全
    DataType* _array;
    size_t _capacity;
    size_t _size;
};

int main()
{
    Stack s;
    s.Init(); // 直接调用,不用传地址
    s.Push(1);
    s.Push(2);
    // s._size = 100; // 编译报错!private,防止新手乱改
    return 0;
}

9.3 总结:C++ 香在哪?

*1.不用手动传Stack ,编译器用 this 搞定;
2.成员变量私有化,防止误操作;
3.调用函数更简单,不用写&s;
4.代码更整洁,数据和方法绑在一起。**

10. 📝 核心总结(必背)
1.类是 "设计图",对象是 "实物",只有对象占内存,大小仅算成员变量(含对齐);
2.封装是 "管理" 不是 "隐藏",通过访问限定符控制权限,让代码更安全;
3.this 指针是隐藏形参,指向当前对象,为空时访问成员变量会崩溃;
4.class默认private,struct默认public,核心区别在默认权限;
5.C++ 相比 C,把数据和方法封装在一起,代码更简洁、更安全,新手入门稍难但后期更香。

相关推荐
Teable任意门互动2 小时前
中小企业进销存实战:Teable多维表格从零搭建高效库存管理系统
开发语言·数据库·excel·飞书·开源软件
En^_^Joy2 小时前
JavaScript Web API:DOM操作全解析
开发语言·前端·javascript
m0_743297422 小时前
嵌入式LinuxC++开发
开发语言·c++·算法
代码改善世界2 小时前
【C++ 初阶】命名空间 / 输入输出 / 缺省参数 / 函数重载
开发语言·c++
代码探秘者2 小时前
【大模型应用】2.RAG详细流程
java·开发语言·人工智能·后端·python
小小怪7502 小时前
高性能密码学库
开发语言·c++·算法
2301_821700532 小时前
模板代码生成工具
开发语言·c++·算法
xieliyu.2 小时前
Java :类和对象(一)
java·开发语言
佳木逢钺2 小时前
机器人/无人机视觉开发选型指南:RealSense D455 vs D435i 与奥比中光的互补方案
c++·人工智能·计算机视觉·机器人·ros·无人机