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

👌个人主页: 起名字真南
👆个人专栏:【数据结构初阶】 【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;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Kubernetes】常见面试题汇总(五十三)

目录 118. pod 状态为 ErrlmagePull &#xff1f; 119.探测存活 pod 状态为 CrashLoopBackOff &#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。…

Bloom Filter 布隆过滤器

目录 简介 Bloom Filter的基本原理 实现 使用 HashFunc越多&#xff0c;性能越好吗&#xff1f; 如何尽量避免误判&#xff1f; 应用 布隆过滤器优点 简介 Bloom Filter是一种空间效率极高的概率数据结构&#xff0c;它用于测试一个元素是否属于集合。Bloom Filter的优…

【NoSQL】portswigger NoSQL注入 labs 全解

目录 NoSQL NoSQL 数据库模型 NoSQL 注入的类型 NoSQL 语法注入 检测 MongoDB 中的语法注入 lab1:检测 NoSQL 注入 NoSQL 运算符注入 提交查询运算符 检测 MongoDB 中的运算符注入 lab2:利用 NoSQL 运算符注入绕过身份验证 利用语法注入来提取数据 MongoDB 中的数据…

【rust/egui/android】在android中使用egui库

文章目录 说在前面AndroidStudio安装编译安装运行问题 说在前面 操作系统&#xff1a;windows11java版本&#xff1a;23android sdk版本&#xff1a;35android ndk版本&#xff1a;22rust版本&#xff1a; AndroidStudio安装 安装AndroidStudio是为了安装sdk、ndk&#xff0c;…

大数据实时数仓Hologres(三):存储格式介绍

文章目录 存储格式介绍 一、格式 二、使用建议 三、技术原理 1、列存 2、行存 3、行列共存 四、使用示例 存储格式介绍 一、格式 在Hologres中支持行存、列存和行列共存三种存储格式,不同的存储格式适用于不同的场景。在建表时通过设置orientation属性指定表的存储…

【重学 MySQL】五十三、MySQL数据类型概述和字符集设置

【重学 MySQL】五十三、MySQL数据类型概述和字符集设置 MySQL数据类型概述MySQL字符集设置注意事项 MySQL数据类型概述 MySQL是一个流行的关系型数据库管理系统&#xff0c;它支持多种数据类型&#xff0c;以满足不同数据处理和存储的需求。理解并正确使用这些数据类型对于提高…

Linux性能调优技巧

目录 前言1. CPU性能优化1.1 调整CPU调度策略1.2 合理分配多核处理 2. 内存性能优化2.1 调整内存分配策略2.2 缓存和分页优化 3. 磁盘I/O性能优化3.1 使用合适的I/O调度器3.2 磁盘分区和文件系统优化 4. 网络性能优化4.1 优化网络参数4.2 调整网络拥塞控制算法 5. 系统监控与优…

【机器学习】网络安全——异常检测与入侵防御系统

我的主页&#xff1a;2的n次方_ 随着全球互联网和数字基础设施的不断扩展&#xff0c;网络攻击的数量和复杂性都在显著增加。从传统的病毒和蠕虫攻击到现代复杂的高级持续性威胁&#xff08;APT&#xff09;&#xff0c;网络攻击呈现出更加智能化和隐蔽化的趋势。面对这样的…

Kotlin 处理字符串和正则表达式(二十一)

导读大纲 1.1 处理字符串和正则表达式1.1.1 分割字符串1.1.2 正则表达式和三引号字符串1.1.3 多行三引号字符串IntelliJ IDEA 和 Android Studio 中三重引号字符串内部的语法高亮显示 1.1 处理字符串和正则表达式 Kotlin 字符串与 Java 字符串完全相同 可以将 Kotlin 代码中创建…

Python_文件处理

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

查缺补漏----IP通信过程

1.DHCP协议 H3刚接入网络时&#xff0c;只知道自己的MAC地址&#xff0c;所以需要通过DHCP协议请求自己的IP地址。 通过DHCP协议&#xff0c;得到IP地址、子网掩码、网关与DNS服务器IP地址。 DHCP协议是应用层协议(传输层为UDP)&#xff0c;请求报文是广播&#xff08;H3不知…

‌在Python中,print(f‘‘)是什么?

‌在Python中&#xff0c;print(f’)表示使用f-string对字符串进行格式化输出。‌ f-string是Python 3.6及以上版本引入的一种新的字符串格式化机制&#xff0c;它允许在字符串中直接嵌入表达式&#xff0c;这些表达式在运行时会被其值所替换。使用f-string可以更方便地将变量的…

国庆节快乐前端(HTML+CSS+JavaScript+BootStrap.min.css)

一、效果展示 二、制作缘由 最近&#xff0c;到了国庆节&#xff0c;自己呆在学校当守校人&#xff0c;太无聊了&#xff0c;顺便做一个小demo帮祖国目前庆生&#xff01;&#xff01;&#xff01; 三、项目目录结构 四、准备工作 (1)新建好对应的文件目录 为了方便&#xff…

PHP泛目录生成源码,可生成长尾关键词页面,带使用方法视频教程

介绍&#xff1a; 真正的好东西&#xff0c;搞网站优化seo从业必备。可以快速提升网站权重&#xff0c;带来的流量哗哗的 PHP泛目录生成源码 可生成新闻页面和关键词页面 带使用方法视频教程 泛目录可以用来提升网站收录和排名 合理运用目录可以达到快速出词和出权重的效果…

【Bug】解决 Ubuntu 中 “error: Unable to Find Python3 Executable” 错误

解决 Ubuntu 中 “Unable to Find Python3 Executable” 错误 在 Ubuntu 系统上使用 Python 进行开发时&#xff0c;遇到找不到 python3 可执行文件的错误。 主要问题是无法正常打开终端&#xff08;原生与terminator&#xff09;&#xff0c;找不到python3&#xff0c;且无法…

教育技术革新:SpringBoot在线教育系统开发指南

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…

20240930编译orangepi5的Android12使用HDMI0输出

20240930编译orangepi5的Android12使用HDMI0输出 2024/9/30 9:44 缘起&#xff0c;3月份的时候&#xff0c;看PDD拼多多的优惠券给力&#xff01; 就入手了香橙派Orange Pi 5。 自从制作TF卡的启动卡的时候&#xff0c;坏了一张SanDisk的32GB的TF卡。 从此就对TF卡启动无比抵触…

Unity_Obfuscator Pro代码混淆工具_学习日志

Unity_Obfuscator Pro代码混淆工具_学习日志 切勿将密码或 API 密钥存储在您附带的应用程序内。 混淆后的热更新暂时没有想到怎么办 Obfuscator 文档 https://docs.guardingpearsoftware.com/manual/Obfuscator/Description.html商店链接Obfuscator Pro&#xff08;大约$70&a…

Docker面试-24年

1、Docker 是什么&#xff1f; Docker一个开源的应用容器引擎&#xff0c;是实现容器技术的一种工具&#xff0c;让开发者可以打包他们的应用以及环境到一个镜像中&#xff0c;可以快速的发布到任何流行的操作系统上。 2、Docker的三大核心是什么? 镜像&#xff1a;Docker的…

fiddler抓包17_简单接口测试(Composer请求编辑)

课程大纲 ① 进入“Composer”&#xff08;请求编辑&#xff09;界面&#xff1a; Fiddler右侧标签菜单选择“Composer”&#xff0c;中文“请求编辑” 。 ② 编辑、发送请求&#xff1a; 填写接口请求信息&#xff08;或从左侧列表直接拖拽填充&#xff09;&#xff0c;点击“…