前言:
在前面文章中,我们已经讲了类与对象的思想和类与对象的一些基本操作,接下来这篇文章我们将讲解以下类与对象的六个默认成员函数(注意:这部分是类与对象的核心之一,理解这些默认成员函数才有助于我们更好的使用这些默认成员函数)
一、默认成员函数是什么?
在一个类中,我们一般称呼里面的变量等统统为成员,自然函数称为成员函数,变量称为成员变量
class A { public: int Add(int x,int y) //成员函数 { return x + y; } private: int a; //成员变量 };
而默认成员函数就是不用写,编译器会自动生成的函数,比如上面写的Add函数如果为默认生成函数,那么我们可以不进行声明定义,直接调用该函数,具体操作我们可以往下看
二、默认成员函数有哪些?
在解答这个问题之前,我们首先先来考虑,为什么语法上会创建这种默认成员函数,其实就是因为人的惰性,如果一个函数每个类都需要调用或者有可能调用到,那我们为什么不可以通过底层的一些操作,让每个类中都自动生成这些成员函数,这就是默认成员函数
通过上面我们应该明白,默认成员函数一定是所有类中都需要的,那主要有哪些呢?
主要有以下六种:
三、六种默认成员函数
1、构造函数
1.1 构造函数的作用
构造函数是用来初始化的,初始化对于每个类对象都是不可或缺的
比如Date类:
class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date a1; a1.Init(2023, 2, 4); Date a2; a2.Init(2024, 2, 4); return 0; }
当我们创建Date类的成员时,比如例子中创建的a1、a2,在创建后我们需要对其初始化,但是每一个类成员我们都需要调用Init函数,这样就会显得十分麻烦,我们是否可以通过某种操作直接在创建类变量的同时进行初始化,这就是构造函数诞生的原因
1.2 构造函数的用法
注意事项:
1、首先,我们要知道构造函数其实就是特殊的成员函数,它还是封装在类中的
2、因为我们要实现在创建类变量的同时进行初始化,所以构造函数的名字与类名相同
3、构造函数可以是半缺省或者全缺省的
4、一个类中只能有一个构造函数,一旦自己写了编译器就不会生成默认构造函数
构造函数的形式如下:
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _year = year; } void Print() { cout << _year << " " << _month << " " << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2024, 5, 1); d1.Print(); return 0; }
运行结果:
这个就是构造函数的形式,就是将函数名与类名一致,同时不需要返回值,类型上与void一致,只是没有写出来,上面写的是带上形参的,但是构造函数是支持半缺省或者全缺省的,如下所示:
Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //半缺省 Date(int day) { _year = 2024; _month = 5; _day = day; } //全缺省 Date() { _year = 2024; _month = 5; _day = 1; }
1.3 默认构造函数
构造函数在类中实际上是可以自动生成的,当我们不去写默认构造函数时,它就会在类中自动生成,但我们需要注意的是,默认生成的构造函数是无参的,且它会初始化一个随机值
例如:
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; }
在这个程序中,我们并没有写构造函数,这是它就会调用默认构造函数并初始化一个随机值
可能有些朋友会说,既然初始化的是随机值,那跟没有初始化不是没有区别吗?那默认构造函数不是没有用吗?
其实默认构造函数的用处不在于这里,当我们的类成员中都是基本类型的时候,默认成员函数是没什么用,但当我们的类成员中有自定义类型时,默认成员函数就十分关键了
至于原因如何,我们在下面讲
2、析构函数
2.1 析构函数的作用
析构函数的作用与构造函数正好相反,析构函数是程序运行结束时,编译器会自动调用析构函数,对类变量中的资源进行清理,析构函数是否要写也是分情况的
2.2 析构函数的用法
注意事项:
1、析构函数是特殊的类成员函数,还是封装在类中的
2、析构函数的命名规则就是:~类名()
3、当要清理的类成员中涉及到资源申请时,就必须将析构函数写出来,此时默认调用是不满足的
比如栈(Stack):
class Stack { public: Stack(int capacity) //构造函数 { _capacity = capacity; _arr = (int*)malloc(sizeof(int) * _capacity); _size = 0; } ~Stack() //析构函数 { free(_arr); _arr = nullptr; _capacity = 0; _size = 0; } private: int* _arr; int _capacity; int _size; }; int main() { Stack s(3); s.~Stack(); return 0; }
2.3 默认析构函数
对于上面那种需要申请资源的类类型,我们必须将析构函数写出来,但是对于并没有申请资源的类类型,我们就可以不写析构函数,让编译器默认生成
比如日期类:
class Date { public: ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; return 0; }
运行结果:
在这个程序中,我们并没有调用析构函数,但是通过运行结果我们可以发现编译器自动调用了析构函数
3、拷贝构造函数
3.1 拷贝构造函数的作用
顾名思义,拷贝构造函数的作用就是将一个已经构造好的函数拷贝给另一个函数,
拷贝构造函数只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存
在的类类型对象创建新对象时由编译器自动调用 。
3.2 拷贝构造函数的用法
class Date { public: Date(int year, int month, int day) { _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(2024, 5, 1); Date d2(d1); return 0; }
拷贝构造函数其实就是复制,将一个类类型的变量中的值复制给另一个类类型的变量,需要注意的是当涉及到资源申请时要注意写的方式
3.3 默认拷贝构造函数
当对于没有申请资源的类时,我们进行拷贝复制时是可以不用写拷贝构造函数的,可以让编译器默认生成
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 d1(2024, 5, 1); Date d2(d1); d2.Print(); return 0; }
运行结果: