个人主页点这里~
1. 类的默认成员函数
在C++中,当你定义一个类时,即使没有显式地声明它们,编译器也会为该类生成一些默认成员函数。这些默认成员函数在特定情况下非常有用,尤其是当你进行对象拷贝、赋值、销毁或动态内存管理时。
一 . 构造函数
构造函数是特殊的成员函数之一,虽然他的名字是构造,但其实并不是不是用来开辟空间,创建对象的(这些局部变量在栈帧创建时就开辟好了),而是在对象实力化时初始化对象的。本质上,就是为初始化功能提供方便(不需要显示调用函数)。
构造函数的特点:
- 函数名与类名相同
- 无返回值
- 构造函数可以重载
- 对象实例化会自动调用其对应的构造函数,如果类中没有显式定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错。
- 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数(不传实参就可以调用的构造就叫默认构造)。但是这三个函数有且只有一个存在,不能同时存在。
说明:内置类型就是语原有的int char double ,自定义类型就是使用class/struct来定义的类型
#include<iostream>
using namespace std;
class Date
{
public:
// 1.无参构造函数 (默认)
/*Date()
{
_year = 1;
_month = 1;
_day = 1;
}*/
// 2.带参构造函数
//Date(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
// 3.全缺省构造函数 (默认)
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//如果是1和3,或2和3同时存在,虽然构成重载,但会产生调用歧义
Date d1;
d1.Print();
return 0;
}
一般情况下,所定义的类都需要我们自己写默认构造函数
当定义的类成员变量是另一个自定义类并且有自己的构造函数就不需要写等等.
二 . 析构函数
析构函数也是特殊的成员函数之一,与构造函数功能相反,但他不是对对象本身的清理销毁,完成对象中资源的清理释放工作.(只有当有资源申请的时候,才需要写析构函数)
析构函数的特点:
- 析构函数名是在类名前加上字符 ~
- 无返回值
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
- 对象生命周期结束时,系统会自动调用析构函数。
- 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类成员会调用他的析构函数。
- 多个对象, 后定义的先析构(后定义的b,在第一次被析构)
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
//有资源申请
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
~Stack()//析构函数
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main()
{
Stack a;
Stack b;
return 0;
}
一般情况,类成员有需要资源申请时,就需要自己写析构函数.
三 . 拷贝构造函数
如果一个构造函数的第一个参数是该类类型的引用,且有其他参数都有默认值,那么这个函数就叫拷贝构造函数,所以拷贝构造函数是特殊的构造函数。
拷贝构造函数的特点:
- 拷贝构造是构造函数的重载
- 拷贝构造函数的第一个参数必须是该类类型对象的引用,如果是传值传参会报错,引发无限递归
- C++规定自定义类型对象进行拷贝必须调用拷贝构造,所以这里自定义类型传值传参和传值返 回都会调用构造完成。
-
C++规定 传值返回会 产生一个临时对象调用拷贝构造 ,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝。
- 若未显式定义拷贝构造,编译器会生成自动生成构造函数。自动生成的拷贝构造对内置类型成 员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝)。 对自定义类型成员变量会调用他的拷贝构造。
- 像类成员都是内置类型且没有资源申请的类,我们可以不写拷贝构造函数,编译器自动生成的就可以完成功能。 但是像上面栈需要申请空间资源的类,自动生成的无法满足功能,就要根据实际情况手动完成拷贝构造函数进行深拷贝。
1.浅拷贝:浅拷贝会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝.两个 对象都指向同一个内存地址。因此,如果一个对象中的某个引用类型属性被修改了,通 过浅拷贝得到的新对象也会受到影响.它们共享同一个引用类型属性的内存地址。
2.深拷贝:深拷贝会创建一个新对象,并且递归地复制原始对象中的所有属性,包括所有 引用的对象。这样,原始对象和新对象之间就没有任何共享的内存地址了。修改新对象 的任何属性(包括嵌套对象)都不会影响到原始对象。
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
//有资源申请
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc fail");
return;
}
_capacity = n;
_top = 0;
}
~Stack()//析构函数
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
Stack(const Stack& s) //拷贝构造
{
_a = (STDataType*)malloc(sizeof(STDataType) * s._capacity);
if (nullptr == _a)
{
perror("malloc fail");
return;
}
memcpy(_a, s._a, sizeof(s._top));
_capacity = s._capacity;
_top = s._top;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main()
{
Stack a;
Stack b(a);
return 0;
}
深拷贝后两地址不同.
四 . 赋值运算符重载
当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规
定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编
译报错。
关键字: operator
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有⼀个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
- 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个.
-
重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。 C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。
-
重载<<和>>时,需要重载为全局函数, 因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成对象<<cout ,不符合使用习惯和可读性。
-
运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致.
通过对日期类的实现完成复习
#include<iostream> using namespace std; class Date { friend ostream& operator<<(ostream& out, const Date& d); friend istream& operator>>(istream& in, Date& d); public: // 获取某年某月的天数 int GetMonthDay(int year, int month) { static int MonthDayArray[13] = { -1,31,28,31,30,31 ,30,31,31,30,31,30,31 }; if (month == 2 && ((year % 400 != 0) || (year % 400 == 0))) { return 29; } return MonthDayArray[month]; } // 全缺省的构造函数 Date(int year = 1900, int month = 1, int day = 1); //打印 void print(); // 拷贝构造函数 // d2(d1) Date(const Date& d); bool CheckDate(); // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& operator=(const Date& d); // 日期+=天数 Date& operator+=(int day); // 日期+天数 Date operator+(int day); // 日期-天数 Date operator-(int day); // 日期-=天数 Date& operator-=(int day); // 前置++ Date& operator++(); // 后置++ Date operator++(int); // 后置-- Date operator--(int); // 前置-- Date& operator--(); // >运算符重载 bool operator>(const Date& d); // ==运算符重载 bool operator==(const Date& d); // >=运算符重载 bool operator >= (const Date& d); // <运算符重载 bool operator < (const Date& d); // <=运算符重载 bool operator <= (const Date& d); // !=运算符重载 bool operator != (const Date& d); // 日期-日期 返回天数 int operator-(const Date& d); private: int _year; int _month; int _day; }; ostream& operator<<(ostream& out, const Date& d); istream& operator>>(istream& in, Date& d); // 全缺省的构造函数 Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //打印 void Date::print() { cout << _year << '/' << _month << '/' << _day << endl; } // 拷贝构造函数 // d2(d1) Date::Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& Date::operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; } // 日期+=天数 Date& Date::operator+=(int day) { if (day < 0) { return *this -= (-day); } _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month == 13) { _year++; _month = 1; } } return *this; } // 日期+天数 Date Date::operator+(int day) { Date tmp = *this; tmp += day; return tmp; } Date& Date::operator-=(int day) { if (day < 0) { return *this += (-day); } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { --_year; _month = 12; _day += GetMonthDay(_year, _month); } } return *this; } // 日期-天数 Date Date::operator-(int day) { Date tmp = *this; tmp -= day; return tmp; } // 前置++ Date& Date::operator++() { *this += 1; return *this; } // 后置++ Date Date::operator++(int) { Date tmp = *this; *this += 1; return tmp; } // 后置-- Date Date::operator--(int) { Date tmp = *this; *this -= 1; return tmp; } // 前置-- Date& Date::operator--() { *this -= 1; return *this; } // >运算符重载 bool Date::operator>(const Date& d) { if (_year < d._year) { return true; } else if (_year == d._year) { if (_month > d._month) { return true; } else if (_month == d._month) { if (_day > d._day) { return true; } } } return false; } // ==运算符重载 bool Date::operator==(const Date& d) { return (_year == d._year) && (_month == d._month) && (_day == d._day); } // >=运算符重载 bool Date::operator >= (const Date& d) { return *this > d || *this == d; } // <运算符重载 bool Date::operator< (const Date& d) { return !(*this >= d); } // <=运算符重载 bool Date::operator<= (const Date& d) { return !(*this > d); } // !=运算符重载 bool Date::operator != (const Date& d) { return !(*this == d); } // 日期-日期 返回天数 int Date::operator-(const Date& d) { int flag = 1; Date max = *this; Date min = d; if (*this < d) { max = d; min = *this; flag = -1; } int count = 0; while (min < max) { ++min; ++count; } return flag * count; } ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } bool Date::CheckDate() { if (_year < 1 || _month < 1 || _month>12 || _day<1 || _day>GetMonthDay(_year, _month)) { return false; } else { return true; } } istream& operator>>(istream& in, Date& d) { while (1) { cout << "请依次输入年月日:>"; in >> d._year >> d._month >> d._day; if (!d.CheckDate()) { cout << "输入日期非法:"; d.print(); cout << "请重新输入" << endl; } else { break; } } return in; }
分享到这了~
个人主页点这里~