类与对象
1.构造函数
1.1构造函数的定义
对于Date类,可以通过SetDate函数的方法给对象设置内容,可是我们每次创立一个对象后,才能去使用SetDate函数设置日期,这样未免太繁琐。如下:
那么我们可不可以创建Date对象的同时并给对象设置一个值?答案是有的,c++中给了我们一个构造函数就有这个功能。
构造函数是特殊的成员函数:
1.名字与类名相同,创建类类型对象时由编译器自动调用
2.在对象的声明周期只调用一次
1.2构造函数的特性
其主要特征如下:
1.名字与类名相同
2.无返回值
3.对象实例化时编译器会自动调用构造函数
4.构造函数可以重载
假如我们在类中没有写一个构造函数,那么我们调用对象时,编译器自动生成一个默认构造函数。一旦用户在类中定义了一个构造函数,则这个默认构造函数将不会生成。
在Data类中没有一个构造函数,当我们创建一个对象时,编译器会自动调用默认构造函数将a进行了初始化,只是a中的成员变量为随机值,使用无参的构造函数不需要+()。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参
构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
默认构造函数的意思是:不需要传参数就能初始化对象。
内置类型(基本类型):int,char,double等,还有指针都是内置类型;
自定义类型:我们用struct,class类自己定义的类型
关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成
默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但
是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵
用??
对于下面这个Data没有自定义一个构造函数,Date类中有基本类型和自定义类型,那么编译器怎么去调用构造函数呢?
一般建议我们定义出一个全缺省构造函数,因为它可以灵活应用,它既可以传参,也可以不需要传参也能够初始化对象。
2.析构函数
2.1概念
在c语言中,当我们要去创建栈去使用,当这个栈使用完后,我们要去调用StackDestory把这个栈中malloc创建的数组必须及时释放内存(free函数),并把该数组的地址给置为空,如果忘记去调用这个函数,那么就会发生内存泄露。
什么使内存泄露?
内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
所以c++中有一个析构函数就可以帮助我们去解决这个问题,当我们去创建一个对象栈后,当这个栈使用完后(被销毁时),编译器会去自动调用析构函数,去完成一些资源清理工作。(只要有使用malloc出一个数组,都需要做资源清理工作)
2.2 特性
1.析构函数是在在类名加~
2.一个类只能有一个析构函数,如果类中没有定义一个析构函数,则系统会自动生成一个默认的析构函数
3.无参数无返回值。
4.对象声明周期结束时会编译器会自动调用析构函数。
假如我们自己没有写析构函数,那么编译器会去调用自己的析构函数,而这个析构函数对内置类型不做处理,对自定义类型会去调用它自己的析构函数。
3.拷贝构造函数
3.1 概念
拷贝构造函数在创建对象时能够(将一个已存在的相同类型的对象的所有值拷贝)给它。
如下:
创建d2的同时把d1的值拷贝给d2.
3.2 拷贝构造函数的特性
拷贝构造函数也是一个特殊的成员函数,它的特征如下:
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个,且参数必须为传引用,若传值的话会引发无穷的递归的调用拷贝函数
如果拷贝构造函数的参数为为传值,那么在传值的过程会去调用拷贝构造函数去拷贝一个临时变量,每一次调用都会去在调用拷贝构造函数,以此类推,则会引起无穷无尽的调用拷贝构造函数。所以拷贝构造函数必须传引用
3.如果类中没有定义一个拷贝函数,则系统会默认一个拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节完成拷贝,这种为浅拷贝,或值拷贝。
那么我们可不可以用编译器自动生成的拷贝构造函数去拷贝生成一个栈呢?
由上面的图片可以知道,用编译器自动生成的构造函数将s1的值拷贝给s2后,程序会奔溃,那么为什么会发生这样的情况呢?
首先我们得知道,在栈中这个对象栈中存储的什么内容。
当我们要把s1中的内容拷贝给s2时:
由于编译器默认的拷贝构造函数是浅拷贝,s1中的指向数组的地址直接拷贝给s2,这时就有两个指针指向这个数组,那么当程序结束后,编译器就会去调用析构函数,就会分别对s1和s2中的数组给free掉,由于malloc出的数组只能free掉一次,而s1和s2的指针是指向同一个数组,所以析构函数就会对这个数组使用两次free,最终程序会奔溃。
4.赋值运算符的重载
4.1 概念
在c语言中两个相同的内置类型(int,char,double,long,char*,int*,double*,long*等内置类型)可以直接使用运算符,比如:
我们定义一个int i,那么我们可以直接拿它来+100,或-100.
如果我们可不可以让自定义类型像内置类型一样去使用运算符呢?比如:
将(2021年6月4日)+100天后的日期得到的日期,或减-100天后的日期得到的日期。
c++中为了使自定义类型能够像内置类型一样去使用运算符,就有了一个运算符重载函数,当我们需要哪个运算符就去自定义哪一个运算符。
定义:
运算符重载函数是为了增加代码的可读性,运算符重载是具有特殊函数名的函数, 也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表).
那么以下面+=运算符的重载来讲解:
4.2 实现日期类的运算符
4.2.1 日期+=天数
那么我们来实现着这个日期+=天数的赋值重载函数;
首先,我们知道一年中每个月的天数各不相同,有的月有30天,有的月有31天,闰年的二月有29天,所以我们需要先写一个函数能够知道某年某月来获取天数。
在函数前面加inline是把函数定义为内联函数,因为该函数需要经常调用,在数组前面添加一个stastic要把数组定义在静态区,这样就函数每次调用完以后,数组不会被销毁掉,这样就不会每次去调用函数就创建一次数组,以提高效率。
那么我们加完天数后该怎样去把它转化为准确的日期呢?我们以2021年6月4日加100天作为例子:
加完后天数为104天,这时候已经超出了6月的天数,这时候我们需要减去6月的天数(30天),然后月数加1变为7月,则为2021.7.74,这时候天数还是大于7月的天数,这时候我们在减去7月的天数(31天),月数在加1变为8月,则为2021.8.43,同样道理再减去8月的31天,最终结果为2021.9.12.
代码:
得到的结果为:
4.2.2日期+天数
由之前我写的博客可以知道,在类中的成员函数都隐藏着一个this指针,这个this指针是指向对象的地址。我们先通过拷贝构造函数将日期拷贝给tmp,然后tmp再去+=一个日期,最后返回tmp,这样原本的日期就不是发生改变。
4.2.3日期-=天数
这时候我们要需要判断日期是否小于等于0,如果小于等于0,那么我们需要对月数减1,然后在判断月数是否等于0,如果等于0,则年数减1,然后月数赋值为12,然后在加上该月中的天数,以上面为例:这时候的天数为-96,显然天数为负数,那么月数先减1后变为5,然后月数在加上5月的天数31天后变为-65,以此类推,直到天数变为正数。
代码如下:
4.2.4日期-天数:
4.2.5日期++和++日期:
由于前置++和后置++的赋值运算符重载的函数名是一样的,c++为了区分这两个运算符,则后置++的运算符重载函数中的参数里加一个int以便区分,前置++则不需要。如下:
4.2.6 两个日期的比较
判断日期>日期的函数:
判断日期==日期的函数:
判断日期<日期的函数:根据>=的反例是<,我们可以用上面已经实现好的两个函数去实现判断日期<日期。
判断日期>=日期:
判断日期<=日期:
判断日期!=日期:
由上面例子可以得出:实现日期判断大小的6个函数,我们只需要先实现判断日期>日期和日期==日期这两个函数,其它的函数直接调用这两个函数就可以,以后写这种判断大小的也是一样。
4.2.7日期1-日期2=天数
我们先找出较大的日期和较小的日期,然后用较大日期和较小日期进行比较,如果不相等,较小日期++,并count++,直到较小日期等于较大日期时,则count为它们相差的天数。如果日期1大于日期2,则返回一个正数,如果日期1小于日期2,则返回一个负数。
代码如下:
感谢你的点赞,关注,收藏,评论~