情報処理1 解説サイト

ポインタ

今回はポインタのおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.

  • アドレス

数値などを格納するための箱である変数(オブジェクト)は、コンピュータの記憶域(メモリ空間)の一部として存在します。記憶域には、たくさんの変数が配置されるため、それぞれの変数の場所をアドレスを用いて表します。つまり、変数のアドレスとは、それが格納されている記憶域上の番地のことです。
演算子を用いることで、その変数のアドレスを知ることができます。

  • ポインタ

変数名の前に*をつけることによって、ポインタとなります。

int m;
int *n;

ここでmはint型の変数であり、nはint型の変数を指すためのポインタです。mには整数が入り、nには整数を格納する変数のアドレスが入ります。
演算子を用いることで、それが指す変数の値を表すことができます。

  • 関数の引数としてのポインタ

関数の中で仮引数を変更しても、呼び出し側の実引数には反映されません。しかし、関数にその変数へのポインタを渡すことで、変数の値を変更することができます。以下に具体例を示します。

#include <stdio.h>

void function(int a){
    a = 5;
}

void function2(int *a){
    *a = 5;
}

int main(void){
    int n = 3;
    printf("%d\n", n);

    function(n);
    printf("%d\n", n);

    function2(&n);
    printf("%d\n", n);
}

function2のように変数へのポインタを仮引数に受け取れば、そのポインタに*演算子を用いることで、その変数そのものにアクセスができます。よって、function2でnの値を変更できます。

  • ポインタと配列

配列名は、その配列の先頭要素へのポインタと解釈されます。つまり配列aにおいて、a=&a[0](a[0]のアドレス)です。
また、ポインタpが配列aを指すとき、p+iは、配列aの先頭要素のi 個後ろの要素を指すポインタとなり、*(p+i)は、i 個後ろの要素となります。
つまり、&a[i] , a+i , &p[i] , p+i は、すべて配列の各要素を指すポインタであり、a[i] , *(a+i) , p[i] , *(p+i) は、すべて配列の各要素を表します。

#include <stdio.h>

int main(void){
    int a[3] = {1, 2, 3};
    int *p = a;

    for(int i=0;i<3;i++){
        printf("a[%d]:%d, *(a+%d):%d, p[%d]:%d, *(p+%d):%d\n", i, a[i], i, *(a+i), i, p[i], i, *(p+i));
    }

    for(int i=0;i<3;i++){
        printf("&a[%d]:%p, a+%d:%p, &p[%d]:%p, p+%d:%p\n", i, &a[i], i, a+i, i, &p[i], i, p+i);
    }
}

関数間の配列の受け渡しは、先頭要素へのポインタで行います。もちろん値を書き換えれば、呼び出し側の配列の要素も書き換わります。

void function(int *a){
    ....
}

  • ポインタによる文字列

配列による文字列は

char s[] = "abc";

のように初期化しました。
ポインタによる文字列は

char *p = "abc";

のように初期化します。ここで、ポインタpは先頭文字 ' a ' を指します。また、ポインタによる文字列の配列は

char *p[] = {"abc", "de", "fghij"};

のように初期化します。ここで、ポインタp[0]は ' a '、p[1]は ' d '、p[2]は ' f ' を指します。


以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.

  • 5組

1. 要素数 5 個のchar型配列,short型配列,int型配列,double型配列をそれぞれ用意し,配列の各要素のアドレスを順に表示するプログラムを作成せよ.
アドレスは,printf文の書式指定文字列%pで表示させることができる.(実際に表示されるアドレス値は環境依存のため,下の例の通りとはならない)

for文を用いて配列の各要素を表示させましょう.
演算子を用いることで、その配列の要素のアドレスを知ることができます.
「配列とポインタの関係」の練習問題を参考にしましょう.


2. 入力した文字列の順序を反転して表示するプログラムを,ポインタを用いて実現せよ.
inverted_print関数を完成させよ.
main関数は変更しないこと.ヒント:swap関数を作成し,上手に使うと...

まず,文字列の文字数を数えましょう.
「関数の仮引数としてのポインタ」の練習問題を参考にしましょう.

int count = 0;

while(???){
    count++;
    ...
}

swap(???, count);

そして,swap関数に配列の先頭アドレスと文字数を渡して文字列を反転させましょう.

char tmp;

for(int i=0;i<???;i++){
    tmp = p[i];
    p[i] = p[???];
    p[???] = tmp;
}

3. 以下のルールに沿ってある文字列(「TCznemhs!^lsngehJmAjr^UEbdadfkp 」)を解読しその答えを表示するプログラムを作成しなさい.
・main文は変更してはならない.
・関数 decode を作成する.
・関数 decode は受け取った文字列の偶数番目の文字の文字コードをインクリメント(+1)して表示.

