C++基础知识(一)——构造函数

C++基础知识(一)——构造函数

目录

构造函数定义

构造函数到底是什么,它在C++对象创建过程到底起什么样的作用呢
《C++ Primer》中文版(第五版)中是这么定义的:

每一个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

看完书中的定义其实我们还是什么似懂非懂,我们都知道C++中一个对象的创建分两步:

  • 为对象分配空间;
  • 对对象进行初始化工作;
    而我们的构造函数其实就是做的第二步的工作,所以构造函数的实质其实就是一个对对象进行初始化的工作。

构造函数的形式

那么到底如何进行初始化呢,或者说如何进行构造函数的编写和使用呢
构造函数分为默认构造函数,拷贝构造函数以及其他形式的构造函数。至于调用哪一个构造函数则根据函数重载机制进行选择。

class Test
{
public:
    Test();
    Test(const Test &copy_test);
    Test(const std::string &arg_s, int arg_x, int arg_y);
private:
    std::string s;
    int x = 0;
    int y;
};

对于上面的Test类,
1. Test()就是一个简单的默认构造函数,当然这是一种最简单的形式;
2. Test(const Test &copy_test)拷贝构造函数
3. Test(const std::string &arg_s, int arg_x, int arg_y)其他形式的构造函数
4. Test() : Test("", 0, 0) 1委托构造函数。即委托它所属类中其他构造函数执行自己的初始化过程。委托函数执行完后会返回该构造函数的函数体继续执行。

而对于Test(const std::string &arg_s = "", int new_x = 0, int new_y = 0); 这样含有默认参数的构造函数,当所有的参数都有默认值时,则其实质上相当于也定义了默认构造函数。

构造函数初始化的方式

1.在函数体内进行初始化

Test(const std::string &arg_s, int new_x, int new_y)
{
    s = arg_s;
    x = new_x;
    y = new_y;
}

这种方式效率最低,不建议大家使用。至于为什么,后面会讲到。

2.使用初始化列表进行初始化

Test() : x(5),y(5)
{
}

在构造函数后面加:成员变量名(值),成员变量名(值)这种形式就是参数化列表。
使用参数化列表初始化效率高于函数体内赋值。建议使用这种方法进行初始化。

为什么说参数化列表的效率比函数体内赋值要高呢?

  • 构造函数在执行函数体之前,会使用初始化列表对数据成员进行初始化;
  • 对没有初始化列表的数据成员,若有类内参数值,则会采用类内参数进行初始化;
  • 其他的则采用默认初始化

也就是说,如果你在初始化列表中初始化了,就会采用你的初始化值,否则就是编译器执行默认初始化操作。像本例子中,根据声明顺序s,x,y(类定义中数据成员排列的顺序),先初始化s,采用默认初始化;在初始化x,y,采用自己的初始化值5进行初始化。

初始化的顺序只与声明顺序有关,与初始化列表中的顺序没有关系。也就是说,你最好不要打乱顺序,且不要用一个成员来初始化另一个成员。例如:Test(int value) : y(value), x(y) { },由于初始化顺序只与声明顺序有关,因此先初始化s,在初始化x,然后是y,造成x在y之前初始化,此时由于y没有初始化,所有x的值是未知的。

那么默认初始化究竟是执行一个什么样的过程呢?

  • 对于类成员,则会调用类的默认构造函数进行初始化(当然,如果没有默认构造函数的话,编译器就会报错);
  • 对于内置类型(int,char,double等),若在函数体之外,则被初始化为0;在函数体内,则值为未定义(即不被初始化)。

编译器合成的默认构造函数

1.定义

没有定义任何一个构造函数时,编译器就会自动合成默认构造函数。

下列情况下,编译器不能合成默认的构造函数:

  • 当类中有其他的构造函数;(C++认为如果类在某种情况下需要控制对象初始化,即调用自定义的构造函数,它就会认为你会在所有的情况下都调用自定义的构造函数,即控制对象初始化)
  • 当类中的成员没有默认构造函数。

2.合成默认构造函数的形式

  • Test() = default; 2这种形式表示使用编译器合成的默认构造函数。可以在类内声明时使用,也可以在类外定义时使用。
  • 没有构造函数时,编译器会自动加一个默认构造函数。不建议使用,这样无法控制合成的默认构造函数是不是自己想要的情况。

3.合成默认构造函数的使用场景

使用自定义默认构造函数,而不用编译器合成的的场景

  • 某些情况下,假如我们的成员中有指针,我们可能需要初始化指针所指空间中的所有内容,而不仅仅是拷贝指针的值。使用编译器合成的默认构造函数会造成浅拷贝。(详情请查看浅拷贝与深拷贝的区别string类的实现);
  • 含有内置类型或者复合类型,由于它们在函数内默认初始化为未定义的值,所以最好使用自定义的默认构造函数进行初始化;

继承中的构造函数

当该类继承自父类时,调用构造函数时,如果没有显示在参数列表中列出父类初始化(形式为父类构造函数调用),则会先调用父类的默认构造函数进行父类对象的初始化,然后才会进行子类成员的初始化。

含有虚函数的构造函数

当类中含有虚函数时,在调用构造函数初始化时,会首先初始化对象的虚表指针,在初始化其他的成员,然后进入函数体内执行某些操作。

总结

1.构造函数的调用过程

  • 当创建一个对象时,会首先在内存中为对象开辟空间,然后调用构造函数;
  • 构造函数调用时,会首先进行初始化;
    • 有参数列表,则根据参数列表进行初始化;
    • 没有参数列表的成员,若类中其有初始值,则根据类中初始值进行初始化;
    • 若都没有,则采用默认初始化。

2.构造函数返回值

构造函数是回调函数,当C++创建对象时,会首先使用内存分配函数对对象进行空间的分配(类似于malloc,::operator new),当分配空间完成后(类似于做GUI的时候双击按钮),会回调构造函数(类似于点完按钮,然后会执行某些操作),构造函数完成后,又会返回回来,最后返回内存分配函数分配的空间地址(malloc,::operator new最后都会返回开辟内存的地址)。

构造函数是没有返回值的,而创建对象会返回创建的对象的地址。(这是分配空间完成的操作,当构造函数回调完成之后返回对象的地址)。


  1. C++11标准
  2. C++11标准
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值