【C++类和对象】类有哪些默认成员函数呢?(上)
目录
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;//赋值重载(复制拷贝) 两个已经存在的对象之间赋值
}
补充知识:空指针是不存在的吗?是存在的,空地址是一个存在的地址。