今天你学C++了吗——C++中的类与对象(第一集)

今天你学C++了吗——C++中的类与对象(第一集)


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨
❤C++专栏中比较重要的类与对象知识现在已经更新完毕❤

❤欢迎大家随时访问哦❤

类与对象——第一集

类与对象——第二集

类与对象——第三集

类与对象——第四集
类与对象——最后一集(日期类的实现)

这一篇博客在前面一篇博客的基础上,继续领悟C++的魅力~从博客标题就知道这一篇博客主题是类与对象~准备好了吗~发车了🚗~

目录

类的定义

类定义格式

访问限定符

类域

实例化

对象的大小

this指针

趣味代码

C++和C语言实现Stack对比

C语言实现Stack

C++实现Stack

分析对比


类的定义

类定义格式

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

》类体中内容称为类的成员:

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

b.类中的函数称为类的方法或者成员函数。

我们来看看下面这个定义的类

#include<iostream> using namespace std; //定义一个类 //class为定义类的关键字 //Stack为类名 class Stack { //成员函数func void func() { cout << "func" << endl; } //成员变量 int* arrary; int top; int capacity; };//类定义结束时后面分号不能省略

知道了类一般定义的格式,同时还有下面的注意点:

》为了区分成员变量,一般习惯上成员变量会加一个特殊标识如成员变量前面或者后面加_ 或者 m开头,这个C++中这个并不是强制的,只是一些惯例,方便我们使用区分~
//成员变量加上一个特殊标识便于区分 int* _arrary; int _top; int _capacity; 
》C++中struct也可以定义类,C++兼容C中struct的用法,同时将struct升级成了类》明显的变化是C++中struct中可以定义函数,一般情况下我们还是推荐用class定义类
》定义在类里面的成员函数默认为inline(是否展开由编译器决定)

访问限定符

》访问限定符是C++⼀种实现封装的方式,用类将对象的属性与方法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用~
》访问限定符包括public、private、protected
》public修饰的成员在类外可以直接被访问》protected和private修饰的成员在类外不能直接被访问protected和private是差不多的,后续在继承里面会更好地体现出他们的区别。
》访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } ,即类结束的位置
》class定义成员没有被访问限定符修饰时默认为private(所以我们前面的变量和函数不能在类外面访问),但是struct里面的成员没有被访问限定符修饰时默认为public

例:

 #include<iostream> using namespace std; //C++中struct里面可以定义函数 struct S { void func() { printf("haha\n"); } }; //定义一个类 //class为定义类的关键字 //Stack为类名 class Stack { //成员函数func void func() { cout << "func" << endl; } //成员变量加上一个特殊标识便于区分 int* _arrary; int _top; int _capacity; };//类定义结束时后面分号不能省略 int main() { //struct S,在C++这里可以不写struct了 S s1; s1.func();//访问S里面的成员函数, //可以访问,struct里面的成员默认为public Stack s2; s2.func(); s2._top; s2._arrary; //class定义成员没有被访问限定符修饰时默认为private // (所以我们前面的变量和函数不能在类外面访问) return 0; }

class Stack这里面的成员变量和成员函数是不能在类外面被访问的~class定义成员没有被访问限定符修饰时默认为private

如何使用 访问限定符?》一般成员变量都会被限制为private/protected》需要给别人使用的成员函数会放为public

类域

》上一篇博客,我们就提到了 C++中有函数局部域、全局域、命名空间域、类域》类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域》类域影响的是编译的查找规则例如下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,在当前全局域找不到的array等成员,就会到类域中去查找。
#include<iostream> using namespace std; class Stack { public: // 成员函数 void Init(int n = 4);//初始化函数声明 private: // 成员变量 int* array; size_t capacity; size_t top; }; //void Init(int n) //err // 声明和定义分离,需要指定类域 void Stack::Init(int n)////初始化函数定义 { array = (int*)malloc(sizeof(int) * n); if (nullptr == array) { perror("malloc申请空间失败"); return; } capacity = n; top = 0; } int main() { Stack st; st.Init(); return 0; }

实例化

》用类类型在物理内存中创建对象的过程,称为类实例化出对象。》类是对象进行⼀种抽象描述,类似于⼀个模型,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。》⼀个类可以实例化出多个对象,实例化出的对象会占用实际的物理空间存储类成员变量。》类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多少个房间,房间大小、功能等,但是并没有实体的建筑存在,也不能住人,我们用设计图修建出房子,房子才能住人。类就像设计图⼀样,没有分配空间,不能存储数据,实例化出的对象分配物理内存存储数据。

例:

#include<iostream> using namespace std; //日期类 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;//类实例化出对象 Date d2; //实例化出的对象d1、d2就会占用实际的物理空间存储类成员变量 d1.Init(2024, 12, 12); d2.Init(2020, 11, 11); d1.Print(); d2.Print(); return 0; }

对象的大小

前面外面提到了变量、指针、结构体它们都有大小~那么对象的大小是怎么计算呢?

我们来测试一下~

#include<iostream> using namespace std; //日期类 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; d1.Init(2024, 12, 12); Date d2; d2.Init(2020, 11, 11); cout << sizeof(d1) << endl; cout << sizeof(d2) << endl; return 0; }

上面代码实例化出来的对象d1、d2都为12个字节大小,这是怎么计算的呢?我们来分析一下~

》类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量(上面代码里面成员变量是三个整型,字节大小刚好是12,那么是不是说明对象的大小就是保存的成员变量的大小呢?)》那么成员函数是否包含呢?首先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析⼀下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量 _year/_month/_day存储自己的的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了。如果使用Date实例化100个对象,那么成员函数指针就重复存储100次,这样不就浪费空间了嘛~下面的存储设计就是不合理的~》事实上, 函数指针是不需要存储的,函数指针是⼀个地址,调用函数被编译成汇编指令[call 地址], 编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址(我们以后的博客会细致讲解)下面的存储设计才是合理的
结论:对象中只存储成员变量
同时,C++规定类实例化的对象也要符合内存对齐的规则(与前面C语言struct类似)如果想了解内存对齐更多的内容,可以看看这一篇博客~

C语言——自定义类型

知道了这些,我们来看看下面的代码,你知道A/B/C实例化的对象是多大吗?

#include<iostream> using namespace std; // 计算⼀下A/B/C实例化的对象是多大? class A { public: void Print() { cout << _ch << endl; } private: char _ch; int _i; }; class B { public: void Print() { //... } }; class C {}; int main() { A a; B b; C c; cout << sizeof(a) << endl; cout << sizeof(b) << endl; cout << sizeof(c) << endl; return 0; }

 

第一个结果是8很好理解,因为A里面有两个成员变量一个char类型,一个int类型,根据内存对齐规则对象大小为8,但是B里面只有成员函数,C里面什么都没有,为什么大小为1,不是0呢?

注意:这里如果⼀个字节都不给,就不能对象存在过!

所以这里给1字节,纯粹是为了占位标识对象存在。(同时这里不给两个甚至更多的字节,1字节是为了节省空间嘛)

this指针

》我们可以看到 Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?这里也就是C++的魔法了~C++给了⼀个隐含的this指针解决这个问题》编译器编译后,类的成员函数默认都会在形参第⼀个位置增加⼀个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为void Init(Date* const this, int year, int month, int day)》所以类的成员函数中访问成员变量,本质都是通过this指针访问的如Init函数中给_year赋值, this->_year = year;》C++规定不能在实参和形参的位置显示的写this指针(编译时编译器自己会处理,不需要我们写),可以在函数体内显示使用this指针

例:

#include<iostream> using namespace std; class Date { public: //void Init(Date* const this, int year, int month, int day); //err 不能在实参和形参的位置显示的写this指针 void Init(int year, int month, int day) { // 编译报错:error C2106: “=”: 左操作数必须为左值 //this = nullptr;//err—— this指针是不可以被修改的 this->_year = year; _month = month; this->_day = day; //可以在函数体内显示使用this指针 } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { // Date类实例化出对象d1和d2 Date d1; Date d2; //d1.Init(&d1, 2024, 12, 12); //err 不能在实参和形参的位置显示的写this指针 d1.Init(2024, 12, 12); d1.Print(); d2.Init(2020, 11, 11); //d2.Print(&d2);//err d2.Print(); return 0; }

