C++言語解説:1-4.配列と文字列の基本
2002-05-19

[概要] C言語習得で「挫折経験」のある人は、ここから教育に参加することをお勧めしま
    す。C言語での文字列に対する考え方と配列の基本について学習します。

[構成]・1次元配列
     * スタイル
     * 配列データのメモリ配置と容量
     * 境界検証は行われない
   ・文字列
     * 文字列の定義
     * 文字列定数
     * キーボードから文字を読む
     * ヌル文字を利用する


1次元配列

 スタイル
 
  ・1次元配列(array)は関連する変数のリストです。一般形式は以下です。
   ┌───────────────────────────────────┐
     型 変数名[サイズ];
     (例)int i_sample[10];
   └───────────────────────────────────┘
  
  ・型は配列構成要素のデータ型を指します。サイズは要素の数を指定します。
  
  ・配列の要素には添え字(index)を使ってアクセスします。配列の要素は「0」から始
   まります。
よって
     int i_sample[10];
   と定義した場合、最初の要素は
     i_sample[0];
   最後の要素は
     i_sample[9];
   となります。

  
   [List1.1次元配列に慣れる(その1)]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      int i_sample[10]; //配列の定義
      int i;
      
      for (i=0; i<10; i++){
        i_sample[i]=i; //各要素へ数値代入
      }
      
      for (i=0; i<10; i++){
        cout << i_sample[i] << ' '; //各要素のデータ表示
      }
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [実行結果]
    0 1 2 3 4 5 6 7 8 9
   └───────────────────────────────────┘
 
 
 配列データのメモリ配置と容量

  ・配列は連続したメモリー位置で構成されます。例えば
     int i[7];
   とした場合
   ┌───────────────────────────────────┐
      4byte  4byte  4byte  4byte  4byte  4byte  4byte
     ←──→←──→←──→←──→←──→←──→←──→
     ┌───┬───┬───┬───┬───┬───┬───┐
     │ i[0] │ i[1] │ i[2] │ i[3] │ i[4] │ i[5] │ i[6]
     └───┴───┴───┴───┴───┴───┴───┘
   └───────────────────────────────────┘
   の連続したメモリ領域が確保されます。
  
  ・各要素に割り当てられるメモリ領域の大きさは、プラットフォームにより異なりま
   す
