超详解C++类与对象(中)

news2024/11/18 5:55:57

目录

1. 构造函数

1.1. 定义

1.2. 注意

2.析构函数 

2.1定义

2.2注意 

3.拷贝构造函数 

3..1. 定义

3.2. 注意 

4.运算符重载 

4.1. 定义

5. 赋值运算符重载 

5.1. 定义

5.2. 注意 

​​​​​​​

💓 博客主页:C-SDN花园GGbond

⏩ 文章专栏:玩转c++

1. 构造函数

1.1. 定义

构造函数是一个特殊的成员函数,名字与类名相同创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。其特点如下:

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

下面是一个日期类的构造函数 

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

int main()
{
	Date d1(1,1,1);//自动调用
	d1.Print();
	return 0;
}

  • 构造函数的功能就相当于我们之前书写的初始化函数,但由于其自动调用的特性,大大提升了代码的容错率。
1.2. 注意

1.无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。并且默认构造函数只能有一个(会出现调用歧义)

class Date
{
public:
	Date()//无参
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, 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 d;//引起歧义
	return 0;
}

  

编译器不知道调用哪个构造函数,就会引起歧义。 

2.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成 

class Date
{
public:
	/*Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
    //编译器会自动生成一个无参的默认构造函数
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

3.编译器生成的默认构造函数只会对自定义类型(类)进行初始化(调用自定义类型的构造函数),内置类型(int,double…)不会进行初始化。

#include<iostream>
using namespace std;
class GGBond
{
public:
	GGBond()
	{
		cout << "GGBond" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	GGBond b;
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	d.Print();
	return 0;
}

从上述实例观察,编译器自动生成的默认构造函数的确调用自定义类型的构造函数只对自定义类型进行初始化。 

 

4.**C++11 中针对内置类型成员不初始化的缺陷,又进行了优化,即:内置类型成员变量在类中声明时可以给默认值。 

class Date
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year = 1;//默认值
	int _month = 1;//默认值
	int _day = 1;//默认值
};
int main()
{
	Date d;
	d.Print();
	return 0;
}

2.析构函数 

2.1定义

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。其特点如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载(因为无参)
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

下面是一个日期类的析构函数:

class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//析构函数
	~Date()
	{
		_year = _month = _day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};

析构函数就相当于C语言中的销毁函数,但由于其自动调用的特性,大大提升了代码的容错率 

2.2注意 

 1.如果类中没有显式定义析构函数,则C++编译器会自动生成一个析构函数,一旦用户显式定义编译器将不再生成。

class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//析构函数
	/*~Date()
	{
		_year = _month = _day = 0;
	}*/
    //编译器会自动生成一个析构函数
private:
	int _year;
	int _month;
	int _day;
};

2.编译器生成的析构函数对成员变量的内置类型(int,double…)不会进行处理,对于成员变量的自定义类型调用其析构函数 

#include<iostream>
using namespace std;
class GGBond
{
public:
	~GGBond()
	{
		cout << "~GGBond" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//默认生成
private:
	GGBond b;
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	d.Print();
	return 0;
}

3.因为指针类型也属于内置类型,所以默认成员在动态内存开辟内存后,必须显式写成析构函数。不能靠编译器默认生成。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 2)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array!=nullptr)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

比如说上述代码,默认生成的析构函数并不会释放其内存,就可能造成内存泄漏。 

3.拷贝构造函数 

3..1. 定义

在C++编程中,拷贝构造函数是一个特殊的构造函数,用于从一个已存在的对象创建一个新的对象。如果你没有为类显式地定义一个拷贝构造函数,编译器会为你自动生成一个。这个由编译器自动生成的拷贝构造函数通常被称为默认拷贝构造函数(default copy constructor)

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

示例

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d) 
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024,2,2);
	Date d2(d1);//拷贝构造
	Date d3 = d1;//拷贝构造
    d1.Print();
	d2.Print();
	d3.Print();
	return 0;
}

3.2. 注意 

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

	Date(const Date d) //error
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

2.若未显式定义,编译器会生构造函数(默认拷贝函数)。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

  • 浅拷贝:默认拷贝构造函数通常执行浅拷贝,即复制对象的所有成员变量的值。如果成员变量是指针,那么指针本身的值(即地址)会被复制,但指向的数据不会被复制。这可能导致两个对象共享同一块内存,从而可能引发问题(如双重释放内存)。
  • 非指针成员:对于非指针类型的成员变量,默认拷贝构造函数会正确复制其值。
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date(const 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;
};
int main()
{
	Date d1(2024, 2, 2);
	Date d2(d1);//拷贝构造
	Date d3 = d1;//拷贝构造
	d1.Print();
	d2.Print();
	d3.Print();
	return 0;
}

 编译器默认生成的拷贝构造函数是值拷贝,在某些场景下就会出错。比如说以下场景:

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);//默认拷贝构造
	return 0;
}

