C++类和对象再探

文章目录

  • const成员
  • 再谈构造函数
  • 成员变量的定义
    • 函数体内赋值
    • 初始化列表
  • 隐式类型转换
  • explicit
  • static成员

const成员

我们知道在调用类的成员函数时,会有一个默认的this指针且这个this指针时不可以被修改的,例如在日期类中,会有隐式的Date * const this;注意这里默认会在this前加const让指针的指向不被改变,那么如何让指针指向的成员变量不可以被改变呢?

class Date
{
public:
	Date(const int year = 2003, const int month = 9, const int day = 19)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year = 1998;
	int _month = 1;
	int _day = 1;
};
int main()
{
	Date d1(2000, 12);
	const Date d2;
	d1.Print();//d1.Print(&d1);     Date* const this;
	//d2.Print();//d2.Print(&d2);		Date const* const this;
	return  0;
}

为什么d2不可以调用成员函数,而d1可以?
本质原因是因为形参与实参不匹配,对象d2因为被const修饰所以成员变量不允许被改变,而在传过去时默认的this指针指向的成员变量是可以被改变,这就是权限的放大,所以不被允许。

void Print() const
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}

在被调的成员函数后面加上修饰符const,这里的const修饰的是* this,也就由原来默认的
Date
const this变成了Date const* const this;this和*this都不可以被改变。
只要函数内部this不发生改变,都可以在后面加上const对this进行修饰,这样const对象和普通对象都可以调用这个函数

再谈构造函数

前面我们提到默认构造函数,那么是不是每一个类中都要一个默认构造函数呢?当我们不显示的写构造函数时,编译器会默认生成一个无参的默认构造函数。默认构造函数就不是不需要参数就能够调用的,有三种一种就是我们不写构造函数时,编译器自己生成的,另一种是我们自己定义的构造函数,且没有参数,还有一种我们自己写的全缺省的构造函数,这三种都叫做,默认构造函数。而如果我们自己写的构造函数,就是必须手动传参才能够调用的构造函数,这种就不是默认构造函数,如果一个类中没有默认构造函数,那么我们就必须显示的调用我们自己写的那个构造函数,且要满足这个构造函数对参数的要求。
综上,我们可以只写构造函数不写默认构造函数,但是在初始化对象时,就必须要与我们自己写的构造函数的参数对应上,不过我们建议最好要写上,不然有时直接定义对象时,不传参就会出现没有合适的默认构造的情况。

成员变量的定义

首先我们要弄清楚类成员变量是在哪里定义和声明的

