【C++学习笔记】【基础】1.类与对象(上)

🍕阿i索 个人主页
《C语言专栏》 《C++专栏》
《数据结构专栏》 《LaTeX专栏》
待更新...

这一篇,我们正式开启 C++ 学习的第一章:类与对象(上)。


一、类的定义

1. 类的定义格式

class 为定义类的关键字,后接类名,{ } 内为类的主体,类定义结束后分号不能省略 。类中的变量称为属性 / 成员变量;类中的函数称为方法 / 成员函数。定义在类内的成员函数默认为 inline 内联函数。

为区分普通变量,成员变量通常加特殊标识(如前缀_、后缀_),C++ 无强制要求。

2. struct 与 class 的关系

  1. C++ 兼容 C 中struct的用法,同时将struct升级为类 ,支持在struct内定义成员函数。
  2. 访问权限默认值不同:class未指定访问限定符时,成员默认为privatestruct未指定时,成员默认为public
  3. 使用场景:一般推荐用class定义类;无需私有成员的简单场景,可使用struct
  4. 类型标识:C++ 中struct的类名可直接作为类型使用,无需像 C 语言那样通过typedef重定义。

3. 类的访问限定符

C++ 通过访问限定符实现封装,将对象的属性与方法结合,选择性对外提供接口,访问权限作用域从限定符出现位置开始,到下一个限定符或者类结束为止。

访问限定符 类外访问性 适用场景
public 可直接访问 对外提供的接口(如成员函数)
protected 不可直接访问 继承场景使用(与 private 暂无区别)
private 不可直接访问 类的内部属性(成员变量)
通用规范 :成员变量设为private/protected,需外部调用的成员函数设为public

4. 类域

  1. 类定义了一个新的作用域,类的所有成员均属于该类域。
  2. 类的声明与定义分离时,类体外定义成员函数必须通过::作用域操作符指明所属类域,否则编译器会将其视为全局函数,导致无法找到类内成员。
  3. 不同类域的函数无重载关系:重载要求函数在同一作用域,不同类的同名函数因作用域不同,不构成重载。

类的声明与定义分离示例

复制代码
#include<iostream>
using namespace std;
class Stack
{
public:
    // 类内声明成员函数
    void Init(int n = 4);
private:
    int* _a;
    size_t _capacity;
    size_t _top;
};
// 类外定义,通过Stack::指明类域
void Stack::Init(int n)
{
    _a = (int*)malloc(sizeof(int) * n);
    if (nullptr == _a)
    {
        perror("malloc申请空间失败");
        return;
    }
    _capacity = n;
    _top = 0;
}

二、类的实例化

1. 实例化的概念

  1. 定义:用类类型在物理内存中创建对象的过程,称为类实例化出对象。
  2. 类与对象的关系:类是对对象的抽象描述 / 模型 ,仅声明成员变量,不分配实际内存;对象是类的具体实例,实例化时才会为成员变量分配物理内存,存储实际数据。
  3. 一个类可实例化多个对象,每个对象拥有独立的成员变量空间,存储各自的数据。

2. 对象的大小计算

(1)对象的存储规则
  1. 对象中仅存储成员变量 ,成员函数编译后为指令,存储在公共代码段,所有对象共享同一套成员函数指令。
  2. 原因:若每个对象存储成员函数指针,会造成内存冗余(如 100 个对象会重复存储 100 次相同指针);编译器编译链接时已确定成员函数地址,无需运行时存储(动态多态除外)。
(2)内存对齐规则

C++ 类实例化的对象,其成员变量布局遵循结构体内存对齐规则,具体如下:

  1. 第一个成员在与对象偏移量为 0 的地址处。
  2. 其他成员变量对齐到对齐数的整数倍地址处;
    嵌套结构体时:

嵌套的结构体对齐到自身的最大对齐数的整数倍处;

整个对象的总大小为所有最大对齐数(含嵌套结构体)的整数倍。

对齐数 = 编译器默认对齐数 与 成员变量大小的较小值(VS 中默认对齐数为 8)。

对象的总大小为最大对齐数(所有成员的对齐数的最大值)的整数倍

(3)特殊情况:无成员变量的类

无成员变量的类(或仅含成员函数的类),实例化的对象大小为1 字节。

该字节仅用于占位标识对象的存在,不存储任何实际数据。

(4)大小计算示例
复制代码
#include<iostream>
using namespace std;
class A // 含char和int成员
{
public:
    void Print() { cout << _ch << endl; }
private:
    char _ch;  // 大小1,对齐数1
    int _i;    // 大小4,对齐数4(VS默认8,取较小值4)
};
class B // 仅含成员函数
{
public:
    void Print() {}
};
class C // 无任何成员
{};
int main()
{
    cout << sizeof(A) << endl; // 8(char占1字节,补3字节对齐,int占4字节,总大小8=4*2)
    cout << sizeof(B) << endl; // 1(占位字节)
    cout << sizeof(C) << endl; // 1(占位字节)
    return 0;
}

