深入探讨const关键字

这一次我准备用一个实际的例子来更加深入的探讨const关键字,可能这个例子不是特别的符合要求。
这个例子的需求是这样的:
我们需要一个画折线的对象,这个对象可以添加新的点,也可以删除的点,为了方便实践,我们规定这个折线最多由20个点组成,并且可以输出当前点的个数和所有点的信息。
首先我们来分析一下需求:

  • 创建这样的一个简单对象,我们需要一个Point类和Line类,而且他们的关系属于has-a,所以应该用组合的方式实现。
  • Point类中需要存储点的坐标信息,并且可以修改和获取这些信息。
  • Line类中需要存储点的信息,并且可以修改和获取这些信息,以及获取总共的点的数量,删除的点的功能。

首先看看Point.h

class Point
{
private:
    int x;
    int y;
    bool isInit;
public:
    Point();
    Point(const int& _x,const int& _y);
    void updateXY(const int& _x,const int& _y);
    int getX() const {return x;}
    int getY() const {return y;}
    bool getIsInit() const {return isInit;}
    void display() const;
};
  • x,y表示点的坐标,isInit表示这个点是否被初始化。
  • 有参构造函数中和updateXY方法中,参数我们使用的const reference to int的类型,这样做的原因是因为我们只需要传递这个值进入这个方法。
  • 而在get以及display方法中,我们只需要传递某一个值出去,并不需要函数去修改某些变量,所以我们将方法标记为const。

接下来我们在看看Point.cpp

Point::Point():x(0),y(0),isInit(false)
{
    
}

Point::Point(const int& _x,const int& _y):x(_x),y(_y),isInit(true)
{
    
}

void Point::updateXY(const int &_x, const int &_y)
{
    x = _x;
    y = _y;
}

void Point::display() const
{
    printf("(%d,%d)\n",x,y);
}

实现很简单,但是这里我需要谈谈另外一个话题,关于初始化的问题。
我相信很多初学者都是这样实现第一个无参构造函数的:

Point::Point()
{
    x = 0;
    y = 0;
    isInit = false;
}

这样的做法叫做赋值,而非初始化,C++有一条这样的规定,在成员变量的初始化动作发生在进入构造函数本体之前。换句话说,你应该使用参数列表去初始化所有的成员变量,就像示例代码中所实现的一样。
这样做符合C++规定并在效率也会更高。

接下来回归正题,我们来细谈Line类的实现,我们先谈谈成员变量:

class Line
{
private:
    Point pointArray[20];
    int count;
    bool countIsValid;
}
  • 我们需要一个长度为20的Point的数组,这个没什么好多说的。
  • count变量用来表示当前Line中所存储的点的数量,countIsValid用来表示点的数量是否发生的变化

接下来我们来看看怎么实现所有需要的函数,首先是构造函数:

Line::Line():count(0),countIsValid(true)
{
}

构造函数的实现方法我上面说过了,还是请记住构造函数,并不是赋值函数。
接下来是添加点的函数实现:

void Line::addPoint(const Point &_new)
{
    for(int i = 0 ; i < 20 ; i++)
    {
        if(!pointArray[i].getIsInit())
        {
            pointArray[i] = _new;
            break;
        }
    }
    countIsValid = false;
}

参数我们只是需要值就行,而不是需要对象,所以我们使用const reference类型进行传递就行了,函数中我们遍历到我们第一个没有使用的空间时,便使用这一空间存储当前传入的数据。

这段实现很不合实际,但是我只想想为后面最关键的部分做铺垫而已,大家就不要吐槽了。

void Line::deletePointByIndex(const int &_index)
{
    for(int i = _index;i<=20;i++)
    {
        if(pointArray[i+1].getIsInit())
        {
            pointArray[i] = pointArray[i+1];
            pointArray[i+1] = Point();
        }
        else
        {
            break;
        }
    }
}

根据下标删除一个点,然后将后面的点前向靠拢的一个操作。没有什么特别的地方。
接下来就这一次的重点,重载[]:

const Point& Line::operator[] (const int& _index) const
{
    if(_index >= 20)
    {
        throw "Error";
    }
    if(!pointArray[_index].getIsInit())
    {
        throw "Error";
    }
    return pointArray[_index];
}

第一个if语句用来表示下标超出的范围,然后抛出一个错误,第二个下标用来检测当前点是不是有有效数据,如果没有就会抛出一个错误,错误处理实现的很简单,因为这不是重点。
这里我们返回的是一个const Point&类型的值并且函数也为const类型,因为我们不希望函数修改任何值,只是用来返回一个值。
然后我们发现返回的这个值并不能被操作,这个不是我们想要的,于是我们需要再次重载一个返回Point&类型的函数,但是如果我们的类特别复杂,前面的检查方法十分的复杂,我们再这样写一遍就特别的麻烦,对了我们有粘贴复制,但整体代码会显得很长,或与又有人说,可以把检查方法写一成一个函数,但是并有这样的必要,因为这样的函数并非广泛使用,下面我就来说一种特别的方法:

Point& Line::operator[] (const int& _index)
{
    return const_cast<Point&>(static_cast<const Line&>(*this)[_index]);
}

首先我们将本对象转化为一个const Line&的对象,因为我们只想使用值并不想修改static_cast<const Line&>(this)帮助我们完成了这样的想法,然后我们使用了static_cast<const Line&>(this)[_index]返回的const Point&类型的值,但是我们需要去掉const关键字,这里使用了
const_cast<Point&>()这个方法,它可以帮我们去掉const关键字。
这样我们就可以做这样的事情了:

Line l1;
l1.addPoint(Point(3,3));
l1[0].display();

接下来我们来说说怎么实现返回当前点的总和,上面那段烂代码也是为了这段代码出现的必要,因为它要告诉大家一个重要的关键字。

int Line::getPointCount() const
{
    if(!countIsValid)
    {
        count = 0;
        for(int i = 0 ; i < 20 ; i++)
        {
            if(pointArray[i].getIsInit())
            {
                count++;
            }
        }
        countIsValid = true;
    }
    return count;
}

作为一个获取某个值的函数,它同样被设置成为了const类型,但是我们在这个函数中改变了count和countIsValid的量,为了完成这个方法,我们需要修改一个成员变量的类型:

    mutable int count;
    mutable bool countIsValid;

mutable这个关键字,允许这个变量在任何地方都可以被修改,即使它在const函数内。这样大家都懂了吧。

好了其他的函数都不重要了,代码怎么实现的也不重要,关键是他们使用的方法,如果你已经完全掌握了上面的方法,现在你可以自己写一个String类。const能帮助你完成安全性的工作。

下一次我们会分享一些构造/析构/赋值运算相关的内容。

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

推荐阅读更多精彩内容