C++ Primer 第七章⑤

C++ Primer

第七章 类

构造函数再探

特殊的成员变量

构造函数的初始值有时候必不可少,什么时候必不可少呢?当成员变量是const或者引用的时候,原因么,你懂的:

class ConstRef
{
public:
    ConstRef(int ii);
private:
    int i;
    const int ci;
    int &ri;
};

这样去构造一个类的时候很容易犯错,比如你写这样一个构造函数:

ConstRef::ConstRef(int ii)
{
    i = ii; //正确
    ci = ii; //错误:不能给const赋值
    ri = i; //错误:引用没有初始化
}

所以说啊:我们初始化const或者引用类型的数据成员的唯一机会就是通过构造函数初始值,于是我们可以这么写:

ConstRef::ConstRef(int ii) : i(ii), ci(ii), ri(i){}

书上有一句话可以称为金玉良言:如果成员是const、引用或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。

成员的初始化顺序与它们在类定义中的出现顺序一致,而与初始化列表无关,这句话经常考,什么意思呢,看例子:

class X
{
    int i;
    int j;
public:
    X(int val) : j(val), i(j){}
};

这样是错的,对吧?因为i和j的初始化顺序是按照定义来的,先有i后有j,所以,你怎么能用j去初始化i呢?
最好的方式是用构造函数传进来的参数去初始化成员,这样我们就不用考虑初始化顺序了:

X(int val) : i(val), j(val){}
默认实参和构造函数

通常,我们说默认构造函数是没有参数的,但我们也可以这么干:

class Sales_data
{
public:
    Sales_data(string s = "") : bookNo(s){}
}

你可能觉得这个函数是一个普通的构造函数,但是,其实它是这个类的默认构造函数,它用到了默认实参,这么写的好处是,你可以不传参数,也能调用(不就相当于默认构造函数了吗),你不传参数的时候,它就用空字符串s去初始化bookNo,传的话就按照传的来,一式两用,还是很不错的。

委托构造函数

这个偷懒方法也是丧心病狂,我给个例子吧,就以Sales_data类为例:

class Sales_data
{
public:
    //这是函数一,是一个普通的构造函数
    Sales_data(string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt*price){}

    //接下来就是各种偷懒方法了,注意看
    Sales_data(): Sales_data("", 0, 0){} //函数二是默认构造函数,委托函数一帮忙初始化,也可以认为是调用了函数一
    Sales_data(string s): Sales_data(s, 0, 0){} //函数三接受一个string参数,委托函数一帮忙初始化
    Sales_data(istream &is): Sales_data()
    {
        read(is, *this);
    }
    //函数四复杂些,它先委托函数二,就是默认构造函数,函数二去委托函数一,这些函数执行完成后,再执行函数四的函数体
    //调用read函数读取给定的istream
};

默认构造函数的作用

记住一条好习惯:如果定义了其他的构造函数,最好也提供一个默认构造函数,下面来两个没有默认构造函数的经典错误:

class NoDefault
{
public:
    NoDefault(const string &);
    //定义了构造函数,但没有默认构造函数,而且编译器不会生成,这样这个类本身不会有问题,但是用起来很容易出问题
};

struct A
{
    NoDefault a;
};
A a; //你这样看着挺正常,A没有写构造函数,于是编译器会自动生成默认构造函数去初始化成员变量,可惜啊,这个成员变量的
//类型是类类型NoDefault,而这个该死的类类型又没有默认构造函数,还不让编译器自己生成,于是就报错了

//还有一种错误是这样的:
struct B
{
    B(){}
    NoDefault b;
    //b作为类类型成员,在构造函数没有被初始化,于是它会调用默认构造函数初始化,结果该死的NoDefault没有默认构造函数
};
使用默认构造函数
Sales_data obj; //注意不要加括号啊,加了括号就成了函数声明了

隐式的类类型转换

如果构造函数只接受一个实参,那它实际上定义了一种隐式转换机制,什么意思呢,以Sales_data为例,该类有一个只接受一个string的构造函数,所以啊,我们可以把string转换为Sales_data类的对象:

Sales_data b;
string a = "1";
b.combine(a); //这里a被转换为Sales_data对象,编译器创建了一个临时对象

这种只带一个参数的构造函数,我们也把它称为转换构造函数。

但是我们只允许一步类类型转换,举个例子:

item.combine("1");
//这样是错的,因为字符串常量到string是一步,string到类对象时一步,两步不行的
//不过我们可以用显式地写
item.combine(string("1"));
item.combine(Sales_data("1"))
//总之,隐式的只能帮你一步
抑制在构造函数中的隐式转换