三、this 指针

1. this 指针的引入原因

类的成员函数为所有对象共享,函数体中无对象区分标识,this 指针用于解决成员函数如何识别操作的是哪个对象的问题

2. this 指针的特性

  1. 编译器自动添加:类的成员函数编译后,形参第一个位置会自动增加当前类类型的 const 指针this,无需程序员显式写在形参 / 实参位置。
  2. 实际函数原型:如Date类的Init函数,声明为void Init(int year, int month, int day)真实原型为void Init(Date* const this, int year, int month, int day)
  3. 成员访问本质:成员函数中访问成员变量 / 成员函数,本质是通过this指针访问 ,如_year = year;等价于this->_year = year;
  4. 显式使用:C++ 禁止在形参 / 实参位置显式写this,但可在成员函数体内显式使用
  5. 不可修改:this指针为const指针,指向不可修改(如this = nullptr;会编译报错)。

3. this 指针的调用机制

对象调用成员函数时,编译器会自动将对象的地址作为实参传递给 this 指针 ,如:d1.Init(2024,3,31);等价于d1.Init(&d1,2024,3,31);

4. this 指针的经典例题分析

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

分析Print函数为成员函数,存储在公共代码段,调用时仅需函数地址,无需解引用this指针(p),因此空指针调用无崩溃。

例题 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,本质是this->_athis指针为nullptr,解引用空指针导致运行崩溃。

例题 3:this 指针的存储区域

问题 :this 指针存在内存哪个区域?(答案:A 栈)分析 :this 指针是成员函数的形参,形参存储在函数的栈帧中,因此 this 指针位于栈区。

四、C 与 C++ 实现栈(Stack)的对比

C++ 基于 C 的底层逻辑,通过面向对象的封装特性重构栈的实现,核心变化体现在语法和管理规范上,底层内存操作逻辑一致。

1. 差异对比

特性 C 语言实现 C++ 实现
数据与函数关系 数据(结构体)与函数分离,函数需显式传递结构体地址 数据(成员变量)与函数(成员函数)封装在类内,通过 this 指针隐式传递对象地址
访问控制 无访问限定符,结构体成员可随意修改,安全性低 通过 public/private 实现访问控制,成员变量私有化,仅通过接口操作,安全性高
类型使用 需通过 typedef 重定义结构体为新类型 类名直接作为类型使用,无需 typedef
函数调用 函数名需加前缀(如 STInit),区分模块 直接通过对象调用成员函数(如 s.Init ()),语法更简洁
缺省参数 不支持缺省参数,需手动传递所有参数 成员函数支持缺省参数(如 Init (int n=4)),使用更灵活

C 语言:结构体 + 全局函数

复制代码
typedef struct Stack {
    int* a;
    int top, capacity;
} ST;

void STInit(ST* ps);
void STPush(ST* ps, int x);

C++:类 + 成员函数

复制代码
class Stack {
public:
    void Init(int cap = 4);
    void Push(int x);
private:
    int* _a;
    int _top, _capacity;
};

封装的本质

C++ 封装的本质是对数据和方法进行更严格的规范管理,将数据私有化,避免外部随意修改,仅通过对外暴露的公共接口操作数据,降低程序出错概率。

五、面向对象三大特性(基础)

C++ 面向对象的三大核心特性为:封装、继承、多态 ,本章节核心讲解封装的基础实现:

  1. 封装的第一层:将数据(成员变量)和操作数据的方法(成员函数)封装在类的同一作用域内
  2. 封装的第二层:通过访问限定符(public/private/protected)控制类成员的对外访问权限,实现数据的隐藏和接口的暴露。

六、编程命名规范(惯例)

  1. 驼峰命名法:
    • 类名、函数名:大驼峰(单词首字母大写,如StackInitDate);
    • 普通变量:小驼峰(第一个单词首字母小写,后续单词首字母大写,如stackSize);
  2. 下划线命名法:单词间用下划线分隔(如stack_init),适用于 C/C++ 的函数 / 变量命名;
  3. 成员变量:在驼峰 / 下划线基础上加特殊标识(如_am_capacity),区分普通变量。

下一篇再见🍕

相关推荐
寻寻觅觅☆13 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
时代的凡人13 小时前
0208晨间笔记
笔记
fpcc13 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
今天只学一颗糖14 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
testpassportcn14 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
ceclar12315 小时前
C++使用format
开发语言·c++·算法
lanhuazui1015 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee4415 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索
老约家的可汗15 小时前
初识C++
开发语言·c++
crescent_悦16 小时前
C++:Product of Polynomials
开发语言·c++