关于缺省参数?
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
class Date
{
public:
//全缺省的构造函数
Date(int year = 1995, int month = 8,int day = 19)
:_year(year), _month(month), _day(day)
{}
void Print()const
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//当这个里面什么都不输入的情况下
//将会使用默认值。也就是1995,08,19
Date d2(2024);
//那么这个地方的话,会使用提供的年,
//打印出来就是2024-8-19
Date d3(2024, 6, 25);
d1.Print();
d2.Print();
d3.Print();
std::cout << std::endl;
}
全缺省的构造函数是指一个类的构造函数,其中所有参数都有默认值。这使得在创建对象时可以选择不传递任何参数,构造函数将使用提供的默认值来初始化对象。
全缺省构造函数的特点
-
所有参数都有默认值:在函数的参数列表中,每个参数都提供了一个默认值。
- 可以选择性传参:调用构造函数时可以省略某些或所有参数,未提供的参数将使用其默认值。
作用与优势
- 简化对象创建:全缺省构造函数允许用户在创建对象时省略一些参数,只提供他们关心的参数。
- 代码更简洁:调用者可以少写一些参数,提高代码的可读性。
- 默认初始化:提供合理的默认值,可以确保对象在未完全指定参数时仍然处于有效状态。
注意事项
- 确保默认值合理且对对象是有效的。
- 在提供默认值时,考虑类的实际使用场景和需求,避免不必要的复杂性。
拷贝构造函数的作用
这段代码展示了一个简单的拷贝构造函数的实现。让我们逐步解释它:
// 拷贝构造函数
Date(const Date& d)
{
this->_year = d._year;
//d._year 是拷贝构造函数的参数对象 d 的成员变量。
_month = d._month;
_day = d._day;
}
拷贝构造函数的作用
拷贝构造函数用于创建一个新对象,其内容是通过复制另一个同类型对象的内容而来。在这里,Date
类的拷贝构造函数会复制另一个Date
对象的年、月、日信息。
参数和语法
- 参数类型:
const Date& d
,这表示拷贝构造函数接收一个常量引用作为参数,用来指定需要被复制的对象。 - 初始化对象:在函数体内部,通过成员初始化列表(不是必须,但是更高效),将当前对象的
_year
、_month
和_day
成员变量赋值为参数对象d
的对应成员变量的值。
为什么使用常量引用作为参数?
- 避免不必要的复制:使用常量引用可以避免在传递参数时触发额外的拷贝构造函数,提高效率。
- 确保不修改参数:常量引用确保在函数内部不能修改传入的对象
d
,符合拷贝构造函数的语义,即创建一个新对象,而不是修改现有对象。
成员初始化列表的优势
在拷贝构造函数中使用成员初始化列表(Member Initialization List)有以下优势:
- 初始化顺序:确保成员变量按照它们在类中声明的顺序初始化,而不是在构造函数体内按照定义的顺序。
- 效率:直接初始化成员变量,而不是先默认构造再赋值,可以更高效地创建对象。
- 常见做法:是良好的 C++ 编程实践,尤其是在拷贝构造函数和其他构造函数中。
使用示例
假设有两个 Date
对象 d1
和 d2
,可以通过拷贝构造函数初始化一个新的对象 d3
:
Date d1(2023, 6, 25); // 创建一个 Date 对象 d1
Date d2 = d1; // 使用拷贝构造函数创建一个新对象 d2,其内容与 d1 相同
在这个示例中,d2 = d1;
这行代码会调用拷贝构造函数,将 d1
的值复制给 d2
,从而创建一个完全独立但是内容相同的新对象。
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) {}
Date(const Date& d) {
this->_year = d._year;
_month = d._month;
_day = d._day;
}
void Print() const {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2023, 6, 25); // 创建一个 Date 对象 d1
Date d2(d1); // 使用拷贝构造函数创建一个新的 Date 对象 d2
cout << "d1: ";
d1.Print(); // 输出: 2023-6-25
cout << "d2: ";
d2.Print(); // 输出: 2023-6-25
return 0;
}
在这个示例中:
d1
是通过参数构造函数创建的对象,日期为2023-6-25
。d2
是通过拷贝构造函数创建的对象,使用d1
来初始化。
当执行 Date d2(d1);
时,拷贝构造函数被调用,d2
的 _year
、_month
和 _day
成员被设置为 d1
的 _year
、_month
和 _day
的值,因此 d2
和 d1
的日期相同。
总结
拷贝构造函数是 C++ 中的一个重要概念,用于创建一个对象的副本,通常在对象初始化时调用。它通过常量引用参数和成员初始化列表实现了高效和准确的对象复制。
赋值运算符重载
Date& operator=(const Date& d)
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
-
函数签名:
Date& operator=(const Date& d)
: 重载赋值运算符,返回当前对象的引用。const Date& d
: 传入一个常量引用,表示赋值操作的右操作数(即要复制的数据来源)。
-
自我赋值检查:
if (this != &d)
: 检查是否自我赋值。this
是一个指向当前对象的指针,&d
是传入对象的地址。如果两个对象是同一个,赋值操作就没有必要进行。- 这一步是关键的,因为如果忽略自我赋值检查,在复杂对象(如包含动态分配内存的对象)中可能会导致未定义行为或资源泄漏。
-
复制成员变量:
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
- 这些操作将传入对象
d
的成员变量的值复制到当前对象的对应成员变量中。
-
返回当前对象的引用:
return *this;
- 返回当前对象的引用,以便支持链式赋值(如
d1 = d2 = d3
)
使用示例
假设你有两个 Date
对象 d1
和 d2
,你可以将 d2
的值赋给 d1
:
Date d1(2022, 5, 15); // 创建 Date 对象 d1
Date d2(2023, 6, 25); // 创建 Date 对象 d2
d1 = d2; // 使用赋值运算符重载,将 d2 的值赋给 d1
cout << "d1: " << d1 << endl; // 输出: 2023-6-25
cout << "d2: " << d2 << endl; // 输出: 2023-6-25
在这个示例中,d1 = d2
触发了赋值运算符重载函数,d1
的 _year
、_month
和 _day
被设置为 d2
的相应值。最终 d1
和 d2
的日期相同。
为什么需要自我赋值检查
自我赋值检查在处理对象的赋值操作时尤为重要,特别是在对象包含指针或动态分配的资源时。例如:
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
delete[] this->_data; // 假设有动态分配的数据
this->_data = new char[strlen(d._data) + 1];
strcpy(this->_data, d._data);
}
return *this;
}
在这个例子中,如果没有 if (this != &d)
检查,并且 d
和当前对象是同一个对象,自我赋值会导致删除已分配的内存,然后试图从已经删除的内存复制数据,从而引发未定义行为。
总结
赋值运算符重载允许安全地将一个对象的状态赋值给另一个对象,并正确处理自我赋值的情况。它在确保对象一致性和支持链式赋值操作方面起着重要作用。在实际应用中,尤其是处理包含动态分配资源的对象时,自我赋值检查非常关键。