C++--深入类和对象(上)

news2024/9/28 22:26:02

引言:

        本篇博客将深入探究C++中的类和对象。我们将从普通高校教学点开始,逐步介绍类的定义、对象的创建和使用,以及类与对象之间的关系。通过详细讲解访问控制和成员函数,我们将揭示封装的重要性以及如何实现数据的隐藏和安全性。

目录

1.引用传值引入与拷贝构造

1.1引用传参防止对象重复析构

1.2 引用缺陷与拷贝构造

1.2.1拷贝构造函数的特征及分析

2.运算符重载

3.const成员

3.1权限的放缩与const成员

3.1.1读写分离函数的重载

3.1.2 const成员与非const成员的调用

3.1.3 拓展:取地址及const取地址操作符重载

4.未完待续......


1.引用传值引入与拷贝构造

1.1引用传参防止对象重复析构

我们先来看一段传值函数的代码:

class A
{
public:
	A(int a = 0)
	{
		cout << "A(int a)" << endl;
		_a = a;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_array);
		_array = nullptr;
		_size = _capacity = 0;
	}
private:
	// 内置类型
	DataType* _array;
	int _capacity;
	int _size;

	// 自定义类型
	A _aa;
};

void func(Stack s)
{

}
int main()
{
	Stack s;
	func(s);
    //类对象二次析构
	return 0;
}

       我们都知道函数的调用是需要创建函数栈帧的,而函数的传值传参传的只是实参的一份拷贝,但是我们的实参中含有在堆中开辟空间的成员,这种成员要进行拷贝,就是我们所谓的“浅拷贝”,相当于将拷贝出来的指针也指向了与实参指向的同一块空间,这样在C中本来没太大的问题,但是在C++的类中,有了析构函数,这种的传值传参就会带来析构两次的错误。

    为此,我们可以采用引用传参的方法,传入一个实参对象的拷贝,这样一来,参数的生存周期就变成了main函数结束后再析构,这样也就解决了问题。

1.2 引用缺陷与拷贝构造

       我们常常会遇到某个函数只是为了当时获得对象的属性,但是我并不想因为调用函数而改变实参从而失去数据或者是某些信息,上面的func函数是引用传参,虽然解决了重复析构的错误问题,但是如果我们要在函数里操作该对象,也可能会导致原实参的改变(含有指针等数据成员),这是我们不愿意看到的,这就要引出我们的C++的又一个默认成员函数-拷贝构造函数,俗称“深拷贝”。

      拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

1.2.1拷贝构造函数的特征及分析

拷贝构造函数也是特殊的成员函数,其特征如下:

     1. 拷贝构造函数是构造函数的一个重载形式。

     2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用

为何不能使用传值传参?

       当拷贝构造函数的参数是对象本身并采用了传值传参的方式时,会导致无穷递归调用的问题。这是因为传值传参会触发拷贝构造函数的调用,而该调用又会触发新的拷贝构造函数的调用,形成了无限循环。

       具体来说,当我们使用对象的值作为参数传递给拷贝构造函数时,编译器会尝试创建一个新的对象,以便在函数内部使用。但是在创建新对象的过程中,会再次调用拷贝构造函数,因为创建新对象需要将原始对象的值拷贝给新对象,这又会触发新一轮的拷贝构造函数调用。这就形成了一个无限循环,导致无穷递归调用,直到内存耗尽或栈溢出。

       为了避免这种无限递归调用,我们通常要么使用对象的引用作为拷贝构造函数的参数,或者避免在拷贝构造函数中使用对象的值作为参数。通过使用引用参数,我们可以避免对象的拷贝,并且只传递对象的引用,从而避免了无限递归调用的问题。

       注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝

       3.若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的

2.运算符重载

这里简要的给出日期类的代码,附有基础的运算符重载的代码,这里不做过多的解释:

