C++言語解説:1-7.他のデータ型と演算子
2002-06-30

[概要] 変数のアクセス修飾子、記憶クラス修飾子、typedef、bit/条件演算子、省略記法、
    カンマ演算子、多重代入を学習します。またC++独自の動的メモリ確保についても
    扱います。

[構成]・アクセス修飾子
     * const修飾子
     * volatile修飾子
   ・記憶クラス指定子
     * auto指定子
     * extern指定子
     * 脱線:makeとMakefile
     * static指定子
     * register指定子
   ・その他の型及び型定義
     * 列挙
     * typedefキーワード
   ・ビット演算子
     * AND,OR,NOT,XOR演算子
     * ビットシフト演算子
   ・その他の演算子
     * 条件(?)演算子
     * 代入文の省略記法
     * カンマ演算子
     * 多重代入
     * sizeof演算子
   ・newとdelete
     * newとdeleteは演算子
     * newとdelete使用法
     * newメモリ確保時の初期化
     * 配列メモリの動的確保


アクセス修飾子

 const修飾子
 
  ・C++には、オブジェクトに対するアクセスを制御する型修飾子(access modifier)とし
   てconstvolatile があります。
  
  ・const修飾子を付けて定義したオブジェクトは、そのスコープ内で値を変更すること
   ができなくなります。

     (例) const int i_a = 10;
  
  ・const修飾子は、定義した変数を定数として扱いたい、又は参照渡しを行った関数で
   内容を変更されたくない場合等で利用します。例えばList1のようにconstが指すオ
   ブジェクトの内容を変更しようとすると、コンパイルエラーとなります。

  
   [List1.仮引数へのconst適用 (コンパイルエラーになる)]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    void StrChange(const char* cp_str, char c_a);
    
    int main()
    {
      char* cp_str = "abcdefgHIJKLMN";
      
      StrChange(cp_str, 'a');
      cout << cp_str << endl;
      
      return 0;
    }
    
    void Strhange(const char* cp_str, char c_a)
    {
      int i = 0;
      
      while(*(cp_str+i)!='\0'){
        if (*(cp_str+i) == c_a) *(cp_str+i)='*'; //NG
      }
      
      return;
    }
   └───────────────────────────────────┘
  

 volatile修飾子
 
  ・volatile修飾子は、対象変数/オブジェクトがプログラム以外からでも変更される可
   能性があることをコンパイラに示します。

  
  ・通常、コンパイラは「高速化」を念頭に置いた最適化を行いますが、OS割り込みな
   どによりプログラム内で明示的な記述が無いにも関わらず、値が変更されるオブジ
   ェクトに最適化を施すと都合の悪いケースがあります。
  
  ・よってこのvolatile修飾子により「最適化を避けるべきオブジェクト」であること
   をコンパイラに知らせているわけです。


