类和对象2(难点)

类和对象2(难点)

1.类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数,对于一个类,我们不写的情况下编译器会默认生成以下6个成员函数,最重要的是前四个,C++11以后还会增加两个默认成员函数分别是移动构造和移动赋值

6个默认成员函数:
一:初始化和清理
    构造函数主要完成初始化工作
    析构函数主要完成清理工作
二:拷贝复制
    拷贝构造是使用同类对象初始化创建对象
    赋值重载主要是把一个对象赋值成另一个对象
三:取地址重载
    主要是普通对象和const对象取地址,很少自己实现

学习时要思考问题

1.我们不写的时候,编译器默认生成的函数行为是什么,是否满足我们的需求

2.编译器默认生成的函数不满足我们的要求,需要自己实现,如何自己实现?

2.构造函数

构造函数是特殊的成员函数,虽然叫构造,但是构造函数的主要任务并不是开空间创造对象(我们常使用的局部对象是栈帧创建的时候空间就开好了),而是对象实例化时初始化对象,构造函数的本质是替代我们以前Date类中写的Init函数的功能,构造函数自动调用的特点就完美的替代了Init

构造函数的特点:

1.函数名和类名相同

2.无返回值(返回值啥都不需要给,也不需要写void)

3.对象实例化时系统会自动调用相应的构造函数

4.构造函数可以重载

5.如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义,编译器将不再生成

6.无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数都叫做默认构造函数,但是这三个函数有且只有一个存在,不能同时存在。无参函数和全缺省函数虽然构成函数重载,但是调用的时候会发生歧义,其实就是不传参就可以调用的函数就叫做默认构造

7.编译器默认生成的构造对内置类型成员变量的初始化没有要求,也就是是否初始化是不确定的,如果这个成员变量没有默认构造函数那么就会报错,初始化成员变量需要用初始化列表才能解决

补充:C++把类型分为内置类型(基本类型)和自定义类型,内置类型就是语言提供的原生数据类型如int/指针等,自定义类型就是使用class/struct等关键字自己定义的类型

class Date 
{


public: 
        void  Init(int year, int month, int day) {
                _year = year;
                _month = month;
                _day = day;
        }

         Date() { //赋值构造
                 _year = 1;
                 _month = 1;
                 _day = 1;
        }
         Date(int year, int month, int day) {//带参构造
                 _year = year;
                 _month = month;
                 _day = day;
         }

private:
        int _year;
        int _month;
        int _day;
};

对象实例化一定会调用构造函数确保实例化是正确的

class Date 
{


public: 
        void  Init(int year, int month, int day) {
                _year = year;
                _month = month;
                _day = day;
        }
         Date(int year, int month, int day) {//带参构造
                 _year = year;
                 _month = month;
                 _day = day;
         }

private:
        int _year;
        int _month;
        int _day;
};

int main() {
        Date d1;
        //这里会出现错误,因为只有带参数的构造
        //只有我们不进行自己构造的时候才默认构造不带参数的
}

3.析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++ 规定对象在销毁时会自动调用析构函数,完成对对象中资源的清理释放工作。析构函数的功能类比我们之前 Stack 实现的 Destroy 功能,而像 Date 没有 Destroy,其实就是没有资源需要释放,所以严格说 Date 是不需要析构函数的。

析构函数的特点:

  1. 析构函数名是在类名前加上字符~。

  2. 无参数无返回值。(这里跟构造类似,也不需要加 void)

  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

  4. 对象生命周期结束时,系统会自动调用析构函数。

  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用他的析构函数。

  6. 还需要注意的是我们显式写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。

就拿手写栈来举例子,手写栈的析构函数为

~Stack() {
        free(a);
        _a = nullptr;
        _capacity = _top = 0;

}

拷贝构造

int main() {
        Date d1(2020, 2, 4);//普通构造
        Date d2(d1);//拷贝构造
}

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

拷贝构造的特点:

  1. 拷贝构造函数是构造函数的一个重载。

普通构造:用字面量 / 多参数创建全新空白对象;

拷贝构造:用已存在的同类对象复制生成新对象; 二者函数名都是类名,参数列表不同,构成重载,编译器自动匹配调用。

  1. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
