深入篇【C++】类与对象:const成员与Static成员

news2024/11/26 18:27:56

深入篇【C++】类与对象:const成员与Static成员

  • ⏰<const成员>
    • 🕓1.权限
    • 🕐2.规则
    • 🕒3.思考:
  • ⏰<Static成员>
    • 🕑1.概念
    • 🕗2.特性
    • 🕕3.思考:

在这里插入图片描述

⏰<const成员>

🕓1.权限

在C语言中,我们知道const是用来缩小权限或者偏移权限的。

//权限的缩小和偏移
int main()
{
	const int a = 1;
	//const 修饰a表明a不能被修改
	//也就是不能通过其他方式来修改变量a,权限由原来的可读可写,变成了可读不可写,权限缩小了。

	int& b = a;//不能通过取别名来修改变量a
	int* c = &a;//也不能通过指针来修改变量a

	const int& b = a;//权限是可以偏移的,相同权限是可以访问的
}

在这里插入图片描述
在C++中如果const修饰一个对象会怎么样呢?

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023, 5, 16);
	d1.Print();
	const Date d2(2023, 5, 17);
	d2.Print();

}

在这里插入图片描述
而在这里我们发现d1可以调用成员函数Print,而d2无法调用成员函数Print,这是为什么呢?
这里的原因就在于const修饰了对象d2,使得d2的成员无法进行修改了。
我们知道调用成员函数时,会将对象的地址传给函数,函数会用一个this指针来接收。
在这里插入图片描述
而被const修饰的d2是不能再通过其他方式来对d2进行访问修改的。所以d2是无法调用Print成员函数的,所以只要cosnt修饰了对象,那么对象就无法再调用成员函数了,因为编译器会自动的将对象的地址传给成员函数,成员函数会用一个this指针来接收。
const使对象d2权限缩小了,由可读可写变成了只读。
那对象d2如何调用它的成员成员函数呢?
我们知道权限缩小后就无法调用,但权限偏移还是可以访问的。所以我们只要让成员函数的权限也变得跟对象d2一样大,那成员函数不就可以调用了吗。所以让const来修饰成员函数就能解决这个问题。

将const西洋参的成员函数称为const成员函数,const修饰类成员函数,实际上修饰的是该成员函数中隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改

用const修饰成员函数,成员函数的权限就和对象一样大了,那对象就可以调用成员函数了。
不过这里有一个问题,const修饰成员函数,其实是修饰隐藏的this指针,C++规定this指针不能显式出现在函数外和函数参数列表的。那const应该写在哪里呢?
C++要求const修饰成员函数,const一律写在函数后面。

void Print()const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//其实本质就是下面这样:
	//只不过this指针不能显式出现
void Print(const Date* this)
	{
	    cout << _year << "-" << _month << "-" << _day << endl;
    }

在成员函数后面加上const后,普通对象和const修饰的对象都可以调用了

因为成员函数后面加上const后权限变小,普通对象肯定是可以调用的,因为普通对象权限相比较大,const修饰的对象就和const修饰的成员函数权限一样大,所以也可以调用。

那是不是所有的成员函数后面都能加上const修饰呢?
当然不是。

🕐2.规则

const成员规则:

要修改对象成员变量的函数后面是不能加const,一旦加上const那么该函数就不能修改对象成员变量了,但只要成员变量不需要修改的函数后面都能加上const。

所以我们对于那些不需要修改对象成员变量的函数后面都应该加上const,为什么呢?
因为这样普通对象和const修饰的对象都可以调用成员函数。

🕒3.思考:

思考下面几个问题:

1.const对象可以调用非const成员函数吗?

不能,const对象权限小,非const成员函数权限大。

2.非const对象可以调用const成员函数吗?

可以,非const对象权限大,const成员函数权限小。

3.const成员函数内可以调用其他的非const成员函数吗?

不能,cosnt成员函数权限小,其他非const成员函数权限大。

 4.非const成员函数内可以调用其他的const成员函数吗?

能,非const成员函数权限大,其他const成员函数权限小。

从权限角度我们可以很轻松的解决这些问题。

⏰<Static成员>

🕑1.概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量。用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。

有没有一个办法可以计算出一个程序中出现了多少个对象呢?
我们的初步想法是当对象实例化时肯定会调用构造函数,当拷贝生成对象时肯定会调用拷贝构造函数,当对象销毁时肯定会调用析构函数,所以我们只要在这些函数里弄一个计数器就就可以了,当对象调用构造函数或者拷贝构造函数时,计数器加一,当调用析构函数时计数器减一。
而这个计数器要保证在全局都有效,不能是局部变量,不然函数使用完计数器就销毁了,所以我们可以将计数的变量定义为全局变量count,最开始为0.

