C嘎嘎~~ [类 下篇]

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

1.类的6个默认成员函数

如果一个类中什么都没有, 简称为空类
空类中真的就什么都没有吗? 并不是, 任何类在什么都不写的时候, 编译器会自动生成 6 个默认成员函数
==>默认成员函数: 用户没有显示实现, 编译器生成的成员函数称为默认成员函数
就是这个6个默认成员函数, 如果你没有定义出来, 编译器就会自动调用

C嘎嘎~~ [类 下篇]

2.构造函数

2.1 构造函数出现的原因

相信很多小伙伴们跟我想的一样, 当我们调用函数时, 比如: 栈, 队列, 堆… …, 我们都要初始化一下~. 一个两个的还好说, 十几个的我们就受不了了, 成百上千的更不成遑论!!
这时候就想, 如果编译器能够帮我们自己初始化函数就好了 ⇒ 这样, 我们就害怕初始化函数, 而且也不会忘记初始化函数
我们的构造函数就闪亮登场了!!

构造函数是一个特殊的成员函数, 函数名和类名相同, 对象实例化时会自动调用构造函数,以保证类中的每一个成员变量都有一个合适的初始值, 并且在对象的整个生命周期内只调用一次.

2.2 特性

构造函数 虽然名字叫做构造, 听的有点像是开辟一块空间来创建对象, 实际不然 ==> 构造函数并不会开辟空间创建对象, 而是初始化对象
在明确了构造函数的主要功能, 来了解一下构造函数的特性:

  1. 函数名 和 类名相同
  2. 没有返回值
  3. 对象实例化时编译器会自动调用对应的构造函数(分为内置类型 和 自定义类型)
  4. 构造函数可以重载
  5. 如果我们没有显示定义构造函数, 编译器会自动生成一个无参默认构造函数, 一旦我们默认显示定义, 编译器将不会生成, 而是去使用我们定义的构造函数
  6. 默认构造函数有三个: 无参构造函数, 全缺省构造函数, 编译器默认生成的构造函数, 但是编译器的默认构造函数只能是其中的一个

2.3 深刻解读—构造函数可以重载

class Date
{

public:

    // 无参构造
	Date()
	{

	}
    
    // 有参构造
	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;
    // 无参构造不能这样调用
    // Date d1();
	d1.Print();
	
	Date d2(2023, 5,5);
	d2.Print();

}

*****
-858993460 -858993460 -858993460
2023 5 5
*****

总结:

  • 这个构造函数虽然也是函数, 但跟普通的函数有所不同:
    一. 即使没有返回值, 也没有 void
    二. 函数名跟普通函数也有所不同, 函数名是 类名
    三. 调用无参构造函数时, 不能跟普通函数一样, 只能 类名 + 对象 <== 这个也从侧面说明了对象实例化会 自动调用构造函数
    四. 调用有参构造函数时, 就跟上面不一样了, 类名 + 对象 (参数)

疑问:

  1. 为什么调用无参构造, 不能用 类名 + 对象 ()?

因为这样会跟函数声明有冲突. 以 Date d1( )为例子, 这样编译器就不知道 这个 d1是对象还是函数名~

  1. 上面的有参构造能不能用全缺省啊?

上面的函数如果用全缺省的话, 当无参调用时, 虽然语法上是构成重载的, 但是当无参调用时, 就会构成歧义 ==> 这个到底是真的无参调用 还是 全缺省啊~

2.4 深刻解读—默认构造函数

前面我们已经知道, 我们的成员变量的类型分为两种: 内置类型 和 自定义类型. 那么编译器在面对这两种类型生成的构造函数是否相同 是我们现在主要思考的一个问题?? ==> 这里先给出结论: 自定义类型会调用它的默认构造函数, 而编译器不会对内置类型做处理.

那么问题来了: 什么是默认构造函数??
默认构造函数有三种形式: 无参构造函数, 全缺省构造函数, 编译器自动生成的构造函数

由于编译器的不同, 编译器版本的不同… …, 编译器对内置类型所做的处理也就不同. ==>在这里, 我们就默认编译器对内置类型都不做处理~~(本人偷个懒)


