第二章 类与对象
注意:只要成员函数中不需要修改成员变量都最好加上const。因为普通对象调不动const成员函数(常函数)
前言
C++中任何类就算什么也不写(空类),编译器会自动生成6个默认成员函数
默认成员函数:又称特殊成员函数,用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。即:我们不写一个,编译器就会自己生成一个;我们自己写了,编译器就不会生成。(隐含的意思:对于有些类,需要我们自己写;对于另外一些类,编译器默认生成就可以用)
默认成员函数
1.初始化和清理:
构造函数:主要完成初始化工作。
析构函数:主要完成清理工作。
2.拷贝赋值:
拷贝构造:使用同类对象初始化创建对象。
赋值重载:把对象赋值给另一个对象。
3.取地址重载:
普通对象的取地址。
const对象的取地址。 (这两个很少会自己去实现)
构造函数
语法
class 类名{
类名(构造形参表){
//主要负责初始化对象,即初始化成员变量
}
};
- 函数名与类名相同,没有返回类型
- 构造函数在创建对象时自动调用和执行,不能向普通的成员函数通过对象去调用
- 支持函数重载(参数个数不同包括类型不同或顺序不同)
explicit关键字
class 目标类型{
explicit 目标类型(源类型){...}
};
可以实现源类型到目标类型的隐式转换。
注:使用explicit关键字,可以强制这种转换必须显式的完成。
初始化列表
初始化列表,可以理解为对象的成员变量的定义的地方
初始化时的顺序与声明的顺序的有关
注意:
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
class 类名{
类名(形参表)
:成员变量(初值)
,...
{}
};
例子
class Date {
private:
int _year;
const int _n = 30; // 给一个缺省值,如果构造函数给值了,使用构造函数的。
public:
explicit Date(int year = 0)
:_year(year) // 初始化列表
,_n(20) // 初始化列表的体现
{
//_n = 40; 因为是const不能被修改,报错
}
};
int main() {
Date d1;
// 相当于构造出 tmp(2) 再用tmp拷贝构造d2(tmp)
Date d2 = 2 ; // 构造器加上 explicit 关键字, 就无法隐式转换 所以这句会报错
// 拓展:如果有多个参数时,可以用大括号
/*Date d3 = {2,3} ;*/
}
析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象里的资源的清理工作。
语法:
class 类名{
// 构造函数
// .....
// 析构函数
~类名()
{
// 释放对象里开辟的空间
}
};
注意:
如果类中没有申请资源(比如开辟空间)时,析构函数可以不写,直接使用编译器生成的默认析构函数,如果有资源申请时,一定要写,否则会造成资源泄漏。
拷贝构造函数
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个,使用传值方式编译器直接报错,因为会引发无穷递归,推荐用引用做形参,指针也可以(不推荐)。
语法:
class Date {
private:
int _year;
public:
Date()
:_year(2023)
{}
// 拷贝构造函数
Date(const Date& d) // 不能 使用传值方式
{
_year = d._year; // 将 d 对象中数据复制给this(新创建的对象)
}
};
int main() {
Date d1;
Date d2(d1); // 拷贝构造
}
注意事项:
像上述的例子中没必要写拷贝构造函数。因为默认拷贝构造函数能完成简单内置类型的拷贝操作
但是对于涉及空间开辟的情况。一定要写拷贝构造函数。(如果不写会造成浅拷贝,只拷贝了开辟空间的地址(造成两个对象指向同一个空间),对象销毁时,会析构两次(即释放两次一样的空间地址))
建议加上const,防止被拷贝对象的数据被更改。
运算符重载
operator 关键字,实现自定义类型的运算。
operator 函数中的操作数取决于参数个数
写在类中时,this 指针就算一个隐藏参数
operator 一般写在类中,方便通过 this 指针访问成员变量
operator也可以写在类外,此时会发生无法访问成员变量问题,可以这样解决:
将成员变量设为 public (不安全)
通过函数获取类中的成员变量值 (麻烦)
设置为友元函数(特殊情况推荐,比如重载 << 和 >>)
写在类中,最简单、省事,而且还可以使用 this 指针
赋值重载
// d2 = d1 // 返回值是d4才能支持连等。
Date& operator=(const Date& d) {
if (this != &d) // 对自己给自己复制的判断
{
_year = d._year; // this -> d2 , d -> d1
}
return *this; // 支持连等
}
赋值重载注意事项:
与拷贝构造不同 d2对象已存在。
前置++ 和 后置++(哑元函数)的重载
前置++:
// ++d1
Date& operator++() {
// 实现++;并返回当前对象
}
后置++:
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// d1++
Date operator++(int) { // 为了构成函数重载
// 实现 先返回原来的值。在++
}
前置-- 和 后置-- 是一样的。
友元函数 实现 (<< 及 >> )重载
#include <iostream>
using namespace std;
class Date {
private:
int _year;
int _month;
int _day;
public:
// 友元函数的声明
friend ostream& operator<<(ostream& out, const Date& d); // 友元声明
friend istream& operator>>(istream& input, Date& d);
Date(int year = 0 , int month = 1, int day = 1)
: _year(year)
,_month(month)
,_day(day)
{}
// 这样不好
// d << cout;只能这样用,
// 默认左参数是this 即 d -> this
/*void operator<<(ostream& out) {
out << _year << "-" << _month << "-" << _day << endl;
}*/
};
// cout -> ostream
// cin -> istream
// 使用这个返回值 就可以使用 cout << d << d2;连续输出
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "/" << d._month << "/" << d._day << endl; // 友元使用后就可以直接访问对象里private里的内容
return out;
}
istream& operator>>(istream& input, Date& d) {
input >> d._year >> d._month >> d._day;
return input;
}
int main() {
Date d;
/*d << cout;*/
/*cout << d;
int i = 0;
cout << i;*/
cin >> d;
cout << d;
return 0;
}
普通对象的 &(取地址) 重载
// 没必要写,除非不想被取地址。编译器会自动生成
Date* operator&() {
cout << "operator&()\n";
return this;
}
const 关键字
const 修饰可以提高程序的健壮性
const 常被用来修饰引用、指针
被const修饰后,就不能再修改了(权限缩小)
const 对象的 &(取地址)重载
const Date* operator&() const // 相当于隐含的参数变为 const 类名* this
{
cout << "operator&() const\n";
return this;
}
注意:只要成员函数中不需要修改成员变量都最好加上const。因为const对象调不动普通成员函数
const对象只能调用const成员函数(常函数)
class Date {
private:
int _year;
int _month;
int _day;
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 打印
void print() const // -> void print(Date* this)
// 这里加上const f函数中才能调用
{
cout << _year << "-" << _month << "-" << _day << endl;
//this->_year = 10; 不能修改了,const修饰保护了*this
}
};
void f(const Date& d) {
d.print(); // -> d.print(&d)
// 因为传的是const Date& d
}
int main() {
Date d(2020, 1, 20);
f(d);
return 0;
}