class Date
{
public:
	Date(const int year, 
	const int month, const int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

一个成员变量只能在一个地方定义,并且只能初始化一次,不可以对同一个变量重复定义,但是当这个变量定义好之后,我们可以对它重复赋值,例如

int main()
{
	int a = 5;
	a = 6;
	a = 7;
}

那么上面的日期类中成员变量是在哪里定义和声明的呢?
声明

private:
	int _year;
	int _month;
	int _day = 1;

这里只是对成员变量的声明并不是定义,不占空间,这里的_day = 1,其中1是_day的缺省值,会在初始化列表中起作用,但如果形参day也有缺省值那么它就会被替代

函数体内赋值

Date(const int year, const int month, const int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

构造函数大括号内是对成员变量赋值的地方,不是成员变量变量的地方

初始化列表

成员变量的定义是通过初始化列表对成员变量进行定义,并初始化,且有三种情况只能在初始化列表进行初始化。上面已经说过了一个变量只能初始化一次,如果在初始化列表中对成员变量初始化了,那么在大括号中的就是对它的赋值而不是初始化。
只能在初始化列表进行初始化的三种情况:
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数时)
对于内置类型可以在初始化列表进行定义,在大括号内进行初始化
为什么上面三种情况定义和初始化不可以分开呢?
前两种情况因为引用成员变量和const成员变量必须在定义时就对它进行初始化,第三种情况,是因为它是自定义类型,在定义时编译器默认就要对它进行初始化,而此时它却没有默认构造函数(因为你定义了其它普通的构造函数),所以你就必须显式的对你写的构造函数传参,来对这个自定义类型成员变量进行初始化。如果在有默认构造函数的情况下,我们就可以不显式的去对它进行初始化,因为编译器会自动调用默认构造函数对它进行初始化。而内置类型C++不对它进行处理,如果没有缺省值,在你不在初始化列表写的情况下,它默认只在初始化列表定义但不初始化。
总结:所有的成员类变量默认都要走一遍初始化列表,因为这里是成员变量定义的地方,但是有的成员变量必须在初始化列表进行初始化,有的则可以先定义在后面的大括号里进行初始化。那为什么有时候我们不写初始化列表,也可以直接在大括号里面进行赋值呢,比如上面的日期类,那是因为即使我们不显式的写出来,它也会默认的在初始化列表进行定义,并对自定义类型自动调用它的构造函数对它初始化,而另外三种必须在初始化列表进行初始化情况,如果我们选择直接在大括号内进行对它进行赋值的话就会出错,编译器不允许这种情况的存在。
初始化列表位于构造函数小括号和大括号之间,以冒号开头,不同变量之间用逗号分隔,初始化的值放在要初始化变量的括号里面

Date(const int year, const int month, const int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}

三种必须显式在初始化列表进行初始化的情况

class A
{

public:
	A(int a)
		:_a(a)
	{
	}

private:
	int _a;
};

class B
{

public:
	B(int a, int& ref)
		:_aobj(a)
		,_ref(ref)
		,_n(10)
	{
	}

private:
	A _aobj; // 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const 
};

成员变量声明的顺序就是成员变量在定义时的顺序,与初始化列表中成员变量的顺序无关,所以我们在写成员初始化列表时,最好让初始化列表中变量的顺序与声明时的顺序保持一致,不然可能会出错。初始化列表是定义的地方,所以定义的顺序就是成员变量声明的顺序,如果你在大括号内进行初始化的话,是什么顺序就是什么顺序。

隐式类型转换

在之前我们知道在把一个变量拷贝值给另一个变量值时,其实不是直接拷贝而是会产生一个临时变量,例如一个double类型的变量赋给int类型的变量,这期间就会先产生一个int类型的临时变量,在把int类型的临时变量赋给int类型的变量,并且这个临时变量具有常性,而在赋值期间产生不同类型的临时变量的过程,就是隐式类型的转换,是由编译器主动完成的。
那么问题来了是否可以从一个内置类型转换成自定义类型呢?

//隐式类型转换
class A
{

public:
	A(const int val)
		:_val(val)
	{
		
		cout << "A(const int val)" << endl;
	}

private:
	int _val;
};

int main()
{
	A a1(5);
	A a2 = 10;
	//A& a3 = 12;
	return 0;
}

在a1中是直接调用构造函数,那么像a2的这种写法是否可以呢?它的具体过程又是什么样的呢?
a2的写法是可以的,不过它与a1略有不同,它包含隐式类型转换的过程,先由int类型的10通过调用构造函数,然后产生自定义类型A的临时变量,再由临时变量A拷贝构造给a2,实际上编译器对这种情况也会优化,优化为直接构造。那么你可能会有疑惑,我怎么知道它到底有没有进行隐式类型转换产生临时变量的过程呢?
例如上面a3的这种写法

A& a3 = 12;//错误的

在这里插入图片描述

这种写法是错误的,编译并不会通过,为什么呢?
其实就是因为中间产生了一个具有常性临时变量的原因,而这里的引用a3是变量,权限放大,自然不能绑定到常量上。

const A& a3 = 12;

如果加上了const让引用也变成常量,不可以被改变那么就可以,这里是权限的平移,所以这就很好的证明了这期间确实发生了隐式类型转换,先通过构造函数产生了一个临时变量。
隐式类型转换为自定义类型只适用于只有一个参数的自定义类型,如果有多个参数就不可以

explicit

英文意思为显式,显性
在编写程序的过程中可能需要从一个类型转换成另一个类型这期间可能会发生隐式类型转换
例如显式的

int i = 5;
double d = (int)i;

单参构造函数,没有使用explicit修饰,具有类型转换作用。
为提高代码的可读性我们可以阻止隐式类型转换的发生,那么在转换为自定义类型的过程中我们怎么阻止隐式类型的转换呢?
explicit修饰构造函数,禁止类型转换。

static成员

有时候我们希望一个变量在函数调用后不被销毁,在下一次再调用这个函数时,其中一个变量不用再次被创建,且还保持为上一次的值,为了满足这些条件我们可以定义一个全局变量,但是全局变量也有一个坏处,就是谁都可以访问谁都可以改变,而当我们放在类中声明为静态成员变量时就会安全很多,不会被轻易访问并改变。
与普通成员变量不同的是静态成员变量属于这个类,而普通成员变量属于每一个类的对象

class A
{
public:
	A(int a, int b = 6)
		:_a(a),
		_b(b)
	{

	}

	static int GetStatic()
	{
		return _c;
	}

	void Print()
	{
		cout << _a << '-' << _b << '-' << _c << endl;
	}

private:
	int _a = 4;
	int _b = 3;
	static int _c;//静态成员变量声明的地方
};

int A::_c = 1;//这里是类的静态成员变量定义的地方

int main()
{
	A a1(4, 8);
	a1.Print();
	cout << a1.GetStatic() << endl;
	cout << A::GetStatic() << endl;
	return 0;
}

初始化列表是每一个对象的成员变量定义的地方,而静态成员变量_c是属于类的而不是属于对象,所以它不可以在初始化列表定义,也不会默认像普通成员变量一样走一遍初始化列表。C++规定类的静态成员变量在定义时可以通过访问限定符或者类域突破访问限定符,从而有一次可以在类外定义的机会,所以静态成员变量的定义可以在类外。类的静态成员变量规定在类外定义。
既然规定静态成员变量只有在定义时才可以通过类域或者访问限定符来访问,那么在定义之后我们要是想访问怎么访问呢?
我们可以再类中再定义一个函数来获取静态变量的值,但是对于访问静态成员变量我们一般定义一个静态成员函数与它对应,静态成员变量和静态成员函数这两个通常来说是配对使用的,因为静态成员变量属于类而不是属于哪一个对象,被存储在类中,而对于普通成员变量来说,是存储在每一个对象中的,因此每个对象的成员变量对应的值可能不同,所以在访问某一个对象的成员变量时就必须指明是哪一个对象的成员变量,对于静态成员变量它是属于这个类的,存储在类中,而不是存储在某一个对象中,它是被所有对象共享的,所以对于所有对象而言这个静态成员变量的值都是一样的,所以我们也可以不通过指明对象来访问。
静态成员函数形参中没有默认的形参this,所以可以直接通过类域来访问
静态成员函数的好处在于它不仅可以通过访问限定符来访问,也可以通过指定类域来访问,而普通成员函数只能通过访问限定符来进行访问,因为普通成员函数包含this指针,所以要给定确定的对象。
因为静态成员函数没有this指针,所以静态函数体内部不可以通过this来访问普通的成员变量,隐式写和显式写this都不行。
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一五一、web+小程序骨架屏整理

骨架屏介绍 请点击查看智能小程序骨架屏 车载小程序骨架屏 车载小程序为方便开发者设置骨架屏&#xff0c;在智能小程序的基础上抽取出骨架屏模板&#xff0c;开发者只需要在 skeleton 文件夹下配置config.json&#xff08;page 和骨架屏的映射关系文件&#xff09;即可生效骨…

第十四届蓝桥杯青少组模拟赛Python真题 (2022年11月8日)

第十四届蓝桥杯青少组模拟赛Python真题 (2022年11月8日) 编程题 第 1 题 问答题 二进制位数 十进制整数2在十进制中是1位数,在二进制中对应10,是2位数。 十进制整数22在十进制中是2位数,在二进制中对应10110,是5位数。 请问十进制整数2022在二进制中是几位数? 第2题问…

Pr 拍立得风格图片展示

哈喽&#xff0c;各位小伙伴&#xff01;今天我们来学习一下如何制作拍立得风格的照片展示效果&#xff1f; 新建三个序列 在开始之前&#xff0c;我们需要新建三个序列 序列1&#xff1a;总合成-尺寸1902*1080序列2&#xff1a;照片合成-尺寸1920*1080序列3&#xff1a;照片…

自动驾驶TPM技术杂谈 ———— I-vista验收标准(试验规程)

文章目录 术语介绍试验准备场地要求环境要求精度要求边界车辆&路沿石 试验方法能力试验双边界车辆平行车位白色标线平行车位双边界车辆垂直车位白色标线垂直车位方柱垂直车位双边界车辆斜向车位白色标线斜向车位 新功能评价平行车位远程操控泊入泊出试验垂直车位远程操控泊…

能伸展脖子的机器人?东京大学最新研究成果:基于鸵鸟肌肉骨骼结构和行为,具有高度灵活性的新型机械臂—RobOstrich(附论文)

原创 | 文 BFT机器人 得益于高度灵活的颈部&#xff0c;鸟类可以做很多事情&#xff0c;无论是转过头梳理自己的后背&#xff0c;在飞行过程中“眼观六路”&#xff0c;还是在地面或树上难以触及的角落和缝隙寻找食物。而在所有鸟类中&#xff0c;鸵鸟以其结实灵巧的颈部脱颖而…

​ NISP一级备考知识总结之信息安全概述、信息安全基础

参加每年的大学生网络安全精英赛通过初赛就可以嫖一张 nisp&#xff08;国家信息安全水平考试&#xff09; 一级证书&#xff0c;nisp 一级本身没啥考的价值&#xff0c;能白嫖自然很香 1.信息安全概述 信息与信息技术 信息概述 信息奠基人香农认为&#xff1a;信息是用来消…

【Linux】如何实现单机版QQ,来看进程间通信之管道

学会了管道&#xff0c;就可以实现简单的qq哦~ 文章目录 前言一、匿名管道总结 前言 为什么要进行进程间通信呢&#xff1f;因为需要以下这些事&#xff1a; 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 …

ChatGPT实现旅行安排

工作之余&#xff0c;出门旅行一趟放松放松身心&#xff0c;是对自己辛勤工作最好的犒劳方式之一。旅行可以近郊游、可以远游&#xff0c;可以穷游&#xff0c;可以自驾游&#xff0c;可以一言不合打飞的喂鸽子&#xff0c;方式多种多样。但是多数情况&#xff0c;我们是到一个…

论文解析-基于 Unity3D 游戏人工智能的研究与应用

1.重写 AgentAction 方法 1.1 重写 AgentAction 方法 这段代码是一个重写了 AgentAction 方法的方法。以下是对每行代码解释&#xff1a; ①public override void AgentAction(float[] vectorAction) 这行代码声明了一个公共的、重写了父类的 AgentAction 方法的方法。它接受…

Java版本工程管理系统源码企业工程项目管理系统简介

一、立项管理 1、招标立项申请 功能点&#xff1a;招标类项目立项申请入口&#xff0c;用户可以保存为草稿&#xff0c;提交。 2、非招标立项申请 功能点&#xff1a;非招标立项申请入口、用户可以保存为草稿、提交。 3、采购立项列表 功能点&#xff1a;对草稿进行编辑&#x…

Vue收集表单数据和过滤器

目录 收集表单数据 收集表单数据总结 过滤器 过滤器小结 收集表单数据 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><!--vue--><script src"https://cdn.sta…

C++ ---- 类和对象(下)

目录 初始化列表 初始化列表的语法 初始化列表的特性 explicit关键字 构造函数的隐式转换 explicit的作用 static修饰成员变量和成员函数 static修饰成员变量 static修饰成员函数 友元 友元函数 友元类 内部类 匿名对象 拷贝对象时的一些编译器优化 初始化列表 …

Kibana 的安装

1. 简介 Kibana 是一个开源的分析与可视化平台&#xff0c;可以用 Kibana 搜索、查看存放在 Elasticsearch 中的数据&#xff0c;就跟谷歌的 elasticsearch head 插件类似&#xff0c;但 Kibana 与 Elasticsearch 的交互方式是各种不同的图表、表格、地图等&#xff0c;直观的…

超稳定ChatGPT镜像网站,小白适用,赶紧收藏【持续更新中】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

Redis修炼 (15. redis的持久化-RDB)

RDB 就是 redis database backup file 数据备份文件 就是把内存中的所有数据都保存在磁盘中。 save 注意这个保存过程是主进程在做 因为redis 是单线程 所有其他所有请求都会被卡死。 bgsave 这个稍微友好一点 是子进程 执行&#xff0c;避免主进程收到影响。 redis在服务停机…

母亲节快到了,祝所有母亲节日快乐!Happy Mother‘s Day

《游子吟》唐孟郊 慈母手中线&#xff0c;游子身上衣。 临行密密缝&#xff0c;意恐迟迟归。 谁言寸草心&#xff0c;报得三春晖。 My kind mother has a needle and thread in her hand,Making new clothes for her son who is to travel far away. She is busy sewing c…

【Pandas与SQL系列】Pandas实现分布函数percent_rank、cume_dist

目录 1&#xff0c;分布函数,1.1&#xff0c;percent_rank()1.2&#xff0c;cume_dist()1.3 SQL例子 2&#xff0c;Pandas 实现3&#xff0c;补充Pandas实现排序 1&#xff0c;分布函数, 应用场景&#xff1a;快速查看某个记录所归属的组内的比例 分布函数分类及基础语法&…

Kali-linux系统指纹识别

现在一些便携式计算机操作系统使用指纹识别来验证密码进行登录。指纹识别是识别系统的一个典型模式&#xff0c;包括指纹图像获取、处理、特征提取和对等模块。如果要做渗透测试&#xff0c;需要了解要渗透测试的操作系统的类型才可以。本节将介绍使用Nmap工具测试正在运行的主…

图像处理:高斯滤波算法

目录 前言 概念介绍 基本原理 卷积核的大小 卷积核的形状和权重比 卷积核的归一化 结论 Opencv实现高斯滤波 Python手写实现高斯滤波 参考文章 前言 在此之前&#xff0c;我曾在此篇中推导过图像处理&#xff1a;推导五种滤波算法&#xff08;均值、中值、高斯、双边…

springboot+jsp乡村中小学校园网站建设

随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;乡村小学校园网当然也不能排除在外&#xff0c;从校园概况、学校风采、招生信息的统计和分析&#xff0c;在过程中会产生大量的…