第4回:ポインタ変数 (10/15)


第3回演習の解答例


今週はC言語のもっともC言語らしいところである,ポインタ変数について取り上げます.


 ポインタの前に,前提知識として アドレスについて.

アドレスとは?

プログラム中の変数や配列などの値は,コンピュータのメインメモリ(Random Access Memory,RAM,ラムと読む)上に記憶されている.
このメモリには,場所を表す「アドレス」という連続した通し番号(整数の値)がついており,変数ごとのアドレスがOS(Operating System, ここではWindows)により管理されている.

たとえば,C言語プログラム中で int a; と書くと,整数の値1個を格納する場所が確保され,a という名前を使ってこの場所に値を書き込んだり参照したりすることができる.
下の図ではアドレスは int型の変数 a が,4 バイト分のメモリを割り当てられていることを示す.

では,ある変数の場所(メモリのアドレス,上の図では0x0000E008)を調べるには,どうしたらよいか.
また,アドレスを知ることができるという事にどのような利点があるのか.

C言語では,変数の名前の直前に「&」を付けると,変数のアドレスを取り出すことができる.


#include <stdio.h>

void main()
{
    int a = 10;
    
    printf("aの 値  は,%d です¥n",  a); /*  普通に値を表示  */
    printf("aのアドレスは,%p です¥n", &a); /*  %p はアドレスを16進数8桁で表示する */
}

また,その逆,ある「アドレス」に格納されている変数の「値」を調べるには,どうしたらよいうか.


ポインタ変数

変数の置かれているメモリの位置(アドレス)を格納するための変数のことを,ポインタ変数と呼ぶ.

「アドレス」自身はもちろん数値で,演習で用いているPC(32bitCPUと,32bitOSであるWindows XP)では,0 から 232 までの値である.16進数で表すと,0x00000000から,0xFFFFFFFFまでである

参考:1 bit(ビット)= 0 か 1 の値をとる最小単位で,電気信号では電圧の有無に対応.
1 byte(バイト)= 8 bit,28 であるから,0から 255 までの値.
int型は 4 byteのメモリを占有.char型は 1 byte, float,double型はそれぞれ 4 byte, 8 byte.

「ポインタ変数」と「(intなど)普通の(整数型)変数」は,数値を格納する点では同じであるが,その数値の解釈に差異がある


ポインタ変数の基本的な書式

int a=10, b;

これは普通の整数型変数a,bの宣言.

int *p;

ポインタ変数pの宣言.ポインタの指す型を指定し,変数名の前に(アスタリスク)をつける.

p = &a;

int型の変数のアドレスを格納するポインタ変数pに,int型変数aのアドレスを代入.(数値の代入ではない!)
aのアドレスを取り出すには,変数名の前に&をつける.

b = *p;

ポインタ変数に格納されているアドレスの示す先に格納されているを取り出すには,ポインタ変数の前に*をつける.
ここではポインタ変数 p の指す変数(ここでは変数 a )の値を,普通の整数型変数 b に代入.b に10が代入される.

プログラム例:


#include <stdio.h>

void main()
{
    int a, b; /* 普通の変数の宣言 */ 
    int *p;   /* ポインタ変数の宣言 */

    a = 3;
    printf("aのアドレスは,%p です¥n", &a);  

    p = &a;
    printf("ポインタ変数pの値(=aのアドレス)は,%p です¥n", p);  
    printf("ポインタ変数pの指している値は,%d です¥n", *p);  

    p++;
    printf("ポインタ変数を一つ増やすと,%p です¥n", p);
}

配列とポインタの関係(重要!)

C言語において配列は,メモリの連続した区間を使うという約束になっている.

このことから,ポインタへの整数の足し算,ポインタ同士の引き算をすることが可能である.ここではポインタを使って,配列の要素を扱ってみよう.

例:


#include <stdio.h>

void main()
{
    int a[5] = {10, 20, 30, 40, 50};   /* 配列 */
    int b;
    int *p;   /* ポインタ変数 */

    p = &a[0];            /* 配列の先頭のアドレスを代入する */ 

    printf("配列 a の先頭のアドレスは,%p です¥n", p);

    printf("はじめに:p の指している値は,%d です¥n", *p);

    p += 2;    /* ポインタ変数に2を足すと,pは配列の先頭から2つ先を指す=a[2]のアドレス! */

    printf("ここでは:p の指している値は,%d です¥n", *p);

    b = *(p-1); /* ポインタの指しているアドレスのひとつ前隣の値をbに代入 */
    printf("b の中身は,%d です¥n", b);  
}

このように,ポインタ変数に足し算,引き算をすることにより,配列の要素に順次アクセスできる.
ポインタに1を足したときに何バイト先に進むかはポインタの指す変数の型により異なるが,ポインタ変数を宣言する時の型指定から自動的に計算される.


いろいろな型の変数のアドレスを調べよう.


#include <stdio.h>

void main()
{
    int e[3];
    double f[3];
    char g[3];

    printf("int配列    :%p %p %p¥n", &e[0], &e[1], &e[2]);
    printf("double配列:%p %p %p¥n", &f[0], &f[1], &f[2]);
    printf("char配列   :%p %p %p¥n", &g[0], &g[1], &g[2]);
}

実行結果の例(アドレスの値はプログラム実行時にOSが決めるため,必ずしも値がこうなるとは限らない)

0xbffffab4 0xbffffab8 0xbffffabc
0xbffffac0 0xbffffac8 0xbffffad0
0xbffffad8 0xbffffad9 0xbffffada

この結果から,int, double, char, 配列などの各型がメモリ上でどれくらいの大きさかがわかる.


演習課題

(1)変数a,bに数字をキーボードから入力し,aのアドレスとaにbを代入した時のアドレスが同じになることを以下のプログラムを完成させて確かめよ.


#include<stdio.h>
void main()
{
	int a, b;
	int *pa;
	printf("a = "); scanf("%d", &a);
	printf("b = "); scanf("%d", &b);

	pa = ???;

	printf("ポインタ変数paの値(=aのアドレス)は,%pです¥n", ???);
	printf("ポインタ変数paの指している値は,%dです¥n", ???);

	a = b;

	printf("aにbを代入したときのポインタ変数paの値(=aのアドレス)は,%pです¥n", ???);
	printf("aにbを代入したときのポインタ変数paの指している値は,%dです¥n", ???);
}

(2)整数型配列に初期値として,(10,20,30,40,50)を設定したとき,全ての要素の値を表示するプログラムをポインタを用いて作成せよ.

ヒント:


#include <stdio.h>

void main()
{
    int a[5] = {10, 20, 30, 40, 50};
    int *p;

    p = .........

    for(i=0; i<5; i++) {
        printf(" 配列の %d 番目は ... です¥n", ????????? );
    }
}

関数の復習

(3)三つの整数をキーボードから入力すると,その最大値を表示するプログラムを関数を用いて作成せよ.

(4)実数xの値を入力し,y=5x5-12x4+3x+1を求めるプログラムを作成せよ.
ただし,xn(nは自然数)の値は,関数を自ら定義して計算すること(pow関数使用不可).

授業終了時までのプログラムと完成した提出用プログラムをoh-meijiシステムを使って提出すること.
授業終了時に送るのは出席の確認用であり,完成した課題は提出用の回に送ること.
(提出期限を厳守し,提出用の回に提出しないと採点を行わない)