那问题来了,自定义类型构造函数是调用默认构造函数. 默认构造函数是从上面三种形式选一个, 那么哪一种比较好呢??
我的建议是全缺省的比较好, 相比较无参, 全缺省可以随时符合我们的选择
相比较于编译器自动生成的构造函数, 编译器生成的构造函数对内置类型不做处理~


补充:

  1. C++ 11 针对 “编译器对内置类型不处理” 做了一些 ‘补丁’ : 用户可以在类的成员变量声明的时候赋初值(注意这里还不是初始化, 因为初始化是针对对象来说的), 以此作为编译器生成的构造函数的缺省值
    C嘎嘎~~ [类 下篇]

当然, 编译器的处理我们还是当做不放心的来看, 就跟我们没有初始化 a 数组, 这个版本下的编译器会自动处理, 有的编译器就不会处理. 这种情况下, 我们要不就对全部的内置类型都做处理, 要不然就都不做处理吧~
2.
C嘎嘎~~ [类 下篇]


对构造函数做一个总结吧:
C嘎嘎~~ [类 下篇]

3.析构函数

3.1概念

前面已经了解了构造函数, 现在我们来看一下它的反面 — 析构函数

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

3.2 特性

析构函数是特殊的成员函数, 其特性有:

  1. 函数名: ~ + 类名
  2. “三无”: 无参数, 无返回值 和 无void
  3. 一个类只能有一个析构函数. 如果没有显示定义, 系统会自动生成默认的析构函数. 因为三无⇒ 析构函数能重载 ⇒ 所以析构函数比构造函数简单一点
  4. 对象生命周期结束时, 编译器会自动调用析构函数
  5. 编译器默认生成的析构函数, 对内置类型不做处理, 对自定义类型调用它的析构函数

3.3深刻解读

  1. 对象生命周期结束时, 编译器会自动调用析构函数
class Stack
{
public:
	// ...
	
	//...

	~Stack()
	{
		cout << "~Stack()" << endl;
		_a = NULL;
		_top = 0;
		_capacity = 0;
	}

private:
	int* _a;
	int _top = 0;
	int _capacity = 4;
};

int main()
{
	Stack st1;
	Stack st2;
	Stack st3;

}

*****
~Stack()
~Stack()
~Stack()
*****

通过上面的代码可以发现: 我们并没有调用析构函数, 当对象 st1, st2, st3的生命周期结束时, 即出了main函数, 就会自动调用析构函数来清理对象里面的资源

  1. 编译器默认生成的析构函数对内置类型不做处理, 对自定义类型调用它的析构函数
  • 细心的小伙伴应该就会发现上面的析构函数其实是多此一举的. <== 因为成员变量都是内置类型, 出了函数作用域, 编译器就会自动地进行销毁, 我们就不需要写一个析构函数来处理

  • 那么, 问题来了: 什么时候适合我们写析构函数, 什么时候又可以偷个懒??
    有些老铁肯定说可以用上面构造函数的情形将成员变量 分成 内置和自定义来处理一下~, 但是大家有没有发现有些内置类型不是在栈区的, 而是在堆区或是静态区的, 这又如何区分得了自定义
    所以大方向上是以是否动态开辟空间来划分:

  1. 有动态申请资源的, 一般都是要自己写析构函数
  2. 没有动态申请资源的, 可以偷个懒
  3. 需要释放动态资源的都是自定义类型, 也可以偷个懒, 使用编译器的析构函数
    C嘎嘎~~ [类 下篇]
  • 有些小伙伴有些问题了: 如果成员变量里面有自定义类型还有没有申请动态资源的内置类型, 可不可以偷个懒
    答案是当然可以 <== 因为没有申请动态资源的内置类型就不用析构函数区处理, 出了作用域就会销毁.

例子 总结

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}

private:

	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:

	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;

	// 自定义类型
	Time _t;

};

int main()
{
	Date d;

	return 0;
}

*****
~Time()
*****
  • 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
    因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
    而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time 类的析构函数即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
    注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

4.拷贝构造函数

4.1 概念

双胞胎, 两个外表一样却又独立思想的两个人. 这是一个多么美妙的事情啊~~
如果在我们创建对象时, 能不能创建一个 和已存在对象一模一样的新对象呢? ⇒

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

4.2 特性

拷贝构造函数的特性有:

  1. 拷贝构造函数是构造函数的一种重载形式
  2. 拷贝构造函数的参数只有一个 且 必须是类型对象的引用. 使用传值方式编译器会直接报错, 否则将会无限递归下去
  3. 如果没有显示定义, 编译器会生成默认的拷贝构造函数. 默认拷贝构造函数是一种浅拷贝(值拷贝)的行为⇒ 将对象按内存存储按字节完成拷贝(相当于memcpy)

4.3深刻解读—拷贝构造是构造的一种重载

class Date
{

public:

	//Date(int year = 2023, int month = 5, int day = 5)
	//{
	//	_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; // error C2512: “Date”: 没有合适的默认构造函数可用

}

上面的代码运行结果就可以看出: 如果我们手动写拷贝构造, 那么我们就必须要写一个构造函数 . 如果我们不写, 就会报错 — 该类没有合适的默认构造函数.
上面的代码中, 我们期望编译器给我们生成一个默认构造函数出来, 但是我们通过报错就可以看出它是把拷贝构造看成了一个构造, 它们的功能都是初始化对象, ⇒ 这样就肯定调用错误啊~~


拷贝构造是一种特殊的构造的几个点:

  1. 都是 类(形参列表), 都是无返回值, 无void
  2. 功能都是一样的: 都是在对象实例化时初始化对象

不同的几个点:
3. 形参列表不同: 构造函数⇒ 可以无参, 可以有参, 形参数量不确定; 拷贝构造⇒ 只有 且只能有一个形参, 且形参必须用 cons 类名& 来修饰
4. 构造函数是没有依赖性的, 而拷贝构造函数是有依赖性的⇒ 有一个已经存在的对象, 让这个对象来初始化新建的对象

4.4深刻理解—形参必须用引用来修饰

相信大家都有一些疑问: 自定义类型为什么非要传引用啊, 我直接传值不行吗?

我先问一下: 函数传参的实质是什么呢? ⇒ 让实参去初始化形参~

我们来假设一下如果自定义类型进行传值传参.
C++规定: 进行自定义类型传值传参时要先调用拷贝构造函数. 因为要借助拷贝构造函数去初始化形参, 然后再回到调用函数
C嘎嘎~~ [类 下篇]
根据上图推导过程, 就会发现⇒ 自定义类型如果是传值传参, 就会无限递归下去, 永远不会进去构造函数内部⇒ 所以编译器就会自动把他停止掉⇒ 所以, 即使我们自定义类型 传值传参就不会导致栈溢出~~

那么, 我们自定义类型该怎么传参呢??

  1. 实参传指针 — — 因为不管任何指针都是内置类型, 直接拷贝地址
  2. 形参用引用 — — 上面的主要原因是进入不了构造函数内部, 直接形参用引用修饰呗 <== 语法上, 引用并不会开辟空间, 直接进入拷贝构造函数内部

补充:

  • 建议用引用修饰形参的时候, 再加上一个 const ⇒ 这样就能自动检查是否写错方向
    C嘎嘎~~ [类 下篇]
  • 自定义类型传值传参赋值 都是要调用拷贝构造的, 因为中间要生成一个临时拷贝来充当中间量, 这时候就要用到拷贝构造~~

4.5深刻理解—编译器自动生成的默认拷贝构造函数

前面, 已经知道编译器生成的默认拷贝构造函数是对内置类型也做了处理 — 浅拷贝, 而且非常爽~, 那我们可不可以直接偷懒, 不写拷贝构造函数呢?
答案是不行的, 因为是浅拷贝, 相当于 memcpy, 如果遇到有资源申请的类型, 这样是非常危险的~
C嘎嘎~~ [类 下篇]
总结:

默认拷贝构造函数对内置成员完成浅拷贝, 这个也叫做值拷贝; 自定义类型会调用他的拷贝构造.

换句话说:

如果成员都是没有资源申请的类型, 建议直接偷个懒, 使用默认构造函数; 如果成员里面有资源申请的类型, 建议自己写一个深拷贝构造(深拷贝构造这个以后再说)~~


