news 2026/5/12 1:25:33

C++ 类和对象完全指南:从入门到精通(上篇:类与对象基础)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ 类和对象完全指南:从入门到精通(上篇:类与对象基础)

本文是 C++ 类和对象系列的第一篇,涵盖:类的定义、访问限定符、类域、实例化、对象大小、this 指针,以及 C++ 封装相比 C 语言的优势。每个知识点都有完整代码示例

一:类的定义

1.基本语法:

类是 C++ 面向对象编程的核心。类将数据(成员变量)和操作数据的方法(成员函数)封装在一起。

#include <iostream> #include <string> using namespace std; class Stack { //class定义一个类 public: // 成员函数(类内定义默认为 inline) void Init(int n = 4) { _a = (int*)malloc(sizeof(int) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } void Push(int x) { if (_top == _capacity) { int newcapacity = _capacity * 2; int* tmp = (int*)realloc(_a, newcapacity * sizeof(int)); if (tmp == nullptr) return; _a = tmp; _capacity = newcapacity; } _a[_top++] = x; } int Top() { assert(_top > 0); return _a[_top - 1]; } void Destroy() { free(_a); _a = nullptr; _top = _capacity = 0; } private: //成员变量 int* _a; size_t _capacity; size_t _top; };

语法要点:

1.class关键字定义类,{}内是类体

2.类体末尾的分号不能省略(新手最容易犯的错误)

3.类中的变量称为成员变量或属性

4.类中的函数称为成员函数或方法

2.成员变量的命名惯例

为了区分成员变量和普通变量,常见的命名习惯有:

