] > Programing Language

第 3 回 プログラミング言語

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

3-1. C 言語

ローカル変数、グローバル変数

C 言語では関数の内部で宣言された変数は、その関数の外部(他の関数など)か らはアクセスできません。 これをローカル変数と言います。 一方、関数の外側でも変数を宣言できます。 これをグローバル変数と言います。 グローバル変数へはどんな関数でもアクセスできます。 但し、別のファイルの中にあるグローバル変数にアクセスするには関数のプロ トタイプのように宣言をする必要があります。 グローバル変数を宣言するのがextern 宣言 です。構文は次の通りです。


extern 変数名

これは、グローバル変数であることを宣言しているだけで、変数そのものの宣 言になってません。そのため、一つのソースファイルでグローバル変数の宣言 をする必要があります。 一般に、このような extern 宣言はヘッダファイルに格納します。

但し、グローバル変数の利用には注意が必要です。 グローバル変数は全ての関数からアクセスが可能なので、プログラムミ スなどによりグローバル変数の値がおかしくなった時など、原因の追求が大変 です。プログラム分割をシンプルにしたい立場からも、グローバル変数という 全関数が知らなければならないような情報はない方が良いです。 但し、 C++ などのオブジェクト指向言語ではないので、関数の外部に情報を 蓄えるにはグローバル変数を使う必要があるでしょう。 逆に C++ 言語、Java 言語などのオブジェクト指向言語では、特定の変数とそ れにアクセスする関数をまとめてクラスとして扱えるので、グローバル変数を 使わないようにすべきです。 変数など、不必要な情報へのアクセスを制限することをオブジェクト指向では カプセル化と言います。

なお、グローバル変数と同じ名前の変数をローカル変数として宣言可能です。 おなじ名前になった時は、グローバル変数にはアクセスできず、ローカル変数 にだけアクセスできます。

また、関数の宣言の時に宣言する引数は、値を読み出すことはできますが、そ れは値が与えられたローカル変数なので、書き換えた値は呼び出した側には影 響しません。

演習3-1

次のプログラムがどのように動くか予想し、また、実際に動かして動作を確か めなさい。


#include <stdio.h>
int i;
int a(int i){
   i++;
   return i;
}
int b(int j){
   i++;
   return i;
}
int c(int k){
  int i;
  i=0;
  return i;
}
main(){
  i=1;
  printf("%d\n",i);
  printf("%d ",a(i));
  printf("%d\n",i);
  printf("%d ",b(i));
  printf("%d\n",i);
  printf("%d ",c(i));
  printf("%d\n",i);
}  

キャスト

ある型を別の型として扱いたい時に、一時的に別の型を指定する方法があります。 例えば、整数(int)の値 1 と 2 を考えた場合、個数や合計は整数(int)になり ますが、平均値は実数(float)になります。 C 言語では(整数)/(整数)の結果は整数になってしまうため、割算をする時、 float だと指定する必要があります。そこで、「(型) 式」と書くと小数点以 下を切り捨てられず、結果が float 型になります。


#include <stdio.h>
main(){
  int a[]={1,2,-1}
  int i;
  int goukei, kosuu;
  float heikin;

  kosuu=0;
  goukei=0;
  for(i=0;a[i]!=-1;i++){
    kosuu++;
    goukei+=a[i];
  }
  heikin=(float)goukei / kosuu;
  printf("個数=%d, 合計=%d, 平均=%f\n",
          kosuu, goukei, heikin);
}

ポインタ

C 言語では、変数の値の格納しているメモリの番地を取り扱うことができます。 変数 x に対して、その変数の値のメモリの番地は &x で表します。 また、この番地を変数に代入して処理することもできます。 int 型の変数の番地を扱う変数の型は int * で表します。 int *x; と宣言すると x は int の変数の番地を取り扱うこと ができます。 そして int y; と整数型変数 y を宣言しておくと、 x = &yで y の番地を代入できます。 また、 *x とするとこれは x に格納されているメモリの番地に入っている値 を表します。 もし y の値が 1 ならば、*x も 1 になります。 このメモリの番地を扱う変数を ポインタ と言います。 このメモリの番地やポインタには様々な利用法があります。