記憶クラス指定子

 auto指定子
 
  ・記憶クラス指定子(memory class modifier)とは、コンパイラに変数/オブジェクト
   の格納方法を知らせる修飾子です。
  
  ・auto指定子は、指定した変数/オブジェクトが自動変数であることを意味します。た
   だし、ローカル変数はデフォルトがautoなので、このキーワードを使う機会はほと
   んど無いと思われます。
 
 
 extern指定子
 
  ・全ての関数の外で定義した変数やオブジェクトはグローバルな存在になりますが、
   そのスコープ(有効範囲)は、定義が記述されたソースコードファイルの中だけとな
   ります。
  
  ・しかし、大規模なプログラムを作成する場合、ソースファイルを複数に分割して共
   同開発します。そのときは外部ファイルに存在するオブジェクトを使用するケース
   も当然出てきます。
  
  ・その場合、オブジェクトが外部ファイルにあるかもしれない(*1)ことを示すために
   extern指定子を使用します。例としてList2を見て下さい。
   
   [List2.extern使用法(分割コンパイル)]
   ┌───────────────────────────────────┐
     <Prog.h>          <Subprog.h>
    ┌──────────┐  ┌───────────────┐
    │#ifndef PROG    │  │#ifndef SUBPROG        │
    │ extern int gi_a; │  │ void Subprog();       │
    │#define PROG    │  │#define SUBPROG        │
    │#endif       │  │#endif            │
    └──────────┘  └───────────────┘
     <Prog.cpp>         <Subprog.cpp>
    ┌──────────┐  ┌───────────────┐
    │#include <iostream> │  │#include <iostream>      │
    │#include "Prog.h"  │  │#include "Subprog.h"     │
    │#include "Subprog.h"│  │#include "Prog.h"       │
    │using namespace std;│  │using namespace std;     │
    │          │  │               │
    │int gi_a;      │  │void Subprog()        │
    │          │  │{               │
    │int main()     │  │  cout << gi_a + 10 << endl;│
    │{          │  │               │
    │  gi_a = 20;   │  │  return ;         │
    │  Subprog();   │  │}               │
    │          │  └───────────────┘
    │  return 0;    │
    │}          │
    └──────────┘
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     30
   └───────────────────────────────────┘
  
  ・List2の例では、Prog.cppで定義したグローバル変数gi_aををSubprog.cpp内でも使
   用しています。そこで、Prog.h内で
     extern int gi_a;
   と宣言し、Prog.hをインクルードしたSubprog.cppに対し、「gi_aは外部で定義され
   ている」
ことをextern指定子で示しています。
  
  ・同様に関数についても外部ファイルで定義されたものにはextern指定子を付けます。
   しかし関数についてはstatic(後述)なもの以外、常にexternであるとコンパイラか
   ら見なされているので実は省略できます。
List2の例では省略しています。
  
  ・ということは、ソースコードを複数ファイルに分割する場合、関数のプロトタイプ
   宣言は別ファイル化
しておいて、他のファイルがそれを取り込めるようにした方が
   良いことなります。
  
  
 脱線:makeとMakefile

  ・List2のコードをbcc32でコンパイルする場合、List3のような手順が必要です。-cオ
   プションはcompile onlyでオブジェクトファイルのみ作成します。-eオプションは
   リンクによる実行ファイル作成です。List3ではProg.objとSubprog.objをリンクして
   Progall.exeを作成しています。
  
   [List3.bcc32による分割コンパイル(手作業)]
   ┌───────────────────────────────────┐
    C:> bcc32 -c Subprog.cpp
    C:> bcc32 -c Prog.cpp
    C:> bcc32 -eProgall Prog.obj Subprog.obj
   └───────────────────────────────────┘
  
  ・今回の例ではソースコードが2分割なのでまだ良いですが、更にコードが大きくかつ
   多数に分割されたら非常に大変
であることは容易に想像できるでしょう。
  
  ・このような分割コンパイルを行う際、通常はMakefileを作成し、makeコマンドでコ
   ンパイルを実施します。1行目はProgallがProg.objとSubprog.objから生成される
   ことを意味し、2行目はタブで字下げ後、コマンドを記述しています。
   

   [List4.makeによる分割コンパイル]
   ┌───────────────────────────────────┐
     <Makefile : すごくシンプルな例>
    ┌─────────────────────┐
    │Progall : Prog.obj Subprog.obj      │
    │  bcc32 -eProgall Prog.obj Subprog.obj │ ←字下げ部はタブ
    │Prog.obj : Prog.cpp Prog.h Subprog.h   │
    │  bcc32 -c Prog.cpp           │
    │Subprog.obj : Subprog.cpp Subprog.h Prog.h│
    │  bcc32 -c Subprog.cpp         │
    └─────────────────────┘
    
     C:>make
     MAKE Version 5.2 Copyright (c) 1987, 2000 Borland
         bcc32 -c Prog.cpp
     Borland C++ 5.5 for Win32 Copyright (c) 1993, 2000 Borland
     Prog.cpp:
         bcc32 -c Subprog.cpp
     Borland C++ 5.5 for Win32 Copyright (c) 1993, 2000 Borland
     Subprog.cpp:
         bcc32 -eProgall Prog.obj Subprog.obj
     Borland C++ 5.5 for Win32 Copyright (c) 1993, 2000 Borland
     Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
   └───────────────────────────────────┘
  
  ・makeコマンドの便利なところは、分割コンパイルの手間が省けるだけでなく、タイ
   ムスタンプを参照して必要なものだけをコンパイルしてくれる
