默认成员函数
任何类在上面都不写时,编译器会自动生成六个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
六个默认成员函数
- 初始化和清理
- 构造函数:主要完成初始化工作
- 析构函数:主要完成清理工作
- 拷贝赋值
- 拷贝构造:使用同类对象初始化创建对象
- 赋值重载:主要时把一个对象赋值给另一个对象
- 取地址重载
- 普通对象取地址
- const对象取地址
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合格的初始值,并且在对象整个声明周期中只调用一次。
特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
无参构造
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
Date d1;
//Date d2(); //不可以
return 0;
}
- 没有参数的时候不加括号,加括号是函数声明,不加括号才是创建对象。
带参构造
class Date
{
public:
Date(int year, int month , int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省
Date(int year = 2024, int month = 4 , int day = 23)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
Date d3(2024,4,23);
Date d4(2024,10);
}
- 全缺省构造和无参构造不能同时存在,如果同时存在,编译器会出现冲突。
- 无参构造和带参构造可以同时存在
- 函数名与类名相同
- 无返回值,不需要写void
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 默认生成构造函数:内置类型成员不做处理(编译器决定是否处理),自定义类型会去调用他(自定义类型)的构造函数。
- 对内置类型进行处理的时候,处理为随机值
- 内置类型是否处理不确定(由编译器决定),建议统一当成不处理
- 在C++11中针对内置类型成员不初始化的缺陷,又打了补丁,内置类型成员变量在类中声明时可以给默认值。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数。并且默认构造函数只能有一个。
- 无参构造函数、全缺省构造函数,我们没有写编译器默认生成的构造函数,都可以认为是默认构造函数。
C++将类型分为内置类型和自定义类型。
- 内置类型:int、double、char、long…
- 自定义类型:class、struct等用户自己定义的类型
内置类型成员变量在类中声明时可以给默认值。
class Date
{
public:
Date(int year, int month , int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
}
-
给每个成员变量设一个缺省值。
-
一般情况下,都需要写构造函数
-
如果成员都是自定义类型,或者声明时给了缺省值,可以考虑让编译器自己生成构造函数。
语言只能打补丁,不能修改。向前兼容性。
默认构造
-
我们不写编译器默认生成的那个构造函数,叫做默认构造
-
无参构造函数也可以叫做默认构造
-
全缺省也可以叫默认构造
-
可以不传参数就调用构造,都可以称为默认构造
-
这三个函数不能同时存在,否则会有调用歧义
上面的构造函数初始化成员变量是函数体内初始化
初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个“成员变量”后面跟一个放在括号中的初始值或表达式。
初始化列表就是成员定义的地方。
Date(int year,int month,int day)
: _year(year)
, _month(month)
, _day(day)
{}
- 每个成员变量在初始化列表中只能出现依次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量,函数体内无法初始化引用成员
- 引用必须在定义的时候初始化
- const成员变量
- const变量必须在定义的时候初始化
- 自定义类型成员(且该类没有默认构造函数时)
- 引用成员变量,函数体内无法初始化引用成员
class A
{
public:
A(int a) //初始化顺序与这里的顺序无关
:_a(a)
{}
private: //初始化顺序与这里的顺序有关
int _a;
};
class B
{
public:
B(int a,int ref) //初始化顺序与这里的顺序无关
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private: //初始化顺序与这里的顺序有关
A _aobj; //没有默认构造
int& _ref; //引用
const int _n; //const
};
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:
A(int a) //初始化顺序与这里的顺序无关
:_a1(a)
,_a2(_a1)
{}
private: //初始化顺序与这里的顺序有关
int _a2;
int _a1;
};
初始化列表解决的问题
- 必须在定义的地方初始化的:引用、const、没有默认构造的
- 有些自定义成员想要显式初始化,自己控制
构造函数不能只要初始化列表,不要函数体初始化。有些初始化或者检查的工作,初始化列表不能搞定。初始化列表和函数体可以混着用。
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
内置类型对象,隐式转换成自定义类型对象
class A
{
public:
A(int a)
: _a(a)
{}
private:
int _a;
}
int main()
{
A aa3 = 3;
return 0;
}
内置类型向自定义类型转,是因为A的int单参数构造函数支持。
如果不想隐式类型转换发生,就在构造函数前加上explicit关键字。
class A
{
public:
explicit A(int a)
: _a(a)
{}
private:
int _a;
}
int main()
{
A aa3 = 3;
return 0;
}