类的默认成员函数:我们不写,编译器会自动生成,我们写了就会调用我们写的。
1.构造函数
在定义对象时,构造函数会初始化该对象,他没有返回值,可以重载,可以有多种初始化的方式,可以有缺省参数(不能同时有无参的构造函数)。
构造函数的定义及使用:
- 无参构造函数:
class Date
{
private:
int year;
int month;
int day;
public:
Date()
{
year = 2008;
month = 8;
day = 8;
}
};
int main()
{
Date d1; //调用无参的构造函数。
return 0;
}
- 带参构造函数:
class Date
{
private:
int year;
int month;
int day;
public:
Date(int _year,int _monoth,int _day)
{
year = _year;
month = _month;
day = _day;
}
};
int main()
{
Date d1(2008,8,8); //调用带参构造函数。
return 0;
}
- 缺省构造函数
class Date
{
private:
int year;
int month;
int day;
public:
Date(int _year = 2001,int _monoth = 8,int _day = 8)
{
year = _year;
month = _month;
day = _day;
}
};
int main()
{
Date d1(2008,8,8); //用传入的参数初始化对象d1。
Date d2(); //用缺省参数初始化对象d2。
return 0;
}
注意:全缺省的构造函数和无参构造函数不能同时出现,因为创建对象时如果不传参数,编译器就不知道该用哪个构造函数来初始化该对象。
- 默认构造函数
我们知道,当自己不定义构造函数时,编译器也会自动生成默认的构造函数,但是默认构造函数对我们的数据并不会赋特定的值,那么他有什么用呢?
通过这段代码可以清晰地了解:
class Time
{
public:
time()
{
hour = 2;
minute = 24;
second = 35;
}
private:
int hour;
int minute;
int second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time t;
};
int main()
{
Date d1;
return 0;
}
由此可见,默认构造函数的作用:在创建对象时,Date的默认构造函数会自动调用自定义类型Time的构造函数,对其进行初始化。
2.析构函数
没有参数和返回值,一个类只有一个,不写系统会默认生成,对象生命周期结束时,c++编译器会自动调用析构函数。
作用:默认生成的析构函数清理自定义类型,调用自定义类型的析构函数清理它,不处理内置类型。也可以自己编写析构函数,释放开辟出来的资源。
析构函数的声明与定义:
class person
{
public:
person(const char* _str = "Tom")
{
str = (char*)malloc(strlen(_str) + 1);
strcpy(str, _str);
}
~person() //这里就是析构函数的声明和定义
{
free(str);
}
private:
int age;
char* name;
};
int main()
{
person p1;
return 0;
}
当对象p1生命周期结束时,编译器就会自动调用析构函数,释放掉开辟出来的空间。
析构函数的调用规则为:
先构造的对象后调用析构函数
例:
class A
{};
class B
{};
class C
{};
class D
{};
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
构造的先后次序为:全局>局部;静态>普通。
因此上述代码中四个对象构造的顺序为:c d a b
所以其析构函数调用的顺序为:b a d c
3.拷贝构造函数
是构造函数的重载,有一个参数,类型为本类型对象的引用(要用const修饰,避免该对象被误修改)。没有显式定义时系统会自动生成。
应用场景:用已存在的类类型对象创建新对象时由编译器自动调用
class Date
{
private:
int year;
int month;
int day;
public:
Date(int _year = 2021, int _month = 4, int _day = 14)
{
year = _year;
month = _month;
day = _day;
}
};
int main()
{
Date d1;
Date d2(d1); //编译器会调用该类的默认拷贝构造函数,把d1的值拷贝给d2.
return 0;
}
我们再来自己实现上述拷贝过程:
Date(const Date& d)
{
year = d.year;
month = d.month;
day = d.day;
}
只需要在类中定义这样的拷贝构造函数即可。
这里有几个需要深究的问题:
1. 为什么调用拷贝构造函数时要传引用呢?
如果传值传参,会发生无穷递归!详解如下图:
传引用则不会出现实参给形参拷贝,上述问题就不会发生。
2. 系统默认生成的拷贝构造函数可以用,为什么我们还要自己定义呢?
默认的拷贝构造函数在对对象初始化时,会把一个对象所有成员变量的值原封不动的拷贝给当前要初始化的对象,这里就出现一个问题,如果将一个指针变量的值拷贝给当前对象,就会出现两个指针指向同一块数据,下面我们分析这种情况会带来的后果:
4.赋值运算符重载
我们先来谈谈运算符重载:
运算符重载的函数原型为:返回值类型 operator操作符(参数列表)
运算符重载让自定义类型可以像内置类型一样使用运算符。
例:
int main()
{
Date d1;
Date d2;
cout << (d1 == d2) <<endl;
return 0;
}
这里d1==d2的意思是比较两个日期是否是同一天,我们需要自己实现这个功能:
bool operator==(const Date& d) //对 == 这个运算符进行了重载,它可以适用于我们的自定义类型
{
return year == d.year&&month == d.month&&day == d.day;
}
传引用的作用是防止上面讲过的无限递归调用拷贝构造函数情况发生。
注:注意以下5个运算符不能重载:
.* | :: | sizeof | ?: | . |
---|
4.1赋值运算符重载:
注意事项:
- 先判断是否自己给自己赋值
- 参数类型为实参的引用,防止无限递归拷贝构造函数
- 返回值类型为当前类的引用,防止无限递归拷贝构造函数
- 返回值为*this
- 不显式定义系统会默认生成,如果对象中有指针类变量就必须自己定义,否则会出现与默认拷贝构造函数中多个对象的指针变量指向同一份内存的情况
Date& operator=(const Date& d)
{
if (this != &d)
{
year = d.year;
month = d.month;
day = d.day;
return *this;
}
}
使用时需要注意:
int main()
{
Date d1;
Date d2 = d1; //这里为拷贝构造
d2 = d1; //这里是使用了赋值运算符
return 0;
}
5.对普通对象和const对象的取地址重载
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
这两个默认成员函数很少会自己实现