c++类与对象(二)
前言: 我们之前学习了类是一个泛指的概念比较虚无,之后学习了对象,对象是类的具体实例,如今我们要进一步掌握,我们如何运用类与对象去解决问题,以及它的思考模式。但是为了更好的解决一些我们对一个对象的基本的操作:赋初始值,销毁对象,简便的使用基本操作符实现对象之间的运算符操作, 取地址运算符重载(不常用),我们的对象都会有这些默认的成员函数也就是说已经帮你实现了一个对象一创建这些家伙就有了,其中有一些甚至会被自动调用,但是它可能达不到你的预期所以需要你去具体实现它。
对应功能的六大默认的成员函数如下:
长相如Date类的头文件展示:
一.构造函数
构造函数是默认成员函数之一,1.函数名与类名相同 2. 无返回值 3. 可以写多个构造函数构成重载 3. 对象实例化会自动调用(不信的调试试试)
创建对象的时候,写构造函数 可以进行赋初始值,写法如下:
下面几种是等价的:
主函数中:创建对象:
Date d1 = Date(1999,1,1);
Date d1(1999,1,1);
// but! 注意哈 人家这个它可没有开辟空间哈这只不过就是一个静态的在栈上 以及呢 调用了构造成员函数 进行赋初值
注意如下:切记哈: 对应创建的无参构造函数他会自动调用 你只需要写类 +对象名就行会自动调用默认的无参构造函数。 不需要写成类 +对象名()不然编译器报错 认为
public:
Date()
{
printf("haha");
}int main()
{
Date d1();// 编译器报错 认为 这是在声明一个默认构造
}
Date类举例:
class Date{public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。_month = month;_day = day;}private:int _year;int _month;int _day;};void TestDate(){Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();
注意: 1.1默认构造函数 又名无参构造函数 所以你说哈
Date(int year = 1900, int month = 1, int day = 1);
Date();
int main()
{Date d1;
}
会发生什么呢 会报错因为不知道调用哪个啊 全缺省也可以接受无参构造呢 。
总之 通过调用构造函数可以对对象进行赋初值。
1.2 在成员变量声明中给初值
class Date
{
private:
int _year =1980;
int _month = 1;
int _day = 1;
};
二.析构函数
构造函数是默认成员函数之一 ,1.函数名与类名相同 2. 写法是函数名前面加一个~ 3.就写一个 3. 无返回值 ,参数4. 在该对象结束的时候会自动调用
特点:1对于内置数据
作用用来清理资源空间: 比如1.清理动态内存,2.对一些指针进行赋空值 3.调用自定义类型(也就是你自己写的类)的析构函数
class Stack
{public:
~Date()
{
free(a);
a = nullptr;
_size = _capacity = 0;
}
private:
int *_a;int _capacity;
int _size;
};
注意点:你认为下面代码输出啥?:
class Time {public:~Time(){cout<<"~Time()"<<endl;}};
class myDate {public:myDate(){cout << "myDate()"<<endl;}~myDate(){cout << "~myDate()"<<endl;}private:int _year;int _month;int _day;Time t1;};
int main()
{myDate d1;}
会输出: myDate()
~myDate()
~Time()
tip
为啥: 明明我在Date类中没有调用time的析构啊 ,谁干的: c++生命周期的管理机制他会检测当一个对象的生命结束后会调用它的析构函数 完成它的资源清理。
2. 析构顺序
对于同一类的创建的对象析构的顺序会于它的创建顺序相反 这里用静态变量来证明:
3. 常用情况
对于设计到动态资源申请的尽量自己实现析构函数 推荐一道题目 : 232. 用栈实现队列 - 力扣(LeetCode)
三.拷贝构造函数
构造函数是默认成员函数之一,1.函数名与类名相同 2. 写法是函数名中是用const加引用的调用3.就写一个3.无返回值
说白了就是进行赋值给对方 实现拷贝
使用目的: 1. 它是为了对于一个已经创建了的对象拷贝给一个正在创建的对象 而不是 两个都是已经创建了的 类似于 int a = b; 而不是 c = a; 这样的
1.1 如何使用
举例:
Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
这是对于Date类的实现 :
1.1.1 为什么必须是传递引用而不是 传值
传值长这样 Date(const Date d) ; 如果是传值调用成员函数的拷贝构造函数 。 如下
int main()
{
Date d1(1990,1,1);
Date d2(d1); // 创建d2把d1拷贝给他 这是 这里是传值哈
}
接下来就会调用对象d2的拷贝构造函数但是在这之前又会拷贝一个临时对象因为是传值拷贝 给(const Date d) 这时为了创建d的拷贝 又会调用它的拷贝构造 就这样不停的调用拷贝构造 无线死循环了 :
所以 拷贝构造函数的参数只有一个那就是类对象的引用
1.2 默认拷贝构造
默认拷贝构造 会按照字节序的方式进行拷贝,这就是值拷贝,对于动态内存可能会出现问题 比如 一个栈中的stack s1 拷贝给stack s2 加入 s1 中的a的地址 是0x112233 在拷贝的时候
会使得s2 中的a指向的地址也是0x112233 但是在调用析构的时候就会报错了:因为第一次析构 s1已经把0x112233这块内存给销毁了 但是 s2再次调用又会销毁一次 这就会报错。
所以设计到动态内存的对象一定要写拷贝构造函数 向日期类这样的对象都是int 没有设计的资源的申请 结束后内存就之间还给系统了,我们就可以不用写它的拷贝构造了。
2. 使用的常见以及编译器的优化
我们都已经了解到了,所谓拷贝构造就是将一个以及创建的拷贝给一个正在创建的对象。
使用场景: 1.使用已存在对象创建新对象2.函数参数类型为类类型对象 3.函数返回值类型为类类型对象
简单说 :创建对象 传参 返回值
如下哈:
我们的预期是输出四个打印 然后在析构掉 :但是实际上:
实际上这是因为设计到了:编译器的优化: 对于连续的拷贝构造和构造函数会进行优化直接拷贝构造 如上面的代码 第一次是构造: 地址6E8 然后调用Test(d1) 拷贝构造给d1 然后又拷贝构造给temp 这时候就只执行了一次 直接拷贝构造给temp (优化) temp 地址 (7D8)
返回值的时候 把temp返回 这时候又进行拷贝构造返回 所以又得到一个返回值 地址 824
拓展:设计到的内存管理
这里进行析构的时候 最先被销毁的是7D8也就是 temp的地址 我们之前学习了 不是说同一对象先创建的后销毁吗 那是要对于同一生命周期的情况下 ,temp是定义在栈里面的它一出栈就结束生命周期了 而对于一开始的d1 以及返回值 824 他们是在主函数结束才完成生命周期 我们根据先创建后销毁 所以应该是 824先被销毁。
四.赋值运算符重载函数
1. 运算符重载
学习赋值运算符重载之前我们了解一下啥?是运算符重载:


使用的时候 可以是 :if(d1>=d2)
类似的还要很多 += - 啊 等等
2. 赋值运算符重载
首先赋值元素符重载 返回得到的是 对象的类型 其次 它是默认成员函数 你不显式写编译器会自动生成。
这里需要注意的就是 前置++ 和后置加加 奇怪都是 operator++() 怎么区分呢 :
五. 关于取地址运算符重载
其实一般这两个家伙不用自己写 : 一个就是 你得到它的地址 没有现在 甚至可以修改 另一个就是 你只能得到它的地址但是不能修改它的内容只能访问 。
1. 如何给this指针加上const呢
首先this指针是被隐式生成的 我们无法这样写 Date(const Date*this) 就算这样写了也会报错 因为编译器会隐式生成了 所以 祖师爷想了一招 把const写到外面去 意思不变 也就让人不能修改this指定的内容 Date()const 所以所以的成员函数如果想加const修饰就去往外面加const
2. 取地址运算符重载
日期类举例:
class Date
{
public :Date* operator&(){return this ;//这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
//要重载,比如想让别人获取到指定的内容!}const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};
到这里默认成员函数就被如此优秀的你复习完了,那我问你,你要不自己写一个日期类呢!
特别是里面的 日期-日期 日期+日期 我保证你会很难受 。;;;;;;;; 哈哈哈哈 别问我为什么知道