类的六个默认函数
如果一个类当中没有成员的话,那叫空类,实际上空类有6个编译器默认生成的函数成员
默认成员函数:没有显示实现,编译器生成的成员函数称为默认成员函数
1,构造函数与构析函数
1.1构造函数的概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以
保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
1.2构造函数的特性
构造函数主要任务是初始化对象,并不是开辟空间
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
class Data {
public:
//无参构造函数
Data(){
_year = 1;
_month = 1;
_day = 1;
}
//含参构造函数
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省函数
Data(int year = 2, int month = 2, int day = 2)
{
}
void printf()
{
cout << _year << _month << _day << endl;//崩溃,
cout << "printf()" << endl;//正常
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
d1.printf();
Data d2(2024, 2, 2);
d2.printf();
Data d3;
d3.printf();
return 0;
}
4.1无参构造函数与含参构造函数的使用,无参函数直接Data d1;不加括号为了
避免重复声明新函数
Data d1;
d1.printf();
Data d2(2024, 2, 2);
d2.printf();
4.2全缺省函数与无参构造函数,d1与d3构成重载函数,但下面会构成重载失误
Data d1;
d1.printf();
Data d3;
d3.printf();//d3与d1构成重载,
会出现下面错误
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
6. C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。
class Time {
public:
Time()
{
cout << "time()" << endl;
_hour = 0;
_minute = 0;
}
private:
int _hour;
int _minute;
};
class Data
{
public:
private:int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Data d;
return 0;
}
上面的代码的处理结果是内置类型不一会被处理(具体看编译器vs不会处理),而自定义类型会被处理,Time是自定义类型所以会初始化
但只有是无参构造函数才会成功,若有参数会报错Time(int a){}
1.3析构函数的概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
1.4析构函数的特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
}
如果像这一类,申请了空间,没有调用显示析构函数,会有内存泄漏的风险,所以如果有类调用堆上的空间,一定要调用析构函数
2,拷贝构造函数
2,1概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
2.2特性
1, 拷贝构造函数的参数只有一个
2参数必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(const Date& d) // 正确写法
Date(const Date& d) // 错误写法:编译报错,会引发无穷递归
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
因为传值会调用拷贝构造函数,造成无限递归,所以用引用
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
4类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
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;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
这是为什么,是因为浅拷贝是在同一块空间,如果创建两个对象,s1,s2,都指向同一内存,调用析构函数时,会将同一块空间free两次,造成错误
5. 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
class Date {
public:
Date(int year,int month,int day)
{
cout << "Data()"<<endl;
_year = year;
_month = month;
_day = day;
}
~Date()
{
cout << "~Data()"<<endl;
}
Date(const Date& d)
{
cout << "Date(const Date&d)"<<endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
Date test(Date d1)
{
Date tem(d1);第二次拷贝构造
return tem;//本来是第三次拷贝构造,但被编译被优化了因为第二次拷贝构造的值可以做返回值
}
int main()
{
Date d1(2024, 2, 1);
test(d1);//第一次拷贝构造函数
return 0;
}
下面是结果
6.传值与引用的细分
1传值会多拷贝一次,比较麻烦,但是可以避免一些错误
2而引用虽然方便但是会有一些情况出问题;
class Date {
public:
Date(int year,int month,int day)
{
cout << "Data()"<<endl;
_year = year;
_month = month;
_day = day;
}
~Date()
{
cout << "~Data()"<<endl;
_year = -1;
_month = -1;
_day = -1;
}
Date(const Date& d)
{
cout << "Date(const Date&d)"<<endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
Date operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day =d._day;//this是d2的形参,d是_d4
//然后d1是this的位置,d2是d4
return *this;
}
void printf()
{
cout << _year <<" " << _month <<" " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
Date& fun()
{
Date d(1, 2, 2);
return d;
}void func()
{
}
int main()
{
Date d1(2024, 4, 14);
Date d2(d1);
Date d3 = d1;//拷贝构造,把存在的拷贝给不存在的
Date d4(2024, 5, 1);
d1= d4;//赋值重载,不会调用拷贝构造函数
//连续赋值
d1 = d2 = d4;
//到这一行总共5个调用拷贝,但是把上面的变成引用返回,只有两个
return 0;
}
下面是结果
但是如果是引用的话
下面是一些错误的发生
Date& fun()
{
Date d(1, 2, 2);
return d;
}
int main()
{
Date& ret = fun();
ret.printf();
return 0;
}
因为return d前先析构,再返回,本来是调试看到析构后this是随机值是野空间,如果下面在调用一个函数,就会可能会变野空间会 变化,随机值被改变
总 结 返回对象是临时对象,或局部变量不用引用返回,有风险
1如果返回对象生命周期到了,不会析构,可以用传引用返回uobuyi
创作不易,希望点赞!!!
目录