C++_类的定义和使用

news2024/11/18 23:47:20

       

目录

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;//成员变量的类型也可以不加struct
	int 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.coloradmin.cn/o/1313747.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Vue】日期格式化(全局)

系列文章 【Vue】vue增加导航标签 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/134965353 【Vue】Element开发笔记 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/133947977 【Vue】vue&#xff0c;在Windows IIS平台…

Tomcat的结构分析和请求处理原理解析

目录 Tomcat服务器&#xff1f;Tomcat结构处理请求流程Tomcat作用其他的web服务器 Tomcat服务器&#xff1f; 我们经常开口闭口“服务器”、“服务器”的&#xff0c;其实“服务器”是个很容易引发歧义的概念 其实&#xff0c;Tomcat服务器 Web服务器 Servlet/JSP容器&#…

java简易制作-王者荣耀游戏

一.准备工作 首先创建一个新的Java项目命名为“王者荣耀”&#xff0c;并在src下创建两个包分别命名为“com.sxt"、”com.stx.beast",在相应的包中创建所需的类。 创建一个名为“img”的文件夹来储存所需的图片素材。 二.代码呈现 package com.sxt; import javax…

人工智能联盟的首件神兵利器——“Purple Llama” 项目,旨为保护工智能模型安全性

Meta公司&#xff08;Meta Platform Inc&#xff09;&#xff0c;原名Facebook&#xff0c;创立于2004年2月4日&#xff0c;市值5321.71亿美元。总部位于美国加利福尼亚州门洛帕克。 Meta 公司推出了名为“Purple Llama”的项目&#xff0c;旨在保护和加固其开源人工智能模型。…

【MODBUS】Modbus的3种传输方式

概述 1979年&#xff0c;Modicon 首先推出了串行Modbus标准&#xff0c;后来由于网络的普及&#xff0c;需要更高的传输速度&#xff0c;1997年制定了基于TCP网络的Modbus标准。 所以总的可分为两个传输模式:基于串行链路的和基于以太网TCP/IP的。但是我个人还是习惯分为3种传…

【MySQL 索引】InooDB 索引实现

1 索引定义 维基百科对数据库索引的定义: 数据库索引是数据库管理系统&#xff08;DBMS&#xff09;中的一个排序数据结构, 以协助快速查询和更新数据库表中的数据。 MongoDB对索引的定义: 索引是一种特殊的数据结构, 以有序和便于遍历的形式存储数据集合中特定字段或一组字段…

蓝桥杯专题-真题版含答案-【制作表格】【5位黑洞数】【泊松分酒】【亲密数】

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

UG NX二次开发(C++)-库缺少需要的入口点的原因与解决方案

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、“库缺少需要的入口点”错误展示3、可能出现的原因与解决方案3.1 对于采用CTRL+U方式调用3.2 对于menu菜单下调用1、前言 在UG NX二次开发过程中,有时会遇到形形色色的bug,比如有个读…

可视化监控云平台/智能监控EasyCVR如何使用脚本创建ramdisk挂载并在ramdisk中临时运行

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防管理视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存…

【Spring Boot】内网穿透实现远程调用调试

文章目录 1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址 4.…

管理类联考——英语二——考点+记忆篇——大作文——表格

大作文Remarkablechangesconcerning主题词havebeenwitnessedinTheabovechartthoroughlyrevealedthefascinatingissue.Onecanseethat年份1,事物1wasonly数字1.ButwithinashortdurationofXyears,事物1bumpedto数字2in年份2.Apartfromthat,事物2hasenjoyedaslightdeclineduringthe…

基于3D-CGAN的跨模态MR脑肿瘤分割图像合成

3D CGAN BASED CROSS-MODALITY MR IMAGE SYNTHESIS FOR BRAIN TUMOR SEGMENTATION 基于3D-CGAN的跨模态MR脑肿瘤分割图像合成背景贡献实验方法Subject-specific local adaptive fusion&#xff08;针对特定主题的局部自适应融合&#xff09;Brain tumor segmentation model 损失…

外汇天眼:如果美元开始降息,会为市场带来哪些影响?

一、美元降息的影响 美元一旦开始降息&#xff0c;将对全球经济产生重要影响。 降息政策将带动美国内部的消费和投资增长。 透过降低借贷成本&#xff0c;鼓励个人和企业增加支出&#xff0c;刺激经济活动。 以最新数据为例&#xff0c;美国的消费者信心指数正在上升&#xff…

4.qml 3D-Light、DirectionalLight、PointLight、SpotLight、AxisHelper类深入学习

今天我们学习灯光类 首先来学习Light类&#xff0c;它是所有灯光的虚基类&#xff0c;该类是无法创建的&#xff0c;主要是为子类提供很多公共属性。 常用属性如下所示&#xff1a; ambientColor : color&#xff0c;该属性定义在被该光照亮之前应用于材质的环境颜色。默认值…

Halcon参考手册异常检测知识总结

1.1异常检测介绍 本章将介绍如何使用基于深度学习的异常检测和全局上下文异常检测。通过这两种方法&#xff0c;我们想要检测图像是否包含异常(异常是指偏离正常的事物&#xff0c;未知的事物)。 异常检测或全局上下文异常检测模型学习无异常图像的共同特征。经过训练的模型将…

CentOS7 OpenSSL升级到OpenSSH9.5p1

原文链接&#xff1a; CentOS7 OpenSSL升级1.1.1w&#xff1b;OpenSSH 升级 9.5p1 保姆级教程 openssl从3.1.0升级到3.1.1遇到的问题 注意操作时需要联网请参考如下链接 内网服务器联网安装依赖参见我的另一篇文章 一、 前言 OpenSSH 的加密功能需要用到OpenSSL&#xff0c;所…

STM32-UART-DMA HAL库缓冲收发

文章目录 1、说明1.1、注意事项&#xff1a;1.2、接收部分1.3、发送部分 2、代码2.1、初始化2.2、缓冲接收2.3、缓冲发送2.4、格式化打印 1、说明 1.1、注意事项&#xff1a; HAL库的DMA底层基本都会默认开启中断使能&#xff0c;如果在STM32CubeMx禁用了中断相关的功能&…

基于ssm企业人事管理系统的设计与实现论文

摘 要 进入信息时代以来&#xff0c;很多数据都需要配套软件协助处理&#xff0c;这样可以解决传统方式带来的管理困扰。比如耗时长&#xff0c;成本高&#xff0c;维护数据困难&#xff0c;数据易丢失等缺点。本次使用数据库工具MySQL和编程技术SSM开发的企业人事管理系统&am…

人工智能与天文:技术前沿与未来展望

人工智能与天文&#xff1a;技术前沿与未来展望 一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;在各个领域的应用越来越广泛。在天文领域&#xff0c;AI也发挥着越来越重要的作用。本文将探讨人工智能与天文学的结合&#xff0c;以及这种结合带…

动态规划习题

动态规划的核心思想是利用子问题的解来构建整个问题的解。为此&#xff0c;我们通常使用一个表格或数组来存储子问题的解&#xff0c;以便在需要时进行查找和使用。 1.最大字段和 #include <iostream> using namespace std; #define M 200000int main() {int n, a[M], d…