趣味代码

了解了前面类与对象的一些知识~我们来看看一些趣味代码~

1.

下面这个代码有没有什么问题呢?它的运行结果是什么呢?

A、编译报错 B、运行崩溃 C、正常运行

#include<iostream> using namespace std; class A { public: void Print() { cout << "A::Print()" << endl; } private: int _a; }; int main() { A* p = nullptr; p->Print(); return 0; }

正确答案是C,这一段代码是可以正常运行的,有人可能会说这里不是对空指针进行解引用了嘛?事实上,在Print函数中并没有访问成员变量,我们说对象里面保存的是成员变量,成员函数本身不在类中,这里没有访问成员变量,也就不存在解引用操作了~

我们来看看第二个代码

2.

下面这个代码有没有什么问题呢?它的运行结果是什么呢?

A、编译报错 B、运行崩溃 C、正常运行

#include<iostream> using namespace std; class A { public: void Print() { cout << "A::Print()" << endl; cout << _a << endl; } private: int _a; }; int main() { A* p = nullptr; p->Print(); return 0; }

正确答案是B,这里成员函数里面有对成员变量的访问,相当于this->_a,这就造成空指针解引用了,空指针没有指向有效的内存空间,这里试图通过空指针读取内存中的数据导致程序崩溃~

总结:空指针调用的成员函数里面不包含访问成员变量,那么就不会出错,如果访问成员变量就会导致程序崩溃~相当于对空指针解引用~没有有效的内存空间访问~

3.this指针存在内存哪个区域的 ()

A. 栈 B.堆 C.静态区 D.常量区 E.对象里面正确答案是A解释:this指针是成员函数的一个隐含参数(也就是一个函数参数),存储在栈里面,调用成员函数,this指针就作为函数参数压入栈中~

C++和C语言实现Stack对比

C语言实现Stack

#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<assert.h> typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* ps) { assert(ps); ps->a = NULL; ps->top = 0; ps->capacity = 0; } void STDestroy(ST* ps) { assert(ps); free(ps->a); ps->a = NULL; ps->top = ps->capacity = 0; } void STPush(ST* ps, STDataType x) { assert(ps); if (ps->top == ps->capacity) { int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; 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++; } bool STEmpty(ST* ps) { assert(ps); return ps->top == 0; } void STPop(ST* ps) { assert(ps); assert(!STEmpty(ps)); ps->top--; } STDataType STTop(ST* ps) { assert(ps); assert(!STEmpty(ps)); return ps->a[ps->top - 1]; } int STSize(ST* ps) { assert(ps); return ps->top; } int main() { ST s; STInit(&s); STPush(&s, 1); STPush(&s, 2); STPush(&s, 3); STPush(&s, 4); while (!STEmpty(&s)) { printf("%d\n", STTop(&s)); STPop(&s); } STDestroy(&s); return 0; }

C++实现Stack

#include<iostream> using namespace std; typedef int STDataType; class Stack { public: // 成员函数 void Init(int n = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } void Push(STDataType x) { if (_top == _capacity) { int newcapacity = _capacity * 2; STDataType* tmp = (STDataType*)realloc(_a, newcapacity * sizeof(STDataType)); if (tmp == NULL) { perror("realloc fail"); return; } _a = tmp; _capacity = newcapacity; } _a[_top++] = x; } void Pop() { assert(_top > 0); --_top; } bool Empty() { return _top == 0; } int Top() { assert(_top > 0); return _a[_top - 1]; } void Destroy() { free(_a); _a = nullptr; _top = _capacity = 0; } private: // 成员变量 STDataType* _a; size_t _capacity; size_t _top; }; int main() { Stack s; s.Init(); s.Push(1); s.Push(2); s.Push(3); s.Push(4); while (!s.Empty()) { printf("%d\n", s.Top()); s.Pop(); } s.Destroy(); return 0; }

分析对比

最大的区别是C++代码更加简洁了~这是因为C++里面有一些比较方便的语法(比如成员函数每次不需要传对象地址,this指针隐含传递了)~但是底层和逻辑上没有什么变化~

