lesson03:类和对象(中)

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

2.构造函数

3.析构函数

4.拷贝构造函数

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

空类(类中一个成员都没没有)会有成员函数吗?

其实是有的!如果我们在类中什么都不写,编译器会自动生成6个默认成员函数(用户什么都不写,编译器自动生成的函数叫默认成员函数)。

它们分别是:

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值运算符重载函数

5.取地址操作符重载函数

6.const取地址操作符重载函数

2.构造函数

2.1定义

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:void Init(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(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}

构造函数是一个特殊的成员函数,是用来给对象的数据成员初始化的。(用处类似于上面的自定义成员函数Init,注意:这个Init不是构造函数!)

2.2特性

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};void TestDate()
{Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数
}int main()
{TestDate();return 0;
}

构造函数是特殊的成员函数,构造函数虽然叫构造函数,但它不是用来开空间创建对象的,而是用来初始化对象的。

1.构造函数函数名和类名相同

2.构造函数无返回值,也不用在定义时写返回值类型(简单的来说,连void都不要写,返回值类型直接空着)

3.实例化对象时编译器自动调用构造函数。(这一步是一定进行的,实例化对象时一定会调用构造函数),在对象整个生命周期内只能调用一次(在创建对象的时候必须调用一次,所以不能显示调用构造函数)。

4.构造函数可以重载。

5.在自定义函数TestDate中:

5.1因为d1后面什么都没写,所以实例化(创建)对象d1时,编译器将调用无参构造函数或全缺省构造函数,也就是第一个构造函数。

那为什么只能调用这两种呢?

解答:因为没有传递参数,其他种类的构造函数都要传参数啊。

5.2因为d2后面在括号里写了三个参数(不要问为什么这么写,这是规定),这两个参数将传递给构造函数,所以将调用需传递两个参数的构造函数,也就是第二个函数。

那为什么不能调用给其他的构造函数呢?

解答:以无参构造函数为例,也就是第一个构造函数,他没有形参去接受这两个参数。

那么一般的多参构造,以有4个参数的构造函数为例,它也是不可以的,传递的参数不够。

5.3那么所有的多参构造都不可以吗?

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day, int n = 5){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date d(1, 2, 3);return 0;
}

解答:其实不是的,像上面这样的半缺省构造函数可以只传三个参数,那么它就是可以的。

注意:看第8行和第12行,构造函数函数名前什么都不写,不要写类型

	Date d3();

注意:如果实例化对象时不给构造函数传参,一定不能写小括号。

这个是函数的声明,如果实例化对象这样写,无法和函数声明区分!

	Date d3(1, 2, 3);

但是,像这样给构造函数传参的,是可以写小括号的,函数声明要写明类型,而上面的代码没有写类型,所以这里是可以和函数声明区分开的。

6.如果用户显示定义了构造函数(就是你自己写了构造函数),编译器将不会生成默认构造函数(默认构造也没有形参,属于一种无参构造)。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year;int _month;int _day;
};int main()
{Date d3;return 0;
}

前面说过,实例化(创建)对象时编译器一定会自动调用构造函数,但是这里我们没有定义构造函数,会不会报错呢?

答案是不会的,Date类中有一个构造函数,它由编译器生成,我们是看不见的。在实例化d3时调用的是这个由编译器生成的构造函数,这个默认生成的构造函数是无参构造函数,不需要传参。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year;int _month;int _day;
public:Date (int year,int month,int day){_year = year;_month = month;_day = day;}
};int main()
{Date d3;return 0;
}

这段代码是编不过去的,为什么错了呢?编译器不是会生成默认的无参构造函数吗?

解答:要注意,由于我们自己定义了构造函数,编译器就不再生成默认构造函数了!现在只有一个要传三个参数的构造函数,必须传三个参数。 

7.默认构造函数能干什么呢?

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:void print(){cout << _year << ' ' << _month << ' ' << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d3;d3.print();return 0;
}

