C++初阶--类和对象(中)
向大佬学习 |
一、类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
class Date {};
二、构造函数
2.1 概念
对于以下Date类:
#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; }
对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值,不用写void
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
#include <iostream> using namespace std; class Date { public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = 1;_month = 1;_day = 1;} private:int _year;int _month;int _day; };void TestDate() {Date d1; // 调用无参构造函数Date d2(2022, 7, 17); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3(); }
调试监视:
#include <iostream> using namespace std; class Date { public:Date(int year=1, int month=1, int day=1){_year = 1;_month = 1;_day = 1;} private:int _year;int _month;int _day; };void TestDate() {Date d1(2022, 7, 17);Date d2;Date d3(2022); }
监视:
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
#include <iostream> using namespace std; 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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;}
6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。
#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; }
监视初始化情况:
注:默认生成构造函数时,内置类型成员不做处理,自定义类型成员回去调用它的默认构造函数
#include <iostream> using namespace std; class Date { public: // 不写 默认生成构造函数 private:int _year;int _month;int _day; };int main() {Date d1; }
注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在 类中声明时可以给默认值。
#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=2023;int _month=4;int _day=5;// 自定义类型Time _t; }; int main() {Date d;return 0; }
监视:
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
默认构造函数:
- 我们不写,编译器自动生成的那个
- 我们自己写的,全缺省构造函数
- 我们自己写的,无参构造函数
注:1和2可以同时存在,但调用时会引发歧义;1或3、2或3都不能同时存在
#include <iostream> using namespace std; class Date { public:// 1.无参构造函数Date(){}// 2.全缺省的构造函数Date(int year=1, int month=1, int day=1){_year;_month;_day1;} private:int _year;int _month;int _day; };int main() {Date d1; // 调用1还是2,不确定 }
默认构造函数特点:不传参数就可以调用
#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; }
三、析构函数
3.1 概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
注:什么时候清理?->对象销毁时->什么时候对象销毁?->对象的生命周期到了以后自动销毁->什么是生命周期?->作用域范围内就是生命周期
3.2 特性
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
#include <iostream> using namespace std; typedef int DataType; class Stack { public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 自定义的析构函数,非系统默认自动生成的~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}cout << "~Stack()" << endl;//检验是否调用了析构函数~Stack} private:DataType* _array;int _capacity;int _size; }; void TestStack() {Stack s;s.Push(1);s.Push(2); }int main() {TestStack(); }
运行结果:
说明即使在主函数及调用的函数内为写明调用析构函数,但在实际运行中,编译器自动依旧采用了它
5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器 生成的默认析构函数,对自定类型成员调用它的析构函数。
#include <iostream> using namespace std; 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类生成的默认析 //构函数 // 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
默认生成析构函数的特点:a、内置类型不处理 b、自定义类型会去调用它的析构
两个栈实现队列
同学们可以使用C++的方式,自己封装栈,实现上述oj题,深刻体会编译器生成析构函数的 作用。
6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
7.析构函数的调用顺序:先定义的先构造后析构,后定义的后构造先析构
#include <iostream> using namespace std;class A { public:A(int a = 0){_a=a;cout << "A(int a=0)->" << _a << endl;}~A(){cout << "~A()->" << _a << endl;} private:int _a; };int main() {A aa1(1);//先定义A aa2(2);//后定义return 0; }
运行结果:
原理:
注:编译器是提前就算好了这个栈帧里面的变量一共要开辟多少空间,所以提前就开辟好了空间。符合后进先出,也就是后定义先销毁 / 析构
提高难度:
#include <iostream> using namespace std;class A { public:A(int a = 0){_a=a;cout << "A(int a=0)->" << _a << endl;}~A(){cout << "~A()->" << _a << endl;} private:int _a; };A aa3(3);int main() {static A aa0(0);A aa1(1);A aa2(2);static A aa4(4);return 0; }
运行结果:
全局的先开始定义,并且在main函数之前就要初始化定义,因为全局和static修饰的都在静态区。
全局的和静态的都在静态区,程序结束才销毁。故它们三个排最后销毁/析构
提高难度:
#include <iostream> using namespace std;class A { public:A(int a = 0){_a=a;cout << "A(int a=0)->" << _a << endl;}~A(){cout << "~A()->" << _a << endl;} private:int _a; };A aa3(3);void f() {static A aa0(0);A aa1(1);A aa2(2);static A aa4(4); }int main() {f();f();return 0; }
运行结果:
构造顺序:3 0 1 2 4 1 2
析构顺序:2 1 2 1 4 0 3
四、拷贝构造函数
4.1 概念
在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
4.2 形式
void func()
{Date d1(2023,4,6);//构造函数Date d2(d1);//拷贝构造函数写法一Date da3=d2;//拷贝构造函数写法二
}拷贝构造函数写法的参考:
int a=10;
int b=a;//b把a给拷贝了
4.3 特征
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
#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;//把d1中的_year赋值给da中的_year_month = d._month;_day = d._day;} private:int _year;int _month;int _day; }; int main() {Date d1;Date d2(d1);return 0; }
注:加const的目的是缩小别名权限,从可读可写缩小为仅可读
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
#include <iostream> using namespace std; class Time { public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const 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 d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0; }
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。
4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
#include <iostream> using namespace std; // 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。 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; }
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
5. 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
#include <iostream> using namespace std; class Date { public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;} private:int _year;int _month;int _day; }; Date Test(Date d) {Date temp(d);return temp; } int main() {Date d1(2022, 1, 13);Test(d1);return 0; }
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。
6.传值传参与传引用传参对比:
#include <iostream> using namespace std;class A { public:A(int a = 0){_a = a;cout << "A(int a=0)->" << _a << endl;}A(const A& aa){_a = aa._a;cout << "A(const A& aa)->" << _a << endl;}~A(){cout << "~A()->" << _a << endl;} private:int _a; };A func3() {static A aa(3);return aa;//传值返回,会生成一个临时对象,作为func3的返回值 }A& func4() {static A aa(4);return aa;//返回的是aa(4)的别名 }int main() {func3();//传值传参cout << endl << endl;//间隔func4();//传引用传参return 0; }
运行结果:
五、赋值运算符重载
5.1 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。
#include <iostream> using namespace std; // 全局的operator== class Date { public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:,继续保持隐藏私密状态,则后面判断相等的bool函数无法访问成员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(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;//cout << operator==(d1,d2)<<endl; }
#include <iostream> using namespace std; class Date { public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year;&& _month == d2._month&& _day == d2._day;} private:int _year;int _month;int _day; };} int main() {Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;//cout << d1.operator==(d2)<<endl; }
C++初阶--类和对象(中)相关推荐
- 类Array对象中是否该直接使用Array的原型方法?
题目来自于掘金一位分享者的分享 , 我经实验认证写篇文章记之 var obj={'2' : 3,'3' : 4,'length' : 2,'splice' : Array.prototype.spli ...
- 类与对象(中) 构造函数和析构函数
目录 一.类的6个默认成员函数 二.构造函数 2.1定义 2.2特性 1. 函数名和类名相同: 2. 没有返回值: 3.对象实例化的时候编译器自动调用: 4.可以函数重载. 2.3 无参的构造函数和全 ...
- 《C++类和对象中的static、友元以及内存管理 总结》
学习完前面有关类和对象的基础知识,这里再对其中的某些容易出现的错误及经常性会遇到的问题进行阐述,加深对类和对象的理解是学好C++的基础. 文章目录 1.再次谈及构造函数 2.static成员 3.友元 ...
- 【c++师傅领进门,修行靠个人】第五篇:C++类和对象中的一些小细节
面向对象的总结 1 初始化列表 2 如何突破封装 3 了解静态成员 4 类也能套娃 5 面向对象总结 1 初始化列表 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成 ...
- 【类和对象(中)】六大默认成员函数
文章目录 前言 一.
- python的类和对象——类的静态字段番外篇
什么是静态字段 在开始之前,先上图,解释一下什么是类的静态字段(我有的时候会叫它类的静态变量,总之说的都是它.后面大多数情况可能会简称为类变量.): 我们看上面的例子,这里的money就是静态字段,首 ...
- python基础19 -------面向对象终结篇(介绍python对象中各种内置命令)
一.isinstance()和issubclass()命令 1.isinstance(对象,类型) 用来判定该对象是不是此类型或者说是该对象是不是此类的对象,返回结果为True和False,如图所示. ...
- day34-2 类和对象(重点)
目录 类 定义类和对象 __dict__ 和__class__ 创建对象时的底层运作 定义对象独有的特征 init __slots__(了解) 给对象添加属性时的底层运作 类 分类/类别 上述的代码( ...
- Map对象与实体类Object对象转换
方法一 fastjson 转换 <dependency><groupId>com.alibaba</groupId><artifactId>fastjs ...
最新文章
- eureka自我保护时间_Spring Cloud Eureka 自我保护机制
- 欧盟中止对中国数据卡产品“两反一保”调查
- CentOS进不了mysql
- CSS选择器的权重与优先规
- mallcloud商城基于SpringBoot2.x
- VMware快照功能与(非永久)永久磁盘详解
- RyuBook1.0案例三:REST Linkage
- python单例模式和装饰器
- 华为手机老是android自动升级,华为手机系统怎么升级 华为手机升级系统的两种方法...
- 双闭环矢量控制的电压型PWM整流器参数整定
- 简简单单几行Python代码就能暴力破解网站登录密码,真有这么强吗?
- 第四十九篇: JAVA加密解密之凯撒加密(Caesar cipher)算法
- 十分钟开发出神经网络五子棋(二)
- 更新提示!Chrome新的零日漏洞正被利用
- 【随手记】Oracle存储过程报错 Compilation errors for PACKAGE BODY
- Scrapy爬取IT桔子死亡公司库及资本机构数据
- 强烈建议使用Windows Live Writer发布日志
- Idea 遇到:com.sun.istack.internal不存在和程序包com.sun.image.codec.jpeg不存在
- Web服务器与Web容器的概念
- @font-face使用自定义字体
热门文章
- Cinema 4d 和 3ds Max:哪个软件更好?
- 人工访客系统服务器地址,获取访客本地域名解析服务器的系统
- Discuz 3.1 登陆ucenter验证码错误的解决
- LeetCode 面试题 17.07婴儿名字 (并查集+字符串处理+字典序)
- 用于汽车发动机整车系统检漏及压力测试快速密封连接器 GripSeal格雷希尔快速密封接头有哪些解决方案?
- 如何做出像【酷狗】显示歌词的功能(VC++)
- 网络教学资源平台设计与实现--用例图(初稿)
- Redis核心原理与应用实践
- 2020ICPC上海 B Mine Sweeper II(思维)
- 线程的常见的几种创建方式