前言:当一个类中什么成员都没有称为空类,那空类是类中什么都没有吗?事实上并非如此,当类中用户啥也没写时,编译器会自动生成6个默认的成员函数。
环境:VS2013
编译器自动生成以下6个成员函数。
目录
构造函数
析构函数
一、构造函数
#include<iostream>
using namespace std;
class Data
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
d1.Init(2022, 11, 21);
d1.Print();
Data d2;
d2.Init(2022, 11, 22);
d2.Print();
return 0;
}
我们通过自定义的公有方法给对象d1,d2进行初始化,但我们可以看到在每次创建对象时都得调用该方法进行信息设置,那我们可以想,能否在对象创建时就将信息设置在对象里呢?
- 定义
特殊成员函数,函数名与类名相同,创建类类型对象时由编译器自动调用,在对象整个生命周期内只调用一次。 - 特性
(1)函数名与类名相同
(2)无返回值
(3)创建对象时编译器自动调用
(4)构造函数可重载
Data日期类。
Data() //无参构造函数
{}
Data(int year, int month, int day) //全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
(5)若类中用户没有显式定义构造函数,编译器会自动生成一无参的默认构造函数;若用户显式定义了,编译器便不再生成。
①用户显式定义时,调用用户显式定义的构造函数。
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year=1900, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
return 0;
}
②用户未显式定义构造函数,编译器会自动生成一无参的默认构造函数。
我们可以看到当调用编译器自动生成的默认构造函数时,d2对象中各值为随机值。对象调用了编译器自动生成的默认构造函数,但对象中_year/_month/_day却为随机值,那是不是编译器生成的默认构造函数并没什么用呢?
事实上,C++语法规定,当用户未显式定义任何构造函数,编译器一定会生成一无参的构造函数;但编译器在具体实现时,可能会考虑到程序的运行效率问题,编译器感觉生成的构造函数并无多大意义时,编译器便不实现此方法。
这也是语法上和程序具体实现上的有所不同。
(6)那编译器生成的默认构造函数就没啥用吗?我们在Data类中自定义Time类对象,Time中我们显式定义Time对象的构造函数,而Data中为编译器自动生成。
C++把类型分为内置类型和自定义类型,内置类型即C++中提供的数据类型,如int、char类型等;自定义类型即class、struct等我们自己定义的类型。
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour = 10, int minute = 10, int second = 10)
{
cout << "Time()" << endl;
_hour = hour;
_minute = minute;
_second = second;
}
private:
int _hour;
int _minute;
int _second;
};
class Data
{
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
int main()
{
Data d;
return 0;
}
我们可以看到,编译器生成的Data的默认构造函数调用了自定义类型成员_t的默认成员函数。故,编译器生成的默认构造函数并非无用,而是具体情况具体实现。
二、析构函数
1 定义
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作由编译器完成。即对象在销毁时自动调用析构函数完成对象中资源的清理工作。
2 特性
(1)函数名为在类名前加上~。
(2)无参数无返回值。
(3)一个类只有一个析构函数,用户未显式定义时,编译器自动默认生成。
析构函数的独一性,则其是不可重载的。
(4)对象生命周期结束时,C++编译系统自动调用析构函数。
我们在Data类中自定义Time类对象_t,Time中我们显式定义Time对象的析构函数,而Data中为编译器自动生成。
#include<iostream>
using namespace std;
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Data
{
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
int main()
{
Data d;
return 0;
}
程序输出了~Time(),当Data类对象d生命周期结束时,我们并未显式定义Data的析构函数,编译器自动生成默认的析构函数。对象销毁时内置类型不需要资源清理,最后系统直接将其内存回收即可,故只需要销毁自定义类型对象_t,而要将_t对象销毁,就要调用Time类的析构函数,而调用我们显式定义Time类的析构函数。
需清楚,main函数中我们并未直接调用Time类的析构函数,而是显式调用编译器为Data类生成的默认析构函数。
同构造函数,C++语法规定,当用户未显式定义任何析构函数,编译器一定会生成一析构函数;但编译器在具体实现时,可能会考虑到程序的运行效率问题,编译器感觉生成的析构函数并无多大意义时,编译器便不实现此方法。
这又是语法上和程序具体实现上的有所不同。
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2022,11,22);
d1.Print();
return 0;
}
我们可以看到,本段程序并未调用Data类的析构函数,此为语法和编译器具体实现上的不同。
(5)当类中未涉及到资源的管理时,用户可不显式定义析构函数,直接使用编译器生成的默认析构函数,如上述的Data类;当类中涉及到资源的管理时,析构函数必须由用户进行显式定义,不然会造成资源的泄露,如Stack类。
#include<iostream>
#include<cstdlib>
using namespace std;
class Stack
{
public:
//初始化栈(构造函数)
Stack(size_t _capacity = 5)
{
array = (int*)malloc(sizeof(int)*capacity);
if (array == nullptr)
{
printf("malloc申请空间失败!!!\n");
return;
}
capacity = _capacity;
size = 0;
cout << "Stack()" << this<<endl;
}
//析构函数
~Stack()
{
if (array)
{
free(array);
array =nullptr;
capacity = 0;
size = 0;
}
cout << "~Stack()"<<this << endl;
}
//栈的相关操作
//入栈时空间不够时申请新的空间函数
void ChackCapacity()
{
if (size ==capacity)
{
//说明空间不够需要开辟新空间
//1.开辟新空间
int newcapacity =capacity * 2;
int* temp = (int*)malloc(sizeof(int)*newcapacity);
if (NULL == temp)
{
printf("申请空间失败!!!\n");
return;
}
//2.空间拷贝
memcpy(temp, array, sizeof(int)*size);
//3.把原来空间给释放掉
free(array);
//4.使用新空间
array= temp;
capacity= newcapacity;
}
}
//入栈
void Push(int data)
{
//如果空间不够得扩容
ChackCapacity();
array[size]= data;
size++;
}
//出栈
void Pop()
{
if (Empty())
return;
size--;
}
//获取栈顶元素
int Top()
{
return array[size-1];
}
//获取栈中有效元素个数
int Size()
{
return size;
}
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int Empty()
{
return 0 == size;
}
private:
int* array;
int capacity;
int size;
};
void Test()
{
Stack s;
s.Push(1);
s.Push(2);
cout <<s.Size()<< endl;
s.Pop();
}
int main()
{
Test();
return 0;
}
当对象生命周期结束时,Stack中申请的array的连续空间必须得用户释放,故此时必须实现相应的析构函数以免造成资源的泄露。