C++类和对象(1)

在 C++ 的学习中,类和对象是面向对象编程的基石,它实现了面向对象三大特性中的封装,让代码的管理和复用性大幅提升。本文将从类的定义、实例化、对象大小计算、this 指针四个核心维度,梳理 C++ 类和对象的基础知识点,同时对比 C 语言的实现方式,理解 C++ 封装的本质。

一、类的定义:抽象事物的属性与行为

类是对现实事物的抽象描述,将事物的属性 (成员变量)和行为 (成员函数)封装在一起,是创建对象的 "模板"。

1.1 类的定义格式

使用class关键字定义类,语法格式如下:

cpp 复制代码
class 类名
{
    // 访问限定符 + 类的成员(成员变量/成员函数)
};// 注意:分号不能省略!
  • 成员变量 :也叫类的属性,建议添加特殊标识(如_前缀 / 后缀、m_开头)区分普通变量,这是行业惯例而非 C++ 语法强制要求。
  • 成员函数 :也叫类的方法,定义在类内的成员函数默认是inline(内联函数),减少函数调用开销。
  • 特殊说明 :C++ 兼容 C 的struct,并将其升级为类 ------struct中可以定义成员函数,且无需typedef即可直接作为类型使用。classstruct的核心区别是默认访问权限不同(后文详述)。

1.2 访问限定符:实现封装的核心手段

封装是面向对象的核心特性之一,C++ 通过访问限定符 选择性地开放类的接口,限制外部对类内部成员的直接访问,本质是对数据和方法的严格管理。C++ 提供三种访问限定符,核心规则如下

C++ 提供三种访问限定符,核心规则如下:

访问限定符 类外访问性 作用域
public 可直接访问 从当前位置到下一个限定符 / 类结束
protected 不可直接访问 从当前位置到下一个限定符 / 类结束
private 不可直接访问 从当前位置到下一个限定符 / 类结束

关键注意点

  1. protectedprivate在类外的访问权限一致,区别仅在继承中体现;
  2. 类内成员无访问限制,即使是private成员,类内的成员函数也可访问;
  3. 默认访问权限:class默认privatestruct默认public(兼容 C 的设计);
  4. 工程惯例:成员变量设为private/protected(避免外部随意修改),对外提供的接口函数设为public

1.3 类域:类的专属作用域

类定义了一个新的局部作用域 ,类的所有成员都属于这个类域。当类的声明和定义分离 (成员函数写在类外)时,需要使用 作用域操作符:: 指明成员所属的类,否则编译器会将其当作全局函数处理。

示例:类外定义成员函数

cpp 复制代码
class Stack
{
public:
    void Init(int n = 4); // 类内声明
private:
    int* array;
    size_t capacity;
    size_t top;
};
// 类外定义,需用Stack::指明类域
void Stack::Init(int n)
{
    array = (int*)malloc(sizeof(int) * n);
    // ... 初始化逻辑
}

二、类的实例化:从 "模板" 到 "实体"

2.1 实例化的概念

类类型在物理内存中创建对象 的过程,称为类的实例化。核心认知需明确:

  1. 类是抽象的模板 ,仅声明成员变量,不分配实际物理内存,无法直接存储数据;
  2. 对象是类的具体实例,实例化时会为对象分配物理内存,存储成员变量;
  3. 一个类可以实例化多个独立对象 ,每个对象拥有专属的成员变量,互不干扰。

形象类比:类是建筑设计图,仅规划房间布局;对象是根据设计图建造的房子,拥有实际的空间,可存储物品(数据),可以建多个房子。

示例:countday 类的实例化

cpp 复制代码
#include <iostream>
using namespace std;
class countday
{
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()
{
    countday d1, d2; // 实例化对象,分配物理内存
    d1.Init(2024, 3, 31); // 调用成员函数初始化
    d2.Init(2024, 7, 5);
    d1.Print(); // 调用成员函数打印
    d2.Print();
    return 0;
}

2.2 对象的大小计算:仅存储成员变量

类的成员分为成员变量成员函数 ,但对象的内存中仅存储成员变量 ,成员函数会被存储在公共代码段(所有对象共享),原因如下:

  1. 成员函数编译后是一段指令,无需为每个对象重复存储,否则会造成严重的内存浪费;
  2. 所有对象调用的成员函数是同一个,仅操作的成员变量不同(通过 this 指针区分,后文详述)。

因此,对象的大小 = 其成员变量的大小之和 + 内存对齐的填充字节 ,完全遵循 C++ 的内存对齐规则

1. 内存对齐规则(与结构体一致)
  1. 第一个成员在偏移量为 0 的地址处;
  2. 其他成员对齐到对齐数的整数倍地址处(对齐数 = 编译器默认对齐数 与 成员大小的较小值,VS 默认 8,GCC 默认 4);
  3. 类的总大小为最大对齐数的整数倍;
  4. 嵌套类 / 结构体时,嵌套的部分对齐到自身最大对齐数的整数倍,整体大小为所有最大对齐数的整数倍。
2. 特殊情况:空类 / 无成员变量的类

空类(如class C{})或仅含成员函数的类,实例化的对象大小为1 字节 。这 1 字节无实际数据意义,仅为占位标识,证明对象在内存中存在。

示例:对象大小计算

cpp 复制代码
class A
{
public:
    void Print() {}
private:
    char _ch; // 1字节
    int _i;   // 4字节
};
class B { public: void Print() {} };
class C {};
// 内存对齐后:A的大小=8(1+3填充+4),B=1,C=1
cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) << endl;