int _count=0;
class A
{
public:
	A() //构造函数
	{ ++_count; }
	A(const A & t)//拷贝构造 
	{ ++_count; }
	~A() //析构函数
	{ --_count; }

private:
	int a;
};

void TestA()
{

	A a1, a2;//定义了两个对象
	cout << _count << endl;//这里应该是2
	A a3(a1);//又拷贝一个对象,又多一个计数器应该是3
	cout << _count << endl;
	
}
int main()
{
	TestA();

}

在这里插入图片描述
这样就可以简单的计算出程序中一共创建了多少对象了。
不过上面的做法有些问题,有哪些问题呢?

计数变量作为全局变量可能会被修改。全局变量的缺点就是谁都可以访问,谁都可以修改。

有没有什么办法可以像C++一样将成员封装起来呢?不让别人随意访问这个变量呢?

C++设计出static成员来解决这个问题。
既想拥有全局范围,又不想被随意访问,只能使用static成员。
static修饰变量,使变量具有静态性,存放在静态区,生命周期跟全局变量一样,都是在程序结束后自动销毁。
而想让该变量不被随意访问,只能作为类成员变量,使用访问限定符private来限定,在类里成员函数是可以随意访问,但在类外无法随意使用。
其实本质上就是将全局变量/静态变量封装。
但C++中有规定:静态成员变量在类里声明,在类外定义。

class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& t)
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
	static int GetCount()
	{
		return _count;
	}
private:
	static int _count;//静态成员变量在类里面声明
};

int A::_count = 0;//在类外面定义

到这里你可能意识到不对劲了,那计数变量封装在类里面,我们如何得到它呢?

在类外我们是无法使用这个静态变量的。使用域作用符也没有用,域作用符只是告诉这个变量在哪里,但不给你访问能有是用呢。
而想得到这个成员变量,就要有this指针,但哪里来this指针呀。
所以C++又设计出static成员函数来解决这个问题。
用static修饰的成函数称为静态成员函数,而静态成员函数是没有this指针的,只要指定类域和访问限定符就可以访问。

class A
{
public:
	static int GetCount()//静态成员函数是没有this指针的,只要指定类域和访问限定符就可以调用
	{
		return _count;
	}
private:
	static int _count;
};
int A::_count = 0;

所以我们在类外就可以使用静态成员函数来获得这个成员变量,正常来说我们要想使用类里的成员函数,是需要有this指针的,但静态成员函数是没有this指针,所以它是可以被任意调用的(访问限定符是pubilc)。

class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& t)
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
	static int GetCount()
	{
		return _count;
	}
private:
	static int _count;
};

int A::_count = 0;
void TestA()
{

	A a1, a2;
	cout <<A::GetCount()<< endl;
	//指定类域和访问限定符就可以调用该函数
	A a3(a1);
	cout <<A::GetCount()<< endl;

}
int main()
{
	TestA();
	
}

在这里插入图片描述
所以一般来说,静态成员和静态成员函数是配套出现的,使用静态成员就要使用静态成员函数。

不过要注意静态成员是存储在静态区的,它不是存储在类里的。并且它不是某个对象特有的成员变量,它是所有对象共享的成员变量,每个对象对这个变量都可以修改,但不能控制其他对象对它修改,所有它是共用的。

这里总结一下静态成员和静态成员函数的特性

🕗2.特性

1.静态成员为所有类对象所共享,不属于某个具体的对象,它是存储在静态区的。
2.静态成员变量必须在类外定义,定义时不添加static关键字,在类里声明,还有静态成员变量不能给缺省值。
3.类静态成员可用 类名::静态成员或者对象.静态成员来访问。
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员。因为访问其他成需要this指针才可以。
5.静态成员也是类的成员,受访问限定符的限制。

🕕3.思考:

1.静态成员函数可以调用非静态成员函数吗?
不能!
因为如果非静态成员函数需要访问成员变量时,是需要this指针的,但静态成员函数没有this指针,所有无法调用。
在这里插入图片描述

2.非静态成员函数可以调用类的静态成员函数吗?

可以的。非静态成员函数有this指针,想干嘛干嘛。

3.如何设计一个类,只能在栈或者堆上创建对象?

