【C++基础七】类和对象—下
1.拷贝构造函数
使用一个已经存在的对象,初始化一个正在定义的对象就是拷贝构造
Date d1(2025,3,13);
Date d2(d1);//用d1初始化d2
拷贝构造特点:
- 拷贝构造是构造函数的一个重载形式
- 拷贝构造函数只能有一个参数且必须是类类型对象的引用
class Date
{
public:
Date(int year = 2025, int month = 3, int day = 13)//默认构造函数
{
_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(1999,9,9);
Date d2(d1);//拷贝构造
return 0;
}
2.拷贝构造引用传递的意义
class Date
{
public:
Date(Date d)//错误的拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1999,9,9);
Date d2(d1);//拷贝构造
return 0;
}
类的对象d2实例化需要先调用构造函数,所以需要先传参数,把d1传给拷贝构造中d的这个过程属于传值调用,若两者为内置类型(int ,char ,double)会直接进行拷贝,但d1和d都是自定义类型date,所以会发生拷贝构造,date d(d1)
又是一个拷贝构造,又会发生重复上述过程,从而导致无线循环下去
class Date
{
public:
Date(const Date& d)//拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1999,9,9);
Date d2(d1);//拷贝构造
return 0;
}
由于拷贝中的d变成了d1的别名,d相当于d1本身,所以参数d1传给d的过程中, 不会发生拷贝构造
3.默认拷贝构造函数
如果用户不显示写拷贝构造函数,编译器会自动生成一个默认构造函数
默认构造函数会对内置类型完成值拷贝(浅拷贝)
默认构造函数会去调用自定义类型的构造函数完成拷贝
对于内置类型来说,浅拷贝可能会有问题
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 int& data)//插入函数
{
_array[_size] = data;
_size++;
}
~Stack()//析构函数
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
int *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1(10);
s1.push(1);`在这里插入代码片`
s1.push(2);
s3.push(3);
Stack s2(s1);
retrn 0;
}
stack类没有显示写拷贝构造函数,编译器会自动生成默认拷贝构造函数
默认构造函数是按照字节方式直接拷贝的,array指向的空间是动态开辟在堆区存储的,使用值拷贝的方式,s1和s2中的array指向的空间相同
当s1和s2生命周期结束时,会分别调用它们各自的析构函数,然而两个对象中的指针指向的空间相同,析构函数会调用两个free释放空间,同一份空间释放两次就会出错
对拷贝构造函数的总结:
拷贝构造函数的参数必须是引用
若类中没有涉及到空间申请,则默认拷贝构造就够用了
下面这两种写法都是在拷贝构造:
Date d1(2025,7,30);
Date d2(d1);
Date d3 = d1;
拷贝构造的典型应用场景:
- 使用已存在对象创建新对象
- 函数参数类型为自定义类型对象
- 函数返回值类型为自定义类型对象
后面两种情况实际上是因为在传值和传 返回值时会自动拷贝
4.运算符重载
在日期类中定义一个对象:
Date d1(2023,3,13);
Date d2 = d1 + 
100; //普通的加号不能实现此功能
如果想要计算100天后是几月几号,直接使用+无法实现这个功能
普通的加号只适用于内置类型,对于自定义类型需要的有特定功能的运算符需要用户自己写,而自己实现的运算符就是函数重载
C++为了增强代码的可读性,引入了运算符重载
运算符重载是具有特殊函数名的函数,也具有函数名字,返回值类型以及参数列表,其返回值类型和参数列表与普通函数类似
关键字:operator
举例说明实现日期类+号的重载:
Date operator+(Date d1, int x);
运算符重载是针对自定义类型的,所以函数参数中必须有一个自定义类型参数
但是,如果运算符重载写在类外,它就不能访问类中的私有成员
所以我们常把运算符重载写在类中
class Date
{
public:
Date(int year = 1999, int month = 9, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)//有隐藏的this指针,&d1传过来有this接收
{
return _year == d2._year //由于隐藏的this指针的存在,取第一个参数d1的地址传过去被this指针接收,_year等价于d1._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022,12,21);
Date d2(2022,12,22);
cout << (d1 == d2) << endl;
//d1 == d2等价于d1.operator == (d2);
return 0;
}
5.赋值运算符
赋值运算符十分特殊,若我们不显示写,编译器会自动生成一个
所以赋值运算符只能写在类中,如果显示写在了类外,有可能会和编译器自动生成的一个赋值函数冲突了
class Date
{
public:
Date& operator=(const Date& d)//返回值类型是引用,传*this的引用作为变量的别名,相当于传*this本身,不会消耗一块空间,自然在return*this 返回时,不会进入拷贝构造中,而是直接返回
{
//为了防止自己给自己赋值的事情发生,如:d1=d1
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;//this是类对象(被赋值的)的地址,*this就是类对象本身
}
private:
int _year;
int _month;
int _day;
};
赋值运算符返回类对象的原因是:多次连续赋值
Date d1(2025,3,13);
Date d2;
Date d3;
d2 = d3 = d1;//连续赋值
//d3 = d1 等价于 d3.operator=(d1);
6.前置++和后置++
C++为了区分前置和后置++,在后置++的函数中多加一个int类型的参数来区别前置++
虽然后置++多了一个参数,但是这个参数完全没用,只用于区别前置++
前置++:
Date& operator++();
后置++:
Date& operator++(int);
使用:
虽然后置++多了一个参数,但是不需要我们显示传参,编译器会自动帮助我们传参
Date d1(2023,7,31);
//前置++
++d1;
//后置++
d1++;
7.const成员
7.1对象调用const成员函数
将&a传过去由默认构造函数中隐藏的this指针接收, a指针类型为 const date* ,this指针类型为 date* ,将const date * 传给date * 属于权限的放大,报错
class Date
{
public:
void print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
const date a;
a.print();//权限放大,报错
return 0;
}
在括号外面加上const,使this指针类型改成 const date*
class Date
{
public:
void print()const //this指针类型变为const date*
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
const Date a;
a.print();//正确 权限保持
return 0;
}
const 在*
左右对于指针的影响:
const date* p1
: 修饰的是* p1, const在 * 左面 指针所指向的内容只读date const * p2
: 修饰的是* p2,const在* 左面 指针所指向的内容只读date * const p3
: 修饰的是 p3,const在 * 右面,指针本身只读
注意:
class Date
{
public:
void print()const //this指针类型变为const date*
{
_a = 10;//报错
cout << _a << endl;
}
private:
int _a;
};
int main()
{
const Date a;
a.print();
return 0;
}
若在print函数内部修改成员变量_a的值就会报错,因为_a是this->_a,但是由于 this指针当前类型为 const date *,使this指向的内容只读,所以报错
7.2成员函数调用const成员函数
- 在temp1中时,this指针的类型为 date*,进入 temp2后, this指针 转换为 const date *,属于权限缩小 ,可以实现
class Date
{
public:
void temp1()
{
temp2();
}
void temp2()const
{
}
private:
int _a;
};
int main()
{
Date a;
a.temp1();
return 0;
}
- temp4中 this指针类型为 const date*,temp3中 this指针类型为 date*,权限放大了,不可以实现
class date
{
public:
void temp3()
{
}
void temp4()const
{
temp3();
}
private:
int _a;
};
int main()
{
date a;
a.temp4();
return 0;
}
7.3取地址及const取地址操作符重载
class date
{
public:
date* operator&()
{
return this;
}
const date* operator&()const
{
return this;
}
};
int main()
{
date d;
cout << &d << endl;
return 0;
}