コンテンツにスキップ

最終更新:2026-10-12

C++ const 定数と修飾

著者について

WeChat公式アカウント:

wechat

1.constの意味

const型とは、型修飾子**const**を使って宣言された型のことで、const型の変数やオブジェクトの値は更新できません。

2.constの役割

  • 定数を定義できます
const int a=100;
  • 型チェック

    • const定数と#defineマクロ定数による定数の違い:

      const定数は型を持ち、コンパイラが安全チェックを行えます;#defineマクロ定義にはデータ型がなく、単純な文字列置換にすぎないため、安全チェックができません。ここの問題を指摘してくださった2人の皆さんに感謝します。参照:issue

    • const定数はすべての型をサポートします。

    • その他の場合、それは単なる const 修飾された変数であり、定数と混同しないでください。
  • 変更を防止し、保護の役割を果たし、プログラムの堅牢性を高めます

void f(const int i){
    i++; // error!
}
  • 空間を節約し、不要なメモリ割り当てを避けることができます

    • constによる定数定義は、アセンブリの観点から見ると、対応するメモリアドレスを与えるだけであり、#defineのように即値を与えるわけではありません。
    • constで定義された定数はプログラムの実行中に1つのコピーしか持ちませんが、#defineで定義された定数はメモリ上にいくつものコピーを持ちます。

3.constオブジェクトはデフォルトでファイルローカル

注意:非const変数はデフォルトでexternです。const変数を他のファイルからアクセスできるようにするには、そのファイル中で明示的にexternと指定しなければなりません。

constで修飾されていない変数の、異なるファイル間でのアクセス

// file1.cpp
int ext;
// file2.cpp
#include<iostream>

extern int ext;
int main(){
    std::cout<<(ext+10)<<std::endl;
}

const定数の、異なるファイル間でのアクセス

// extern_file1.cpp
extern const int ext=12;
// extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
    std::cout<<ext<<std::endl;
}

まとめ:
constで修飾されていない変数にはexternの明示的宣言が不要なことがわかります!一方、const定数はexternの明示的宣言が必要であり、かつ初期化も必要です!定数は定義後に変更できないため、定義時に初期化しなければなりません。

4.定数の定義

const int b = 10;
b = 0; // error: assignment of read-only variable ‘b’
const string s = "helloworld";
const int i,j=0 // error: uninitialized const ‘i’

上記には2つのエラーがあります: + bは定数であり、変更できません! + iは定数であり、初期化しなければなりません!(定数は定義後に変更できないため、定義時に初期化する必要があります。)

5.ポインタとconst

ポインタに関連するconstには4種類あります:

const char * a; // 指向const对象的指针或者说指向常量的指针。
char const * a; // 同上
char * const a; // 指向类型对象的const指针。或者说常指针、const指针。
const char * const a; // 指向const对象的const指针。

まとめ:
*const*が*の左側にある場合、そのconstはポインタが指す変数を修飾します。すなわちポインタの指す先が定数です;
constが*の右側にある場合、*const*はポインタ自体を修飾します。すなわちポインタ自体が定数です。

もうひとつの読み解き方
英語を右から左へ読み、toを区切りとします。toより前はポインタの特性の説明、toより後は指す対象の特性の説明です

const char * p; //p is a pointer to const char
char const * p; //同上
char * const p; //p is a const pointer to char
const char * const p; //p is a const pointer to const char
ポインタにconstの特性が付くと、ポインタは指すアドレスを変更できなくなります
指す対象の特性がcharであれば、内容はポインタを通して変更できます。例: *char='y';
指す対象の特性がconst charであれば、内容はポインタを通して変更できません

具体的な使用方法は以下の通りです:

(1) 定数を指すポインタ

const int *ptr;
*ptr = 10; // error

ptrはint型のconstオブジェクトを指すポインタです。constが修飾しているのはint型、すなわちptrが指すオブジェクトの型であって、ptr自体ではありません。したがってptrは初期値を与えなくてもかまいません。しかし、ptrを通して指すオブジェクトの値を変更することはできません。

また、void*ポインタでconstオブジェクトのアドレスを保存することもできず、constオブジェクトのアドレスを保存するにはconst void*型のポインタを使わなければなりません。

const int p = 10;
const void * vp = &p;
void *vp = &p; // error

もうひとつの重要な点は、**非constオブジェクトのアドレスを、定数を指すポインタに代入することは許される**ということです。

