1.运算符重载
当运算符被用于类类型的对象时,C++允许我们通过运算符重载的形式指定出一个新的含义。C++规定类类型对象使用运算符时,必须转换成对应运算符重载,若没有对应的运算符重载,则编译器就会报错。
运算符重载是一个具有特名字的函数,名字是有operator和后面要定义的运算符共同构成,和其它函数一样,也是具有其返回类型和参数列表以及函数体。
重载运算符函数的参数个数和该运算符作用的运算对象数量一样多,一元运算符有一个参数,二元运算符有俩个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算符传给第二个参数。
如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
运算符重载以后,其优先级和结合性与对应的内置类型运算应该保持一致。
不能通过连接语法中没有的符号来创建新的操作符号:不然operator@。
代码展示:
创建俩个日期类,比较年月日是否相等,=是二元操作符,但是这个运算符重载在类里面,所以参数显示只有一个,另一个是隐式this,是有俩个参数,但是只显示一个。
class Date
{
public:
bool operator=(const Date& d2)
{
return _day == d2._day &&
_month == d2._month &&
_year == d2._year;
}
Date(int day = 1, int month = 1, int year = 1)
{
_day = day;
_month = month;
_year = year;
}
private:
int _day;
int _month;
int _year;
};
int main()
{
Date d1(1, 2, 3);
Date d2;
int a=d1.operator=(d2);
cout << a << endl;
return 0;
}
这里实现的是类与整型值相加
class Date
{
public:
Date(int day = 1, int month = 1, int year = 1)
{
_day = day;
_year = year;
_month = month;
}
Date(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
}
void operator+(int day)
{
_day += day;
}
void Print()
{
cout << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
int main()
{
Date s1;
s1 + 100;
s1.Print();
return 0;
}
1.2赋值运算符重载
赋值运算符重载是一个默认成员函数,用于两个已经存在的对象直接的拷贝赋值,跟拷贝构造不一样,拷贝构造是用于一个对象拷贝初始化给另一个要创建的对象。
赋值运算符重载的特点:
1.赋值运算符重载是一个运算符重载,规定必须重载为成员函数,赋值运算符的参数建议写出const当前类类型引用,否则会传值传参会有拷贝
2.没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝,对于自定义类型会调用它的赋值重载
3.像Date这样的类成员变量全是内置类型没有指向什么资源,不需要自己在写赋值重载函数。
class Date
{
public:
Date(int day = 1, int month = 1, int year = 1)
{
_day = day;
_year = year;
_month = month;
}
Date(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
}
void operator+(int day)
{
_day += day;
}
void Print()
{
cout << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
int main()
{
Date s1;
s1 + 100;
s1.Print();
Date s2;
s2 = s1;
s2.Print();
return 0;
}
2.拷贝构造函数
如果一个构造函数第一个参数是自身类类型的引用,且任何格外的参数都有默认值,则此构造函数也叫拷贝构造函数,拷贝构造是一个特殊的构造函数。
拷贝构造的特点:
1.拷贝构造函数构造函数的一个重载。
2.拷贝构造函数的参数第一个参数必须是类类型对象引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。
3.C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造来完成。
4.若未显式定义拷贝构造,编译器会自动生成拷贝构造。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节拷贝),对自定义类型成员变量会调用它的拷贝构造。
对于内置类型的成员变量,自动生成的拷贝构造函数确实会完成浅拷贝。这意味着它会简单地逐字节复制一个对象的成员变量到另一个对象中。这种行为对于内置类型(如整数、浮点数、字符等)是合适的,因为它们的赋值和拷贝操作本身就是逐位复制数据。
考虑以下示例:
#include <iostream>class MyClass {
public:
int x;
double y;// Compiler-generated copy constructor
MyClass(const MyClass& other) {
x = other.x; // 拷贝整数成员 x
y = other.y; // 拷贝双精度浮点数成员 y
}// Constructor to initialize x and y
MyClass(int a, double b) : x(a), y(b) {}
};int main() {
MyClass obj1(10, 3.14);
MyClass obj2 = obj1; // 调用拷贝构造函数std::cout << "obj2.x = " << obj2.x << ", obj2.y = " << obj2.y << std::endl;
return 0;
}在这个例子中,当我们创建 obj2 时,会调用自动生成的拷贝构造函数来初始化 obj2。这个构造函数会逐个成员地将 obj1 的成员变量 x 和 y 的值复制到 obj2 中的对应成员变量。
对于内置类型的成员变量,这种逐位复制的方式是高效且合适的。然而,对于包含指针或者动态分配内存的成员变量,浅拷贝可能会导致问题,因为它们只会复制指针地址,而不会复制指针指向的内容。在这种情况下,可能需要手动编写拷贝构造函数来执行深拷贝操作,以确保每个对象都有独立的资源副本。
总结来说,对于内置类型成员变量,自动生成的拷贝构造函数确实执行浅拷贝,这是其设计上的优化。
5.像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以自动生成拷贝构造就可以完成,不需要自己去写一个拷贝构造函数。像数据结构栈中的结构体就有指向的资源,栈是链表实现,链表头节点下面还连着其它的结点,如果是浅拷贝就只拷贝头节点而不会去拷贝下面的结点,深拷贝才会去拷贝头节点和其余的结点,一般写了析构的释放资源的都是显式拷贝构造,否则就不需要写拷贝构造函数。
下面是C++实现栈:
typedef int DataType;
class Stack
{
public:
void Init()
{
_array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == _array)
{
perror("malloc fail:");
return;
}
_Capacity = 3;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
int Empty() { return _size == 0; }
int size() { return _size; }
DataType Top() { return _array[_size - 1]; }
void Destory()
{
if (_array)
{
free(_array);
_array = NULL;
_Capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _Capacity)
{
int newcapacity = _Capacity * 2;
DataType* tmp = (DataType*)realloc(_array, newcapacity * sizeof(DataType));
if (tmp == NULL)
{
perror("realloc fail:");
return;
}
_array = tmp;
_Capacity = newcapacity;
}
}
private:
DataType* _array;
int _Capacity;
int _size;
};
int main()
{
Stack s1;
s1.Init();
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
//s1.Pop();
cout << s1.Top() << endl;
cout << s1.size() << endl;
return 0;
}
6.传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的返回对象的别名(引用),则没有产生拷贝,但是如果返回对象是当前函数局部域的局部对象,函数就销毁了,那么使用引用返回是有问题的,这时引用就像野引用(指针的野指针这样),传引用返回可以减少拷贝,但是要确保返回对象,在当前函数结束后还在,才能引用返回。