ところにあります。
  
  ・Makefileの作り方についてはコンパイラによって様々なやり方があります。それに
   ついてはコンパイラのマニュアルを参照して下さい。


 static指定子
 
  ・static指定子を付けたオブジェクトは、そのスコープ内で永続変数(permanent
   variable)
となります。
  
  ・ローカル変数への適用であれば、その関数又はコードブロックが終了しても変数の
   メモリ領域は永続的に残り
、スコープの制限されたグローバル変数のように振る舞
   います。
     (例) static int i_a;
  
  ・staticな変数には初期値を与えることもできます。これはプログラムに開始時に1度
   だけ与えられる初期値
となります。
     (例) static int i_a = 100;

   [List5.staticなローカル変数]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    void SubFunc();
    
    int main()
    {
      for (int i=0; i<5; i++) SubFunc();
      return 0;
    }
    
    void SubFunc()
    {
      static int i_a = 10;
      
      i_a++;
      cout << i_a << " ";
      
      return;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     11 12 13 14 15
   └───────────────────────────────────┘
  
  ・グローバル変数にstatic指定子を適用した場合、スコープがそのファイルに制限さ
   れ、他のファイルからは参照できなくなります。ただし、この使用法は推奨されて
   いません。原著ではnamespace(今後のテキストで説明)を使用すべきだと言ってい
   ます。


 register指定子
 
  ・register指定子が付いた変数は、可能な限り高速でアクセスできるようにコンパイ
   ラが工夫
をします。
  
  ・register指定子が付いた場合、多くはCPUのレジスタに変数が割り当てられます。し
   かしレジスタは数が限られているため、指定した全ての変数がレジスタに割り当て
   られるとは限りません。無効だった場合はauto指定として扱われます。よって
   register指定子を適用するオブジェクトは吟味が必要です。
  
  ・register指定子はその性質上、動的な変数にのみ適用できます。つまり対象は仮引
   数を含めたローカル変数です。

  
  ・尚、レジスタにはアドレスの概念が無いため、register指定子を伴って定義された
   オブジェクトからは&演算子等によるアドレス計算ができません(*2)。この点は要注
   意です。


その他の型及び型定義

 列挙
 
  ・C++では列挙(enumeration)と呼ばれる名前付き整数定数リストを定義することがで
   きます。以下の形で宣言します。

   [List6.列挙の定義例]
   ┌───────────────────────────────────┐
     enum 列挙型名 { 列挙リスト} 変数リスト;

     (例) enum Rpg {
          Final_Fantasy,  // 0
          Dragon_Quest,   // 1
          Tales_Of_,    // 2
          Star_Ocean,    // 3
          Wild_Arms     // 4
        } action, movie;
   └───────────────────────────────────┘

  ・上記の例ではRpg列挙型で、actionとmovieという2つの列挙型オブジェクトを定義し
   ています。列挙型オブジェクトは型名を用いて後から定義できるので、変数リスト
   は無くても構いません。

     (例) Rpg action;
  
  ・enum自身の中身は整数定数であり、各シンボルには定義順に0から整数定数が割り当
   てられます
。なので式中では整数に読み替えられますが、整数が列挙型へ読み替え
   られることはありません。それではList7で使用例を見てみます。

   [List7.列挙の使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    enum Rpg {
      Final_Fantasy, Dragon_Quest, Tales_Of_, Star_Ocean, Wild_Arms
    };
    
    void v_DisplayInfo(Rpg Rpg_action);
    
    int main()
    {
      Rpg Rpg_action;
      
      Rpg_action = Final_Fantasy; v_DisplayInfo(Rpg_action);
      Rpg_action = Dragon_Quest; v_DisplayInfo(Rpg_action);
      Rpg_action = Tales_Of_;   v_DisplayInfo(Rpg_action);
      Rpg_action = Star_Ocean;  v_DisplayInfo(Rpg_action);
      Rpg_action = Wild_Arms;   v_DisplayInfo(Rpg_action);
      
      return 0;
    }
    
    void v_DisplayInfo(Rpg Rpg_action)
    {
      switch(Rpg_action) {
        case Final_Fantasy : {
          cout << "Regular Title!!" << endl;
          break;
        }
        case Dragon_Quest : {
          cout << "Best Story!!" << endl;
          break;
        }
        case Tales_Of_ : {
          cout << "2-Dimension Battle!!" << endl;
          break;
        }
        case Star_Ocean : {
          cout << "3-Dimension High-Speed Battle!!" << endl;
          break;
        }
        case Wild_Arms : {
          cout << "Solve a riddle!!" << endl;
          break;
        }
      }
      return;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     Regular Title!!
     Best Story!!
     2-Dimension Battle!!
     3-Dimension High-Speed Battle!!
     Solve a riddle!!
   └───────────────────────────────────┘
  
  ・enumの要素番号は初期化子(initializer)で指定できます。その際要素番号はList8
   のようになります。
  
   [List8.列挙-初期化子の使用例]
   ┌───────────────────────────────────┐
     (例) enum Rpg {
          Final_Fantasy,  // 0
          Dragon_Quest,   // 1
          Tales_Of_ = 10,  // 10
          Star_Ocean,    // 11
          Wild_Arms     // 12
        } action, movie;
   └───────────────────────────────────┘


 typedefキーワード
 
  ・C++ではtypedefキーワードにより、新しいデータ型名を定義できます。
     typedef 型名 新しい型名
     (例)typedef long int money;
  
  ・List9に使用例を示します。

   [List9.typedef使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    typedef long int money;

    int main()
    {
      money a = 10000L;
      cout << a;
      
      return 0;
    }
   └───────────────────────────────────┘
  

ビット演算子

 AND,OR,NOT,XOR演算子
 
  ・C++はビット演算子(bitwise operator)を持ちます。ビット演算はint型/char型系に
   のみ適用できる
演算子です。doubleやfloat等他の型では使用できません。
  
  ・AND, OR, NOT, XORのビット演算子は、以下となります。
     & : AND
     | : OR
     ~ : NOT (単項演算子)
     ^ : XOR (排他的論理和)
   List10に使用例を示します。
 
   [List10.ビット演算子 AND,OR,NOT,XOR使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      int i_a = 0x000000F0; //intが4byte仮定
      cout << "AND : " << hex << (i_a & 0x0000FFFF) << endl;
      cout << "OR : " << hex << (i_a | 0x000000FF) << endl;
      
      i_a = 0xFFFF0F0F;
      cout << "NOT : " << hex << (~i_a) << endl;
      
      i_a = 0x00000F0F;
      cout << "XOR : " << hex << (i_a ^ 0x0000FFFF) << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     AND : f0
     OR : ff
     NOT : f0f0
     XOR : f0f0
   └───────────────────────────────────┘


 ビットシフト演算子
 
  ・ビットシフト演算子(bit-shift operator)は変数のビットを指定した桁だけシフト
   させます。この演算子もint型/char型系にのみ適用できます。
     変数 >> ビット数; //右シフト
     変数 << ビット数; //左シフト
  
  ・List11にビットシフト演算子の使用例を示します。

   [List11.ビットシフト演算子使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      int i_a = 0xF0F0; //intが4byte仮定
      cout << "right 4bit : " << hex << (i_a >> 4) << endl;
      cout << "left 4bit : " << hex << (i_a << 4) << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     right 4bit : f0f
     left 4bit : f0f00
   └───────────────────────────────────┘
  

その他の演算子

 条件(?)演算子
 
  ・?は条件を表す演算子です。3項演算子(ternary oprator)とも呼ばれます。
     expr1 ? expr2 : expr3;
   のように書いた場合
     if (expr1) expr2;
     else expr3;
   と同じ意味になります。
  
  ・条件演算子では、expr2とexpr3の間にコロン(:)を置くことがポイントです。
 
 
 代入文の省略記法
 
  ・C及びC++では
     a = a + 10;
   等の代入演算があります。この例で挙げた +演算子の他に、-, *, / もありますが
   これらには省略記法が適用できます。
     変数 2項演算子(+,-,*,/)= 式;
  
  ・実際の使用例を以下に示します。
     a += 10;  // a = a + 10;
     a -= 10;  // a = a - 10;
     a *= 10;  // a = a * 10;
     a /= 10;  // a = a / 10;


 カンマ演算子
 
   ・カンマ演算子で結びつけられた式は、左から右の順番に実行されていきます。
     変数 = (式1, 式2, ・・・, 式n);
  
   ・例えば
     int x = 1;
     y = (x++, x*10, x+100);
    となったとき、yの値は120となります。
  
   ・カンマ演算子は、一般の演算よりもfor文等でよく使用されます。
     for(i=0, j=10; i<10; i++, j++){・・・}


 多重代入
  
  ・C++では多重代入文を使用することができます。次の文では、a,b,cに100を代入して
   います。
     a = b = c = 100;
  
  ・複数の変数に共通値を設定する場合に用います。


 sizeof演算子
 
  ・sizeofはデータ型又はオブジェクトのメモリサイズを得る演算子です。sizeof演算
   子は環境によって異なるオブジェクトのメモリサイズを処理するのに有用です。演
   算子の対象(データ型orオブジェクト)により表記法が変わります。
     sizeof (データ型)
     sizeof オブジェクト  ←()を付けてもOK
  
  ・sizeof演算子の使用例をList12に示します。
  
   [List12.sizeof演算子使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      char c_a;
      int i_b;
      char c_b[10];
      
      cout << "int : " << sizeof (int) << endl;
      cout << "char: " << sizeof (char) << endl;
      cout << "c_a : " << sizeof c_a << endl;
      cout << "i_a : " << sizeof i_a << endl;
      cout << "c_b[10] : " << sizeof (c_b) << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     int : 4
     char: 1
     c_a : 1
     i_b : 4
     c_c[10] : 10
   └───────────────────────────────────┘
   
   
newとdelete

 newとdeleteは演算子
 
  ・プログラム実行時のメモリ割り当てを、動的メモリ割り当て(dynamic memory
   allocation)
と呼びます。Cであればmalloc()関数やfree()関数を用いました。
  
  ・しかし多種のオブジェクトをプログラマが定義できるC++では、動的メモリ割り当て
   に関する機能もプログラマで実装できる
ようにする必要があり、「オブジェクト操
   作は演算子」の原則から動的メモリ割り当てに対してnewとdeleteの演算子が規定
   れました。
  
  ・これは処理系/コンパイラ実装により異なりますが、動的メモリは通常ヒープ(heap)
   領域から取得されます。ヒープ領域はプログラム(固定)領域とスタック領域以外の
   メモリ空き領域を指します。
  
  ・従ってヒープ領域は無限ではありません。場合によっては使い果たすこともありま
   す。new演算子でメモリ確保に失敗した場合の処理は、C++標準の規定(例外投入)以
   外にコンパイラが処理系に特化した独自機能を持つケースもあります。
従って、コ
   ンパイラが実装するnew/deleteの機能については必ずマニュアルをチェックすべき
   です。
  
  ・例外処理については、2-7章で説明します。本章ではnewが失敗するような大規模な
   メモリ確保を行うサンプルコードはありません。
   

 newとdeleteの使用法

  ・newでメモリ割り当て、deleteでメモリ解放を行います。以下の形式で記述します。
     オブジェクトへのポインタ = new オブジェクトの型;
     delete オブジェクトへのポインタ;
   最もシンプルな例をList13に示します。
   
   [List13.newとdelete:最もシンプルな例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      int* ip_a;    //int型ポインタを用意
      ip_a = new int; //int型サイズメモリーをip_aに割り当て
      
      *ip_a = 10;
      cout << *ip_a << endl;
      
      delete ip_a;   //メモリ解放
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     10
   └───────────────────────────────────┘
  
  ・ポインタip_aから見れば、すでに存在するオブジェクトからアドレス情報をもらう
   ことと、new演算子で確保した領域アドレス情報をもらうことに違いはありません。
   違いといえば、newで確保した領域はdeleteで解放する必要があるという点です。
  

 newメモリ確保時の初期化
 
  ・newでメモリ確保した場合、初期値を与えることができます。使用例をList14に示し
   ます。
  
   [List14.newとdelete:初期化使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    int main()
    {
      int* ip_a;    //int型ポインタを用意
      char* cp_b;   //char型ポインタを用意
      ip_a = new int (10);  //int型10で初期化
      cp_b = new char ('A'); //char型'A'で初期化
      
      cout << *ip_a << endl;
      cout << *cp_b << endl;
      
      delete ip_a;   //int型メモリ解放
      delete cp_b;   //char型メモリ解放
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     10
     A
   └───────────────────────────────────┘


 配列メモリの動的確保
 
  ・newを使用すれば、配列メモリも確保することができます。ただしこの場合は先ほど
   説明した初期化を行うことができません。領域の確保のみです。
  
  ・new/deleteで1次元配列を確保/解放する書式を以下に示します。
     オブジェクトへのポインタ = new オブジェクトの型 [個数];
     delete[] オブジェクトへのポインタ;
  
  ・newでアドレスを割り当てられたポインタは、配列名のように扱うことができます。
   ポインタなのでアドレスを変更することもできますが、deleteでそのポインタを利
   用するので、書き換えは避けた方が良いでしょう。List15に使用例を示します。
 
   [List15.newとdelete:使用例(1)]
   ┌───────────────────────────────────┐
    #include <iostream>
    #include <cstdio>  //<stdio.h>
    #include <cstring> //<string.h>
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      FILE* fp;
      int i_rowcount = 0; //行数カウント
      char* cp_row[100]; //100行分のデータ
      char c_buf[128];  //バッファ用文字列データ
      int i;
      
      if (argc < 2) return 1;
      if ((fp=fopen(argv[1],"rt"))!=NULL){

        //メモリ確保+読み込みのループ
        while(fgets(c_buf,1024,fp)!=NULL){
          cp_row[i_rowcount] = new char [strlen(c_buf)+1];
          strcpy(cp_row[i_rowcount],c_buf);
          i_rowcount++;
        }
        
        //読み込み結果の表示
        for (i=0; i<i_rowcount; i++) cout << cp_row[i];
        
        //メモリ解放
        for (i=0; i<i_rowcount; i++) delete[] cp_row[i];
      }
      fclose(fp);
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     <読み込みデータ>          <出力データ>
    ┌───────────────┐ ┌───────────────┐
    │R.E.M             │ │R.E.M             │
    │U2              │ │U2              │
    │Lenny Kravitz         │ │Lenny Kravitz         │
    │Boston            │ │Boston            │
    │Tom Petty & The Heart Breakers│ │Tom Petty & The Heart Breakers│
    └───────────────┘ └───────────────┘
   └───────────────────────────────────┘
  
  ・List15ではあらかじめ100行分のポインタ配列を用意し、それぞれのポインタにnew
   で確保した文字数分のデータ領域を渡しています。

   [Fig1.newとdelete:使用例(1)-メモリ確保図]
   ┌───────────────────────────────────┐
          ┌────┐
    cp_row[0] ←│"R.E.M" │
          ├──┬─┘
    cp_row[1] ←│"U2"│
          ├──┴─────┐
    cp_row[2] ←│"Lenny Kravitz" │
     :     ├────┬───┘
     :       :
   └───────────────────────────────────┘
  
  ・次に行数/文字数とも動的なメモリ確保を行います。例をList16に示します。

   [List16.newとdelete:使用例(2)]
   ┌───────────────────────────────────┐
    #include <iostream>
    #include <cstdio>  //<stdio.h>
    #include <cstring> //<string.h>
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      FILE* fp;
      int i_rowcount = 0; //行数カウント
      char** cp_row;   //ポインタのポインタ
      char c_buf[128];  //バッファ用文字列データ
      int i=0;
      
      if (argc < 2) return 1;
      if ((fp=fopen(argv[1],"rt"))!=NULL){

        //行数メモリ確保
        while(fgets(c_buf,1024,fp)!=NULL) i_rowcount++;
        cp_row = new char* [i_rowcount]; //行数分ポインタ配列確保
        
        rewind(fp);
        
        //文字データメモリ確保+読み込み
        while(fgets(c_buf,1024,fp)!=NULL){
          cp_row[i] = new char [strlen(c_buf)+1];
          strcpy(cp_row[i],c_buf);
          i++;
        }

        //データ出力
        for(i=0; i<i_rowcount; i++) cout << cp_row[i];

        //メモリ解放
        for (i=0; i<i_rowcount; i++) delete[] cp_row[i];
        delete[] cp_row;
      }
      fclose(fp);
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     <読み込みデータ>          <出力データ>
    ┌───────────────┐ ┌───────────────┐
    │R.E.M             │ │R.E.M             │
    │U2              │ │U2              │
    │Lenny Kravitz         │ │Lenny Kravitz         │
    │Boston            │ │Boston            │
    │Tom Petty & The Heart Breakers│ │Tom Petty & The Heart Breakers│
    └───────────────┘ └───────────────┘
   └───────────────────────────────────┘
  
  ・List16では始めに行数をカウントして、行数分のポインタ配列をnewで確保していま
   す。それから確保した各要素ポインタで文字数データをnewで確保し、文字列データ
   をコピーしています。
  
  ・解放は、各要素ポインタが持つ文字列データ領域を開放してから、要素ポインタ自
   身の領域を開放しています。確保の逆です。

   [Fig2.newとdelete:使用例(2)-メモリ確保図]
   ┌───────────────────────────────────┐
    cp_row ←┌─────┐ ┌────┐
         │cp_row[0] │←│"R.E.M" │
         ├─────┤ ├──┬─┘
         │cp_row[1] │←│"U2"│
         ├─────┤ ├──┴─────┐
         │cp_row[2] │←│"Lenny Kravitz" │
         ├─────┤ ├────┬───┘
         │cp_row[3] │←│"Boston"│
         ├─────┤ ├────┴───────────┐
         │cp_row[4] │←│"Tom Petty & The Heart Breakers"│
         └─────┘ └────────────────┘
   └───────────────────────────────────┘


(*1)extern指定子がある場合、自分自身も含めたどこかのファイルに定義があるという意
  味になります。必ず外部のファイルにあるというわけではありません。

(*2)register指定を行っても、全てautoにする処理系もあるので、本当にアドレス計算が
  できないかどうかは実装によります。

[Revision Table]
 |Revision |Date    |Comments
 |----------|-----------|-----------------------------------------------------
 |1.00   |2002-06-30 |初版
 |1.01   |2002-07-02 |リンク追加
[end]
Copyright(C) 2002 Altmo