#この記事でお話しすること
- 下準備として、これから紹介する課題の解答で「困るポイント」を紹介します。
- 文字列配列のポインタと、ポインタのポインタについて説明します。
- プログラムを書くにあたって必要になる関数の説明をします。
- このプログラムの例を紹介します。
講義の課題をやっていたのですが、下の問題がどうもすぐには出来ませんでした。
30文字以下の文字列を最大10個キーボードからすべて入力した後,
それらの文字列とその長さを表示するプログラムとなるように修正せよ.
ただし,文字列の入力は”END”を入力したときに終了するとする.
つまり、このプログラムの出力例は以下のようになります。
Pencil
Eracer
Stapler
END
Pencilの長さは6です。
Eracerの長さは6です。
Staplerの長さは7です。
文字列は最大10個しかないそうなので、文字列が入る変数を10個用意すればとても簡単な問題になりますが、そういう他で応用できなさそうなコードは書きたくありませんよね・・・ということで、それ以外の方法で何とかできないか考えていました。
#1. この課題の解答で困るポイント
まず最初に困るのが、文字列は配列であるということです。数字の場合なら配列int[]を用意すれば、数字が何桁だろうと(int型の制約とかはもちろんありますが)各要素ごとに数字が入り、参照は容易だったわけですが、文字列の場合は各要素に1個しか文字が入っていません。
これで何が困ってしまうかというと、n個の文字列の情報をある一つの配列に格納し、それを正しく取り出すのが少し難しいのです。実際に書いてみたらその難しさがわかると思います。
#2. 文字列とポインタ
上の画像でchar型配列の各要素の表し方を2通り表していますが、1つの文字列に対するポインタの話は、過去記事**「文字列をscanf関数で扱うとき」**を参考にしてください。
問題になるのは、複数の文字列に関するポインタ配列です。
基本的には、参考サイト(初心者のためのポイント学習C言語)を参照していただければわかるのですが、(配列名)+(添え字)の形が、各文字列の先頭のアドレスになります。
さて、ここで一つ疑問が生まれます。この各文字列の先頭アドレスは、ほかの書き方で表せるのでしょうか?
答えを言いますと、可能です。それは、ポインタのポインタによって実現されます。その名のとおり2重のポインタですので、**p
のように書きます。これを使用することで、配列のポインタarray[i]を、*(p+i)のように表すことができます。
文章だけでは何ともわかりづらいので、図解します。雑な図ですみません。
ポインタが2重にかかるとややこしいですね。これ使うなら配列のポインタ使ったほうがはるかにわかりやすいです。正直使う機会あまりないと思います・・・
#3. 課題解答において必要な関数
いよいよプログラムを作っていきます。これを解くにはどの関数が必要なのかを考えていきましょう。
まずは文字数を数える関数が必要です。これは、strlen関数で表現できます。(要include string.h) 使い方などの詳細はリンク先を見てください。(初心者のためのポイント学習C言語)
次に、入力された文字列を格納する配列のメモリを動的に確保することを考えます。「動的に確保」というと難しそうですが、要は文字列の長さに合わせて必要な分だけメモリを取ってくるということです。これはmalloc関数で表現できます。(要include stdlib.h)今回の例ではchar型を動的に確保することもあって、sizeof演算子も使用しています。
また、malloc関数で動的に確保した配列に、文字列の情報を代入する操作が必要です。これはstrcpy関数で表現できます。(要include string.h)
最後に、ENDの文字列を数えないようにするために(除外)、ENDか否かで条件分岐させます。これをするためには、文字列比較のstrcmp関数を用います。(要include string.h)
#4. プログラム例
以上を踏まえて実際に書いてみると、次のようになります。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_STR 30
#define DEF_NUMBER 10
int main(void) {
char str[MAX_STR+1]; //最初に代入される文字列
char *text[DEF_NUMBER]; //結果出力用の数列(入力された文字列をコピー)
char **ptr; //textのポインタ(ポインタのポインタ)
int k;
ptr = text; //ptrはtextのポインタ
int number[DEF_NUMBER+1]; //文字数を格納する配列
int cnt = 0;
while(cnt<DEF_NUMBER&&strcmp(str,"END") != 0) {
scanf("%s",str);
number[cnt] = strlen(str); //strlen関数で文字数をnumberに代入
text[cnt] = (char *)malloc(sizeof(char) * (number[cnt]+1)); //textのメモリ確保
strcpy(text[cnt],str); //strcpy関数でtextにstrをコピー
cnt++;
}
int i;
for(i=0;i<cnt;i++) {
if(strcmp(text[i],"END") != 0) { //ENDを出力しないための条件文
printf("%sの長さは%dです。\n",*(ptr+i),number[i]);
}
}
for(k=0;k<cnt;k++) {
free(text[k]); //メモリ解放(個数はcntに依存)
}
}
上の例では、問題文にのっとって30文字・10文字列の設定にしています。変えたければDEFINEの値を変えて対応してください。
指摘や意見大歓迎です。それでは。