非constオブジェクトのアドレスを、constオブジェクトへのポインタに代入します:

const int *ptr;
int val = 3;
ptr = &val; // ok

ptrポインタを通してvalの値を変更することはできません。たとえそれが指しているのが非constオブジェクトであってもです!

定数を指すポインタを使って元のオブジェクトを変更することはできませんが、そのポインタが非constオブジェクトを指している場合は、他の方法で指しているオブジェクトを変更できます。constポインタが指す値を変更することは可能ですが、constオブジェクトへのポインタを通して行うことはできません!以下のように変更します:

int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;

まとめ:
1.定数を指すポインタについては、ポインタを通してオブジェクトの値を変更できません。
2.void*ポインタでconstオブジェクトのアドレスを保存することはできず、constオブジェクトのアドレスを保存するにはconst void*型のポインタを使わなければなりません。
3.非constオブジェクトのアドレスをconstオブジェクトへのポインタに代入することは許されます。ポインタが指すオブジェクトの値を変更したい場合は、他の方法で変更しなければならず、現在のポインタを通して直接変更することはできません。

(2) 定数ポインタ(constポインタ)

constポインタは初期化しなければならず、constポインタが指す値は変更できますが、指す先は変更できません。

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    ptr = &num1; // error! const指针不能修改指向!
    cout<<*ptr<<endl;
}

このコードはコンパイルエラーになります:constポインタは指す先を変更できません。

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    *ptr = 1;
    cout<<*ptr<<endl;
}

このコードは何も起こらず、正常に1を出力します。

最後に、const定数のアドレスをptrに代入すると、ptrが指しているのは変数であってconst定数ではないため、エラーが発生します。const int* -> int *というエラーです!

#include<iostream>
using namespace std;
int main(){
    const int num=0;
    int * const ptr=&num; // error! const int* -> int*
    cout<<*ptr<<endl;
}

上記を const int *ptr または const int *const ptr に変えれば、どちらも正常に動作します!

(3)定数を指す定数ポインタ

前の2つの場合を理解すれば、以下の場合も理解しやすいでしょう:

const int p = 3;
const int * const ptr = &p; 

ptrはconstポインタであり、int型のconstオブジェクトを指しています。

6.関数でのconstの使用

constが関数の戻り値を修飾

これはconstが通常の変数やポインタを修飾する場合と基本的に同じ意味です:

(1)const int

const int func1();

これ自体には意味がありません。戻り値はもともと他の変数に代入されるものだからです!

(2)const int*

const int* func2();

ポインタが指す内容は不変です。

(3)int *const

int *const func2();

ポインタ自体は不変です。

constが関数のパラメータを修飾

(1)渡されてきたパラメータおよびポインタ自体が関数内で不変である場合、意味がありません!

void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变

パラメータは関数本体内で変更できないことを示しますが、ここでは何の意味もありません。var自体が仮引数であり、関数内で変化しないからです。渡される仮引数がポインタである場合も同様です。

入力パラメータが「値渡し」を採用している場合、関数が自動的にテンポラリ変数を生成してそのパラメータを複製するため、その入力パラメータはもともと保護する必要がなく、constを付ける必要はありません。

(2)パラメータポインタが指す内容が定数であり変更不可

void StringCopy(char *dst, const char *src);

ここでsrcは入力パラメータ、dstは出力パラメータです。srcにconstを付けると、関数本体内の文がsrcの内容を変更しようとしたとき、コンパイラがエラーを指摘します。これがconstを付ける効果のひとつです。

(3)パラメータが参照の場合。効率を高めると同時に変更を防ぐためです。

void func(const A &a)

非内部データ型のパラメータにとって、void func(A a) のように宣言された関数は効率が低くなる運命にあります。関数本体内でA型のテンポラリオブジェクトを生成してパラメータaを複製する必要があり、テンポラリオブジェクトの構築・複製・破棄の過程すべてに時間がかかるためです。

効率を上げるには、関数宣言をvoid func(A &a)に変更できます。「参照渡し」はパラメータの別名を借りるだけで、テンポラリオブジェクトを生成する必要がないからです。

しかし関数void func(A &a)には欠点があります:

「参照渡し」ではパラメータaが変更される可能性があり、これは私たちが望むことではありません。この問題の解決は簡単で、constを付ければよいのです。したがって関数は最終的に void func(const A &a)となります。

