目录
1. 类的6个默认成员函数
2. 构造函数(*^▽^*)
2.1 概念
2.2 特性
3. 析构函数(*^▽^*)
3.1 概念
3.2 特性
4. 拷贝构造函数(*^▽^*)
4.1 概念
4.2 特性
5. 赋值运算符重载(*^▽^*)
5.1 运算符重载
5.2 赋值运算符重载
ヾ(๑╹◡╹)ノ"人总要为过去的懒惰而付出代价!ヾ(๑╹◡╹)ノ"
1. 类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
2. 构造函数(*^▽^*)
2.1 概念
对于data类:
class Date
{
public:
void Init(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;
d1.Init(2022, 7, 5);//初始化
d1.Print();
Date d2;
d2.Init(2022, 7, 6);//初始化
d2.Print();
return 0;
}
对于上面的这个代码,可以通过 Init 公有方法给对象设置日期,每次创建对象时都调用该方法设置信息。
2.2 特性
class Date
{
public:
// 1.无参构造函数
Date()//构造函数:函数名与类名相同,无返回值
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数,注意,不能写成Date d1();
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
}
默认构造函数:(1)无参的构造函数(2)全缺省的构造函数(3)C++编译器生成的无参的构造函数【即三种必须要有一种,如果没有默认的构造函数【写的构造函数不是无参的,也不是全缺省的】就会报错】
如果一个类中的成员全是自定义类型,我们就可以用默认生成的函数。如果有内置类型函数成员,或者需要显示传参初始化,那么都要自己实现构造函数。(需要传参初始化,需要自己实现构造函数)【大部分都是自己写构造函数】
C++编译器默认生成构造函数对内置类型函数成员变量不作处理,自定义类型成员会去调用它自己的默认构造函数。
注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在类中声明时 可以给默认值 。【注意:这里是声明,所以是一个缺省值,并不是初始化】
class Date
{
public:
Date()//无参的构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1;//这里会发生错误,默认构造函数只能有一个
}
运行时都会发生错误,出现冲突。
一般情况一个C++类,都要自己写构造函数。只有极少数情况下可以让编译器自动生成:
(1)类里面成员都是自定义类型成员, 并且这些成员都提供了默认构造函数。
(2)内置类型成员声明时给出缺省值。
3. 析构函数(*^▽^*)
3.1 概念
3.2 特性
4. 拷贝构造函数(*^▽^*)
4.1 概念
创建对象时,创建一个与一个对象一某一样的新对象。
4.2 特性
1. 拷贝构造函数是构造函数的一个重载形式。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_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;
Date d2(d1);
return 0;
}
传值方式:调用拷贝构造会调用拷贝构造函数,调用这个函数需要先传参,传参是一份临时拷贝,此时的状态是临时拷贝对象,相当于又是拷贝构造,又需要调用拷贝构造函数,此时就会引发无穷递归调用。
引用传参:就不会调用拷贝构造。
【一个对象初始化这个类的另一个对象,就是拷贝构造】【进行拷贝对象就是拷贝构造】
自定义类型对象,拷贝初始化要调用拷贝构造完成。
结论:一般的类,自己生成的拷贝构造就够用了,不需要自己写拷贝构造。但是像stack这样的类,直接自己管理资源,就需要自己实现深拷贝。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
5. 赋值运算符重载(*^▽^*)
5.1 运算符重载
//日期的判断是否相等
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
//放到类里面 Date,此时是在类外面的写法
//1.
if (operator==(d1, d2))
{
cout << "==" << endl;
}
//2/
if (d1 == d2)
{
cout << "==" << endl;
}
//1.和2.是等价的,编译器会处理成1.
(1)内置类型是可以直接用各种运算符,但是自定义类型是不可以直接用各种运算法的。为了让自定义类型可以使用各种运算符,所以就有了运算符重载。
(2)有多少个操作数就有多少个函数参数。(==两个操作数;++一个操作数;)
在类里面的写法:
//日期的判断是否相等
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//放到类里面 Date,此时是在类里面的写法
//1.
if (d1.operator==(d2))
{
cout << "==" << endl;
}
//2/
if (d1 == d2)
{
cout << "==" << endl;
}
//1.和2.是等价的,编译器会处理成对应重载运算法调用
如果两个代码都存在(编译器是可以通过的,因为符合函数重载),编译器优先使用类里面的运算符重载。
//判断日期小
bool operator<(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;
}
}
这里注意细节,不能想当然。
5.2 赋值运算符重载
1. 参数类型2. 返回值3. 检测是否自己给自己赋值4. 返回 *this5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
//d2 = d1;-> d2.operator(&d2, d1)
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
int main()
{
Date d1(2000, 8, 20);
Date d2(2000, 9, 17);
Date d3(d1);//拷贝构造,一个存在的对象去初始化另一个要创建的对象
d2 = d1;//赋值重载(复制拷贝) 两个已经存在的对象之间赋值
}
补充知识:空指针是不存在的吗?是存在的,空地址是一个存在的地址。