C++初阶第五篇——类和对象(中)(构造函数+析构函数+拷贝构造函数+赋值操作符重载)
Posted 呆呆兽学编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶第五篇——类和对象(中)(构造函数+析构函数+拷贝构造函数+赋值操作符重载)相关的知识,希望对你有一定的参考价值。
⭐️今天我要和大家分享C++中类和对象中有关6个默认成员函数的相关知识。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code
目录
🌏类的6个默认成员函数
在一个类中,会默认生成6个默认成员函数,即使是空类也是如此。
分类如下:
🌏构造函数
概念: 一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,一般是用来初始化成员变量,且一个生命周期只调用一次。
构造函数特性:
- 函数名和类名相同
- 无返回值
- 对象实例化是自动调用
- 可以发生重载
class Date
public:
// 构造函数
// 1.无参构造函数
Date()
// 2. 有参构造
Date(int year, int month, int day)
_year = year;
_month = month;
_day = day;
// 3.全缺省
Date(int year = 1, int month = 1, int day = 1)
_year = year;
_month = month;
_day = day;
private:
int _year;
int _month;
int _day;
;
构造函数显示定义的三种方法:
- 无参构造函数(实例化对象时后面不跟括号,否则会变成函数的声明)
- 有参构造函数
- 全缺省构造函数
类中无构造函数时,编译器会默认生成一个无参的默认构造函数。
默认构造函数有三种:全缺省、不含参和编译器给的
在我们不实现构造函数时,编译器就会调用编译器默认生成的构造函数,看下面一串代码,编译器的默认构造函数能否实现成员变量的初始化呢?
class Date
public:
void Print()
cout << _year << "-" << _month << "-" << _day << endl;
private:
int _year;
int _month;
int _day;
;
int main()
Date d1;
d1.Print();
return 0;
我们再看运行结果:
3个值都是随机值,编译器的默认构造函数并没有进行对成员变量进行初始化,是不是说这个函数就没有什么用呢?
当然不能这么说,C++中把类型分为内置类型(基础类型)和自定义类型(class,struct等),编译器的默认构造函数对内置类型不处理,对自定义类型会调用它自己的默认构造函数,如果不存在(调有参时),编译器会报错。
看下面的代码,我给大家演示一下:class A public: A() cout << "A()" << endl; private: int _a; ; class Date public: void Print() cout << _year << "-" << _month << "-" << _day << endl; private: int _year; int _month; int _day; A _a; ; int main() Date d1; return 0;
代码运行结果如下:
显然是调用了_a的默认构造函数。
🌏析构函数
概念: 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
特性:
- 函数名前加一个‘~’
- 无参数,无返回值
- 一个类中有且只有一个析构函数,对象的生命周期结束是,编译器会自动调用
class Array
public:
Array(int length = 0)
_length = length;
_p = (int*)malloc(sizeof(int) * _length);
// 析构函数
~Array()
if (_p)
free(_p);
_p = nullptr;
private:
int _length;
int* _p;
;
如果我们没有实现析构函数,编译器会自动生成一个析构函数,且这个析构函数堆内置类型不处理,对自定义类型会调用它的析构函数,这一点和编译器生成的默认构造函数有点相似。
一个小思考
看下面一串代码,他们的构造和析构顺序是怎么样的?
class A public: A() cout << "A()" << endl; ~A() cout << "~A()" << endl; private: int _a; ; class B public: B() cout << "B()" << endl; ~B() cout << "~B()" << endl; private: int _b; ; int main() A a; B b; return 0;
代码运行结果如下:
构造的顺序和对象的创建顺序相同,析构和对象的创建顺序相反。(其实就是一个压栈的问题,先进后出)
🌏拷贝构造函数
概念: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特性:
- 拷贝构造函数是构造函数的一个重载形式
- 参数有且只有一个,且必须使用引用传参,使用传值会引发无穷递归
class Date
public:
// 拷贝构造
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;
AA _a;
;
下面解释为什么传值会无穷递归
如果未定义拷贝构造函数,编译器会默认生成一个拷贝构造函数,默认的拷贝构造函数会对内置类型按内存存储的字节序完成拷贝,对于自定义类型编译器会调用他们的默认构造函数,这种拷贝是浅拷贝。
浅拷贝有很大的潜在危险,看下面一串代码:class Array public: Array(int length = 0) _length = length; _p = (int*)malloc(sizeof(int) * _length); // 析构函数 ~Array() if (_p) free(_p); _p = nullptr; private: int _length; int* _p; ; int main() Array a1(10); Array a2(a1); return 0;
程序运行结果如下:
代码直接就崩了,这就是浅拷贝带来的影响。
🌏赋值云运算符重载
🌰运算符载
概念: 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数原型:返回值 operator操作符(参数列表)
需要注意的几点:
- 不能通过重载操作符来创建新的操作符,如operator@
- 必须含有一个类类型或枚举类型的操作数
- 不能改变内置类型的含义
- 作为类成员的重载操作符,隐含了一个Date* const this的形参,限定为第一个形参
- .*,::,?:,sizeof,.这五个运算符不能重载
class Date
public:
Date(int year = 1, int month = 1, int day = 1)
_year = year;
_month = month;
_day = day;
// operator==
bool operator==(const Date& d)
return _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(2022, 1, 20);
Date d2(2022, 1, 19);
cout << (d1 == d2) << endl;// ==>d1.operator==(d2)
return 0;
d1 == d2 <==> d1.operator==(d2)
两种调用方式等价
🌰赋值运算符重载
原型: 返回值 operator=(参数列表)
特性:
- 返回是*this
- 如果没有显示定义,编译器也会生成一个,按字节序拷贝
Date& operator=(const Date& d)
// 检测是否自己给自己赋值
if (this == &d)
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
🌰与拷贝构造函数的异同
相同:没有显示定义时,编译器都会默认生成一个,对于内置类型进行字节序的浅拷贝,对自定义类型会调用它自身的拷贝构造函数或operator=。
不同:
拷贝构造:用一个已经存在的对象初始化一个马上要创建的对象。
赋值重载:两个已经存在的对象之间进行赋值。
🌰前置++和后置++的实现
先看下面一个类:
class Person
public:
Person(string name, int age)
_name = name;
_age = age;
private:
string _name;
int _age;
;
前置++和后置++最大的区别就是返回值不同,前置是返回变化之后的值,后置是返回变化之前的值,两个在重载是,都是operator++,我们如何区分呢?
一般operator++默认是前置++,为了区分后置++,我们通常会在参数列表加一个占位参数,且这个参数必须是int类型的,从而构造成函数重载。实现如下:class Person public: Person(string name, int age) _name = name; _age = age; // 前置++ Person& operator++() _age++; return *this;// 返回变化之后的值,传引用 // 后置++ Person operator++(int) Person ret = *this; _age++; return ret;// 返回变化之前的值,传值 void Print() cout << _name << "-" << _age << endl; private: string _name; int _age; ; int main() Person p("wxj", 19); p.Print(); cout << "前置++" << endl; // 前置++ Person ret = ++p; ret.Print(); p.Print(); cout << "后置++" << endl; // 后置++ ret = p++; ret.Print(); p.Print(); return 0;
运行结果如下:
🌰const修饰类的成员函数
概念: 将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
一般我们再成员函数后面加const,就会使得this的类型变为 const Date* const ,也就是该成员函数中成员变量不能够被修改。
事例:
bool operator==(const Date& d) const // const Date* const this
return _year == d._year
&& _month == d._month
&& _day == d._day;
🌏取地址及const取地址重载
这两个函数一般自己不定义,都是编译器自己生成。
Date* operator&()
return this ;
const Date* operator&() const
return this ;
🌐总结
类和对象的大体内容就差不多是这些了,类和对象(下)还会继续介绍一些有关的零碎知识。喜欢的话,欢迎大家点赞支持~
以上是关于C++初阶第五篇——类和对象(中)(构造函数+析构函数+拷贝构造函数+赋值操作符重载)的主要内容,如果未能解决你的问题,请参考以下文章
C++初阶:类和对象(中篇)构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载