関数とのデータのやりとり

関数を呼び出す時、引数の処理のしかたには次の 3 種類があります。

値呼び出し call by value
関数を呼び出す時、引数の値をコピーして関数に渡します。 関数内で引数の変数の変更をしても、呼び出し側の変数は変更されません。
変数呼び出し call by value (参照呼び出し call by reference)
関数を呼び出す時、引数として指定された変数は呼び出し側の変数と同一のも のとして扱われます。 したがって、関数内で引数を変更すると、呼び出し側でその値を参照できます。
名前呼び出し call by name
関数を呼び出す時に、引数に式や関数が指定されている場合、引数を評価 する必要がない可能性を考慮して、関数に引数を式のまま持っていきます。 値を計算する必要が生じる時だけ評価します。 なお、評価が高々一回だけ行われる場合、必要呼び出し call by need と呼ば れます。

C 言語では値呼び出しだけがサポートされています。 また、C 言語では関数の中で宣言された変数はローカル変数です。 したがって、ある変数の値を表示する関数は何の工夫もなしに作ることができ ますが、その変数の値を変更するようなプログラムは何の工夫もなくは作れま せん。 例えば次のような関数に意味はありません。


int f(int x){
   x=1;
   return 0;
}

この関数を f(y) と呼び出しても、 y の値は変わりません。

さて、関数で変数の値を変えるにはどうすればよいでしょうか? もし、一つの変数の値だけなら、 a=f(a) のように関数の戻り 値を代入することで f から a の値を変更できます。 しかし、二つ以上の変数に対しては行えません。 例えば、変数 a と変数 b の値を交換することを考えます。 関数さえ呼び出さなければ次のようにすればできます。


int a=4,b=1;
int c;

c=a;
a=b;
b=c;

この処理はしばしば出てくるので、これを関数としたいところです。 ところが次のようにしても思うように動きません。


#include <stdio.h>
swap(int x, int y){
  int z;
  z=x;
  x=y;
  y=z;
  return;
}
main(){
  int a,b;
  a=2; b=3;
  swap(a,b);
  printf("a=%d, b=%d\n",a,b);
}

関数の引数の宣言での x, y はローカル変数です。 従って、呼び出す側の変数と値は同じですが、既に別の変数ですので、代入し ても呼出側の変数の値は変わりません。

そこで、ポインタを使います。 呼び出し側の変数の番地をサブルーチンに渡し、関数では、その番地に値を書 き込むのです。 次のようにすると思った通りの動きになります。


#include <stdio.h>
swap(int *x, int *y){
  int z;
  z=*x;
  *x=*y;
  *y=z;
  return;
}
main(){
  int a,b;
  a=2; b=3;
  swap(&a,&b);
  printf("a=%d, b=%d\n",a,b);
}

配列の要素を指す

C 言語での配列名は実はポインタです。 配列 a の 0 番目の要素が格納されているメモリ上の番地が a になっていま す。つまり、 &a[0] と a は等しいです。ですから、a[0] と *a は等し いです。


#include <stdio.h>
main(){
  double a[]={1.0,2.0,3.0,-1.0};
  printf("&a[0]=%d, a=%d\n",(int)&a[0],(int)a);
  printf("a[0]=%f, *a=%f\n",a[0],*a);
}

では a[1] と a の関係はどうでしょうか? double のように一文字では表せないようなデータは、複数の番地に渡って格 納されているため、 a の隣の番地はまだ a[0] の内容の続きが入っています。 sizeof 演算子を使うと、その型が何バイトの記憶領域を使うかが 分かります。 筆者の環境では sizeof (double) の値は 8 になっています。 つまり、一つの double 型の変数を格納するのに 8 バイトの記憶領域が必要 になってきます。 ですから、 a[1] の番地は a[0] の番地から 8 バイト先の位置に記憶されて います。 結局、 C 言語の配列というのは次のようにして計算された値になります。