同时C++数据和函数都放到了类里面,还可以通过访问限定符进行限制,不能再随意通过对象直接修改数据,这是C++封装的⼀种体现,这也就以⼀种更严格规范的管理,避免出现乱访问修改的问题,这也就增强了数据安全性~

后面我们会继续了解面向对象三大特性:封装、继承、多态~这里简单了解了一下封装~



♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨

Read more

【Linux:文件 + 进程】进程间通信进阶(1)

【Linux:文件 + 进程】进程间通信进阶(1)

🎬 个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬 艾莉丝的简介: 文章目录 * 1 ~> 准备阶段:进程间通信的概念 * 1.1 是什么(本质前提) * 1.2 为什么 * 1.3 怎么办 * 1.4 思维导图 * 2 ~> 进程间通信 * 2.1 进程间通信的定制标准:System V * 2.2 进程间通信的发展 * 3

By Ne0inhk
当 AI 嚼碎数据吐模块,人类开发者的创意还能留几行?—— 老码农的反编译式安心剂

当 AI 嚼碎数据吐模块,人类开发者的创意还能留几行?—— 老码农的反编译式安心剂

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 * 当 AI 嚼碎数据吐模块,人类开发者的创意还能留几行?—— 老码农的反编译式安心剂 * 📚 一、那些被 AI 吓得半夜查招聘网站的日子 * 📚 二、AI 生成功能模块的底层逻辑:它不是创意家,是数据缝合怪 * 📘 2.1 AI 生成功能的三板斧:统计、模仿与排列组合 * 📘 2.2 人类创意 vs AI 生成:核心差异在哪? * 📘 2.3 代码演示:AI 能生成 "正确" 的代码,但生成不了 "贴心&

By Ne0inhk
OpenClaw 实战:让 AI 拥有“眼睛“——摄像头访问完全指南

OpenClaw 实战:让 AI 拥有“眼睛“——摄像头访问完全指南

今天冒出个想法,想让openclaw能控制摄像头分析图片。原因是我有本书,网上还没有电子版,想让openclaw分析然后把重点内容讲给我听。 📖让运行在 WSL2 里的 OpenClaw AI 助手能够"看见"摄像头画面。 🚧 探索过程 第一阶段:OpenClaw Node 配对(失败)折腾了 3 小时+,最终因为 WSL2 网络隔离问题放弃。 我在wsl里安了openclaw,他说要控制摄像头,必须在windows上安装node.js,安装npm,折腾了好久,就是报错。结论就是windows和wsl就是隔离的。 具体过程: **安装 Node.js:** 最开始下载了绿色版 Node.js(v24.14.0),遇到了一系列问题: ```powershell # 绿色版 Node.js

By Ne0inhk
使用ai一键生成漫剧/真人三视图的实现教程(本地批量生成,4K高清)整合包下载及使用教程

使用ai一键生成漫剧/真人三视图的实现教程(本地批量生成,4K高清)整合包下载及使用教程

使用ai一键生成漫剧/真人三视图的实现教程(本地批量生成,4K高清)整合包下载及使用教程 关键词:漫剧制作、人物三视图生成、本地批量出图、角色一致性控制、4K高清输出、AI人物建模 整合包下载地址:https://pan.quark.cn/s/b472f9c452d6 现在做漫剧的人越来越多。真正开始做项目之后会发现一个核心问题: 人物资产的标准化生产。 尤其是从小说改编漫剧时,我们往往需要: * 批量解析小说人物(主角 / 配角 / 路人) * 生成对应的真人形象 * 生成动漫风形象 * 再生成标准三视图(正面 / 侧面 / 背面 / 特写) 如果没有三视图,在微度、C-Dance 这类工具里做分镜、建模、动作合成时,很难保证人物一致性。 这篇文章重点讲:如何在本地批量生成漫剧人物三视图,并大幅降低制作成本。 一、为什么三视图这么重要? 在漫剧制作流程中,三视图的作用非常明确: 视角作用正面主视觉展示侧面轮廓一致性控制背面服饰结构完整特写面部识别统一

By Ne0inhk