int main()
{
	C c1;//栈上开辟的
	static C c2;//静态区开辟的
	C* c3 = new C;//堆上开辟的
}

创建对象都有一个特点那就是调用构造函数,所有我们首先将构造函数进行封闭,放进限定保护符里,不给它们调用那么就无法创建在栈上,在静态区,在堆上创建对象了,但我们要求是只在栈上或者堆上,不能全部无法创建呀。

在这里插入图片描述
我们可以这样做,利用成员函数来获得我们想要在哪开辟空间的功能。

class C
{
public:
	C Stack()
	{
		C c1;//栈上开辟的
		return c1;

	}
	C Static()
	{
		static C c2;//静态区开辟的
		return c2;
	}
	C* Pile()
	{
		C* c3 = new C;//堆上开辟的
		return c3;
	}
private:
	C()
	{ }
};

想在哪创建对象就调用哪个函数,这是我们的想法。
但问题是怎么调用呀?你无法调用成员函数呀。
要调用成员函数那必须要有this指针,也就是创建一个对象才可以调用成员函数,可我们要求就是能给定在哪开辟对象,这里却让我先创建一个对象然后再给定在哪创建对象?
这肯定是不行的,这里就需要用到刚刚的知识了,静态成员函数是没有this指针的,没有this指针也可以调用静态成员函数。
所以这里我们只要将这些成员函数变成静态成员函数,那么我们就可以调用啦。
这样问题就解决了。

class C
{
public:
	static C Stack()
	{
		C c1;//栈上开辟的
		return c1;

	}
	static C Static()
	{
		static C c2;//静态区开辟的
		return c2;
	}
	static C* Pile()
	{
		C* c3 = new C;//堆上开辟的
		return c3;
	}
private:
	C()
	{ }
};
int main()
{
	C c=C::Stack();//栈上开辟的
	C* c1 = C::Pile();//堆上开辟的
}

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

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

相关文章

从零开始 Spring Boot 29:类型转换

从零开始 Spring Boot 29&#xff1a;类型转换 图源&#xff1a;简书 (jianshu.com) PropertyEditor Spring使用PropertyEditor进行String和具体类型之间的转换&#xff1a; public interface PropertyEditor {void setValue(Object value);Object getValue();String getAsT…

第五章 面向对象-7.hashCode()和toString()

hashCode()和toString() hashCode() hashCoed 的特性&#xff1a; &#xff08;1&#xff09;HashCode的存在主要是用于查找的快捷性&#xff0c;如Hashtable&#xff0c;HashMap等&#xff0c;HashCode经常用于确定对象的存储地址&#xff1b; &#xff08;2&#xff09;如果…

华为OD机试真题 Java 实现【统一限载货物数最小值】【2023Q1 200分】

一、题目描述 火车站附近的货物中转站负责将到站货物运往仓库&#xff0c;小明在中转站负责调度 2K 辆中转车(K辆干货中转车&#xff0c;K 辆湿货中转车)货物由不同供货商从各地发来&#xff0c;各地的货物是依次进站&#xff0c;然后小明按照卸货顺序依次装货到中转车&#x…

智能床垫市场调研分析报告

文章目录 一、简介&#xff08;1&#xff09;电动床&#xff08;2&#xff09;气垫床 二、使用人群三、睡姿四、实用性 一、简介 &#xff08;1&#xff09;电动床 电动床之下又分成了分体、连体和床头分体。分体电动床是指床垫与床底座分开的电动床&#xff1b;连体的则是床垫…

数据结构-外部排序-(多路归并排序、败者树、置换选择排序、最佳归并树)

目录 一、外部归并排序 二、败者树 三、置换选择排序 四、最佳归并树 一、外部归并排序 16个块&#xff0c;先每个块读入内存进行排序在输出回来&#xff0c;进行16次读和16次写 两两归并&#xff0c;第一趟如下 在两两归并 时间分析 外部排序时间开销读写外存时间内存排序时…

C语言基础知识:函数的声明和使用

目录 函数的声明 1.定义顺序 2.函数的声明 3.函数的声明格式 多源文件开发 1.为什么要有多个源文件 2.将sum函数写到其他源文件中 3.在main函数中调用sum函数 4.编译所有的源文件 5.链接所有的目标文件 #include 1.#include的作用 2.#include可以使用绝对路径 3.#…

Linux免交互操作

免交互操作 Here DocumentExpect工具 Here Document Here Document概述 使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp 、cat 或 read 命令。Here Document 是标准输入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息…

