C++~类和对象基础(构造函数,析构函数,拷贝构造函数,赋值运算符重载详解)

目录

构造函数

1.概念

2.特性

Q.什么是默认构造函数?自动生成默认构造函数有什么用?

析构函数

1.概念

2.特性

3. 默认生成的析构函数有什么用?

拷贝构造函数

1.概念

2.特性

3.使用默认生成拷贝构造函数需要注意什么

赋值运算符重载

1.什么是运算符重载

2.赋值运算符重载

3.默认生成的赋值运算符重载

取地址重载

const取地址重载


构造函数

1.概念

        构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用。构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用

2.特性

1. 函数名与类名相同。

2. 没有返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

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

6.无参的构造函数全缺省的构造函数都称为默认构造函数可以不用传参的构造函数就是默认构造参数),并且默认构造函数只能有一个(你定义一个无参的,再定义一个全缺省的,能通过编译,但是在调用的时候,不传入参数的情况下就会出现冲突)。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(默认就是指不用传参就可以调用的构造函数)。

7.构造函数的主要任务并不是开空间创建对象,而是初始化对象。

Q.什么是默认构造函数?自动生成默认构造函数有什么用?

默认构造参数(不用传参就可以调用的构造函数就是默认构造函数)有三种:

  1. 我们不写,编译器默认生成的构造函数
  2. 我们写的无参构造函数
  3. 我们写的全缺省构造函数(全缺省最常用,因为使用的时候既可以传参数也可以不传参数

C++把类型分成内置类型(基本类型包括指针)和自定义类型。

        默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理(会调用这个自定义类型成员的默认构造函数)

        如果调用了编译器自己生成的默认构造函数,则对内置类型不做处理,对自定义类型会在初始化列表中调用该类型的默认构造函数进行初始化(如果该类型没有默认构造函数,则需要显示地在初始化列表初始化自定义类型,否则编译会报错),如果调用的是自己写的构造函数,则编译器在初始化列表中也会调用该类型的默认构造函数

        所以自动生成的默认构造函数的存在还是有必要的,当我们不显示写构造函数的时候,生成默认构造函数的初始化列表会帮助我们默认初始化对象,对内置类型不作处理,对自定义类型会调用自定义类型默认的构造函数


析构函数

1.概念

         析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要我们显式调用,而是在销毁对象时自动执行,完成对象中资源的清理工作(资源清理并不是笼统的指销毁对象,资源清理指的是如下代码对栈的处理,对指针的空间进行释放,将指针置空,将栈的元素数量置0这种操作)。        

~Stack() {
	free(_a);
	_a = nullptr;
	_top = capacity = 0;
}

2.特性

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

2. 无参数无返回值类型(所以自然也就无法重载)。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(和自动生成的构造函数类似,对内置类型不做处理,自定义类型会去调用它的析构函数)。注意:析构函数不能重载

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

3. 默认生成的析构函数有什么用?

     如果没有自己写的构造函数,会生成默认的构造函数,它对内置类型不做处理,对象生命周期结束的时候,会调用该自定义类型里面默认的构造函数。

        如果对象的成员属性中有指针变量,而且在初始化的时候要new一块空间给它的,在这种需要自己直接对资源进行管理的情况下,需要自己重写析构函数对指针指向的空间进行释放,因为指针属于内置类型,而默认生成的析构函数对内置类型是不做处理的。


拷贝构造函数

1.概念

      拷贝构造函数:是一种特殊的构造函数,在用对象初始化同一类的对象的时候,编译器会自动调用

2.特性

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

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

3.类中如果没有涉及资源申请(没有自己在堆上面new一片连续空间时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

4.拷贝构造函数典型调用场景:(编译器会自己调用拷贝构造函数来做这些事

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象

3.使用默认生成拷贝构造函数需要注意什么

        一般的类,使用默认自动生成拷贝构造就够用了,它对内置类型的成员会完成值拷贝,浅拷贝,对自定义类型的成员,去调用这个成员的拷贝构造。

        只有像stack这样类,内部存在内置类型(Mtype*),而且在初始化的时候需要自己new一块空间进行初始化,需要这样自己直接管理资源的类,我们需要自己实现拷贝构造函数完成深拷贝。

        默认生成的拷贝构造函数实际上是memcpy按字节拷贝值过去的,属于浅拷贝,所以如果栈里面存放的是内置类型的数组元素,则也不需要自己写拷贝构造,因为数组元素是存放在栈上的,通过memcpy的浅拷贝可以正确实现拷贝。

如果在需要深拷贝的地方使用了默认生成的拷贝构造函数进行了浅拷贝,会出现两个问题:

        1.这两个对象的某个指针成员变量会指向同一片内存区域,这两个对象生命周期结束后调用析构函数的时候会进行两次析构函数,会对同一片内存空间进行两次delete操作,这个时候就会报错

        2.像栈这样的对象,在插入元素的时候,两个栈共用一块空间,修改数据会互相影响。


赋值运算符重载

1.什么是运算符重载

        原来内置类型能够使用的运算符,自定义类型无法使用,运算符重载,就是让原本已经存在的运算符有了新的用法和意义,让自定义类型也能够使用这些运算符。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)——参数列表个数由运算符决定

注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数(不能改变内置类型的运算符,写在类里面的,默认有一个this,这里针对的是写在类外面的,至少要有一个类类型参数)类里面的像这样写在类里面的,和内置类型参数进行运算,默认有this
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义(改变含义虽然语法上没错,但是对人的理解和使用会有很大的影响)
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .*   ::   sizeof   ?:  .  注意以上5个运算符不能重载。(点星,域作用限定符,sizeof,三目运算符,对象点
  6. 如果operator定义在类外面,则==需要传入两个运算符,并且访问的时候需要考虑对象里面的成员是私有的情况。

  7. 一般来说,operator定义在类里面,定义在里面的时候,需要少写一个参数,因为有一个是* const this(注意这里左操作数是this) d1==d2实际上是d1.operator(&d1,d2)

2.赋值运算符重载

  1. 参数类型:const T&,传递引用可以提高传参效率(可以防止原对象被改变)
  2. 返回值类型:T&,返回引用可以提高返回的效率(因为出了函数作用域以后,对象还在,所以可以用引用返回),有返回值目的是为了支持连续赋值
  3. 检测是否自己给自己赋值
  4. 返回*this :要符合连续赋值的含义(因为赋值表达式是有返回值的,i=j返回值是i赋值后的值,为了符合i=j=k的写法
  5. 赋值运算符只能作为类的成员函数重载( 赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数)

eg.:

右边的才是正确写法,加上返回值,为了符合连续赋值的定义,因为出了函数作用域以后,对象还在,所以可以使用引用返回,提高效率。

但是这里不能加const引用,如果是const那么(i=j)=k的时候会编译报错。注意右边参数内的&是引用,下面if判断里面的&是取地址

               

3.默认生成的赋值运算符重载

自动默认生成的拷贝构造函数:

1、内置类型的成员会完成值拷贝,浅拷贝。

2、自定义类型的成员,去调用这个成员的拷贝构造

        但是如果是用一个对象去初始化另一个对象,即使使用赋值运算符,Date d1=d2,也会识别成d1(d2)这种写法,调用拷贝构造函数。

        如果不显示写赋值运算符重载,编译器会为我们自动生成一个,和前面的拷贝构造函数一样,对内置类型会直接赋值,对自定义类型的成员,去调用这个成员的运算符重载。

地址重载

 Date* operator&() 
{ 
    return this ; 
} 

        这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,比如不想让别人拿到真实的地址。

const取地址重载

         这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,比如不想让别人拿到真实的地址。

const Date* operator&()const 
{ 
    return this ; 
} 

你可能感兴趣的:(C++,c++,后端,c语言,java,jvm)