🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨ 🐻推荐专栏1: 🍔🍟🌯C语言初阶 🐻推荐专栏2: 🍔🍟🌯C语言进阶 🔑个人信条: 🌵知行合一 🍉本篇简介:>:讲解C++中有关类和对象的介绍,本篇是中篇的第结尾篇文章,讲解拷贝构造,运算符重载以及取地址重载符. 金句分享: ✨别在最好的年纪,辜负了最好的自己.✨
拷贝构造函数:
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
假设哦我们需要创建两个一模一样的对象A和B.
那我们可以先创建一个对象A,再通过将A作为参数,传给B进行初始化,
即一个自定义类型实例化出的对象(B)用另一个该类型实例化出的对象(A)进行初始化.
class Date
{
public:
Date(int year = 2020, 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 A(2023, 7, 20);
A.Print();
printf("\n");
Date B(A);//会调用系统生成的拷贝构造
B.Print();
return 0;
}运行结果:
2023-7-20 2023-7-20
其实拷贝构造函数就是构造函数的一种重载形式,他也是六大天选之子之一,没有显式定义时,编译器也会自动生成,但是只会完成"浅拷贝"(下面讲)…
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
class Date
{
public:
Date(int year = 2020, int month = 1, int day = 1)//全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数
{
cout << "拷贝构造" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 7, 20);
d1.Print();
printf("\n");
Date d2(d1);
d2.Print();
return 0;
}我们发现Date(const Date& d)这里使用了引用传参,如果直接传参会怎样呢?

为什么会报错呢?
void test(int a)
{
}
void test(Date d1)
{
}
int main()
{
Date d1(2023, 7, 20);
test(2);
test(d1);
return 0;
}这段代码会调用Date 类的拷贝构造.

对于自定义类型作为参数时,必须调用该类型的拷贝构造函数. 所以可以回答上面的问题了.

所以拷贝构造函数传参时采用引用传参,这样就避免了传参时调用拷贝构造.
前面在介绍编译器自动生成的"拷贝构造函数"时,提到了浅拷贝,那什么是浅拷贝呢?
浅拷贝:按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

深拷贝:

示例: 栈类中没有显式定义拷贝构造函数,编译器自动生成的拷贝构造是浅拷贝带来的问题.
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
typedef int DataType;
class Stack
{
public:
Stack(int capacity=5)//全缺省构造函数
{
cout << "Stack" << endl;
_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()//析构函数
{
cout << "~Stack"<< endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_array, newcapacity *
sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);//这条语句会报错.
return 0;
}运行结果:

原因:
因为编译器默认生成的拷贝构造是浅拷贝,这里两个对象的_array也就指向了同一块内存空间,但是两个对象的声生命周期结束时,会调用各自的析构函数,这也就导致对同一块空间进行了释放操作.
解决方法:
显示定义一个拷贝构造函数.
Stack(const Stack& S)//深拷贝
{
_array = (int*)malloc(sizeof(int) * S._capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(S._array,_array,sizeof(int)*S._size);
_capacity = S._capacity;
_size = S._size;
}拷贝构造使用场景:
class Date//日期类
{
public:
Date(int year = 2023, int month = 10, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void test1()
{
Date d1(2023, 7, 28);
Date d2;
if (d2 == d1)
{
cout << "d1=d2";
}
if (d1 < d2)
{
cout << "d1<d2";
}
}
自定义类型是无法像内置类型一样比较大小和使用一些常规运算符的. 为什么呢? 因为自定义类型是用户自己定义的,编译器不知道该如何进行比较.那编译器太笨了吧,日期按 年-月-日依次比较不就行了? 个人理解:
person是按名字还是按职位,还是按什么?你不告诉编译器如何比较,编译器也很无奈,不敢瞎搞的.year是年,要是牛牛用nian来命名,他也能识别出来是年吗?综上,自定义类型如何进行运算比较,只有用户自己知道,所以用户需要自己来设计规则.

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型.
函数名:关键字operator+需要重载的运算符符号。
operator+ 需要重载的运算符
注意事项:
operator@
*不要实现为了/,害人是不对的.
.*” (点星) 、" :: " sizeof ? : .
在C++中,有一些操作符是不能被重载的,包括以下几种情况:
::(作用域解析操作符):作用域解析操作符用于指定命名空间、类或结构的作用域,并访问其成员。它不能被重载,因为它的含义在语言中已经固定不可更改。
.*(指针到成员操作符)和 ->*(指向成员指针的操作符):这些操作符用于访问类的成员指针。它们存储了一个指向类成员的指针,并用于在运行时访问该成员。它们也不能被重载。
sizeof(大小操作符):sizeof操作符用于获取一个对象或类型的大小(以字节为单位)。它是一个编译时的操作符,不能在运行时被重载。因为在编译时就已经确定了对象或类型的大小。
?:(条件操作符,即三目运算符):条件操作符是一个三元操作符,用于根据条件选择不同的表达式。它不能被重载,因为它的语法和含义已经在语言中定义好了。
.在C++中,点操作符(“.”)是用来访问对象的成员的,而它本身是不能被重载的。点操作符的行为在语言中是固定的,无法通过重载来改变。
class Date//日期类
{
public:
Date(int year = 2023, int month = 10, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void test1()
{
Date d1(2023, 7, 28);
Date d2;
d1.print();
d2.print();
cout << endl;
d2 = d1;
d1.print();
d2.print();
}
int main()
{
test1();
return 0;
}
赋值运算符只能重载成类的成员函数不能重载成全局函数: 原因:
赋值运算符如果不显式实现(自己定义),编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

那编译器会生成一个默认赋值运算符重载会做什么事情呢?
以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
当然对于日期类这种只需要浅拷贝的类来说,编译器默认生成就已经足够了,但是像stack类,同样引发深浅拷贝的问题.
哈哈哈,期待到最后的两个默认成员函数其实没什么要讲解的.
operator&()operator&()const
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可除非你想搞点特殊的,返回一个特定的特殊地址.
本篇内容到此讲解完了,后续介绍日期类的具体实现,方便大家更好的理解类和对象的知识,实战才能锻炼水平哦.