当前位置: 首页 > news >正文

类和对象详解(1)

一、类的定义

1.1 定义格式与核心要素

  • 关键字与结构:用classstruct定义类,格式为class 类名{成员变量+成员函数};末尾分号不可省略
    • 类体中的变量称为成员变量 或 属性,函数称为成员函数 或 方法
    • 成员变量命名惯例:加前缀_(如_year)或m_(如m_capacity),非 C++ 强制要求,仅为区分成员变量与局部变量。
  • class 与 struct 的区别
    特性classstruct
    默认访问权限private(类外不可直接访问)public(类外可直接访问)
    兼容性仅 C++ 特性兼容 C 的结构体用法,同时支持定义函数
    推荐场景定义面向对象的类兼容 C 代码或简单数据结构
  • 成员函数特殊规则:定义在类内部的成员函数,默认视为inline(内联函数),适合代码量少的函数;类外定义需用::(作用域操作符)指定类域。

1.2 访问限定符

  • 作用:实现 C++ 的 “封装” 特性,控制类成员在类外的访问权限,保护数据不被随意修改。
  • 三种限定符及规则
    限定符访问权限作用域范围
    public类内、类外均可访问从当前限定符开始,到下一个限定符结束
    protected类内可访问,类外不可访问(继承时生效区别)同 public
    private类内可访问,类外不可访问同 public
  • 使用建议:成员变量通常设为private或protected,提供public的成员函数(如InitPush)供类外间接操作数据,确保数据安全性。

1.3 类域

  • 概念:类定义了独立的作用域,所有成员(变量 / 函数)均属于该类域,编译器通过类域查找成员声明 / 定义。
  • 类外定义成员函数:需显式用类名::成员函数名指定类域,否则编译器会将函数视为全局函数,导致无法找到类内成员而报错。

 

  • class Stack {
    public:void Init(int n = 4); // 类内声明
    private:int* _a;
    };
    // 类外定义,指定Stack类域
    void Stack::Init(int n) {_a = (int*)malloc(sizeof(int) * n);
    }
    

二、类的实例化

2.1 实例化概念

  • 核心逻辑:类是 “抽象模板”(如建筑设计图),仅声明成员变量(未分配内存);实例化是用类类型创建对象(如按设计图建房子),此时才为对象分配物理内存,存储成员变量。
  • 关键特性
    • 一个类可实例化多个对象,每个对象拥有独立的成员变量存储空间(存储各自数据)。
    • 成员函数不存储在对象中,而是统一存放在代码段(公共区域),所有对象共享同一套成员函数指令,避免内存浪费。

2.2 对象大小计算

  • 计算规则:对象仅存储成员变量,大小需遵循内存对齐规则(与结构体内存对齐一致),成员函数不占对象内存。
  • 内存对齐规则
    1. 第一个成员变量对齐到对象偏移量为 0 的地址。
    2. 其他成员变量对齐到 “对齐数” 的整数倍地址,对齐数 = 编译器默认对齐数与成员大小的较小值(VS 默认对齐数为 8)。
    3. 对象总大小为 “最大对齐数”(所有成员对齐数的最大值)的整数倍。
    4. 嵌套结构体时,嵌套结构体对齐到自身最大对齐数的整数倍,整体大小为包含嵌套结构体在内的所有最大对齐数的整数倍。
  • 特殊情况:无成员变量的类(如class C{}),实例化对象大小为1 字节,仅用于 “占位标识对象存在”,无实际数据存储意义。

     

    class A { private: char _ch; int _i; }; // 大小:8(char占1字节,对齐到4字节,int占4字节,总8字节)
    class B { public: void Print(); }; // 无成员变量,大小1字节
    

三、this 指针

3.1 核心作用

解决 “多个对象调用同一成员函数时,如何区分当前操作的对象” 的问题。C++ 编译器会为每个非静态成员函数隐含添加一个this 指针参数,指向当前调用函数的对象。

3.2 特性与使用规则

  • 隐含传递:this 指针无需在函数形参 / 实参中显式声明,编译器自动处理。例如Date类的Init函数:
    • 实际原型:void Init(Date* const this, int year, int month, int day)
    • 调用时:d1.Init(2024,3,31) 自动转换为 Init(&d1, 2024,3,31)
  • 访问成员变量:函数体内访问成员变量(如_year = year),本质是this->_year = year,this 指针可显式使用(如this->_month = month)。
  • const 限制:this 指针默认是 “指向当前类类型的 const 指针”(如Date* const this),不可修改指向(即不能赋值this = nullptr)。

3.3 典型问题测试

通过两个例题理解 this 指针的访问逻辑:

  1. 例题 1:空指针调用无成员变量访问的函数

     

    A* p = nullptr; p->Print(); // Print仅输出字符串,不访问成员变量
    
    • 结果:正常运行。成员函数存于代码段,调用时仅需函数地址,无需访问对象内存(this 指针虽为 nullptr,但未解引用)。
  2. 例题 2:空指针调用访问成员变量的函数

     

    A* p = nullptr; p->Print(); // Print中输出this->_a(成员变量)
    
    • 结果:运行崩溃。需通过 this 指针(nullptr)解引用访问成员变量,触发空指针访问错误。
  3. this 指针存储位置:答案为A(栈)。this 指针是成员函数的隐含形参,函数调用时形参存储在栈区。

