前言
空类:一个类中什么都没有,简称空类
但空类中会有编译器自动生成的6个默认成员函数。
默认成员函数:用户没有显示实现,但编译器会生成的成员函数称为默认成员函数
1.构造函数
1.1概念
构造函数是一个特殊的成员函数,名字以类名相同,创建类 类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且再对象生命周期内只调用一次。
1.2特性
1.函数名与类名相同。
2.无返回值。
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
class Date
{
public:
//1.无参构造函数
Date()
{}
//2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
5.如果类中没有显示定义构造函数,则编译器自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成。
6.C++将类型分为内置类型,和自定义类型。(内置类型就是语言提供的数据类型,如 int, char), 编译器生成的默认构造函数会调用自定义类型的默认成员函数
class Date
{
public:
Date()
{
cout << "Date()" << endl;
_year = 0;
_month = 0;
_day = 0;
}
private()
//基本类型(内置类型)
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
class Time()
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
注意:C++11 中针对内置类型不初始化的缺陷,又打了补丁,即:内置类型成员变量再类声明时可以给默认值。
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、编译器自动生成的构造函数,都可以认为是默认构造函数。
8.在创建对象时,编译器会通过调用构造函数,给对象中各个成员一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然这个构造函数调用之后,对象中已经有了一个初始值,但还是不能将其称为对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能将其作为初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
9.初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔成员列表,每个“成员变量”后面跟着一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.. 类中包含以下成员,必须放在初始化列表位置进行初始化:
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数时)
lass 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
};
10. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
11.成员变量在类中声明的顺序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
}
这个程序输出的结果是: 1 随机值
12.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
explicit Date(int year)
:_year(year)
{}
/*
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
有类型转换作用
// explicit修饰构造函数,禁止类型转换
explicit Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
*/
2.析构函数
1.概念
析构函数的作用与构造函数的功能恰恰相反,析构函数不是完成对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
2.特性
1.析构函数在类名前加上~
2.无参数,无返回值
3.一个类只能由一个析构函数
4.对象生命周期结束时,C++编译系统自动调用析构函数。
5.如果类中没有申请资源时,析构函数可以不写,直接用编译器生成的默认析构函数。
注意:存在统一区域的对象,后定义的先析构
销毁顺序:局部对象->局部静态->全局对象
3.拷贝构造
概念:
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特性:
1.拷贝构造是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类 类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(const Date& d) // 正确写法
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;
}
C++规定自定义类型都会调用拷贝构造
3.若为显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数按内存存储字节序完成拷贝(这种拷贝叫做浅拷贝、值拷贝)。
4.拷贝构造函数的典型调用场景
1.使用已经存在的对象创建新对象
2.函数参数类型为类类型对象
3.函数返回类型为类类型对象
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时更具实际场景,能引用尽量引用
4.赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名以及参数列表,其返回值类型与参数列表与普通函数类似。
函数名字为 返回值类型 operator操作符(参数列表)
注意
1.不能通过连接其他符号来创建新的操作符:如operator @
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变
4.作为类成员的函数重载时,其形参会比操作数数目少1,因为成员函数的第一个参数为影藏的this指针
.* :: sizeof ? : . 这些操作符不能重载
class Date
{
public:
Date(int year = 2000, int month = 1, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
bool operate==(cosnt Date& d)
{
if(_year == d.year && _month == d.month == _day == d.day)
return true;
else return false;
}
private:
int _year;
int _month;
int _day;
}
用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝(浅拷贝)。
5.取地址及const 取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器会默认生成