C++——类和对象(4):取地址及const取地址操作符重载、初始化列表

2.5 取地址及const取地址操作符重载

2.5.1 const成员

        在介绍取地址和const取地址操作符重载前,需要先介绍一下const成员作为基础。在C++中,我们将使用const修饰的成员函数称为const成员函数。const看似修饰成员函数,实际则是对成员函数隐含的this指针进行修饰,被const修饰的this指针就不可以解引用改变值了,即该成员函数不能对类的成员做修改。

        由于this指针不能显式写出,所以const就加在函数之后来表示修饰。

void Print2() const	
{cout << _year << endl;
}
//void Print2(const Date* this)
//{
//	cout << _year << endl;
//}

 const成员的说明

①因为函数内修改成员变量都是通过this指针完成的,所以当this被加上const限制,就说明该函数无法对成员变量做任何修改。

②注意调用传参情况下权限问题。权限的缩小和平移都是允许的,而权限的放大(由const变为非const)是不被允许的。所以const修饰的对象不可以调用非const修饰的成员函数。同理,const修饰的成员函数内也不允许调用非const修饰的成员函数。

③成员函数是否需要const修饰取决于其是否需要在函数内进行写操作。如果成员函数涉及到成员变量的写操作,则不可以加const修饰;如果只涉及到成员变量的读访问,则建议加const修饰。​​​​​​

class Date
{
private:int _year;
public:Date(int year){_year = year;}//如果成员函数涉及到成员变量的写操作,则不可以加const修饰
//如果成员函数只涉及到成员变量的读访问,则建议加const修饰void Print1(){cout << _year << endl;this->Print2();	//非const修饰的成员函数调用const修饰成员函数,this指针由非const变为const,权限缩小}void Print2() const	//const修饰this指针{cout << _year << endl;//this->Print1();	//error:const修饰的成员函数this指针是const,调用非const修饰成员函数会使this指针权限放大}
};int main()
{const Date d1(2022);Date d2(2023);//d1.Print1();	//error:(&d1)const Date* -> (this)Date*,权限的放大 d1.Print2();	// (&d1)const Date* -> (this)const Date*,权限的平移 d2.Print1();	// (&d2)Date* -> (this)Date*,权限的平移 d2.Print2();	// (&d2)Date* -> (this)Date*,权限的缩小 
}

2.5.2 取地址及const取地址操作符重载

        取地址操作符重载和const取地址操作符重载是类的6个默认成员函数的最后两个,因为重载了取地址操作符,所以其功能就是取出对象的地址。

        因为是默认成员函数,所以当我们不实现时,编译器会自动生成。编译器自动生成的取地址操作符重载也是返回对象的地址。所以一般情况下,没有对取地址操作符有特殊要求,那么可以不用写,使用编译器默认生成的即可。

class Date
{
public://取地址及const取地址操作符重载//一般不用自己写,采取编译器默认生成的,编译器生成的默认成员函数也是返回对象变量的地址//特殊情况下才需要自己生成Date* operator&(){return this;}const Date* operator&()const{return this;}
};
int main()
{Date d1;Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}

到此为止,我们便对六个默认成员函数进行了细致的解析,当然还有许多我们没有涉及到的细节点。在对类和成员函数有了大致认识后,接下来我们再来深入了解一下类中所涉及到的细节。

3. 构造函数——初始化列表

        在最初介绍构造函数的时候,我们曾说对象在定义时会自动调用构造函数,并在构造函数内完成定义与初始化。这句话不能算是很精确,因为成员变量的定义实际并不是在构造函数体内完成的,而是在初始化列表中完成定义与初始化的。

 初始化列表的说明

①初始化列表位于构造函数名之后,以一个冒号开始,接着是以逗号分隔的数据成员列表,每个成员变量后使用放在括号中的初始值或表达式来初始化。

class Date	
{
private:int _year;int _month;int _day;
public://初始化列表Date(int year,int month,int day):_year(year),_month(month),_day(day){}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};
int main()
{Date d1(2003,12,4);d1.Print();
}