四、C++ 与 C 语言实现 Stack 的对比

以 “栈(Stack)” 数据结构为例,体现 C++ 面向对象封装的优势:

对比维度C 语言实现C++ 实现
数据与函数组织结构体(仅存数据)+ 全局函数(需传结构体指针)类(数据 + 函数封装),无需传对象指针
数据安全性可直接修改结构体成员(如ps->top = 10),无保护成员变量设为 private,仅通过 public 函数修改
语法便捷性需用 typedef 定义结构体别名,函数需显式传指针类名直接作为类型,this 指针隐含传递,支持缺省参数(如 Init (n=4))
封装性无封装,数据与操作分离封装数据与操作,通过访问限定符控制权限

代码片段(核心差异)

  • C 语言
    typedef struct Stack { int* a; int top; } ST;
    void STInit(ST* ps) { ps->top = 0; } // 需传结构体指针
    int main() { ST s; STInit(&s); s.top = 10; } // 可直接修改成员
    
  • C++
    class Stack {
    public:void Init(int n=4) { _top = 0; } // this指针隐含指向当前对象
    private:int* _a; int _top; // 私有成员,类外不可直接修改
    };
    int main() { Stack s; s.Init(); } // 无需传指针,无法直接修改_top
    

完整代码

 c语言

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>// 定义栈存储的数据类型(方便后续修改存储类型)
typedef int STDataType;// 定义栈的结构体(存储栈的核心信息)
typedef struct Stack
{STDataType* a;        // 指向栈底层数组的指针int top;              // 栈顶位置(指向栈顶元素的下一个位置,空栈时为 0)int capacity;         // 栈的最大容量(避免频繁扩容)
} ST;// 1. 初始化栈
void STInit(ST* ps)
{assert(ps);  // 断言:确保传入的结构体指针非空,避免非法访问ps->a = NULL;  // 初始时栈无空间,数组指针置空ps->top = 0;   // 空栈的栈顶位置为 0ps->capacity = 0;  // 初始容量为 0
}// 2. 销毁栈(释放内存,避免内存泄漏)
void STDestroy(ST* ps)
{assert(ps);free(ps->a);  // 释放底层数组的内存ps->a = NULL;  // 指针置空,避免野指针ps->top = ps->capacity = 0;  // 重置栈顶和容量为初始状态
}// 3. 入栈(将元素 x 压入栈顶,满栈时自动扩容)
void STPush(ST* ps, STDataType x)
{assert(ps);// 满栈判断:栈顶 == 容量,需扩容if (ps->top == ps->capacity){// 扩容策略:初始容量为 0 时扩为 4,否则扩为原来的 2 倍int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;// 重新分配内存(realloc 可直接使用 NULL 指针,等价于 malloc)STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));if (tmp == NULL)  // 内存分配失败判断{perror("realloc fail");  // 打印错误信息return;  // 分配失败直接返回,避免后续非法操作}ps->a = tmp;  // 更新数组指针为新分配的内存ps->capacity = newcapacity;  // 更新栈的最大容量}// 将元素压入栈顶,栈顶位置后移ps->a[ps->top] = x;ps->top++;
}// 4. 出栈(删除栈顶元素,需确保栈非空)
void STPop(ST* ps)
{assert(ps);assert(!STEmpty(ps));  // 断言:栈非空才能出栈ps->top--;  // 栈顶位置前移,等效删除栈顶元素(后续入栈会覆盖)
}// 5. 获取栈顶元素(需确保栈非空)
STDataType STTop(ST* ps)
{assert(ps);assert(!STEmpty(ps));  // 断言:栈非空才能获取栈顶return ps->a[ps->top - 1];  // 栈顶指向元素下一个位置,故减 1
}// 6. 判断栈是否为空(空栈返回 true,非空返回 false)
bool STEmpty(ST* ps)
{assert(ps);return (ps->top == 0);  // 栈顶为 0 时为空栈
}// 7. 获取栈中元素的个数
int STSize(ST* ps)
{assert(ps);return ps->top;  // 栈顶位置即为元素个数(空栈为 0,有 n 个元素时 top 为 n)
}// 测试函数:演示栈的基本操作
int main()
{ST s;  // 定义栈对象STInit(&s);  // 初始化栈(需传递结构体地址)// 入栈操作:压入 1、2、3、4STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);// 遍历栈:从栈顶到栈底打印元素(出栈方式)printf("栈中元素(从顶到底):");while (!STEmpty(&s)){STDataType top = STTop(&s);  // 获取栈顶元素printf("%d ", top);STPop(&s);  // 出栈(删除当前栈顶,下一轮获取新栈顶)}printf("\n");// 销毁栈(释放内存,避免内存泄漏)STDestroy(&s);return 0;
}

c++

