C++类的默认成员函数

这篇具有很好参考价值的文章主要介绍了C++类的默认成员函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

默认函数

什么是默认函数?
默认函数就是当你使用这个类对象时,这个类会自动调用的函数C++中有六个默认成员函数,并且作用各不相同,下面我们来一一进行介绍

构造函数和析构函数

什么是构造函数?构造函数是干什么的?
什么是析构函数?析构函数是干什么的?
我们以栈为例,每一次我们在使用栈的时候我们都要先定义它,并且每次在使用完栈之后还要去销毁它,为了释放空间防止内存泄漏。然而我们在实际操作时经常会忘记去定义,特别是最后的销毁。祖师爷为了防止你的遗忘,专门设计出了这两个默认函数来减小你的压力

构造函数

构造函数就是在定义对象的同时,就对成员变量进行初始化。

#include <iostream>

using namespace std;

class Date
{
public:
	//过去
	/*void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	//现在
	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;
	//D1.Init(2002, 01, 01);
	Date D1(2002, 01, 01);
	D1.Print();
	return 0;
}

一般情况下,我们会定义一个Init函数,再定义完一个对象后,再调用Init函数对成员变量进行初始化,这样显得有些繁琐,那么可不可以选择在定义对象时就对成员变量进行初始化呢?
答案是肯定的,我们可以通过构造函数来对成员变量进行初始化。
默认构造函数,其实类中是有默认构造函数的,就是在你创建这个对象时,它就会自动调用这个函数,对成员变量进行初始化,只不过有时候它默认生成的值并不是我们想要的,所以就需要我们自己去定义。
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造(成员)函数,一旦用户显式定义编译器将不再生成。
无论何时只要类的对象被创建,就会执行构造函数。C++规定内置类型不做处理,自定义类型会去调用而它的默认构造。但是有些也会对内置类型做处理,但那只是编译器的个性化行为,C++并没有规定。
构造函数怎么去定义呢?首先构造函数的取名就与其他函数不同,它的名字与类名相同,且不需要返回值,这里的不需要返回值是指他不需要加返回类型,void也不用加。
什么时候自己写构造函数呢?
如果类成员函数中有自定义类型就要去自己写构造函数,如果类成员全是自定义类型就可以选择不写。
构造函数参数可以给缺省值,且可以重载。
还有一个问题当我们在定义对象时调用无参的构造函数,为什么不写成这样?

Date D1();

因为这样编译器很可能会把它当做返回类型为Date的函数声明。但是如果我们传的是值

Date D1(2002, 01, 01);

编译器就会识别出来它是变量的初始化而不是函数声明。
注意:区分一些概念,我们不写编译器自己生成的是默认成员函数,也属于默认构造函数,默认构造函数是一个大类包括我们自己显示写的和编译器自己生成的。
我们不传参自动调用的构造函数就是默认构造函数,必须要传参的不属于默认构造函数,属于构造函数。
默认构造函数有三种:一种是编译器自己生成的,一种是参数全缺省的,另一种是无参的构造函数,且这三个默认构造函数只能有一个。如果是自己写的构造函数,编译器就不会自动生成构造函数。

Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	Date(int year = 2, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
	}

上面两个构造函数,符合语法规定构成重载,但是C++规定只能有一个默认构造函数,所以我们只能写其中的一个,且上面两个在函数调用的时候存在歧义,当你不写形参的时候,编译器不知道你是要调用哪一个。
构造函数可以重载,那默认构造函数与半缺省的构造函数可不可以同时写呢?

Date(int year = 2, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(int year = 3, int month = 2)
	{
		_year = year;
		_month = month;
		//_day = day;
	}

这样也是不行的,半缺省与默认构造函数放在一起时也会存在调用歧义。
例如:

void Print(int x = 2, int y = 2)
{
	cout << x << " " << y << endl;
}

void Print(int x = 3, int y = 3, int z = 5)
{
	cout << x << " " << y << " " << z << endl;
}

这种虽然语法上来说也是构成重载的,但是在你传一个参数或两个参数时会有多个参数参数列表匹配,就会存在调用歧义。

void Print(int x, int y )
{
	x = 2;
	y = 3;
	cout << x << " " << y << endl;
}

void Print(int x, int y, int z )
{
	x = 5;
	y = 6;
	z = 8;
	cout << x << " " << y << " " << z << endl;
}

上面这种才是真正的符合重载,不会再冲调用歧义。
对类成员初始化方式不同时才考虑使用重载构造函数,比如对一个栈的操作你可能想初始化时只对成员变量赋值,也可能想直接插入很多数据,比如将一个数组都插入,这时才真正符合重载,就是参数类型不同。在有缺省的情况下,你再定义一个缺省函数就很有可能会造成调用歧义,除非你传的实参个数不符合其它的重载函数。
全局对象先于局部对象进行构造
局部对象按照出现的顺序进行构造,无论是否为static

析构函数

析构函数的函数名也与类名相同,但是要在函数名前加上按位取反符号‘~’,析构函数的作用是在函数调用结束之后释放空间,注意不是释放这个对象,对象是在栈上开辟的,出了作用域栈上的空间会自动释放,只有动态开辟的空间才需要用到析构函数去释放,就是在堆上开辟的空间,其它的像临时变量等都是在栈上开辟的空间它会自动释放,此外析构函数没有参数,所以析构函数不可以重载,一个类中只有一个析构函数,如果不显示的去写,编译器也会默认生成构造函数,且对内置类型不做处理,自定义类型会去调用它的构造函数。出了作用域系统会自动调用析构函数。

~Stack()
	{
		if (_array)//_array是动态开辟的空间
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

什么时候写析构函数?
析构函数释放的空间是释放堆上的空间,就是我们动态开辟的空间。
1.一般情况下,有动态开辟的空间,就需要我们显示的去写析构函数去释放空间。
2.没有动态申请资源,就不需要写析构。像对象这种临时变量,不是动态开辟的空间,是在栈上开辟的,出了作用域系统会自动清除栈上的资源。例如日期类类里面就没有动态申请的资源。
3.如果需要释放的资源都是自定义类型,就不需要写析构函数,默认生成的析构函数足够用了。例如:

class Myque
{
private:
	Stack _pushst;
	Stack _popst;
};

拷贝构造函数

在我们传参数的过程中,可能会选择值传参,值传参实际上就是对形参进行初始化,对于内置类型变量赋给另一个变量的时候就是值拷贝也称为浅拷贝不用调用拷贝构造函数,而对于自定义类型在值拷贝时,会调用拷贝构造函数。每一个类都有它的默认拷贝构造函数,但是这个默认构造函数是浅拷贝,也就是说这个拷贝它是按字节赋给新的变量,并没有开辟新的空间。如果这个自定义类型中包含指针,那么新变量和被拷贝指针变量就会指向同一个空间,如果是这样的话就会出问题,当这两个对象空间释放时,就会导致同一块空间被析构两次,同一空间是不能被析构两次的,此外如果其中一个对象值发生改变,也会导致另一个对象值发生改变。任何类型的指针都是内置类型,包括自定义类型。如果我们自己定义拷贝构造函数,就会解决上述问题,我们选择深拷贝,深拷贝就是新开辟一块空间,并将原指针指向的空间地址的值赋给新的空间,这样就完成了拷贝,指针指向的空间不同,但是值是相同的。
那么我们可不可以直接用自定义类型作为形参?
答案是否定的,首先我们在对自定义类型进行拷贝时,调用的就是拷贝构造函数,调用它的时候又要进行传参,而在传参的过程中,就是相当于对自定义类型的形参进行初始化,而这种赋值,就又会调用拷贝构造函数,而调用拷贝构造函数就要传参,传参就要调用拷贝构造,最后会形成无限递归。
C语言当中自定义类型传参就直接是浅拷贝,而在C++当中在传参时要先调用拷贝构造,如果想避免上面的问题,还要进行深拷贝,也就是调用我们自定义的拷贝构造。
那么怎么解决自定义类型对象的拷贝呢?
上述传自定义类型时在拷贝时都会调用拷贝构造函数,那么可以选择传地址或者用引用去解决上述问题。因为自定义类型时才要拷贝构造,传指针或引用不需要,因为任何类型的指针都是内置类型,不会调用拷贝构造函数,直接把地址赋给新变量就可以,而传引用就是传这个对象的别名,引用对应的变量并没有开辟新的空间,这里重要的是这个类变量的地址和它的引用的地址是相同的,所以起引用名时不存在什么拷贝
C++类的默认成员函数
引用的地址是相同的

class Date
{
public:
	Date(int year = 2023, 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;
};

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,实际上是有两个参数一个是显示的被拷贝的对象的引用,另一个是隐式的this指针
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

运算符重载

运算符重载的作用:比如日期类我们想让两个日期进行相减,如果我们直接让这两个对象进行相减就会报错,因为自定义类型不是内置类型,编译器不知道他们是怎么进行运算的,所以我们要自己定义出这些符号具体的操作。运算符重载的关键字是字operator然后后面加上重载符号。

#include <iostream>

using namespace std;

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

public:
	int _year;
	int _month;
	int _day;
};

//重载操作符定义在类外
bool operator<(Date d1, Date d2)
{
	if (d1._year < d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month < d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year 
	&& d1._month < d2._month 
	&& d1._day < d2._day)
	{
		return true;
	}
	return false;
}

int main()
{
	Date d1(2005, 02, 03);
	Date d2(2006, 6, 10);
	d1 < d2;
	cout << operator<(d1, d2) << endl;
	return 0;
}

因为重载操作也是一个函数所以也可以直接通过调用这个函数来对这两个对象进行比较。
注意如果是定义在类外的那么类成员就不能是私密的应是public,因为在类外不能直接访问私密的类成员
上面的重载操作符是全局函数即定义在类外的,所以显示的形参个数与比较对象的个数相同。如果重载操作符函数是定义在类内的,那么显示传参的个数就要比实际比较的对象个数少一个,因为定义在类内就会有一个隐式的参数this指针存在

#include <iostream>

using namespace std;

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

	bool operator<(Date d)//只有一个显式的参数因为此时还有一个隐式的this
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month && _day < d._day)
		{
			return true;
		}
		return false;
	}

public:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2005, 02, 03);
	Date d2(2006, 6, 10);
	//定义为全局的重载操作运算符时的调用
	/*d1 < d2;
	cout << operator<(d1, d2) << endl;*/
	//定义在类内时的调用
	d1 < d2;
	d1.operator<(d2);
	return 0;
}