默认生成的拷贝构造只是进行值拷贝,对于size,capacity拷贝并不会出现问题,但是当s1的_array拷贝给s2的_array时,就会让s1与s2的同时指向同一片空间。而我们知道当对象的作用域结束时,会自动调用析构函数,同时对同一片空间析构两次,就会保错。所以当类中需要资源申请时,都需要手动写拷贝构造。

4.运算符重载 

4.1. 定义

C++为了增强代码的可读性引入了运算符重载,运算符重载是具由运算符operator定义有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。该函数能让我们自定义类型像内置类型一样使用-+*/等运算符。

下面实现了简单判断日期是否相当的运算符重载:  

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
    {
	    _year = year;
	    _month = month;
	    _day = day;
    }
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	int _year;
	int _month;
	int _day;
};
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,1,1);
	Date d2(2024, 1, 1);
	if (d1 == d2)//也可以显示调用operator==(d1,d2);
	{
		cout << "日期相等" << endl;
	}
	else
	{
		cout << "日期不相等" << endl;
	}
	return 0;
}

当然我们也可以将运算符重载声明在类中。 

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 1, 1);
	Date d2(2024, 1, 1);
	if (d1 == d2)//也可以显示调用operator==(d1,d2);
	{
		cout << "日期相等" << endl;
	}
	else
	{
		cout << "日期不相等" << endl;
	}
	return 0;
}

4..2. 注意 

1.不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数
2.用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
3.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
4..* sizeof ? : :: . 注意以上5个运算符不能重载。

5. 赋值运算符重载 

5.1. 定义

赋值运算符重载是将运算符 =进行运算符重载。但是它相较于其他运算符重载有着自己独特(没有显示给,编译器在类中会给出)的特点。

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)//赋值运算符重载
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
  • 参数类型:const T& ,传递引用可以提高传参效率。
  • 返回值类型:T& ,返回引用可以提高返回的效率,支持连续赋值。
  • 检测是否自己给自己赋值。
  • 返回* this :要复合连续赋值的值。

5.2. 注意 

1.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		cout << "Time& operator=(const Time& t)" << endl;
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 2024;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

2.因为编译器默认生成默认赋值运算符重载的是值拷贝,在某些场景下就会出错。具体实例参考拷贝构造函数。

3.赋值运算符只能重载成类的成员函数不能重载成全局函数。

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
Date& operator=(Date& left, const Date& right)//error
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}

因为赋值运算符如果不显式实现,编译器会生成一个默认的赋值运算符重载。此时用户再在类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载冲突。 


 

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

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

相关文章

大模型学习----什么是RAG

大模型快速定制的 RAG&#xff08;Retrieval-Augmented Generation&#xff09;方法 一、什么是 RAG RAG&#xff08;Retrieval-Augmented Generation&#xff09;即检索增强生成&#xff0c;它是一种结合了检索和语言生成的技术&#xff0c;旨在利用外部知识源来增强大型语言…

YOLO11改进|注意力机制篇|引入全局上下文注意力机制GCA

目录 一、【】注意力机制1.1【GCA】注意力介绍1.2【GCA】核心代码 二、添加【GCA】注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【】注意力机制 1.1【GCA】注意力介绍 下图是【GCA】的结构图&#xff0c;让我们简单分析一下…

SQL优化 where谓词条件is null优化

1.创建测试表及谓词条件中包含is null模拟语句 create table t641 as select * from dba_objects; set autot trace select SUBOBJECT_NAME,OBJECT_NAME from t641 where OBJECT_NAMEWRI$_OPTSTAT_SYNOPSIS$ and SUBOBJECT_NAME is null; 2.全表扫描逻辑读1237 3.创建等值谓词条…

PE结构之导出表

导出表结构中各种值的意义 ​​​​​​ 根据函数地址表遍历函数名称RVA表,和上面的图是逆过程 //函数地址表 和当前内存中的位置DWORD AddressOfFunctionsFOA RVAToFOA(LPdosHeader, LPexprotDir->AddressOfFunctions);PDWORD LPFunctionsAddressInMemary (PDWORD)((cha…

flask发送邮件

开通邮件IMAP/SMTP服务 以网易邮箱为例 点击开启发送验证后会收到一个密钥&#xff0c;记得保存好 编写代码 安装flask-mail pip install flask-mail在config.py文件中配置邮件信息 MAIL_SERVER&#xff1a;邮件服务器 MAIL_USE_SSL&#xff1a;使用SSL MAIL_PORT&#…

【计算机网络】网络相关技术介绍

文章目录 NAT概述NAT的基本概念NAT的工作原理1. **基本NAT&#xff08;静态NAT&#xff09;**2. **动态NAT**3. **NAPT&#xff08;网络地址端口转换&#xff0c;也称为PAT&#xff09;** 底层实现原理1. **数据包处理**2. **转换表**3. **超时机制** NAT的优点NAT的缺点总结 P…

Linux:多线程中的生产消费模型

多线程 生产消费模型三种关系两个角色一个交易场所交易场所的实现&#xff08;阻塞队列&#xff09;pthread_cond_wait 接口判断阻塞队列的空或满时&#xff0c;需要使用while测试一&#xff1a;单消费单生产案例测试二&#xff1a;多生产多消费案例 生产消费模型 消费者与生产…

鸿蒙网络管理模块05——数据流量统计

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、概述 HarmonyOS供了基于物理网络的数据流量统计能力&#xff0c;支持基于网卡/U…

贪心,CF 865B - Ordering Pizza

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 865B - Ordering Pizza 二、解题报告 1、思路分析 如果我们不考虑披萨数…

读懂MySQL事务隔离

什么是事务 事务就是一组原子性的SQL查询&#xff0c;或者说一个独立的工作单元。事务内的语句&#xff0c;要么全部执行成功&#xff0c;要么全部执行失败。 关于事务银行系统的应用是解释事务必要性的一个经典例子。 假设一个银行的数据库有两张表&#xff1a;支票表&#x…

MySql数据库---存储过程

存储过程概念 MySQL 5.0 版本开始支持存储过程。 简单的说&#xff0c;存储过程就是一组SQL语句集&#xff0c;功能强大&#xff0c;可以实现一些比较复杂的逻辑功能&#xff0c;类似于JAVA语言中的方法&#xff0c;类似Python中的函数&#xff1b; 存储过就是数据库 SQL 语言…

【数据结构】红黑树相关知识详细梳理

1. 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0c…

大数据行业应用实训室建设方案

摘要&#xff1a; 本文旨在探讨唯众针对当前大数据行业的人才需求&#xff0c;提出的《大数据行业应用实训室建设方案》。该方案旨在构建一个集理论教学、实践操作、技术创新与行业应用于一体的综合实训平台&#xff0c;以培养具备实战能力的大数据专业人才。 一、大数据课程体…

【AI知识点】机器学习中的常用优化算法(梯度下降、SGD、Adam等)

更多AI知识点总结见我的专栏&#xff1a;【AI知识点】 AI论文精读、项目和一些个人思考见我另一专栏&#xff1a;【AI修炼之路】 有什么问题、批评和建议都非常欢迎交流&#xff0c;三人行必有我师焉&#x1f601; 1. 什么是优化算法&#xff1f; 在机器学习中优化算法&#x…

决策树随机森林-笔记

决策树 1. 什么是决策树&#xff1f; 决策树是一种基于树结构的监督学习算法&#xff0c;适用于分类和回归任务。 根据数据集构建一棵树&#xff08;二叉树或多叉树&#xff09;。 先选哪个属性作为向下分裂的依据&#xff08;越接近根节点越关键&#xff09;&#xff1f;…

【动态规划-最长递增子序列(LIS)】【hard】力扣1671. 得到山形数组的最少删除次数

我们定义 arr 是 山形数组 当且仅当它满足&#xff1a; arr.length > 3 存在某个下标 i &#xff08;从 0 开始&#xff09; 满足 0 < i < arr.length - 1 且&#xff1a; arr[0] < arr[1] < … < arr[i - 1] < arr[i] arr[i] > arr[i 1] > … &g…

【hot100-java】二叉搜索树中第 K 小的元素

二叉树 二叉搜索树的中序遍历是递增序列。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode lef…

【C++】面向对象之继承

不要否定过去&#xff0c;也不要用过去牵扯未来。不是因为有希望才去努力&#xff0c;而是努力了&#xff0c;才能看到希望。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;继承的概念及定义 •&#x1f330;1.继承的概念 •&…

ECCV24高分论文:MVSplat稀疏视图下的高效的前馈三维重建模型

目录 一、概述 二、相关工作 1、稀疏视角场景重建 2、前馈NeRF 3、前馈3DGS 4、多视角立体视觉 三、MVSplat 1、多视角Transformer 一、概述 本文提出了一个MVSplat高效的前馈三维重建模型&#xff0c;可以从稀疏的多视图图像中预测3D高斯分布&#xff0c;并且相较于p…

三角形面积 python

题目&#xff1a; 计算三角形面积 代码&#xff1a; a int(input("请输入三角形的第一个边长&#xff1a;")) b int(input("请输入三角形的第二个边长&#xff1a;")) c int(input("请输入三角形的第三个边长&#xff1a;")) s (abc) / 2 #…