a[i] = *( a+i *sizeof(a[0] ))

ところが、ポインタの計算では、何の型のポインタかが明らかなため、 +1 を すると、次の要素を指すように値が増えます。つまり、その型のサイズ分だけ 増えます。 結局 a[1]=*(a+1) となります。


#include <stdio.h>
main(){
  double a[]={1.0,2.0,3.0,-1.0};
  printf("&a[1]=%d, a=%d, sizeof double = %d\n",
	 (int)&a[1],(int)a,sizeof (double));
  printf("a + 1 * sizeof a[0] = %d, a + 1 =%d \n",
	 (int)a + 1*sizeof a[0], (int)(a+1)); 
  printf("a[1]=%f, *(a+1)=%f\n",a[1],*(a+1));
}

これを使うと、配列の値を次々指し示す場合に ++ などのインクリメントが使えま す。 またループ変数を無くすこともできます。


#include <stdio.h>
main(){
  double a[]={1.0,2.0,3.0,-1.0};
  double *x;
  for(x=a; *x != -1; x++){
    printf("%f\n",*x);
  }
}

構造体

座標のように、複数の値を対に用いたい時に使うのが構造体です。 座標の構造体の型は次のように宣言されます(最後のセミコロン(;)は必須です)。


struct point {
  double x;
  double y;
};

そして、この後、座標の変数を宣言するには次のようにします。


struct point p;
struct point *q;
struct point r={1.0,2.0};

プログラムの中では、まず、構造体から構造体へは代入が可能です。 p=rq=&p*q=r な どできます。 また、構造体の内部の値はピリオド(.)の後にフィールド名を書きます。 p.x=3(*q).y=4 などが可能です。 また、特に、(*q).yq->yと書くこ とができます。

文字配列

C 言語では文字配列の関数が提供されています。 利用するには string.h ヘッダファイルを読み込む必要があります。 その時、次を行うプログラムが以下に示すように簡単に書けます。 なお、文字列の最後には必ず '\0' を入れておく必要があります。

  1. 文字列の長さを求める
  2. 文字列中で最初に特定の文字が出現する位置
  3. 文字列中に含まれる特定の文字の数
  4. 文字列の結合

関数を使わない場合

  1. 
    #include <stdio.h>
    main(){
      char x[]="This is a pen.";
      int i,k;
      k=0;
      for(i=0;x[i]!='\0';i++){
        k++;
      }
      printf("%d\n",k);
    }
    
  2. 
    #include <stdio.h>
    main(){
      char x[]="This is a pen.";
      char y='p';
      int i,k;
      k=0;
      for(i=0;(x[i]!='\0')&&(x[i]!=y);i++){
        k++;
      }
      if(x[k]!='\0'){
        printf("%d\n",k);
      }else{
        printf("Not found.\n");
      }
    }
    
  3. 
    #include <stdio.h>
    main(){
      char x[]="This is a pen.";
      char y='p';
      int i,k;
      k=0;
      for(i=0;x[i]!='\0';i++){
        if(x[i]==y){
          k++;
        }
      }
      printf("%d\n",k);
    }
    
  4. 
    #include <stdio.h>
    main(){
      char x[]="This is a pen.";
      char y[]="That is a book.";
      char z[50];
      char *p,*q;
      for(p=x,q=z;*p!='\0';*(q++)=*(p++));
      for(p=y;*p!='\0';*(q++)=*(p++));
      *q='\0';
      printf("%s\n",z);
    }
    