void decode(const char *s)
{
    /* ここを作成 */
}

int main(void)
{

    decode("TCznemhs!^lsngehJmAjr^UEbdadfkp ");

    return 0;

}

実行結果:
Dont_think_Feel!

この問題のポイントは,(i)受け取ったポインタのアドレスのインクリメント,(ii)ポインタが示すあるアドレスに格納されている文字コードのインクリメント. . . の2パターンのインクリメントをしっかり使い分けられるかが鍵となります.
上記の参考コードを例に取ると,ポインタのアドレスのインクリメントは

*s++;

ポインタが示すあるアドレスに格納されている文字コードのインクリメントは

*s+1;

となります.このことと,以下のさらなる参考コードをヒントに解いて下さい.

#include <stdio.h>

void decode(const char *s)
{
	int i = 1;
	while(*s){
		if(. . .){
                       . . .
                }
                . . .
	}
	printf("\n");
}


int main(void)
{
	decode("TCznemhs!^lsngehJmAjr^UEbdadfkp ");

	return 0;
}

3. 10個のint型配列を作成し,

1. create関数で,配列の各要素に,添字の値の2乗の値を格納し
2. print関数で,その結果を画面に表示する

プログラムである.
それぞれの関数の中ではポインタ変数を用いて配列アクセスせよ.

main()関数は変更しないこと.
変数 SIZE の値を 100 や 20 など任意の値に変更した場合でも正しく動作することを確認せよ.

void create(int *p, ...)
{
    //...配列のi番目の値は *(p+i) または p[i]
}

void print(int *p, ...)
{
    //...
}

int main(void)
{
    const int SIZE = 10;
    int data[SIZE];

    create(data, SIZE);
    print(data, SIZE);

    return 0;
}

【実行例】
data[0] = 0
data[1] = 1
data[2] = 4
data[3] = 9
....

この問題はポインタが示す先頭アドレスから数えてi番目(i = 0, 1, .. , SIZE)に格納されている値を参照できれば簡単です.
参照の仕方は上記の参考コード中にコメントアウトで書かれているとおりです.

  • 6組

1. キーボードから一文字ずつ入力し,その文字をそれまで入力した文字列の 最後に追加するプログラムを作りなさい.文字列の最後を覚えておくポインタを 用いなさい.一文字ずつ入力する度に画面に文字列を表示し,10文字入力した時点で終わること.

1文字目を入力しEnterキーを押すと改行文字列を入力した文字として処理し、改行した空の1行を出力する処理が行われます。その後に2番目の文字を入力しても、3番目の文字列を入力した処理が行われます。
これを改善するためには、下記のように「%*c」句を「%c」変換子のあとに記述します。
「%*c」句で改行文字を除去します。
char moji;
scanf("%c%*c",&moji);


2. 文字列の中をスキャンして,キャラクタコード番号の 小さい順に1文字ずつ拾い,画面にプリントするプログラムを作りなさい. 表示するタイミングは,上記の10文字入力が終わった後(最後)とする. ソートを使わず,あくまでポインタを使ってスキャンして実現してください.

ここではポインタを使ってスキャンすることしか指定されていないのでやり方はいろいろあると思います。ポインタが理解できていればあまり難しくないので自分でやり方を考えて書いてみましょう。


3. 入力した文字列の中から英数字以外の文字(記号)のみを表示するプログラムを作成せよ. ただし,文字列のスキャニングにはポインタを用いること. また,配列の要素を示す括弧( [ ] ) は定義のとき以外で使用しないこと.

こちらもポインタを理解出来ていれば特に問題無いと思います。LECTURE6を振り返りながらやりましょう。

4.ポインタの2次元配列を用いて, 月の英語名称,和名のデータテーブルを作り, 月の番号(1,2,・・・,12)を入れると英名と和名が, 英名を入れると月番号と和名が表示されるようにせよ. また,存在しない月番号や英名を入力すると,誤りを指摘するようにエラー処理を行え.

ここでは入力する番号と データテーブルの先頭アドレスから数えた配列にアクセスできれば,画面表示するだけです.
また,2次元配列の場合,アドレスを保持しているのはmonth[0][0]のアドレスだけであり,month[1][0]にアクセスするときは
&month[0][0] + 1*(2次元の配列の要素数,今回であれば2)+0
となります

5. 文字列をキーボードから istr に入力し, 大文字を抜き出す関数 find_capital(), 小文字を抽出する関数 find_small() を作成せよ.
抽出した大文字の文字列は cstr に,小文字の文字列は sstr に それぞれ格納し,画面に出力せよ. 但し,配列へのアクセスはポインタを用いること. また,string.h に用意された関数を用いてはならない.

問題1、3ができれば問題なくできると思います。