探索C++中的不变之美:const与构造函数的深度剖析

news2024/11/26 5:56:41

W...Y的主页😊

代码仓库分享💕 


🍔前言:

关于C++的博客中,我们已经了解了六个默认函数中的四个,分别是构造函数、析构函数、拷贝构造函数以及函数的重载。但是这些函数都是有返回值与参数的。提到参数与返回值我们就会想到可以修饰它们的一个关键字const。而且关于构造函数,我们并没有将内容全部讲完,所以我们今天这篇博客就是对const关键字的讲解以及构造函数的补充!话不多说,我们直接开始。

目录

const成员

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

再谈构造函数

初始化列表


const成员

const对于我们有语言基础的人并不陌生,就是关于修饰变量使其成为一个不可修改的内容。在C++中也是如此,但是C++中类的出现,伴随的出现的就是一系列的成员函数,而被const修饰的成员函数就是const成员函数。

我们来看一下这段代码:

class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}

private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022,1,13);
d1.Print();
const Date d2(2022,1,13);
d2.Print();
}

上述代码是有错的,在编译器编译时就会出现

这是为什么呢?当我们使用const修饰d2时,d2的类型就是const Date类型,而我们去调用print函数去打印时,print隐藏的函数参数其实是Date* const this,所以参数不匹配导致程序报错。

那this指针的参数应该怎么是隐藏的,所以C++规定在函数后加上const的实际意义就是在this指针前加const。

所以正确的print函数应该在函数后加上const进行修饰。 

void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}

 这样无论在自定义类型的前面是否加上const进行修饰,都可以对上述函数进行调用。所以在调用时,我们可以将一个变量的权限放小,但是绝不能进行放大。

随之又会引出一个问题:成员函数有const进行修饰,无论实参有无const都能进行调用,那我们需不需要将所以的成员函数都加上const呢?

其实是不用的,我们加上const进行修饰的this指针指向的内容不被修改,如果我们的成员函数需要修改this指针所指向的内容,我们就不用去加const。

Date operator++(int);
Date& operator+=(int day);
Date& operator-=(int day);
Date& operator++();
Date& operator--();
Date operator--(int);

比如上述的运算符重载就不用加const,因为这些都是改变this指向的内容的。

bool Date::operator>(const Date& y) 
{
	if (_year > y._year)
	{
		return true;
	}
	else if (_year == y._year && _month > y._month)
	{
		return true;
	}
	else if (_year == y._year && _month == y._month && _day > y._day)
	{
		return true;
	}

	return false;
}
int main()
{
Date s1();
const Date s2();
s1 < s2;//正确
s2 < s1;//报错
return 0;
}

 上述代码是<的运算符重载,在之前的博客中我们已经进行了复现,但是当我们的参数类型一个被const修饰,另一个没有const修饰当我们调用此函数s2 < s1时就会出现报错,因为不能将实参的权限进行放大,也就是参数类型不匹配,所以这种类似内容的函数就必须加上const进行修饰。

void Print() const;
bool operator==(const Date& y) const;
bool operator!=(const Date& y) const;
bool operator>(const Date& y) const;
bool operator<(const Date& y) const;
bool operator>=(const Date& y) const;
bool operator<=(const Date& y) const;
int operator-(const Date& d) const;
Date operator+(int day) const;
Date operator-(int day) const;

总结:

1.能定义const的成员函数都应该定义成const,这样const成员与非const成员都可以进行调用。调用条件(权限平移)(权限缩小)。

2.要修改成员变量的函数不能定义const。

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

取地址操作运算符重载也是六大默认函数之一,通过重定义对对象进行取地址操作就是取地址操作符的重载。这两个默认成员函数一般不用重新定义 ,编译器默认会生成。为什么会是两个呢?因为有无const是有区别的,他们会形成函数重载。

Date* operator&()
{
	cout << "Date* operator&()" << endl;

	return this;
	
}

