上篇文章初步认识了类以及类的相关知识,本篇将继续深入学习类与对象——类的默认6个成员函数:
上一章我们说到,如果一个类中什么成员都没有,那么这个类就叫空类。这么说其实不是很严谨,因为当我们定义好一个,不做任何处理时,编译器会自动生成以下6个默认成员函数:
cons
t):const
对象取地址操作在C语言阶段,我们在使用栈的数据结构的时候,每当创建一个Stack
对象,首先得调用它的初始化函数StackInit
来初始化对象。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}St;
void StackInit(St* ps);
//……
int main()
{
St s;
StackInit(&s);
return 0;
}
在每次使用前,都要先初始化,这样显得太麻烦而且容易遗忘。在C++
中,构造函数为我们很好的解决了这一问题。
构造函数是一个特殊的成员函数。构造函数虽然叫构造,但是其主要作用并不是开辟空间创建对象,而是初始化对象。
构造函数具有以下特性:
1. 函数名与类名相同
2. 无返回值
3. 对象实例化时,编译器自动调用对应的构造函数
4. 构造函数可以重载
举例:
class Data
{
public:
// 无参的构造函数
Data()
{}
// 带参的构造函数
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestData()
{
// 调用无参构造函数
Data d1;
// 调用带参的构造函数
Data d2(2023, 5, 3);
}
注意:
创建对象时编译器会自动调用构造函数,若是调用无参的构造函数,则无需在对象后加
()
,否则会产生歧义:编译器无法确定是在声明函数还是在创建对象。
错误示例:
// 错误示例
Data d3();
5. 如果类中没有显示定义的构造函数,则C++
编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成
class Data
{
public:
// 若用户没有显示定义,则编译器自动生成
//Data(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
private:
int _year;
int _month;
int _day;
};
6. 默认生成构造函数,对内置成员不作处理,对自定义类型成员,会调用它的默认构造函数
C++
把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的基本数据类型,如:int
,char
,double
……,自定义类型就是我们使用的class
,struct
,union
等自己定义的类型
举例:
默认构造函数对内置类型:
class Data
{
public:
void print()
{
cout << _year << '-' << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestData1()
{
Data d1;
d1.print();
}
int main()
{
TestData1();
return 0;
}
运行结果:
如图所示,默认构造函数未对内置类型做处理。
默认构造函数对自定义类型:
class stack
{
public:
// stack的默认构造函数
stack()
{
cout << "stack" << endl;
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class queue
{
public:
// 不对queue的构造函数做显示定义,测试构造函数
private:
stack _s;
};
void TestQueue()
{
queue q;
}
int main()
{
TestQueue();
return 0;
}
运行结果:
如图所示,在创建queue
对象时,默认构造函数对自定义成员_s
做了处理,调用了它的默认构造函数stack()
C++11
中针对内置类型成员变量不初始化的缺陷,又打了补丁,即:7. 内置类型成员变量在类中声明时可以给默认值
示例:
class Date
{
public:
void print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
void TestDate2()
{
Date d2;
d2.print();
}
int main()
{
TestDate2();
return 0;
}
运行结果:
默认值:若不对成员变量作处理,则使用默认值
8. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只有一个
默认构造函数有三个:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
示例:
class Date
{
public:
// 1.无参的默认构造函数
// Date()
// {
// ……
// }
// 2.全缺省的默认构造函数
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
析构函数与构造函数的特性相似,但功能恰好相反。构造函数是用来初始化对象的,析构函数是用来销毁对象的。
需要注意的是,析构函数并不是对对象本身进行销毁(因为局部对象出了作用域会自行销毁,由编译器来完成),而是在对象销毁时会自动调用析构函数,对对象内部的资源做清理(如对象
stack
中的int *a
)。
同样,有了析构函数,我们就不用担心创建对象后由于忘记释放内存而造成内存泄漏了。
示例:
class Stack
{
public:
// 构造函数
Stack()
{
}
void push(int x);
bool empty();
int Top();
// 手动释放
void Destory();
private:
// 成员变量
int* _a;
int _top;
int _capacity;
};
void TestStack()
{
Stack s;
s.push(1);
s.push(2);
// 手动释放
s.Destory();
}
示例:
class Date
{
public:
// 构造函数
Date()
{
cout << "Date()" << endl;
}
// 析构函数
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestDate3()
{
// d3生命周期结束时自动调用构造函数
Date d3;
}
int main()
{
TestDate3();
}
6.编译器生成的默认析构函数,对自定义类型调用它的析构函数
示例:
class Stack
{
public:
// 构造函数
Stack()
{
cout << "stack()" << endl;
_a = nullptr;
_top = _capacity = 0;
}
// 析构函数
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class queue
{
public:
// 不对queue的构造函数和析构函数做显示定义
// 测试默认构造函数和默认析构函数
private:
Stack _s;
};
void TestQueue1()
{
queue q;
}
int main()
{
TestQueue1();
return 0;
}
运行结果:
这是因为:内置类型在生命周期结束时会自动销毁。而自定义类类型里可能有动态开辟的空间,因此要进行清理工作。
因此:
7.如果类中没有申请资源时,析构函数可以不写,直接用编译器生成的默认析构函数,比如Date
类;有资源申请时,一定要写,否则会造成资源泄露,比如Stack
类
本文到此结束,码文不易,还请多多支持哦!!