好大喜功则为宇宙汪洋所吞没,开动脑筋则领悟世界。
——帕斯卡《感想录》
文章来源地址https://www.toymoban.com/news/detail-435494.html

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

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

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

相关文章

  • 再探C++——默认成员函数

    目录 一、构造函数 二、析构函数 三、赋值运算符 四、拷贝构造 如果一个类中没有成员,我们称为空类。空类,也存在6个默认的类成员函数。 默认成员函数:用户不显示地写,编译器会 默认生成 的函数叫做默认成员函数。 6个默认成员函数: 构造函数:完成对象初始化 

    2024年02月14日
    浏览(37)
  • 类的默认成员函数

    为什么会有构造函数和析构函数呢? 1、初始化和销毁经常忘记 2、有些地方写起来很繁琐. Stack有了构造和析构,就不怕忘记写初始化和清理函数了,也简化了 例如在队列oj时,忘记释放,造成内存泄漏 主要任务:初始化对象 我们不写,编译器会自己生成 特征: 函数名和类

    2024年02月01日
    浏览(32)
  • C嘎嘎~~ [类 下篇]

    如果一个类中什么都没有, 简称为空类 空类中真的就什么都没有吗? 并不是, 任何类在什么都不写的时候, 编译器会自动生成 6 个默认成员函数 ==默认成员函数: 用户没有显示实现, 编译器生成的成员函数称为默认成员函数 就是这个6个默认成员函数, 如果你没有定义出来, 编译器

    2024年02月03日
    浏览(25)
  • [C++]六大默认成员函数详解

    ☃️个人主页:fighting小泽 🌸作者简介:目前正在学习C++和Linux 🌼博客专栏:C++入门 🏵️欢迎关注:评论👊🏻点赞👍🏻留言💪🏻 如果一个类中什么都没有,简称空类。 但它并不是什么都没有,任何类在什么都不写的情况下, 编译器会自动生成以下6个默认成员函数。

    2024年02月04日
    浏览(40)
  • C++类的默认成员函数

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

    2024年02月02日
    浏览(40)
  • C嘎嘎~~ 【初识C++ 下篇】

    相信大家小时候, 肯定有小名、绰号、亲朋好友的昵称… … 这些称呼,在一定程度上就是你自己本人。 假如,你的小名叫做二蛋, 别人喊二蛋的时候, 你就会不由自主地回头去确定是否是在喊你… 想想这些, 儿时的回忆就渐渐涌上心头, 时而捧腹大笑, 时而陷入沉思。

    2024年02月05日
    浏览(33)
  • 【类和对象(中)】六大默认成员函数

    本文继类和对象上,开始讲述默认成员函数。 默认成员函数是:我们不具体写,编译器会自动生成的函数叫默认成员函数。 构造函数是类的一个默认成员函数,它虽然叫构造函数,但它的作用并不是构造一个对象,而是初始化一个对象。 它与Init函数不同, 每次实例化一个

    2024年02月03日
    浏览(38)
  • 【C++】类的默认成员函数(下)

    🔥 博客主页 : 小羊失眠啦. 🎥 系列专栏 : 《C语言》 《数据结构》 《C++》 《Linux》 《Cpolar》 ❤️ 感谢大家点赞👍收藏⭐评论✍️ 本章主要内容为认识与学习C++非常重要的概念—— 运算符重载 。通过 日期类 的实现,逐步学习各个运算符重载的实现方法即含义。6个默

    2024年03月18日
    浏览(42)
  • C++中类的6个默认成员函数 【拷贝构造函数】

    在前几章学习对象的时候,我们有的时候需要一个与已存在对象一某一样的新对象 那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢? 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时

    2024年02月20日
    浏览(51)
  • C++:类的六个默认成员函数

    个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C++》 本篇博客作为C++知识总结,我们来认识类的六个默认成员函数。 下面我主要以日期类作为示例显示。 构造函数 是一个特殊的成员函数,名字与类名相同,创建类类型对象时(实例化类)由编译器自动调用,以保

    2024年02月08日
    浏览(50)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包