が、32bit OSのint型データには、一般的に32bit(4byte)が割り当てられます。
  
  ・よってこの場合の消費メモリ合計バイト数は
     合計バイト数 = 4byte × 7要素 = 28byte
   となります。
  
  ・また配列の場合、変数のように「=」演算子を用いて代入する事はできません(*1)
     (例) int a[10], b[10];
         :
        a = b; //これはエラーになる
  
   [List2.1次元配列に慣れる(その2)]
   ┌───────────────────────────────────┐
    /* ===========================================
      各要素に乱数を入れて、最大値/最小値を求める
      =========================================== */
    #include <iostream>
    #include <cstdlib> //<stdlib.h>
    #include <ctime>  //<time.h>
    using namespace std;
    
    int main()
    {
      int i; //インデックス
      int i_min; //最小値
      int i_max; //最大値
      int i_list[10];
      
      randomize(); //void randmize():乱数種の初期化<stdlib.h>+<time.h>
      
      /* 乱数を代入する */
      for (i=0; i<10; i++){
        i_list[i] = rand(); //int rand():疑似乱数を返す<stdlib.h>
      }
      
      /* Max/Min値を初期化する */
      i_min = RAND_MAX; //RAND_MAX:rand()発生乱数の最大値<stdlib.h>
      i_max = 0;
      
      /* Max/Min値を求める ついでに要素の数値も表示 */
      for (i=0; i<10; i++){
        if (i_min > i_list[i]) i_min = i_list[i];
        if (i_max < i_list[i]) i_max = i_list[i];
        
        cout << "要素" << i << '=' << i_list[i] << endl;
      }
      
      /* 結果を表示する */
      cout << "最小値=" << i_min << endl;
      cout << "最大値=" << i_max;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [実行結果]
     要素0=28773
     要素1=9386
     要素2=238
     要素3=27047
     要素4=31373
     要素5=3145
     要素6=17359
     要素7=32578
     要素8=30204
     要素9=6456
     最小値=238
     最大値=32578
   └───────────────────────────────────┘


 境界検証は行われない
 
  ・C/C++は配列の境界検証を行いません。具体的には
     int a[10];
     a[10] = 1; //間違い!! a[0]〜a[9]までしか確保されていない
   等としても、コンパイラはエラーとして扱いません。
  
  ・ちなみに上記のa[10]は、メモリ位置として、a[9]の次にくる連続領域です。
   ┌───────────────────────────────────┐
      4byte  4byte      4byte  4byte
     ←──→←──→    ←──→←──→
     ┌───┬───┬   ┬───┬───┐
     │ a[0] │ a[1] │・・・│ a[9] │ a[10]│
     └───┴───┴   ┴───┴───┘
     │← ここは int a[10]; で確保 →│←??→│
   └───────────────────────────────────┘

  ・最初の定義で、a[0]〜a[9]についてはメモリ領域を確保するので、自由に読み書き
   できますが、a[10]の領域は他のデータとして使用されているかもしれないので、
   のようなプログラムを実行すると基本的に落ちます。

  
  ・「何故コンパイラはチェックしないのか?」という根本的な疑問があるでしょう。
   それは以下のような「コンパイラの設計思想」という言葉で片付けられています。
   ┌───────────────────────────────────┐
    C++の設計目標が、プロフェッショナルなプログラマに最高速の、可能な限り
    最も効率的なコードの作成能力を提供することにあります。そのため、C++に
    はプログラムの実行速度を(時には著しく)低下させるエラー検証がほとんど含
    まれていないのです。
    〜ハーバート・シルト氏著「標準講座C++」より〜
   └───────────────────────────────────┘
  
  ・この「厳しさ」はC/C++の根底に流れているものであり「容易に修得できない」原因
   ともなっていますが、そのおかげで「低レベル(マシンに近い)プログラミング」や
   「多種プラットフォームへの対応」が可能になっているのです。C/C++を使うには、
   この思想を了解しておく必要があります。


文字列

 文字列の定義
 
  ・文字列は、末尾がヌル文字(null character)となる文字配列として定義されます。
   ヌル文字とは、値(コード)が0の文字であり「\0」で表します。
  
  ・例えば
     char str[11];
   とした場合、10の文字(character)を保持できる、文字列(string)と扱うことができ
   ます。1文字少ないのは、ヌル文字の分です。

 
 文字列定数
 
  ・文字列定数は、ダブルクォート「"」で囲まれた文字のリストです。
    (例) "Hello"
  
  ・ダブルクォートで囲むとコンパイラは自動的にヌル文字を付加します。結果として
   文字列 "Hello" は以下のようなメモリ配置となります。
   ┌───────────────────────────────────┐
     ┌─┬─┬─┬─┬─┬─┐
     │ H│ e│ l│ l│ o│\0│ (1枠は 1byte:char型)
     └─┴─┴─┴─┴─┴─┘
   └───────────────────────────────────┘

  ・よって空文字列(null string)は、連続したダブルクォート「""」で表すことができ
   ます。このときの内部文字データはヌル文字のみです。
  

 キーボードから文字を読む
 
  ・キーボードから文字データを入力してみます。データを受け取る仕掛けとして、標
   準入力cinを使ってみます。

   [List3.cinで文字列データを受け取る]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      char s_str[80];
      
      cout << "文字を入力して下さい : ";
      cin >> s_str; //s_strは配列名
      
      cout << "入力された文字です : ";
      cout << s_str; //s_strは配列名
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [実行結果]
     文字を入力して下さい : this is a test
     入力された文字です : this
   └───────────────────────────────────┘
  
  ・この結果からわかるように、thisの部分しか取り込まれていません。この理由は、
   cinで文字列を読み込む場合、最初のホワイトスペース(white space)で読みとりを
   終了してしまうからです。ホワイトスペースに属する文字は以下の3種です。
     半角スペース : ' '
     タブ文字   : '\t'
     改行文字   : '\n'
  
  ・これを回避する関数レベルのソリューションとしては、標準ライブラリ関数gets()
   を使います。gets()のプロトタイプ(*2)は以下です。
     gets(配列名);
   尚、gets()を使用するには<stdio.h>ヘッダが必要です。
   
   [List4.gets()で文字列データを受け取る]
   ┌───────────────────────────────────┐
    #include <iostream>
    #include <cstdio> //<stdio.h>
    using namespace std;
    
    int main()
    {
      char s_str[80];
      
      cout << "文字を入力して下さい : ";
      gets(s_str); //s_strは配列名
      
      cout << "入力された文字です : ";
      cout << s_str; //s_strは配列名
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [実行結果]
     文字を入力して下さい : this is a test
     入力された文字です : this is a test
   └───────────────────────────────────┘

  ・さて、先にも出た話ですが、cin / gets()とも、データの境界検証は行いません。
   List3/4では入力データを受け取るための、79文字分の配列を用意しましたが、入力
   データが79文字を超えても入ってしまいます。結果としてプログラムが落ちるので
   このことは頭に入れておいて下さい。


 ヌル文字を利用する

  ・文字列はヌル文字で終わります。よって「文字列の最初から最後まで」という処理
   を行う場合には文字数を気にせず、「ヌル文字でなければ処理続行」という書き方
   が可能になります。
  
  ・これを利用して、入力された文字を大文字へ変換するプログラムを作ってみます。
   大文字への変換は、標準ライブラリ関数toupper()を使用します。toupper()関数を
   使うには<ctype.h>ヘッダが必要です。

   [List5.ヌル文字を利用する]
   ┌───────────────────────────────────┐
    #include <iostream>
    #include <cstring> // <string.h>
    #include <cctype> // <ctype.h>
    using namespace std;
    
    int main()
    {
      char s_str[80];
      int i;
      
      cout << "文字列を入力して下さい : ";
      gets(s_str);
      
      for (i=0; s_str[i]; i++) { //ヌル文字でなければ処理続行
        s_str[i] = toupper(s_str[i]); //大文字変換
      }
      
      cout << "大文字へ変換しました : ";
      cout << s_str;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [実行結果]
     文字列を入力して下さい : this is a test
     大文字へ変換しました : THIS IS A TEST
   └───────────────────────────────────┘
  
  ・さて、大文字変換のforループブロック条件式で
     s_str[i]
   と記述しています。forループの条件式は
     true : 続行
     false : 終了
   です。そして、C++におけるtrueとfalseの定義は
     true : 0以外
     false : 0
   となっています。そしてヌル文字は「コード(数値)0の文字」です。つまりヌル文字
   以外はtrue, ヌル文字はfalseとなり、ループ制御の論理判定にこれを利用していま
   す。このコーディングは多用されるので慣れるようにして下さい。
   

(*1)理由を理解するには次回説明するポインタの知識が必要です。配列名が意味するもの
  は何なのか。
  
(*2)gets()関数のプロトタイプを真面目に書くと
    char* gets(char* s_str);
  となります。引数はchar型配列の先頭アドレスです。戻り値には、読み込みが成功し
  た場合s_strの先頭アドレス、失敗した場合NULLが返ります。これにはポインタの概念
  が入っているので、本文中では一般的な記述を避けました。

[Revision Table]
 |Revision |Date    |Comments
 |----------|-----------|-----------------------------------------------------
 |1.00   |2002-05-19 |初版
 |1.01   |2002-05-26 |[進む]リンク変更
 |1.02   |2002-06-30 |語句修正
 |1.03   |2002-08-15 |リンク追加
[end]
Copyright(C) 2002 Altmo