成功案例 品牌网站,wordpress主题代码,深圳网站定制设计,wordpress标签关键词文章目录 类的默认成员函数构造函数#xff08;Constructor#xff09;构造函数的特点 析构函数 #xff08;Destructor#xff09;析构函数的特点 拷贝构造函数#xff08;Copy Constructor#xff09;拷贝构造函数的特点 运算符重载#xff08;Operator OverloadingConstructor构造函数的特点 析构函数 Destructor析构函数的特点 拷贝构造函数Copy Constructor拷贝构造函数的特点 运算符重载Operator Overloading赋值运算符重载前置/后置的自增和自减运算符重载 const成员实现完整的日期类关于取地址和const取地址运算符操作符 萌新的学习笔记写错了恳请斧正。
类的默认成员函数
我们写一个类在类体中什么都不写这样的类就叫空类。
空类真的什么都没有吗并不是的。
类有六个默认成员函数如果我们自己没有写这六个函数类也会自己去补齐它们。
默认构造函数Default Constructor - 用于无参的初始化一个对象。析构函数Destructor - 用于在对象生命周期结束时进行资源的回收等收尾工作。拷贝构造函数Copy Constructor - 用于通过同类型的另一个对象来初始化新对象。拷贝赋值运算符Copy Assignment Operator - 用于将一个对象的内容赋值给另一个同类型的对象。移动构造函数Move Constructor - C11引入用于通过移动而非拷贝另一个同类型的对象来初始化新对象。移动赋值运算符Move Assignment Operator - C11引入用于通过移动而非拷贝来将一个对象的内容赋值给另一个同类型的对象。
其中后两个先不管下面我们介绍一下前面几个函数。
构造函数Constructor
我们先看这样一个类
class Date
{
public:void Init(int y, int m, int d){m_year y;m_month m;m_day d;}void Print(){cout m_year - m_month - m_day endl;}private:int m_year;int m_month;int m_day;
}对于这个日期类我们每次要实例化一个对象时都需要这样先创建对象再使用Init函数完成初始化非常的不方便。
所以在C中我们就有了构造函数。构造函数是一个特殊的成员函数名字与类名相同。当我们创建对象时其构造函数就会被自动调用并且在对象的整个生命周期中只调用一次。
比方说我们可以把上面的类改写成这样
class Date
{
public:Date(int y, int m, int d) // 构造函数前面就不要加void了{m_year y;m_month m;m_day d;}void Print(){cout m_year - m_month - m_day endl;}private:int m_year;int m_month;int m_day;
}构造函数的特点 函数名与类名相同。 没有返回值。 对象实例化时编译器自动调用对应的构造函数。 同样可以重载 class Date
{
public:Date() // 无参{m_year 1900;m_month 1;m_day 1;}Date(int y, int m, int d) // 带参{m_year y;m_month m;m_day d;}void Print(){cout m_year - m_month - m_day endl;}private:int m_year;int m_month;int m_day;
}int main()
{Date d1; // 调用无参的构造函数Date d2(2024, 5, 22); //调用带参的构造函数return 0;
}注意这里实例化时不能这样写 int main()
{Date d(); // 调用无参的构造函数不要带括号return 0;
}这里的语句可能会被认为是在声明一个叫d的函数其返回值是Date类型的对象。 如果类中没有显式定义构造函数编译器会自动生成一个无参的构造函数如果显式定义了就不会这么做。 这里默认生成的构造函数是遵循什么规则给对象初始化的呢且看 C中的类型分为内置类型基本类型比如int、double这些自带的和自定义类型我们用class、struct、union等自己定义出来的。而默认生成的构造函数不一定会对内置类型进行初始化不排除某些编译器有自己的想法对于自定义类型编译器会去寻找其构造函数以实现初始化。 但是在C11中打了一个补丁内置成员变量可以在类中声明时给默认值要给就全给。 class Date
{
public:Date(int y, int m, int d){m_year y;m_month m;m_day d;}void Print(){cout m_year - m_month - m_day endl;}private:int m_year 1900;int m_month 1;int m_day 1;
}int main()
{Date d; //1900-1-1d.Print();return 0;
}析构函数 Destructor
析构函数与构造函数相反在对象销毁时自动调用析构函数完成对象中资源的清理工作。
析构函数的特点
析构函数名是在类名前加上字符~。析构函数无参数无返回值类型不能重载。一个类只能有一个析构函数。若显式未定义则自动生成默认的析构函数。对象生命周期结束时自动调用析构函数。和构造函数同样的自动生成的析构函数不会清理内置类型的变量本身也不需要清理对于自定义类型的变量去寻找其析构函数并调用。
这里我们还是以之前的Stack类为例
#pragma once#include iostream
#include stringusing namespace std;typedef int StackDataType;class Stack
{
public:Stack(int size);~Stack();void Push(StackDataType data);StackDataType Pop();StackDataType Top();bool IsEmpty();bool IsFull();int GetSize();int GetTop();private:StackDataType* m_data;int m_size;int m_capacity;
};#include Stack.hStack::Stack(int size 4)
{m_data new StackDataType[size];m_size 0;m_capacity size;
}Stack::~Stack()
{delete[] m_data;
}void Stack::Push(StackDataType data)
{if (IsFull()){cout Stack is full! endl;return;}m_data[m_size] data;
}StackDataType Stack::Pop()
{if (IsEmpty()){cout Stack is empty! endl;return -1;}return m_data[--m_size];
}StackDataType Stack::Top()
{if (IsEmpty()){cout Stack is empty! endl;return -1;}return m_data[m_size - 1];
}bool Stack::IsEmpty()
{return m_size 0;
}bool Stack::IsFull()
{return m_size m_capacity;
}int Stack::GetSize()
{return m_size;
}int Stack::GetTop()
{return m_size - 1;
}拷贝构造函数Copy Constructor
如果我们想通过一个已有对象去创建一个新对象就要用到拷贝构造函数。简单来说就是我们有一个类X的对象a现在想再创建一个类X的对象b而且要和a完全一致。那么就直接使用拷贝构造函数使用a来创建b。
拷贝构造函数的特点 拷贝构造函数和默认构造函数一样都是构造函数的一个重载形式。 拷贝构造函数的参数只有一个且为类类型对象的引用。使用传值的方式会造成无穷递归调用会被编译器报错。 class Date
{
public:Date(int y, int m, int d){m_year y;m_month m;m_day d;}Date(const Date d){m_y d.m_y;m_m d.m_m;m_d d.m_d;}void Print(){cout m_year - m_month - m_day endl;}private:int m_year 1900;int m_month 1;int m_day 1;
}与之类似的我们一般传参时能使用引用就尽量使用引用类型可以提高程序效率。 如果没有显式定义编译器会生成默认的拷贝构造函数。但是默认的拷贝构造函数是直接将内存按字节序复制过来属于浅拷贝值拷贝。但是我们很多时候是需要深拷贝的尤其是存在资源申请时因为这种情况下依旧直接复制内存并不能申请新的资源不能完成我们所需的拷贝。
运算符重载Operator Overloading
运算符重载是面对对象编程的一种方法。在C中运算符重载允许我们为类对象自定义运算符的计算方法。
简单来说我们自定义一个类编译器是不知道这个类的加减乘除等计算是如何进行的。像加减乘除等计算一般只能用于系统的内置类型但我们可以通过运算符重载来规定自定义类型遇到这些运算符应该怎么处理。
运算符重载函数的名字为operator后面加上需要重载的运算符符号。
比方说下面就是重载了日期类的日期加天数等于新日期的计算的重载
class Date
{
public:Date(int year 1900, int month 1, int day 1){m_year year;m_month month;m_day day;}Date(const Date date){m_year date.m_year;m_month date.m_month;m_day date.m_day;}~Date() {};int GetMonthDays(int year, int month){if (month 2){if (IsLeapYear(year)){return 29;}else{return 28;}}else if (month 4 || month 6 || month 9 || month 11){return 30;}else{return 31;}}bool IsLeapYear(int year){if (year % 4 0 year % 100 ! 0 || year % 400 0){return true;}else{return false;}}Date operator(int days){while (days 0){if (days m_day GetMonthDays(m_year, m_month)){days - GetMonthDays(m_year, m_month) - m_day 1;m_day 1;if (m_month 12){m_month 1;m_year;}else{m_month;}}else{m_day days;days 0;}}return *this;}void Print(){std::cout m_year - m_month - m_day std::endl;}private:int m_year;int m_month;int m_day;
};我们也可以将运算符重载为全局函数。但是需要涉及的成员变量是公有的这会破坏类的封装性但是也可以通过友元来解决后面会讲
class Date
{
public:Date(int year 1900, int month 1, int day 1){m_year year;m_month month;m_day day;}Date(const Date date){m_year date.m_year;m_month date.m_month;m_day date.m_day;}~Date() {};int m_year;int m_month;int m_day;
};bool operator(const Date d1, const Date d2)
{return d1.m_year d2.m_year d1.m_month d2.m_month d1.m_day d2.m_day;
}void test()
{Date d1(2024, 1, 1);Date d2(2024, 1, 1);cout (d1 d2) endl; // 由于优先级问题需要加括号
}注意
只能重载已有的运算符不能自创新的符号。不能对内置类型去重载。有5个运算符不能重载.*通过成员函数指针访问类的成员函数的运算符、::作用域限定符、sizeof、?:三目运算符、.重载操作符至少要有一个类类型参数。作为类成员函数重载时其有一个隐藏的参数为其第一个参数this。
赋值运算符重载
赋值运算符重载与拷贝构造函数类似。其参数应当选择传引用返引用以提高效率并且这样能够支持连续赋值。
注意
应当注意是否出现自己给自己赋值的情况。赋值运算符不能重载为全局函数只能是成员函数全局函数没有this指针而且赋值运算符如果不显式实现编译器会生成一个默认的此时用户再在类外自己实现一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突。用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式在内存逐字节拷贝浅拷贝。
我们还是以日期类为例
Date Date::operator(const Date date)
{if (this ! date){m_year date.m_year;m_month date.m_month;m_day date.m_day;}return *this;
}前置/后置的自增和自减运算符重载
我们以自增运算符为例前置自增应当这样重载
Date Date::operator()
{*this 1;return *this;
}而后置自增运算符应当如何重载呢C规定为了区分后置运算符重载时增加一个int类型参数但是调用该函数时忽略该参数就像这样
Date Date::operator(int)
{Date date *this;*this 1;return date;
}void test()
{Date d1;Date d2(2024, 1, 1);d1 d2;d1 d2;
}注意后置自增是使用后再自增因此要返回自增前的旧值。
const成员
在成员函数的括号后面加一个const就将这个成员函数称为const成员函数。这里实际修饰的是该成员函数的this指针表示这个成员函数不能对类的任何成员进行修改。
具体的例子可以看下面实现完整的日期类中。
实现完整的日期类
下面我们通过上面学的内容实现一个日期类吧
#pragma onceclass Date
{
public:Date(int year 1900, int month 1, int day 1);Date(const Date date);~Date();int GetMonthDays(int year, int month) const;int GetYearDays(int year) const;bool IsLeapYear(int year) const;Date operator(int days) const;Date operator-(int days) const;Date operator-(int days);Date operator(int days);Date operator();Date operator(int);Date operator--();Date operator--(int);Date operator(const Date date);bool operator(const Date date) const;bool operator!(const Date date) const;bool operator(const Date date) const;bool operator(const Date date) const;bool operator(const Date date) const;bool operator(const Date date) const;int operator-(const Date date) const;void Print() const;private:int m_year;int m_month;int m_day;
};#include iostream
#include Date.husing namespace std;Date::Date(int year, int month, int day)
{m_year year;m_month month;m_day day;
}Date::Date(const Date date)
{m_year date.m_year;m_month date.m_month;m_day date.m_day;
}Date::~Date()
{
}int Date::GetMonthDays(int year, int month) const
{if (month 2){if (IsLeapYear(year)){return 29;}else{return 28;}}else if (month 4 || month 6 || month 9 || month 11){return 30;}else{return 31;}
}int Date::GetYearDays(int year) const
{if (IsLeapYear(year)){return 366;}else{return 365;}
}bool Date::IsLeapYear(int year) const
{if (year % 4 0 year % 100 ! 0 || year % 400 0){return true;}else{return false;}
}Date Date::operator(int days) const
{Date date *this;date days;return date;
}Date Date::operator-(int days) const
{Date date *this;date - days;return date;
}Date Date::operator-(int days)
{while (days 0){if (days m_day){days - m_day;if (m_month 1){m_month 12;m_year--;}else{m_month--;}m_day GetMonthDays(m_year, m_month);}else{m_day - days;days 0;}}return *this;
}Date Date::operator(int days)
{while (days 0){if (days m_day GetMonthDays(m_year, m_month)){days - GetMonthDays(m_year, m_month) - m_day 1;m_day 1;if (m_month 12){m_month 1;m_year;}else{m_month;}}else{m_day days;days 0;}}return *this;
}Date Date::operator()
{*this 1;return *this;
}Date Date::operator(int)
{Date date *this;*this 1;return date;
}Date Date::operator--()
{*this - 1;return *this;
}Date Date::operator--(int)
{Date date *this;*this - 1;return date;
}Date Date::operator(const Date date)
{if (this ! date){m_year date.m_year;m_month date.m_month;m_day date.m_day;}return *this;
}bool Date::operator(const Date date) const
{if (m_year date.m_year m_month date.m_month m_day date.m_day){return true;}else{return false;}
}bool Date::operator!(const Date date) const
{return !(*this date);
}bool Date::operator(const Date date) const
{if (m_year date.m_year){return true;}else if (m_year date.m_year){if (m_month date.m_month){return true;}else if (m_month date.m_month){if (m_day date.m_day){return true;}}}return false;
}bool Date::operator(const Date date) const
{return !(*this date) *this ! date;
}bool Date::operator(const Date date) const
{return *this date || *this date;
}bool Date::operator(const Date date) const
{return *this date || *this date;
}int Date::operator-(const Date date) const
{int days 0;Date temp *this;if (temp date){while (temp ! date){temp;days;}}else{while (temp ! date){temp--;days;}}return days;
}void Date::Print() const
{std::cout m_year - m_month - m_day std::endl;
}关于取地址和const取地址运算符操作符
有人说我们自定义的类型能够直接取地址或者const取地址因此这两种运算符也是被编译器默认重载了的默认成员函数。
事实上这不能认为是类的默认成员函数虽然我们自己可以去重载这两个运算符不推荐一般也用不上但是空类生成的时候编译器并没有生成这样一个重载成员函数这个操作是C内置的基础功能。只有当显式地为类重载了取地址操作符这个操作的行为才会改变。