风格示例说明
下划线后缀year_Google 风格
下划线前缀_year常见于竞赛/教学代码
m 前缀m_yearMFC/Windows 风格
class Date { private: int _year; // 下划线前缀 int month_; // 下划线后缀 int m_day; // m 前缀 // 三种风格都可以,但要保持一致 };

3.struct 也可以定义类

C++ 兼容 C 语言的struct,同时将其升级为类。

classstruct的区别:

特性classstruct
默认访问限定符privatepublic
使用场景复杂对象(有私有数据)纯数据结构(POD)
class MyClass { int x; // 默认 private,外部不能访问 }; struct MyStruct { int x; // 默认 public,外部可以访问 };

4.类内定义的成员函数默认为 inline

定义在类内部的成员函数,编译器会默认将其视为inline函数,建议短小的函数定义在类内,复杂的函数声明在类内、定义在类外。

class Example { public: // 短小函数:定义在类内(自动 inline) int getValue() const { return _value; } // 复杂函数:只声明 void complexFunction(); }; // 类外定义 void Example::complexFunction() { // 复杂的实现... }

二:访问限定符

1.基本用法

限定符作用类外访问
public公有成员✅ 可以
private私有成员❌ 不可以(默认)
protected保护成员❌ 不可以(派生类可访问)
class Date { public: // 公有成员函数:对外接口 void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() const { cout << _year << "/" << _month << "/" << _day << endl; } // 公有成员变量(通常不推荐) int version = 1; private: // 私有成员变量:隐藏内部数据 int _year; int _month; int _day; void privateHelper() { // 私有函数:仅供类内部使用 cout << "内部辅助函数" << endl; } }; int main() { Date d; d.Init(2024, 7, 5); // public,可以调用 d.Print(); // public,可以调用 d.version = 2; // public,可以访问(但不推荐) // d._year = 2024; // private,编译错误 // d.privateHelper(); // private,编译错误 return 0; }

2.访问限定符的作用域

访问限定符从它出现的位置开始,一直作用到下一个访问限定符出现为止。

class Demo { int a; // 默认 private(class 默认) public: // 从这里开始 public int b; int c; private: // 从这里开始 private int d; int e; protected: // 从这里开始 protected int f; }; // 类结束

3.封装的设计原则

数据隐藏 + 接口暴露

class BankAccount { public: // 对外接口:通过方法访问和修改数据 void deposit(double money) { if (money > 0) { _balance += money; } } double getBalance() const { return _balance; } private: // 内部数据:外部无法直接修改 double _balance = 0; }; int main() { BankAccount account; account.deposit(100); cout << account.getBalance() << endl; // 100 // account._balance = 1000; // 编译错误,不能直接修改 return 0; }

三:类域

1.类外定义成员函数

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); _capacity = n; _top = 0; }

为什么必须指定类域?

如果不指定Stack::,编译器会把Init当成全局函数,找不到_a_capacity_top的声明

2.类域影响编译查找规则

class Example { public: void func(); static void staticFunc(); private: int _value; static int _staticValue; }; // 正确:指定了类域,编译器知道这些成员属于 Example void Example::func() { _value = 10; // 在类域中找到 _value staticFunc(); // 在类域中找到 staticFunc } // 错误:不指定类域,编译器找不到成员 // void func() { // _value = 10; // 找不到 _value // }

四:实例化

1.什么是实例化?

类是对象的抽象描述(相当于建筑设计图),不占用内存空间。
实例化是用类类型在物理内存中创建对象的过程(相当于盖房子),占用实际内存。

class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() const { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; // 声明,没有开空间 int _month; int _day; }; int main() { // 实例化对象:此时才分配内存 Date d1; Date d2; d1.Init(2024, 7, 5); d2.Init(2024, 8, 10); d1.Print(); // 2024/7/5 d2.Print(); // 2024/8/10 return 0; }

2.类与对象的关系(生活类比)

概念生活类比编程类比
建筑设计图class Date { ... };
实例化照图纸盖房子Date d;
对象具体的房子d是一个具体的对象

3.一个类可以实例化多个对象

Date d1, d2, d3; // 三个独立的对象,各自有各自的 _year/_month/_day d1.Init(2024, 1, 1); d2.Init(2024, 2, 2); // d1 和 d2 的数据互不影响

五:对象大小与内存对齐

1.对象中存储什么?

结论:对象中只存储成员变量,不存储成员函数。成员函数被编译成一段指令,存储在代码段。如果每个对象都存储成员函数的指针,会浪费大量内存。

class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() const { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1, d2; cout << sizeof(d1) << endl; // 12(3个int,每个4字节) // d1 和 d2 的成员函数是同一份代码,不重复存储 return 0; }

2.空类的大小

class B { public: void Print() {} }; class C { }; int main() { cout << sizeof(B) << endl; // 1(空类占位) cout << sizeof(C) << endl; // 1(空类占位) return 0; }

为什么空类大小为 1?如果一个字节都不给,创建对象时无法在内存中标识它的存在。1 字节纯粹是为了占位,表示对象存在。

3.内存对齐规则

C++ 规定类实例化的对象也要符合内存对齐规则。

对齐规则:

1.第一个成员在偏移量为 0 的地址处

2.其他成员要对齐到对齐数的整数倍地址

3.对齐数=min(编译器默认对齐数, 成员大小)

VS 中默认对齐数为 8

Linux/GCC 中默认对齐数为 8

4.结构体总大小 =最大对齐数的整数倍

5.嵌套结构体时,内部结构体对齐到自己的最大对齐数的整数倍

class A { public: void Print() { cout << _ch << endl; } private: char _ch; // 1字节,偏移0 int _i; // 4字节,对齐数=min(8,4)=4,偏移4 }; // 总大小 = 8(最大对齐数4的倍数) class D { char c1; // 1字节,偏移0 int i; // 4字节,对齐数4,偏移4-7 char c2; // 1字节,偏移8 }; // 总大小 = 12(最大对齐数4的倍数 → 12) class E { char c1; // 1字节,偏移0 char c2; // 1字节,偏移1 int i; // 4字节,对齐数4,偏移4-7 }; // 总大小 = 8(最大对齐数4的倍数 → 8) int main() { cout << sizeof(A) << endl; // 8 cout << sizeof(D) << endl; // 12 cout << sizeof(E) << endl; // 8 return 0; }

六:this 指针

1.this 指针的本质

this指针是 C++ 为成员函数隐含添加的一个参数,指向当前对象。

class Date { public: // 你写的代码 void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } // 编译器实际处理的代码 // void Init(Date* const this, int year, int month, int day) { // this->_year = year; // this->_month = month; // this->_day = day; // } };

2.this 指针的使用

class Date { public: void Init(int year, int month, int day) { // 可以直接访问成员变量(编译器自动转换成 this->) _year = year; // 也可以显式使用 this this->_month = month; this->_day = day; // this = nullptr; // 编译错误,this 是 const 指针,不能被修改 } // 返回 this 对象(支持链式调用) Date& SetYear(int year) { this->_year = year; return *this; // 返回当前对象 } Date& SetMonth(int month) { _month = month; return *this; } Date& SetDay(int day) { _day = day; return *this; } void Print() const { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d; d.Init(2024, 7, 5); // 链式调用(因为每个 Set 函数都返回 *this) d.SetYear(2025).SetMonth(8).SetDay(10); d.Print(); // 2025/8/10 return 0; }

3.经典面试题:空指针调用成员函数

class Test { public: void Print1() { cout << "Print1() called" << endl; // 不访问成员变量,正常运行 } void Print2() { cout << "_a = " << _a << endl; // 访问成员变量(需要 this),崩溃 } void Print3() { cout << "this = " << this << endl; // 打印 this 地址(不崩溃) } private: int _a = 10; }; int main() { Test* p = nullptr; p->Print1(); // 输出:Print1() called // 原因:不需要访问成员变量,相当于调用一个普通函数 // p->Print2(); // 崩溃 // 原因:需要读取 _a,相当于 this->_a,但 this = nullptr p->Print3(); // 输出:this = 0(不崩溃) return 0; }

结论: 空指针调用成员函数,只要不访问成员变量(即不解引用 this),就不会崩溃。

4.this 指针存在哪里?

this是作为隐含参数传递给成员函数的,通常通过寄存器(如 ECX)传递,不存储在对象中,也不存储在固定的内存区域。

七:C++ 与 C 实现 Stack 对比(封装体现)

1.C 语言实现 Stack(过程式)

// C 风格 Stack(用 C++ 语法模拟) #include <stdio.h> #include <stdlib.h> #include <assert.h> typedef int STDataType; typedef struct CStack { STDataType* a; int top; int capacity; } CStack; void CStackInit(CStack* ps) { ps->a = NULL; ps->top = 0; ps->capacity = 0; } void CStackDestroy(CStack* ps) { free(ps->a); ps->a = NULL; ps->top = ps->capacity = 0; } void CStackPush(CStack* ps, STDataType x) { if (ps->top == ps->capacity) { int newcap = ps->capacity == 0 ? 4 : ps->capacity * 2; STDataType* tmp = (STDataType*)realloc(ps->a, newcap * sizeof(STDataType)); if (tmp == NULL) return; ps->a = tmp; ps->capacity = newcap; } ps->a[ps->top++] = x; } int CStackTop(CStack* ps) { assert(ps->top > 0); return ps->a[ps->top - 1]; } void CStackPop(CStack* ps) { assert(ps->top > 0); --ps->top; } int CStackEmpty(CStack* ps) { return ps->top == 0; } int main() { CStack s; CStackInit(&s); CStackPush(&s, 1); CStackPush(&s, 2); CStackPush(&s, 3); while (!CStackEmpty(&s)) { printf("%d\n", CStackTop(&s)); CStackPop(&s); } CStackDestroy(&s); return 0; }

C 风格的缺点:

  1. 需要手动传递结构体指针(&s

  2. 数据和方法分离,相关代码不在一起

  3. 没有数据保护,外部可以直接修改s.top等成员

  4. 容易忘记调用Init/Destroy

2.C++ 实现 Stack(封装)

// C++ 风格 Stack(封装) #include <iostream> #include <cassert> #include <cstdlib> using namespace std; typedef int STDataType; class CPPStack { public: // 构造函数(自动初始化) CPPStack(int n = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } // 析构函数(自动清理) ~CPPStack() { free(_a); _a = nullptr; _top = _capacity = 0; } void Push(STDataType x) { if (_top == _capacity) { int newcap = _capacity * 2; STDataType* tmp = (STDataType*)realloc(_a, newcap * sizeof(STDataType)); if (tmp == NULL) return; _a = tmp; _capacity = newcap; } _a[_top++] = x; } void Pop() { assert(_top > 0); --_top; } int Top() { assert(_top > 0); return _a[_top - 1]; } bool Empty() { return _top == 0; } private: STDataType* _a; size_t _capacity; size_t _top; }; int main() { CPPStack s; // 自动调用构造函数初始化 s.Push(1); s.Push(2); s.Push(3); while (!s.Empty()) { cout << s.Top() << endl; s.Pop(); } // 离开作用域时自动调用析构函数清理 return 0; }

3.对比总结

对比维度C 实现C++ 实现(封装)
数据和方法分离封装在一个类中
数据保护无(外部可直接访问修改)有(private 保护成员)
调用方式CStackPush(&s, 1)s.Push(1),更自然
this 指针手动传递结构体指针自动隐式传递
初始化/清理手动调用Init/Destroy构造/析构自动调用
类型使用需要typedef类名直接当类型
缺省参数不支持(C语言)支持,更方便

(析构、构造等函数在中篇会介绍)

4.封装的本质

封装是一种更严格规范的管理,避免数据被随意访问和修改。

// C 风格:外部可以直接修改内部数据 CStack s; s.top = 100; // 危险!可以绕过 Push 直接修改 // C++ 风格:外部无法直接修改私有成员 CPPStack s; // s._top = 100; // 编译错误,_top 是 private s.Push(100); // 只能通过公有接口操作

这保证了数据的完整性和一致性。

上篇总结

知识点核心内容
类的定义class+ 成员变量 + 成员函数
访问限定符public/private/protected控制访问权限
类域成员函数类外定义需要类名::
实例化类不占内存,对象占内存
对象大小只存储成员变量,符合内存对齐
this 指针隐含参数,指向当前对象
封装对比C++ 将数据和方法封装,比 C 更安全、更方便

下一篇预告:类和对象(中篇)—— 默认成员函数:构造、析构、拷贝构造、赋值重载、const 成员函数。

有任何疑问或需要补充的内容,欢迎随时交流!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/12 1:20:36

AI- RAG笔记02 - Load Chunking

导读 本文学习来源 all-in-rag 个人学习笔记整理总结&#xff0c;有错误或者遗漏希望大家指正 Load 解决“怎么把不同格式的资料读进来&#xff0c;并变成可处理的文本和元数据”。Chunking 解决“怎么把长文档切成适合检索和生成的知识块”。 Load 和 Chunking 在 RAG 中的…

作者头像 李华
网站建设 2026/5/12 1:18:34

PowerShell玩转Excel COM对象:从入门到解决‘被呼叫方拒绝’报错

PowerShell深度操控Excel COM对象&#xff1a;从进程管理到异常处理实战指南 当你在深夜加班赶制报表&#xff0c;突然遭遇"被呼叫方拒绝接收呼叫"的红色错误提示时&#xff0c;那种挫败感每个自动化办公开发者都深有体会。Excel COM对象就像个傲娇的合作伙伴——用好…

作者头像 李华
网站建设 2026/5/12 1:18:33

从零开始学AI:一个面向新手的终极学习指南

从零开始学AI&#xff1a;一个面向新手的终极学习指南 最近两年&#xff0c;人工智能的发展速度快得让人眼花缭乱。ChatGPT、Midjourney、Sora等产品接连引爆市场&#xff0c;让AI从一个相对小众的技术领域&#xff0c;变成了每个人都在谈论的话题。 面对这股浪潮&#xff0c;很…

作者头像 李华
网站建设 2026/5/12 1:15:40

CoPaw开源RGB灯光控制框架:跨设备同步与插件化开发实战

1. 项目概述与核心价值最近在折腾一个挺有意思的开源项目&#xff0c;叫CoPaw&#xff0c;作者是 felix800822。说实话&#xff0c;第一次看到这个项目名&#xff0c;我以为是和“协作”或者“爪子”相关的工具&#xff0c;但深入研究后发现&#xff0c;它其实是一个专注于RGB灯…

作者头像 李华