const Date* operator&()const
{
	cout << "const Date* operator&()const" << endl;

	return this;
	
}
int main()
{
	// const对象和非const对象都可以调用const成员函数
	const Date d1(2023, 10, 31);
	d1.Print();

	Date d2(2023, 1, 1);
	d2.Print();

	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

这里许多人就会有疑问,这里不会产生二义性吗?针对cout << &d2 << endl;因为d2没有被const修饰,所以既可以调用理论上来说两个函数都可以进行调用。但是C++会优先匹配最合适的类型,因为d2没有被const进行修饰,所以优先会调用没有被const修饰的函数。

如果将没有const修饰的函数进行屏蔽,两种实参照样可以进行调用。

再谈构造函数

之前我们就讲过构造函数已经将了有80%了,现在我们将构造函数中剩下的20%进行收尾。我们先来复习一下之前的构造体系:

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

这是函数体内初始化,我们进行对象的初始化时就会调用此函数,当我们没有构造函数时,我们就会调用C++提供的默认构造函数进行匹配。

构造函数的特征:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量
的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始
化一次,而构造函数体内可以多次赋值。

现在我们还有一种可以初始化的办法:

 Date(int year, int month, int day)
         :_year(year)
         ,_month(month)         
         ,_day(day)
         ,_ref(year)
         ,_n(1)
     {
       // 初始化列表
     }

这样的初始化我们称之为初始化列表。

初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。

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

这样写我们照样可以进行初始化。这两种方法都可以进行初始化,他们的区别在哪呢?

上述的例子使用两种初始化都可以,但是有些成员变量就只能使用初始化列表进行初始化。因为在类中私有成员都只是声明,没有开辟空间,而特殊的成员变量只能在定义的时候进行赋值,比如:引用、const修饰……所以我们要在初始化列表进行定义。

在内置类型中构造函数将内置类型进行赋随机值,而特殊内置类型只能赋值一次所以不能再被改变,所以我们就要一次性将其赋值好!!!

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
};

所以引用成员变量、const成员变量、自定义类型成员(且该类没有默认构造函数时)都要进行初始化列表赋值。

当我们去定义一种自定义类型时,如果没有对应的构造函数,程序就会报错。所以当我们定义一个类嵌套在另一个类时,在创建类的构造函数时创建成全缺省参数的构造函数。

下面给大家看一个题:

class A
{
public:
  A(int a)
   :_a1(a)
   ,_a2(_a1)
 {}
 
  void Print() {
    cout<<_a1<<" "<<_a2<<endl;
 }
private:
  int _a2;
  int _a1;
};
int main() {
  A aa(1);
  aa.Print();
}
A. 输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

这道题应该选D,这是为什么呢?成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关 ,所以_a2是在私有成员中先声明的,所以在初始化中先定义_a2,因为_a1在后面所以先为随机值,所以_a2为随机值,_a1为1.

最后我们来总结一下初始化列表解决的问题:

1、必须在定义的地方显示初始化  引用  const   没有默认构造自定义成员
2、有些自定义成员想要显示初始化,自己控制
3.   尽量使用初始化列表初始化
4. 构造函数能不能只要初始化列表,不要函数体初始化
答:不能,因为有些初始化或者检查的工作,初始化列表也不能全部搞定