在visual studio 2022社区版上,上面的代码运行后,输出的是随机值,所以默认构造函数貌似没有初始化的功能,那么这个默认构造函数有什么用呢?

解答:c++把类型分为了内置类型(编译器自带的,如int,char等)和自定义类型(自己定义的,如类类型,结构体类型等,注意:凡是指针类型都是内置类型,如类指针类型,结构体指针类型也是内置类型),默认构造函数会自动调用自定义类型成员的构造函数

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

这里打印了“Time()”,说明自定义类型成员_t的无参构造函数1确实被调用了。

总结:在实例化(创建)对象时,构造函数对内置类型成员不作处理,对1自定义类型成员调用它的无参构造函数。

补充:由于标准未说明不允许对内置类型作处理,所以有些编译器会对内置类型作处理;但是标准也未说明一定要对内置类型作处理,有些编译器是不会对内置类型作处理的。

8.考虑到函数重载和调用歧义问题,无参构造和全缺省构造的总数最好不要超过1个

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
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;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1;
}
int main()
{Test();return 0;
}

如这里在实例化d1时,因为d1后面什么都没写,所以编译器要调用d1的无参构造函数或全缺省构造函数,由于这样的函数有两个,编译器不知道用哪个,所以这里就报错了。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
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;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1(1);
}
int main()
{Test();return 0;
}

但是如果我们在实例化对象d1时传一个参数,就可以躲过调用歧义,就可以编过去了,但是极度不建议这么写!!!

9.如果实例化(创建)对象时,当中有自定义类型成员,且该成员所对应的类没有无参构造或全缺省构造,编译器会直接报错。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Time
{
public:Time(int a){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

 但是,自定义类型成员并不是必须是含有无参构造函数或全缺省构造函数的对象,后面会讲到初始化列表,可以解决这个问题。

可能很多小伙伴已经想到办法了,像下面这样写不就行了?

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Time
{
public:Time(int a){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t(1);
};
int main()
{Date d;return 0;
}

 要注意啊,Time _t(1);这种写法是定义对象,类的声明中是不能定义数据对象或变量的,不过在类的成员函数的定义中是可以定义对象或变量的。

3析构函数

3.1概念

析构函数与构造函数的功能相反,构造函数不是创建对象的,析构函数也不是销毁对象的,析构函数的作用是完成对象中资源的清理工作,在对象生命周期结束时由函数自动调用

3.2特性

1.析构函数名就是在类名前加上~

2.无参数,无返回值类型(这里和构造一样,连void也不要写)

3.一个类只能有一个析构函数(因为析构函数没有参数,无法实现重载),若未显式定义,编译器会自动生成默认析构函数

4.对象生命周期结束时编译器自动调用析构函数。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(){cout << "Date()" << endl;_year = 1900;_month = 1;_day = 1;}~Date(){cout << "~Date()" << endl;_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};
void test()
{Date d;
}
int main()
{test();return 0;
}

对象d是在test函数中定义的局部变量,出函数后,自动调用析构函数;在这里,主函数并未结束,但已经调用了对象d的析构函数,可见,对象的析构函数不是一定在主函数结束时调用的。

5.默认的析构函数和默认的构造函数类似,对内置类型不做处理,对自定义类型自动调用它的析构函数。

补充:构造函数都会对自定义类型成员调用它的构造函数;析构函数都会对自定义类型成员调用它的析构函数。这样的特性并不是默认构造和默认析构所特有的,自定义的也有。

6.如果类中没有申请资源(如申请空间,打开文件等),析构函数没必要写,直接使用编译器生成的默认析构函数;有资源申请时一定要写,否则会造成内存泄漏等问题。

4.拷贝构造函数

4.1概念

拷贝构造函数时一个特殊的构造函数,主要体现在形参不同,它的作用也是初始化对象,它也拥有构造函数的特性(它也能对自定义类型成员自动调用它的构造)。

4.2特性

1.拷贝构造函数是构造函数的一个重载

2.拷贝构造函数只有一个形参,且一定是本类类型的引用,使用传值传参编译器会报错,因为会引发无穷递归。

3.拷贝构造函数不仅可以用普通构造函数的方式使用,而且可以用初始化普通变量的方式使用(如int型变量,char型变量等),如下图。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date d)//错误写法:编译报错,会引发无穷递归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 d2(2024, 4, 19);//Date d1(d2);与Date d1 = d2;是等价的,一般情况下写第二种,意思更加明确//Date d1(d2);Date d1 = d2;d1.print();return 0;
}

注意:

Date d1;
Date d2;
d2 = d1;

向这样分开写用的就不是拷贝构造函数了,而是使用了默认赋值运算符重载函数(后面会讲)。

3.若未显式定义拷贝构造函数,编译器会生成默认的拷贝构造函数

那默认的拷贝构造也和默认的构造一样吗?也是对内置类型不做处理吗?

解答:默认的拷贝构造函数会将参数按内存存储按字节序完成拷贝,这种拷贝叫浅拷贝,也叫值拷贝。顾名思义,就是将数据成员的值拷贝过去。

4.编译器生成的默认拷贝构造已经能完成拷贝了,还要显式实现吗?

解答:看情况,例如以下的就需要。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class arr
{
private:int* a;
public:arr(){a = (int*)malloc(10 * sizeof(int));}
};
int main()
{arr a1;arr a2 = a1;return 0;
}

可以看到,对象a1和对象a2中数据成员,也就是指针变量a是一样的,两个指针指向了同一块空间,这不是我们想要的,我们希望的是让编译器再开一块空间的。

有人就要问了,这也拷贝成功了,有什么不妥吗?

解答:这两个对象的指针变量a指向的是同一块空间,它们是共用这块空间的,会相互影响的。

所以这里需要我们显式写拷贝构造函数,手动开空间,完成深拷贝。

5.拷贝构造函数典型调用场景:

场景一:使用已存在对象创建新对象

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 0, int month = 0, int day = 0){cout << "Date()" << endl;_year = year;_month = month;_day = day;}Date(const Date& d){cout << "Date(const Date& d)" << endl;_year = d._day;_month = d._month;_day = d._day;}
};
void test(Date d)
{}
int main()
{Date d;Date d1 = d; //使用已存在对象创建新对象return 0;
}

