【C++】--类和对象(2)

news2024/11/25 6:53:10

👌个人主页: 起名字真南
👆个人专栏:【数据结构初阶】 【C语言】 【C++】

请添加图片描述

目录

  • 1 类的默认成员函数
  • 2 构造函数
  • 3 析构函数
  • 4 拷贝构造
  • 5 赋值运算符重载
    • 5.1 运算符重载
    • 5.2 赋值运算符的重载

1 类的默认成员函数

默认成员函数就是用户没有显示实现,编译器自动生成的的成员函数称为默认成员函数

一个类我们不写的情况下会默认生成六个成员函数,分别是构造函数,析构函数,拷贝构造,赋值重载以及关于取地址对普通对象和const对象的重载。
在这里插入图片描述

2 构造函数

构造函数是特殊的成员函数,构造函数的主要任务是在对象实例化时对其进行初始化对象。他的本质就是Stack和Date类中Init函数的功能,而且构造函数可以自动调用的特点就完美的替代了Init函数。

构造函数的特点 :

  1. 函数名和类型名一致
  2. 无返回值(不需要写void )
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载
  5. 如果类中没有定义构造函数那么编译器会自动生成一个无参的构造函数,如果显示定义了就不会自己生成
  6. 无参构造函数,全缺省构造函数,以及我们不写编译器自动生成的构造函数都叫做默认构造函数,并且这三个函数不能同时存在,同时存在虽然无参和全缺省构成了函数的重载但是调用函数时会存在歧义。总结不传实参就可以调用的构造函数统称为默认构造。
  7. 我们不写编译器默认生成的构造函数对内置类型没有影响,具体怎么初始化取决于编译器怎么初始化,但是对于我们用class/struct来创建的自定义类型成员变量就需要调用他的构造函数进行初始化,如果没有构造函数就会报错,我们要初始化成员变量就需要用到初始化列表(后面会解释)
#include<iostream>

