C++_类的定义和使用

       

目录

1、类的引用

1.1 类的成员函数

1.2  类成员函数的声明和定义

2、类的定义

2.1 类的访问限定(封装)

3、类重名问题

4、类的实例化

4.1 类的大小

5、隐含的this指针

5.1 空指针问题

结语:


前言:

        C++的类跟c语言中的结构体在概念上是一样的,只不过在c语言中叫结构体,而在C++中叫类。c语言中的结构体内只能定义变量,而C++在此基础上升级后,类里面可以定义变量和函数,并且把类中的内容叫做类的成员,类中的变量叫做成员变量,类中的函数叫做成员函数。

1、类的引用

        在c语言中,结构体的类型是:struct+结构体名称,在没有使用typedef对其进行重命名时,是不能省略struct的。例子如下:

struct ListNode//定义一个结构体
{struct ListNode* Node;int data;
};int main()
{struct ListNode lst;//定义一个结构体变量时,不能省略的掉struct return 0;
}

        然而在C++中,就可以做到不使用typedef的情况下,省略struct并使用该结构体类型(这里暂且把类叫做结构体,因为用的还是struct定义出来的,方便理解)。例子如下:

struct ListNode//定义一个结构体,也可以理解成定义一个类
{ListNode* Node;//成员变量的类型也可以不加structint data;
};int main()
{ListNode lst;//定义结构体变量时,可以不加struct return 0;
}

1.1 类的成员函数

        用c语言实现的栈或者链表,通常是把各个功能函数放在结构体的外面,用的是让函数与结构体成员分开的写法。而在C++中,可以把这些功能函数都放到结构体(类)中,让函数与结构体成员都处于同一作用域。

        类中函数的写法:

#include<iostream>
using namespace std;struct Stack//结构体(类)
{//成员函数//栈的初始化void Init(int n=4){arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("malloc");return;}Top = 0;capacity = n;}//压栈void push(int x){if (capacity == Top){int newcapacity = 2 * capacity;int* temp = (int*)realloc(arr, sizeof(int*) * newcapacity);if (temp == nullptr){perror("Push");return;}arr = temp;capacity = newcapacity;}arr[Top++] = x;}//成员变量int* arr;int Top;int capacity;
};int main()
{Stack st1;//创建结构体变量st1.Init();//调用函数时要表明调用对象st1.push(1);st1.push(102);cout << st1.arr[st1.Top - 2] << endl;//打印栈里元素cout << st1.arr[st1.Top - 1] << endl;return 0;
}

        运行结果:

        可以看到上述代码中,栈的初始化和压栈函数都是直接写在结构体作用域中,而且能够正常实现栈的功能,说明C++支持把函数写进结构体内。并且注意调用函数时要表明调用对象,因为此时在类中的函数不再是全局范围的了,而是只属于当前类,因此调用成员函数时要先创建一个变量,并且用改变量去调用(写法和调用结构体成员一样)。

1.2  类成员函数的声明和定义

        我们一般实现某个功能函数时,都是把该函数的声明放在头文件内,把该函数的定义放在.cpp文件中,做到声明和定义分开,那么在C++中如何实现函数的声明和定义分离呢。

        比如把上述代码分成三个文件:3.cpp、3.h、test.cpp。3.cpp用于存放成员函数的定义,3.h是结构体的创建,test.cpp是主函数实现。

        3.h代码如下:

#pragma once#include<iostream>
using namespace std;struct Stack
{//成员函数声明void Init(int n = 4);//栈的初始化void push(int x);//压栈//成员变量int* arr;int Top;int capacity;
};

        3.cpp代码如下:

#include"3.h"//成员函数定义
void Stack::Init(int n )//栈的初始化,注意添加作用域限定符
{arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("malloc");return;}Top = 0;capacity = n;
}void Stack::push(int x)//压栈,注意添加作用域限定符
{if (capacity == Top){int newcapacity = 2 * capacity;int* temp = (int*)realloc(arr, sizeof(int*) * newcapacity);if (temp == nullptr){perror("Push");return;}arr = temp;capacity = newcapacity;}arr[Top++] = x;
}

        test.cpp代码如下:


#include"3.h"int main()
{Stack st1;//创建结构体变量st1.Init();st1.push(1);st1.push(102);cout << st1.arr[st1.Top - 2] << endl;//检查压栈是否成功cout << st1.arr[st1.Top - 1] << endl;return 0;
}

        可以从3.cpp文件中看到,声明成员函数的定义写法跟以前直接定义函数不一样,而是在成员函数名的前面加了作用域限定符’::’,表达的是该函数并不是全局的函数,而是只针对Stack结构体类型的函数。

        声明和定义分离后的运行结果:

        从结果可以看到,即使分成三个文件,只要使用作用域限制符依然是可以正常运行的。 

2、类的定义

        C++的类其实就是c语言中的结构体,只是类相比于结构体是做了升级的。因为C++兼容c语言,因此仍然可以采用struct来定义一个类,而且是支持类的相关功能的。但是在C++中基本都是用关键字class来定义一个类,比如创建一个栈的类,具体写法为:

class Stack//class后面跟类的名称
{//括号内存放成员变量和成员函数
};int main()
{Stack st1;//st1在c语言中是变量,但是在C++中,更喜欢把st1叫做对象return 0;
}

2.1 类的访问限定(封装)

        既然了解了class的作用后,将上述代码的struct替换成class,真正的去使用C++的类,但是发现替换后编译器开始报错了:

        报错显示类中的成员都不可访问, 主要是因为C++的类相比于c语言的struct更加的安全,具体体现在类中的空间分为私有域和公有域,公有域是可以让类外随意访问的,而私有域拒绝让类外访问。在用class创建类时,如果没有明确对类进行公有域和私有域的划分,那么默认类里的所有域为私有域,这也是报错的原因。

访问限定符说明:

1、public修饰的成员是可以让类外进行访问。

2、protected和private修饰的成员不可让类外直接进行访问。

3、一个访问限定符的作用域范围是直到遇到下一个访问限定符或者遇到‘}’。

4、class的默认访问权限是private(这也是上述代码报错的原因),而struct默认访问权限是public(这也是为什么上述代码用struct就能够正常运行)。


        像上述把类分成两个区域的这一操作又称为封装,封装:隐藏对象的细节,对外只公开接口,让外部通过接口与对象达成交互。目的是为了更安全的使用代码,通常是把成员变量都放在私有域中,而成员函数放在公有域中,提高用户使用代码的安全性。 

        把struct替换成class并且优化后的代码如下:

#include<iostream>
using namespace std;class Stack//结构体(类)
{
public://成员函数//栈的初始化void Init(int n = 4){arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("malloc");return;}Top = 0;capacity = n;}//压栈void push(int x){if (capacity == Top){int newcapacity = 2 * capacity;int* temp = (int*)realloc(arr, sizeof(int*) * newcapacity);if (temp == nullptr){perror("Push");return;}arr = temp;capacity = newcapacity;}arr[Top++] = x;}void Print()//只能在类中进行对private的访问{cout << arr[Top-1] << endl;}private://成员变量int* arr;int Top;int capacity;
};int main()
{Stack st1;//创建结构体变量st1.Init();st1.push(1);st1.push(102);st1.Print();return 0;
}

        因此如果要访问类的私有域内容,只能在通过类中的函数进行访问。 

3、类重名问题

        例如,现对一个日期类进行初始化:

#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 dt1;//创建对象dt1.Init(2022, 2, 2);dt1.Print();return 0;
}

        运行结果:

        可以发现结果竟然是随机值,原因就是初始化函数的形参与类成员变量同名,然后编译器遵循局部优先的概念,把左值当成了形参本身,结果是形参自己给自己赋值,类成员变量并没有完成初始化。

        解决方法:把成员变量名和形参名进行区分即可。

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;
};