三、this 指针:解决对象的成员区分问题

3.1 this 指针的产生原因

同一个类的多个对象,共享类的成员函数,但拥有独立的成员变量。那么成员函数如何知道操作的是哪个对象的成员变量 ?C++ 通过隐含的 this 指针 解决该问题:编译器会为每个非静态成员函数,自动添加一个形参第一个位置的类类型 const 指针,即 this 指针,用于指向调用该函数的对象。

3.2 this 指针的核心特性

  1. 隐式传递 :编译器自动在形参添加 类名* const this,调用函数时自动将对象地址作为实参传递,程序员不能在形参 / 实参位置显式书写
  2. 显式使用 :函数体内可显式使用 this 指针访问成员变量 / 成员函数,如this->_year = year;,编译器默认省略 this 指针;
  3. 不可修改 :this 指针是const指针,指向不可变(不能执行this = nullptr;),保证始终指向调用函数的对象;
  4. 存储位置 :this 指针作为函数形参,存储在栈区(而非对象内部,不占用对象的内存空间)。

示例:this 指针的隐式与显式使用

cpp 复制代码
class Date
{
public:
    // 编译器实际处理为:void Init(Date* const this, int year, int month, int day)
    void Init(int year, int month, int day)
    {
        this->_year = year;   // 显式使用this
        _month = month;       // 隐式使用this,编译器自动补全this->_month
        this->_day = day;
    }
private:
    int _year, _month, _day;
};
int main()
{
    Date d1;
    d1.Init(2024, 3, 31); // 编译器实际处理为:d1.Init(&d1, 2024, 3, 31);
    return 0;
}

3.3 this 指针的经典考题分析

通过两道考题理解 this 指针的工作机制:

cpp 复制代码
class A
{
public:
    void Print() { cout << "A::Print()" << endl; }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print(); // 结果:正常运行(C)
    return 0;
}

分析Print函数是成员函数,存储在公共代码段,调用时仅需传递 this 指针(p=nullptr),但函数体内未访问任何成员变量 (无需解引用 this 指针),因此不会触发空指针访问崩溃。

cpp 复制代码
class A
{
public:
    void Print() { cout << _a << endl; } // 隐含this->_a
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print(); // 结果:运行崩溃(B)
    return 0;
}

分析 :函数体内访问_a,编译器会补全为this->_a,而 this 指针为 nullptr,解引用空指针会导致程序运行崩溃。

四、C++ 与 C 实现栈的对比:理解封装的优势

C 语言是面向过程的编程语言,数据和函数分离;C++ 通过类实现了数据和方法的封装,结合访问限定符和 this 指针,让代码更简洁、安全、易维护。以下是 C 和 C++ 实现 Stack 的核心区别:

特性 C 语言实现 C++ 实现
数据与函数关系 数据(结构体)和函数分离,函数需传结构体地址 数据和函数封装在类内,通过 this 指针隐式传递对象地址
访问控制 无访问限定符,可直接修改结构体成员 成员变量设为 private,仅通过 public 接口操作,避免乱修改
语法便捷性 需 typedef 重命名结构体,函数参数繁琐 类名直接作为类型,成员函数可设缺省参数,调用更简洁
封装性 无封装,数据安全性低 封装性强,数据和方法统一管理,安全性高

五、核心知识点总结

  1. 类是抽象模板,对象是类的具体实例,实例化才会分配物理内存 ,对象仅存储成员变量,成员函数共享在公共代码段;
  2. 访问限定符(public/protected/private)是 C++ 实现封装的核心,工程中成员变量私有化,接口公有化;
  3. 类域是专属作用域,类外定义成员函数需用::指明类域;
  4. this 指针是编译器隐式添加的 const 指针,指向调用成员函数的对象,存储在栈区,解决对象的成员区分问题;
  5. 对象大小遵循内存对齐规则,空类 / 无成员变量的类对象大小为 1 字节(占位标识);
  6. C++ 的封装本质是对数据和方法的严格管理,将数据和操作数据的方法封装在一起,提升代码的安全性和可维护性。
相关推荐
2401_873204652 小时前
模板编译期循环展开
开发语言·c++·算法
神舟之光2 小时前
Java面向对象编程知识补充学习-2026.3.21
java·开发语言·学习
奶人五毛拉人一块2 小时前
C++入门学习
开发语言·c++·函数重载·入门·nullptr
吃不饱的得可可2 小时前
protobuf万字总结(C++)
开发语言·c++
m0_662577972 小时前
嵌入式C++安全编码
开发语言·c++·算法
2301_810160952 小时前
代码生成器优化策略
开发语言·c++·算法
HUTAC2 小时前
关于进制转换及其应用的算法题总结
数据结构·c++·算法
SPC的存折2 小时前
Python3编程之python基础
开发语言·python
暮冬-  Gentle°2 小时前
C++中的工厂模式实战
开发语言·c++·算法