情報処理1 解説サイト

文字列と配列

今回は文字列と配列のおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.

  • 文字

文字は,char型を用いて表します.char型は符号なしの8ビット整数型です.つまり,文字は非負の整数値として扱われ,それぞれの文字に整数の文字コードが与えられています.
アスキーコード表にしたがって,文字' 0 ' ~ ' 9 'を16進数と10進数で表すと

文字  16進数  10進数
'0'     0x30       48
'1'     0x31       49
'2'     0x32       50
          .
          .
          .
'9'     0x39       57

のようになります.文字の' 0 'と数値の0は違うものなので注意しましょう.文字の' 0 'は16進数の0x30,10進数の48と同値です.

  • 文字列

文字列は一連の文字の並びを表します.よって,文字列はchar型の配列に格納します.
文字列の終端を示す目印として,ヌル文字' \0 'を用います.
文字列の宣言は以下のように行います.

char str[100];

文字が何文字入力されるか事前には知ることができないため,配列の要素数は大きめにしておくと良いでしょう.
文字列の初期化は以下のように行います.

char str[4] = {'A', 'B', 'C', '\0'};
char str[4] = "ABC";

これはどちらも同じ処理です.配列の要素数はヌル文字を含めた数以上にする必要があります.

char str[4] = {'A', 'B', 'C', 'D'};
char str[4] = "ABCD";

は文字列ではなく,文字が4つ集まった配列となります.
printf関数で文字列を表示するときには,変換指定に%sを用います.文字を表示するときには,%cを用います.

char moji = 'A';
char mojiretsu[] = "ABC";

printf("%c\n", moji);
printf("%s\n", mojiretsu);
for(int i=0;mojiretsu[i];i++){
    printf("%c", mojiretsu[i]);
}
printf("\n");

文字列をキーボードから読み込む場合は&演算子をつけません.詳しくはポインタの回で習います.また以下のようにscanf関数を用いて読み込むと,スペース以降の文字は保存されません.

char str[20];

printf("input a string : ");
scanf("%s", str);
  • 配列

配列は,同じ型の変数の集合を扱うときに利用します.複数の変数を単一の変数名で扱うことで,同じような処理を何度も繰り返し記述する必要がなくなり,非常に便利です.

  • 配列の宣言

変数の宣言は

int n;

のように型と変数名で宣言したと思います.配列の宣言は,

int n[5];

のように要素型,変数名,要素数を宣言します.このとき,要素数は定数にしなければなりません.

  • 配列の初期化

変数の初期化は

int n = 3;

のように行ったと思います.配列の初期化は,

int n[5] = {1, 2, 3, 4, 5};

のように各要素の値をコンマ区切りで並べ,{ }で囲みます.また,

int a[] = {1, 2, 3, 4, 5};
int b[5] = {1, 2};

のように書くこともできます.このときaの要素数は自動的に5に決定され,bの要素は,{ }内で与えられていない要素は0で初期化されるため,{1, 2, 0, 0, 0}になります.

  • 配列の要素へのアクセス

配列の各要素には,

n[0] n[1] ... 

のように添字演算子[ ]を用いてアクセスします.配列の先頭要素の添字は0であり,要素数-1までの整数値で各要素にアクセスできます.

  • 配列のコピー

変数は,

int a = 1;
int b = 0;
b = a; 

のように=で代入することができたと思います.しかし,配列は同じように代入することができません.

int a[3] = {1, 2, 3};
int b[3];
for(int i=0;i<3;i++){
    b[i] = a[i];
}

コピーはfor文などの繰り返し文を用いて,各要素を逐一コピーします.

  • 多次元配列

配列の要素を配列にすることで,2次元以上の配列を作ることもできます.例えば,

int n[2][3];

は2行3列の2次元配列であり,要素は,

n[0][0], n[0][1], n[0][2], n[1][0], n[1][1], n[1][2]

の6個です.


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

  • 5組

1. キーボードから実数を10個入力し,全ての値をまず配列に格納し,そのなかの最大値,最小値を画面に表示せよ.
(配列の要素数を増減させても,正しく動作するプログラムとすること.以下同様.)

#include<stdio.h>

int main(void)
{
    const int N = 10;  /* 配列の要素数 */
    float data[N];     /* 実数10個分の配列 */
    
    .../* データ入力 */
    for(...)
        ...
    
    printf("最大値は %f \n", ... );
    printf("最小値は %f \n", ... );

    return 0;
}

まず,最大値と最小値を格納するための変数を定義しましょう.今回入力されるのは実数です.用意する変数の型に注意しましょう.
次に,for文の中の処理を書いていきます.データ入力の方法は練習問題1を参考にしましょう.
最初のループで,用意した変数の両方に,とりあえず入力された値を代入し,それ以降のループで,最大値と最小値の探索を行うと良いと思います.
最大値の探索は練習問題2を参考にしましょう.最小値も似た処理を行えば良いはずです.

2. キーボードから正の整数を10個入力し配列に格納し,そのなかから奇数の数をカウントして画面に表示せよ.
正でない整数が入力されたらエラーを表示して,正しい値が入力されるまで繰り返し再入力させよ.
引数として配列とその要素数を受け取り,奇数の個数を返す関数count_oddを作ると良い.

まず,奇数の数を保存しておくための変数を定義しましょう.
次に,配列の各要素にデータを入力していきます.このとき,if文を用いて0以下の整数が入力された時,実行例のような文を出力するようにしましょう.
入力エラーの場合は奇数かどうかの判定を行う必要が無いため,それ以降の処理をスキップして,再入力させましょう.ループのスキップは第5回の授業で習いました.
このとき,添字に何か処理をしてからスキップしないと,入力エラーの要素に再入力のデータが上書きされません.