重载运算符的参数中必须有一个是自定义类型的参数,不能全是内置类型。
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。
.* //千万别忘了这个运算符也不能重载
:: sizeof ? : . 注意以上5个运算符不能重载。

赋值运算符重载

运算符重载中有一个特殊的,就是赋值运算符重载,像大于,小于,加,减等,如果你不写那么这个自定义类型就不能进行这些运算符操作,但是赋值运算符=,如果你不写,编译器也会自动生成默认的赋值运算符重载,只不过默认生成的是按字节赋值的浅拷贝,如果自定义类型中成员变量有动态开辟的空间,那么就需要我们自己手动的写上。特别注意,其它的运算符重载既可以定义成全局的,也可以定义在类内,但是赋值运算符只能定义在类内,因为其它的运算符编译器不会自动生成,而赋值运算符编译器会在类内自动生成默认的赋值运算符重载,如果你在类外定义就会造成调用不明确,出现歧义,在类内定义了,编译器就不会自动生成。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。另外赋值运算符,我们要有返回值且选择返回它的引用,当然也可以返回值,只不过是浅拷贝,且传值返回会调用拷贝构造,而返回引用不会调用拷贝构造,且会减少空间的消耗,这个是定义在类里面的,出了作用域 this 还在,只不过是在调用时的中介this不在了,this是这个对象,对象的声明周期不是在这个类里的

