前言:本文介绍类和对象中的一些比较重要的知识点,为以后的继续学习打好基础。
目录
拷贝构造
拷贝构造的特征:
自定义类型的传值传参
自定义类型在函数中的传值返回
如果返回值时自定义的引用呢?
在什么情况下使用呢?
拷贝构造中的浅拷贝问题
编辑
为什么会释放两次呢?
那么什么情况下需要深拷贝?
运算符重载
运算符重载的基本语法:
类中运算符重载函数的调用的两种方法:
运算符重载与函数重载
运算符重载的特征:
运算符重载的价值:
如果将运算符重载成全局函数,就无法访问类中的私有成员了。
解决方法:
赋值运算符
调用拷贝构造与调用赋值重载的区别
拷贝构造
拷贝构造是一种特殊的构造函数
拷贝构造的特征:
1.是构造函数的重载
2.参数只有一个并且只能是引用
3.拷贝构造可以不显示写,编译器会自动生成默认构造。
浅拷贝(值拷贝)就是一个字节一个字节的拷贝。
编译器自动生成的默认拷贝的特点:对内置类型的成员,浅拷贝(值拷贝);对自定义类型的成员,拷贝需要调用其拷贝构造
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(Date& d)
{
cout << "拷贝构造" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,6,10);
Date d2(d1);//拷贝构造
Date d3 = d2;//拷贝构造
return 0;
}
自定义类型的传值传参
#include <iostream>
using namespace std;
class Date
{
public:
int GetYear()
{
return _year;
}
int GetMonth()
{
return _month;
}
int GetDay()
{
return _day;
}
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
cout << "拷贝构造" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
void f(Date d)
{
cout << d.GetYear() << " " << d.GetMonth() << " " << d.GetDay() << endl;
}
int main()
{
Date d1(2024,6,10);
Date d2(d1);
Date d3 = d2;
f(d1);
return 0;
}
以上代码的运行结果
在给f函数传值传参时,调用了一次拷贝构造。
结论:自定义类型在进行传值传参时会进行拷贝构造。
如果拷贝构造的参数是自定义类型而不是自定义的引用那么就会出现无穷递归调用。
自定义类型在函数中的传值返回
下面有一段代码,以这段代码为例讲一下该问题。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
Date f(Date d)
{
return d;
}
int main()
{
Date d1(2024, 6, 10);
Date d2 = f(d1);
return 0;
}
结论:如果返回值,是自定义类型,那么,返回时就会进行拷贝构造,创建临时对象,再将临时对象赋值给正在创建的类。
用一段错误代码解释上面的结论:
Date f(Date& d)
{
return d;
}
int main()
{
Date d1(2024, 6, 10);
Date& d2 = f(d1);
return 0;
}
上述错误代码的报错:
原因是因为,临时对象具有常性,用d2来引用临时对象是会出现权限放大的问题,所以验证了上述的结论,如果加上const(权限平移)报错就会消失。
而编译器为了提高效率往往会直接将其优化为一次拷贝构造。
如果返回值时自定义的引用呢?
Date& f()
{
Date d1(2023, 1, 2);
return d1;
}
int main()
{
Date& d1 = f();
return 0;
}
因为,d1 实在函数中定义的对象,出了函数的作用域就会销毁。
从栈帧的角度来理解,引用的本质是指针,f函数被销毁了,main函数中的d1仍指向f中的d1的那块已被销毁的空间
自定义类型的引用返回存在风险
在什么情况下使用呢?
出了函数的作用域,生命周期没到,不构析,对象还在,,那么就可以用引用 返回
出了函数的作用域,生命周期到了,析构,对象不存在,那么就只能用传值返回
拷贝构造中的浅拷贝问题
以下代码存在浅拷贝问题
#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack()" << endl;
_arr = (int*)malloc(sizeof(int) * capacity);
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()"<<endl;
free(_arr);
_capacity = 0;
_top = 0;
}
private:
int* _arr;
int _top;
int _capacity;
};
int main()
{
Stack st1(4);
Stack st2(st1);
return 0;
}
程序崩溃:
该代码的问题就在于对一块开辟的空间释放两次。
为什么会释放两次呢?
因为没有显示写拷贝构造函数,所以用的是编译器自动生成的拷贝构造函数(浅拷贝),所以在拷贝构造st2时,使st2中_arr指向的空间与st1中的一样,最后分别调用析构函数时,就造成了对用一块开辟的空间释放两次。
解决方案就是深拷贝:
Stack(Stack& st)
{
_arr = (int*)malloc(sizeof(int) * st._capacity);//深拷贝
_capacity = st._capacity;
_top = st._top;
}
那么什么情况下需要深拷贝?
总结:
1.如果没有管理资源,就不显示写拷贝构造,用默认拷贝构造就可以
2.都是自定义的成员,内置类型(内置类型不指向资源),也用默认拷贝;如果自定义类型的成员的内置类型指向资源,那么在该自定义类型中显示写拷贝构造
3.一般,不需要写析构函数,就需要写构造函数
4.内部有指针或一些值指向资源,显示写析构释放,通常需要写拷贝构造来完成深拷贝
运算符重载
运算符重载的基本语法:
返回值类型 + operater+运算符(参数列表)
operator是关键字,operator和运算符一起构成函数名。
类中运算符重载函数的调用的两种方法:
//在类中实现的+运算符重载(实现日期与天数的相加)
Date operator+(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
//已经在类中写了一个加号重载的函数
Date d1(2024, 6, 10);
d1 + 100;//第一种调用方法
d1.operator+(100);//第二种调用方法
运算符重载与函数重载
运算符重载和函数重载没有关系,是两回事,而多个相同的运算符的重载是函数重载。
比如<<(流插入)可以自动识别内置类型的原因就是对<<进行重载,构成了函数重载。
运算符重载的特征:
1.不能通过其他符号重载
2.必须有一个类类型的参数
3.含义不能改变(这里是建议,比如重载的+的含义是将两个数相加,而你写的含义是相减)
4.一般,参数比运算符操纵的操作数的数目少1,因为在参数列表中有隐含的this指针
5. .* :: sizeof ?: . 这五个操作符不能被重载,.*是用于类成员函数指针的访问,
如果想了解:函数指针到底需不需要解引用?类成员函数呢?_函数指针需要解引用吗-CSDN博客
运算符重载的价值:
运算符重载是运算符不仅限于操纵内置类型的数据,可以实现类与类之间,或类与内置类型直间的运算,可以增强代码的可读性。
一个使用运算符重载的例子:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//得到当前月份的天数
int GetMonthDay(int year, int month)
{
int month_day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if(month==2&&((year%4==0&&year%100!=0)||year%400==0))
return 29;
return month_day[month];
}
//重载+运算符实现日期与天数的相加
Date operator+(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 6, 10);
Date d2 = d1 + 100;
d2.Print();
return 0;
}
上述代码中的+的重载,是在类中实现的,或在类中声明,在类外实现。
如果将运算符重载成全局函数,就无法访问类中的私有成员了。
解决方法:
1.在类中实现成员的Get(获取成员)和Set(重新给成员赋值)的接口
2.将全局函数设为该类的友元
3.重载为成员函数(可以访问类的成员,但函数不在是全局函数)
这些方法比较建议第二种。
以下的代码是通过友元来实现全局减号的运算符重载
#include <iostream>
using namespace std;
class Date
{
//友元就是在函数前加上一个关键字friend,并在相应的类中声明
friend Date operator-(Date& d,int day);
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int GetMonthDay(int year, int month)
{
int month_day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if(month==2&&((year%4==0&&year%100!=0)||year%400==0))
return 29;
return month_day[month];
}
Date operator+(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
//全局函数减号的重载
Date operator-(Date& d,int day)
{
d._day -= day;
while (d._day <= 0)
{
if (d._month == 1)
{
d._month = 12;
d._year--;
}
else
{
d._month--;
}
d._day += d.GetMonthDay(d._year, d._month);
}
return d;
}
赋值运算符
赋值运算符重载也是6个默认成员函数之一。
调用拷贝构造与调用赋值重载的区别
Date d1(2024, 6, 10);
Date d2 = d1;//拷贝构造
Date d3(d1);//拷贝构造
Date d4(2024, 2, 11);
d4 = d1;//赋值重载
注意:上面代码中两个等号的调用方式容易混,但最后这两个有本质的区别。
结语:希望本文能够让你有所收获 。