//Date d1(d2)
         Date(Date& d) {//这里必须用引用不能用传值
                 _year = d._year;
                 _month = d._month;
                 _day = d._day;
         }
         --------------------------
         假如使用了传值
         Date(Date d)
         调用该函数的时候,值传递需要拷贝实参生成形参d
         拷贝自定义对象必须调用拷贝构造函数(第三条)
         于是再次执行Date(Date d)无限递归,导致栈溢出编译器报错
         
        ----------------------------------------
        多参拷贝构造规则
        允许多个形参,但是第一个必须是本类引用,剩下的参数必须给默认值
        否则不被认定为是拷贝构造
        // 合法拷贝构造Date(const Date& d, int addYear = 0){}
        // 非法:第二个参数无默认值,不属于拷贝构造
        Date(const Date& d, int addYear){}
  1. C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。

  2. 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。

  3. 显式定义析构函数并且释放资源一般是和自定义拷贝构造是绑定的

  4. 传值返回会产生一个临时对象调用拷贝构造,传值引用返回返回的是对象的别名,没有产生拷贝。如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个类似于野指针的野引用,传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能引用返回

补充:浅拷贝和深拷贝的区别

浅拷贝(编译器默认拷贝构造的行为)

只复制对象内存表层的值:

  • 对于普通内置变量(int/double):直接复制数值;

  • 对于指针成员:只复制指针本身的地址,不会重新开辟堆内存。

结果:两个对象的指针指向同一块堆资源。

深拷贝(手动实现拷贝构造)

不仅复制表层变量,当成员是堆指针时:

  1. 给新对象单独开辟一块全新堆内存;

  2. 将源对象堆里的数据完整复制到新堆空间; 结果:两个对象拥有独立的堆资源,互不干扰。

运算符重载

1.当运算符被用于类类型的对象的时候,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符的时候,必须转化成调用对应的运算符重载,如果没有对应的运算符重载,则会发生编译报错

2.运算符重载是具有特殊名字的函数,他的名字是operator和后面要定义的运算符共同构成,和其他函数一样,他也有返回类型和参数列表以及函数体

3.重载运算符函数的参数个数和该运算符作用的运算对象数量一样多,一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧传给第二个参数

4.如果一个重载运算符函数是成员函数,则他的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数的时候参数比运算对象少一个

5.运算符重载之后,其优先级和结合性和对应的内置类型运算符保持一致

还是以Date日期类举例子

Date d1(2020, 2, 4);//普通构造
Date d3(2020, 2, 5);
bool ret = d3 > d1;

会产生报错,错误原因

二进制“>”: 没有找到接受“Date”类型的左操作数的运算符(或没有可接受的转换)

没有与这些操作数匹配的 “>” 运算符

这就用到了我们的运算符重载,先用相等做例子

bool operator==(const Date& d1,const Date& d2){
   return d1._year==d2._year
       && d1._month==d2._month
       &&d1._day == d2._day;
}

但是这里会发生报错,原因是operator的参数过多,说明是几元操作就是几个参数,但是有一个隐含的this,所以要发生改变,this默认指向第一个参数

bool operator>(const Date& d2) {
                 if (this->_year > d2._year) {
                         return true;
                 }
                 else if (this->_month > d2._month) {
                         return true;
                 }
                 else if (this->_day > d2._day) {
                         return true;
                 }
                 else {
                         return false;
                 }
         }
         

补充:不能通过链接语法中没有的符号来创建新的操作符,如operator@

.* :: sizeof ?: . 以上五个运算符不能重载

重载操作符至少要有一个类类型的参数,不能通过运算符重载改变内置类型对象的含义

一个类需要重载哪些运算符是看哪些运算符重载之后有意义,比如Date类中的operator-有意义而+没有意义

重载++运算符时,有前置++和后置++,运算符重载都是operator++,没办法很好的区分,所以C++规定,后置++重载的时候,增加一个int实参,和前置++构成函数重载,方便区分

// 1. 前置++:++d  无参数    
Date& operator++(){
 // 日期+1(简化逻辑,只简单日+1)        
 _day++;return *this; // 返回自增后的自身,引用返回支持连续++
   }
   // 2. 后置++:d++  多加一个int占位参数    
Date operator++(int){         
  Date temp = *this; // 先保存原始值        
  _day++;            // 自身自增
  return temp;       // 返回修改前的旧对象,值返回不能引用
     }

赋值运算符重载的特点:

1.赋值运算符重载是一个运算符重载,规定必须重载为成员函数,赋值运算符重载的参数建议写成 const 当前类类型引用,否则传值传参会有拷贝

2.有返回值,且建议写成当前类型引用,引用返回可以提高效率,有返回值的目的是支持连续赋值操作(d1 = d2 =d3)

3.默认的赋值运算符重载也是浅拷贝

//拷贝构造
Date d1(2002.2.2);
Date d2(d1);//是创造一个新的
//赋值是存在的两个对象的拷贝
Date d3(2005.2.3);
d1 = d3;
/////////////////////////////////////////////
Date& operator= (const Date& d){
    _year = d._year;
    _month=d._month;
    _day = d._day;
    return *this;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值