重温C与C++之构造函数

写在前面

相信做过Java、C++或者其他面向对象语言开发的朋友们一定对构造函数这个概念不陌生。以前初学C++的时候笔者看过几次《C++ Primer》这本书,但是每次都是走马观花式的快速阅读,
每次浏览完之后内心就会冒出两个字:就这?现如今回想起来真是图样图森破

学习最忌讳的就是心急如焚,砍柴不磨刀,所谓欲速则不达,一步一个脚印才能走得更稳。

由问题开始

下面我们就从几个问题出发,加深一下对C++中构造函数的了解:

1、构造函数初始化与赋值的问题

以下的这两个写法有什么区别?

class Person {
public:
    Person(const string name, int age);

private:
    string name;
    int age;
};

// 第一种写法
Person::Person(const string name, int age) {
    this->name = name;
    this->age = age;
}

// 第二种写法
Person::Person(const string name, int age):name(name),age(age) {
    
}

在这个例子中第二种写法是使用构造函数初始值的写法,第一种写法虽然合法,也没有错误,但是并不是合理的写法,并不推荐。

那么这两种写法有什么区别呢?
第一种写法会经历先初始化,再赋值这么两个过程;而第二种写法则是直接初始化数据成员一步到位。所以这里面会存在一个效率的问题,第二种写法的效率更高。

我们再看一个例子,如果我们把类的成员使用const修饰呢,结果会怎样?

class Person {
public:
    Person(const string name, int age);

private:
    string name;
    const int age;
};

// 第一种写法,编译报错
Person::Person(const string name, int age) {
    this->name = name;
    this->age = age;
}

// 第二种写法
Person::Person(const string name, int age):name(name),age(age) {

}

我们发现第一种写法行不通了,不能编译通过了,这是因为age被const修饰了,必须在初始化时赋值,所以第一种写法就不行了,由此看出使用构造函数初始值的写法更加规范,更加安全。

建议:在《Effective C++》一书中的第4条"确定对象被使用前已先被初始化"中也强调了绝对必要使用构造函数初始值

2、成员变量的初始化顺序

如下例子,如果外部调用Point对象的getX方法,能拿到正确的值吗?答案是不能的,因为成员x比成员y先初始化。

class Point {

public:
    Point(int x, int y);

    int getX() const{
        return x;
    }

    int getY() const{
        return y;
    }

private:
    int x;
    int y;
};

// 本意是把 yVal的值赋值给成员变量y,然后把成员变量y的值赋值给成员变量x
Point::Point(int xVal, int yVal):y(yVal),x(y) {

}

一般按照我们常规的思维,我们在构造函数中先写了y,再x,那应该是写初始化y,再初始化x吧?然而事实并不是这样子的。

起始构造函数初始值是有一定的规则的:

构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体执行顺序。成员的初始化顺序与它们在类定义中的出现顺序一致:第一个成员先被初始化,
然后第二个,以此类推。构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序。

所以上面构造函数的写法中虽然y出现在了x的前面,但是在成员变量声明的时候是先声明了x的,所以初始化的时候是先初始化了x,但是把一个未经初始化的y赋值给了x,那肯定是不能成功赋值的,
所以通过getX方法获取到的值也就不是你想要的那个值了。

3、对于继承而来的派生类的成员初始化顺序是怎么样的呢?

尽管在派生类对象中含有从基类继承而来的成员,但是派生类并不能直接初始化这些成员。和其他创建了基类对象的代码一样,派生类也必须使用基类的构造函数来初始化它的基类部分。
首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。

3、委托构造函数的执行顺序

所谓委托构造函数就是构造函数相互调用。

当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体被依次执行。
如果受委托的构造函数体恰好是空的。假如函数体包含有代码的话,将先执行这些代码,然后控制权才会交还给委托者的函数体。

4、构造函数异常如何捕获

处理构造函数初始值异常的唯一方法是将构造函数写成函数try语句块。

5、如何让类不能在栈内构造

笔者查了下网上的资料说大概就是说将构造方法私有化,并且将拷贝构造函数私有化就能禁止类的对象在栈内构造了。笔者测试了一下其实这并不严谨,这样的做法只能做到在类的外部禁用了栈内初始化,
在类的内部依然可以使用栈的方式构造对象,比如一下例子:

class Data {
public:
// 在类的内部依然可以使用栈的方式构造
    Data create() {
        Data data = Data();
    }

private:
    Data();
    Data(const Data &data) {

    }

};

经过笔者的测试,私有化构造函数,再加上使用delete关键字移除拷贝构造函数即可实现禁用类在栈内构造的功能:

class Data {
public:
    // 不能在栈内构造,编译会报错
    Data create() {
        return Data();
    }

private:
    Data();
    Data(const Data &data) = delete;
};

但是这种做法实在是太过了,而且笔者笔者才疏学浅,也不知道这种做法会不会造成什么隐藏的坑,如有高手,请赐教。

《More EffectiveC++》一书中第27条:要求(或禁止)对象产生与heap之中,提到将构造函数和析构函数私有化即可达到禁止对象在栈内定义的目的。
但是这个做法太过了,比较好的办法是让析构函数r成为 private,而构造函数仍为 public。

6、如何让类不能在堆内构造对象

使用new在堆内构造对象主要会调用构造函数以及new运算符这两个步骤,所以我们只要把运算符new移除即可:

class Data {
public:
    Data();
    // 重载new运算符,禁止使用new在堆内构造对象
    void* operator new (size_t size) = delete;
};

然而笔者发现,虽然这样能够禁用new在堆内构造对象,但是我们知道使用 malloc 也能在堆内分配对象,只是使用 malloc 不会调用类的构造函数而已,所以类内的所有成员都需要自己手动初始化,
那么有没有办法把malloc也禁用掉呢?笔者并不知晓,恳请高手赐教。。。

在《Effective C++》一书中第06条有提到为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。

所以为了达到某个类只能在堆内或者只能在栈内构造的目的可以参考这一条。

总结一下

1、谁先声明谁先初始化,与构造函数中出现的顺序无关;

2、初始化值中的相关调用比构造函数中的函数体优先执行;

3、在派生类中首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。

结语

不得不感叹一下,C++真是一门博大精深的语言,你学得越多,你不知道的就越多。

学C++三年,口出狂言
再学三年,不敢妄言
又学三年,沉默寡言

就笔者而言,我觉得学习是一个实践的过程,尤其是对于编程类的学习。看博客、看书、看文章只能解决你当时的疑惑,并不能在我脑海中留下根深蒂固的印象,如果不去实践证明、不去总结归纳的话,很快就会忘记了,过不了多久其实和没有学过差不多。

共勉!!!

关注我,一起进步,人生不止coding!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容