 ②首先理清定义发生的逻辑:列在类中的成员变量属于成员变量的声明,还没有定义。成员变量的定义发生在对象实例化后,调用了对象的构造函数,构造函数会先执行初始化列表,再执行函数体。而成员变量的定义正是发生在初始化列表中。

③初始化列表是成员变量完成定义的地方,所有成员变量无一例外都在也只能在初始化列表定义,尽管有的成员变量没有在初始化列表显式写出,定义和初始化依旧会发生。对于定义后初始化的值,(1)首先取决于初始化列表中成员变量之后的括号中的值,如果没有给出,那么(2)考虑缺省值,初始化列表初始化的缺省值在成员变量的声明处给出。如果以上两步均没有成功初始化,那么便会(3)初始化为随机值

④所有变量都在初始化列表完成了定义与初始化,且在初始化列表每个成员变量只能出现一次(只能定义并初始化一次)。在构造函数体内部,则是修改赋值的部分,成员变量在此处可以多次修改赋值。

⑤需要区分清楚,在构造函数中涉及到两组缺省值。第一组是构造函数的参数缺省值,其作用就是参数缺省值的作用,当实参数量不够时才采用该缺省值赋值给形参。第二组是声明处给定的缺省值,其作用是为初始化列表提供初始化的缺省值,如果初始化列表没有给定初始化值,便会使用该缺省值初始化。

class Date	
{
private://此部分属于成员变量的声明,还没有定义//此处所写的缺省值是提供给初始化列表的,是成员变量定义的初始化值int _year = 1999;int _month = 10;int _day;const int _n;public://在构造函数中,初始化列表部分才是每个成员变量定义初始化的位置Date(int year,int month,int day)	//构造函数参数缺省值和声明给定初始化缺省值不冲突,前者在调用构造函数传参缺省时才会启用,后者在初始化列表缺省时启用//此处虽然看似只定义了_n和_month并初始化,实际上所以成员变量都在此定义了:_n(1),_month(1)	//没有给出_year成员的初始化值,采取缺省值,初始化为1999//给出了_month成员的初始化值,直接初始化为1//没有给出_day成员的初始化值,且_day成员没有缺省值,则初始化为随机值//给出了_n成员的初始化值,直接初始化为1//在构造函数的函数体内就不再是定义初始化了,而是赋值修改{_year = year;_month = month;_day = day;//_n = 1;	//error:定义不发生在构造函数函数体,所以此处认为是赋值,给const赋值所以报错}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};
int main()
{//在对象实例化的时候定义了对象整体,而对象的成员定义则要通过调用构造函数来定义Date d1(2003,12,4);d1.Print();
}

⑥在定义成员变量时,建议能用初始化列表,就用初始化列表,因为初始化列表是不可避免会执行的,所以建议尽量多使用初始化列表。

⑦在定义时必须初始化的变量必须使用初始化列表(与其说必须使用,不如说必须显示写出,因为要被人为初始化就必须显式写出):const修饰的成员变量引用成员变量自定义类型成员(且不存在默认构造函数时)。这三种都是需要在定义时就完成初始化的,所以需要必须要在初始化列表显式写出。

        重点解释一下不存在默认构造函数的自定义成员。

        首先明确默认构造函数的定义:不需要传参的构造函数就是默认构造函数,编译器自动生成的构造函数的类型就是默认构造函数。

        如果类的成员变量中有自定义类型成员,那么在初始化列表定义时,当定义到该自定义类型成员时,会调用它的构造函数。如果该自定义类型存在默认构造函数,那么就说明无需传参也可以调用构造函数,因此不需要手动完成,可以不在初始化列表显式定义。但是如果自定义类型中没有默认构造函数,即构造函数需要手动传参,那么就必须在初始化列表显式定义了。

        总结一下,自定义对象是一定会在初始化列表定义的,初始化时调用其自身的构造函数。而是否存在默认构造函数,只决定是否允许不在初始化列表显式写出定义。

class A
{
public:A(int a){}
};
class C
{
public:C(int a = 1){}
};
class B
{
private:int _b;const int _n;int& _ref;A _aa;C _cc;
public:B(int b, int n, int& ref):_b(b),_n(n),_ref(ref),_aa(3)	//A类因为实现了需要传参的构造函数,所以对A类对象初始化需要手动传参,所以需要在初始化列表显式定义//C类因为存在默认构造函数(不需要传参的构造函数),所以无需手动操作,可以不在初始化列表显式定义//类对象是一定会在初始化列表定义的,初始化时调用其自身的构造函数//而是否存在默认构造函数,只决定是否允许不在初始化列表显式写出定义{}
};
int main()
{int num;B b1(1, 2, num);
}

 ⑧初始化列表的初始化值只限制是可以赋值的右值即可。

class A
{
private:int* p = nullptr;
public:A():p((int*)malloc(sizeof(int) * 10))//x(y) 相当于是 x=y,同理此处是p=(int*)malloc(sizeof(int) * 10),括号内应该是一个值,缺省值同理{if (p == NULL){perror("malloc fail");}}
};

⑨以下介绍另一个构造函数的特性:单参数(或只有第一个参数无缺省值)构造函数支持隐式类型转换

