首先把完整的代码放在下面,我们慢慢研究,当然可以跳过这段代码直接看下面
#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
Stack(DataType* a, int n)
{
cout << "Stack(DataType* a, int n)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * n);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_array, a, sizeof(DataType) * n);
_capacity = n;
_size = n;
}
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Init()
{
_array = (DataType*)malloc(sizeof(DataType) * 4);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = 4;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
DataType Top() { return _array[_size - 1]; }
int Empty() { return 0 == _size; }
int Size() { return _size; }
//void Destroy()
//{
// if (_array)
// {
// free(_array);
// _array = NULL;
// _capacity = 0;
// _size = 0;
// }
//}
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_array, newcapacity * sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main() {
Stack s;
s.Push(1);
s.Push(2);
cout<<s.Size()<<endl;
cout << s.Top() << endl;
s.Pop();
cout << s.Top() << endl;
return 0;
}
首先来讲构造函数:
6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(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()
{
// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生
成
// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0; }
Stack(DataType* a, int n)
{
cout << "Stack(DataType* a, int n)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * n);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_array, a, sizeof(DataType) * n);
_capacity = n;
_size = n;
}
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
然后在主函数中不用调用Init()初始化,可以直接调用push(),构造函数会帮你自动初始化。这就是构造函数的好处
再来看析构函数
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
然后我们把整个代码运行一下,发现在最后确实会打印"~Stack()",说明析构函数在生命周期结束时确实会运行帮助清理剩余资源
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0; }
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所
以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函 数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构
函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用
Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
最后回过头看看构造函数,也与构造函数的第7点相关。然后我们把上次的博客中的代码复制一下,并加两个构造函数
class Date
{
public:
// 构成函数重载
// 但是无参调用存在歧义?
/*Date()这个函数和下面那个函数确实构成重载,但是不能同时存在,如果调用函数的时候就是没传参呢?那编译器也不知道实际要调用哪个函数,因此两个函数只能存在一个。这里最好是留下下面那个函数
{
_year = 1;
_month = 1;
_day = 1;
}*/
Date(int year = 1, int month = 1, int day = 1)//构造函数也可以缺省!
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
// 自定义类型
//Stack _st;
};
// 1、一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
// 2、全部都是自定义类型成员,可以考虑让编译器自己生成
int main()
{
// 构造函数的调用跟普通函数也不一样
Date d1;
//Date d1(); // 不可以这样写,会跟函数声明有点冲突,编译器不好识别
Date d2(2023, 11, 11);
d1.Print();//输出1-1-1
d2.Print();//输出2023-11-11
Date d3(2000);
d3.Print();//输出2000-1-1
//Date d1;
//d1.Date();
//Date d2;
//d2.Date(2023, 1, 1);
return 0;
}
总结一下构造函数:
1 一般构造函数都要自己写
2内置类型成员都有缺省值,且初始化符合我们的要求(或者private后的成员声明的赋值也符合要求)
3全是自定义类型的构造,且这些类型都定义默认构造,看个例子
class Stack{
//...
}
class Myqueue{
private:
Stack s1;
Stack s2;
}
下面也补充一下析构函数的东西。我们平时写成员是在栈上建立,比如直接写int a=10,我们可以不释放,生命周期结束之后会自动释放,不用特意写析构函数,但是用到malloc等,在堆上建立的变量一定要手动释放,去写它们的析构函数,不然就会内存泄漏。还有就是自定义类型也不用去专门写析构函数。看个同样的例子,这个也不用在Myqueue类中写析构函数去专门释放,应该由Stack类来解决它的释放
class Stack{
//...
}
class Myqueue{
private:
Stack s1;
Stack s2;
}
然后分析一段构造函数和析构函数运行顺序的代码
//设已经有A,B,C,D 4个类的定义
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
1、类的析构函数调用一般按照构造函数调用的相反顺序进行调用,但是要注意static对象的存在, 因为static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象
2、全局对象先于局部对象进行构造
3、局部对象按照出现的顺序进行构造,无论是否为static
4、所以构造的顺序为 c a b d
5、析构的顺序按照构造的相反顺序析构,只需注意static改变对象的生存作用域之后,会放在局部 对象之后进行析构
6、因此析构顺序为B A D C
最后强调一下,我们最好平时要按规矩写,写好构造函数和析构函数,不要用系统自己默认生成的,通常根据编译器不同,中间的细节也会有不同,这就很麻烦