関数を使った場合

  1. 
    #include <stdio.h>
    #include <string.h>
    main(){
      char x[]="This is a pen.";
      printf("%d\n",strlen(x));
    }
    
  2. 
    #include <stdio.h>
    #include <string.h>
    main(){
      char x[]="This is a pen.";
      char y='p';
      char *z;
      z=strchr(x,y);
      if(z!=NULL){
        printf("%d\n",(int)(z-x));
      }else{
        printf("Not found.\n");
      }
    }
    
  3. 
    #include <stdio.h>
    #include <string.h>
    main(){
      char x[]="This is a pen.";
      char y='p';
      char *z;
      int k;
      k=0;
      for(z=strchr(x,y);z!=NULL;z=strchr(z+1,y)){
        k++;
      }
      printf("%d\n",k);
    }
    
  4. 
    #include <stdio.h>
    #include <string.h>
    main(){
      char x[]="This is a pen.";
      char y[]="That is a book.";
      char z[50];
      strcpy(z,x);
      strcat(z,y);
      printf("%s\n",z);
    }
    

3-2. C++言語

C++ 言語は C 言語に Simula67 で開発されたオブジェクト指向の考え方を導入 したものです。 これはプログラミングの方法論を変えてしまう仕組みです。 C++ 言語を使う以上、C++ の流儀に基づいてプログラミングをすべきです。

コメント

C 言語ではコメントとして/* と */ を使いましたが、 C++ 言語ではさらに // から行末までもコメントとして取り扱います。 /* */ は入れ子に出来ませんので、 // でコメントを付けた部分は、後で /* と */ で囲めるので便利です。 例えば、プログラムの注釈のコメントは // の後に書くように統一すると、プ ログラムの特定部分を無効にするのに /* と */ で括っても、 // のコメント は含むことができるので、便利です。

オーバーロード

C++ 言語では引数の型だけが違う同じ名前の関数が許されています。 これをオーバーロードと言います。 これは、C 言語では禁止されています。

名前空間

大きなプロジェクトにおいて関数、変数、クラスなどの名前は衝突する可能性 があります。 そのため、これらの名前に名字を付けるような感覚で、名前空間という手法を 使うことができます。


namespace sakamoto {
  int i;
  double f(double x);
  class C {
    C* create(); 
  };
}

また namespace でプロトタイプを定義した場合、 namespace 外でスコープ演算子 :: を用いて定義できます。


double sakamoto::f(doube x){
  return 1.2;
}
sakamoto::C* sakamoto::C::create(){
  return new sakamoto::C();
}

このように定義すると他で定義された変数 i や関数 f(x) やクラス C と分け ることができます。 実際に使う際には次のようにします。

スコープ演算子を使用

main(){
  sakamoto::C x;
  x.show();
}
using を使って、使用する名前を登録

using sakamoto::C;
main(){
  C x;
  x.show();
}
using を使って、名前空間を登録

using namespace sakamoto;
main(){
  C x;
  x.show();
}

このうち最後の名前空間を登録する方法は簡便ですが、名前の衝突を避けると いう本来の名前空間の目的に反しているので推奨できません。

なお、C++ はもともとは名前空間を持たず、途中から導入されました。 名前空間を持たなかった頃のヘッダファイルには C 言語同様に .h の拡張子 が付いていました。 ですから、iostream.h は名前空間が定義されていません。 一方 iostream と .h の付かないヘッダファイルには std という名前空間が 定義されています。 従って、例えば iostream を使って標準出力に値を出力するには次のようにし ます。

  1. 
    #include <iostream>
    main(){
      std::cout << 1 << std::endl;
    }
    
  2. 
    #include <iostream>
    using std::cout;
    using std::endl;
    main(){
      cout << 1 << endl;
    }
    
  3. 
    #include <iostream>
    using namespace std;
    main(){
      cout << 1 << endl;
    }
    

クラス

C++ では C 言語とのしがらみがない限りは、構造体を使いません。 その代わり、構造体の拡張のような形で クラス が導入されています。 クラスの内部では変数だけではなく、関数のプロトタイプを書くことができま す。

class はオブジェクト指向を実現するために導入されました。 オブジェクト指向とは、メッセージのやりとりを行うオブジェクトを用いてプ ログラムを作成する手法です。 オブジェクトとは変数とメソッドをもつものです。 オブジェクトを生成し、それに「メソッド+引数」というメッセージ を送ることで計算を行います。 このオブジェクトの設計図と言えるものがクラスです。 クラスでは生成したオブジェクトが持つ変数宣言と、メソッドの定義を行いま す。メソッドの定義はクラスの中では関数のように定義します。

