- 作 用
- 为对象成员变量赋初始值
- 语 句
- new运算符一起使用
- 中文名
- 构造函数
- 外文名
- constructor
-
2021-03-09 16:28:49
文章目录
一、构造函数
在程序执行的过程中,当遇到与对声明语句时,程序会向操作系统申请一定的内存空间用于存放新建的对象。但是与普通变量相比,类的对象特别复杂,编译器不知如何产生代码去初始化对象,这便引出了构造函数。
1.1 构造函数是什么?
C++中,构造函数是一种特殊的成员函数,在每次创建一个类的时候编译器都会默认调用构造函数进行初始化。
1.2 为什么要有构造函数?
构造函数的作用就是在对象被创建的时候,利用特定的值构造对象,将对象初始化为一个特定的状态。
1.3 如何使用构造函数?
要学会如何使用构造函数,首先需要了解构造函数的一些特殊性质:构造函数的函数名与类名相同,而且没有返回值;构造函数通常被声明为公有函数。
Notes:只要类中有了构造函数,编译器就会在创建新对象的地方自动插入对构造函数调用的代码。所以构造函数在对象被创建的时候将会被自动调用。
下面分别介绍常见的几种构造函数:
- 默认构造函数:
class Clock { public: Clock() //编译器自动生成的隐含的默认构造函数; { } .... };
默认构造函数调用时无须提供参数。如果类中没有写构造函数,那么编译器会自动生成一个隐含的默认构造参数,该构造函数的参数列表和函数体都为空,这个构造函数不做任何事情。
Notes: 无参数的构造函数与全缺省的构造函数都称为默认构造函数。
Q:既然默认构造函数不做任何事情,那么为什么还要生成这个构造函数?
答:因为在建立对象时自动调用构造函数时C++必然要做的事情。上述例子中,默认构造函数什么都没有做,但是有些函数体为空的构造函数并非什么都不做,因为它还要负责基类的构造和成员对象的构造。
- 有参数的构造函数和无参数的构造函数
class Clock { public: Clock(int NewH,int NewM,int NewS); //有参数的构造函数 Clock() //无参数的构造函数 { hour = 0; minute = 0; second = 0; } void SetTime(int NewH,int NewM,int NewS); void ShowTime(); private: int hour,minute,second; }; int main() { Clock(0,0,0); //调用有参数的构造函数 Clock my_clock; //调用无参数的构造函数 return 0; }
上述例子中出现了两种重载的构造函数的形式:有参数的和无参数的(即默认构造函数)
1.4 构造函数的实现
Clock::Clock(int NewH, int NewM, int NewS) { hour = NewH; minute = NewM; second = NewS; }
二、复制构造函数
生成一个对象的副本有两种途径,第一种途径是建立一个新的对象,然后将原始对象的数据成员取出来,赋值给新对象。这样做显然太繁琐了,所以为了使得一个类具有自行复制本类对象的能力,复制构造函数被引出。
2.1 什么是复制构造函数?
复制构造函数是一种特殊的构造函数,其具有一般构造函数的所有特性,其形参是本类对象的引用。
2.2 为什么要有复制构造函数?
复制构造函数的作用是使得一个已经存在的对象(由复制构造函数的参数指定),去初始化一个同类的一个新对象。如果程序没有定义类的复制构造函数,系统就会在必要时自动生成一个隐藏的复制构造函数。这个隐藏的复制构造函数的功能是,把初始值对象的每个数据成员复制到新建立的对象中去。这样得到的对象和原本的对象具有相同的数据成员和属性。
2.3 复制构造函数的功能
在说明复制构造函数的功能之前,我们先看一下声明和实现复制构造函数的一般方法:
class 类名 { public: 类名(形参); //构造函数 类名(类名& 对象名); //复制构造函数 ... }; 类名::类名(类名 &对象名) //复制构造函数的实现 { //函数体 }
前面我们知道,普通的构造函数在对象被创建的时候调用,而复制构造函数在下面3种情况下均会被调用:
例:
class Point { public: Point(int x = 0,int y = 0) { _x = x; _y = y; } Point(Point& p); //复制构造函数 int GetX() { return _x; } int GetY() { return _y; } private: int _x,_y; };
- 当用类的一个对象去初始化另一个对象时:
int main() { Point a(1,2); Point b(a); //用对象a初始化对象b时,复制构造函数被调用 Point c = a; //用对象a初始化对象c时,复制构造函数被调用 return 0; }
Notes:上面对b和c的初始化都能够调用复制构造函数,两种写法是等价的,执行的操作完全相同。
- 如果函数的形参是类的对象,调用函数时,进行形参和实参结合时:
void Fun(Point p) { //函数体 } int main() { Point a(1,2); Fun(a); //函数的形参为类的对象,当调用对象时,复制构造函数被调用. return 0; }
Notes:只有把对象用值传递的时候,才会调用复制构造函数。如果传递的是引用,则不会调用复制构造函数。所以这也是传递引用会比传值的效率高的原因。
- 如果函数的返回值是类的对象,函数执行完成返回调用者时:
Point Fun() { Point a(1,2); return a; //函数Fun的返回值是类的对象,返回函数值的时候,调用复制构造函数 } int main() { Point b; b = Fun(); return 0; }
Q:为什么在这种情况下,返回函数值的时候会调用复制构造函数?
答:函数Fun()将a返回给了主函数,但是我们都知道a是Fun()的局部变量,当Fun()函数的生命周期结束的时候,a也就消亡了,不可能在返回主函数后继续生存。所以在这种情况下编译器会在主函数中创建一个无名的临时对象,该临时对象的生命周期仅仅在b = Fun()中。当执行语句return a时,实际上是调用复制构造函数将a的值复制到无名临时对象中。当函数Fun()运行结束时,对象a消失,但是临时对象会存在于b = Fun()中。当计算这个表达式后,临时对象也就完成了它的工作。
三、析构函数
我们刚讨论了当一个局部变量随着它的函数生命周期结束的时候,函数中的对象也会消失,那么在对象,要消失的时候(比如构造对象的时候,在函数中用malloc动态申请了空间)谁来做这些所谓的“善后”工作呢?这样我们就引出了析构函数。
什么是析构函数?
与构造函数一样,析构函数通常也是类的一个公有成员函数,它的名称是由类名前面加一个"~"构成,没有返回值。析构函数与构造函数不同的是,析构函数不接受任何参数!(参数可以是虚函数),如果我们不自行定义析构函数,则编译器同样会自动生成一个隐藏的析构函数,它的函数体为空。
下面我们看一下析构函数的声明:
class Clock { public: Clock(int NewH,int NewM,int NewS); //有参数的构造函数 Clock() //无参数的构造函数 { hour = 0; minute = 0; second = 0; } void SetTime(int NewH,int NewM,int NewS); void ShowTime(); ~Clock(); //析构函数 private: int hour,minute,second; };
Notes:函数体为空的析构函数并不是什么都不做。
Tips:
类的构造顺序是按照语句的顺序进行构造
类的析构函数调用完全按照构造函数调用的相反顺序进行调用
1.全局对象先于局部对象进行构造
2.静态对象先于普通对象进行构造更多相关内容 -
构造函数
2021-05-07 21:25:46构造函数在类体里的声明形式: 类名(形参一,形参二,…);//也可以没有形参 构造函数的定义形式: 假设数据成员为x1,x2,…x,类外定义构造函数时通常有3种形式: 1、类名::类名(形参1,形参2,…):x1...构造函数在类体里的声明形式:
类名(形参一,形参二,…);//也可以没有形参
构造函数的定义形式:
假设数据成员为x1,x2,…x,类外定义构造函数时通常有3种形式:1、类名::类名(形参1,形参2,…):x1(形参1),x2(形参2),…{ }
2、类名::类名(形参1.形参2,…){ x1=形参1; x2=形参2;…}
3、类名::类名()//成员变量所赋的初值都是固定的
{
x1=初始化表达式;
x2=初始化表达式;
…
}
说明:
1、构造函数的名字必须和类名相同;
2、在定义构造函数时不能指定返回类型,既不要返回值,即使是void类型也不可以。
3、另外类可有多个构造函数,即函数重载;或重载;
4、构造函数的参数在排列时无顺序要求,只要保证相互对应即可;
5、构造函数可以使用默认参数;
6、在程序中说明一个对象时,程序自动调用构造函数来初始化该对象;注意:
自动调用类的后遭函数的时机是:定义类的成员函数、成员对象及友元函数及友元函数时,均不调用类的构造函数。仅当定义类的对象时,才有系统自动调用类的构造函数。
构造函数的使用
注意:使用new创建对象时,下面两种都是合法的:
myData *pd=new myData() //带括号 myData *pd=new myData
用户定义了构造函数,都会调用构造函数进行初始化;
用户未定义构造函数,对带括号的情况,系统在为成员变量分配内存的同时,将其初始化为0.不加括号时,系统只为成员变量分配内存空间,但不进行内存的初始化,成员变量的值是随机的。
复制构造函数与类型转换构造函数
复制构造函数是构造函数的一种,也称为拷贝构造函数。
复制构造函数的作用:使用一个已存在的对象去初始化另一个正在创建的对象。复制构造函数其原型为: 类名::类名(类名&)//对象的引用作为形参 或类名::类名(const类名&) //为了不改变原有对象,使用const限制
注意:如果类中没有给出复制构造函数,那么编译器会自动生成一个默认复制构造函数。
Student stud; Student ss[2]={stud,Student()}; //创建ss[0]中的对象时,用到了默认复制构造函数。 等效一下: Student ss[2]; ss[0]=Student(stud);//调用默认复制构造函数 ss[1]=Student();//调用构造函数
析构函数
析构函数的作用是在对象消失时,释放由构造函数分配的内存;
析构函数在类体里的声明形式:~类名();
析构函数的定义形式:类名::~类名(){}
类只能定义一个析构函数,且不能有参数;
如果程序中没有定义析构函数,则编译器自动生成默认的析构函数,默认析构函数的函数体为空。
封闭类构造函数的初始化列表
-
【C++】构造函数 利用构造函数对类对象进行初始化
2022-01-25 16:01:26有关构造函数与析构函数的知识点前言:
因为类对象的数据成员的值是很重要的 而对类对象数据成员初始化这一重要而频繁的操作 当然越简便越好 所以这里讲的就是对对象数据成员初始化的一些方法
9.1.1 对象的初始化
1. 在声明类时直接对数据成员进行初始化 如下所示
在某红书中说到 在声明类时对数据成员初始化是不对的 但是在 c++11标准下 是允许在声明类时 对数据成员直接赋初值的 因为这本书比较落后了 所以内容没有更新 在vs2019中 这种方法是没有报错的 但是注意考试的时候要填不能在声明类时直接对数据成员赋初值
2. 把数据成员声明为公用成员
把数据成员声明为公用成员后 在定义对象时 可以像定义结构体变量那样对数据成员初始化 但是绝大多数情况下 数据成员都不会声明为公用的 所以这种方法并不可取
9.1.2 用构造函数实现数据成员的初始化
c++提供了构造函数来处理对象的初始化
① 用户不能调用构造函数,在建立对象时自动执行,且只能执行一次。一般声明为public
② 构造函数是在声明类的时候由类的设计者定义的,程序用户只须在定义对象的同时指定数据成员的初值即可。
③ 构造函数名必须和类名同名,不能随意命名。
④ 构造函数不具有任何类型,不具有返回值。 可以有参数。
⑤ 可以用一个类对象初始化另一个类对象 如 Time t1; Time t2 = t1; 这样就把t1的各数据成员的值复制到t2相应的各成员 而不调用t2的构造函数Time();
⑥ 构造函数的函数体中还可以包含如cout的其他语句 但是不推荐这样做
⑦ 如果类的定义者没有自己定义构造函数,则c++系统会自动形成一个构造函数,这个构造函数函数体是空的,没有参数,不执行任何操作。
例
class Time { public: Time() { hour = 0; minute = 0; sec = 0; } void set_time(); void show_time(); private: int hour; int minute; int sec; }; void Time::set_time() { cin >> hour >> minute >> sec; } void Time::show_time() { cout << hour << ":" << minute << ":" << sec << endl; } int main() { Time t1; t1.set_time(); t1.show_time(); Time t2; t2.show_time(); return 0; }
这是构造函数的一个很简单的运用 这里的构造函数的功能是 给对象的数据成员赋初值为0 建立对象t1时调用构造函数 对象t1再调用了set_time函数 重新赋了一次值 而t2中的数据成员的值都是0
构造函数同样可以在类内声明 在类外定义 同样需要声明构造函数的所属类 以及作用于限定符 即
9.1.3 带参数的构造函数
引:上方给出的构造函数,是十分死板的。每个建立的对象都有同样的初始化值,无法达到不同对象不同初始化值的效果。所以有了带参数的构造函数。
即构造函数的参数列表中加若干参数 并在函数体中对不同数据成员赋值。 这样在建立对象并执行构造函数时,就可以把定义对象时给的一些数据赋给数据成员。
构造函数的实参只能在定义对象时给出,即建立对象的同时给出数据成员的初值。
// 带参数的构造函数 class Cube { public: int volume(); Cube(int, int, int); private: int length; int width; int height; }; Cube::Cube(int a, int b, int c) { length = a; width = b; height = c; } int Cube::volume() { return length * width * height; } int main() { //Cube c1 = { 10,20,30 }; Cube c1(10, 20, 30); //建立对象c1 并指定c1的长宽高 直接在后面加括号就行; cout << "c1的体积为:" << c1.volume() << endl; Cube c2(10, 10, 10); cout << "c2的体积为:" << c2.volume() << endl; return 0; }
带参数的构造函数的声明以及类对象的定义方法如上所示,注意main函数中第一行第二行的方式都可以, 第二种可以看成给构造函数的形参赋实参。类似于函数调用。
9.1.4 用参数初始化表对数据成员初始化
不在构造函数函数体内执行初始化语句,放在函数首部实现。让构造函数显得简短精炼,就可以在类体内定义构造函数而不在类外定义。尤其当数据成员较多时更显其优越性,许多c++程序人员都喜欢用这种方式初始化所有数据成员。
// 参数初始化表对数据成员初始化 #include<string.h> class cube { public: cube(int l, int w, int h,string n) :length(l), width(w), height(h) ,name(n) //参数初始化表 { //strcpy(arr, brr); } void show(); private: int length; int width; int height; //char arr[10]; string name; }; void cube::show() { cout << name << ":" << length * width * height << endl; } int main() { cube c1(10, 20, 30, "yang"); c1.show(); cube c2(10, 10, 10, "li"); c2.show(); return 0; }
格式如上,即: 在构造函数的函数首部后方再加一个冒号后面给出对应的值
注意:对于数组的初始化不可以像其他数据那样 因为数组不可以直接用等于号赋值(string可以)需要用strcpy这样的字符数组操作函数进行初始化。
9.1.5 构造函数的重载
即多个构造函数同名,但参数的个数不同,或参数的类型不同的构造函数。称为构造函数的重载
class Box { public: int volume(); Box(int l, int w, int h) :length(l),width(w),height(h){} //有三个参数的构造函数 Box(int l,int w):length(l),width(w) //有两个参数的构造函数 { height = 10; } Box(int l) :length(l) //有一个参数的构造函数 { width = 10; height = 10; } Box(); //无参构造函数 private: int length; int width; int height; }; int Box::volume() { return length * height * width; } Box::Box() { length = 10; width = 10; height = 10; } int main() { Box b1; cout << "b1的体积为:" << b1.volume() << endl; Box b2(20, 20, 20); cout << "b2的体积为:" << b2.volume() << endl; Box b3(20, 20); cout << "b3的体积为:" << b3.volume() << endl; Box b4(20); cout << "b4的体积为:" << b4.volume() << endl; return 0; }
9.1.6 使用默认参数的构造函数
构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值。
class cube { public: cube(int l = 10, int w = 10, int h = 10):length(l),width(w),height(h){} void show(); private: int length; int width; int height; }; void cube::show() { cout << "length=" << length <<" " << "width=" << width <<" " << "height=" << height << endl; } int main() { cube c1(20, 20, 20); c1.show(); cube c2(20, 20); c2.show(); cube c3(20); c3.show(); cube c4; c4.show(); return 0; }
可以看到,在构造函数中使用默认参数是方便而有效的,它提供了建立对象时的多种选择,它的作用相当于好几个重载的构造函数。它的好处是:即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初始化。尤其在希望对每一个对象都有同样的初始化状况时用这种方法更为方便,不需输人数据,对象全按事先指定的值进行初始化。
注意:
(1)在建立对象时不必给出实参的构造函数,称为默认构造函数( defaultconstructor)。显然,无参构造函数属于默认构造函数。一个类只能有一个默认构造函数。如果用户未定义构造函数,则系统会自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。如果用户希望在创建对象时就能使数据成员有初值,就必须自己定义构造函数。(2) 应该在什么地方指定构造函数的默认参数?应在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。因为类声明是放在头文件中的,它是类的对外接口,用户是可以看到的,而函数的定义是类的实现细节,用户往往是看不到的。(类声明和类定义会放在两个文件中)。在声明构造函数时指定默认参数值,使用户知道在建立对象时怎样使用默认参数。
(3)程序第5行在声明构造函数时,形参名可以省略,即写成Box ( int = 10 , int = 10 , int = 10);
(4)如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。由于不需要实参也可以调用构造函数,因此全部参数都指定了默认值的构造函数也属于默认构造函数。前面曾提到过:一个类只能有一个默认构造函数,也就是说,可以不用参数而调用的构造函数,一个类只能有一个。其道理是显然的,是为了避免调用时的歧义性。
(5)在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。自行理解为什么
(6) 但如果构造函数中的参数并非全部为默认值时,(即部分指定默认值)就要分析具体情况。不是一定会出错,是很容易出错,要十分仔细。因此,一般不应同时使用构造函数的重载和有默认参数的构造函数。
总结:
(上方6点是我复制的= =) 构造函数,就是一个在对象建立时自动执行的函数,作用就是方便给对象的数据成员赋初值。 核心就是有参数的构造函数以及有默认参数的构造函数。 我认为这种是最方便的,在建立对象时可直接指定该对象的数据成员的值。
析构函数
析构函数是与构造函数作用相反的函数 当对象的生命期结束时,会自动执行析构函数。
析构函数不返回任何值,没有函数类型,也没有函数参数。由于没有函数参数,因此它不能被重载。一个类可以有多个构造函数,但是只能有一个析构函数。
实际上,析构函数的作用并不仅限于释放资源方面,它还可以被用来执行“类的设计者希望在最后一次使用对象之后所执行的任何操作”,例如输出有关的信息。如果用户没有定义析构函数,C++编译系统会自动生成一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。
调用构造函数和析构函数的顺序
一般情况下: 先构造的后析构,后构造的先析构。
构造函数和析构函数在面向对象的程序设计中是相当重要的,是类的设计中的一个重要部分。
-
C++11移动构造函数详解
2021-12-28 21:42:57这里写目录标题拷贝构造函数修改后的拷贝构造函数移动构造函数移动构造函数的优点 当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。...这里写目录标题
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。拷贝构造函数
C++在三种情况下会调用拷贝构造函数(可能有纰漏),第一种情况是函数形实结合时,第二种情况是函数返回时,函数栈区的对象会复制一份到函数的返回去,第三种情况是用一个对象初始化另一个对象时也会调用拷贝构造函数。
除了这三种情况下会调用拷贝构造函数,另外如果将一个对象赋值给另一个对象,这个时候回调用重载的赋值运算符函数。
无论是拷贝构造函数,还是重载的赋值运算符函数,我记得当时在上C++课的时候,老师再三强调,一定要注意指针的浅层复制问题。
这里在简单回忆一下拷贝构造函数中的浅层复制问题
首先看一个浅层复制的代码:
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *value; Str(char s[]) { cout<<"调用构造函数..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"调用拷贝构造函数..."<<endl; this->value = v.value; } ~Str() { cout<<"调用析构函数..."<<endl; if(value != NULL) delete[] value; } }; int main() { char s[] = "I love BIT"; Str *a = new Str(s); Str *b = new Str(*a); delete a; cout<<"b对象中的字符串为:"<<b->value<<endl; delete b; return 0; }
输出结果为:
调用构造函数... 调用拷贝构造函数... 调用析构函数... b对象中的字符串为: 调用析构函数...
首先结果并不符合预期,我们希望b对象中的字符串也是I love BIT但是输出为空,这是因为b->value和a->value指向了同一片内存区域,当delete a的时候,该内存区域已经被收回,所以再用b->value访问那块内存实际上是不合适的,而且,虽然我运行时程序没有崩溃,但是程序存在崩溃的风险呀,因为当delete b的时候,那块内存区域又被释放了一次,两次释放同一块内存,相当危险呀。
因此对于类中的指针数据成员,必须采用深层复制的方式进行拷贝,深层复制的代码如下:
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *value; Str(char s[]) { cout<<"调用构造函数..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"调用拷贝构造函数..."<<endl; int len = strlen(v.value); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,v.value); } ~Str() { cout<<"调用析构函数..."<<endl; if(value != NULL) { delete[] value; value = NULL; } } }; int main() { char s[] = "I love BIT"; Str *a = new Str(s); Str *b = new Str(*a); delete a; cout<<"b对象中的字符串为:"<<b->value<<endl; delete b; return 0; }
运行结果为:
调用构造函数.. . 调用拷贝构造函数... 调用析构函数... b对象中的字符串为:l love BIT 调用析构函数...
修改后的拷贝构造函数
有时候我们会遇到这样一种情况,我们用对象a初始化对象b,后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么我们可以对指针进行浅复制,这样就避免了新的空间的分配,大大降低了构造的成本。
但是上面提到,指针的浅层复制是非常危险的呀。没错,确实很危险,而且通过上面的例子,我们也可以看出,浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间(同时也是b->value指向的空间)
所以我们可以把上面的拷贝构造函数的代码修改一下:
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *value; Str(char s[]) { cout<<"调用构造函数..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"调用拷贝构造函数..."<<endl; this->value = v.value; v.value = NULL; } ~Str() { cout<<"调用析构函数..."<<endl; if(value != NULL) delete[] value; } }; int main() { char s[] = "I love BIT"; Str *a = new Str(s); Str *b = new Str(*a); delete a; cout<<"b对象中的字符串为:"<<b->value<<endl; delete b; return 0; }
运行结果为:
调用构造函数... 调用拷贝构造函数... 调用析构函数... b对象中的字符串为:l love BIT 调用析构函数...
修改后的拷贝构造函数,采用了浅层复制,但是结果仍能够达到我们想要的效果,关键在于在拷贝构造函数中,最后我们将v.value置为了NULL,这样在析构a的时候,就不会回收a->value指向的内存空间。
这样用a初始化b的过程中,实际上我们就减少了开辟内存,构造成本就降低了。
但要注意,我们这样使用拷贝构造函数有一个前提是:用a初始化b后,a我们就不需要了,最好是初始化完成后就将a析构。如果说,我们用a初始化了b后,仍要对a进行操作,用这种浅层复制的方法就不合适了。
所以C++引入了移动构造函数,专门处理这种用a初始化b后就将a析构的情况。
移动构造函数
移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。这意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。移动构造函数的例子如下:
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *str; Str(char value[]) { cout<<"普通构造函数..."<<endl; str = NULL; int len = strlen(value); str = (char *)malloc(len + 1); memset(str,0,len + 1); strcpy(str,value); } Str(const Str &s) { cout<<"拷贝构造函数..."<<endl; str = NULL; int len = strlen(s.str); str = (char *)malloc(len + 1); memset(str,0,len + 1); strcpy(str,s.str); } Str(Str &&s) { cout<<"移动构造函数..."<<endl; str = NULL; str = s.str; s.str = NULL; } ~Str() { cout<<"析构函数"<<endl; if(str != NULL) { free(str); str = NULL; } } }; int main() { char value[] = "I love zx"; Str s(value); vector<Str> vs; //vs.push_back(move(s)); vs.push_back(s); cout<<vs[0].str<<endl; if(s.str != NULL) cout<<s.str<<endl; return 0; }
在此构造函数中,num 指针变量采用的是浅拷贝的复制方式,同时在函数内部重置了 d.num,有效避免了“同一块对空间被释放多次”情况的发生。
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。
使用move()函数,可以将一个左值变成右值,因此a=move(b)调用的是移动构造函数。
移动构造函数的优点
移动构造函数是c++11的新特性,移动构造函数传入的参数是一个右值 用&&标出。
首先讲讲拷贝构造函数:拷贝构造函数是先将传入的参数对象进行一次深拷贝,再传给新对象。这就会有一次拷贝对象的开销,并且进行了深拷贝,就需要给对象分配地址空间。而移动构造函数就是为了解决这个拷贝开销而产生的。移动构造函数首先将传递参数的内存地址空间接管,然后将内部所有指针设置为nullptr,并且在原地址上进行新对象的构造,最后调用原对象的的析构函数,这样做既不会产生额外的拷贝开销,也不会给新对象分配内存空间。
-
构造函数详解
2020-08-04 16:04:35构造函数详解 构造函数的概念: 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。 构造函数的特性 ... -
【Kotlin】Kotlin 构造函数 ( 主构造函数 | 主构造函数声明属性 | init 初始化代码块 | 次构造函数 | 构造...
2020-03-27 14:01:57主构造函数 II . 主构造函数声明属性 III . init 初始化代码块 IV . 主构造函数参数 和 成员变量访问方式 V . 主构造函数 可见性 设置 VI . 次构造函数 ( 常用情况 ) VII . 次构造函数 ( 没有主构造函数 ) VIII . ... -
C++ 拷贝构造函数和赋值构造函数
2019-04-06 16:43:35在C++中复制控制是一个比较重要的话题,主要包括复制构造函数、重载赋值操作符、析构函数这三部分,这三个函数是一致的,如果类需要析构函数,则它也需要复制操作符 和 复制构造函数,这个规则被称为 C++的“三法则... -
js 中的构造函数,构造函数作用,构造函数和普通函数的区别
2020-08-15 10:25:04这种定义方式,会将函数声明提升到该函数所在作用域的最开头,也是就无论你在这个函数的最小作用域的那儿使用这种方式声明的函数,在这个作用域内,你都可以调用这个函数为你所用。 2.函数表达式:let fun = ... -
C++类的构造函数
2022-03-01 21:36:01详细讲解C++类中的构造函数,包含各种细节,一次讲明白! -
c++ 构造函数详解
2019-05-31 17:20:58c++构造函数详解。(构造函数的分类、拷贝构造函数) -
Java构造函数
2022-02-18 16:16:33Java构造函数 1、Java构造方法定义 Java中的构造方法是一种特殊的方法,用于初始化对象。Java构造函数在对象创建时被调用。它构造值,即提供对象的数据。与函数名相同,无返回值。 2、作用 一般用来初始化成员属性和... -
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数
2022-03-08 18:02:17我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地... -
C++:构造函数与重载构造函数
2020-10-18 22:21:01本文主要总结了默认构造函数的相关用法和构造函数重载,旨在能够对平时的项目开发起到一定的夯实基本功的作用,言简意赅,一目了然。 一、构造函数的定义 构造函数是用来做什么?就是该类对象被创建时,编译器为对象... -
构造函数与拷贝构造函数
2019-04-03 09:07:15拷贝构造函数和构造函数不能分开说,他们都是初始化对象的一种方法。但是我们这里用构造函数辅助说明拷贝构造函数,主要说说拷贝构造函数的声明,用途和使用注意事项。 众所周知,构造函数是一个初始化类对象的函数... -
C++的构造函数和默认构造函数详解
2019-04-07 15:19:45C++的构造函数和默认构造函数 今天学习c++时突然感觉自己对构造函数和默认构造函数的区别有些分不清,于是查找了各大网站将资料汇总一下,供自己和其他朋友们参考。 构造函数是c++的类在构造对象时调用的函数,此... -
移动构造函数与拷贝构造函数
2021-03-07 11:15:14一、移动构造函数: 定义: 所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。 优点: 提高执行... -
js的构造函数理解
2018-11-02 18:05:55文章目录1、什么是构造函数2、为什么要使用构造函数?3、构造函数的执行过程4、构造函数的返回值5构造函数首字母必须大写吗?6不用new关键字,直接运行构造函数,是否会出错?如果不会出错,那么,用new和不用new... -
C++——拷贝构造函数
2022-05-18 23:50:28一、什么是拷贝构造函数 二、如何进行拷贝 实现缺省的拷贝构造函数 三、拷贝构造函数的使用 //c自动添加缺省构造函数。。。6个,c11新增两个 //拷贝构造函数不能够。。。 系统调动 //当一个对象初始化另一个... -
C++拷贝构造函数、构造函数和析构函数
2018-08-30 22:09:15一、拷贝构造函数 转载自:http://www.cnblogs.com/BlueTzar/articles/1223313.html 1、类对象的拷贝 对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通... -
C++中的五种构造函数
2021-05-12 16:55:53C++中的构造函数可以分为4类:默认构造函数、普通构造函数、拷贝构造函数、转换构造函数。 (1)默认构造函数。 未提供显式初始值时,用来穿件对象的构造函数。以Student类为例,默认构造函数的原型为 Student();//... -
C++构造函数(详细)
2020-06-14 15:00:13一、普通类的构造函数 class A { int a,b; public: A(){cout<<"默认构造函数"<<endl;}//自己定义的默认构造函数,什么也不坐 A(int x):a(x){cout<<"转换构造函数1"<<endl;}//转换构造... -
Java的构造函数与默认构造函数(深入版)
2020-03-07 22:39:11我们知道在创建对象的时候,一般会通过构造函数来进行初始化。在Java的继承(深入版)有介绍到类加载过程中的验证阶段,会检查这个类的父类数据,但为什么要怎么做?构造函数在类初始化和实例化的过程中发挥什么作用... -
模板构造函数
2020-12-09 15:00:24模板构造函数不同于模板类 使用模板的作用有以下两个好处: 1.可以将类型作为参数传进; 2.可以传进不同类型的参数; 下面先看看模板类 模板类 模板类的两个示例: template <class T> //声明一个模板,虚拟... -
Dart 构造函数最详细的解析
2019-12-10 18:53:43本文讲述了Dart中构造函数写法 。工厂构造函数 常量构造函数 普通构造函数的关系 -
拷贝构造函数详解
2020-08-09 15:24:51拷贝构造函数详解 拷贝构造函数 int main() { int a; int b = 10; int c(b); return 0 } //结果就是b和c的结果都是10 int main() { int a; int b = 10; int c(b); string s1("hello"); string s2(s1); ... -
C++中默认构造函数和构造函数初始化列表
2018-12-13 10:59:451、默认构造函数和构造函数 (1)构造函数:C++用于构建类的新对象时需要调用的函数,该函数无返回类型!(注意:是“无”! 不是空!(void))。 (2)默认构造函数:默认构造函数是在调用时不需要显示地传入实参... -
C++类中的构造函数和赋值函数
2020-05-30 17:20:17构造函数主要包括:默认构造函数、带参构造函数、拷贝构造函数三种,且构造函数经常与赋值函数混淆,这里放在一起讲,便于区分。 首先初始化一个简单的类作为例子讲解: class A { public: A() { v = 1; printf... -
C++构造函数详解及显示调用构造函数
2018-10-08 10:55:11c++类的构造函数详解 一、 构造函数是干什么的 class Counter { public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回类型 Counter() { ... -
什么是构造函数及定义
2021-03-07 20:47:43什么是构造函数 建立一个对象时,通常最需要立即做的工作是初始化对象,如对数据成员赋初值 构造函数就是用来在创造对象时初始化对象,为对象数据成员赋初始值 -
Th3.14:对象的移动、移动构造函数、移动赋值运算符函数
2021-11-18 20:54:16今天我们将学习 对象的移动、移动构造函数、移动赋值运算符函数。 今天总结的知识分为以下6个点: 一、对象移动的概念 二、移动构造函数和移动赋值运算符的概念 三、移动构造函数的演示 四、移动赋值运算符的演示 ...