東京工芸大学 工学部  電子機械学科 2年 前期 

基礎プログラミング 第5回
 2017.5.9
藤木 文彦
インデックスへ戻る

http://fujiki.tv/t-kougei/kisoprog
fujiki.kougei@gmail.com



 今日は、演習を行うだけで無く、仕組みの解説をするので、ノートに書いてもらいます。

 1.ポインタ変数の宣言


 コンピュータプログラムで、変数を使うときは、その変数の内容を置く場所をコンピュータメモリ上に確保し、その場所に、データを保存します。
 このとき、データの置かれる場所を示すのがアドレスです。

 int a=12;

 のようにして、int型変数 ”a” を使用するとき、コンピュータのメモリ上には、a の内容を置くための場所が 「メモリ上のデータ領域」に設定されます。

 a を置く場所のアドレスは、 &a という指定をすることで知ることが出来ます。
 メモリ上のアドレスは、16進数で表現するのが普通ですので、printf文の書式では、16進数表示フォーマット %x を用います。 %d表示でも表示できますが、値が良く分からなくなります。(負の数になる場合もあります。)

 int a=12 のように割り当てられたアドレスは、システムが設定した固定の値を持ちますので、後から変更できません。
(注意:a の内容である、 「12」が変更できないのでは無い。置き場所であるアドレスが変更できないのである。)

 それに対して、後から変更できるようにもともと、「アドレスを入れる場所」として使用する変数を、

 int *p;

 のように宣言します。このとき、 p をポインタ型と言い、 p には、変数を格納するアドレスの値が入ることを示します。

(以下の説明で、データは、全て2バイトのメモリに格納されるとして説明します。実際は、4バイト、8バイトなどありますが、説明の便宜上、2バイトとし、アドレスは、2ずつ増加するとします。)









★ ここで、指示があったら、上の図面を自分のノートに写し、黒板に書く各種の動作を書き入れ、教卓に持参して検印を受けること。
ノートを持参していなければ、教卓に白紙の用紙を用意しておくのでそれを持って行って使っても良いが、授業終了後もきちんと保存して参照できるようにしておくこと。


 int型変数 a を宣言したとき、その内容の格納されるアドレスは、 &a で、取り出されます。
 これを、ポインタ型変数 p に入れると、その後、 ポインタ型変数 p を用いて、そのアドレスの内容を扱うことが出来ます。

 内容を変更するときには、

 *p = 数値;

 のように使用します。

 変数宣言法  内容   アドレス 
 整数   int a    a  &a
 ポインタ int *p   *p   p 

※ 型は、 int 以外に各種の型を指定することが出来る。

 下記の例では、p には、aのアドレスが入ります。
 そのアドレスの内容は、 *p で操作出来ますので、2つめの代入で、*p= で値を代入することで、a の内容を変えることが出来ます。






  (例題1) 

Project0501


int _tmain(int argc, _TCHAR* argv[])
{
// 整数型変数を宣言。変数を格納する場所を確保し、内容を 123 とする。
    int a=123;

// 整数型変数を格納するための場所への「アドレスを置く場所」を確保。
    int *p;
// 上記だけでは、実際の変数を入れる場所が確保されていないことに注意。(ここに内容を入れることはできない。)

// 整数型変数を入れる場所だけを確保。(後から内容を入れられる)
    int b;

        printf("a=%d, addr=%x\n",a, &a);
        printf("b=%d, addr=%x\n",b, &b);
        p = &a;
        printf("p=%d, p=%x *p=%d\n",p, p, *p);

        *p = 456;
        printf("p=%d, p=%x *p=%d\n",p, p, *p);

        p = &b;
        printf("p=%d, p=%x *p=%d\n",p, p, *p);

        *p = 789;
        printf("b=%d, addr=%x\n",b, &b);

        getchar();
        return 0;
}


  【演習問題1】 

 次のプロジェクトを新しく作成して実行してみなさい。
 コンパイル時にエラーとなるはずです。

Project0501a


int _tmain(int argc, _TCHAR* argv[])
{
    int *q; //この行は変更しないこと。

        *q = 999; //この行は変更しないこと。
        printf("q=%d, q=%x *q=%d\n",q, q, *q); //この行は変更しないこと。

        getchar();
        return 0;
}


 指定の3行には変更を加えずに、このプログラムが正しく動作し、 値、 999 が出力されるように、何行かを追加して動かしなさい。

  2.ポインタ変数の初期化

