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

C++编程之旅-- -- --类与对象的奇幻征途之初识篇(一)(了解类的基本用法,计算类大小,分析this指针)

目录

  • 了解类的基本概念及用方法
    • 面向过程和面向对象初步认识
    • 类的引入
    • 类的定义
    • 类的访问限定符及封装
    • 类的作用域
    • 类的实例化
  • 类的对象大小的计算
  • 类成员函数的this指针
  • ❤️总结

了解类的基本概念及用方法

面向过程和面向对象初步认识

C语言对于一个事件是面向过程的,那么什么是面向过程呢?就拿洗衣服这件事来说,拿个容器,放水,放衣服,放洗衣粉,手搓,拧干,晾衣服,这些都是洗衣服的过程(任务拆解成线性的函数 / 操作序列,关注 怎么做),他是写代码的开发者直接指挥步骤,通过步骤流程完成这些程序。

而C++对于一个事件是面对对象的(也就是我们要了解的类:按 对象交互 驱动程序),C++对于洗衣服这件事,它分有4个对象:人,洗衣机,洗衣粉,衣服。每个对象交互完成事件,你不需要了解洗衣机如何干活。也就说C++将放水,手搓,拧干,放洗衣粉,这些步骤,用对象重新组织步骤 。通过对象的调用来完成程序。是不是感觉这就类似我们的社会从人工时代走向智能时代。

这里只是简单的了解一下。

类的引入

在C语言里,我们学过结构体,但结构体中只能定义变量,然而在C++中,结构体内不仅可以定义变量,也可以定义函数。(也就是我们的类了)。就拿我们在数据结构篇里学过的栈来说。

