深入探討const關(guān)鍵字

這一次我準(zhǔn)備用一個(gè)實(shí)際的例子來更加深入的探討const關(guān)鍵字,可能這個(gè)例子不是特別的符合要求。
這個(gè)例子的需求是這樣的:
我們需要一個(gè)畫折線的對象,這個(gè)對象可以添加新的點(diǎn),也可以刪除的點(diǎn),為了方便實(shí)踐,我們規(guī)定這個(gè)折線最多由20個(gè)點(diǎn)組成,并且可以輸出當(dāng)前點(diǎn)的個(gè)數(shù)和所有點(diǎn)的信息。
首先我們來分析一下需求:

  • 創(chuàng)建這樣的一個(gè)簡單對象,我們需要一個(gè)Point類和Line類,而且他們的關(guān)系屬于has-a,所以應(yīng)該用組合的方式實(shí)現(xiàn)。
  • Point類中需要存儲(chǔ)點(diǎn)的坐標(biāo)信息,并且可以修改和獲取這些信息。
  • Line類中需要存儲(chǔ)點(diǎn)的信息,并且可以修改和獲取這些信息,以及獲取總共的點(diǎn)的數(shù)量,刪除的點(diǎn)的功能。

首先看看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表示點(diǎn)的坐標(biāo),isInit表示這個(gè)點(diǎn)是否被初始化。
  • 有參構(gòu)造函數(shù)中和updateXY方法中,參數(shù)我們使用的const reference to int的類型,這樣做的原因是因?yàn)槲覀冎恍枰獋鬟f這個(gè)值進(jìn)入這個(gè)方法。
  • 而在get以及display方法中,我們只需要傳遞某一個(gè)值出去,并不需要函數(shù)去修改某些變量,所以我們將方法標(biāo)記為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);
}

實(shí)現(xiàn)很簡單,但是這里我需要談?wù)劻硗庖粋€(gè)話題,關(guān)于初始化的問題。
我相信很多初學(xué)者都是這樣實(shí)現(xiàn)第一個(gè)無參構(gòu)造函數(shù)的:

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

這樣的做法叫做賦值,而非初始化,C++有一條這樣的規(guī)定,在成員變量的初始化動(dòng)作發(fā)生在進(jìn)入構(gòu)造函數(shù)本體之前。換句話說,你應(yīng)該使用參數(shù)列表去初始化所有的成員變量,就像示例代碼中所實(shí)現(xiàn)的一樣。
這樣做符合C++規(guī)定并在效率也會(huì)更高。

接下來回歸正題,我們來細(xì)談Line類的實(shí)現(xiàn),我們先談?wù)劤蓡T變量:

class Line
{
private:
    Point pointArray[20];
    int count;
    bool countIsValid;
}
  • 我們需要一個(gè)長度為20的Point的數(shù)組,這個(gè)沒什么好多說的。
  • count變量用來表示當(dāng)前Line中所存儲(chǔ)的點(diǎn)的數(shù)量,countIsValid用來表示點(diǎn)的數(shù)量是否發(fā)生的變化

接下來我們來看看怎么實(shí)現(xiàn)所有需要的函數(shù),首先是構(gòu)造函數(shù):

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

構(gòu)造函數(shù)的實(shí)現(xiàn)方法我上面說過了,還是請記住構(gòu)造函數(shù),并不是賦值函數(shù)。
接下來是添加點(diǎn)的函數(shù)實(shí)現(xiàn):

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

參數(shù)我們只是需要值就行,而不是需要對象,所以我們使用const reference類型進(jìn)行傳遞就行了,函數(shù)中我們遍歷到我們第一個(gè)沒有使用的空間時(shí),便使用這一空間存儲(chǔ)當(dāng)前傳入的數(shù)據(jù)。

這段實(shí)現(xiàn)很不合實(shí)際,但是我只想想為后面最關(guān)鍵的部分做鋪墊而已,大家就不要吐槽了。

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;
        }
    }
}

根據(jù)下標(biāo)刪除一個(gè)點(diǎn),然后將后面的點(diǎn)前向靠攏的一個(gè)操作。沒有什么特別的地方。
接下來就這一次的重點(diǎn),重載[]:

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

第一個(gè)if語句用來表示下標(biāo)超出的范圍,然后拋出一個(gè)錯(cuò)誤,第二個(gè)下標(biāo)用來檢測當(dāng)前點(diǎn)是不是有有效數(shù)據(jù),如果沒有就會(huì)拋出一個(gè)錯(cuò)誤,錯(cuò)誤處理實(shí)現(xiàn)的很簡單,因?yàn)檫@不是重點(diǎn)。
這里我們返回的是一個(gè)const Point&類型的值并且函數(shù)也為const類型,因?yàn)槲覀儾幌M瘮?shù)修改任何值,只是用來返回一個(gè)值。
然后我們發(fā)現(xiàn)返回的這個(gè)值并不能被操作,這個(gè)不是我們想要的,于是我們需要再次重載一個(gè)返回Point&類型的函數(shù),但是如果我們的類特別復(fù)雜,前面的檢查方法十分的復(fù)雜,我們再這樣寫一遍就特別的麻煩,對了我們有粘貼復(fù)制,但整體代碼會(huì)顯得很長,或與又有人說,可以把檢查方法寫一成一個(gè)函數(shù),但是并有這樣的必要,因?yàn)檫@樣的函數(shù)并非廣泛使用,下面我就來說一種特別的方法:

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

首先我們將本對象轉(zhuǎn)化為一個(gè)const Line&的對象,因?yàn)槲覀冎幌胧褂弥挡⒉幌胄薷膕tatic_cast<const Line&>(this)幫助我們完成了這樣的想法,然后我們使用了static_cast<const Line&>(this)[_index]返回的const Point&類型的值,但是我們需要去掉const關(guān)鍵字,這里使用了
const_cast<Point&>()這個(gè)方法,它可以幫我們?nèi)サ鬰onst關(guān)鍵字。
這樣我們就可以做這樣的事情了:

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

接下來我們來說說怎么實(shí)現(xiàn)返回當(dāng)前點(diǎn)的總和,上面那段爛代碼也是為了這段代碼出現(xiàn)的必要,因?yàn)樗嬖V大家一個(gè)重要的關(guān)鍵字。

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

作為一個(gè)獲取某個(gè)值的函數(shù),它同樣被設(shè)置成為了const類型,但是我們在這個(gè)函數(shù)中改變了count和countIsValid的量,為了完成這個(gè)方法,我們需要修改一個(gè)成員變量的類型:

    mutable int count;
    mutable bool countIsValid;

mutable這個(gè)關(guān)鍵字,允許這個(gè)變量在任何地方都可以被修改,即使它在const函數(shù)內(nèi)。這樣大家都懂了吧。

好了其他的函數(shù)都不重要了,代碼怎么實(shí)現(xiàn)的也不重要,關(guān)鍵是他們使用的方法,如果你已經(jīng)完全掌握了上面的方法,現(xiàn)在你可以自己寫一個(gè)String類。const能幫助你完成安全性的工作。

下一次我們會(huì)分享一些構(gòu)造/析構(gòu)/賦值運(yùn)算相關(guān)的內(nèi)容。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容