int *p;
のように、ポインタ型として宣言される変数 p には、初期値を入力することはできません。
    int *p=222;

このような初期化はエラーとなります。
なぜなら、 int *p; によって、アドレスを入れる場所だけは確保されたのですが、222 という数値を入れるアドレスがどこかはまだ決められないからです。
 変数は、変更される可能性があるので、メモリのプログラム領域に確保することはできないからです。(文字列変数は、変更されないことを前提として、プログラム領域に確保されるので、 char *s="hello"; のような宣言はできる。)

  (例題2) 

 次のプログラムは、そのままではエラーになります。
 まず、入力実行してみましょう。

Project0502


int _tmain(int argc, _TCHAR* argv[ ])
{
    int a;
    int *p=222;

    printf("p=%x, *p=%d\n",p, *p);
    p = &a;
    printf("p=%x, *p=%d\n",p, *p);

    a = 98765;
    printf("p=%x, *p=%d\n",p, *p);

        getchar();
        return 0;
}

  【演習問題2】 

 上記プログラムを修正し、次のように出力されるようにして実行しなさい。




  3.配列の名前はポインタ、ポインタのインクリメント


 配列変数 a[10] のような宣言をしたときに、この a というのは、10個のデータの並ぶ記憶領域の最初のアドレスを示して居ます。

 そこでそのアドレスは、同じアドレスを示す「ポインタ変数」である p に代入することが出来ます。
 この p の示すアドレスの内容を表示するためには、「内容」を示す *p を用います。

 しかし、これでは、配列の最初の変数しか示す事が出来ません。2番目、3番目等の内容を示すためには、アドレスをずらしていく必要がありますが、このとき、

 p++

 のような指定をします。
 通常 a++ のような指定では、 a の内容が、1増加しますが、ポインタ型で宣言された変数の場合、p++ のように指定すると、内容が1増えるのではなく、 アドレスが、1変数分増加します。
 1変数が、2バイト使用しているとすると、この p の値は、2 増えることになります。
(注:実際には、4バイト使用していると思われるが、説明のために2バイトとした)

  (例題3) 

Project0503 


#define MAX 10

int _tmain(int argc, _TCHAR* argv[])
{
    int a[MAX];
    int *p;
    int i;

    for( i=0; i<MAX; i++)
    {
        a[i] = i*11;
        printf("%d: addr=%x a[%d]=%d\n", i, &a[i], i, a[i]);
    }
        p=&a[0];
 //   p = a;  上の行は、このようにも書けます。

    for( i=0; i<MAX; i++)
    {
        printf("%d: addr=%x *p=%d\n",i, p, *p);
        p++;
    }
        getchar();
        return 0;

}

上記で p++ というのは p の値に1を加えることでは無い。
p は、アドレスなので、p++ は、次のアドレスを示す値に変更する、という意味になる。

  【演習問題3】 

        p=&a[0];

の部分を、
      p = a;
のように変更して実行してみなさい

  4.ポインタの変数の加減算


ポインタ変数の、 n 番目のデータを操作するとき、

*(p+n)

のような書き方をします。これは ポンタ型変数に、 単純にnを足すのでは無く、アドレスに変数型の大きさ分の値を加えることを意味します。

  (例題4) 

Project0504 


#define MAX 10

int _tmain(int argc, _TCHAR* argv[])
{
    int a[MAX];
    int *p;
    int i;

    for( i=0; i<MAX; i++)
    {
        a[i] = i*11;
        printf("%d: addr=%x a[%d]=%d\n", i, &a[i], i, a[i]);
    }
    p = a;
    for( i=0; i<MAX; i++)
    {
        printf("%d: addr=%x *p=%d\n",i, (p + i), *(p + i) );
    }

        getchar();
        return 0;
}


 これにより、配列要素の i 番目のデータは、

 a[i]
 *(p+i)

 の、どちらでも同様に使える事になります。 iが数値の1,2,3 であるにも関わらず、このプログラムでは、アドレスの番地が、変数の大きさバイト数分(2バイトとか4バイトとか8バイトとか)ずつ増えていくことに注意が必要です。

  【演習問題4】 


 上記プログラムで、
int a[MAX];

float a[MAX];

