文章目录
一、类的6个默认成员函数
类中包含6个默认成员函数,分别为(1)构造函数 (2)析构函数 (3)拷贝构造函数 (4)赋值运算符重载函数 (5)const修饰的成员函数 (6)取地址及const取地址操作符重载,这6个默认成员函数就算我们不写,编译器也会自动生成。
1、构造函数
1.1、构造函数概念
构造函数的函数名和类名相同,并且没有返回值,在实例化对象时会自动调用,并且只会调用一次,用于初始化成员变量。具体看如下代码:
class person
{
private:
char* name;//姓名
int age;
int student_num;//身份证号
public:
person(char* _name = nullptr, int _age = 0, int _student_num = 0)
{
name = (char*)malloc(sizeof(char)* 20);
strcpy(name, _name);
age = _age;
student_num = _student_num;
}
};
int main()
{
person xiaomin("小明",18,2);
return 0;
}
上述代码中,我们可以通过person的构造函数,给对象小明进行初始化,我们可以得到对象xiaomin的name = “小明”,age = 18,student_num = 2。
1.2、构造函数特点
(1)函数名与类名相同。
(2)构造函数无返回值。
(3)实例化对象时编译器会自动调用构造函数,且只会调用一次。
(4)构造函数支持重载。
(5)无参的构造函数和全缺省的构造函数以及系统默认生成的构造函数都被称为默认构造函数。
(6)没写构造函数,系统会自动生成,并且初始化的成员变量被赋予随机值。
2、析构函数
2.1、析构函数概念
析构函数用于在程序结束对象被销毁时,释放对象申请的资源,析构函数的函数名为‘~’加类名,如果我们不写会自动生成,且在对象被销毁时自动调用,没有返回值。具体可见如下代码:
class person
{
private:
char* name;//姓名
int age;
int student_num;//身份证号
public:
person(char* _name = nullptr, int _age = 0, int _student_num = 0)
{
name = (char*)malloc(sizeof(char)* 20);
strcpy(name, _name);
age = _age;
student_num = _student_num;
}
~person()
{
name = nullptr;
free(name);
age = 0;
student_num = 0;
}
};
2.2、析构函数特点
(1)函数名为’~'加类名。
(2)析构函数无返回值。
(3)对象生命周期结束时编译器会自动调用析构函数。
(4)没写析构函数,系统会自动生成。
(5)先构造的对象后析构,后构造的对象先析构。因为函数调用时会建立栈帧,二栈中是先入后出的,因此是先析构后构造的顶部对象,后析构栈下方的对象,具体可看下图:
3、拷贝构造函数
3.1、拷贝构造函数概念
拷贝构造函数的函数名和构造函数一样,都是类名,所以拷贝构造函数可以作为构造函数的重载。拷贝构造只有一个形参,这个形参就是自己类实例化的对象的引用,具体见如下代码:
person(const person& _person)
{
name = (char*)malloc(sizeof(char)*20);
strcpy(name, _person.name);
//name = _person.name;浅拷贝,析构时会析构两次目标地址,导致报错
age = _person.age;
student_num = _person.student_num;
}
//使用方法
person xiaomin("小明",18,2);
person xiaomin1(xiaomin);
根据上述代码操作,最后得出的结果,xiaomin和xiaomin1两个成员变量都相同。
3.2、拷贝构造函数特点
(1)拷贝构造和构造函数名相同,属于构造函数的一个重载形式。
(2)拷贝构造没有返回值。
(3)拷贝构造必须传引用,因为在拷贝构造时,传值而不是传引用会使编译器进行传值拷贝,生成临时对象,然后传入拷贝构造函数作为形参,而又遇到传值,就引发无穷从调用,具体可见下图。
(4)如果自己不写拷贝构造函数,编译器会自动生成,但自动生成的拷贝构造函数遇到有指针类型的成员变量会出错,因为系统默认生成的拷贝构造函数只会进行浅拷贝,因此拷贝构造函数一般需要自己写。
4、运算符重载函数
4.1、运算符重载函数概念
为了增加代码的可读性,我们可以引入运算符重载的函数,其目的是让类可以直接通过运算符进行操作。具体代码如下:
class person
{
private:
char* name;//姓名
int age;
int student_num;//身份证号
public:
person(char* _name = nullptr, int _age = 0, int _student_num = 0)
{
name = (char*)malloc(sizeof(char)* 20);
strcpy(name, _name);
age = _age;
student_num = _student_num;
}
bool operator==(const person& d)
{
return age == d.age;
}
~person()
{
free(name);
name = NULL;
age = 0;
student_num = 0;
}
};
int main()
{
person xiaomin("小明",18,2);
person xiaohon("小红", 18, 2);
cout << (xiaomin == xiaohon) << endl;
return 0;
}
假设我们要对比实例化的对象小明和小红的年龄,我们可以直接通过==进行判断,这就很方便,而其他大于小于等等运算符重载函数跟上面的写法类似,但.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
还有一个常用的运算符重载函数,就是赋值运算符重载函数,具体可见下面代码:
person& operator=(const person& _person)
{
if (this != &_person)//不要自己给自己赋值
{
name = (char*)malloc(sizeof(char)* 20);
strcpy(name, _person.name);
//name = _person.name;
age = _person.age;
student_num = _person.student_num;
}
return *this;
}
//使用
person xiaomin("小明",18,2);
person xiaohon("小红", 18, 2);
xiaomin = xiaohon;//让小明等于小红,运算符左边为this,右边为需要传入的形参
4.2、赋值运算符重载函数特性
(1)不写的话,编译器自动生成。
(2)赋值运算符左边为第一个形参this,右边为第二个形参,就是右边赋值给左边。
(3)需要注意深浅拷贝问题。
(4)返回值最好传引用,传参最好加上const修饰。
(5)拷贝构造函数和赋值运算符函数的区别:拷贝构造是用已经存在的对象去构造新的对象,而赋值运算符重载函数是两个都存在的对象,将一个对象赋值给另一个对象。
5、const成员
5.1、const修饰类的成员函数
用const修饰类的成员函数就是在成员函数的右边加入const,具体操作如下:
void print()const
{
cout << "hallo" << endl;
}
使用const修饰成员函数后,相当于修饰了类成员函数隐含指针this,使得成员函数不能使用this指针对对象进行修改。
5.2、面试题
(1)const对象可以调用非const成员函数吗?
不可以,属于权限放大,一个是只读,一个是可读可写
(2)const成员函数内可以调用其它的非const成员函数吗?
不可以,属于权限放大,一个是只读,一个是可读可写
(3)非const对象可以调用const成员函数吗?
可以,属于权限缩小,可读可写调用只读,权限变为只读
(4)非const成员函数内可以调用其它的const成员函数吗?
可以,属于权限缩小,可读可写调用只读,权限变为只读
6、取地址及const取地址操作符重载
一个是取地址,一个是const取地址,具体操作如下:
#include<iostream>
using namespace std;
class person
{
public:
person* operator&()
{
return this;
}
const person* operator&()const
{
return this;
}
private:
char* name;
int age;
int student_num;
};
这两个默认成员函数一般不用重新定义,编译器默认生成。