モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

工作HardwareHub ロゴ画像 (Laptop端末利用時)
工作HardwareHub ロゴ画像 (Mobile端末利用時)

基本型に関する豆知識 (C++をもう一度)

モーダルを閉じる

ステッカーを選択してください

モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2014/12/14
最終更新最終更新
2021/10/07
記事区分記事区分
一般公開

目次

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    0
    ステッカーを贈るとは?

    ポインタ関連

    参照とポインタの違い

    ポインタと参照の決定的な違いの一つは、指す先を後から変更できるかどうかというところにあります。

    #include <iostream>
    using namespace std;
    
    int main() {
        // 参照
        int m = 1;
        int n = 2;
        int& intref = m;
    
        intref = n; // 指す先 m の値を変更しているだけ
        cout << "m: " << m << ", " //=> 2
                "n: " << n << ", " //=> 2
                "intref: " << intref << endl; //=> 2
    
        // ポインタ
        int mm = 1;
        int nn = 2;
        int* intptr = &mm;
    
        intptr = &nn; // 指す先を変更できる
        cout << "mm: " << mm << ", " //=> 1 (変更されていない)
                "nn: " << nn << ", " //=> 2
                "intptr: " << *intptr << endl; //=> 2
    
        return 0;
    }
    

    配列とポインタの違い

    同様にポインタと配列の決定的な違いの一つも、指す先を後から変更できるかどうかというところにあります。

    #include <iostream>
    using namespace std;
    
    int main() {
        int arr[8];
        int* ptr;
    
        int n[8];
        ptr = n; // ← これは問題ないですが
        // arr = n; // ← これはエラーです。
    
        return 0;
    }
    

    ポインタと配列のもう一つの重要な違いは、sizeof 演算子が返す値が配列全体のサイズであるか、ポインタ自身がアドレスを格納している領域のサイズであるか、というところにあります。

    #include <iostream>
    using namespace std;
    
    int main() {
        int arr[8];
        int* ptr;
        ptr = arr;
    
        cout << sizeof arr << endl; //=> 32
        cout << 8 * sizeof(int) << endl; //=> 32
    
        cout << sizeof ptr << endl; //=> 4
        cout << sizeof (int*) << endl; //=> 4
    
        return 0;
    }
    

    そのため、一旦ポインタに入れると、配列サイズが取得できなくなるという事実が有名です。関数の引数で配列の先頭アドレスを渡すときは、サイズも渡すか、もしくは配列に (文字列のヌルターミネータのように) ターミネータを付与しておく必要があります。これは有名なはまりどころです。

    #include <iostream>
    using namespace std;
    
    int main() {
        int arr[8];
        int* ptr;
        ptr = arr;
    
        cout << sizeof arr / sizeof *arr << endl; //=> 8
        cout << sizeof arr / sizeof arr[0] << endl; //=> 8 と同じ意味です
    
        cout << sizeof ptr / sizeof *ptr << endl; //=> 1
        cout << sizeof(int*) / sizeof(int) << endl; //=> 1 と同じ意味です
    
        return 0;
    }
    

    文字列リテラルに関しても配列とポインタの違いをみることができます。

    #include <iostream>
    using namespace std;
    
    const char* MyFunc() {
        // ポインタの文字列リテラル
        // - 静的に確保されたデータのため return してもよいです。
        //   (関数を抜けた後に領域が解放されないためです)
        return "MyFunc";
    
        // 配列の文字列リテラル
        // - 後述の通り静的データではないため関数を抜けた時点で
        //   値の存在は保証されません。return してはなりません。
        // char str[] = "MyFunc";
        // return str;
    }
    
    int main() {
        // ポインタの初期化
        // - 静的変数と同様に、プログラム実行時に既にメモリ領域が
        //   確保されており、main() 時には内容も初期化済みです。
        const char* ptr = "main"; // 注意: その内容も書き換えてはいけない仕様です。
                                            // (const を使用しましょう)
        cout << ptr << endl; //=> MyFunc
        cout << MyFunc() << endl; //=> main
    
        // 配列の初期化
        // - 静的なデータとはなりません
        char str[] = "main";
        // char str[] = {'m', 'a', 'i', 'n', '\0'}; // と同じ意味です
        str[0] = 'M'; // 内容の変更も許されています
        cout << str << endl; //=> Main
    
        return 0;
    }
    

    ポインタの複数同時宣言 (のつもり)

    ポインタの宣言を複数同時にやろうとすると意図しない宣言になります。

    #include <iostream>
    using namespace std;
    
    int main() {
        // int* p, q; // qはintになってしまいます。
        int* p; // 一行ずつ分けて
        int* q; // 宣言しましょう。
    
        int n = 8;
        p = &n;
        q = &n;
        cout << *p << endl
             << *q << endl;
    
        return 0;
    }
    

    アドレス同士の減算

    #include <iostream>
    using namespace std;
    
    int main() {
        int arr[] = {10, 20, 30};
    
        cout << &arr[2] - &arr[0] << endl; //=> 2 となります。これは、
    
        cout << *(&arr[1] + 1) << endl; //=> 30 が
        cout << arr[2] << endl; //=> 30 と等しくなるという事実を知っていると余計に混乱しますが仕様です
    
        return 0;
    }
    

    char型関連

    1バイトとは

    バイトとは char 型のサイズのことです。通常は8ビットです。

    #include <iostream>
    using namespace std;
    
    int main() {
        cout << sizeof(char) << endl; //=> どのような環境でも常に 1 です
        return 0;
    }
    

    文字コードの16進ダンプ

    char型の変数用のメモリに格納された内容を、10進数の正の数としてコンソール出力するためにはやや複雑なキャストが必要です。

    #include <iostream>
    using namespace std;
    
    int main() {
        char ch = 'a';
    
        // 1. char型は負の数として扱われる可能性があるため unsigned char にキャスト
        // 2. cout は unsigned char を文字として表示してしまうため int にキャスト
        cout << (int)(unsigned char)ch << endl; //=> 97
    
        return 0;
    }
    

    文字セットの仕様

    文字コードに関して '0' - '9' は連続していることが保証されています。そのため '0' を減することで文字を数値に変換できます。

    #include <iostream>
    using namespace std;
    
    int main() {
        char ch = '1';
        int n = ch - '0';
    
        cout << ch << endl; //=> 1 (文字として)
        cout << n << endl; //=> 1 (数値として)
    
        return 0;
    }
    

    ただし、アルファベットについては連続していることが保証されておらず、文字セットによっては「'A' <= ch && ch <= 'Z'」などとできません。そのため以下のようにします。

    #include <iostream>
    #include <cctype> // isupper, islower, isdigit
    using namespace std;
    
    int main() {
        char ch;
        cin >> ch;
    
        cout << isupper(ch) << endl; // A-Z ならば真
        cout << islower(ch) << endl; // a-z ならば真
        cout << isdigit(ch) << endl; // '0'-'9' ならば真 ('0' <= ch && ch <= '9' と同じ)
    
        return 0;
    }
    

    その他

    数値の修飾子

    整数や小数に 'l' や 'u' といった文字を末尾に付与すると、その数値の意味をより詳細に指定することができます。

    #include <iostream>
    using namespace std;
    
    int main() {
    
        // long, unsigned 関連
        long int l_intval = 9999l; // 'l' を末尾に付与すると long 修飾を意味
        unsigned int u_intval = 9999u; // 'u' を末尾に付与すると unsigned 修飾を意味
        unsigned long int ul_intval = 9999ul;
    
        // float, double 関連
        float floatval = 10.0f; // 'f': float
        double doubleval = 10.0; // '': double
        long double l_doubleval = 10.0l; // 'l': long
    
        cout << "l: " << l_intval << ", "
                "u: " << u_intval << ", "
                "ul: " << ul_intval << ", "
                "f: " << floatval << ", "
                "'': " << doubleval << ", "
                "l: " << (double)l_doubleval << endl;
    
        return 0;
    }
    

    符号判定

    整数の符号を判定する有名な方法があります。

    #include <iostream>
    using namespace std;
    
    int main() {
        int n;
        cin >> n;
    
        int sign = (n > 0) - (n < 0);
        cout << sign << endl; // -1, 0, 1
    
        return 0;
    }
    

    有効な配列の範囲

    配列において、有効なアドレスの範囲は有効な要素の範囲より一つ大きくなります。

    #include <iostream>
    using namespace std;
    
    int main() {
        int arr[] = {10, 20, 30};
    
        // 値としては arr[0] 〜 arr[2] が有効ですが
        // アドレスとしては arr[0] 〜 arr[3] まで有効です。
        // 以下のような終端判定として利用されることを期待しての仕様です。
        for(int *p = arr, *pEnd = &arr[3]; p != pEnd; ++p) {
            cout << *p << endl;
        }
        return 0;
    }
    

    条件演算子

    以下のようにシンプルに分かりやすくなる場合を除き、入れ子にした形での条件演算子の使用は避けましょう。

    #include <iostream>
    using namespace std;
    
    int main() {
        int a = 1;
        int b = 2;
    
        cout << (
            a > b ? "a > b" :
            a < b ? "a < b" :
                    "a = b"
            ) << endl;
    
        return 0;
    }
    

    基本型の情報を取得する手段

    #include <iostream>
    #include <limits>
    #include <climits>
    #include <cfloat>
    using namespace std;
    
    int main() {
        // limits: 静的メンバ関数によって提供 (STL/numeric_limits)
        cout << (int)numeric_limits<unsigned char>::min() << endl; //=> 0
        cout << (int)numeric_limits<unsigned char>::max() << endl; //=> 255
    
        // climits: マクロによって提供
        cout << CHAR_MIN << endl; //=> -128
        cout << CHAR_MAX << endl; //=> 127
        cout << CHAR_BIT << endl; // 1バイトのビット数 
    
        // cfloat: マクロによって提供
        cout << FLT_MAX << endl; // float
        cout << DBL_MAX << endl; // double
        cout << LDBL_MAX << endl; // long double
    
        return 0;
    }
    
    0
    詳細設定を開く/閉じる
    アカウント プロフィール画像 (本文下)

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    さらに詳しく →
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      関連記事