        如果类对象的构造函数仅有一个参数,那么就支持隐式类型转换。例如如下代码,当写下A a2 = 2; 的代码后,由于A类的构造函数是单参数,所以赋值号右侧的2就会发生隐式类型转换,将类型变为A类的对象类型,这和使用强制类型转换 (A)2; 的效果一致。此时2变成了A类的临时对象(因为发生了隐式类型转换所以是临时的),在赋值给a2,就是调用了拷贝构造将该临时对象拷贝给了a2。

class A
{
private:int _a;
public:A(int a):_a(a){}
};
class B
{
private:A _aa = 3;//由于单参数构造函数支持隐式类型转换,便可以顺利给出类对象缺省值
};
int main()
{A a1(1);//单参数(或只有第一个参数无缺省值)构造函数支持隐式类型转换A a2 = 2;	//A a2 = (A)2;//构造函数参数需要int,所以实际是2先构造了一个A类的临时对象,再将这个临时对象通过调用拷贝构造给a2//实际执行时,同一个表达式连续步骤的构造编译器会进行优化,此处将最后的拷贝构造优化掉了const A& ra1 = 3;	//3隐式转换为A类型的临时对象,具有常性从而可以被常引用引用//A& ra2 = 4;	//error:发生隐式转换后的临时对象具有常性,权限缩小
}