对象的传值才要调用拷贝构造,内置类型传值不调用拷贝构造,任何类型的指针都是内置类型,包括自定义类型的指针,传引用就是在传别名也不会调用拷贝构造。

赋值运算符重载和构造函数

赋值运算符是作用于两个已经存在的对象,一个对象的值赋给另一个对象。而构造函数是用一个已经存在的对象去初始化另一个对象。文章来源地址https://www.toymoban.com/news/detail-431875.html

到了这里,关于C++类的默认成员函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 类的6个默认成员函数 构造函数 概念 构造函数的特性及用法 析构函数 概念 析构函数的特性及用法 结语 本篇主要内容:类的6个默认成员函数中的 构造函数 和 析构函数 进入到类和对象内容的第二节,上篇博客中介绍了

    2024年04月16日
    浏览(57)
  • C++从入门到精通——类的6个默认成员函数之拷贝构造函数

    类的6个默认成员函数:如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。 在现实生活中,可

    2024年04月26日
    浏览(50)
  • 【C++初阶】第三站:类和对象(中) -- 类的6个默认成员函数

    目录 前言 类的6个默认成员函数 构造函数 概念 特性 析构函数  概念 特性 拷贝构造函数 概念 特征 赋值运算符重载 运算符重载 赋值运算符重载 const成员 const修饰类成员函数 取地址及const取地址操作符重载 本章总结:         有时候我们写好了一个栈,头脑中第一件事

    2024年02月20日
    浏览(44)
  • 【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 本篇主要内容:类的6个默认成员函数中的 拷贝构造函数 和 赋值运算符重载 在上篇文章中我们讲到了类的默认成员函数的

    2024年04月17日
    浏览(48)
  • 【C++】中类的6个默认成员函数 取地址及const成员函数 && 学习运算符重载 && 【实现一个日期类】

    1.1 运算符重载【引入】 C++为了增强代码的可读性引入了 运算符重载 ,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。 函数名字为: operator后面接需要重载的运算符符号。 函数原型:

    2024年02月21日
    浏览(49)
  • 【C++精华铺】5.C++类和对象(中)类的六个默认成员函数

    目录 1. 六个默认成员函数 2. 构造函数 2.1 概念 2.2 默认构造 2.2.1 系统生成的默认构造 2.2.2 自定义默认构造函数  2.3 构造函数的重载 3. 析构函数 3.1 概念  3.2 系统生成的析构函数  3.3 自定义析构函数 4. 拷贝构造 4.1 概念  4.2 默认生成的拷贝构造(浅拷贝)  4.3 自定义拷贝构

    2024年02月13日
    浏览(77)
  • C++从入门到精通——类的6个默认成员函数之赋值运算符重载

    类的6个默认成员函数:如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。 C++为了增强代码的

    2024年04月25日
    浏览(52)
  • 【C++练级之路】【Lv.3】类和对象(中)(没掌握类的6个默认成员函数,那你根本就没学过C++!)

    欢迎各位小伙伴关注我的专栏,和我一起系统学习C++,共同探讨和进步哦! 学习专栏 : 《进击的C++》 在C++的学习中,类和对象章节的学习尤为重要,犹如坚固的地基,基础不牢,地动山摇;而默认成员函数的学习,在类和对象的学习里最为重要。所以要 学好C++,学好默认

    2024年02月04日
    浏览(49)
  • 【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit 成员变量缺省值 结语 本篇主要内容:类的六个默认成员函数中的 取地址 及 const取地址重载 , 构造函数 初始化列表 , 隐式类型转换

    2024年04月26日
    浏览(51)
  • 类的默认成员函数——析构函数

    析构函数的功能和构造函数的功能是相反的,构造函数的功能是完成初始化,析构函数的功能是完成对象中资源清理的工作 注意析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作 清

    2024年02月14日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包