目录
1类在创建时,即使是一个空类,其中也包含了六个默认的成员函数,这六个成员函数既可以自定义,也可以不写,直接使用默认的函数。但是在某些特殊的情况下可能会发生意想不到的事情。这里我们介绍一下这六个默认成员函数的功能:
一、构造函数
构造函数的功能:构造函数是用来初始化成员变量,并不是用来开辟成员的空间的函数
构造函数的特点:
- 函数名和类名相同
- 函数没有返回值
- 对象实例化时自动调用
- 可以进行函数重载
- 类中必须创建默认构造函数;如果没创建默认构造函数,会自动生成默认构造函数
类的三种默认构造函数
类有三种默认构造函数,分别是:无参类型、全缺省值、自动生成的默认构造函数。这三种默认函数一个类中只能出现一种,出现一种以上系统会报错
//类中的默认构造函数
class Time
{
public:
//无参,默认构造函数
Time()
{
_hours = 0;
_minutes = 0;
_second = 0;
}
//全缺省,默认构造函数
Time(int hours = 0, int minutes = 0, int second = 0)
{
_hours = hours;
_minutes = minutes;
_second = second;
}
//不写默认构造函数,编译器会默认生成一个
private:
int _hours;
int _minutes;
int _second;
};
使用默认生成的默认构造函数的注意事项:
默认生成的默认构造函数不会处理内置类型,会处理自定义类型;会调用自定义类型的默认构造函数对自定义类型的数据进行初始化。
二、析构函数
析构函数的功能:完成对象中的成员变量的销毁工作,不是释放对象的空间。
析构函数的特点:
- 析构函数就是类名前面加按位取反操作符:~Time()
- 析构函数在对象销毁时会自动调用
- 析构函数不能重载
- 析构函数不写会默认生成,默认生成的析构函数:内置类型不处理,自定义类型调用自定义类型的析构函数
- 一般情况下不需要写析构函数;当开辟新空间时,一定要写析构函数,防止出现内存泄漏
class Time
{
public:
~Time()
{
//类成员没有开辟空间时,不需要处理……
}
private:
int _hours;
int _minutes;
int _second;
};
class Stack
{
public:
Stack(int capacity = 4)
{
//构造函数开辟了新空间
int* tmp = (int*)malloc(sizeof(int) * capacity);
if (tmp == nullptr)
{
perror("malloc fail\n");
exit(-1);
}
_a = tmp;
_top = 0;
_capacity = capacity;
}
~Stack()
{
//需要析构函数释放掉开辟的新空间
free(_a);
}
private:
int* _a;
int _top;
int _capacity;
};
三、拷贝构造函数
拷贝构造函数功能:将一个自定义对象中的数据拷贝给其他自定义对象,它的用法:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//这个地方是否可以使用传值传参呢?
// Date(Date d)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 6, 29);
Date d3;
//将d1拷贝到d2
Date d2(d1);
return 0;
}
this指针:
在进入拷贝构造正题之前,我想问一下各位读者。在以上例子中,d1要拷贝给d2,但是传参时仅仅传递了d1的值,函数是如何确保将d1拷贝给d2,而不是d3的呢?
答案是:类的成员函数中包含了隐藏的this指针,这个this指针是默认存在的,但对用户是透明的,用户可以进行使用。函数的实际写法其实是:
class Date
{
public:
//……
//用户不能写成这种形式,会报错
Date(Date* const this, const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
//……
};
int main()
{
Date d1(2023, 6, 29);
Date d3;
Date d2(&d2, d1);
Date d4 = d1;//在对象初始化的过程中使用赋值符号,会调用拷贝构造函数
return 0;
}
拷贝构造函数的特点:
- 是构造函数的重载
- 不写系统会默认生成
- 生成的拷贝构造函数,内置类型会按照字节依次进行拷贝;自定义类型会调用自定义类型的拷贝构造函数
拷贝构造要使用传引用传参:
传值传参时,形参时实参的临时拷贝,形参会在栈上开辟一块空间,假设开辟空间名字是tmp。在将变量d传给tmp,就相当于Date tmp(d)。是不是很眼熟?对了这就是调用了一次拷贝构造函数。调用拷贝构造函数之后,在进行传值传参,就实现了无穷递归。因此不能使用传值传参。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
//……
}
//传值传参时,形参时实参的临时拷贝,形参会在栈上开辟一块空间,假设开辟空间名字是tmp。在将变量d传给tmp,就相当于Date tmp(d)。是不是很眼熟?对了这就是调用了一次拷贝构造函数。调用拷贝构造函数之后,在进行传值传参,就实现了无穷递归。因此不能使用传值传参。
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
四、赋值运算符重载
赋值运算符功能:在初始化两个对象的前提下,将其中一个对象的值赋给另一个对象。
运算符重载关键字:operator
函数返回值 operator=(Date d1, Date d2, ……)
运算符重载函数返回值不固定,要根据运算符的结果自定义
=是要重载的运算符
Date d1, Date d2, ……运算符操作数,按照从左到右的顺序依次传参,别忘记隐藏的this指针
//传引用返回,避免拷贝返回值,提高效率
Date& operator=(const Date& d)
{
//可以先判断一下 *this != d
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
operator关键字除了能重载赋值运算符,也能重载其他运算符,其他运算符各位小伙伴们可以自行尝试呦!
五、普通对象和const修饰对象&运算符重载
const修饰成员
访问const修饰的变量中的函数,传递过去的this指针具有常属性。而函数中隐藏的this指针并不具有常属性,导致权限放大,编译器报错。而不用const修饰的变量也可以使用const修饰的成员函数,权限缩小不会产生影响
class Date
{
public:
//解决办法:在函数后面加const会修饰this指针 void Print()const
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
//……
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 6, 29);
const Date d2 = d1;
d1.Print();
d2.Print();//编译器报错,const修饰的变量具有常性,this指针存在权限放大
return 0;
}
普通对象和const修饰对象&运算符重载使用场景
普通对象和const修饰对象&运算符重载这两个默认函数的功能还是取对象所在位置的地址,一般的场景不会用到,使用默认的就可以。当需要隐藏地址时,可以自己写
Date* operator&()
{
return nullptr;
}
Date* operator&()const
{
return nullptr;
}