Index

HOME > プログラムTOP > C++



C++の壺・ポインタ篇

 C++の基本テクニックのTipsです。C/C++には、ポインタというメモリのアドレスを制御する便利な機能があります。ポインタ篇では、ポインタのしくみと、配列や関数などにおける基本的な使用方法を扱います。
[index] ポインタの基礎 ... ポインタの宣言と代入
ポインタと配列 ... ポインタと配列の関係
ポインタと関数 ... 関数でのポインタの使い方
ポインタのポインタ ... ポインタを指すポインタ

sasaraan programming

Exposition

●ポインタの基礎

 通常の変数は値を保持しますが、ポインタは、値の置かれているメモリ上のアドレスを保持します。ただし、様々な値は、システムが自動的にアドレスを決めて配置しますので、アドレスの番号自体を気にする必要も、アドレスの番号を直接指定する必要もありません。通常は、以下のように、'*''&' のポインタ演算子を用いて間接的にアドレスを表現してやり取りをします。
 なお、値は値同士、アドレスはアドレス同士でしか代入はできません。
【ポインタ表現】宣言アドレス
・通常の変数int n;n&n
・ポインタint *n;*nn
 上記中、ポインタの記述は、int* n; でも int * n; でも可能です。
 ポインタには、まずアドレスを代入します。アドレスを指定しないうちに値を入れると、システムによっては、既存のデータを壊してしまいますので注意が必要です。
【ポインタ使用の手順】数値配列文字列
 1. ポインタの宣言double *p;int *pchar *p;
 2. アドレスの代入double n; p = &n;int a[8]; p = a; char s[16]; p = s;
 3. 値の代入*p = 1.1;*p = 100; p = "Hello";
 ただし、文字列は例外となります。文字列は自動的にメモリ上に配置されますので、ポインタ側でアドレスを受け取ることが可能となります。その代わりに定数扱いとなります。
int m = 0;    // 通常の変数
int *n;        // ポインタ 
*n = m;      // ここでの値の代入は×
n = &m;      // アドレスの代入
*n = 100;   // ここでの値の代入は○
int *p = 0;  // 値での初期化は×
int m = 0;
int *n = &m;  // 宣言と初期化
*n = 100;      // 値の代入可
char *s;        // 文字列へのポインタ
s = "Hello !"; // いきなり代入可
char *t = "Hello !"; // 宣言と代入

●ポインタと配列

 要素なしの配列名は先頭要素へのアドレスを表します。このことから、ポインタに、要素なしの配列名を代入することができます。ただし、ポインタが保持するのは、アドレスひとつ分だけですから、各要素には加算/減算を行ってアクセスします。
#include <stdio.h>     // printf使用
#include <conio.h>    // getch使用

int main() 
{
    int a[5] = {33, 44, 55, 66, 77};  // 配列の宣言 ・ int型の要素5個で初期化
    int *p = a;                           // ポインタを宣言 ・ 配列 a のアドレスで初期化
    int i;
    for (i=0; i<5; i++) {
        printf("%p, %p, %p\n", &a[i], a+i, p+i);     // アドレスの出力 (すべて同じアドレス)
        printf("%d, %d, %d\n", a[i], p[i], *(p+i));  // 値の出力 (すべて同じ値)
     }
    if (getch()) return 0;
} 
 この時、各要素の値やアドレスは、次のように表すことができます。(アドレスは変動)
address0012FEC40012FEC80012FECC0012FED00012FED4
配列 a のアドレス(1)&a[0]&a[1]&a[2]&a[3]&a[4]
配列 a のアドレス(2)aa + 1a + 2a + 3a + 4
ポインタ p のアドレスpp + 1p + 2p + 3p + 4
value3344556677
配列 a の値(1)a[0]a[1]a[2]a[3]a[4]
配列 a の値(2)*a*(a + 1)*(a + 2)*(a + 3)*(a + 4)
ポインタ p の値(1)p[0]p[1]p[2]p[3]p[4]
ポインタ p の値(2)*p*(p + 1)*(p + 2)*(p + 3)*(p + 4)
 上記の例で、+1〜+4、あるいは [1]〜[4] は、要素ひとつ分のアドレスを数字分だけ進めていることが分かります。この例では、int型は4バイトごとにメモリが確保(32ビット環境の時)されますので、数字1〜4はそれぞれ、先頭要素の位置から見て、4バイト〜16バイト先のアドレスを表すことになります。
 また、ポインタは、加減のほかに、インクリメント/デクリメントによる計算も可能です。ただし、アドレスを表すとき、ポインタは、p++, p--, とすることはできますが、配列の各要素は、アドレスを保持しているわけではないので、a++, a-- とすることはできません。