なお、オブジェクト指向プログラミングでは外部から変数への直接アクセスを 禁止し、必ずメソッドのみでオブジェクトを操作することが好まれます。 このようにオブジェクト指向で変数などを隠蔽することをカプセル化 と言います。 また、メソッドも外部に公開するものと内部だけで使うものに分かれますが、 特に外部に公開するメソッドをインターフェイスと言います。 このようにすると、オブジェクトの操作方法は限定されたものになるため、操 作に必要な情報が少なくて済みます。

C++ ではこのカプセル化のコントロールのため、外部からのアクセスを禁止す るため private: というキーワードが用意されています。 その反対に外部に公開するメソッド(インターフェイス)のために public: というキーワードも用意されています。

クラスを作ると、新しい型としてオブジェクトの宣言が可能です。

クラス名 オブジェクト名;

また、構造体と同様に、変数名やメソッド(関数)はピリオドの後に書きます。 メソッド(関数)はメソッド名の後に丸カッコ()を付けます。

オブジェクト名.メソッド(引数)

C++ では C 言語と違い、宣言を自由な位置に置くことができます。 従って、条件に適合する時だけ巨大な配列を作り、必要ないときは領域を作ら ないように処理することもできます。 また、変数宣言はブロック内のみ有効で、ブロックの外で廃棄されます。


if(a==0){
  int b=1;
}    // この部分で b は廃棄される
a=b; // b は既に廃棄されているのでエラーになる

なお、 C++ 言語ではオブジェクトのポインタも用意されています。 宣言は通常と同じ * 付きの変数として宣言します。 オブジェクトは変数宣言の他、 new 演算子によっても生成できます。 クラス名と同じ名前のメソッドをコンストラクタと言います。 これはオブジェクトを生成する時に初期化のために呼び出されるメソッドです。 new 演算子の後にコンストラクタを呼び出すと、オブジェクトを生成してポイ ンタを返します。 また、メソッドの呼び出しには * と . の組合せでもできますが、ポインタ専 用に -> を使うことができます。 なお、 new で生成したオブジェクトは自動的に廃棄されませんので、 delete で消す必要があるときがあります。


#include <iostream>
using std::cout;
using std::endl;
class A {
  int x;
  public:
  A(){x=0;}
  void set(int n){x=n;}
  int get(){return x;}
};
main(){
  A b; //コンストラクタが自動的に呼ばれる
  cout << b.get() << endl;
  b.set(3);
  cout << b.get() << endl;
  A *c;
  c = new A();
  cout << c->get() << endl;
  c->set(4);
  cout << c->get() << endl;
}

テンプレート

テンプレートは特定の関数やオブジェクトクラスを任意の型に対して生成した い時に使います。数の二乗をする関数を次のようにテンプレートで作成します。


template <typename T>
T square(T a){
  return a*a;
}

このように用意してヘッダファイルに入れておくと、次のようにプロトタイプ を書くだけで、C++ コンパイラは自動的にその型に応じた関数を生成します。


#include <iostream>
#include "square.h"
using namespace std;
template int square(int);
template double square(double);
main(){
  cout << square(2) << endl
       << square(3.1) << endl;
}

STL

STL(Standard Template Library) は良く使われるデータ構造と関 数が集められたテンプレートです。 なお、STL の関数はアルゴリズムと呼ばれています。 STL に含まれるデータ構造や関数は、この講義でもしばしば取り上げます。

さようなら配列

STL では要素を自由に追加、削除できる配列のテンプレートである vector が 用意されています。 C 言語では領域外にアクセスした場合、動作は不定で暴走することもありまし た。 しかし、Vector で定義した配列は x.at(n) のようにアクセスするとエラーを 発生し、暴走を防ぐことができます。 また、アルゴリズムを使うと高速に検索などのアクセスができます。 以下は配列の合計と平均を求めるプログラムです。


