基本型に関する豆知識 (C++をもう一度)
[最終更新] (2019/06/03 00:42:06)
最近の投稿
注目の記事

ポインタ関連

参照とポインタの違い

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

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

その他

数値の修飾子

この続きが気になる方は

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

残り文字数は全体の約 33 %
tybot
100 円
関連ページ
    サンプルコード void へのポインタ void* には関数ポインタおよびメンバポインタを除く (← コンパイラによっては代入できてしまいますが独自仕様です) ポインタをキャストなしで代入できます。ただし void* のままでは、実体にアクセスすることも他のポインタに代入することもできません。事前に何らかの型のポインタ型にキャストする必要があります。
    概要 Go 言語に関する基本的な事項を記載します。 モジュール、パッケージ、ライブラリ、アプリケーション 一つ以上の関数をまとめたものをパッケージとして利用します。更に一つ以上のパッケージをまとめたものをモジュールとして利用します。モジュールにはライブラリとして機能するものと、アプリケーションとして機能するものがあります。