typedef int DataType;
struct Stack
{void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容if(_size ==_capacity){// 用临时指针接收realloc的结果DataType* newArray = (DataType*)realloc(_array, sizeof(DataType) * newCapacity);if (newArray == nullptr){perror("realloc failed");return; // 扩容失败时,不执行后续插入操作}// 扩容成功,更新指针和容量_array = newArray;_capacity = newCapacity;}_array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}DataType* _array;size_t _capacity;size_t _size;};
int main()
{Stack s;s.Init(10);s.Push(1);s.Push(2);s.Push(3);cout << s.Top() << endl;s.Destroy();return 0;

在C++里我们跟喜欢用class代替struct。

类的定义

class className
{// 类体:由成员函数和成员变量组成。
};  // 一定要注意后面的分号:与struct一样。

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分
号不能省略。

类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。

类有两种定义方式:1.将声明和定义放在一起,2.将声明放在头文件,将定义放在源文件。

1.声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内
联函数处理。

class Person
{
public: void showinfo(){cout << _name << "-" << _sex << "-" << _age << "-" << endl;}
public: char* _name;char* _sex;int _age;
};

2.将声明放在头文件,将定义放在源文件 。 成员函数名前需要加类名::
头文件:

#include<iostream>
using namespace std;
class Person
{
public:void showinfo(){/*cout << _name << "-" << _sex << "-" << _age << "-" << endl;*/}
public:char* _name;char* _sex;int _age;
};

源文件:

void Person::showinfo()
{cout << _name << "-" << _sex << "-" << _age << "-" << endl;
}

一般情况下,更期望采用第二种方式。

成员变量的命名规则的建议:

class Date
{
public:void Init(int year){// 这里的year到底是成员变量,还是函数形参?year = year;}
private:int year;
};

year这个变量用起来很不舒服。所以我们一般在成员变量前面加“-”。

class Date
{
public:void Init(int year){_year = year;}
private:int _year;
};

简单来说,要让它有区分度就行。

类的访问限定符及封装

在这里插入图片描述

  1. public修饰的成员在类外可以直接被访问。
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. C++中struct和class的区别:class的默认访问权限为private,struct为public(因为struct要兼容C)。
  6. 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

那么访问限定符的意义是什么呢?那便是实现封装,比如你不想把你的一些类里面的数据公之于众,那么就可以放入私有或保护。

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。

封装本质上是一种管理,让用户更方便使用类。就拿电脑来说,我们能用的就只有开关机键、通过键盘输入,显示器,USB插孔等,这些能让我们实现:怎么开机、怎么通过键盘和鼠标与计算机进行交互。但实际上是电脑里面的是CPU、显卡、内存等一些硬件元件工作。这就是封装的。

C++里面的封装就是通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

类的作用域

这就是我们之前在命名空间域里面提到的:类域。

类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

void Person::showinfo()
{cout << _name << "-" << _sex << "-" << _age << "-" << endl;
}

类的实例化

类的实例化,你可以将类理解成一个类型,就像int ,float等类型一样,只不过它包含很多东西。

对于类,我们可以理解成一张信息表格,也就是说他是虚的:定义出一个类并没有分配实际的内存空间。只有实例化才会成实的。

一个类可以实例化出多个对象,就像人这一物种,可以有不同属性的人,类就是物种,而实例化之后就真有一个人诞生了。

class Person
{
public: void showinfo(){cout << _name << "-" << _sex << "-" << _age << "-" << endl;}
public: char* _name;char* _sex;int _age;
};
int main()
{Person man;man._name ="zhangsan";man._sex = "女 ";man._age = 20;man.showinfo();
}

实例化对象出来之后,通过对象+ . 来对类里面的成员进行操作。

类的对象大小的计算

对于类的对象大小计算我们可以先分析类里面有什么?一个类可以包含:成员变量,成员函数(不影响类的大小),虚函数表指针(在多态里会提及,但在这不考虑,但它影响类的大小,就像指针一样计算。),静态成员变量(不影响类的大小)。那么我们就可以猜想这些会不会包含在计算类的对象大小的范围里面。

第一个猜想:对象中包含类的各个成员
每个对象中都会保存一份代码,相同代码保存多次,浪费空间,所以不行。

第二个猜想:代码只保存一份,在对象中保存存放代码的地址。
可行,解决的浪费空间的弊端。

第三个猜想:只保存成员变量,成员函数存放在公共的代码段。
这二三猜想思路是类似的,但在实际上三更简洁直观,无需关心对象里存函数指针的细节。

ok,想好了要计算类的对象里面包含什么了之后,我们就要像计算结构体的大小一样计算类的对象大小。

计算类的对象大小:成员变量总大小 + 对齐填充 + 虚函数表指针(若有)。

1.既然是变量那么就会有类类型的成员变量,对于嵌套类成员的处理,就计算完这个嵌套类成员的大小加上即可。

2.对齐填充可以看我之前写的计算结构体大小:链接计算结构体大小。

3.对于一个虚函数表指针,那要么是4字节,要么是8字节。取决你的几位系统。

对于一个空类,编译器会给它1字节来辨识它

类成员函数的this指针

对于this指针的讲解,我们想要引入一个类:
Data类:

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;      // 日
};
int main()
{Date d1, d2;d1.Init(2022,1,11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}

在这个类中,有一个这样的问题:类的对象用成员函数Init和Print时,函数是怎么区分d1,d2的?

C++中通过引入this指针解决该问题,即:C++编译器给每个非静态的成员函数增加了一个隐藏
的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编
译器自动完成。

通过 . 运算符告诉编译器 要把哪个对象的地址作为 this 指针传给函数,而函数内部正是通过 this 指针才能准确找到要操作的对象数据。

this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在成员函数的内部使用。
  3. this指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给
    this形参。所以对象中不存储this指针。
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
    递,不需要用户传递。

this 指针存储位置:
在 C++ 中,this指针的存储位置依赖于编译器实现,一般:
1.多数情况下,this指针通过寄存器传递(如 x86 架构常用ecx寄存器,x64 架构常用rcx寄存器 ),调用成员函数时,编译器自动把对象地址装入对应寄存器,作为this指针值 。

2.若寄存器资源紧张等特殊情况,也可能存入栈,但寄存器传递是主流高效方式,无统一标准存储位置,由编译器根据架构和优化策略决定。

this 指针可否为空:
语法上,this指针可以是NULL(如通过空对象指针调用非虚成员函数,且函数内未访问this指向的对象成员时 。但逻辑危险。

若成员函数中访问this指向的对象成员(如this->member ),此时this为NULL会导致解引用空指针,触发未定义行为(程序崩溃、异常等 )。所以实际开发应避免this为NULL的调用,this本质是隐含的对象指针参数,空指针调用成员函数是否报错,取决于函数是否实际使用this访问对象数据 。

最后你们可以去对比一下C++的类实现栈与C语言的结构体实现栈:C语言栈的实现
C++的类实现栈:

typedef int DataType;
class Stack
{
public:void Init(){_array = (DataType*)malloc(sizeof(DataType) * 3);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = 3;_size = 0;}void Push(DataType data){CheckCapacity();_array[_size] = data;_size++;}void Pop(){if (Empty())return;_size--;}DataType Top(){return _array[_size - 1];}int Empty() {return 0 == _size;}int Size(){ return _size;}void Destroy(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity *sizeof(DataType));}if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}private:DataType* _array;int _capacity;int _size;
};

❤️总结

文字的旅程暂告段落,感谢你温柔相伴。若文中有疏漏,盼你轻声指正,那是成长的微光。倘若这些字句,曾为你拂去些许迷茫,不妨留个赞,让温暖延续,也欢迎你常来,共赴文字的山海,聆听心灵的回响❤️

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

相关文章:

  • 【完整源码+数据集+部署教程】海洋物体实例分割系统源码和数据集:改进yolo11-EfficientHead
  • Java【问题 07】SSH不同版本使用jsch问题处理(7.4升级9.7及欧拉原生8.8)
  • WD5202 非隔离降压转换芯片,220V降5V,输出电流80MA
  • Java学习Collection单列集合中的三种通用遍历方法
  • 【Erdas实验教程】029:遥感图像光谱增强(缨帽变换)
  • 经济学从业者职业发展认证体系分析
  • 在 Git 中,将本地分支的修改提交到主分支
  • 数据结构--哈希表与排序、选择算法
  • PVE 9.0 保姆级安装及优化教程(换源、网络配置、远程唤醒等)【基础篇】
  • 农行鉴权问题
  • 嵌入式 Linux 驱动开发常见问题排查宝典(驱动开发篇)v1.0
  • “人工”智能究竟需要多少人工?
  • 《设计模式之禅》笔记摘录 - 14.组合模式
  • 使用Python+selenium实现第一个自动化测试脚本
  • 【GPT-OSS 全面测评】释放推理、部署和自主掌控的 AI 新纪元
  • 1688 图片搜图找货接口开发实战:从图像特征提取到商品匹配全流程
  • InfluxDB漏洞:Metrics 未授权访问漏洞
  • 自定义上传本地文件夹到七牛云
  • 【深度学习新浪潮】GPT-5正式发布:开启博士级智能新纪元
  • Redis基础数据类型
  • 支持向量机(SVM)全解析:原理、类别与实践
  • Nestjs框架: 基于 Argon2 的用户登录注册安全机制设计与实现
  • Vue框架总结案例
  • 抖音AI分身:帮助每个抖音创作者,打造自己的AI分身
  • 垃圾堆放识别准确率↑32%:陌讯多模态融合算法实战解析
  • 设计一个 Java 本地缓存组件
  • P1119 灾后重建【题解】
  • 【动态规划 | 二维费用背包问题】二维费用背包问题详解:状态设计与转移方程优化
  • 温室韭菜收割机的设计cad【12张】三维图+设计说明书
  • WinForm 实战 (进度条):用 ProgressBar+Timer 打造动态进度展示功能