これと同様に、効率を上げるためにvoid func(int x) をvoid func(const int &x)と書き換えるべきでしょうか?まったく必要ありません。内部データ型のパラメータには構築・破棄の過程がなく、複製も非常に速いため、「値渡し」と「参照渡し」の効率はほぼ同等だからです。

まとめ:
1.非内部データ型の入力パラメータについては、「値渡し」方式を「const参照渡し」に変更すべきです。目的は効率の向上です。例えばvoid func(A a) をvoid func(const A &a)に変更します。

2.内部データ型の入力パラメータについては、「値渡し」方式を「const参照渡し」に変更しないでください。そうしないと効率向上の目的を達成できないばかりか、関数の理解しやすさも下がります。例えばvoid func(int x) はvoid func(const int &x)とすべきではありません。

以上で、2つの面接問題が解決されました:

  • 関数にポインタを渡す必要がある場合、そのポインタにconstを付けるべきか。constをポインタの異なる位置に付けると何が違うのか。
  • 書く関数の渡すべきパラメータが複合型のインスタンスである場合、値パラメータと参照パラメータでは何が違うのか。いつ渡す参照パラメータにconstを付ける必要があるのか。

7.クラスでのconstの使用

クラスの中で、データメンバを決して変更しない関数はすべてconst型として宣言すべきです。constメンバ関数を書く際にうっかりデータメンバを変更したり、他の非constメンバ関数を呼び出したりすると、コンパイラがエラーを指摘してくれます。これは間違いなくプログラムの堅牢性を高めます。

constキーワードを使って宣言されたメンバ関数を、定数メンバ関数と呼びます。定数メンバ関数だけが定数や定数オブジェクトを操作する資格を持ち、constキーワードを使って宣言されていないメンバ関数は、定数オブジェクトを操作するのに使えません。

クラスのconstメンバ変数は、初期化リストを通して初期化しなければなりません。以下に示します:

class Apple{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
};

Apple::Apple(int i):apple_number(i)
{

}

constオブジェクトはconstメンバ関数にしかアクセスできません。一方、非constオブジェクトは任意のメンバ関数にアクセスできます。constメンバ関数を含みます.

例えば:

// apple.cpp
class Apple
{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
    void take(int num) const;
    int add();
    int add(int num) const;
    int getCount() const;

};
// apple.cpp
Apple::Apple(int i) : apple_number(i)
{
}
int Apple::add(int num)
{
    take(num);
    return 0;
}
int Apple::add(int num) const
{
    take(num);
    return 0;
}
void Apple::take(int num) const
{
    std::cout << "take func " << num << std::endl;
}
int Apple::getCount() const
{
    take(1);
    //    add(); // error
    return apple_number;
}
int main()
{
    Apple a(2);
    cout << a.getCount() << endl;
    a.add(10);
    const Apple b(3);
    b.add(100);
    return 0;
}
// main.cpp

コンパイル:bazel run basic_content/const/class_const/first_example:main

このときエラーが発生します。上のgetCount()メソッドの中でaddメソッドが呼び出されていますが、addメソッドはconst修飾されていないため、実行時にエラーになります。つまり、constメンバ関数がアクセスできるのはconstメンバ関数だけです。

呼び出しを次のように変えると:

const Apple b(3);
b.add(); // error
このとき、constオブジェクトはconstメンバ関数にしかアクセスできないことが証明されます。

上述の初期化リストによるconst定数の初期化に加えて、以下の方法もあります。

第一に、定数の定義をstaticと組み合わせます。すなわち:

static const int apple_number

第二に、外で初期化します:

const int Apple::apple_number=10;

もちろん、c++11でコンパイルするなら、定義の場所で直接初期化できます。次のように直接書けます:

static const int apple_number=10;
// 或者
const int apple_number=10;

この2つともc++11でサポートされています!

コンパイル時に-std=c++11を付けるだけです!

ここでstaticが出てきたので、簡単に説明しておきます:

C++では、static静的メンバ変数はクラスの内部で初期化できません。クラスの内部では宣言のみを行い、定義はクラス定義体の外部、通常はクラスの実装ファイルで初期化しなければなりません。

クラス内で宣言:

static int ap;

クラスの実装ファイルで使用:

int Apple::ap=666

これについては、c++11では宣言と同時に初期化することはできません。つまり上記の使用方法になります。

関連トピック

コメント