4、类的实例化

       用类的类型去创建一个对象,该操作称之为类的实例化,跟c语言中创建变量是一个意思。在对类实例化之前是不能直接去访问类中的内容,因为类好比一个构想图,光有一份构想图是无法真正获得图内的东西,只有在类实例化后,即真正实现了构想图的内容才可以实际的获得其中。因此在实例化后,系统会为该对象开辟一块空间,这时候以该空间为对象,可以去访问他的类。

        类的实例化代码例子如下: 

class Date
{//....
public:int _year;int _month;int _day;
};int main()
{Date._year = 12;//错误写法,不能直接访问类Date dt1;//类的实例化Date dt2;//类的实例化//实例化后,可以通过对象访问该对象的类dt1._day = 12;dt2._day = 12;return 0;
}

4.1 类的大小

        一个结构体的大小是根据他的成员类型计算出来的,但是一个类所包含的不只有成员变量,还有成员函数,那么一个类的大小该如何计算呢,用上述日期类作为例子,计算该类的大小。

        可以看到,尽管调用了成员函数,但是该类的大小依然是12,说明系统只为该类的成员变量开辟了空间,并没有给成员函数调用空间。

        因为不同对象的成员变量肯定是不一样的,就拿日期类举例,对象dt1的成员变量可以是2022.2.2,但是重新实例化一个对象dt2的成员变量可以为2023.3.3。只是对象dt1和dt2再调用其成员函数时,实质上全部调用的都是同一个成员函数,因为函数都是实现相同功能,比如dt1中的初始化函数和dt2中的初始化函数所实现的功能都是一样的,因此把类的成员函数放在公共区域(即代码段中),不同对象再调用函数时,统一去代码段中调用,因此函数不计入类的大小。

5、隐含的this指针

        上述说到了类成员函数是放在公共区域的,那么问题来了,不同的对象再调用同一个函数时,编译器是如何知道是哪个对象调用的,因为传参的时候传的只有实参数据,并无其他区分对象的标记号。


        当对象调用函数时,编译器会自行给函数的实参和形参补上该对象的地址和指针,如下图:

        编译器会自动把对象的地址一并当作实参传递给函数形参,并且会添加一个(隐藏指针)this指针作为形参接收对象的地址,用指针this就能访问并且修改具体对象里的成员变量了 ,当然这都是编译器自动完成的。我们可以在函数内部使用this指针,但是不能在形参和实参上直接手动添加。因为是形参,因此this指针存放在栈空间,在vs环境下,this指针通过ecx寄存器进行自动传递,所以无需用户干涉过程。

5.1 空指针问题

        我们都知道如果对一个空指针进行解引用是会报错的,那么以下代码的运行结果是什么呢?

#include<iostream>
using namespace std;class example_one
{
public:void Print(){cout << "Print()" << endl;}void Init(int x){_a = x;}
private:int _a;
};
int main()
{example_one* p = nullptr;//定义一个指针p指向空p->Print();return 0;
}

        运行结果:

        结果竟然是正常运行并且打印了Print函数内的信息, 当我们看到语句:p->Print(),第一反应都是对p进行解引用,对空指针进行解引用肯定会报错,原因在于这里并没有对p进行解引用,只是调用了类型为example_one的对象里的函数Print(因为函数不是存储在类里面,而是存储在代码段中),并且把指针的内容传给函数Print,这一过程并没有对空指针进行解引用。


        然而下面这种写法就会报错:

#include<iostream>
using namespace std;class example_one
{
public:void Print(){cout << "Print()" << endl;}void Init(int x){cout <<this<< endl;//打印出来的是00000000,表示空指针_a = x;//这里可以理解为*this->_a,对空指针this解引用,因此报错}
private:int _a;
};
int main()
{example_one* p = nullptr;//p->Print();//可以正常运行p->Init(1);return 0;
}

        结合this指针的概念,这里把p的值作为实参传递给了this指针,因此发生错误的原因在于Init函数内部对this指针进行了解引用操作,即对空指针进行解引用操作,导致报错。

        所以‘->'并不一定是解引用操作,关键点在于右值是否为类里的成员变量,如果只是成员函数那么‘->'不为解引用操作,如果右值为成员变量则‘->'表示解引用操作。

结语:

        以上就是关于C++_类的讲解,类与结构体在概念上虽然相似,但是类的细节更多,较结构体更复杂,类作为C++中的基础需掌握好。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!( ̄︶ ̄)↗ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/217884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VRRP协议详解

目录 一、基础概念 1、概念 2、VRRP的基本结构 状态机 二、VRRP主备备份工作过程 1、备份工作过程 2、VRRP的负载分担工作 三、实验 一、基础概念 1、概念 VRRP能够在不改变组网的情况下&#xff0c;将多台路由器虚拟成一个虚拟路由器&#xff0c;通过配置虚拟路由器的I…

自媒体新闻中心-后台管理端

0.本节内容说明 本节主要是一个功能概述&#xff0c;了解清楚这个这个后台管理端做的什么&#xff0c;以及实现的思路&#xff0c;具体的实现代码部分&#xff0c;后面讲解 1.后台功能概述 登陆: 账号密码登陆&#xff0c;或者是账号人脸进行登陆内容审核&#xff1a;对于用户…

【Stable Diffusion】在windows环境下部署并使用Stable Diffusion Web UI---通过 Conda

本专栏主要记录人工智能的应用方面的内容&#xff0c;包括chatGPT、AI绘图等等&#xff1b; 在当今AI的热潮下&#xff0c;不学习AI&#xff0c;就要被AI淘汰&#xff1b;所以欢迎小伙伴加入本专栏和我一起探索AI的应用&#xff0c;通过AI来帮助自己提升生产力&#xff1b; 订阅…

DevOps 和人工智能 – 天作之合

如今&#xff0c;人工智能和机器学习无处不在&#xff0c;所以它们开始在 DevOps 领域崭露头角也毫不令人意外。人工智能和机器学习正在通过自动化任务改变 DevOps&#xff0c;并使各企业的软件开发生命周期更高效、更深刻和更安全。我们在 DevOps 趋势中简要讨论过这一问题&am…

LeetCode力扣每日一题(Java)66、加一

每日一题在昨天断开了一天&#xff0c;是因为作者沉迷吉他&#xff0c;无法自拔……竟然把每日一题给忘了&#xff0c;所以今天&#xff0c;发两篇每日一题&#xff0c;把昨天的给补上 一、题目 二、解题思路 1、我的思路 其实乍一看这道题还是比较简单的&#xff0c;就是让…

记录 | linux安装Manim

linux 安装 Manim sudo apt update sudo apt install build-essential python3-dev libcairo2-dev libpango1.0-dev ffmpeg sudo apt install xdg-utilsconda create manim_py39 python3.9 conda activate manim_py39pip install manim安装好环境后来测试一个例程&#xff0c;…

Gitlab+GitlabRunner搭建CICD自动化流水线将应用部署上Kubernetes

文章目录 安装Gitlab服务器准备安装版本安装依赖和暴露端口安装Gitlab修改Gitlab配置文件访问Gitlab 安装Gitlab Runner服务器准备安装版本安装依赖安装Gitlab Runner安装打包工具安装docker安装java17安装maven 注册Gitlab Runner 搭建自动化部署准备SpringBoot项目添加一个Co…

企业IT安全:内部威胁检测和缓解

什么是内部威胁 内部威胁是指由组织内部的某个人造成的威胁&#xff0c;他们可能会造成损害或窃取数据以谋取自己的经济利益&#xff0c;造成这种威胁的主要原因是心怀不满的员工。 任何内部人员&#xff0c;无论是员工、前雇员、承包商、第三方供应商还是业务合作伙伴&#…

el-table的复选框占满全格

el-table的复选框格子很小每次点击都点不到&#xff0c;又不想设置行点击&#xff0c;因为每次复制内容都会选中&#xff0c;实现效果是点击el-table的复选框单元格就可以选中 <template><div style"width: 60vw; margin: 10px;"><el-table :data&quo…

openHarmony添加system_basic权限安装报错

openHarmony添加system_basic权限安装报错 12/14 13:49:57: Install Failed: [Info]App install path:D:\huawei\project\FCTTest\entry\build\default\outputs\default\entry-default-signed.hap, queuesize:0, msg:error: failed to install bundle. error: install failed …

动态内存管理,malloc和calloc以及realloc函数用法

目录 一.malloc函数的介绍 malloc的用法 举个例子 注意点 浅谈数据结构里的动态分配空间 二.calloc函数的介绍 三.realloc函数的介绍 四.柔性数组的介绍 为什么有些时候动态内存函数头文件是malloc.h,有些时候却是stdlib.h 一.malloc函数的介绍 malloc其实就是动态开辟…

Docker, Docker-compose部署Sonarqube

参考文档 镜像地址: https://hub.docker.com/_/sonarqube/tags Docker部署文档地址 Installing from Docker | SonarQube Docs Docker-compose文档部署地址&#xff1a; Installing from Docker | SonarQube Docs 部署镜像 2.1 docker部署 # 宿主机执行 $. vi /etc/sysctl.conf…

网络安全——SQL注入实验

一、实验目的要求&#xff1a; 二、实验设备与环境&#xff1a; 三、实验原理&#xff1a; 四、实验步骤&#xff1a; 五、实验现象、结果记录及整理&#xff1a; 六、分析讨论与思考题解答&#xff1a; 七、实验截图&#xff1a; 一、实验目的要求&#xff1a; 1、…

言简意赅的 el-table 跨页多选

步骤一 在<el-table>中:row-key"getRowKeys"和selection-change"handleSelectionChange" 在<el-table-column>中type"selection"那列&#xff0c;添加:reserve-selection"true" <el-table:data"tableData"r…

嵌入式开发板qt gdb调试

1&#xff09; 启动 gdbserver ssh 或者 telnet 登陆扬创平板 192.168.0.253&#xff0c; 进入命令行执行如下&#xff1a; chmod 777 /home/HelloWorld &#xff08;2&#xff09; 打 开 QTcreator->Debug->StartDebugging->Attach to Running Debug Server 进行…

音乐制作软件Ableton Live 11 mac功能特点

Ableton Live 11 mac是一款数字音频工作站软件&#xff0c;用于音乐制作、录音、混音和现场演出是一款流行的音乐制作软件。 Ableton Live 11 mac特点和功能 Comping功能&#xff1a;Live 11增加了Comping功能&#xff0c;允许用户在不同的录音轨道上进行多次录音&#xff0c;…

西南科技大学数字电子技术实验四(基本触发器逻辑功能测试及FPGA的实现)预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) (1)D触发器 特征方程: Q…

网络层--TCP/UDP协议

目录 一、TCP/UDP协议介绍 1、UDP(User Datagram Protocol)--用户数据报协议 1.1 UDP报文格式 1.2 UDP协议的特性 2、TCP(Transmission Control Protocol )--传输控制协议 2.1 TCP报文格式 2.2 TCP协议的特性 2.3 TCP三次握手 2.4 四次挥手 三、TCP和UDP的区别 四、t…

技术分享 | app测试中常用的Android模拟器

Emulator Emualor 是 Android Studio 自带的模拟器&#xff0c;是官方提供的工具&#xff0c;Android 开发最常使用的就是这一款。 它功能非常齐全&#xff0c;电话本、通话等功能都可正常使用。用户可以使用键盘输入&#xff0c;鼠标点击模拟器按键输入&#xff0c;甚至还可以…

Spring基于xml半注解开发

目录 Component的使用 依赖注解的使用 非自定义Bean的注解开发 Component的使用 基本Bean注解&#xff0c;主要是使用注解的方式替代原有的xml的<bean>标签及其标签属性的配置&#xff0c;使用Component注解替代<bean>标签中的id以及class属性&#xff0c;而对…