场景二:函数传参时传递对象

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 0, int month = 0, int day = 0){cout << "Date()" << endl;_year = year;_month = month;_day = day;}Date(const Date& d){cout << "Date(const Date& d)" << endl;_year = d._day;_month = d._month;_day = d._day;}
};
void test(Date d)
{}
int main()
{Date d;test(d); //使用已存在对象创建新对象return 0;
}

场景三:函数返回值返回对象

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 0, int month = 0, int day = 0){cout << "Date()" << endl;_year = year;_month = month;_day = day;}Date(const Date& d){cout << "Date(const Date& d)" << endl;_year = d._day;_month = d._month;_day = d._day;}
};
Date test(Date d)
{return d;//函数返回值返回对象
}
int main()
{Date d;test(d); //使用已存在对象创建新对象return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浏览器渲染流程中的 9 个面试点

记得 08 年以前&#xff0c;打开网页的时候一个页面卡死整个浏览器凉凉。 这是因为当时浏览器是单进程架构&#xff0c;一个页面或者插件卡死&#xff0c;整个浏览器都会崩溃&#xff0c;非常影响用户体验。 经过了一代代工程师的设计&#xff0c;现代浏览器改成了多进程架构&…

【BUG】前端|GET _MG_0001.JPG 404 (Not Found),hexo博客搭建过程图片路径正确却找不到图片

我的问题 我查了好多资料&#xff0c;结果原因是图片名称开头是_则该文件会被忽略。。。我注意到网上并没有提到这个问题&#xff0c;遂补了一下这篇博客并且汇总了我找到的所有解决办法。 具体检查方式&#xff1a; hexo生成一下静态资源&#xff1a; hexo g会发现这张图片…

深入了解PBKDF2:密码学中的关键推导函数

title: 深入了解PBKDF2&#xff1a;密码学中的关键推导函数 date: 2024/4/20 20:37:35 updated: 2024/4/20 20:37:35 tags: 密码学对称加密哈希函数KDFPBKDF2安全密钥派生 第一章&#xff1a;密码学基础 对称加密和哈希函数 对称加密&#xff1a;对称加密是一种加密技术&…

【Pytorch】Yolov5中CPU转GPU过程报错完善留档归纳

Yolov5 从CPU转GPU Python多版本切换 Conda包处理 文章目录 Yolov5 从CPU转GPU Python多版本切换 Conda包处理1.Pytorch套件中存在版本不匹配2.numpy停留在3.8没跟上pytorch2.2.23.ModuleNotFoundError: No module named pandas._libs.interval4.ImportError: cannot imp…

【Ubuntu20.04+Noetic】UR5e+Gazebo+Moveit

环境准备 创建工作空间 mkdir -p ur5e_ws/src cd ur5e_ws/srcUR机械臂软件包 UR官方没更新最新的noetic的分支,因此安装melodic,并需要改动相关文件。 安装UR的模型配置包,包里面有UR模型文件,moveit配置等: cd ~/ur5e_ws/src git clone -b melodic-devel https://git…

SQLite轻量级会话扩展(三十四)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite R*Tree 模块&#xff08;三十三&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 1. 引言 会话扩展提供了一种方便记录的机制 对 SQLite 数据库中某些表的部分或全部更改&#xff0c;以及 将这些…

R语言中的execl数据转plink

文章目录 带出外部连接的方式添加列的方式从列表中选出对应的数据信息查看变量信息没有成功 带出外部连接的方式 点击这个黄色的按钮就可以弹出外部链接的方式 添加列的方式 创建一个数据框的方式 我们创建一个三行三列的数据方式 df <- data.frame(name c("Alice&…

拓展网络技能:利用lua-http库下载www.linkedin.com信息的方法

引言 在当今的数字时代&#xff0c;网络技能的重要性日益凸显。本文将介绍如何使用Lua语言和lua-http库来下载和提取LinkedIn网站的信息&#xff0c;这是一种扩展网络技能的有效方法。 背景介绍 在当今科技潮流中&#xff0c;Lua语言以其轻量级和高效的特性&#xff0c;不仅…

Swift-25-普通函数、闭包函数与Lamda表达式编程

函数 语法定义 先来看下swift中函数的定义&#xff0c;函数用关键字func来指定&#xff0c;语法相对复杂一点&#xff0c;主要有下列4种基本情况&#xff0c;还有比较复杂的&#xff0c;会在后续详细讲解。 无参函数定义 有参函数定义 一个简单的函数和函数调用示例如下&…

打印机扫描到共享文件夹教程(Win系统和Mac系统)

一&#xff0e;Windows系统扫描文件到共享文件夹。 1.同时按下键盘WinR键&#xff0c;输入control&#xff0c;点击确定。 2.点击类别&#xff0c;点击大图标&#xff0c;点击凭据管理器。 3.点击Windows凭据&#xff0c;点击添加Windows凭据。 4.internet地址或网络地址&…

【Godot4自学手册】第三十九节利用shader(着色器)给游戏添加一层雾气效果

今天&#xff0c;主要是利用shader给游戏给地宫场景添加一层雾气效果&#xff0c;增加一下气氛&#xff0c;先看一下效果&#xff1a; 一、新建ParallaxBackground根节点 新建场景&#xff0c;根节点选择ParallaxBackground&#xff0c;命名为Fog&#xff0c;然后将该场景保…

循环神经网络(RNN):概念、挑战与应用

循环神经网络&#xff08;RNN&#xff09;&#xff1a;概念、挑战与应用 1 引言 1.1 简要回顾 RNN 在深度学习中的位置与重要性 在深度学习的壮丽图景中&#xff0c;循环神经网络&#xff08;Recurrent Neural Networks&#xff0c;RNN&#xff09;占据着不可或缺的地位。自从…