#include <iostream>
#include <numeric> //accumulate に必要
#include <vector>
using std::cout;
using std::endl;
main(){
  int a[5]={ 3, 5, 2, 1, 4 };
  vector<int> v(a, a+5);
  int kosuu=v.size();
  int goukei=accumulate(v.begin(),v.end(),0);
  cout << "個数: " << kosuu << endl
       << "合計: " << goukei << endl
       << "平均: " << (double) goukei/kosuu << endl;
}

さようなら文字配列

C++ の STL では string テンプレートが用意されています。 これを用いて C 言語で示した文字列の処理を書くと次のようになります。

  1. 文字列の長さを求める
    
    #include <iostream>
    #include <string>
    using namespace std;
    main(){
      string x="This is a pen.";
      cout << x.size() << endl;
    }
    
  2. 文字列中で最初に特定の文字が出現する位置 (但しこの場合、文字ではなく文字列でも可能)
    
    #include <iostream>
    #include <string>
    using namespace std;
    main(){
      string x="This is a pen.";
      string y="p";
      int z=x.find(y);
      if(z<=x.size()){
        cout << z << endl;
      }else{
        cout << "Not found." << endl;
      }
    }
    
  3. 文字列中に含まれる特定の文字の数
    
    #include <iostream>
    #include <string>
    #include <algorithm>
    using namespace std;
    bool match(const char &c){
      return (c == 'p');
    }
    main(){
      string x="This is a pen.";
      string::iterator p;
      int count=0;
      for(p=find_if(x.begin(), x.end(), match);
          p!=x.end();
          p=find_if(p+1, x.end(), match)){
        count++;
      }
      cout << count << endl;
    }
    
  4. 文字列の結合
    
    #include <iostream>
    #include <string>
    using namespace std;
    main(){
      string x="This is a pen.";
      string y="That is a book.";
      string z=x+y;
      cout << z << endl;
    }
    

3-3. Java 言語特有の話

Java は version 1.5 から Generics という C++ の template と同様の機能 が追加されました。 しかし、 Java では複数の情報を扱うためのツールは Generics ではなく java.util カテゴリのクラスライブラリで提供されています。 C++ の vector テンプレートに対応するのが java.util.ArrayList クラスに なります。 なぜ、 Java ではクラスによる提供が可能なのでしょうか? それは Java にはルートクラス java.lang.Object があり、すべてのオブジェ クトクラスはこの java.lang.Object のサブクラスになります。 従って、すべてのオブジェクトは Object 型の変数に代入できます。 つまり、 java.util.ArrayList の中には java.lang.Object 型の変数が宣言 されており、値を入れる時はどんなオブジェクトでもそのまま入れることがで きます。 しかし、取り出す時は java.lang.Object 型で取り出すことになりますから、 入れたオブジェクトを取り出す時キャストする必要があります。

ところで int などの基本型はオブジェクトではないので、この java.util.ArrayList には入れることができません。 そのため、ラッパークラスと呼ばれる基本型をオブジェクトに変 換するクラスが用意されています。 例えば、 int に対して java.lang.Integer クラスが対応していて、 new Integer(3) などでオブジェクトに変換します。 得られたオブジェクトは intValue() メソッドで int 型に変換できます。

一方、Java には文字列型として java.lang.String(変更不可), java.lang.StringBuffer(変更可) があります。


