C++类和对象再探

news2024/9/20 9:20:48

文章目录

  • 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都不行。
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

【Java EE 初阶】JUC常见工具类介绍

目录 1.JUC 1.Callable接口&#xff08;描述线程任务的接口&#xff09; 2.Callable接口和Runnable接口区别 2.Reentrant Lock 1.演示基本方法 2.出现异常&#xff0c;如何确保释放锁 3.创建一个公平锁 4.创建一个读写锁 3.Reentrant Lock和Synchronized的区别 4.原子…

( 动态规划) 509. 斐波那契数 ——【Leetcode每日一题】

❓509. 斐波那契数 难度&#xff1a;简单 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(…

HTTPS如何防止DNS欺骗?

HTTPS加密可以有效帮助服务器应对DNS欺骗、DNS劫持、ARP攻击等安全威胁。DNS是什么&#xff1f;DNS如何被利用&#xff1f;HTTPS如何防止DNS欺骗&#xff1f; DNS如何工作&#xff1f; 如果您想访问www.example.com&#xff0c;您的浏览器需要找到该特定Web服务器的IP地址。它…

入行3年,月薪不足20K,看来是时候跳一次了.....

写在前面的话 不知不觉已经毕业两年半&#xff0c;工作三年了在同一家公司并未跳槽&#xff0c;今年在合适的契机选择了换一家公司&#xff0c;在这里想总结一下三年的工作经验和这次的面试经验。 我毕业于一个本科院校&#xff0c;二本&#xff0c;学习网络工程出来的。到大…

加拿大访学/博后的子女选择公立学校及办理入学手续详解

访问学者及博士后申请者携子女出国接受国外教育是较普遍的现象。近年来由于美国对CSC资助学者的入境限制&#xff0c;越来越多的申请者将目标定为加拿大等英语发达国家。在咨询中&#xff0c;经常有客户问及加拿大孩子入学问题。为此&#xff0c;知识人网小编就谈谈如何选择公立…

jQuery-层级选择器

<!DOCTYPE HTML> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <title>层级选择器</title> <style type"text/css"> …

如何通过边缘智能网关实现暴雨灾害监测预警

随着夏季来临&#xff0c;我国南方逐渐进入雨季&#xff0c;暴雨灾害也进入到频发阶段&#xff0c;给村镇和城市居民都造成诸多人身和财产损失。针对南方雨季的水灾防治&#xff0c;物联网技术派上大用场&#xff0c;本篇就基于边缘智能网关的数采方案&#xff0c;简单介绍对暴…

圣杯布局实现

是什么&#xff1f; 【圣杯布局是两边固定宽度&#xff0c;中间自适应的三栏布局】 圣杯布局是网页排版布局中的一种常见方式&#xff0c;通常用于具有两个侧边栏和一个中间内容区域的网页设计。 这种布局将整个页面区域分为三个部分&#xff1a;顶部导航栏、左侧边栏、右侧…

IDEA直接请求controller,不用postman请求http接口

generated-requests.http工具用法 第一步&#xff1a;点击下面按钮&#xff0c;HTTP Client 第二步、生成generated-requests.http文件 第三步、更改服务的ip和端口&#xff0c;启动服务 请求实例&#xff1a; 1、post请求&#xff0c;body传参&#xff1a; POST http://loc…

新库上线 | CnOpenData·A股上市公司违规处罚数据

A股上市公司违规处罚数据 一、数据简介 据《上市公司信息披露管理办法》&#xff0c;上市公司作为信息披露义务人&#xff0c;应真实、准确、及时、完整地向市场公开依法及自愿披露的信息。这些公开披露的信息包含但不仅限于公司基本情况、主要会计数据和财务指标、股东持股情…

markdown数据转换,处理html2canvas+jsPDF下载后文字截断问题(记录)

声明&#xff1a;此篇文章并不是最优解决办法&#xff0c;下载pdf这一步主要参考睡衣大佬提供的思路和代码&#xff0c;个人在此基础上进行细微修改处理段落文字截断&#xff0c;勉强实现不截断文字效果&#xff0c;但也有诸多限制和不足。 原文引路&#xff1a;https://blog.c…

软件测试基础面试题大全(下)

11. 一个输入框&#xff0c;如何编写测试用例&#xff1f; 字符型输入框 1. 字符型输入框&#xff1a;英文全角、英文半角、数字、空或者空格、特殊字符“~&#xff01;#&#xffe5;%……&*&#xff1f;[]{}”特别要注意单引号和&符号。禁止直接输入特殊字符时&…

nodejs安装及配置过程

目录 如何查看自己电脑中有没有下载nodejs 请问应该如何更新nodejs呢&#xff1f; 为什么称nodejs为vue脚手架呢&#xff1f; 如何打开系统变量呢 C:\Users\abner>npm config set registry https: registry.npm.taobao.org npm WARN invalid config registry"http…

Mysql 学习(十 二)查询优化 Explain

什么是Explain&#xff1f; 一条查询语句经过Mysql查询优化器的各种基于成本和规则的优化后生成一个所谓的执行计划&#xff0c;而Explain 语句可以让我们知道执行计划的语法&#xff0c;从而我们有针对性的提升性能举例子&#xff1a;EXPLAIN SELECT 1 由此我们得到了一些参数…

Prompt 指南

https://github.com/OleNet/YouPromptMe/tree/gh-pages/you-prompt-me 这是一份如何调整 Prompt 得到更漂亮的图片的经验性文档。结果和经验都来源于文心 ERNIE-ViLG Demo 和社区的资料。 极乐迪斯科里的猫,故障艺术 呼吁与准则 机器生成图片的最终目的还是便捷地为人类创造…

Cyanine7-Hylauronic菁染料CY7标记透明质酸Cy7-Hylauronic

荧光CY7是一种近红外荧光染料&#xff0c;具有高吸收和高荧光发射强度&#xff0c;适用于生物医学研究中的细胞成像和药物传递等领域。而荧光Cy7-Hylauronic透明质酸则是将荧光CY7与透明质酸结合而成的复合物&#xff0c;可以在细胞内或体内被稳定地释放&#xff0c;具有良好的…

【Python入门】Python循环语句(while循环的嵌套应用)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

centos7.5离线安装部署TiDB-6.5.0分布式系统

centos7.5离线安装部署TiDB-6.5.0分布式系统 一、需求&#xff0c;为什么要部署TiDB-6.5.0分布式系统 当前绝大部分企业的业务数据都分散在不同的系统中&#xff0c;没有一个统一的汇总&#xff0c;随着业务的发展&#xff0c;企业的决策层需要了解整个公司的业务状况以便及时…

抖音seo源码保姆式服务搭建|定制产品开发分享

抖音seo霸屏&#xff0c;是一种专为抖音视频创作者和传播者打造的视频批量剪辑&#xff0c;批量分发产品。使用抖音seo霸屏软件&#xff0c;可以帮助用户快速高效的制作出高质量的优质视频。 使用方法&#xff1a;1. 了解用户的行为习惯 2. 充分利用自身资源进行开发 3. 不…

蓝桥杯第十四届青少年Python组省赛试题--第4题

提示信息&#xff1a; 杨辉三角就是一个用数排列起来的三角形&#xff08;如下图&#xff09;&#xff0c;杨辉三角规则如下&#xff1a; 1&#xff09;每行第一个数和最后一个数都为1&#xff0c;其它每个数等于它左上方和右上方的两数之和&#xff1b; 2&#xff09;第n行有n…