欢迎光临
我们一直在努力

C++ 类和对象入门(一):从 class、访问限定符到 this 指针

C++ 类和对象入门:从 class、访问限定符到 this 指针

头像

🔥 星恒随风:
个人主页

❄️ 个人专栏:
《指针合集》

《C语言基础》

《数据结构》

《机器学习导论》

《前端基础》

《python基础》

✨ 数据即知识,压缩即智能


目录

  • C++ 类和对象入门:从 class、访问限定符到 this 指针
    • 前言
    • 一、类的基本认识
      • 1.1 为什么需要类?
      • 1.2 什么是类?
      • 1.3 类的基本定义格式
      • 1.4 成员变量为什么常常加下划线?
    • 二、访问限定符与封装
      • 2.1 什么是访问限定符?
      • 2.2 为什么成员变量通常放 private?
      • 2.3 封装的本质是什么?
    • 三、class 和 struct 的区别
      • 3.1 C++ 中 struct 也可以定义成员函数
      • 3.2 class 和 struct 的主要区别
      • 3.3 什么时候用 class,什么时候用 struct?
    • 四、类域与成员函数定义
      • 4.1 什么是类域?
      • 4.2 类外定义成员函数为什么要加类名?
      • 4.3 类域的作用
    • 五、对象实例化
      • 5.1 类只是设计图,对象才真正占空间
      • 5.2 一个类可以实例化多个对象
      • 5.3 类和对象的关系
    • 六、对象大小与内存布局
      • 6.1 对象里面到底存了什么?
      • 6.2 sizeof 对象时算什么?
      • 6.3 内存对齐是什么?
      • 6.4 内存对齐的基本规则
      • 6.5 空类对象为什么大小是 1?
    • 七、this 指针
      • 7.1 问题:成员函数怎么知道当前对象是谁?
      • 7.2 this 指针是什么?
      • 7.3 this 指针能不能显式使用?
      • 7.4 this 指针能不能被修改?
      • 7.5 this 指针存在哪里?
    • 八、空指针调用成员函数的问题
      • 8.1 为什么有些空指针调用成员函数不崩?
      • 8.2 为什么访问成员变量就可能崩?
      • 8.3 这类写法能不能在项目中使用?
    • 九、C 和 C++ 实现 Stack 的对比
      • 9.1 C 语言实现 Stack 的特点
      • 9.2 C++ 实现 Stack 的方式
      • 9.3 C++ 版本看起来改变了什么?
      • 9.4 C 和 C++ 实现 Stack 的核心区别
    • 十、重新理解封装
      • 10.1 封装不是把代码写复杂
      • 10.2 封装让接口和实现分离
    • 十一、常见错误总结
      • 11.1 类定义结尾忘记分号
      • 11.2 类外访问 private 成员
      • 11.3 类外定义成员函数忘记加类域
      • 11.4 以为类定义时就分配了对象空间
      • 11.5 以为成员函数存储在每个对象中
      • 11.6 误以为 this 指针存在对象里
    • 十二、全文总结
      • 13.1 本文核心内容

前言

学完 C 语言以后,再开始学习 C++,第一个真正有“C++味道”的内容通常就是:类和对象。

很多同学刚看到类时,会觉得它像是“升级版结构体”:

  • C 语言结构体只能放数据;
  • C++ 的类既可以放数据,也可以放函数;
  • 数据和函数放在一起之后,代码看起来更像一个整体。

这个理解没错,但还不够完整。

C++ 类和对象真正重要的地方在于:

它把数据和操作数据的函数封装到一起,并通过访问权限控制外部如何使用对象。

比如我们实现一个栈。

在 C 语言里,通常是结构体保存数据,函数单独放在外面:

ST s;
STInit(&s);
STPush(&s, 1);
STPop(&s);

而在 C++ 中,我们可以写成:

Stack s;
s.Init();
s.Push(1);
s.Pop();

这两种写法底层逻辑差别没有想象中那么大,但代码组织方式已经变了。

C++ 更强调:

对象自己管理自己的数据,对外只暴露必要的接口。


一、类的基本认识

1.1 为什么需要类?

在 C 语言中,我们可以用结构体描述一个对象的数据。

比如日期:

struct Date
{
int year;
int month;
int day;
};

这个结构体能说明一个日期应该有三个数据:

但是如果我们想初始化日期、打印日期,就需要额外写函数:

void DateInit(struct Date* d, int year, int month, int day)
{
d->year = year;
d->month = month;
d->day = day;
}

void DatePrint(struct Date* d)
{
printf("%d/%d/%d\\n", d->year, d->month, d->day);
}

调用时:

struct Date d;
DateInit(&d, 2024, 3, 31);
DatePrint(&d);

这种写法能用,但数据和函数是分开的。

当代码规模变大以后,就会出现一些问题:

  • 谁都可以直接修改结构体成员;
  • 数据和操作数据的函数分散在不同地方;
  • 调用函数时总要手动传结构体地址;
  • 接口和内部细节边界不清楚。

C++ 的类就是为了解决这类问题而引入的重要机制。


1.2 什么是类?

类可以理解成一种自定义类型。

它不仅能描述对象有哪些数据,还能描述对象能做哪些操作。

比如定义一个日期类:

#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;
};

这里的 Date 就是一个类。

它里面有两类成员:

成员类型含义
成员变量 保存对象的数据,也叫属性
成员函数 操作对象数据的函数,也叫方法

在这个例子中:

int _year;
int _month;
int _day;

是成员变量。

void Init(...)
void Print()

是成员函数。

在这里插入图片描述


1.3 类的基本定义格式

C++ 使用 class 关键字定义类。

基本格式如下:

class 类名
{
public:
// 公有成员

private:
// 私有成员

protected:
// 保护成员
};

注意:类定义结束时,右大括号后面的分号不能省略。

};

例如:

class Stack
{
public:
void Init()
{
// 初始化
}

private:
int* _a;
size_t _capacity;
size_t _top;
};

其中:

  • class 是定义类的关键字;
  • Stack 是类名;
  • {} 中是类体;
  • 类体中可以定义成员变量和成员函数;
  • 最后的 ; 必须写。

1.4 成员变量为什么常常加下划线?

在很多 C++ 代码中,成员变量经常写成:

int _year;
int _month;
int _day;

也有一些代码会写成:

int year_;
int month_;
int day_;

或者:

int m_year;
int m_month;
int m_day;

这些写法不是 C++ 强制规定,而是命名习惯。

目的很简单:

区分成员变量、普通局部变量和函数参数。

例如:

void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}

这里:

  • _year 是成员变量;
  • year 是函数参数。

这样写代码时,不容易混淆。


二、访问限定符与封装

2.1 什么是访问限定符?

C++ 中有三个常见访问限定符:

访问限定符含义
public 公有成员,类外可以直接访问
private 私有成员,类外不能直接访问
protected 保护成员,类外不能直接访问,继承中会体现作用

入门阶段可以先这样理解:

  • public:对外开放的接口;
  • private:类内部维护的实现细节;
  • protected:暂时可以先理解为类似 private,后面学继承时再深入。

例如:

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;
};

类外可以访问 public 成员:

Date d;
d.Init(2024, 3, 31);
d.Print();

但是不能直接访问 private 成员:

d._year = 2025; // 错误,_year 是 private

在这里插入图片描述


2.2 为什么成员变量通常放 private?

如果成员变量全部放到 public,外部代码就可以随便修改对象内部状态。

比如一个栈:

class Stack
{
public:
int* _a;
size_t _capacity;
size_t _top;
};

这样写以后,外部可以直接修改:

Stack s;
s._top = 100000;
s._capacity = 1;

这会破坏栈的内部逻辑。

正常情况下:

  • _top 应该由 Push() 和 Pop() 维护;
  • _capacity 应该由扩容逻辑维护;
  • _a 不应该被外部随便改。

更合理的写法是:

class Stack
{
public:
void Push(int x);
void Pop();
int Top();
bool Empty();

private:
int* _a;
size_t _capacity;
size_t _top;
};

这样,外部只能通过公开接口使用栈:

s.Push(1);
s.Pop();
s.Top();

不能直接破坏内部数据。


2.3 封装的本质是什么?

封装不是简单地“把东西藏起来”。

