这是关于一个普通双非本科大一学生的C++的学习记录贴
在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料
那么开启正题
今天继续学习了类和对象,对c++的理解更深了一点点,由于考试周影响进度更新有点慢,下面开始分享
拷贝构造函数::只有一个形参(this指针不算),该形参是对本类类型对象的引用,一般加上const修饰,在类类型对象创建新对象时由编译器自动调用
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个,且是类类型对象的引用,使用传值方式编译器会直接报错,因为会引发无穷递归调用
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d) //不能使用值传递,会引发无穷递归
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 1, 9);
Date d2(d1);
Date d3 = d2;//这两种方式,编译器都会自动识别调用拷贝构造函数
return 0;
}
关于为什么不能使用值传递,对于内置类型的值传递,编译器可以进行处理,而对于自定义类型的值传递需要借助函数来传递,这里的函数就是拷贝构造函数,而我们要实习的拷贝构造函数,这里就涉及到先有鸡还是先有蛋的问题,显然我们是只能使用引用传参的
3.如果我们未显式定义,编译器会生成默认的构造拷贝函数
注意::默认的拷贝构造函数对象按内存存储方式按字节完成拷贝,这种叫做浅拷贝,或者值拷贝(对应的肯定有深拷贝的存在,这里我们还涉及不到,后面再了解)
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 1, 9);
Date d2(d1);
Date d3 = d2;//这两种方式,编译器都会自动识别调用拷贝构造函数
return 0;
}
这样的代码可以正常运行,并且得到我们的预期结果
4.编译器生成的默认构造函数已经可以完成字节序的值拷贝,但是有很多情况下,但但的值拷贝是不够的
class Stack
{
public:
Stack(int capacity = 10)
{
_a = (int*)malloc(sizeof(int) * capacity);
_capacity = capacity;
_size = 0;
}
~Stack()
{
free(_a);
_a = nullptr;
_size = 0;
_capacity = 0;
}
private:
int* _a;
int _capacity;
int _size;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
上面这段代码运行起来就会出错,当然拷贝构造函数是可以正常调用的,但是到了析构函数清理资源的时候,就有问题了,两个对象的*_a是指向同一块内存的,数据的调用肯定会出现错乱,并且,在析构函数清理资源阶段,*_a被释放了两次,程序就崩溃报错了
实际运用当中,这种涉及动态空间管理的拷贝,我们应使用深拷贝,具体怎么实现,在后面再深入
5.拷贝构造函数的典型调用场景
void Func1(Stack s)
{
;
}
Stack Func2()
{
;
}
int main()
{
Stack s1;
Func1(s1);
Func2();
return 0;
}
a.使用已经存在的对象创建新对象
b.函数参数类型为类类型对象
c.函数返回值类型为类类型对象
注意::为了提高效率,一般能使用引用传参的情况,尽量使用引用传参
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字,以及参数列表
函数名字::关键字operator后面接需要重载的运算符符号
函数原型::返回值类型operatorca操作符(参数列表)
这里还有一些需要注意的点
a.不能C++本来就没有的运算符(@)
b.重载操作符必须有一个类类型参数(this指针也算)
c.用于内置类型的运算符,不能改变其含义
d.作为类函数重载时,其形参看起来比操作数少1,因为函数形参列表隐含this
e.(.*)(::)(sizeof)(?:)(.)这五个运算符不能被重载
1.赋值运算符重载格式
参数类型::const T& ,传递引用可以提高传递效率
返回值类型,T&,返回值引用提高传递效率,目的时为了支持连续赋值
检查是否自己给自己赋值
返回*this,要符合连续赋值的含义(从右向左赋值,返回值是左值)
利用上述描述,我们可以对类类型日期写出如下代码
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date d)
{
if (*this != d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
当然以上代码是有问题的,其中*this != d编译器无法处理,明天我们会写出 != 的重载函数
2.赋值运算符只能重载成类的成员函数不能重载成全局函数
因为赋值运算符如果不显式实现,编译器会生成一个默认的,此时我们再自己实现一个全局的赋值运算符重载,就和编译器类中得默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数
3.我们没有显式实现得时候,编译器会生成一个默认赋值运算符重载,以值得方式逐字节拷贝
注意::内置类型得变量是直接赋值得,而自定义类型成员函数需要调用对应类的赋值运算符重载完成赋值
注意::如果类中未涉及到资源管理,赋值运算符重载是否实现都可以,但是涉及到资源管理必须要直接实现,这与拷贝构造函数相似
后置++的重载因为是++后返回目标,我们可以返回引用,不需要创造临时变量
前置++的重载因为是++前返回目标,我们必须要创建临时变量存储操作前的类,再进行操作,最后返回临时变量,因为临时变量出作用域后不在拥有访问权限,所以我们只能值传递,不能传引用
综上::在能使用后置++的情况下我们尽量使用后置++,这样有利于提高程序运行效率
总结::拷贝构造函数和赋值运算重载让我们对类的使用更加方便,但是它有很多细节值得我们注意,作为后面的打下基础,我们应该深刻理解学习
今天的博客就到这里了,后续内容明天分享,最近因为考试周原因不能更新太多内容,等考试周结束了再"快马加鞭"
新手第一次写博客,有不对的位置希望大佬们能够指出,也谢谢大家能看到这里,让我们一起学习进步吧!!!