在前面我详细介绍了类和对象的一些基本语法,类和对象(1),在本章博客中继续深入讨论类的六个默认成员函数。如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。
一、构造函数
- 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
- 构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主 要任务并不是开空间创建对象,而是初始化对象。
- 构造函数的特性:① 函数名与类名相同。 ②无返回值。 ③对象实例化时编译器自动调用对应的构造函数。 ④构造函数可以重载。
- 如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。
class Date
{
public:
Date()
{
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void printDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//调用无参的构造函数
d1.printDate();
Date d2(2019, 9, 11);//调用带参的构造函数
d2.printDate();
//注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
system("pause");
return 0;
}
- 无参构造函数与有参构造函数同时存在的话我们可以把他们优化成全缺省的构造函数,这样可以使代码更加简练。
class Date
{
public:
//全缺省的构造参数,不论有参还是无参都可以用
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void printDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2019, 9, 11);
d1.printDate();
d2.printDate();
system("pause");
return 0;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定 义,编译器将不再生成。 没有定义构造函数,对象也可以创建成功,因此调用的是编译器生成的默认构造函数 。
class Date
{
public:
//如果编译器显示定义了构造函数,编译器将不会再默认生成
/*Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}*/
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
system("pause");
return 0;
}
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:**无参 构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。**无参构造函数与全缺省的构造函数只能存在一个,同时存在的话会出现二义性。
- 不是全缺省的带参的构造函数是构造函数而不是默认构造函数。
class Date
{
public:
//是构造函数,但不是默认构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
-
关于编译器自动生成的默认构造函数它并不会对对象的成员变量初始化为某一个值,而是随机值:
通过调试窗口可以看出,我并没有显示的的给出构造函数,但是d1对象也创建成功了,是因为调用了编译器默认生成的构造函数,但是成员变量依旧被初始化为随机值,所以编译器默认生成的构造函数到底有什么作用?解答:C++把类型分成内置类型(基本类型)和自定义类型。编译器生成默认的构造函数其实会对自定类型成员调用的它的默认构造函数。看下面一段程序:
class Time
{
public:
Time()
{
cout << "Time" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
int main()
{
Date d1;
system("pause");
return 0;
}
运行结果(可以看到编译器默认生成的构造函数对自定类型成员调用的它的默认构造函数)为:
- 我们一般在声明成员变量时,会对成员变量前面那加上英文版的下划线或(_year),或者加上m与下划线(m _year),这是比较好的命名风格。在声明成员变量时,有两种情况:
class Date
{
private:
int _year;
int _month;
int _day;
};
//或者:
class Date
{
private:
int _year = 1900;
int _month = 1;
int _day = 1;
};
上面两种方法都可以声明成员变量,第一种是常见的,第二种是 C++11 之后的方法,给成员变量默认的缺省值。但是注意上面两种方法都是对成员变量的声明。
二、析构函数
- 析构函数的功能与构造函数相反,**析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。**而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
- ①:析构函数名是在类名前加上字符 ~。
②:无参数无返回值
③:一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
④:对象生命周期结束时,C++编译系统系统自动调用析构函数。
⑤:析构函数的析构顺序与栈的性质一样,后创建的对象先析构。
⑥:析构函数不能构成重载。
⑦:并不是所有的类都需要析构函数。比如:日起类。 - 编译器生成的 默认析构函数,会对自定类型成员调用它的析构函数。
三、拷贝构造函数
- 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时由编译器自动调用。
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
- 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
- 熟悉深拷贝。
四、赋值运算符重载
4.1、运算符重载
-
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
-
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表) -
使用运算符重载应该注意的几点:
①:重载操作符必须有一个类类型或者枚举类型的操作数 。
②:作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操 作符有一个默认的形参this,限定为第一个形参 。
③:.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
4.2、赋值运算符重载
- 赋值运算符重载的主要特点:
①: 参数类型。
②: 返回值。
③: 检测是否自己给自己赋值。
④: 返回 *this 。
⑤: 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
注意:关于拷贝构造与赋值运算符重载的两种情况:
情况1:
T t1;
t1 = f3();
情况2:
T t1 = f3();
情况1:因为在 t1 = f3(); 之前已经创建 t1 这个对象,所以 这个 = (赋值符号)是赋值。
情况2:虽然是赋值符号,但是 t1 对象在执行这行代码之前并没有被创建,所属于拷贝构造。
两种情况代码举例:
class Date
{
public:
Date(int year = 1990, 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;
cout << "Date(const Date& d)" << endl;
}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "Date& operator=(const Date& d)" << endl;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//此情况输出:Date& operator=(const Date& d)
Date t2(2018,1,1);
Date t1;
t1 = t2;
//此情况输出:Date(const Date& d)
Date t2(2018,1,1);
Date t1 = t2;
return 0;
}
五、const 成员
5.1、const 修饰类的成员函数
- 将 const 修饰的类成员函数称之为 const 成员函数,const 修饰类成员函数,实际修饰该成员函数隐含的 this 指针指向的对象,表明在该成员函数中不能对类的任何成员进行修改。const 对象默认调用 const 成员函数,非 const 对象默认调用非 const 成员函数。
class Date
{
public:
void Display()
{
cout << "Display ()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Display() const
{
cout << "Display () const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year = 1990;
int _month = 1;
int _day = 1;
};
int main()
{
Date d1;
d1.Display();
const Date d2;
d2.Display();
system("pause");
return 0;
}
四、几个常见的问题
(1)const对象可以调用非const成员函数吗?
(2)非const对象可以调用const成员函数吗?
(3)const成员函数内可以调用其它的非const成员函数吗?
(4)非const成员函数内可以调用其它的const成员函数吗
六、 取地址及 const 取地址操作符重载
- 这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year;
int _month;
int _day;
};