认识C++中的五个特殊函数和一些应用场景

一直对C++拷贝构造函数和赋值函数有所混淆,索性今天自己也整理篇这方面的文章,一遍加深记忆

首先把C++里面分为五类函数

  • 构造函数
  • 拷贝构造函数
  • 赋值函数
  • 析构函数
  • 移动构造函数

认识C++中的五个特殊函数和一些应用场景_第1张图片

下面就以上面的图进行说明

普通构造函数

普通构造函数分为两类,无参构造函数,又参数构造函数

无参构造函数

格式:

class MyClass {
public:
	MyClass()
};

特点:

  • 如果系统中没有显示声明任何的构造的函数,编译的时候会自动添加一个无参构造函数
  • 用户可以直接声明无参构造函数,然后在构造函数的实现里面初始化自己想初始化的东西
    则可以写成
MyClass::MyClass() {
	//添加你自己初始化的东西
}

有参构造函数格式:

class MyClass {
public:
	MyClass(int val);

private:
	int val;
};

特点:
有参构造函数一般用在在构造函数的时候需要用已知的参数进行初始化的地方,比如我需要用一个int类型的值10来初始化一个对象,可以写成

MyClass *myClass = new MyClass(10);

️参构造函数和无参数构造函数总结

  • 都可以通过 MyClass *myClass = new MyClass()方式实现
  • 如果是无参数构造函数还可以简写成这样 MyClass *myClass = new MyClass
  • 通过对象声明的时候 写成这样 MyClass myClass(10)

拷贝构造函数

首先构造函数,一定是从无到有的一个创造的过程,意思就是我需要创建一个对象myClass,这个对象之前不存在,我需要另外一个对象other来给到一些信息来才能创建。

格式

MyClass (const MyClass&other)

拷贝构造函数例子

MyClass *my1;
MyClass *my2 = new MyClass;
my1 = my2; //调用了拷贝构造函数

三种使用场景

在C++中,3种对象需要复制,此时拷贝构造函数会被调用

  1. 一个对象以值传递的方式传入函数体
  2. 一个对象以值传递的方式从函数返回
  3. 一个对象需要通过另一个对象进行初始化

特点

1)如果用户没有自定义拷贝构造函数,并且在代码中使用到了拷贝构造函数,编译器就会生成默认的拷贝构造函数。但如果用户定义了拷贝构造函数,编译器就不在生成。

2)如果用户定义了一个构造函数,但不是拷贝构造函数,而此时代码中又用到了拷贝构造函数,那编译器也会生成默认的拷贝构造函数。

深copy和浅copy

我们可以想象如果在other对象里我们malloc 了一片内存,然后对这片内存进行赋值操作,如果浅copy操作,那么myClass里面只是保存了other对象里的地址,两个人时共享这个地址,这往往并不是我们想要的,因此我们就需要深copy,深copy的操作就是自己在copy构造函数的时候分配自己的内存,然后把other里面的内存上面的数据copy到myClass上来,完成用other的参数初始化了myClass的参数。

  • 编译器默认的copy构造函数一般都是浅copy的。
  • 标准模板库(STL)中的 string、vector、stack、set、map 等也都必须使用深拷贝。

赋值函数

有人把赋值函数也理解成为一种构造函数,我一般不这样理解。赋值函数的前提是对象已经存在,我还需要用一个对象去更改,去赋值,才会调用赋值函数,他不满足一个对象从无到有的过程,所以他不能理解为一个构造函数。

格式:

 MyClass & operator = (const MyClass& other);

赋值函数的例子

MyClass *my1 = new MyClass;
MyClass *my2 = new MyClass;
my1 = my2; //调用了赋值函数

copy构造函数和赋值函数的比较

  • 拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。
  • 一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
    实现不一样。
  • 拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。(这些要点会在下面的String实现代码中体现)

析构函数

析构函数一般主要目的是把自己所拥有的资源归还给系统,对象声明周期终止。

格式

~MyClass();

特点

  • 如果没有显示的定义构造函数,系统会默认添加一个构造函数
  • 有的空的构造函数看似简单,实则是他执行了复杂的操作
  • 析构函数先构造父类,再构造子类,和析构函数的顺序刚好相反
  • 析构函数的按照成员的顺序进行析构的
  • 析构函数没有参数

noncopyable原理及实现

我们可以看到很多开源的代码里面类都继承了noncopyable,
我这里尝试理解了这种写法的作用,主要是为了限制他的使用范围
举个例子

class MyClass : public noncopyable {
};
MyClass *myClass = new MyClass;

有一下几点限制,否则会出现编译错误

  • myClass 不能作为函数参数传递
  • myClass 不能作为返回值
  • myClass 不能被另外一个MyClass对象初始化
  • myClass 不能被另外一个MyClass对象复制

class noncopyable的基本思想是把构造函数和析构函数设置protected权限,这样子类可以调用,但是外面的类不能调用,那么当子类需要定义构造函数的时候不至于通不过编译。但是最关键的是noncopyable把拷贝构造函数和拷贝赋值函数做成了private的,继承自noncopyable的类在执行拷贝操作时会调用基类的拷贝操作,但是基类的拷贝操作是private的,因此无法调用,引发编译错误。

class noncopyable {
protected:
	noncopyable() = default;
	~noncopyable() = default;
private:
	noncopyable(const noncopyable&) = delete;
	const noncopyable& operator=( const noncopyable& ) = delete;
};	

移动构造函数

所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。

动手体会

由于时间有限 这里不做过多的解释,在网上摘抄了一段String实现的代码,自己测试和体会。


#include 
#include 
#include 
#include 
using namespace std;

class String{
    public:
        char *str;
        String(char value[])
        {
            cout<<"普通构造函数..."< vs;
    vs.push_back(move(s));
    //vs.push_back(s);
    cout<

编译方式:
g++ -o String String.cpp -std=c++11

你可能感兴趣的:(c++)