0、构造函数

C++类的默认函数

在C++中,一个类有八个默认函数:

  1. 默认构造函数;
  2. 默认拷贝构造函数;
  3. 默认析构函数;
  4. 默认重载赋值运算符函数;
  5. 默认重载取址运算符函数;
  6. 默认重载取址运算符const函数;
  7. 默认移动构造函数(C++11);
  8. 默认重载移动赋值操作符函数(C++11)。

如下这些函数:

  • 一个默认构造函数
  • 一个默认拷贝构造函数
  • 一个默认析构函数
  • 一个默认重载赋值操作符函数

1、只有在第一次被调用时,才会被编译器创建,当然这几个生成的默认函数的实现就是什么都不做;

2、所有这些函数都是inline和public的;

3、如果你显式的声明了这些函数中的任何一个函数,那么编译器将不再生成默认的函数。

个人理解:

如上的 “默认构造、默认拷贝构造、默认析构、默认赋值重载”这四个函数, 因为“如果你显式的声明了这些函数中的任何一个函数,那么编译器将不再生成默认的函数”,所以开发者(程序员)应该保证进行显示声明。

理由:

  • 1、一个程序员自己定义的类, 在使用中肯定要进行初始化, 所以必然会显示定义构造函数,那么如上这四个函数必将不存在。那么显示定义构造函数,定义几个好呢? 原则是, 用到的既声明定义就好, 如果后续开发遇到必须新增,则再新增即可,新增的原则也是先考虑新增默认的类型是否即可满足。
  • 2、由于如上原因, 析构显然也要被动的进行显示声明定义了。
  • 3、拷贝构造、赋值重载,为了程序的健壮性、适应性,显然也是显示声明定义的好。
  • 4、因为默认的这四个函数,什么也不做,所以更应该显示声明这四个函数,确保类可以正常的被使用。

我们不希望对象被显示构造或赋值,可以将对应函数声明为private,或者写一个基类,开放部分默认函数,子类去继承就可以了。

C++11新增标识符default和delete,控制这些默认函数是否使用。

  • default:被标识的默认函数将使用类的默认行为,如:A() = default;
  • delete:被标识的默认函数将禁用,如:A() = delete;

override:被标识的函数需要强制重写基类虚函数;

final:被标识的函数禁止重写基类虚函数;

构造函数(Constructor)

  • 函数名与类名相同,可以重载,不能为虚函数,不能有返回值,连void也不行;

  • 如果没有显式定义,编译器会自动生成一个无参的默认构造函数,默认的构造函什么都不会做;注意: 默认构造函数 和 缺省构造函数不是一个概念

  • 无参构造函数和带有缺省值的构造函数(全缺省)都认为是缺省的构造函数,并且**缺省的构造函数只能有一个**;

  • 构造函数有初始化列表构造函数体内赋值两种方式

    • 初始化列表在初始化对象时更高效(每个成员在初始化列表中只能出现一次),减少了一次赋值操作,推荐此方法;
    • 以下成员变量必须在初始化列表中初始化:常量成员变量引用类型成员变量没有缺省构造函数的成员变量(如果构造函数的参数列表中有一个类的对象,并且该对象的类里没有缺省参数的构造函数时,要是不使用初始化列表,参数中会调用无参或者全缺省的构造函数,而那个类中又没有);
    • 构造函数内可以使用this指针,但不可以用于初始化列表。
    • 初始化列表仅用于初始化数据成员,不指定这些数据成员的初始化顺序,数据成员在类中定义的顺序就是在参数列表中的初始化顺序。
  • 构造顺序:

    • 虚拟基类的构造函数(如果有多个虚拟基类,按照它们被继承的顺序构造,而不是它们在成员初始化列表中的顺序);
    • 非虚拟基类的构造函数(如果有多个非虚拟基类,按照它们被继承的顺序构造,而不是它们在成员初始化列表中的顺序);
    • 成员对象的构造函数(如果有多个成员类对象,按照它们声明的顺序调用,而不是它们在成员初始化列表中的顺序);
  • 构造函数不能用const来修饰,因为const修饰的是this指针,加上const之后就无法对对象的值进行赋值。

  • 构造函数不能用static修饰,因为静态函数没有this指针。

拷贝构造函数(Copy Constructor)

  • 拷贝构造函数实际上是构造函数的重载,具有一般构造函数的所有特性;用类的一个已知的对象去初始化该类的另一个对象时,会自动调用对象的拷贝构造函数;

  • 第一个参数是对某个同类对象的引用,且没有其他参数(除非其他参数都有默认值),返回值是类对象的引用,通过返回引用值可以实现连续构造,即类似A(B©)这样;

  • 如果没有显式定义,编译器会自动生成一个默认的拷贝构造函数,默认的拷贝构造函数会依次拷贝类的数据成员完成初始化;

  • 浅拷贝和深拷贝:编译器创建的默认拷贝构造函数只会执行"浅拷贝"。

  • 它的参数必须使用同类型对象的引用传递。因为对象以值传递的方式进入函数体就会调用拷贝构造函数,这样就会形成无限递归。

析构函数(Destructor)

  • 函数名在类名前加上字符~,没有参数(可以有void类型的参数),没有返回值;
  • 可以为虚函数(通过基类的指针去析构子类对象时候);
  • 不能重载,故析构函数只有一个;
  • 如果没有显式定义,编译器会自动生成一个默认的析构函数,默认的析构函什么都不会做;
  • 析构顺序:和构造函数顺序相反。