using namespace std;
class Date 
{
public:
	//无参构造函数
	/*Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}*/
	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//全缺省构造函数
	/*Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;  //调用无参的构造函数
	d1.Print();
	Date d2(2024, 10, 6); //调用带参的构造函数
	d2.Print();
	return 0;
}

在这里插入图片描述
如果我们将第一个无参构造函数和第三个全缺省构造函数注释掉编译器就会报错显示没有可用的构造函数。

在这里插入图片描述
当我们使用无参的构造函数进行初始化时后面不需要加()否则编译器无法区分这里是函数声明还是实例化对象。

typedef int STLDateType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STLDateType*)malloc(sizeof(STLDateType) * n);
		if (_a == nullptr)
		{
			perror("malloc fail!");
			return;
		}
		_top = 0;
		_capacity = n;
	}
private:
	STLDateType* _a;
	size_t _top;
	size_t _capacity;
};

//两个Stack实现队列
class MyQueue
{
public:
	//编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化
private:
	Stack _STPush;
	Stack _STPop;
};
int main()
{
	MyQueue mq;
	return 0;
}

当一个类类型的成员变量是自定义类型的时候例如上面的代码MyQueue类的成员变量是自定义类型Stack类,所以编译器不会生成MyQueue的默认构造函数需要我们自己来实现,但是为什么我们明明没有写但是还是可以运行,因为编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化。如果我们将Stack的构造函数注释掉就会报错。出现下面这种情况
在这里插入图片描述

3 析构函数

析构函数与构造函数的功能相反,析构函数不是完成对对象本身的销毁比如局部对象创立是在栈帧,如果函数结束那么栈帧就会被销毁,不需要我们去管。C++规定对象在销毁时会自动调用析构函数完成对对象中资源的清理和释放工作。析构函数的功能就类似与我们Stack类中Destory函数的功能,而向Date类没有Destory就不需要析构函数。

析构函数的特点 :

  1. 析构函数的函数名是在类名的前面加上‘~’。
  2. 无参数无返回值和构造函数类似
  3. 一个类只能有一个析构函数,如果未显示定义编译器就会自动生成一个默认析构函数
  4. 函数声明周期结束会自动调用析构函数
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型的成员变量不做处理,自定义类型成员会调用他自己的析构函数
  6. 需要注意的是如果我们写了析构函数但是对于自定义类型还是会调用他自己的析构函数,也就是说自定义类型成员变量无论什么时候都会调用自己的析构函数
  7. 一个类中如果没有资源申请那么使用编译器自己生成的析构函数就可以比如Date类,如果有资源申请像Stack/MyQueue类都需要自己写析构函数,否则会造成资源泄露。
  8. 一个局部域有多个对象,C++规定后定义的先析构。
#include<iostream>

using namespace std;
typedef int STLDateType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STLDateType*)malloc(sizeof(STLDateType) * n);
		if (_a == nullptr)
		{
			perror("malloc fail!");
			return;
		}
		_top = 0;
		_capacity = n;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STLDateType* _a;
	size_t _top;
	size_t _capacity;
};

//两个Stack实现队列
class MyQueue
{
public:
	//编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化
private:
	Stack _STPush;
	Stack _STPop;
};
int main()
{
	Stack st;
	MyQueue mq;
	return 0;
}

在这里插入图片描述
从运行结果我们可以看到一共调用了三次析构函数,分别是st一次和mq内的两次

4 拷贝构造

如果说构造函数的第一个参数是自身类类型的引用,并且额外的参数都有默认值,那么这个构造函数可以叫做拷贝构造函数,拷贝构造也是一种特殊的构造函数。

拷贝构造的特点 :

  1. 拷贝构造函数是构造函数的重载
  2. 拷贝构造的函数参数有且必须只有一个就是自身类类型的引用,使用传值的方式编译器会报错,因为会引发无穷递归。
  3. C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造来完成
  4. 若未显示定义拷贝构造,编译器会自动生成拷贝构造函数,自动生成的内置类型成员变量会进行值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员会调用他自己的拷贝构造
  5. 像Date类型的成员变量都是内置类型可以不写拷贝构造直接使用编译器自己生成的拷贝构造函数,但是如果是Stack类型虽然内部的成员变量都是内置类型但是其中的_a涉及到了资源的指向一起开辟,如果使用浅拷贝会发生冲突,所以需要我们自己实现并且将指向的资源同样进行拷贝。像MyQueue类型的虽然他的成员变量都是自定义类型但是在调用拷贝构造的时候也会调用Stack的拷贝构造所以也不用自己去实现。
  6. 传值返回会产生一个临时对象调用拷贝构造,传引用返回,返回的是对象的别名(引用),没有产生拷贝。但是如果返回对象是当前局部域的临时对象当前函数结束时该局部对象也会销毁就会出现野引用的问题,类似于野指针,传引用返回可以减少拷贝,但是一定要确定返回的对象不会因为当前函数结束而消失才可以使用。
#include<iostream>

using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//编译报错 Date类的复制构造函数不能有Date类型的参数,但是可以有 Date& 以及 Date* 类型
	//Date(Date d)
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	Date(Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}
	void print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void func1(Date d)
{
	cout << &d << endl;
	d.print();
}
Date& func2()
{
	Date tmp(2024, 10, 01);
	tmp.print();
	return tmp;
}
int main()
{
	//C++规定传值传参必须调用拷贝构造,所以这里调用了拷贝构造
	Date d1(2024,10,07);

	//传值传参给d会调用拷贝构造,使用传引用传参可以减少拷贝
	func1(d1);
	cout << &d1 << endl;

	//传引用传参,这里不是拷贝构造只是单纯的拷贝
	Date d2(&d1);
	d1.print();
	d2.print();

	//这里也是拷贝构造
	Date d3 = d1;
	d3.print();
	//拷贝构造用同类型的对象初始化而不是指针
	Date d4(d1);
	d4.print();

	//传引用返回的tmp在函数结束的时候就已经被销毁了,所以返回的是野引用
	Date ret = func2();
	ret.print();
	return 0;
}
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (_a == nullptr)
		{
			perror("malloc fail");
		}
		_capacity = n;
		_top = 0;
	}
	Stack(const Stack& st)
	{
		//需要对_a指向的资源同样进行拷贝
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_capacity = st._capacity;
		_top = st._top;
	}
	void Push(STDataType data)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
			}
			_a = tmp;
			_capacity = newcapacity;
			
		}
		_a[_top++] = data;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
//用两个栈实现队列
class MyQueue
{
private:
	Stack push;
	Stack pop;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	MyQueue m1;
	MyQueue m2 = m1;

	return 0;
}

5 赋值运算符重载

5.1 运算符重载

  • 当运算符被用于类类型对象时,C++允许我们通过运算符重载的方式指定新的含义
  • 重载运算符的参数个数和运算符本来的操作对象数量一样多一元操作符有一个参数,二元运算符有两个参数。
  • 如果一个重载运算符函数是成员函数那么他的第一个参数是隐式指针this,因此运算符重载作为成员函数时会少一个参数。
  • . :: ?: . sizeof* 这五个运算符不能重载
  • 运算符重载是具有图书名字的函数,他的名字是由operator加上后面定义的运算符共同构成。和其他函数一样他也有返回类型 函数体和参数列表
  • 重载++运算符要注意后置++多一个int类型的形参用来区分两个运算符
  • 重载‘<<’ '>>'两个运算符的时候需要重载为全局函数,如果是成员函数他的第一个默认类型是隐式类型this作为他的左操作数调用时就变成了 ‘ 输出的对象<<cout ’ 不符合使用习惯和可读性,重载为全局函数只需要第一个参数类型时ostream/istream就可以了,第二个参数位置为该类类型的对象。
  • 运算符重载后优先级和结合性要保持一致
  • 重载操作符必须有一个类类型的参数,不能改变其原有的含义。

在这里插入图片描述

class A
{
public:
	void func()
	{
		cout << "A::func()" << endl;
	}
};
typedef void(A::* PF)(); //成员函数指针类型

int main()
{
	//C++规定成员函数要加&才可以取到函数指针
	PF pf = &A::func;

	A obj;

	(obj.*pf)();//对象调用成员函数指针使用.*运算符

	return 0;
}
//重载为全局函数会面临的问题
// 无法访问私有变量
// 解决办法
// 1 用public
// 2 提供get函数
// 3 友元函数
// 4 重载为成员函数

//public成员变量
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2024, 10, 06);
	Date d2(2024, 10, 07);

	//显示调用
	operator==(d1, d2);

	//编译器会转换成operator==(d1, d2)
	d1 == d2;
	return 0;
}

5.2 赋值运算符的重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的的对象直接拷贝赋值,但这里和拷贝构造的区分,拷贝构造用于一个对象拷贝初始化给另一个创建的对象。

赋值运算符重载的特点 :

  1. 赋值运算符重载是一个运算符重载,必须重载为成员函数,他的参数必须是当前的类类型建议加上const,否则传值传参会有拷贝。
  2. 有返回值,建议写成当前类类型的引用,引用返回可以提高效率,有返回值的母体是为了可以连续赋值
  3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造
#include<iostream>

using namespace std;
typedef int STDataType;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//编译报错 Date类的复制构造函数不能有Date类型的参数,但是可以有 Date& 以及 Date* 类型
	//Date(Date d)
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	Date(Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}
	void print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
	Date& operator=(const Date& d1)
	{
		if (this != &d1)
		{
			_year = d1._year;
			_month = d1._month;
			_day = d1._day;
		}
		return *this;
	}
//private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 10, 06);
	Date d2(2024, 10, 07);

	//显示调用
	operator==(d1, d2);

	//编译器会转换成operator==(d1, d2)
	d1 == d2;
	d2.print();
	d2 = d1;
	d2.print();

	//注意这里不是赋值重载,而是拷贝构造,赋值重载只用于两个已经存在的对象,
	// 而拷贝构造用于一个对象初始化另一个对象
	Date d3 = d1;
	return 0;
}

在这里插入图片描述

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

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

相关文章

制作U盘启动盘1 — UltraISO

官网&#xff1a;UltraISO软碟通中文官方网站 - 光盘映像文件制作/编辑/转换工具 点击工具栏的“打开”&#xff0c;在弹出的窗口选择要刻录的ISO文件。 点击菜单栏的“启动”—“写入磁盘映像”。 硬盘驱动器选择要刻录的U盘。 点击写入&#xff0c;会弹出数据丢失的提示&…

数据结构和算法简介

目录 1.认识数据结构 什么是数据结构 逻辑结构 物理结构 常见的数据结构 2.认识算法 什么是算法 如何衡量算法效率 时间复杂度 什么是时间复杂度 如何计算时间复杂度 大O渐进表示法 常见时间复杂度计算例子 空间复杂度 什么是空间复杂度 如何计算空间复杂度 常…

【数据结构】深度解析堆排序

目录 &#x1f4af;引言 &#x1f4af;堆的概念 &#xff08;一&#xff09;什么是堆 &#xff08;二&#xff09;堆的表示 &#x1f4af;堆排序原理 &#xff08;一&#xff09;建堆 &#xff08;二&#xff09;排序 &#x1f4af;代码实现 &#x1f4af;代码分析 &…

【Sqlite】sqlite内部函数sqlite3_value_text特性

目录 ⚛️1 结论 ☪️2 说明 ☪️3 传入数值转成科学计数法 ♋3.1 只有整数部分 ♏3.2 只有小数部分 ♐3.3 整数小数 ⚛️1 结论 整数(sqlite视为int64)位数 > 20位&#xff0c;sqlite3_value_text 采用科学计数法。否则正常表示。 浮点数(sqlite视为double)的整数部…

STM32 通用同步/异步通信

一、串行通信简介 CPU与外围设备之间的信息交换称为通信。基本的通信方式有并行通信和串行通信两种。STM32单片机提供了功能强大的串行通信模块&#xff0c;即通用同步/异步收发器&#xff08;USART&#xff09;。 1.串行通信 串行通信是数据字节一位一位地依次传送的通信方式。…

HarmonyOS第一课 05 从简单的页面开始-习题

【习题】从简单的页面开始 通过/及格分80/ 满分100 判断题 1.Button作为容器使用时可以通过添加子组件实现包含文字、图片等元素的按钮&#xff0c;其类型包括胶囊按钮、圆形按钮、普通按钮。T 正确(True) 错误(False) 大部分前端框架的按钮都具有这几个类型,鸿蒙也不例外…

Ubuntu+VsCode++搭建C++开发环境

Ubuntu下使用VsCode搭建C开发环境 1、基本工具的安装 首先Ubuntu下安装好C开发的一个些基本工具g、gdb、make、cmake等&#xff0c;安装方式点这里 检查一下安装环境 $ g --version g (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation,…

位图的应用

目录 问题引入 位图概念 位图的实现 应用2&#xff1a;找到只出现一次的整数 应用三&#xff1a;找交集 STL中的位图 问题引入 面试题 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在 这40亿个数中。【腾讯】 解决…

幂,你去哪儿了-《分析模式》漫谈37

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第3章的图3.5&#xff0c;原文的图是&#xff1a; 2004&#xff08;机械工业出版社&#xff09;中译本的图是&#xff1a; direct翻译成分子&#xff0c;inv…

master节点k8s部署]33.ceph分布式存储(四)

总结ceph分布式存储&#xff08;三&#xff09;中提到的三种方法&#xff1a; 1.创建rbda&#xff0c;并且在创建pv的时候配置该rbda,以下代码仅展示关键信息。 [rootxianchaomaster1 ~]# cat pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: ceph-pv ...…

MySQL多表查询:行子查询

先看我的表数据 dept表 emp表 行子查询 子查询返回的结果是一行&#xff08;可以是多列&#xff09;, 这种子查询称为行子查询 常用的操作符: , <>, IN, NOT IN 例子1. 查询与“张无忌” 的薪资及直属领导相同的员工信息 拆解成两个问题 a. 查询"张无忌"…

基于SpringBoot+Vue+MySQL的汽车租赁系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着城市化和交通需求的不断增加&#xff0c;汽车租赁业务成为了现代社会的一个重要组成部分。汽车租赁服务为人们提供了一种灵活便捷的交通解决方案&#xff0c;让用户在无需购买车辆的情况下&#xff0c;根据实际需要租赁车辆…

端口冲突的解决方案以及SpringBoot自动检测可用端口demo

端口冲突的解决方案 端口冲突通常发生在尝试运行两个或多个应用程序或服务时&#xff0c;它们尝试使用同一个端口号&#xff0c;导致系统无法正确分配资源。 各种端口错误 你是否遇到过下面这些报错信息呢&#xff1f; Windows 系统报错&#xff1a; 系统错误 1004 套接字操作…

图像转3D视差视频:DepthFlow、kling

1、DepthFlow 参看: https://github.com/BrokenSource/DepthFlow 通过深度图实现图像3d效果 安装 https://brokensrc.dev/get/pypi/#installing pip insatll depthflow shaderflow broken-source pianola spectronote turbopipe 使用 1、下载项目 git clone https://gith…

约数个数约数之和

好久没发文章了.......不过粉丝还是一个没少...... 今天来看两道超级恶心的数论题目&#xff01; No.1 约数个数 No.2 约数之和 先来看第一道&#xff1a;约数个数 题目描述 给定 n 个正整数 ai​,请你输出这些数的乘积的约数个数,答案对 10^97 取模 输入格式 第一行包含…

Python_文件处理

一个完整的程序一般都包括数据的存储和读取&#xff1b;我们在前面写的程序数据都没有进行实际的存储&#xff0c;因此python解释器执行完数据就消失了。实际开发中&#xff0c;我们经常需要从外部存储介质&#xff08;硬盘、光盘、U盘等&#xff09;读取数据&#xff0c;或者将…

微信小程序开发-目录结构介绍

文章目录 一&#xff0c;目录结构介绍1&#xff0c;主体文件2&#xff0c;页面文件3&#xff0c;修改页面渲染模式 二&#xff0c;新增页面1&#xff0c;右键“pages”-新建文件夹2&#xff0c;右键文件夹-新建page3&#xff0c;新建页面的快捷方式 四&#xff0c;基础库设置 一…

①EtherNet/IP转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherNet/IP 转 Modbus TCP GW型号系列 MS-GW25 概述 简介 MS-GW25 是 EtherNet/IP 和 Modbus TCP 协议转换网关&#xff0c;为…

C语言 | 第十一章 | static 日期函数 数学函数

P 100 变量作用域基本规则 2023/1/9 一、基本介绍 概念&#xff1a;所谓变量作用域&#xff08;Scope&#xff09;&#xff0c;就是指变量的有效范围。 函数内部声明/定义的局部变量&#xff0c;作用域仅限于函数内部。 #include<stdio.h> void sayHello() {char nam…

【C++】—— 继承(上)

【C】—— 继承&#xff08;上&#xff09; 1 继承的概念与定义1.1 继承的概念1.2 继承定义1.2.1 定义格式1.2.2 继承父类成员访问方式的变化 1.3 继承类模板 2 父类和子类对象赋值兼容转换3 继承中的作用域3.1 隐藏规则3.2 例题 4 子类的默认成员函数4.1 构造函数4.1.1 父类有…