constキーワードの理解

11028 ワード

constキーワードのc/c++での役割は、修飾された変数を可変に限定することであり、初期化後は変更できません.const修飾を変更しようとする変数の動作は、コンパイルエラーを引き起こします.constの修飾規則は複雑であり,以下のコードを参照して理解できる.
// NOTE:           ,      ,                    。
const int a;        //    ,     'a'        
const int b = 15;   //  
int c;

const int *p;       //  , p    const int    , p     ,     const int   。
p = &b;             //  
int const *q;       //  , q q    ,      
q = &c;             //  , q const int   ,      int   ,        q  c  。
*q = 12;            //    ,     p        。

int * const x;      //    ,     'x'        ,x     ,  int    ,          。
int * const y = &b; //    , y          int ,  const int  ,          y    b  。
int * const z = &c; //  , z      c     ,    z    。

const int * const u = &b;   //  ,     u      b,    u  ,     u  b  。
constがポインタを修飾しているのかタイプを修飾しているのか分からない場合は、const*の位置から判断することができます.
  • const*の左側にある場合、修飾はタイプであり、const int * pおよびint const * qのうちconstはいずれもintを修飾するために用いる.
  • const*の右側にある場合、修飾はポインタであり、int * const yconstは修飾ポインタである.
  • const修飾ポインタによってconstキーワードの意味が理解され、constキーワードがどのような内容を修飾できるかを見てみましょう.constは変数を修飾し、参照変数を修飾し、関数のパラメータを修飾し、関数の戻り値を修飾し、クラスのメンバー変数を修飾し、クラスのメンバー関数を修飾します.
    const修飾変数
    int a = 15;
    const int b = a;    //  ,    a     b,b       。
    const int c = b;    //  ,      b     c, c        。
    int d = c;          //  ,      c     d.
    
    b = c;              //    ,        b
    c = d;              //    ,        c
    

    const修飾参照
    int a = 27;
    const int &b = a;   //  ,b    const int &,          :b a     ,      b  a,      b  a。
    int &c = a;         //  ,c    int &,         :c a   (  ),    c  a,     c  a。
    c = 12;             //  ,a    12,  b    12
    b = c;              //    ,b     ,     。
    
    const int d = c;    //  ,d const int   ,    12,
    const int &e = d;   //  ,e d   。
    int &f = d;         //    ,    const int   int &     。 
                        //  f             ,  d        ,  d f  ,    d      f  ,       。
    
    int &g;             //    ,         
    const int &h;       //    ,         
    

    const修飾関数のパラメータ
    関数のパラメータを理解することは,関数呼び出しを把握する上で非常に重要な一環である.最も重要な概念は、関数呼び出しで使用される値で渡すか、参照で渡すかを理解することです.
    値別転送(by value)/参照別転送(by reference)
  • 値で渡される関数パラメータは、元のオブジェクトのコピーであり、関数パラメータの操作は、入力パラメータとは無関係である.
  • 参照によって伝達される関数パラメータは、伝達されたオブジェクトの別名であり、関数パラメータに対する操作は、伝達パラメータに対する操作である.次の例は、値による伝達で予想される結果が得られなかった例です.
  • #include 
    void swap(int a, int b)  //a、b   
    {
        int temp = a;
        a = b;
        b = temp;
    }
    int mian()
    {
        int x = 1;
        int y = 2;
        swap(x,y);      //x、y   
        std::cout << "x = " << x << " y = " << y << std::endl;
    }
    //         ,
    //  x = 1,
    //  y = 2,
    //  a = x,(    a  x       ,    a x     )
    //  b = y,(    b  y       ,    b y     )
    //  temp = a,(temp = 1)
    //  a = b,(a = 2)
    //  b = temp,(b = 1)
    //        ,a b      ,   。
    //     x = 1, y = 2,
    

    以上のコードが予想された結果を得られなかったのは,値で伝達された後,パラメータが実パラメータのコピーであり,パラメータの修正と実パラメータには何の関係もないためである.したがって、上記のコードはaおよびbの値を交換しただけであり、xおよびyの値は変化しなかった.参照で渡すように変更します.次のように変更します.
    void swap(int &a, int &b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    

    const修飾関数パラメータ
    値別転送と参照別転送の違いを理解したら、constキーワードが関数パラメータを修飾するときにコードに与える影響を見てみましょう.次に、参照によって渡される例を示します.
    int abs_good(const int& x)
    {
        if (x < 0) { return -x;}
        else { return x;}
    }
    int abs_bad(int& x)
    {
        if (x < 0) { return -x;}
        else { return x;}
    }
    //            。
    int main()
    {
        int a = -15;
        const int b = -7;
    
        int c = abs_good(a);    //c = 15;
        int d = abs_bad(a);     //d = 15;
    
        int e = abs_good(b);    //e = 7;
        int f = abs_bad(b);     //    ,  b const int,     。
                               //  abs_bad             b  ,
                              //      abs_bad    b  (            ,          ),       。
    }
    

    上記の参照伝達例から分かるように、関数のパラメータを変更する必要がない場合、パラメータを宣言する際に説明すると、呼び出しの最大互換性が保証され、int型パラメータとconst int型パラメータが呼び出されることができる.同時に、コードの可読性とセキュリティがより保障されています.
    次に、値別に渡す例を示します.
    int abs_good(const int x)
    {
        if (x < 0) { return -x;}
        else { return x;}
    }
    int abs_normal(int x)
    {
        if (x < 0) { return -x;}
        else { return x;}
    }
    //            。
    int main()
    {
        int a = -15;
        const int b = -7;
    
        int c = abs_good(a);    //c = 15;
        int d = abs_normal(a);     //d = 15;
    
        int e = abs_good(b);    //e = 7;
        int f = abs_normal(b);  //f = 7;   ,abs_normal    x b   , x    b       
    }
    

    上記の値による伝達の例から,値による伝達過程において,パラメータと実パラメータはそれぞれ独立していることが分かる.constキーワードを値で渡す必要はなく、関数呼び出しの互換性にも影響しません.しかし、パラメータを修正する必要がない場合は、constキーワードを追加し、コードの可読性を高めると同時に、コンパイラにチェックしてもらい、無意識に修正していないことを確認することをお勧めします.関数パラメータがポインタの場合、参照による伝達と同様に、以下の簡単な例では、自己体験します.
    #include 
    void swap(int* pa, int* pb)
    {
        int temp = *pa;
        *pa = *pb;
        *pb = temp;
    }
    
    int abs_good(const int * x)
    {
        if (*x < 0) { return -*x; }
        else { return *x; }
    }
    
    int main()
    {
        int a = 12;
        int b = -22;
        swap(&a, &b);
        const int c = abs_good(&a);
        std::cout << "a = " << a << " b = " << b << " c = " << c << std::endl;
    }
    

    const修飾関数戻り値
    関数の戻り値が値によって渡される場合、constのキーワード修飾を使用しても効果はありません.関数の戻り値が参照によって渡される場合にのみ、constのキーワードを使用する必要があります.ブレンドがc/c++でプログラミングされている場合、よく遭遇するシーンのタイプは次のとおりです.
    #include 
    #include 
    //         
    void print_message_bad(char *msg)
    {
        printf("Message[%s]
    ", msg); } int main() { std::string Info = "hello world"; print_message_bad(Info.c_str()); // ,"const char *" "char *" return 0; }

    以上のコードコンパイルが失敗したのは、std::stringのメンバー関数c_str()の戻り値がconst char *タイプであり、char *タイプのパラメータに渡すことができないためである.その理由は、std::stringのタイプでは、ポインタが指すメモリの内容を直接変更することは禁止されており、ポインタが指すメモリの内容のみを読み取ることができます.std::string型のsize()length()などの関数は、コンテンツ修正時に相応の変更が必要であるため、std::string型で提供されるインタフェースを使用することができ、そうでなければstd::stringの動作の異常を引き起こす.
    #include 
    const int kMessageLen = 64;
    const char* get_message(int line)
    {
        char *p = new char[kMessageLen];
        snprintf(p, kMessageLen, "Message line :[%d]", line);
        return p;
    }
    int main()
    {
        int line = 15;
        const char *msg = get_message(line);    //
        printf("%s
    ", msg); char *msg_error = get_message(line); // , return 0; }

    以上、constが戻りポインタを修飾する場合を説明し、次にconstが戻り参照を修飾する場合を示す.
    const int& get_const_reference(const int & x)
    {
        return x;
    }
    
    int main()
    {
        int a = 15;
        int b = get_const_reference(a);
        const int c = get_const_reference(b);
        //???            ?   ?
        a = 17;
        b = 18;
        c = 19;
        //??? a b c     19 ?
    
        int &d = get_const_reference(a);
        const int &e = get_const_reference(c);
        //???           ?   ?
        return 0;
    }
    

    さて、上記の例では、constキーワードが戻り関数の戻り値を修飾する役割が理解されているはずです.この使用は、クラスのメンバー関数によく使用されます.クラスが内部データにアクセスし、内部データを保護することを望んでいる場合は、constで修飾された関数の戻り値で完了できます.
    const修飾クラスメンバー変数
    #include 
    #include 
    class Student{
    public:
        explicit Student(std::string name, int age): age_(age), name_(name)
        {}
    
        ~Student() {};
    
        int get_age() { return age_; }
        const std::string& get_name() { return name_; }
    
    private:
        int age_;
        const std::string name_;    //    ??? const      ?
    };
    
    int main()
    {
        Student xiaoMing("xiaoMing", 8);
        Student xiaoHong("xiaoHong", 7);
        std::cout << "name: " << xiaoMing.get_name() << " age: " << xiaoMing.get_age() << std::endl;
        std::cout << "name: " << xiaoHong.get_name() << " age: " << xiaoHong.get_age() << std::endl;
        return 0;
    }
    
    constクラスのメンバー変数を修飾する場合、宣言時に初期化する必要はなく、コンストラクション関数で初期化する必要があります.このルールは、クラスオブジェクトがインスタンス化(構造/存在)されている場合にのみ、constで修飾されたクラスのメンバー変数がインスタンス化(構造/存在)を開始することを理解しやすい.実際には、c++11以降でクラスのメンバー変数に宣言時に値を割り当てることができます.この値はデフォルト値として使用できます.コンストラクション関数に対応する値が指定されていない場合に使用されます.詳細:「C++11の詳細理解」を参照して、次のコードを参照してください.
    // NOTE:    c++98      
    #include 
    #include 
    class Student{
    public:
        Student() {}
        Student(std::string name, int age): age_(age), name_(name)
        {}
    
        ~Student() {};
    
        int get_age() { return age_; }
        const std::string& get_name() { return name_; }
    
    private:
        int age_ = 165;
        const std::string name_ = "gua";  
    };
    
    int main()
    {
        Student xiaoMing("xiaoMing", 8);
        Student oldMan;
        std::cout << "name: " << xiaoMing.get_name() << " age: " << xiaoMing.get_age() << std::endl;
        std::cout << "name: " << oldMan.get_name() << " age: " << oldMan.get_age() << std::endl;
        return 0;
    }
    

    次はmuduoネットワークライブラリのTimestampのコードセグメントです.static const int kMicroSecondsPerSecond = 1000 * 1000;に注意してください.
    class Timestamp :
    {
     public:
      Timestamp()
        : microSecondsSinceEpoch_(0)
      {
      }
    
      // default copy/assignment/dtor are Okay
      
      string toString() const;
      string toFormattedString(bool showMicroseconds = true) const;
    
      bool valid() const { return microSecondsSinceEpoch_ > 0; }
    
      static const int kMicroSecondsPerSecond = 1000 * 1000;    // c++98 c++11        。   ?
    
     private:
      int64_t microSecondsSinceEpoch_;
    };
    
    class A {
        const static int num1; //   
        const static int num2 = 13; //       
    };
    const int A::num1 = 12; //       ,       
    const int num2;  //   
    

    const修飾クラスメンバー関数const修飾クラスのメンバー関数クラスを設計する際、重要な原則は、データメンバー関数を変更しない場合にconstを追加し、データメンバーを変更するメンバー関数にconstを追加しないことです.したがって、constキーワードは、メンバー関数の動作をより明確に限定します.
    (1)constで修飾されたメンバー関数(関数の前やパラメータテーブル内ではなく、constが関数パラメータテーブルの後ろに置かれていることを指す)は、データメンバーのみを読み取ることができ、データメンバーを変更することはできない.const修飾メンバー関数はなく、データ・メンバーは読み書き可能である.(2)それ以外にクラスのメンバー関数にconstを付けるメリットは何ですか?すなわち、定数オブジェクト(すなわち、constで修飾されたクラスオブジェクト)は、constメンバー関数を呼び出すことができ、constで修飾されていない関数を呼び出すことができない.詳細アクセス可能:C++のconstクラスメンバー変数、constメンバー関数