这个又是C++牛逼也蛋疼的地方,提出了类类型隐式转换,我又可以阻止它,不得不说给了程序员极大的权力,也是需要极大的责任心的,在转换构造函数(只接受一个实参的构造函数)前加explicit关键字就可以阻止隐式转换,注意,explicit关键字只对类内的转换构造函数有用,而且被声明为explicit的构造函数只能用于直接初始化,不能拷贝初始化:

class Sales_data
{
public:
    Sales_data() = default;
    explicit Sales_data(const string &s): bookNo(s){}
    explicit Sales_data(istream&);
};

Sales_data item;
string b = "1"
item.combine(b); //这样就不行了
explicit Sales_data::Sales_data(istream& is){} //这样也不行,在外面了
Sales_data item1(b); //这样可以
Sales_data item2 = item1; //不行,explicit声明的不能拷贝初始化

//但是,多事C++又说我们还是可以强行转换:
item.combine(Sales_data(b)); //可以
item.combine(static_cats<Sales_data>(cin)); //可以

下面介绍两个特殊的类,也不太常用。

聚合类

聚合类是一个概念,使得用户可以直接访问其成员,它有特殊的初始化语法,那么什么样的类是聚合类呢?满足下面四个条件:

  1. 所有成员都是public
  2. 没有定义任何构造函数
  3. 没有类内初始值
  4. 没有基类,也没有virtual函数(以后介绍) 聚合类举例:
    struct Data
    {
     int val;
     string s;
    };
    //实例化
    Data a = {0, "a"}; //一定要按类内定义的顺序
    

字面值常量类

之前我们学过,constexpr函数的参数和返回类型都得是字面值类型。这回说到了类,就有字面值常量类: 数据成员都是字面值类型的聚合类是字面值常量类
或者
满足以下条件:

  1. 数据成员均为字面值类型
  2. 类至少有一个constexpr构造函数
  3. 有类内初始值的话,该值必须是常量表达式,即便该成员是类类型,这个类也要有自己的constexpr构造函数
  4. 类必须使用析构函数的默认定义,该成员负责销毁对象
constexpr构造函数

之前提过,构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr的,且必须至少有一个constexpr构造函数。 如果不是默认的构造函数,那么constexpr类的构造函数基本就是空函数体,因为以下两点:

  1. constexpr函数的要求是唯一可执行语句时return语句(之前说过的规定,忘了自己回去翻)
  2. 构造函数不能包含返回语句 所以就只好函数体为空了。 constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数或者是一条常量表达式。 说了那么多,来举个字面值常量类的例子吧:
class Debug
{
public:
    //带有一个默认实参,它也是默认构造函数哦
    constexpr Debug(bool b = true): hw(b), io(b), other(b){}

private:
    bool hw;
    bool io;
    bool other;
};
#C++工程师#
全部评论
class X { int i; int j; public: X(int val) : j(val), i(j){} }; 这样是错的,对吧? 这个应该不是错的,亲测可以的,只不过 i 的值是个垃圾值,语法上是没问题的
点赞 回复 分享
发布于 2017-01-19 17:50
c++真的好烦啊,最后explicit开始就觉得烦了,这个实际常用吗
点赞 回复 分享
发布于 2019-09-13 22:06
点赞 回复 分享
发布于 2017-01-19 17:56
👍👍👍
点赞 回复 分享
发布于 2016-12-12 20:28

相关推荐