#include <iostream>
#include <cstdlib>  // 包含 malloc、free 函数
#include <cassert>  // 包含 assert 断言
using namespace std;  // 简化标准库使用(入门阶段常用)// 定义栈存储的数据类型
typedef int STDataType;// 定义 Stack 类(封装数据和操作)
class Stack
{
public:// 1. 初始化栈(带缺省参数:默认初始容量为 4)void Init(int n = 4){// 分配 n 个 STDataType 大小的内存_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr)  // 内存分配失败判断(C++ 推荐用 nullptr 代替 NULL){perror("malloc fail");  // 打印错误信息return;}_capacity = n;  // 初始化容量为 n_top = 0;       // 初始化栈顶为 0(空栈)}// 2. 销毁栈(释放内存)void Destroy(){free(_a);         // 释放底层数组内存_a = nullptr;     // 指针置空,避免野指针_top = 0;         // 重置栈顶_capacity = 0;    // 重置容量}// 3. 入栈(压入元素 x)void Push(STDataType x){// 满栈判断:栈顶 == 容量,需扩容if (_top == _capacity){// 扩容策略:初始容量为 0 时扩为 4,否则扩为 2 倍int newcapacity = (_capacity == 0) ? 4 : _capacity * 2;// 重新分配内存STDataType* tmp = (STDataType*)realloc(_a, newcapacity * sizeof(STDataType));if (tmp == nullptr){perror("realloc fail");return;}_a = tmp;         // 更新数组指针_capacity = newcapacity;  // 更新容量}// 压入元素,栈顶后移(this 指针隐含访问成员变量,可省略)_a[_top++] = x;}// 4. 出栈(删除栈顶元素)void Pop(){assert(_top > 0);  // 断言:栈非空才能出栈(等价于 !Empty())_top--;  // 栈顶前移,等效删除栈顶}// 5. 获取栈顶元素STDataType Top(){assert(_top > 0);  // 断言:栈非空return _a[_top - 1];  // 返回栈顶元素}// 6. 判断栈是否为空bool Empty(){return (_top == 0);  // 栈顶为 0 则为空}// 7. 获取栈中元素个数int Size(){return _top;  // 栈顶位置即为元素个数}private:// 成员变量(私有:仅类内成员函数可访问,避免外部随意修改)STDataType* _a;        // 指向底层数组的指针(前缀 _ 区分成员变量与局部变量)size_t _capacity;      // 栈的最大容量(size_t 为无符号整数,更贴合容量语义)size_t _top;           // 栈顶位置(无符号整数,避免负索引)
};// 测试函数:演示栈的操作
int main()
{Stack s;  // 定义 Stack 类对象(自动分配内存,无需手动定义结构体)s.Init();  // 初始化栈(可传参指定初始容量,如 s.Init(8))// 入栈:压入 1、2、3、4s.Push(1);s.Push(2);s.Push(3);s.Push(4);// 遍历栈:从顶到底打印cout << "栈中元素(从顶到底):";while (!s.Empty()){cout << s.Top() << " ";  // 调用成员函数获取栈顶s.Pop();                  // 调用成员函数出栈}cout << endl;// 销毁栈(释放内存)s.Destroy();return 0;
}

 

http://www.dtcms.com/a/579309.html

相关文章:

  • 中石化石油工程建设公司官方网站合肥缶间网络科技有限公司
  • 做一手房开什么网站比较好呢外贸生意如何做
  • 毕业设计心理评测网站开发淘宝代运营服务
  • 邯郸高端网站建设价格做网站设计需要多少钱
  • 济南网站网站建设怒江北京网站建设
  • 抖音seo排名软件哪个好seo课程总结怎么写
  • 如何用iis做网站房产交易中心官网
  • 网站开发的出路长沙优化网站价格
  • 网站开发导向图安徽网站建设认准-晨飞网络
  • 孟津网站开发阿里云域名注册万网
  • 项目软件开发中自动检测死锁的监控功能
  • 网站建设的基本技术步骤网站开发研究方法
  • 怎么注册微网站自己做网站的好处
  • 网站APP注册做任务制作简历的免费模板网站
  • 小程序如何开发制作广州百度提升优化
  • 建立网站的意义企业推广网站建设报价
  • react中的useEffect使用方法
  • 2025 口语练习软件实测排名 Top5:短期攻克口语难题
  • 做网站工作室wordpress加速访问
  • 泉州网站建设哪里好漳州做网站喊多少钱
  • 建设网站的合同建设主流媒体网站
  • 郑州做网站公司中wordpress加超链接
  • 网站建设一般是用哪个软件专业的企业宣传片制作企业
  • 网站开发验收确 认书兰州网站设计厂家
  • 软件设计师重点笔记-6
  • 网站建设的职位类别苏州手机网站开发公司
  • Rocky9基于MySQL安装Zabbix7 详细步骤
  • 企业 php网站建设电子商务网站的网站架构
  • 开发公司法人和项目负责人质量安全责任制度东莞seo外包公司
  • 套模版做的网站好优化吗做网站赚钱还是做应用赚钱