海外网站有哪些,电商运营怎么推广一个新品,南宁seo网络优化公司,做网站填素材文章目录 类的六个默认成员函数构造函数析构函数特性使用 总结构造函数和析构函数拷贝构造函数特性拷贝构造总结 赋值运算符的重载运算符重载赋值运算符重载总结拷贝构造函数和赋值运算符重载 关于operator重载日期类实现const 修饰的成员函数取地址重载以及const取地址… 文章目录 类的六个默认成员函数构造函数析构函数特性使用 总结构造函数和析构函数拷贝构造函数特性拷贝构造总结 赋值运算符的重载运算符重载赋值运算符重载总结拷贝构造函数和赋值运算符重载 关于operator重载日期类实现const 修饰的成员函数取地址重载以及const取地址重载 二次修订版本date: 2024 3 12; 类的六个默认成员函数 当我们在类中什么都不写的时候编译器会在类内默认生成六个成员函数。 分别是构造函数析构函数拷贝构造赋值运算符重载取地址重载和const取地址。
构造函数 构造函数是负责对象的初始化工作的并不是创建对象。 构造函数会在对象实例化的时候自动调用完成对象的初始化工作。 构造函数的特征函数名与类名相同无返回值。创建对象的时候由编译器自动调用可以保证每个对象都有一个初始值在对象的生命周期内只会调用一次。构造函数可以重载。 这里我们写了一个Date()的构造函数创建对象后并不需要我们主动调用这个对象就已经初始化好了。 注意void并不是无返回值而是空返回值无返回值就直接不写。 当然如果想要主动传参也是可以的。就如下面这样
class Date
{
public:Date(){_year 2022;_month 6;_day 18;}Date(int year, int month, int day){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 6, 23);return 0;
}这两个构造函数构成了函数重载这就使得我们可以有多种初始化对象的方式可以使用默认值也可以使用给定值初始化。但是这样写两个函数略显复杂可以使用全缺省的构造函数替代。
class Date
{
public:Date(int year 2022, int month 6, int day 18){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
}就像上面这个样子我们不传参的时候自动调用就是使用缺省值。 但是要注意下面这种写法是错误的
class Date
{
public:Date(){_year 2022;_month 6;_day 18;}Date(int year 2022, int month 6, int day 30){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
}这里既写了无参的构造函数又写了全缺省当不传参的时候编译器不知道调用无参的构造函数还是调用全缺省的构造函数使用默认值初始化这个错误叫做调用不明确也就是二义性。 如果不写构造函数那编译器会自动生成一个默认构造函数
class Date
{
public:private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
}默认生成的构造函数对于内置类型就是intint*包括Date*等不会进行初始化但是对于自定义类型就会调用这个类的默认构造函数对这个类的对象进行初始化如果这个类没有默认构造函数会报错。 数据成员“Date::_a”不具备相应的 默认构造函数 或重载解决不明确因此已隐式删除函数
class A
{
public:A(){_a 10;_b 20;}
private:int _a;int _b;
};
class Date
{
public:private:int _year;int _month;int _day;A _a;
};int main()
{Date d1;return 0;
}上面的代码在Date类里面包含了一个A的类的对象在调用构造函数的时候对于int这些内置类型编译器不会进行处理对于A这样的自定义类型的对象会去调用他的默认构造函数进行初始化。 使用栈实现队列的时候MyQueen的构造函数可以不用写系统生成的会调用Stack的默认构造函数完成初始化 最后注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认成员函数。默认成员函数就是不需要传参就可以调用的函数。
析构函数
构造函数不是创建对象而是负责初始化对象析构函数也不是负责销毁对象而是在对象销毁之前调用用来清理资源。
特性
析构函数名是类名但是前面要加~无返回值无参数一个类只有一个析构函数如果不写系统自动生成。析构函数会在对象销毁生命周期结束的时候由C编译系统自动调用。
C的析构函数本质上对于内置类型是没有什么用的只对使用malloc申请过空间的才有用用来防止内存泄漏在服务器方面内存泄漏是很严重的问题而且相比于不初始化忘记释放内存是不会报错的这就是隐藏的利刃当服务器因为内存泄漏崩溃之后你才能发现内存泄漏的问题。 C不像java还有垃圾回收器GCC只能靠析构函数和智能指针来防止内存泄漏。 使用
class SList
{
public:SList(int size 10){_arr (int*)malloc(sizeof(int) * size);_size 0;_capacity size;}~SList(){if (_arr){free(_arr);_arr nullptr;_size 0;_capacity 0;}}private:int* _arr;int _size;int _capacity;
};int main()
{SList _new;return 0;
}这里我们就是自己定义的析构函数在_new这个对象生命周期结束的时候就会自动释放对象内的_arrmalloc出来的空间又叫清理资源。 int main()
{SList n1;SList n2;SList n3;return 0;
}这里定义了三个对象在定义的时候构造顺序是n1,n2,n3在析构的时候是n3,n2,n1;因为我们的对象创建在栈上栈区的性质是后进先出最后入栈的n3会是最先出栈的因此也是n3先析构的。 析构函数也是默认成员函数不写的时候也是会默认生成一个的他的处理方式和构造函数也是一样的。对于内置类型不起作用对于自定义类型会去调用它的析构函数。 因此如果我们malloc申请了空间那么就需要自己写出析构函数进行内存释放方便的是不需要我们手动去调用。
class String
{
public:String(const char* str jack){_str (char*)malloc(strlen(str) 1);strcpy(_str, str);}~String(){cout ~String() endl;free(_str);}
private:char* _str;
};
class Person
{
private:String _name;int _age;
};
int main()
{Person p;return 0;
}这段代码可以看出编译器默认生成的析构函数对于自定义类型是去调用它自身的析构函数。 如果对象内没有资源需要清理不需要写系统自动生成就够用而且在release版本下编译器会自动去掉没有资源需要清理的析构函数。 对同一块内存空间释放两次会发生错误。
总结构造函数和析构函数 对于内置类型构造函数和析构函数都是不起作用的对于自定义类型会调用这个自定义类型对象的默认构造和析构函数。 拷贝构造函数 拷贝构造函数的应用场景是创建一个对象用另一个同类的对象对他进行初始化。 就像是内置类型中的int a b用b给a初始化值。 拷贝构造的代码
class Date
{
public:Date(int year 0, int month 0, int day 0){_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;Date d2(d1);Date d3 d1;return 0;
}拷贝构造函数是构造函数的重载。下面定义d2d3对象的时候初始化都是调用了拷贝构造函数这里两种形式都可以。第二种实际上是重载赋值运算符然后调用了拷贝构造函数。
特性
1拷贝构造是构造函数的重载函数 2拷贝构造函数的参数只有一个且只能是类的引用类型的隐含的类型*this指针不算如果参数不是引用会引发无限递归。 原因如果使用传值参数是Date d那么值传参的时候就要将实参的值拷贝给形参这时候就需要调用拷贝构造然后就无限递归了。 当不写拷贝构造函数编译器默认生成的拷贝构造函数完成的是浅拷贝也就是值拷贝在Date类这里是没有错误的但是一旦成员对象有指针指针指向的空间是malloc出来的时候就会出错了。
class Date
{
public:Date(int year 0, int month 0, int day 0){_year year;_month month;_day day;}void SetDate(int year, int month, int day){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.SetDate(2022, 6, 18);Date d2(d1);Date d3 d1;return 0; 编译器默认生成的拷贝构造函数对于我们的Date类是完全没有问题的。浅拷贝完全能满足Date类对象的需求。 那么现在来看下面这段代码 class stack
{
public:stack(int capacity 4){_a (int*)malloc(sizeof(int) * capacity);_size 0;_capacity capacity;}~stack(){if (_a ! nullptr){free(_a);_a nullptr;_size 0;_capacity 0;cout free is ok endl;}}
private:int* _a;int _size;int _capacity;
};int main()
{stack s1;stack s2(s1);return 0;
}这段代码我们手动实现了构造和析构然后调用编译器默认的拷贝构造函数给对象s2初始化成与s1相同的对象但是程序运行到最后的时候却崩溃了。 上面崩溃的图中我们可以看出完成了一次s2对象的析构对s1进行析构的时候程序挂了从调试图看到两个对象的成员对象_a指向的是同一块空间原因就是对同一块空间释放了两次所以程序崩溃了。这也说明了系统默认生成的拷贝构造函数进行了浅拷贝。 因此对于这种类就需要手动实现深拷贝构造函数 至于为什么释放两次就会崩溃系统方面的问题就是这块空间收回来之后有可能又给了别人用别人还没用完你给他释放了这是错误的。所以不能两次释放同一块空间。
拷贝构造总结
对于date这种类没有涉及到malloc或者指针之间的关系的时候是可以不写的系统默认生成的拷贝构造函数实现的浅拷贝完全够用但是到了stack这种类的时候就不能用默认拷贝构造函数了。
赋值运算符的重载
运算符重载 内置类型想要比较大小或者加减乘除赋值都可以用运算符来实现±*/但是自定义类型想进行运算只能通过函数来实现不仅麻烦而且可读性也不高。所以C引入了运算符重载让自定义类型也可以通过运算符重载后使用运算符进行运算。 运算符重载的特性 1运算符重载函数名是operator后面接上要重载的符号。 2函数的返回值与普通函数类似可以根据情况自定义。运算符重载函数的参数必须有一个类的类型的参数或者枚举类型的参数。 3重载的操作符不能改变其原来的含义比如不能重载成减法。 4如果运算符重载函数定义为全局的函数要显示写出类类型的参数定义成成员函数就会少一个有一个参数是this指针。this指针一定是第一个形参 5“.*”,“::”,“:?”,“sizeof”,“.” 这五个运算符点星域作用访问限定符三目运算符sizeof和点操作符是不可以重载的。
class Date
{
public:Date(int year 0, int month 1, int day 1){_year year;_month month;_day day;}//拷贝构造和析构都可以使用默认。bool operator(const Date d){return _year d._year _month d._month _day d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(d1);if (d1 d2)cout yes endl;return 0;
} 这段代码我们重载了来判断自定义的类是不是相等。 赋值运算符重载 特性 1参数类型参数类型是引用可以减少传值的时候拷贝构造的性能消耗 2返回值为了可以使用连续赋值返回值要是第一个对象参数的引用 3检测一下是不是在给自己赋值如果是直接返回即可。 4返回的是第一个对象的引用因为第一个对象出函数也不会销毁所以返回值是*this 5如果这个类没有手动写那编译器会自动生成一个赋值运算符的重载函数。 class Date
{
public:Date(int year 0, int month 1, int day 1){_year year;_month month;_day day;}//这里日期类拷贝构造和析构都可以使用默认。Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022,6,19);Date d2;d2 d1;Date d3;d3 d2 d1;return 0;
}通过返回类的引用可以实现连续赋值。 赋值运算符是默认成员函数就算我们不写编译器也会自动生成。结论是这个默认生成的赋值运算符重载函数与默认生成的拷贝构造函数是类似的只能实现按照字节序拷贝也就是浅拷贝如果遇到Stack那种存在malloc空间的类就会出现和拷贝构造函数类似的错误对同一块空间释放了两次。
总结拷贝构造函数和赋值运算符重载
1拷贝构造函数和构造函数形成了函数重载参数是类对象的引用无返回值 2赋值运算符重载是一个函数名operator的函数参数是对象类的引用返回值是对象的引用因为这个对象出了函数作用域并不会销毁。 3拷贝构造函数和赋值运算符重载函数如果不写系统默认生成的都是实现的浅拷贝或者说是值拷贝对于日期类这种没有问题但是对于Stack就会出现问题。
关于operator重载 operator是不可以在类内实现的因为类内默认第一个参数为this指针调用的时候就是d1cout 因此operator一般在类外定义可以通过1友元声明访问private成员。2通过类内的get方法获取成员。 tips全局函数或者变量定义在.h文件中的时候如果被两个cpp文件包含会现重定义的情况解决方案
全局函数的声明与定义分离加static修饰函数static会改变函数的链接属性只在当前文件可见用inline修饰operator,内联函数会在调用的地方展开不会进入符号表。
日期类实现
//Date.h
#pragma once
#includeiostream
using std::cout;
using std::cin;
using std::endl;class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month){static int days[13] { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int day days[month];if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0))){day 1;}return day;}//打印日期void Print(){cout _year 年 _month 月 _day 日 endl;}// 全缺省的构造函数Date(int year 1900, int month 1, int day 1){if (month 12 || day0 || dayGetMonthDay(year, month)){cout 非法日期 endl;}_year year;_month month;_day day;}// 拷贝构造函数// d2(d1)Date(const Date d){*this d;//复用赋值运算符重载}// 赋值运算符重载// d2 d3 - d2.operator(d2, d3)Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}// 析构函数~Date(){_year 0;_month 0;_day 0;}// 日期天数Date operator(int day){_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 12){_year;_month 1;}}return *this;}// 日期天数Date operator(int day){Date ret(*this);ret day;return ret;}// 日期-天数Date operator-(int day){Date ans(*this);ans - day;return ans;}// 日期-天数// 日期-day可以复用 日期 -day;Date operator-(int day){_day - day;while (_day 0){_month--;if (_month 0){_year--;_month 12;}_day GetMonthDay(_year, _month);}return *this;}// 前置Date operator(){*this 1;return *this;}// 后置Date operator(int){Date ret *this;*this 1;return ret;}// 后置--Date operator--(int){Date ret *this;*this - 1;return ret;}// 前置--Date operator--(){*this - 1;return *this;}// 运算符重载bool 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 operator(const Date d){return _year d._year _month d._month _day d._day;}// 运算符重载inline bool operator (const Date d){if (_year d._year){return false;}else if (_year d._year){if (_month d._month){return false;}else if (_month d._month){if (_day d._day){return false;}}}return true;}// 运算符重载bool operator (const Date d){return !((*this) d);}// 运算符重载bool operator (const Date d){return !(*this d);}// !运算符重载bool operator ! (const Date d){return !(*this d);}// 日期-日期 返回天数int operator-(const Date d){int count 0;int flag 1;Date max *this;Date min d;if (max min){max d;min *this;flag -1;}while (max ! min){min;count;}return count * flag;}
private:int _year;int _month;int _day;
};//test.cpp测试文件
#includeDate.hvoid Test1()
{Date d1;d1.Print();Date d2(2022, 5, 3);d2.Print();Date d4(2022, 7, 31);d4.Print();Date sum d4 1000;sum.Print();d4.Print();}void Test2()
{Date d1(2022, 1, 10);Date d2 d1 - 20;d2.Print();d1.Print();Date d3(2022, 12, 31);Date ret d3;ret.Print();d3.Print();
}void Test3()
{Date d1(2022, 2, 28);Date d2 --d1;d2.Print();d1.Print();
}void Test4()
{Date d1(2022, 2, 28);Date d2(2022, 2, 28);cout (d1 ! d2) endl;
}
void Test5()
{Date d1(2022, 6, 19);Date d2 d1;Date d3(2022, 7, 18);cout (d3 - d1) endl;
}
int main()
{Test5();return 0;
}可以看到在上面代码的实现中很多函数是可以复用的。比如拷贝构造函数可以复用赋值运算符重载。比大小的我们只需要实现一个和 的实现其他的都可以复用这两个函数加上取反符号来实现 **注意**最后的日期减日期的实现可以使用找出较大的那个日期和较小的那个日期然后让小的那个不断自增等和较大的那个相等了这时候计数器里面的值就是他们之间的天数差。 留一个问题int占位符如何是保证后置能调用到正确地重载函数呢 答案是后置编译器在调用的时候会默认传一个int类型的参数过去参数加上一个int来占位与前置的函数构成重载。
const 修饰的成员函数 const对象调用函数的时候需要this指针指向的对象不可以被修改比如Print或者是这类比较重载的否侧const对象调用不了这些函数。 bool operator(const Date d)const{return _year d._year _month d._month _day d._day;}//bool operator(const Date* const this,const Date d)直接在函数后面加上const即可这时候函数就变成了注释掉的样子。 下面来思考这几个问题
const对象可以调用非const成员函数吗非const对象可以调用const成员函数吗const成员函数内可以调用其它的非const成员函数吗非const成员函数内可以调用其它的const成员函数吗
答案 1不可以const对象不可以调用非const对象const对象只读如果变成了普通对象就是可读可写的权限放大了。 2可以非const的普通对象是可读可写的调用const成员函数被修饰成只读的权限缩小了。 3不可以const成员函数的this指针是const属性的调用非const函数会变成可读可写的this权限放大了。 4可以非const函数调用const的成员函数普通的this指针传给了const的this指针权限缩小。
取地址重载以及const取地址重载 //运算符重载const Date* operator()const{return this;}Date* operator(){return this;}
//第一个是给const对象调用的第二个是给普通对象调用的。取地址并不需要什么重载函数取这个对象的地址的时候直接取就可以了。 唯一可能的用法就是下面这种。 //运算符重载const Date* operator()const{return nullptr;}Date* operator(){return nullptr;}那就是不想让你取到这个类的对象的地址可以像上面这样子写。