头像
10-13 18:10
已编辑
东南大学 C++
。收拾收拾心情下一家吧————————————————10.12更新上面不知道怎么的,每次在手机上编辑都会只有最后一行才会显示。原本不想写凉经的,太伤感情了,但过了一天想了想,凉经的拿起来好好整理,就像象棋一样,你进步最快的时候不是你赢棋的时候,而是在输棋的时候。那废话不多说,就做个复盘吧。一面:1,经典自我介绍2,项目盘问,没啥好说的,感觉问的不是很多3,八股问的比较奇怪,他会深挖性地问一些,比如,我知道MMU,那你知不知道QMMU(记得是这个,总之就是MMU前面加一个字母)4,知不知道slab内存分配器-&gt;这个我清楚5,知不知道排序算法,排序算法一般怎么用6,写一道力扣的,最长回文子串反问:1,工作内容2,工作强度3,关于友商的问题-&gt;后面这个问题问HR去了,和中兴有关,数通这个行业和友商相关的不要提,这个行业和别的行业不同,别的行业干同一行的都是竞争关系,数通这个行业的不同企业的关系比较微妙。特别细节的问题我确实不知道,但一面没挂我。接下来是我被挂的二面,先说说我挂在哪里,技术性问题我应该没啥问题,主要是一些解决问题思路上的回答,一方面是这方面我准备的不多,另一方面是这个面试写的是“专业面试二面”,但是感觉问的问题都是一些主管面/综合面才会问的问题,就是不问技术问方法论。我以前形成的思维定式就是专业面会就是会,不会就直说不会,但事实上如果问到方法论性质的问题的话得扯一下皮,不能按照上面这个模式。刚到位置上就看到面试官叹了一口气,有一些不详的预感。我是下午1点45左右面的。1,经典自我介绍2,你是怎么完成这个项目的,分成几个步骤。我大致说了一下。你有没有觉得你的步骤里面缺了一些什么,(这里已经在引导我往他想的那个方向走了),比如你一个人的能力永远是不够的,,,我们平时会有一些组内的会议来沟通我们的所思所想。。。。3,你在项目中遇到的最困难的地方在什么方面4,说一下你知道的TCP/IP协议网络模型中的网络层有关的协议......5,接着4问,你觉得现在的socket有什么样的缺点,有什么样的优化方向?6,中间手撕了一道很简单的快慢指针的问题。大概是在链表的倒数第N个位置插入一个节点。————————————————————————————————————10.13晚更新补充一下一面说的一些奇怪的概念:1,提到了RPC2,提到了fu(第四声)拷贝,我当时说我只知道零拷贝,知道mmap,然后他说mmap是其中的一种方式,然后他问我知不知道DPDK,我说不知道,他说这个是一个高性能的拷贝方式3,MMU这个前面加了一个什么字母我这里没记,别问我了4,后面还提到了LTU,VFIO,孩子真的不会。
走呀走:华子二面可能会有场景题的,是有些开放性的问题了
点赞 评论 收藏
分享
从小父母离异家里没人管,靠着心里的不安和学校的环境也算是坚持到了学有所成的地步。到了大学环境开始松散不知道该做什么,只觉得在不挂科的基础上能往上考多少就考多少,等到秋招来临才发现自己有多么幼稚无能,今年九月份初才发现自己原来连一个求职的方向都没有。因为之前做过前后端一体的课设,算是有过了解,而对于其他岗位连做什么都不知道,因此这一个半个月在越来越焦虑的同时埋头苦学,事到如今想要活下去我似乎只能走前端这条路了,9月初先是靠着虚假夸大能力的简历得到一些笔试来确定了考察的方向,有一个大厂的无笔试面试最终是拒绝了没有勇气去面对。然后在这个基础上埋头苦学,如今也算是搭好了自己前端学习的框架和思考的瞄,可以逐渐给自己扩展新的知识和能力了,但这并不是一件多好的事儿,因为我发现学的越多越焦虑,学的越多便越无力。因为我感觉我如今努力学习的知识都是竞争对手们早就掌握了的东西,我如今困惑追求答案的难题早就被别人解决。别人早就能得心应手地做出项目而我连思考都会卡壳,看着别人的笔试和面经上那些闻所未闻的题目,我才知道别人到底有多强而我有多幼稚,我什么时候才能达到别人那种堪称熟练的能力呢?而且网上的焦虑越多越多,即便是真有这么高的能力最后也大概落得一个低薪打工人的下场,我真的感到迷茫。秋招都快结束了,而我还在继续痛苦的学习之旅,这些天找前端面试发现似乎问的有些简单跟网上搜到的内容不符(可能因为并不是大厂),我是不是本来就没打算被招所以别人懒得细问呢?我不知道,我只能继续总结下去学习下去,不管如何我都要活下去,如果我能早一些准备就好了,如果暑假能意识到现在这个情况就好了,可惜没有如果。种下一棵树的最好时间是十年前,其次是现在,虽然我相信自己的学习能力,但已经错过了最好的时机,只能在焦虑与痛苦中每天坚持学下去。目前的路还有很长很长,先去把typescript看了,再去巩固vue3的基础,再去练习elementui的使用,如果这能找到实习的话就好了。接下来呢?去学uniapp和小程序,不管如何我都要对得起曾经努力的自己。即便我们都感到痛苦,但我心中还是希望我们都能靠自己的努力来获取自己想要的幸福。
紧张的牛牛等一个of...:在担心什么呢,有一手985的学历在,就算是小厂别人都会要的,咱们双非的人更多,多少还在沉沦的,怕什么了
一句话证明你在找工作
点赞 评论 收藏
分享
评论
6
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务