class Stack
{
public:
    Stack(int n = 2)
        :_a((int*)malloc(sizeof(int)* n))
        , _top(0)
        , _capacity(n)
    {
        //...
        //cout << "Stack(int n = 2)" << endl;
        if (_a == nullptr)
        {
            perror("malloc fail");
            exit(-1);
        }

        memset(_a, 0, sizeof(int) * n);
    }

当我们进行动态内存开辟时,我们就需要进行函数内外的配合,因为在初始化列表中不能进行其他操作,而在函数体内可以,为了避免开辟失败,我们需要进行指针的检查,以及其他操作。所以80-100%初始化列表搞定,还有需要用函数体,他们可以混着用


以上就是本次全部内容,感谢大家观看!!!

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

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

相关文章

Spring Security 6.1.x 系列(4)—— 基于过滤器链的源码分析

一、自动配置 在 Spring Security 6.1.x 系列&#xff08;1&#xff09;—— 初识Spring Security 中我们只引入spring-boot-starter-security 依赖&#xff0c;就可以实现登录认证&#xff0c;这些都得益于Spring Boot 的自动配置。 在spring-boot-autoconfigure模块中集成了…

MyBitis自动拼接了LIMIT

1.前言 最近系统在运营的过程中发现一个很奇怪的问题&#xff0c;莫名其妙的SQL语句会被拼接上一小段SQL&#xff0c;但是发现这被拼接的SQL并不是当前这个API所使用的SQL&#xff0c;因此导致select语句出错。 2.排查思路 2.1.第一步 首先我排查了打印日志里面的错误对应的…

Louis 谈 Restaking:去中心化信任的交流电时刻

人际信任是社会资本的主要形态。信任促成协作&#xff08;主要是经济交易&#xff09;&#xff0c;是人类文明的基石。 当全球已有数十亿人接入互联网&#xff0c;协作的物理限制已经消除&#xff0c;但传统的人际信任仍然局限于家族、长期积累的声誉和长期相处形成的私人关系…

【JAVA学习笔记】55 - 集合-Map接口、HashMap类、HashTable类、Properties类、TreeMap类(难点)

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter14/src/com/yinhai/map_ Map接口 一、Map接口的特点&#xff08;难点&#xff09; 难点在于对Node和Entry和EntrySet的关系 注意:这里讲的是JDK8的Map接口特点 Map java 1) Map与Collect…

【Mquant】2、量化平台的选择

文章目录 一、选择因素二、常见的量化平台三、为什么选择VeighNa&#xff1f;四、参考 一、选择因素 功能和工具集&#xff1a;量化平台应该提供丰富的功能和工具集&#xff0c;包括数据分析、策略回测、实时交易等。不同的平台可能有不同的特点和优势&#xff0c;可以根据自己…

【数据库】形式化关系查询语言(一):关系代数Relational Algebra:基本运算、附加关系代数、扩展的关系代数

目录 一、关系代数Relational Algebra 1. 基本运算 a. 选择运算&#xff08;Select Operation&#xff09; b. 投影运算&#xff08;Project Operation&#xff09; 组合 c. 并运算&#xff08;Union Operation&#xff09; d. 集合差运算&#xff08;Set Difference Op…

Vue3.0 reactive与ref :VCA模式

简介 Vue3 最大的一个变动应该就是推出了 CompositionAPI&#xff0c;可以说它受ReactHook 启发而来&#xff1b;它我们编写逻辑更灵活&#xff0c;便于提取公共逻辑&#xff0c;代码的复用率得到了提高&#xff0c;也不用再使用 mixin 担心命名冲突的问题。 ref 与 reactive…

pytorch学习第五篇:NN与CNN代码实例

这篇文章详细介绍了全链接神经网络实现方法,以及卷积的实现方法。最后我们发现,卷积的实现方法与全链接大同小异,因为 torch 为我们做了很多工作,我们来看看这两个有什么区别。 我们使用 torch 框架来实现两种神经网络,来对图形进行分类。 NN 首先我们引入依赖包 impor…

归并排序深度剖析

目录 一、什么是归并排序&#xff1f; 二、归并排序的实现 三、归并排序非递归 一、什么是归并排序&#xff1f; 归并排序是建立在归并操作上的一种有效&#xff0c;稳定 的排序算法&#xff0c;该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型…

stm32 模拟spi

目录 简介 spi物理层 连接方式 框图 协议层&#xff1a; 数据处理 传输模式 模式0 起始和停止信号 发送和接收数据 模式1 模式2 模式3 总结 简介 spi物理层 SPI&#xff08; Serial Peripheral Interface&#xff0c; 串行外设接口&#xff09;是一种全双工同步…

Vlice DM蓝牙5.2双模热插拔PCB

键盘使用说明索引&#xff08;均为出厂默认值&#xff09; 软件支持&#xff08;驱动的详细使用帮助&#xff09;一些常见问题解答&#xff08;FAQ&#xff09;首次使用步骤蓝牙配对规则&#xff08;重要&#xff09;蓝牙和USB切换键盘默认层默认触发层0的FN键配置的功能默认功…

【51单片机】LED与独立按键(学习笔记)

一、点亮一个LED 1、LED介绍 LED&#xff1a;发光二极管 补&#xff1a;电阻读数 102 > 10 00 1k 473 > 47 000 2、Keil的使用 1、新建工程&#xff1a;Project > New Project Ctrl Shift N &#xff1a;新建文件夹 2、选型号&#xff1a;Atmel-AT89C52 3、xxx…

【Linux】Linux网络总结图详解

网络是进行分层管理的应用层HTTPHTPPS 传输层&#xff08;UDP、TCP&#xff09;UDPTCPTCP和UDP对比 网络层IP 数据链路层&#xff08;MAC&#xff09;/物理层&#xff08;以太网&#xff09;以太网通信&#xff08;负责网卡之间&#xff09; 网络是进行分层管理的 应用层 HTTP…

幂等性设计,及案例分析

一、redis锁处理幂等性失效 上面代码中&#xff0c;锁起不了作用&#xff1b; ——count方法&#xff0c;和insert方法在同一事务中&#xff0c;事务中包含锁&#xff0c;锁没有作用&#xff0c;锁的范围内&#xff0c;事务没提交&#xff0c;但释放锁后&#xff0c;事务提交前…

云安全与容器安全: 探讨在云环境和容器化应用中如何保护数据和工作负载的安全。

在当今数字化时代&#xff0c;云计算和容器化应用已经成为了企业业务的主要组成部分。这两项技术的普及&#xff0c;极大地提高了开发和部署的效率&#xff0c;但也带来了新的安全挑战。在本文中&#xff0c;我们将探讨云安全和容器安全的重要性&#xff0c;以及如何有效地保护…

WordPress外链页面安全跳转插件

老白博客我参照csdn和腾讯云的外链跳转页面&#xff0c;写了一个WordPress外链安全跳转插件&#xff1a;给网站所有第三方链接添加nofollow标签和重定向功能&#xff0c;提高网站安全性。插件包括两个样式&#xff0c;由于涉及到的css不太一样&#xff0c;所以分别写了两个版本…

Spring MVC (Next-1)

1.Restful请求 restFul是符合rest架构风格的网络API接口,完全承认Http是用于标识资源。restFul URL是面向资源的&#xff0c;可以唯一标识和定位资源。 对于该URL标识的资源做何种操作是由Http方法决定的。 rest请求方法有4种&#xff0c;包括get,post,put,delete.分别对应获取…

低代码可视化逻辑编排工具:JNPF

目录 Intro 一、是什么&#xff1f; 提供自动化的解决方案 二、为什么受欢迎&#xff1f; JNPF自身特点——安全、方便、高效、低耗 对于企业&#xff0c;更“安全” 成本“最低”&#xff0c;效率“最高” 三、JNPF开发平台功能展示 技术介绍 参考地址 近几年&#xff0c;随着…

Tomcat下载地址(详细)

Apache Tomcat - Apache Tomcat 8 Software Downloadshttps://tomcat.apache.org/download-80.cgi2.找到Archives 3.选择下载的把版本 4.选择具体下载那个版本 5. 6.一般选择tar.gz结尾的压缩包

飞桨国际化应用案例:挪威广告企业Adevinta应用PaddleOCR提质增效

Adevinta&#xff0c;位于挪威奥斯陆的跨国在线分类广告公司&#xff0c;以其全球市场的图像处理API为特色。Adevinta的主要使命是构建全球买家和卖家之间的桥梁&#xff0c;其在线市场运营覆盖11个国家&#xff0c;拥有众多备受信任的品牌&#xff0c;如荷兰的marktplaats、德…