docker搭建Elasticsearch集群

这里写目录标题 1.拉取es镜像2.配置配置文件3.启动容器4.启动过程中遇到的问题5.查看容器启动情况 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.0版本根据自己需求进行拉取&#xff0c;我这边选择的是7.17.0&#xff0c;不同版本配置可能稍有…

ANR原理篇 - Input超时机制

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、事件分发流程1.1 事件分发流程概览1.2 InputDispatcher 三、ANR触发流程超时重…

ANR原理篇 - service/broadcast/provider超时机制

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、Service超时机制1.1 埋炸弹1.1.1 AS.realStartServiceLocked1.1.2 AS.bumpSer…

三大基础排序算法——我欲修仙(功法篇)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️我欲修仙】 学习名言&#xff1a;莫等闲、白了少年头&#xff0c;空悲切。——岳飞 系列文章目录 第一章 ❤️ 学习前的必知知识 第二章 ❤️ 二分查找 文章目录 系列文章目录前言&#x1f697;&…

Netty实战(三)

Netty的组件和设计 一、Channel、EventLoop 和 ChannelFuture1.1 Channel 接口1.2 EventLoop 接口1.3 ChannelFuture 接口 二、ChannelHandler 和 ChannelPipeline2.1 ChannelHandler 接口2.2 ChannelPipeline 接口2.3 编码器和解码器2.4 抽象类 SimpleChannelInboundHandler 三…

suricata中DPDK收发包源码分析2

《suricata中DPDK收发包源码分析1》中分析了整体的DPDK收发包框架代码&#xff0c;今天我们继续来深入了解一下一些细节方面的问题。 目录 Q1&#xff1a;收发包线程模式在代码中是怎样确定的&#xff1f; Q2: DPDK库的初始化rte_eal_init在哪里调用的&#xff1f; Q3: 对网…

Linux中LV Status的状态为NOT available

今天下午有现场反馈备份磁盘找不到了&#xff0c;使用lvm方式的。提供了todesk帮忙看下&#xff0c; 首先使用 blkid查看&#xff0c;确实看不到备份磁盘的UUID&#xff0c;使用lvdisplay查看状态&#xff0c;状态不对了 [rootdb1 ~]# lvdisplay --- Logical volume --- …

.Vue3项目初始化

文章目录 1.Vue3项目初始化1.1 创建vue项目1.2 vue 初始化1.3 git 项目管理1.4 配置iconfig.json1.5 element 按需引入1.6 element 主题色的定制1.7 axios的基础配置1.8 router路由的配置 1.Vue3项目初始化 1.1 创建vue项目 npm init vuelatest1.2 vue 初始化 npm install1.…

【2023/05/16】MonteCarlo

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第11天。 Share O Beauty,find theyself in love,not in the flattery of thymirror. 译文&#xff1a; 啊&#xff0c;美啊&#xff0c;在爱中找你自己吧&#xff0c;不要到你镜子的诌谀中去寻找。 M…

[遗传学]转座因子的结构与功能

本篇文章主要带你了解:转座因子的发现和分类;原核生物以及真核生物种的转座子;转座作用的分子机制以及转座因子的遗传学效应和应用. &#x1f9ec;转座因子的发现和分类 &#x1f9ec;转座因子的概念 转座因子(transposable element)是在转座酶&#xff08;transposase&#xf…

Class 03 - R语言的 Vectors(向量) 与 lists(列表)

Class 03 - R语言的 Vector与 列表 list R语言语法脚本文件的创建、保存、和修改名称第一个函数使用帮助功能查看函数详细说明语法问题变量与赋值定义变量名称格式调用变量 R中的数据结构Vectors (向量)创建向量查看向量的性质查看数据类型 typeof()查看数据长度 length()检查…

Elasticsearch 核心技术(十):GEO 地理查询(geo_bounding_box、geo_distance、geo_shape)

❤️ 博客主页&#xff1a;水滴技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; &#x1f338; 订阅专栏&#xff1a;大数据核心技术从入门到精通 文章目录 一、地理数据类型1.1、geo_point 地理点类型1.1.1、创建一个含有 geo_point 字…

opencv_c++学习(八)

一、两张图像的像素比较 比较最大最小 最小&#xff1a; min(lnputArray src1, InputArray src2, outputArray dst)最大&#xff1a; max(lnputArray src1, InputArray src2, outputArray dst)src1 :第一个图像矩阵&#xff0c;可以是任意通道数的矩阵。 src2:第二个图像矩…