类有6个默认成员函数
如果一个类什么成员都没有,称为空类。空类中什么都没有吗,并不是的,任何一个类在我们不写的情况下会自动生成以下6个默认成员函数。
class Date
{
};
1、构造函数
概念:构造函数是用来初始化的。
class Date
{
public:
void InitDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.InitDate(2022, 6, 15);
d1.PrintDate();
return 0;
}
在日常的操作中,大家都会或多或少的忘记去初始化了,然后去访问它就会出现随机值或者会出现崩溃。我们想一下,在c语言的时候实现一个Stack的数据结构,如果忘记去初始化的话,在一个随机的指针中去插入一个数据,这就是野指针了。
忘记初始化是正常的事,那有没有什么办法是可以来让它自动来初始化的呢?答案是有的。构造函数就是来解决这种问题的。
1.1构造函数特性
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载
class Date
{
public:
//构造函数:初始化
Date(int year=0, int month=0, int day=0)
{
_year = year;
_month = month;
_day = day;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //会自动调用构造函数
d1.PrintDate();
return 0;
}
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
为什么会是随机值呢?是这样的,c++会把类型分为内置类型、自定义类型。内置类型就是语法上已经定义好了的类型如:int/char等等。自定义类型:使用class、struct自己定义的类型。默认生成的构造函数对于内置类型成员变量不作处理,对自定义类型成员变量才会处理。看看下面的程序,就会发现编译器生成默认的构造函数是去自定类型成员_t调用的它的默认成员函数。
总结:
如果一个类中的成员全是自定义类型,就会自动生成默认构造函数。如果有内置类型的成员就得自己实现构造函数了。
2.析构函数
2.1概念:
析构函数:与构造函数相反,析构函数不是用来完成对象的销毁。而是对象在销毁的时候会自动调用析构函数来完成类的一些资源清洗工作。在数据结构中实现一个动态顺序表,有顺序表的初始化就有顺序表的销毁,而这个顺序表的销毁是用来干什么的呢:是用来释放掉动态开辟的内存空间以及一系列的置空置零。因为经常会忘记释放动态开辟的内存空间,从而导致内存泄漏,所以就有了析构函数来防止这种情况,在对象生命周期结束的时候就会自动调用析构函数来完成一系列的动态内存空间销毁等。
2.2特征:
1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(这里的自动生成上面有涉及,同理)。
4. 对象生命周期结束时,C++编译器会自动调用析构函数。
class SeqList
{
public:
//构造函数:初始化
SeqList(int capacity = 10)
{
_data = new int[10];
_capacity = capacity;
_sz = 0;
}
//析构函数:清理资源
~SeqList()
{
delete _data; //释放动态开辟的空间
_capacity = 0;
_sz = 0;
}
private:
int* _data;
int _capacity;
int _sz;
};
int main()
{
//出了作用域生命周期结束就会自动调用析构函数来进行资源清洗
if (1)
{
SeqList s1;
}
return 0;
}
补充:如存在多个对象时,谁先调出析构函数?跟栈结构一样,后进先出。后实例化的对象先调出析构函数。
3.拷贝构造函数
3.1概念:
拷贝构造函数:就是把之前创建好了的同一类对象用来初始化新创建的对象。
3.2特征:
1. 拷贝构造函数是构造函数的一种。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_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(2022,6,17);
//把已创建好了的对象d1初始化要新创建的对象d2,叫拷贝构造
Date d2(d1);
return 0;
}
为什么拷贝构造函数使用传值传参会出现无穷递归呢?
如果类中没有显式定义拷贝构造函数的话,且类中成员都是内置类型,编译器会生成默认的拷贝构造函数。生成的默认拷贝构造函数对于内置类型的成员会完成值拷贝、浅拷贝。而对于自定义类型会去调用拷贝构造函数。
4.赋值运算符重载:operator
4.1运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
4.2注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
- 操作符有一个默认的形参this,限定为第一个形参
- .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
如果我们想用d1和d2进行比较大小的话,会怎么样呢?
既然自定义类型不支持使用运算符来比较的话,比较大小是不是就没办法了,其实不然,是有个办法能很好的解决的。为了自定义类型可以使用各种运算符,运算符重载operator出现了。
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载
bool operator>(const Date& d1) //编译器会处理成这样:bool operator>(Date* const this,const Date& d)
{
//不是说自定义类型不能使用运算符的吗,但类中的成员是内置类型的啊,所以是可以使用的。
if (_year > d._year
|| _year == d._year && _month > d._month
|| _year == d._year && _month == d._month && _day > d._day)
{
return true;
}
else
{
return false;
}
}
bool operator==(const Date&d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 6, 19);
Date d2(2022, 6, 20);
if (d1 > d2) //编译器会处理成这样:if(d1.operator(&d1,d2))
{
cout << "d1 > d2" << endl;
}
else
{
cout << "d1 < d2" << endl;
}
return 0;
}
4.3赋值运算符重载
赋值运算符主要有四点:
1. 参数类型
2. 返回值
3. 检测是否自己给自己赋值
4. 返回*this
5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
//赋值重载
Date& operator=(const Date&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
void PrintDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 6, 19);
Date d2(d1); //拷贝构造:一个已经存在了的对象d1去初始化另一个要创建的对象d2
Date d3;
d3 = d1; //赋值重载:两个已经存在了的对象之间赋值
return 0;
}