更准确地说,封装做了两件事:

  • 把相关的数据和函数组织到一个类里面;
  • 通过访问权限限制外部对内部数据的直接访问。
  • 封装的意义在于:

    外部只需要知道怎么用,不需要知道内部怎么实现。

    比如使用栈时,外部只需要知道:

    Push()
    Pop()
    Top()
    Empty()

    不需要关心:

    _a
    _top
    _capacity

    这样代码会更安全,也更容易维护。


    三、class 和 struct 的区别

    3.1 C++ 中 struct 也可以定义成员函数

    在 C 语言中,struct 主要用于组织数据。

    但在 C++ 中,struct 被升级了,它也可以定义成员函数。

    例如:

    struct ListNode
    {
    void Init(int x)
    {
    val = x;
    next = nullptr;
    }

    int val;
    ListNode* next;
    };

    这在 C++ 中是合法的。

    也就是说:

    C++ 中的 struct 也可以看作一种类。


    3.2 class 和 struct 的主要区别

    class 和 struct 最主要的区别是默认访问权限不同。

    在这里插入图片描述

    例如:

    class A
    {
    int _a;
    };

    这里 _a 默认是 private。

    而:

    struct B
    {
    int _b;
    };

    这里 _b 默认是 public。


    3.3 什么时候用 class,什么时候用 struct?

    实际写代码时,通常有一个习惯:

    • 如果只是简单保存数据,常用 struct;
    • 如果需要封装数据和操作,常用 class。

    例如链表节点这种简单数据结构,可以写成 struct:

    struct ListNode
    {
    int val;
    ListNode* next;
    };

    而栈、队列、日期类这种需要维护内部逻辑的类型,更适合写成 class:

    class Stack
    {
    public:
    void Push(int x);
    void Pop();

    private:
    int* _a;
    size_t _top;
    size_t _capacity;
    };

    这不是硬性规定,而是工程代码中的常见风格。


    四、类域与成员函数定义

    4.1 什么是类域?

    类本身会形成一个新的作用域。

    类里面定义的成员变量和成员函数,都属于这个类的作用域。

    例如:

    class Stack
    {
    public:
    void Init(int n = 4);

    private:
    int* _a;
    size_t _capacity;
    size_t _top;
    };

    这里的 Init、_a、_capacity、_top 都属于 Stack 这个类域。


    4.2 类外定义成员函数为什么要加类名?

    如果成员函数只在类里面声明,在类外定义,就必须加上类域。

    例如:

    class Stack
    {
    public:
    void Init(int n = 4);

    private:
    int* _a;
    size_t _capacity;
    size_t _top;
    };

    类外定义时要写:

    void Stack::Init(int n)
    {
    _a = (int*)malloc(sizeof(int) * n);
    if (_a == nullptr)
    {
    perror("malloc fail");
    return;
    }

    _capacity = n;
    _top = 0;
    }

    这里的:

    Stack::Init

    表示:

    这个 Init 函数属于 Stack 类。

    这里的使用方法可以参考之前我们名字空间使用的内容。

    如果写成:

    void Init(int n)
    {
    _a = ...
    }

    编译器会把它当成普通全局函数。

    此时 _a、_capacity、_top 都找不到。


    4.3 类域的作用

    类域影响的是编译器查找名字的规则。

    当编译器看到:

    void Stack::Init(int n)

    它知道当前函数是 Stack 的成员函数。

    所以函数体里访问:

    _a
    _capacity
    _top

    时,编译器会到 Stack 类域中查找这些成员。

    这就是为什么类外定义成员函数必须加:

    类名::函数名


    五、对象实例化

    5.1 类只是设计图,对象才真正占空间

    类本身只是一个类型,是一种抽象描述。

    比如:

    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;
    };

    这只是定义了 Date 类型。

    此时还没有真正的日期对象。

    只有当我们写:

    Date d1;
    Date d2;

    才真正创建了两个对象。

    这个过程叫:

    类实例化出对象。

    可以用理解为:

    类像建筑设计图,对象像根据图纸建出来的房子。 图纸说明房子应该怎么建,但图纸本身不能住人; 真正建出来的房子才占空间,才能使用。

    在这里插入图片描述


    5.2 一个类可以实例化多个对象

    例如:

    Date d1;
    Date d2;

    d1.Init(2024, 3, 31);
    d2.Init(2024, 7, 5);

    d1.Print();
    d2.Print();

    输出:

    2024/3/31
    2024/7/5

    d1 和 d2 都是 Date 类型。

    它们结构相同,但数据不同。

    也就是说:

    • d1 有自己的 _year、_month、_day;
    • d2 也有自己的 _year、_month、_day;
    • 两个对象的数据互不影响。

    5.3 类和对象的关系

    可以这样总结:

    概念含义
    类型、模板、设计图
    对象 根据类创建出的具体实体
    成员变量 每个对象各自保存一份
    成员函数 所有对象共享同一份函数代码

    一句话:

    类定义共同结构,对象保存具体数据。


    六、对象大小与内存布局

    6.1 对象里面到底存了什么?

    当我们初学的时候会以为:

    类里既有成员变量,也有成员函数,所以对象里应该也存成员变量和成员函数。

    实际上,对象中主要存储的是成员变量。

    成员函数代码不会在每个对象中保存一份。

    原因很简单。

    比如:

    Date d1;
    Date d2;

    d1 和 d2 的日期数据不同,所以它们必须各自保存成员变量。

    但 Init() 和 Print() 的函数代码是一样的。

    如果每个对象都存一份函数代码,就太浪费空间了。

    所以:

    对象保存成员变量,成员函数放在公共代码区,所有对象共享。

    在这里插入图片描述


    6.2 sizeof 对象时算什么?

    看下面这个类:

    class Date
    {
    private:
    int _year;
    int _month;
    int _day;
    };

    如果一个 int 是 4 字节,那么:

    sizeof(Date)

    通常是:

    12

    因为对象中有三个 int 成员变量。

    但是对象大小不一定永远等于成员变量大小简单相加。

    原因是:

    对象大小还要遵守内存对齐规则。


    6.3 内存对齐是什么?

    看下面这个类:

    class A
    {
    private:
    char _ch;
    int _i;
    };

    很多环境下:

    sizeof(A)

    不是 5,而是 8。

    原因是内存对齐。

    可以粗略理解成:

    _ch 占 1 字节
    中间填充 3 字节
    _i 占 4 字节

    所以总大小是 8 字节。

    内存对齐的目的主要是让 CPU 更高效地访问数据。


    6.4 内存对齐的基本规则

    入门阶段可以先记住这几条:

  • 第一个成员从偏移量 0 开始;
  • 后面的成员要放到合适的对齐位置;
  • 对象整体大小通常要是最大对齐数的整数倍;
  • 成员顺序可能影响对象大小。
  • 比如:

    class A
    {
    private:
    char _ch;
    int _i;
    };

    通常布局大概是:

    偏移量内容
    0 _ch
    1~3 填充字节
    4~7 _i

    所以最终大小是 8 字节。

    在这里插入图片描述


    6.5 空类对象为什么大小是 1?

    看下面两个类:

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

    class C
    {
    };

    它们都没有成员变量。

    但是:

    sizeof(B)
    sizeof(C)

    通常都是:

    1

    为什么没有成员变量还要占 1 个字节?

    原因是:

    对象必须在内存中有一个可区分的地址。

    如果空对象大小是 0,那么多个空对象就不好区分。

    所以 C++ 会给空类对象分配 1 个字节作为占位。

    注意,这 1 个字节不是用来存成员函数的,只是为了表示对象存在。


    七、this 指针

    7.1 问题:成员函数怎么知道当前对象是谁?

    看下面代码:

    Date d1;
    Date d2;

    d1.Init(2024, 3, 31);
    d2.Init(2024, 7, 5);

    Init() 函数只有一份代码。

    那么问题来了:

    • d1.Init(…) 时,函数怎么知道要修改 d1?
    • d2.Init(…) 时,函数怎么知道要修改 d2?

    答案是:

    C++ 会给非静态成员函数隐含传递一个 this 指针。


    7.2 this 指针是什么?

    this 指针指向当前调用成员函数的对象。

    例如:

    d1.Init(2024, 3, 31);

    可以粗略理解成编译器背后传了一个对象地址:

    this = &d1;

    而:

    d2.Init(2024, 7, 5);

    可以理解成:

    this = &d2;

    所以成员函数内部访问成员变量时,本质上是通过 this 指针访问的。

    比如:

    void Init(int year, int month, int day)
    {
    _year = year;
    _month = month;
    _day = day;
    }

    可以理解成:

    void Init(int year, int month, int day)
    {
    this->_year = year;
    this->_month = month;
    this->_day = day;
    }


    7.3 this 指针能不能显式使用?

    可以。

    在成员函数内部,可以显式写 this->:

    void Init(int year, int month, int day)
    {
    this->_year = year;
    this->_month = month;
    this->_day = day;
    }

    一般情况下,不写也可以:

    _year = year;

    编译器会自动识别 _year 是成员变量。

    但是在讲解代码、区分成员来源时,显式写 this-> 会更直观。


    7.4 this 指针能不能被修改?

    不能。

    this 指针可以理解成一个指针常量。

    也就是说,它指向当前对象,不能被重新赋值。

    下面写法是错误的:

    this = nullptr; // 错误

    但是可以通过 this 访问成员:

    this->_year = year;


    7.5 this 指针存在哪里?

    this 是成员函数的隐含参数。

    既然是函数参数,它通常和普通参数一样,在函数调用过程中存在。

    它不存放在对象里面。

    所以不要以为每个对象内部都有一个 this 指针。

    正确理解是:

    对象里主要保存成员变量; 调用成员函数时,编译器隐含传入当前对象地址,这个地址就是 this。


    八、空指针调用成员函数的问题

    8.1 为什么有些空指针调用成员函数不崩?

    看下面代码:

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

    private:
    int _a;
    };

    int main()
    {
    A* p = nullptr;
    p->Print();

    return 0;
    }

    有些环境下,这段代码可能会正常输出:

    A::Print()

    原因是:

    Print() 函数内部没有访问任何成员变量。

    虽然 p 是空指针,但函数体中没有真正通过 this 去访问对象内部数据。

    所以它可能暂时看起来“没事”。


    8.2 为什么访问成员变量就可能崩?

    再看这段代码:

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

    private:
    int _a;
    };

    int main()
    {
    A* p = nullptr;
    p->Print();

    return 0;
    }

    这次 Print() 内部访问了 _a。

    而访问 _a 本质上就是:

    this->_a

    此时 this 是空指针。

    所以程序很可能崩溃。


    8.3 这类写法能不能在项目中使用?

    不能。

    空指针调用成员函数属于非常危险的行为。

    即使某些情况下不崩,也不代表它是正确写法。

    这类例子只适合理解:

    • 成员函数代码不在对象内部;
    • 成员变量访问依赖 this;
    • 空指针调用成员函数存在风险。

    实际写代码时,一定要保证指针有效后再调用成员函数。

    在这里插入图片描述


    九、C 和 C++ 实现 Stack 的对比

    9.1 C 语言实现 Stack 的特点

    在 C 语言中,通常先定义结构体:

    typedef int STDataType;

    typedef struct Stack
    {
    STDataType* a;
    int top;
    int capacity;
    } ST;

    然后再定义一组函数:

    void STInit(ST* ps);
    void STPush(ST* ps, STDataType x);
    void STPop(ST* ps);
    STDataType STTop(ST* ps);
    bool STEmpty(ST* ps);
    void STDestroy(ST* ps);

    使用时:

    ST s;
    STInit(&s);
    STPush(&s, 1);
    STPush(&s, 2);

    while (!STEmpty(&s))
    {
    printf("%d\\n", STTop(&s));
    STPop(&s);
    }

    STDestroy(&s);

    这种写法的特点是:

    • 数据放在结构体中;
    • 操作数据的函数在结构体外;
    • 每次调用函数都要手动传 &s;
    • 如果结构体成员暴露,外部可以直接修改内部数据。

    9.2 C++ 实现 Stack 的方式

    C++ 可以把数据和函数都放到类里面:

    #include <iostream>
    #include <cassert>
    #include <cstdlib>
    using namespace std;

    typedef int STDataType;

    class Stack
    {
    public:
    void Init(int n = 4)
    {
    _a = (STDataType*)malloc(sizeof(STDataType) * n);
    if (_a == nullptr)
    {
    perror("malloc fail");
    return;
    }

    _capacity = n;
    _top = 0;
    }

    void Push(STDataType x)
    {
    if (_top == _capacity)
    {
    int newcapacity = _capacity * 2;
    STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);
    if (tmp == nullptr)
    {
    perror("realloc fail");
    return;
    }

    _a = tmp;
    _capacity = newcapacity;
    }

    _a[_top++] = x;
    }

    void Pop()
    {
    assert(_top > 0);
    _top;
    }

    STDataType Top()
    {
    assert(_top > 0);
    return _a[_top 1];
    }

    bool Empty()
    {
    return _top == 0;
    }

    void Destroy()
    {
    free(_a);
    _a = nullptr;
    _top = _capacity = 0;
    }

    private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
    };

    使用时:

    int main()
    {
    Stack s;
    s.Init();

    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    while (!s.Empty())
    {
    cout << s.Top() << endl;
    s.Pop();
    }

    s.Destroy();

    return 0;
    }


    9.3 C++ 版本看起来改变了什么?

    从使用方式上看,C++ 版本更自然。

    C 语言风格:

    STPush(&s, 1);
    STPop(&s);
    STTop(&s);

    C++ 风格:

    s.Push(1);
    s.Pop();
    s.Top();

    C++ 版本不需要手动传 &s。

    原因是:

    成员函数调用时,this 指针会隐含传递当前对象地址。

    例如:

    s.Push(1);

    可以粗略理解成:

    Push(&s, 1);

    只是这个过程由编译器自动完成。


    9.4 C 和 C++ 实现 Stack 的核心区别

    对比项C 语言版本C++ 类版本
    数据保存 struct Stack class Stack
    函数位置 全局函数 成员函数
    调用方式 STPush(&s, x) s.Push(x)
    对象地址传递 手动传结构体指针 this 隐含传递
    数据保护 容易被外部直接修改 成员变量可以设为 private
    接口表达 函数操作结构体 对象调用自己的方法

    在这里插入图片描述


    十、重新理解封装

    10.1 封装不是把代码写复杂

    有些初学者刚接触类时,会觉得:

    以前 C 语言写结构体加函数挺清楚的,为什么 C++ 非要包成类?

    其实类不是为了把代码写复杂。

    它是为了在代码规模变大后,让代码更可控。

    比如对于栈,外部真正需要使用的是:

    Push()
    Pop()
    Top()
    Empty()

    而不是直接修改:

    _a
    _top
    _capacity

    这些内部成员应该由类自己维护。


    10.2 封装让接口和实现分离

    一个设计良好的类,通常会把成员分成两部分:

    public:
    // 对外接口

    private:
    // 内部实现细节

    外部通过 public 接口使用对象。

    内部通过 private 成员维护对象状态。

    这样做的好处是:

    • 外部使用更简单;
    • 内部数据更安全;
    • 后续修改实现时,对外影响更小。

    比如以后 Stack 不想用动态数组实现,改成链表实现。

    只要接口不变:

    Push()
    Pop()
    Top()
    Empty()

    外部使用代码就可以尽量不用改。

    在这里插入图片描述


    十一、常见错误总结

    11.1 类定义结尾忘记分号

    错误:

    class Date
    {
    private:
    int _year;
    }

    正确:

    class Date
    {
    private:
    int _year;
    };


    11.2 类外访问 private 成员

    错误:

    Date d;
    d._year = 2024;

    如果 _year 是 private,类外不能直接访问。

    正确做法是通过公开接口:

    d.Init(2024, 3, 31);


    11.3 类外定义成员函数忘记加类域

    错误:

    void Init(int year, int month, int day)
    {
    _year = year;
    }

    正确:

    void Date::Init(int year, int month, int day)
    {
    _year = year;
    }


    11.4 以为类定义时就分配了对象空间

    类只是类型定义,不是对象。

    class Date
    {
    private:
    int _year;
    int _month;
    int _day;
    };

    这只是说明 Date 对象应该长什么样。

    只有写:

    Date d;

    才真正创建对象。


    11.5 以为成员函数存储在每个对象中

    成员函数不会在每个对象里保存一份。

    对象主要保存成员变量。

    成员函数代码存放在公共代码区,所有对象共享。


    11.6 误以为 this 指针存在对象里

    this 是成员函数调用时隐含传递的参数。

    对象里面不会额外存一个 this 指针。


    十二、全文总结

    13.1 本文核心内容

    本文主要讲了 C++ 类和对象入门中的几个核心知识点:

    • 类的定义;
    • 成员变量和成员函数;
    • 访问限定符;
    • class 和 struct 的区别;
    • 类域;
    • 对象实例化;
    • 对象大小;
    • 内存对齐;
    • this 指针;
    • C 和 C++ 实现 Stack 的区别;
    • 封装思想。

    赞(0)
    未经允许不得转载:171主机测评 » C++ 类和对象入门(一):从 class、访问限定符到 this 指针
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址