1.类的六个默认成员函数
任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
//空类
class Date{};
默认成员函数:用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数
2.构造函数
class Date
{
public:
//他们俩构成函数重载,但是无参调用时会存在歧义
// 1.无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数
// 一般情况,建议每个类,都可以写一个全缺省的构造(好用)
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;
};
int main()
{
//Date d1(); //d1后面不能带括号否则定义对象无法跟函数声明区分开
//Date func();//这就是d1为什么不能带括号
Date d1;
d1.Print();
//类型 对象(2024,4,2)
Date d2(2024, 4, 2);//这里调用构造函数是对象名加参数列表
//这里可以和函数声明进行区分,如下一行的函数声明所示
//Date func(int x, int y, int z);
d2.Print();
Date d3(2024);
d3.Print();
Date d4(2024, 4);
d4.Print();
return 0;
}
⑤如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
#include<iostream>
using namespace std;
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;
}
如果用户显示定义了构造函数,编译器将不会生成无参的默认构造函数,这时候如果定义一个无参的类如Date d1,会编译失败。
#include<iostream>
using namespace std;
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
这里我们发现编译器自动生成的构造函数对于自定义类型调用了它的默认构造函数。
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
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;
}
⑦无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
总结:不传参数就可以调用的函数就是默认构造
Ⅰ一般情况构造函数都需要我们自己显式去实现
Ⅱ只有少数情况下可以让编译器自动生成构造函数类似MyQueue,成员全是自定义类型
3.析构函数
析构函数是特殊的成员函数,其特征如下:
⑤对于编译器自动生成的默认析构函数,对于自定义类型成员会调用它的析构函数。
跟构造函数类似:
a、内置类型不做处理
b、自定义类型去调用他的析构
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;
}//对象在这里被销毁后,这里会自动调用析构函数
#pragma once
#include<stdlib.h>
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int n = 4);
~Stack();
//void Init();
//void Destroy();
void Push(int x);
bool Empty();
void Pop();
int Top();
private:
// 成员变量
int* _a;
int _top;
int _capacity;
};
class Queue
{
public:
void Init();
void Push(int x);
};
Stack.cpp如下:
#include"Stack.h"
Stack::Stack(int n)//缺省参数声明和定义不能同时给,规定了只在声明时候给,定义的时候不给
{
_a = (int*)malloc(sizeof(int)*n);
_top = 0;
_capacity = n;
}
Stack::~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
//void Stack::Init()//指明类的作用域就指明了类的出处
//{
// _a = nullptr;
// _top = 0;
// _capacity = 0;
//}//任何一个变量都得先定义再使用,不符合语法报语法错误
//void Destroy()
//{
// //...
//}
void Stack::Push(int x)
{
// ...
_a[_top++] = x;
}
bool Stack::Empty()
{
return _top == 0;
}
void Stack::Pop()
{
--_top;
}
int Stack::Top()
{
return _a[_top - 1];
}
//void Queue::Push(int x)
//{
//
//}
#icnlude<Stack.h>
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
int main()
{
MyQueue q;
return 0;
}
这里创建MyQuque类q时,会自动调用调用Stack类的默认构造函数,q销毁时,也会自动调用Stack类的默认析构函数。
下面是一个括号匹配问题,利用c和c++实现的比较,可以发现,利用c++的构造和析构特性后,会方便很多
class Date
{
public:
Date(int year = 1970, int month =1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
//Date(Date d)错误写法
//Date d2(d1);//d1传给了d,d2就是this
Date(const Date& d)//加了const之后d的内容就无法修改
{
cout << "const Date& d" << endl;
//this->_year = d._year;
_year = d._year;
_month = d._month;
_day = d._day;
}
// Date(Date* d)//规定这里不是拷贝构造,就是一个普通构造,编译器会自动生成一个默认的拷贝构造
// {
// _year = d->_year;
// _month = d->_month;
// _day = d->_day;
// }
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 基本类型(内置类型)
int _year ;
int _month ;
int _day ;
};
void func(Date d)
{
d.Print();
}
int main()
{
Date d1(2024,4,18);
Date d2(d1);
func(d2);
return 0;
}
//Date(Date d)是错误写法,
原因:对于自定义类型传值传参要调用拷贝构造完成,这是一种规定,自定义类型的拷贝都要调用拷贝构造才能完成。这里用d1去构建d2,规定需要调用拷贝构造,调用拷贝构造得先传参,先传参形成了一个新的拷贝构造,新的拷贝构造假设去调用,去调用这个拷贝构造,又要先传参,从逻辑上来说就是一个无穷递归
func(d2);
void func(Date d)
{
d.Print();
}
Date(const Date& d)
{
cout << "const Date& d" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
与构造和析构函数一样,内置类型就直接传,自定义类型传值传参就要调用拷贝构造完成,这里调用func之前先传参,传参就会形成一个拷贝构造,d是d2的别名,函数结束回来,传参完成,参数的传递就是完成拷贝构造调用,最后调用调用func函数,结束
也可以从建立函数栈帧的方式去看函数func(d2)的调用
当然也可以采用指针或者引用的方式,这种方式不会调用拷贝构造。
func(d2);
void func(Date& d)
{
d.Print();
}
//这种方式也不会调用拷贝构造,d是d2的别名
func(&d2);
void func(Date* d)
{
d->Print();
}
//这种情况下没有拷贝构造,因为传的是内置类型,把d2地址进行传递,用指针进行接收
③若未显示定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
cout << "Time::Time(const 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 d1;
// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
Date d2(d1);
return 0;
}
//这里如何去掉Stack的拷贝构造会发现下面的程序会崩溃掉,这里就需要深拷贝去解决。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
Stack(const Stack& st)
{
_array = (DataType*)malloc(st._capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
memcpy(_array, st._array, st._size*sizeof(DataType));
_size = st._size;
_capacity = st._capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
bool Empty()
{
return _size == 0;
}
DataType Top()
{
return _array[_size - 1];
}
void Pop()
{
--_size;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
class MyQueue
{
private:
Stack _st1;
Stack _st2;
int _size = 0;
};
int main()
{
Stack st1(10);
st1.Push(1);
st1.Push(1);
st1.Push(1);
Stack st2 = st1;
st2.Push(2);
st2.Push(2);
while (!st2.Empty())
{
cout << st2.Top() << " ";
st2.Pop();
}
cout << endl;
//输出1 1 1
while (!st1.Empty())
{
cout << st1.Top() << " ";
st1.Pop();
}
cout << endl;
//输出2 2 1 1 1
MyQueue q1;
MyQueue q2(q1);
//这里自定义类型会去调用栈的拷贝构造(栈的拷贝构造是深拷贝),内置类型完成值拷贝
return 0;
}
Stack(const Stack& st)
{
_array = (DataType*)malloc(st._capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
memcpy(_array, st._array, st._size*sizeof(DataType));
_size = st._size;
_capacity = st._capacity;
}
实践中总结:
1、如果没有管理资源,一般情况不需要写拷贝构造,默认生成的拷贝构造就可以。如:Date
2、如果都是自定义类型成员,内置类型成员没有指向资源,也类似默认生成的拷贝构造就可以。如:MyQueue3、一般情况下,不需要显示写析构函数,就不需要写拷贝构造
4、如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝。如:Stack Queue List等