import java.lang.*;
import java.util.*;
class Main{
    public static void main(String[] arg){
	int[] a = new int[] {3, 5, 0, 2, 4 };
	List l = new ArrayList();
	for(int i=0; i< a.length;i++){
	    l.add(new Integer(a[i]));
	}
	int kosuu = l.size();
	int goukei=0;
	for(Iterator i = l.iterator(); i.hasNext();){
	    goukei += ((Integer) i.next()).intValue();
	}
	System.out.println("個数: "+kosuu);
	System.out.println("合計: "+goukei);
	System.out.println("平均: "+(double)goukei/kosuu);
    }
}

  1. 文字列の長さを求める
    
    class Test1 {
        public static void main(String[] args){
    	String x="This is a pen.";
    	System.out.println(x.length());
        }
    }
    
  2. 文字列中で最初に特定の文字が出現する位置 (但しこの場合、文字ではなく文字列でも可能)
    
    class Test2 {
        public static void main(String[] args){
    	String x="This is a pen.";
    	String y="p";
    	int k=x.indexOf(y);
    	if(k>=0){
    	    System.out.println(k);
    	}else{
    	    System.out.println("Not found.");
    	}
        }
    }
    
  3. 文字列中に含まれる特定の文字の数
    
    class Test3 {
        public static void main(String[] args){
    	String x="This is a pen.";
    	String y="i";
    	int k=0;
    	for(int i=x.indexOf(y); i>=0; i=x.indexOf(y,i+1)){
    	    k++;
    	}
    	System.out.println(k);
        }
    }
    
  4. 文字列の結合
    
    class Test4 {
        public static void main(String[] args){
    	String x="This is a pen.";
    	String y="That is a book.";
    	StringBuffer z;
    	z=x+y;
    	System.out.println(z);
        }
    }
    

3-4. Smalltalk

Smalltalk は変数に型がありません。 これは、どんなオブジェクトも代入(参照)できることを意味します。 従って、C++ の Template や Java の Generics のような型を引数にする定義 は必要ありません。

Smalltalk はクラスがカテゴリによって分類されています。 複数のデータを扱うカテゴリが Collections です。 この中に Array などのクラスがあります。 Smalltalk のクラスはすべて Object クラスのサブクラスですが、変数に型が ないため、それとは関わらず、 Collections の中のクラスのインスタンスに はどんなものでも格納できます。 Smalltalk の Collections カテゴリには非常の多くのクラスが登録されてい ます。 要素数が変わらないのであれば Array を使用しますが、多くは Bag や OrderdCollection や Dictionary や Set などを使うことになるでしょう。

なお、文字列のクラス String のほか、文字のクラス Character や、唯一性 を保証された文字列のクラス Symbol があります。 それぞれ定数の書き方が、シングルクォーテーションマークで括る、ドル記号 を前に置く、シャープ記号を前に置くと異なります。

出力は UNIX 系の流れとは異なり、出力専用のウィンドウ Transcript に送る ことになります。 これは Collections カテゴリの WriteStream クラスのサブクラスのインスタ ンスとして扱います。


anArray ← #(3,5,0,2,4).
total ← anArray inject: 0 into: [:sum :each | sum + each].
Transcript cr;
           nextPutAll: '個数: ';
           nextPutAll: anArray size asString.
Transcript cr;
           nextPutAll: '合計: ';
           nextPutAll: total asString.
Transcript cr;
           nextPutAll: '平均: ';
           nextPutAll: ( total / anArray size ) asFloat asString;
	   flush.
  1. 文字列の長さを求める
    
    x ← 'This is a pen.'.
    Transcript cr;
               nextPutAll: x size asString;
    	   flush.
    
  2. 文字列中で最初に特定の文字が出現する位置 (但しこの場合、文字ではなく文字列でも可能)
    
    x ← 'This is a pen.'.
    y ← 'p'.
    z ← x findString: y.
    (z = 0) ifFalse: [ Transcript cr; nextPutAll: z asString ; flush.]
            ifTrue: [ Transcript cr; nextPutAll: 'Not found.' ; flush.].
    
  3. 文字列中に含まれる特定の文字の数
    
    x ← 'This is a pen.'.
    y ← $i.
    Transcript cr;
               nextPutAll: (x count: [ :each | each = y ]) asString.
    
  4. 文字列の結合
    
    x ← 'This is a pen.'.
    y ← 'That is a book.'.
    z ← x , y.
    Transcript cr; nextPutAll: z;
               flush.
    

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科