前言
class Stack
{
public:
void Init(int defaultcapacity=4)
{
_a = (int*)malloc(sizeof(int) * defaultcapacity);
if (_a == nullptr)
perror("malloc fail");
_top = 0;
_capacity = defaultcapacity;
}
void push(int x)
{
_a[_top] = x;
_top++;
}
void pop()
{
_top--;
}
void print()
{
cout << _a[_top-1] << endl;
}
void Destroy()
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
st1.Init();
st1.push(1);
st1.push(2);
st1.push(3);
st1.print();
st1.pop();
st1.print();
st1.Destroy();
return 0;
}
上述是我们用c++实现的一个栈,我们能看到,在上述代码中,需要先调用函数Init,进行初始化,使用结束之后又要调用函数Destroy进行清理。如果有时候忘记初始化就会报错,而忘记Destroy则会出现内存泄漏,是非常麻烦的事情。所以,基于此,C++内置了构造函数和析构函数,我们来看看是怎么使用的,以及注意事项。
构造函数的特性
构造函数有如下特性:
1、函数名和类名相同
2、没有返回值,也不需要写void
3、对象实例化的时候编译器会自动调用构造函数
4、构造函数可以重载
5、如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
根据上面的情况,我们来写一个构造函数。
Stack()
{
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
perror("malloc fail");
_top = 0;
_capacity = 4;
}
我们来看看,函数名就是类名,所以是Stack,没有返回值,不需要写void。前面两点已经满足,我们观察这个构造函数,发现它其实和我们上面写的Init好像是一摸一样的,没有任何区别。就是自己实现的初始化。我们再来看看,说对象实例化的时候编译器会自动调用构造函数,那么怎么证明呢?我们来看运行结果。
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
perror("malloc fail");
_top = 0;
_capacity = 4;
}
void push(int x)
{
_a[_top] = x;
_top++;
}
void pop()
{
_top--;
}
void print()
{
cout << _a[_top-1] << endl;
}
void Destroy()
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
st1.push(1);
st1.push(2);
st1.push(3);
st1.print();
st1.pop();
st1.print();
st1.Destroy();
return 0;
}
我们明明没有调用构造函数,最后通过运行的结果来看,确实是编译器自动调用了。
最后说构造函数可以重载,我们来探索一下,观察下面的代码。
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
perror("malloc fail");
_top = 0;
_capacity = 4;
}
Stack(int n)
{
cout << "Stack(int n)" << endl;
_a = (int*)malloc(sizeof(int) * n);
if (_a == nullptr)
perror("malloc fail");
_top = 0;
_capacity = n;
}
void push(int x)
{
_a[_top] = x;
_top++;
}
void pop()
{
_top--;
}
void print()
{
cout << _a[_top-1] << endl;
}
void Destroy()
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
//private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(10);
return 0;
}
观察运行结果,表示确实可以函数重载。
注意事项:构造函数的调用不是st1.Stcak(),而是直接Stcak st1,或者Stack St1(10);如果是无参调用的时候就是Stcak st1,可能有小伙伴会问,为什么不能是st1.Stack()。如果是这样的话和st1.Init()有什么区别呢,那为什么还要设置一个构造函数出来?当然,更不能是Stack st1(),这样的话,会和函数声明很像,会分不清。所以记住,对象后面不用加()!!!
对于第五点,我们可以先来看一个经典的日期类。
class Data
{
public:
Data()
{
_year = 1;
_month = 1;
_day = 1;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
d1.print();
return 0;
}
在上面的代码中,我们可以看到我们自己写了一个无参的构造函数并且都初始化为1了,那我们来看看运行的结果是什么。
不出所料,打印的结果就是我们所写构造函数初始化的值。如果我们不写构造函数,调用编译器自动生成的会怎么样呢?
给出以下代码,观察运行结果。
class Data
{
public:
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
d1.print();
return 0;
}
根据运行结果,能看到编译器自动给的构造函数会初始化成随机值。可能有小伙伴就会说了,这不扯吗?初始化成随机值了可还行,确实这里给的不太好,所以在c++11中打了一个补丁,内置类型成员变量可以给缺省值,这样既可以初始化成随机值,又可以初始化成缺省值。具体可查看以下代码。
class Data
{
public:
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year=1;
int _month=1;
int _day=1;
};
int main()
{
Data d1;
d1.print();
return 0;
}
如果是非内置类型呢?非内置类型就可以不用写构造函数了,例如下面这个代码。
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
perror("malloc fail");
_top = 0;
_capacity = 4;
}
void push(int x)
{
_a[_top] = x;
_top++;
}
void pop()
{
_top--;
}
void print()
{
cout << _a[_top - 1] << endl;
}
void Destroy()
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class Myqueue
{
public:
Stack stackpush;
Stack stackpop;
};
int main()
{
Myqueue q1;
return 0;
}
观察调试。
最终成为了这个样子,这不就是我们之前Stack的构造函数吗,所以虽然非内置类型不用写,但是最终还是会回归到内置类型。
上面提到一个概念,叫做内置类型。我们可以解释以下,什么是内置类型:int,char,double,指针都属于内置类型。非内置类型就是自定义类型,用struct、class定义的类型。
默认构造函数
默认构造函数只能有一个。那么什么是默认构造函数呢?默认构造函数:无参构造函数、全缺省构造函数、编译器自动生成的默认构造函数。
根据前面的分析,大家知道为什么吗?
因为:如果我们自己写了构造函数,那么编译器就不会自动生成默认构造函数,而无参构造函数和全缺省构造函数存在调用歧义,所以他们两个也只能存在一个。一山不容二虎。
class Data
{
public:
Data()
{
_year = 1;
_month = 1;
_day = 1;
}
Data(int year = 2, int month = 2, int day = 2)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year=1;
int _month=1;
int _day=1;
};
int main()
{
Data d1;
d1.print();
return 0;
}
如果我们这样写,但是在生成对象的时候,全缺省,就会出现错误。