として、実行してみなさい。
実行結果を表示、画面キャプチャーして提出しなさい。このとき、アドレスはいくついくつずつ増えていくか。


  5.文字列データの初期化


 文字列は、実は、配列型変数に1文字ずつ入って居る、という考えをします。
 配列の大きさは、文字列を初期設定した時点で、自動的に設定されます。

 配列の大きさは、文字列を初期設定した時点で、自動的に設定されます。

  (例題5) 

Project0505 


#define MAX 10

int _tmain(int argc, _TCHAR* argv[])
{
    char s[ ]="Hello";
    char *p;

    printf("%s\n",s);
    printf("%c\n",s[1]);
    p=s;
    printf("%s\n",p);
    *(p+3)='Q';
    printf("%s\n",p);

        getchar();
        return 0;
}

ここでの文字列配列 s[ ] は、次のようになっています。 \0 は、文字列の最後を表す記号です。

s[0]  s[1]  s[2]  s[3]  s[4]  s[5] 
 H \0 
 p p+1  p+2  p+3  p+4  p+5 




 配列の大きさが、自動的に設定されると、その添字の大きさが分かりません。
 そこで、配列の大きさを知るために、文字列の最後の位置まで、カウントを増やします。実は文字列の最後には、必ず '\0' という値が入って居ますので、これを探して、文字列の長さをカウントすることが出来ます。

 注意:
  char *s という宣言もできるが、この形の宣言では、確保されるのは、「ポインタを格納する場所だけ(上記の図面では、 3010 番地の部分だけ)」 であり、実際のメモリを何バイト使用するかが設定されていないので、このようなアドレスに、データを入れることはできない。
 ただし、「既に確保されているメモリにあるアドレスを示す」ことはできる。

 この話は、メモリの仕組みが十分に理解できていないと理解不能なことなので、最初の数回の授業で理解できなくても心配しなくてよい。何度も繰り返し説明するうちに理解してほしい。


  【演習問題5】 

 上記のプログラムで、
*(p+3)='Q';

の部分を

*(p+5)='Q';

として実行してみなさい。

例えば、次のように変な画面が出るはずです。



なぜおかしくなるのかを考察して、プログラム、実行結果の画面を貼り付けるとともに、メール本文に、なぜ妙な出力となったのかの理由を考察して書きなさい。

























(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウス
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
(ここをマウスでダブルクリックして、文章を入力してください。)
でダブルクリックして、文章を入力してください。)


6.文字列データの初期化


これ以下は修正版を作り直しますので、指示があるまでやらないでください。

  (例題6) 

Project0506


#define MAX 10

int _tmain(int argc, _TCHAR* argv[])
{
    char s[]="Hello";
    int i,n=0;
    printf("%s\n",s);
    while( s[n] !='\0' )
    {
        printf(" s[%d]=%c\n", n, s[n]);
        n++;
    }

    printf("nagasa = %d\n",n);
    for( i=0; i<n; i++)
    {
        printf("%c\n",s[i]);
    }

        getchar();
        return 0;
}


  【演習問題6】 

// char s[] = "Hello";
char *p="Hello";
int i, n = 0;
printf("%s\n", p);
while (*(p+n) != '\0')
{
printf(" *p=%c\n", n,*(p+n));
n++;
}



 ポインタ型として指定した変数は、途中で入れ替えることが出来ますから、次のプログラムのように、
 p が、配列 a[] の先頭を表すように使うことも出来ますし、配列 b[] の先頭を表すように使うことも出来ます。

【注意】 このプログラムは、コンパイルは出来ますが、実行時に次のようなエラーを出します。

 Run-Time Check Failure #3 - The variable 'p' is being used without being initialized.

 通常、ポインタ型変数は、使用前に、実際に使用する文字列のアドレスなどを、次のような形で代入してから使いますが、ここでは、それをしていません。
 そこで、「初期値を設定せずに使っている」というエラーが「実行時に」出るのですが、「アドレスを入れる場所」としての p は、メモリ上に確保されていますから、プログラム自身は正しく実行されます。
 そこで、ここは、

「継続」


 を選びます。

 ポインタだけ確保しての使用は非常に注意が必要なので、このようなエラーが出されるので、実際にはこのような使用法は避けたほうが良いでしょう。ここでは、ポインタの性質を知るために例として掲げました。