a[0]=10
a[1]=-3
入力エラー! 正の整数を入力
a[2] =0
入力エラー! 正の整数を入力
a[3]=5
    .
    .
    .

再入力のデータを上書きできるように添字に処理を加えましょう.
あとは,if文を用いて,奇数をカウントし,最後にその値を表示させれば良いはずです.

3. キーボードから 2 以上の整数を10個入力し配列に格納し,次に,その中の素数の個数を数え表示するプログラムを作成せよ.
入力に際しては,以下のようなエラー処理を行うこと.
素数の数を数えて返す関数を作ること.

この問題では,最後に素数の数だけでなく,入力された素数の値も表示する必要があります.そのため,0などで初期化された同じ大きさの配列を1つ定義しておくと良いと思います.
データの入力やエラー処理は課題2と同様です.
素数の判定は第5回の課題4でやりました.同様の処理を行いましょう.もし素数であればカウントし,用意した配列にその値を代入しておきましょう.
最後に,用意した配列の代入された値とカウントした値を出力しましょう.

int prime_number[N] = ...
int finel = -1;
...
for( ... ){
    ...
    if( ... ){  //もし素数ならば
        final = i;
            ...
    }
}

printf("素数は");
for( ... ){
    if(i==final){
        printf(" %d の", prime_number[i]);
        break;
    }
    else if(prime_number[i] ... ){
        printf(" %d,", prime_number[i]);
    }
}
printf(" %d 個ありました.", count);     

細かい見た目にこだわるならば,上のようにすれば最後に出力する素数の値の後ろには,コンマの代わりに’の’が出力され,きれいな表示になると思います.

4. キーボードから10個の整数を配列に入力し,(1). 配列の全要素を画面に表示, (2).次に大きい順に並べ替えて表示せよ.
ヒント(各自で調べてみよう):ソーティング,アルゴリズム,単純ソート,挿入ソート,バブルソートヒープソートなど

データ入力の方法は課題2と同様です.for文を用いて並べ替え前の各要素を表示させましょう.
大きい順に並べ替えるには,ある要素とそれより後ろの要素を比べて,後ろの要素の方が大きい場合にはその要素どうしを入れ替えるという処理を行います.この処理は2重ループを用いると良さそうです.また,2つの要素を入れ替えるためには,1つ変数を定義しておく必要があります.

int tmp;
...
for(int i= ... ){
    for(int j= ... ){
        if(a[j]>a[i]){
            tmp = ...

最後にfor文を用いて並べ替え後の各要素を表示させましょう.

5. 整数型配列の各要素を全て絶対値に変換して,変換前後の配列の中身をそれぞれ表示せよ.

for文を用いて変換前の各要素を表示しましょう.表示にこだわるならば最後の要素のときだけprint文を変えましょう.

if( ... ){  //最後の要素ならば
    printf(" %d", a[i]);
}
else{
    printf(" %d,", a[i]);
}

絶対値に変換するには,配列の要素の値が負の場合,その要素に-1をかければ良いはずです.


  • 6組

1. 英数字から成る文字列を入力すると, それぞれの文字コードが1ずつ大きい文字列に変換するプログラムを作りなさい.

まず,char型配列に1文字ずつ格納していきましょう.そして,全配列内の中身を1回ずつインクリメントすればOKです.


2. 三次元ベクトル(x,y,z)を一次元配列を使って以下のようにつくる.
typedef float Vector[3];
このとき,
内積d = a・bを計算する関数 float d = dot(Vector a, Vector b);
外積c = a X b を計算する関数 void cross(Vector c, Vector a, Vector b);
をそれぞれ作成せよ.

Vector[0],Vector[1],Vector[2]に代入する中身をそれぞれx, y, zとして考えましょう.基本的に今まで習ってきたことを使いこなして,数学の定義にしたがって各配列の対象となる要素同士で計算させればいいだけです.注意すべきはcross関数で,求めるのはスカラ値ではないので,計算結果はベクトル値としてしっかり表示しましょう.


3. 一次元配列に文字列が格納されています(キーボードから入力してもよい). この文字列を,降順(asciiコード番号の反対順)に並べる(ソートする) プログラムを作ってください.

この問題の解き方の1例として,ある空のchar型配列に対して,文字列が格納されている配列の内,インデックスの一番最後から順番にコピーしていくことでソート出来ます.

//例えば文字列が格納されている配列aのサイズが10とすると
char b[10]; //コピー先の配列を用意
for(int i=9; i>=0; i--){
     for(int j ...){
           b[j] = a[i];
    }
}

4. 複数の単語からなる文を入力すると,各単語の先頭文字を大文字に変えて表示するプログラムを作成せよ.

アスキーコードを見ると小文字と大文字の違いは 0x20 分違うことが見受けられます.ヒントとして以下に小文字から大文字にする関数を表記しますので参考にしてください.

void upper(char str){
     if('a'<= str && str <= 'z'){
        str = str -0x20;
    }

  printf("%c\n",str);
}

これを用いて大文字から小文字を作ってください.


5. int num_list[] = {1, 2, 3, 4, 5, 6, 7} の数字の並びを, 任意の回数ローテーションさせるプログラムを作成せよ.

まず,ローテーションを1回するような関数を用意し,その関数をn回繰り返せるようにfor文でくくればOKです.