// ポインタの計算
void main() {
    int n[4] = {50, 60, 70, 80} ;  // int型の配列(32ビット環境時では、1要素は4バイト)
    int *p ;
    p = n ;
    printf("%d\n", *p);       // = 50 : n[0]の値
    printf("%d\n", *p+1);    // = 51 : "*pの値" + 1
    printf("%d\n", *(p+1));  // = 60 : p + "1要素"のアドレスを参照  (n[1] を参照)
    printf("%d\n", (*p)++);  // = 50 : 計算後に "*pの値" + 1    (n[0]=51 に変更)
    printf("%d\n", *p++);    // = 51 : 計算後に p + "1要素" のアドレス (n[1] に移動)
    printf("%d\n", ++(*p));  // = 61 : = ++(*p) : "*pの値" + 1   (n[1]=61 に変更)
    printf("%d\n", *++p);    // = 70 : = *(++p) : p + "1要素" のアドレス(n[2] に移動)
}

●ポインタと関数

【ポインタと引数】
 関数の引数は通常は値渡しで行われます。これは、関数の引数に値がコピーされて渡されるシステムです。値渡しではもとの変数(または定数)の値を変更することはできません。一方、ポインタを引数にする(アドレス渡し)と、参照渡しと同じ効果が得られます。これは値の入っているアドレスを渡すことになるので、もとの値を変更することが可能です。
// 値渡しの例
#include <stdio.h>     // printf使用
#include <conio.h>    // getch使用

void init(int num)
{
    num = 0;
}

void main()
{
    int n = 100;
    init(n);
    printf("%d\n", n);  // n=100, num=0
    if (getch()) return;
}
 ・・> n と num は異なるアドレス
 ・・> n の値は num にコピーされる
// アドレス(ポインタ)渡しの例
#include <stdio.h>     // printf使用
#include <conio.h>    // getch使用

void init(int *num)
{
    *num = 0;
}

void main()
{
    int n = 100;
    init(&n);
    printf("%&d\n", n);  // n= 0, num=0
    if (getch()) return;
}
 ・・> n と num は同じアドレス
 ・・> *num は &n にある値を参照
【ポインタと戻り値】
 ポインタを関数の戻り値とすることも可能です。ただし、関数内の通常の変数は、関数終了時にアドレスは残っていない可能性があります。プログラム終了まで値を保持する静的変数を使うか、別途引数に格納領域を取る必要があります。
// 静的変数を使う例
int *init() 
{
    static int num = 100;
    return &num;
}

void main() 
{
    int *p;
    p = init();
    printf("%d\n", *p);  // =100

    if (getch()) return;
} 
// 別途格納領域を用意する例
int *init(int *num) 
{
    *num = 100;
    return num;
}

void main() 
{
    int q;
    int *p;
    p = init(&q);
    printf("%d\n", *p);  // =100
    if (getch()) return;
} 
【関数ポインタ】
 関数自体へのポインタも使用することができます。引数記述なしの関数名は、その関数があるアドレスを指しますので、関数ポインタの宣言後は関数名を代入して使用します。この時、引数を囲む () は、* よりも優先度が高いので宣言時は関数名を()で囲む必要があります。
// 関数 tasu の定義
int tasu(int a, int b) 
{
    return a + b;
}

// 関数 hiku の定義
int hiku(int a, int b) 
{
    return a - b;
}

// 関数ポインタの宣言
int (*keisan)(int a, int b);
void main() 
{
    int x=100, y=200, z;

    keisan = tasu;
    z = keisan(x, y);
    printf("%d\n", z);  // =300

    keisan = hiku;
    z = keisan(x, y);
    printf("%d\n", z);  // =-100

    if (getch()) return;
} 

●ポインタのポインタ

 ポインタを指すポインタも使用することができます。この時、参照先のポインタのアドレスは、アドレス演算子& を用います。
// "ポインタのポインタ" の例 1
#include <stdio.h>  
#include <conio.h> 

void main()
{
    int n = 100;
    int *p = &n;
    int **q = &p;  // ポインタのポインタ
    **q = 200;      // 値を代入
    printf("%d\n", n);  // =200
    if (getch()) return;
}
// "ポインタのポインタ" の例 2
#include <stdio.h> 
#include <conio.h> 

void main() 
{ 
    int a1[3] = {33, 44, 55};
    int a2[3] = {77, 88, 99};
    int *p[2] = {a1, a2};  // ポインタの配列
    int **t;     // ポインタのポインタ 
    t = p;         // 代入
    if (getch()) return;
}

www.sasaraan.net

(c) morijoh