重载赋值运算符函数(Copy Assignment operator)

  • 它是两个已有对象,一个给另一个赋值的过程。当两个对象之间进行赋值时,会自动调用重载赋值运算符函数,它不同于拷贝构造函数,拷贝构造函数是用已有对象给新生成的对象赋初值的过程;
  • 赋值运算符重载函数参数中const和&没有强制要求
  • 返回值是类对象的引用,通过返回引用值可以实现连续赋值,即类似a=b=c这样
  • 返回值类型也不是强制的,可以返回void,使用时就不能连续赋值;
  • 赋值运算符重载函只能定义为类的成员函数,不能是静态成员函数,也不能是友元函数,赋值运算符重载函数不能被继承,要避免自赋值;
  • 如果没有显式定义,编译器会自动生成一个默认的赋值运算符重载函数,默认的赋值运算符重载函数实现将数据成员逐一赋值的一种浅拷贝,会导致指针悬挂问题。

重载取址运算符函数

1.重载取址运算符函数没有参数;

2.如果没有显式定义,编译器会自动生成默认的重载取址运算符函数,函数内部直接return this,一般使用默认即可。

移动构造函数和重载移动赋值操作符函数

1.C++11 新增move语义:源对象资源的控制权全部交给目标对象,可以将原对象移动到新对象, 用于a初始化b后,就将a析构的情况;

2.移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用;

3.临时对象即将消亡,并且它里面的资源是需要被再利用的,这个时候就可以使用移动构造。移动构造可以减少不必要的复制,带来性能上的提升。

class String
{
public:
 
	//  移动构造
	// String s(将亡值对象)
	String(String&& s) : _str(nullptr)
	{
			swap(_str,s._str);
	}
      
	// 移动赋值
	String& operator=(String&& s)
	{
		swap(_str,s._str);
		return *this;
	}
    
	~String()
	{
		if(_str)
			delete[] _str;
	}
    
    
private:
	char* _str;
};

相关小问题

new和malloc的比较:

(1).new失败时会调用new_handler处理函数,malloc不会,失败时返回NULL;
(2).new会调用对象的构造函数,malloc不会;
(3).new出来的东西是带类型的,malloc是void*,需要强制转换;
(4).new是C++运算符,malloc是C标准库函数。

delete和free比较

(1).delete能自动调用对象的析构函数,free不会;
(2).delete是C++运算符,free是C标准库函数。

拷贝构造函数参数为什么必须使用类类型对象引用传递

传参的位置如果一直调用拷贝构造函数,也就是会递归引用,导致栈溢出。

赋值运算符重载函数为什么要避免自赋值

  • 自赋值无意义,如果自赋值,可以立即return *this;

  • 如果不避免,当类的数据成员中如果含有指针,自赋值时会造成内存泄漏。

构造函数深入理解

**错误认识1:**若程序员没有自己定义构造函数,那么编译器会自动生成默认构造函数,来进行对成员函数的初始化。

**错误认识2:**编译器合成出来的default constructor会明确设定’“class内每一个data member的默认值”。

正确认识:

构造函数分为有用的和无用的,所谓无用的构造函数就是一个空函数、什么操作也不做,而有用的构造函数是可以初始化成员的函数。

对构造函数的需求也是分为两类:一类是编辑器需求,一类是程序的需求。
**程序的需求:**若程序需求构造函数时,就是要程序员自定义构造函数来显示的初始化类的数据成员。
**编辑器的需求:**编辑器的需求也分为两类:

  • 一类是无关紧要(trivial)的默认构造函数
    • 用户并没有显示地定义构造函数,编译器会为它自动生成一个无关紧要(trivial)的默认构造函数,生成的默认构造函数什么也不做,既不会讲其成员变量置零,也不会做其他的任何事情,只是为了保证程序能够正确运行而已。
  • 一类是编辑器自己合成的有用的构造函数(non-trivival)
    • 自己无构造函数, 并且含有包含缺省构造函数的成员类对象:
      • 如果该类包含一个成员类对象,它有缺省构造函数,那么这个类的隐式构造函数是非平凡的(前提是该类无显示构造函数),并且编译器需要为包含的这个成员类对象生成一个默认构造函数。然后,这个编译器生成的默认构造函数只有在实际上被调用时才会被真正的生成。
      • 如果class中含一个以上的含有缺省构造函数的object,那在为class合成的default constructor中,会按照object的声明次序调用object 的 缺省的构造函数
    • 自己有构造函数, 并且含有包含缺省构造函数的成员类对象:
      • 如果设计者提供了多个constructor,但未提供缺省的构造函数,那么编译器不会合成新的default constructor,而是会扩展所有的现有的constructor,安插进去default constructor所必须的代码。
    • 基类带有缺省构造函数的派生类
      • 当一个类派生自一个含缺省构造函数的基类时,编译器合成的默认构造函数将根据基类声明顺序调用上层的基类缺省构造函数。同样的道理,如果设计者定义了多个构造函数,编译器将不会重新定义一个合成默认构造函数,而是把合成默认构造函数的内容插入到每一个构造函数中去。
    • 带有虚函数的类
      • 类带有虚函数可以分为两种情况:类本身定义了自己的虚函数、类从继承体系中继承了虚函数
      • 这两种情况都使一个类成为带有虚函数的类。这样的类也满足编译器需要合成默认构造函数的类,原因是含有虚函数的类对象都含有一个虚表指针vptr,编译器需要对vptr设置初值以满足虚函数机制的正确运行,编译器会把这个设置初值的操作放在默认构造函数中。如果设计者没有定义任何一个默认构造函数,则编译器会合成一个默认构造函数完成上述操作,否则,编译器将在每一个构造函数中插入代码来完成相同的事情。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值