        在C++11中规定,支持了多参数隐式类型转换。

class A
{
public:A(int a,int b){}
};
int main()
{A a1 = { 1,2 };//多参数隐式类型转换 C++11标准规定
}

explicit关键字修饰构造函数,可以禁止上一点中介绍的类型转换。

class A
{
public:A(int a,int b){}
};
int main()
{A a1 = { 1,2 };//多参数隐式类型转换 C++11标准规定
}

⑪构造函数初始化列表中定义成员变量的顺序是按照成员变量声明的顺序进行的,而不是初始化列表的顺序。

class A
{
private:int _a2;int _a1;
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
};
int main() {A aa(1);aa.Print(); //输出:1 随机值//规定初始化顺序按照声明顺序初始化,并不是按照初始化列表顺序
}

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

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

相关文章

AHU 数据库 实验五

【实验名称】 实验5 数据库的数据更新与视图管理 【实验目的】 1. 熟悉数据更新操作的概念与操作类型&#xff1b; 2. 熟练掌握INSERT、UPDATE、DELETE语句的基本语法&#xff1b; 3. 熟练运用INSERT、UPDATE、DELETE语句实现数据的插入、修改与删除…

Docker一键部署WordPress

使用Docker安装WordPress相对传统安装方式更加便捷高效&#xff0c;因为它可以快速创建一个包含所有必要组件&#xff08;Web服务器、PHP和MySQL数据库&#xff09;的独立容器环境。下面是一个简化的步骤说明如何使用Docker和Docker Compose安装WordPress&#xff1a; 一 安装…

JavaScript 立即调用函式 IIFE (Immediately Invoked Function Expression) 是什么?优缺点是什么?

立即调用函式 IIFE (Immediately Invoked Function Expression) 是什么? JavaScript 中的立即调用函式 (IIFE,Immediately Invoked Function Expression),指的是一种在定义时立即执行的匿名函式,通常用于创建一个局部作用域,避免全局污染。 语法格式如下: (function (…

CentOS/Fedora/Ubuntu/Debian 系统 wget 命令

wget 是云服务器安装环境和面板常用下载命令。下载软件或从远程服务器下载备份到本地服务器&#xff0c;也可以使用 wget 把文件下载到云服务器上。 VPS wget 命令最常用使用方法如下&#xff1a; 安装 wget 一般来说 wget 命令是系统自带的&#xff0c;方面安装环境和面板&…

多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测

多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测 目录 多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.多维时序 …

族群争霸休闲养成小游戏

​游戏概述&#xff1a; 在一个由自然力量支配的幻想世界中&#xff0c;狼族与羊族的战争永无止境。 人族在两者之间寻求和平&#xff0c;建立起坚固的城墙&#xff0c;同时捕捉狼与羊来增强自身实力。 神族则在幕后观察&#xff0c;偶尔以神技介入战场&#xff0c;影响战局…

保障数据安全,提升性能:探秘Redis AOF持久化机制在在线购物网站的应用

AOF&#xff08;Append-Only File&#xff09;日志介绍 Redis使用AOF持久化来保证数据的可靠性。AOF日志是一个追加写文件&#xff0c;记录了所有对Redis数据进行修改的命令。 AOF的常规用途 通常&#xff0c;人们将Redis的AOF用于将后端数据库中的数据存储在内存中&#xf…

复盘-word

word-大学生网络创业交流会 设置段落&#xff0c;段后行距才有分 word-选中左边几行字进行操作 按住alt键进行选中 word复制excel随excel改变&#xff08;选择性粘贴&#xff09; 页边距为普通页边距定义 ##### word 在内容控件里面填文字&#xff08;调属性&#xff09…

JavaScript使用

文章目录 一、JavaScript简介二、JavaScript引入方式1、内部脚本2、外部脚本 三、JavaScript基础语法1、书写语法&输出语句2、变量&数据类型3、运算符4、流程控制语句&函数 四、JavaScript对象1、Array2、String3、自定义对象 五、BOM1、Window2、History3、Locati…

扩展CArray类,增加Contain函数

CArray不包含查找类的函数&#xff0c;使用不便。考虑扩展CArray类&#xff0c;增加Contain函数&#xff0c;通过回调函数暴露数组元素的比较方法&#xff0c;由外部定义。该方法相对重载数组元素的“”符号更加灵活&#xff0c;可以根据需要配置不同的回调函数进行比较 //类型…

C语言————字符函数与字符串函数

在编程的过程中&#xff0c;我们经常要处理字符和字符串&#xff0c;为了⽅便操作字符和字符串&#xff0c;C语⾔标准库中提供了⼀系列库函数&#xff0c;如追加&#xff0c;拷贝&#xff0c;替换等等接下来我们就学习⼀下这些函数&#xff0c;并且自实现。 gets 这个指令大家…

十七、enumerate函数的用法

enumerate() 函数是 Python 内置函数之一&#xff0c;用于同时返回可迭代对象的索引和对应的值。 它的语法结构如下&#xff1a; enumerate(iterable, start0) iterable: 表示一个可迭代的对象&#xff0c;如列表、元组、字符串等。start: 可选参数&#xff0c;表示索引起始…

node mysql 同时向两张表插入数据

方法一 在Node.js中使用MySQL模块&#xff0c;如果需要同时向两张关联的表插入数据,通常不会通过一条SQL语句来完成&#xff0c;因为MySQL本身不支持一次插入操作跨多个表。但是&#xff0c;可以采用事务&#xff08;Transaction&#xff09;的方式来保证两个插入操作的原子性…

校招春招秋招,HR是如何筛选简历的?

一份简历在HR的眼中最多能停留15秒钟。 如果15秒内HR没有决定要通知你做在线测评&#xff0c;那么这事就算过去了。 那么问题来了&#xff0c;如果在15秒内&#xff0c;让HR对你产生兴趣&#xff1f; 1、简历布局 人在浏览信息的时候&#xff0c;习惯性的是从上往下&…

[数据集][目标检测]光伏板太阳能板缺陷检测数据集VOC+YOLO格式2400张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2400 标注数量(xml文件个数)&#xff1a;2400 标注数量(txt文件个数)&#xff1a;2400 标注…

The display: inline property prevents width from having an effect.

The display: inline property prevents width from having an effect. Try setting display to something other than inline. ---------------------------------- display: inline 不支持元素的 width 或 height 属性&#xff0c;若要元素 width 或 height 生效&#xff0c;…

全网上线 IP 归属地功能,一文教你如何实现

细心的朋友们可能已经发现了&#xff0c;先在抖音、知乎、快手、小红书等这些平台已经上线了“网络用户显示 IP 的功能”&#xff0c;境外用户显示的是国家&#xff0c;国内的用户显示的省份&#xff0c;而且此项显示无法关闭&#xff0c;归属地强制显示。 1获取用户 IP 地址 …

LLM 推理优化探微 (2) :Transformer 模型 KV 缓存技术详解

编者按&#xff1a;随着 LLM 赋能越来越多需要实时决策和响应的应用场景&#xff0c;以及用户体验不佳、成本过高、资源受限等问题的出现&#xff0c;大模型高效推理已成为一个重要的研究课题。为此&#xff0c;Baihai IDP 推出 Pierre Lienhart 的系列文章&#xff0c;从多个维…

倒序排列的基本概念和应用场景

倒序排列的基本概念 倒序排列&#xff0c;也称为降序排列&#xff0c;是一种数据组织形式&#xff0c;其中数据按照从大到小的顺序排列。这与升序排列相反&#xff0c;升序排列是按照从小到大的顺序来组织数据。在编程、数据库管理和数据分析等领域中&#xff0c;倒序排列是一种…

企业如何安全参与开源项目?

【开源三句半】 企业参与开源潮&#xff0c; 安全创新都重要&#xff0c; 持续投入不可少&#xff0c; 眼光独到。 开源已经成为构建现代软件的常见方式&#xff0c;这不仅局限于IT技术本身&#xff0c;更推动了多个行业的数字化发展。企业决定引入开源项目打造商业软件时&…