2.12.6. ポインタ#

値を変数に保存するとき、その値はメモリ上のどこかに記憶されます。C/C++ 言語では、値が保存されているメモリのアドレスaddress)を取得することができます。メモリのアドレスを扱う場合は、ポインタ型pointer type)の変数を利用します。例えば、int x = 5; を実行すると、変数 x はメモリ上のどこかに 5 という値を保存します。この値を取り出すには変数 x を使いますが、その値が保存されているメモリ上のアドレスを取り出すには、アドレス演算子 & を使い、&x と記述します。

#include<stdio.h>

int main() {
    int a = 1;

    printf("%d\n", a);   // 1
    printf("%p\n", &a);  // 0x7ffeecde48d8

    return 0;
}

2.12.6.1. 宣言と初期化#

ポインタ型の変数を宣言するときは * を使用します。このとき、ポインタが指すアドレスに保存されている値の型に応じて変数型も指定します。例えば、int 型の値が保存されているアドレスを格納するポインタ型の変数は int* として宣言します。

次の例では、整数型の変数 x を宣言し、そのアドレスをポインタ型の変数 y に格納しています。

#include<stdio.h>

int main() {
    int x = 5;
    int* y = &x;

    return 0;
}

ポインタが指すアドレス上に保存されている値を取り出すには、間接参照と呼ばれる操作を行います。間接参照には間接演算子 * を使用します。

#include<stdio.h>

int main(){
    int a = 1;
    int* p  = &a;

    printf("%p\n", p);   // 0x7ffeef4e38d8
    printf("%d\n", *p);  // 1
    
    char b = 'A';
    char* q  = &b;

    printf("%p\n", q);   // 0x7ffeef4e38cf
    printf("%c\n", *q);  // A
    
    return 0;
}

2.12.6.2. ポインタと配列#

C/C++ 言語では、配列の変数名そのものが配列の先頭要素のアドレスを表します。そのため、配列のアドレスを取得するときに & は不要で、配列名をそのまま使えます。配列名とポインタ型の変数はいずれも配列の先頭要素のアドレスを指すため、ほぼ同じように扱えます。

#include<stdio.h>

int main(){
    int arr[5] = {11, 22, 33, 44, 55};
    int* p  = arr;

    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        printf("%d %d %p\t", arr[i], *(arr + i), &arr[i]);
        printf("%d %d %p\n", p[i], *(p + i), p + i);
    }

    return 0;
}

// 11 11 0x7ffeebea08c0	11 11 0x7ffeebea08c0
// 22 22 0x7ffeebea08c4	22 22 0x7ffeebea08c4
// 33 33 0x7ffeebea08c8	33 33 0x7ffeebea08c8
// 44 44 0x7ffeebea08cc	44 44 0x7ffeebea08cc
// 55 55 0x7ffeebea08d0	55 55 0x7ffeebea08d0

2.12.6.3. ポインタと文字列#

C/C++ 言語では文字列を文字の配列として扱うため、配列と同様に文字列とポインタ変数もほぼ同じように扱えます。文字列の最後には必ずヌル文字が含まれていることに注意してください。

#include<stdio.h>

int main(){
    char arr[6] = "hello";
    char* p = arr;

    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        printf("%c %c %p\t", arr[i], *(arr + i), &arr[i]);
        printf("%c %c %p\n", p[i], *(p + i), p + i);
    }

    return 0;
}

// h h 0x7ffee54ac8d6	h h 0x7ffee54ac8d6
// e e 0x7ffee54ac8d7	e e 0x7ffee54ac8d7
// l l 0x7ffee54ac8d8	l l 0x7ffee54ac8d8
// l l 0x7ffee54ac8d9	l l 0x7ffee54ac8d9
// o o 0x7ffee54ac8da	o o 0x7ffee54ac8da
//   0x7ffee54ac8db	  0x7ffee54ac8db

また、文字の配列を作らずに直接ポインタに文字列を代入することも可能です。

#include<stdio.h>

int main(){
    char* p = "hello";

    for (int i = 0; i < 6; i++) {
        printf("%c %c %p\n", p[i], *(p + i), p + i);
    }

    return 0;
}
// h h 0x10c827f9a
// e e 0x10c827f9b
// l l 0x10c827f9c
// l l 0x10c827f9d
// o o 0x10c827f9e
//  0x10c827f9f

2.12.6.4. ポインタと関数#

関数の引数には値渡しとポインタ渡しがあります。値渡しでは、変数の値のコピーを関数に渡すため、関数内で値を変更しても呼び出し元には影響がありません。ポインタ渡しでは、変数のアドレスを関数に渡すため、関数内でそのアドレスに格納されている値を直接変更できます。その結果、関数を抜けた後も値は変更されたままです。

#include<stdio.h>

void f(int x) {
    x += 1;
}

void g(int* x) {
    *x += 1;
}

int main(void) {
    int x = 10;

    printf("%d\n", x); // 10

    f(x);

    printf("%d\n", x); // 10

    g(&x);

    printf("%d\n", x); // 11

    return 0;
}

C++ では、値渡しやポインタ渡しのほかに参照渡しという機能があり、機能的にポインタ渡しとほぼ同じです。関数内で値を変更すると呼び出し元の変数にも反映されます。

#include <iostream>

void f(int x) {
    x += 1;
}

void h(int& x) {
    x += 1;
}

int main(void) {
    int x = 10;

    std::cout << x << std::endl;  // 10

    f(x);

    std::cout << x << std::endl;  // 10

    h(x);

    std::cout << x << std::endl;  // 11

    return 0;
}