1目录
相信大家都有一个困扰,就是经常忘记初始化以及释放内存空间,因此祖师爷发明了6个默认成员函数,它的好处是哪怕你忘记调用了,也会自己生成。
类与对象(中)
类的6个默认成员函数
构造函数(作用:初始化对象)
构造函数
是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务
并
不是开空间创建对象,而是初始化对象。
其特征如下:
构造函数七大特点
1.
函数名与类名相同。
2.
无返回值。
3.
对象实例化时编译器
自动调用
对应的构造函数。
4.
构造函数可以重载。
默认初始化可以是本有有一组数据初始化,知道数据大小初始化,啥都不知道初始化
typedef int Datatype;
class Stack {
public:
//两个参数不同的构造函数形成了函数重载
//构造函数
//给定数据插入栈
Stack(Datatype* arr, int n)
{
cout << "Stack(Datatype* arr, int n)" << endl;
_a = (Datatype*)malloc(sizeof(Datatype) * n);
if (_a == NULL)
{
cout << "malloc开辟空间失败" << endl;
return;
}
memcpy(_a, arr, sizeof(Datatype) * n);
_top = n;
_capacity = n;
}
//构造函数
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_a = (Datatype*)malloc(sizeof(Datatype) * capacity);
if (_a == NULL)
{
cout << "malloc开辟空间失败" << endl;
return;
}
_top = 0;
_capacity = capacity;
}
void push(Datatype x)
{
checkcapacity();
_a[_top++] = x;
}
bool Empty()
{
return _top == 0;
}
void pop()
{
if (Empty())
{
return;
}
_top--;
}
Datatype Top()
{
return _a[_top - 1];
}
int size()
{
return _top-1;
}
//析构函数
~Stack()
{
cout << "~Stack( )" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
void checkcapacity()
{
if (_top == _capacity)
{
Datatype* tmp = (Datatype*)realloc(_a, sizeof(Datatype) * _capacity * 2);
if (tmp == NULL)
{
cout << "malloc开辟空间失败" << endl;
return;
}
_a = tmp;
_capacity *= 2;
}
}
private:
Datatype* _a;
int _top;
int _capacity;
};
int main()
{
Stack A;
A.push(1);
A.push(2);
A.push(3);
A.push(4);
printf("%d\n", A.size());
printf("%d\n", A.Top());
A.pop();
A.pop();
printf("%d\n", A.size());
printf("%d\n", A.Top());
A.Empty();
//给定容量初始化
Stack B(10);
//给定数据初始化
int arr[] = { 1,2,3,4,5,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
Stack C(arr, sz);
return 0;
}
什么时候我们需要自己写构造函数?
5.
如果类中没有显式定义构造函数,则
C++
编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
不过最终,底层里面还是会有内置类型成员,还是需要自己写初始化
构造函数的调用
构造函数的调用跟普通函数也不一样
对象+参数列表自动调用构造函数;无参数时,后面不加()
Date d1;无参构造函数
Date d2(2023,1,1);传参构造函数
Date d3();错误写法 编译器无法很好区分d3是对象,还是函数名
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:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明
// 这里给的是默认的缺省值,给编译器生成默认构造函数用
int _year = 1;
int _month = 1;
int _day = 1;
// 自定义类型
//Stack _st;
};
// 1、一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
// 2、全部都是自定义类型成员,可以考虑让编译器自己生成
int main()
{
// 构造函数的调用跟普通函数也不一样
Date d1;
//Date d1(); // 不可以这样写,会跟函数声明有点冲突,编译器不好识别
Date d2(2023, 11, 11);//不会冲突,函数声明是形参应为数据类型
d1.Print();
d2.Print();
Date d3(2000);
d3.Print();
//Date d1;
//d1.Date();
//Date d2;
//d2.Date(2023, 1, 1);
return 0;
}
1、一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
2、全部都是自定义类型成员,可以考虑让编译器自己生成
默认构造函数与默认成员函数
6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?
d
对象调用了编译器生成的默
认构造函数,但是
d
对象
_year/_month/_day
,依旧是随机值。也就说在这里
编译器生成的
默认构造函数并没有什么用??
解答:
C++
把类型分成内置类型
(
基本类型
)
和自定义类型。内置类型就是语言提供的数据类
型,如:
int/char...
,自定义类型就是我们使用
class/struct/union
等自己定义的类型,看看
下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员
_t
调用的它的默认成员
函数
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
这三个只能存在一个,
无参跟全缺省虽然可以同时存在,但是调用时存在歧义,
如果有前面两个中的其中一个,那么我们没写编译器默认生成的构造函数,就不会产生。
构造函数形成缺省函数,必须写成全缺省,不能写成半缺省;
默认成员函数:: 不写编译器自己生成。
默认构造函数::
无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,是这三个中的其中一个,不一定是编译器自己生成的那一个。
结论
1、一般情况下构造函数都需要自己去写
2、两种情况下,不要自己写
a、内值类型成员都有缺省值,并且初始化符合我们的要求
b、全是自定义类型的构造,且这些类型都定义默认构造
C++11,声明时成员变量可以给缺省值,给默认构造函数用
析构函数
1.
析构函数名是在类名前加上字符
~
。
2.
无参数无返回值类型。
3.
一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
4.
对象生命周期结束时,
C++
编译系统系统自动调用析构函数。
自动生成的析构函数
1.内置成员变量不作处理
2.自定义类型会去调用他的析构函数
1.一般情况下,有动态申请资源,就需要显示写析构函数释放资源
2.没有动态申请的资源,不需要写析构
3.需要释放资源的成员都是自定义类型,不需要写析构
只有堆上的资源需要我们手动释放