#include<iostream>
using namespace std;
class Date
{
public:
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		static int montharr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//加static的作用是防止函数重复调用导致数组一直重新开辟
		if (month==2 &&((year % 100 != 0 && year % 4 == 0) || year % 400 == 0))//闰年而且二月
		{
			return 29;
		}
		return montharr[month];
	}

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 拷贝构造函数
    // d2(d1)
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	// 赋值运算符重载
    // d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}


	// 析构函数
	~Date() {};


	// 日期+=天数
	Date& operator+=(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			++_month;

			if (_month == 13)
			{
				_year++;
				_month = 1;
			}

		}
		return *this;
	}



	// 日期+天数
	Date operator+(int day)//加法运算符this对象不能改变
	{
		Date temp(*this);
		temp += day;
		return temp;
	}


	// 日期-天数
	Date operator-(int day)
	{
		Date temp(*this);

		temp._day -= day;
		while (temp._day <= 0)
		{
			--temp._month;
			if (temp._month == 0)
			{
				temp._year--;
				temp._month = 12;
			}
			temp._day += GetMonthDay(temp._year, temp._month);
		}
		return temp;
	}



	// 日期-=天数

	Date& operator-=(int day)
	{
		*this = *this-day;
		return *this;
	}

	// 前置++
	Date& operator++()//前置++结束后原对象直接改变
	{
		*this += 1;
		return *this;
	}


	// 后置++
	Date operator++(int)
	{
		Date temp(*this);
		(*this) += 1;;
		return temp;
	}



	// 后置--
	Date operator--(int)
	{
		Date temp(*this);
		(*this)-=1;
		return temp;
	}



	// 前置--
	Date& operator--()
	{
		(* this) -= 1;
		return *this;
	}


	// >运算符重载
	bool operator>(const Date& d)
	{
		if (_year != d._year)
			return _year > d._year;
		if (_month != d._month)
			return _month > d._month;
		if (_day != d._day)
			return _day > d._day;
		return false;
	}



	// ==运算符重载
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}



	// >=运算符重载
    bool operator >= (const Date& d)
	{
		return (*this) > d || (*this) == d;
	}



	// <运算符重载
	bool operator < (const Date& d)
	{
		if (_year != d._year)
			return _year < d._year;
		if (_month != d._month)
			return _month < d._month;
		if (_day != d._day)
			return _day < d._day;
		return false;
	}



	// <=运算符重载
	bool operator <= (const Date& d)
	{
		return (*this) < d || (*this) == d;
	}



	// !=运算符重载
	bool operator != (const Date& d)
	{
		return _year != d._year || _month != d._month || _day != d._day;
	}



	// 日期-日期 返回天数
	//法1:
	/*int operator-(const Date& d)
	{
		Date max = *this;
		Date min = d;
		int flag = 1;

		if (*this < d)
		{
			max = d;
			min = *this;
			flag = -1;
		}

		int n = 0;
		while (min != max)
		{
			min++;
			++n;
		}

		return n * flag;
	}
	*/
	int operator-(const Date& d)
	{
		Date temp(*this);
		int ans = 0;
		if (temp < d)
		{
			while (temp != d)
			{
				++temp;
				ans--;
			}
		}
		else
		{
			while (temp != d)
			{
				temp--;
				ans++;
			}

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

int main()
{
	Date d;
	Date d1(2023, 8, 15);
	d.print();
	d1.print();
	Date d2(d1);
	d2.print();
	Date d3 = d1 + 200;
	Date d4(2002, 2, 13);
	d1.print();
	d3.print();
	d4.print();
	cout << d1 - d4 << endl;
	d1 -= 300;
	d1.print();
	
	
	return 0;
}

3.const成员

       将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

3.1权限的放缩与const成员

我们根据上面的日期类,给出一份这样的测试代码,

      解决办法就是将成员函数print以const修饰,但是这种const实际上是修饰的this指针,而this指针在成员函数中优势隐含的,所以,我们规定将const关键字放在函数的参数列表的括号之后,表示修饰this指针为const,这样,像我们上面的调用,只会产生非const对象调用所产生的“权限缩小”和const对象调用所带来的“权限平移”,两者都是被允许的。

3.1.1读写分离函数的重载

     我们以重载数组访问运算符[ ]为例来看,我们知道,[ ]运算符可以重载为输出功能,当然本省也具有修改数组内容的功能,但是,如果我们将其重载为输出函数,那么,这个运算符就不再具备修改数组的值的能力,我们的目的是在重载这个运算符的基础上,不失去原来运算符所具备的功能,为此,我们可以将const修饰的运算符重载函数写入,与原运算符重载函数形成函数重载,

//只读操作,后面的const修饰,代表将传入的this指针权限缩小为const对象,而const引用返回也防止了被返回对象在堆上的数据被修改
const int& operator[](size_t i)const
{
	assert(i < arr.size());
	return arr[i];
}

//读、写操作,能够返回引用,也就是数组在堆上的空间的引用,所以可以对返回的对象进行修改操作
int& operator[](size_t i)
{
	assert(i < arr.size())
	return arr[i];
}

3.1.2 const成员与非const成员的调用

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

      当一个成员函数被声明为const时,它的语义表示该函数不会修改对象的状态。由于非const成员函数可以修改对象的状态,所以在const成员函数中调用非const成员函数可能会破坏const成员函数的语义,属于“权限放大”,是不被允许的。

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

       非const成员函数内可以通过强制类型转换或使用const_cast来绕过const限制,并间接调用const成员函数。也就是“权限缩小”,是被允许的。在设计类的成员函数时,应该根据函数的目的和预期的行为来合理地使用const关键字。

一个函数,在允许的情况下,写成const好还是非const好呢?

      从上面我们可以看出,const修饰的成员函数,非const对象和const对象都可以调用,而非const函数,const对象却不能调用,所以,const成员函数更加有优势,并且可以避免一些修改值的错误产生,但是,const成员函数要求不能对涉及的内部对象进行修改,所以,一般的,只要不涉及修改对象的属性,我们就可以将函数设置为const修饰。

3.1.3 拓展:取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{ 
public :
 Date* operator&()
 {
    return this;
    //return nullptr;
 }
 const Date* operator&()const
 {
    return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

       这样的目的是想让别人取不到有效的地址在函数返回值中,我们可以让别人获取指定的任一地址,属于恶搞行为,实际中一般不使用,了解即可。

4.未完待续......

敬请期待,C++--深入类和对象(下)。

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

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

相关文章

Docker容器:docker基础概述、安装、网络及资源控制

文章目录 一.docker容器概述1.什么是容器2. docker与虚拟机的区别2.1 docker虚拟化产品有哪些及其对比2.2 Docker与虚拟机的区别 3.Docker容器的使用场景4.Docker容器的优点5.Docker 的底层运行原理6.namespace的六项隔离7.Docker核心概念 二.Docker安装 及管理1.安装 Docker1.…

图数据库_Neo4j基于docker服务版安装_Neo4j Desktop桌面版安装---Neo4j图数据库工作笔记0004

然后我们来看看如何用docker来安装Neo4j community server 首先去执行docker pull neo4j:3.5.22-community 去拉取镜像 然后执行命令就可以安装了 可以用docker ps查看一下 看看暴露了哪些端口 然后再看一下访问一下这个时候,要用IP地址了注意 然后再来看一下安装Desktop 去下…

如何在前端实现WebSocket发送和接收UDP消息(多线程模式)

目录 简介&#xff1a;步骤1&#xff1a;创建WebSocket连接步骤2&#xff1a;创建Web Workers步骤3&#xff1a;发送和接收UDP消息&#xff08;多线程模式&#xff09;结束语&#xff1a; 简介&#xff1a; 本文将继续介绍如何在前端应用中利用WebSocket技术发送和接收UDP消息…

数学建模的概念和学习方法(什么是数学建模)

一、初步认识数学建模 数学建模是将数学方法和技巧应用于实际问题的过程。它涉及使用数学模型来描述和分析现实世界中的现象、系统或过程&#xff0c;并通过数学分析和计算来预测、优化或解决问题。数学建模可以应用于各种领域&#xff0c;包括自然科学、工程、经济学、环境科学…

Effective C++学习笔记(8)

目录 条款49&#xff1a;了解new-handler的行为条款50&#xff1a;了解new和delete的合理替换时机条款51&#xff1a;编写new和delete时需固守常规条款52&#xff1a;写了placement new也要写placement delete条款53&#xff1a;不要轻忽编译器的警告条款54&#xff1a;让自己熟…

XenDesktop5.6如何连接数据库

Citrix在数据库的连接方式上一直不统一&#xff0c;但是也还是有迹可循的。 经过了好长时间的下载以后&#xff0c;今天终于有时间来测试一下最新版本的XenDesktop 5 SP1&#xff0c;由于结合了其他组件和环境的需要&#xff0c;所以&#xff0c;选择了独立部署数据库&#xf…

Microsoft 图像BERT,基于大规模图文数据的跨模态预训练

视觉语言任务是当今自然语言处理&#xff08;NLP&#xff09;和计算机视觉领域的热门话题。大多数现有方法都基于预训练模型&#xff0c;这些模型使用后期融合方法融合下游任务的多模态输入。然而&#xff0c;这种方法通常需要在训练期间进行特定的数据注释&#xff0c;并且对于…

[JavaWeb]【一】入门JavaWeb开发总概及HTML、CSS、JavaScript

目录 一 特色 二 收获​编辑 三 什么是web? 四 网站的工作流程 五 web网站的开发模式​编辑 六 web开发课程学习安排 七、初始web前端 八 HTML、CSS 8.1 什么是HTNL\CSS(w3cschool) 8.2 HTML快速入门 8.3 VS Code开发工具 8.3.1 插件 8.3.2 主题&#xff08;改变颜色&…

vue组件封装——类似bootstraptable的模糊搜索功能,支持语音搜索

插件地址 懒得写了&#xff0c;直接上插件地址去看吧

后端返回图片资源错误404,前端使用默认图片

后端返回的图片资源可能会因为各种原因&#xff08;后台误删&#xff0c;地址更改未及时更新&#xff0c;损毁&#xff09;出现无法展示的情况&#xff0c;比如这种报错 就会导致图片资源错误&#xff0c;页面出现这种情况 用户体验很不好&#xff0c;为了改善这种情况&#xf…

从视觉装备到智能驾驶,天准科技能否打造第二增长极?

智能网联汽车已经成为了上市公司跨界布局的热门赛道。 天准科技是工业视觉智能装备领域的龙头企业&#xff0c;主要客户包括苹果、三星等企业。招股说明书显示&#xff0c;2016年至2018年&#xff0c;天准科技来源于苹果公司及其供应商的收入合计占比达到49.98%、67.99%及76.0…

角色入门01----MetaHuman创建角色

创建网址Epic Games 创建完成后&#xff0c;可以在bridge里边下载自己的模型&#xff0c;导入他。我们想用小白人控制他&#xff0c;还要导入第三人称游戏包&#xff0c;把小白人蓝图拿出来。 把小白人的蓝图复制到自己新建的文件夹&#xff0c;把下载好的metaHunmen的骨骼全部…

微服务概述-7

Shiro 框架 Shiro 是一个用于 Java 应用程序的安全框架。它提供了身份验证、授权、加密和会话管理等功能&#xff0c;可以帮助开发人员构建安全可靠的应用程序。 Java 中针对权限管理常见的有 2 个著名的框架&#xff1a;spring security 和 shiro shiro 基本概念 credentia…

ABAP 新语法--Data Processing

1. String Template 新语法引入了字符串模板&#xff0c;用于处理字符串连接以及格式转换 字符串模板在 | … | 之间定义&#xff0c;主要分为两部分&#xff0c;固定文本和变量 其中&#xff0c;变量只能在 { … } 内使用&#xff0c;大括号之外的所有字符均作为固定文本使用…

windows以管理员的身份运行CMD

电脑在装系统的时候&#xff0c;我的用户不是最高权限的管理员。 今天在工作的时候&#xff0c;使用CMD。运行失败&#xff0c;提示我需要使用管理员的身份运行CMD才可以。 使用右键点击左下角的windows图标 选择红框标注的那项。 以普通身份运行&#xff1a; 以管理员身份运行…

【iMessage频發软件苹果群发技术开源原创】当 APNs 发送通知到一个离线设备时,APNs 会把通知存储起来(一定的时间内),当设备上线时再递送给设备。

推荐内容IMESSGAE相关 作者✈️IMEAE推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容3.日历推 *** …

睿趣科技:抖音开网店现在做还来得及吗

随着社交媒体的迅速发展&#xff0c;抖音作为一款短视频平台&#xff0c;已经在年轻人中间取得了巨大的成功。而近年来&#xff0c;越来越多的人开始考虑在抖音上开设网店&#xff0c;以迎合这一潮流。那么&#xff0c;抖音开网店现在还来得及吗? 首先&#xff0c;要明确的是&…

C语言实现哈希搜索算法

一、哈希搜索算法原理 哈希搜索&#xff0c;也叫散列查找&#xff0c;是一种通过哈希表&#xff08;散列表&#xff09;实现快速查找目标元素的算法。哈希搜索算法通常适用于需要快速查找一组数据中是否存在某个元素的场景&#xff0c;其时间复杂度最高为 O(1)&#xff0c;而平…