情報学部 菅沼ホーム 目次 索引

文法の基本

  1. データ型

    1. リテラル

        数字の 10 や文字列 "hello world" のように Ruby のプログラムの中に直接記述できる値のことをリテラルといいます.Ruby においては,リテラルもオブジェクトです.C/C++ における定数に相当します.

      1. 数値リテラル

          数値において,整数や ?a などは,Integer クラス のサブクラスである Fixnum クラスのインスタンスです.ただし,整数の大きさが Fixnum によって表現できる範囲を超えると Bignum クラスのインスタンスとして扱われます.また,浮動小数点数は,Float クラスのインスタンスです.数値リテラルは,以下のように記述しますが,? で始まる箇所を除いて,C/C++ においても同様です.

        • 123,0d123  整数
        • -123  符号つき整数
        • 123.45  浮動小数点数.ピリオド( . )で始まる浮動小数点数は許されません.0.1 などと書く必要があります.
        • 1.2e-3  浮動小数点数
        • 0xffff,0xFFFF  16 進整数
        • 0377,0o377  8 進整数
        • 0b1011  2 進整数

      2. 文字列リテラル

          文字列リテラルは,ダブルクォート " ),または,シングルクォート ' )で囲んで表現し,String クラスのインスタンスです.ダブルクォートで囲まれた文字列では,後に述べる,バックスラッシュ記法式展開が有効になります.シングルクォートで囲まれた文字列では,\\ (バックスラッシュそのもの)と \' (シングルクォート),行末の \ (改行を無視)を除いて文字列の中身の解釈は行われません.C++ における string クラスを利用した文字列の扱いと似ていますが,多少異なっています.特に,C++ においては,式展開の機能はありません.

          文字列を複数行にわたって書くこともできます.この場合,含まれる改行文字は常に \n になります.また,空白を間に挟んだ文字列リテラルは,1 つの文字列リテラルと見倣されます.文字列の結合は,+ 演算子によって可能ですので,以下の 2 つの式は,同じ結果 abcefg になります.
        "abc" + "efg\n"
        "abc" "efg\n"					
          以下に示すように,バックスラッシュ( \ )を使用して,特別な文字(制御文字など)を表現することが可能です.これを,バックスラッシュ記法と呼びます.C/C++ などにおけるエスケープシーケンスに相当します.

        Seq. 説明 Seq. 説明
        \t タブ( 0x09 ) \n 改行( 0x0a )
        \r キャリッジリターン( 0x0d ) \f 改ページ( 0x0c )
        \b バックスペース( 0x08 ) \a ベル( 0x07 )
        \e エスケープ( 0x1b ) \s 空白( 0x20 )
        \nnn 8 進数表記( n は 0-7 ) \xnn 16 進数表記( n は 0-9,a-f )
        \cx,\C-x コントロール文字( x は ASCII 文字) \M-x メタ x ( c | 0x80 )
        \M-\C-x メタコントロール x \x 文字 x そのもの

          ダブルクォート( " ),または,バッククォート( ` )で囲まれた文字列,及び,正規表現の中では,#{ 式 } という形式で式の内容(式を評価した結果)を埋め込むことができます.この機能を式展開と呼びます.式が変数記号 $,または,@ で始まる変数の場合には { } を省略して,「#変数名」という形式でも展開できます.文字 # に続く文字が {,$,@ でなければ,そのまま文字 # として解釈されます.明示的に式展開を止めるには # の前にバックスラッシュを置きます.式展開の中には,ダブルクォートなども含めて Ruby の式をそのまま書くことができます.コメントも許されます.次のプログラムを実行すると,その下に示すような結果が得られます.なお,print は,カンマで区切られたデータを順に出力するための関数です.
        $sei = "山田"
        $nam = "太郎"
        print "私は,#{ $sei + $nam } です\n"
        print "私は,#{ $sei } です\n"
        print "私は,#$sei です\n"
        print "私は,\#{ $sei } です\n"  # 式展開が行われない					
        (出力)
        私は,山田太郎 です
        私は,山田 です
        私は,山田 です
        私は,#{ $sei } です					
          バッククォート ` )で囲まれた文字列は,ダブルクォートで囲まれた文字列と同様に,バックスラッシュ記法の解釈と式展開が行なわれた後,コマンドとして実行され,その標準出力が文字列として与えられます.コマンドの終了ステータスを得るには,組み込み変数 $? を参照します(このような仕様は許すべきではない).次に示すプログラムは,「 ls -l 」コマンドを実行し,その結果を変数 y に代入しています.このプログラムを実行すると,次のような結果が得られます.C/C++ における system 関数の仕様に相当します.
        #! ruby -Ks
        y = ` ls -l `
        printf "%s\n", y
        printf "終了ステータス %d\n", $?					
        (出力)
        total 1
        -rwxr-xr-x   1 1005     everyone       44 Jun  8 13:45 test.rb
        
        終了ステータス 0					

      3. ヒアドキュメント
      4. シンボル

          シンボルは,Symbol クラスのインスタンスであり,Symbol オブジェクトは,特定の文字列と一対一に対応します.シンボルはプログラム内で一意の整数値であり,
        :abc					
        と記述することによって,文字列 "abc" をシンボル :abc (整数値)に一対一で対応させます.Ruby においては,予約後,変数名,リテラルなどはシンボルを使用して管理しています.例えば,文字列リテラルは,出現するたびに新しい String クラスのインスタンスを生成し,それらに異なるシンボルを対応させます.枠内に示すプログラムにおいて,ダブルクォートで囲まれた文字列は,すべて異なるシンボルに対応しています.従って,その内容が同じであっても,引数が self 自身であるとき真を返す Object クラスのメソッド equal? を使用して比較すると false を返します.なお,このプログラムにおいて,to_s は,シンボルに対応する文字列を返す Symbol クラスのメソッドです.
        print ("abc" == "abc") ? true : false, "\n"
        print ("abc".equal?("abc")) ? true : false, "\n"
        print ("abc" == :abc) ? true : false, "\n"
        print ("abc" == :abc.to_s) ? true : false, "\n"
        print ("abc".equal?(:abc.to_s)) ? true : false, "\n"
        print (:abc == :abc) ? true : false, "\n"
        print (:abc.equal?(:abc)) ? true : false, "\n"
        print (:abc == :"abc") ? true : false, "\n"
        print (:abc.equal?(:"abc")) ? true : false, "\n"
        					
        (出力)
        true
        false
        false
        true
        false
        true
        true
        true
        true
        					
          シンボルは,ハッシュテーブルにおいて良く使用されます.ハッシュとは,任意のオブジェクトをキー(添字)として持つ配列であり,Hash クラスのインスタンスです.下のプログラムでは,:name,及び,:address をキーとする 2 つのデータからなるハッシュテーブルを作成しています.
        #! ruby -Ks
        
        def func(x)
        	print x[:name], "\n"
        	print x[:address], "\n"
        end
        
        func ({:name => "山田太郎", :address => "静岡県"})					
        (出力)
        山田太郎
        静岡県					

      5. % 記法

    2. 変数

        Ruby においては,代入または入力したデータによって,その変数の型が決まります.例えば,Ruby においては,
      a = 10
      a = "abc"				
      のような記述が許され,最後に代入されたデータがその変数に記憶されます.変数に対してその型を宣言しなくてはならないという規則は,非常に面倒なように感じられるかと思いますが,他の場所で別の目的に使用していた変数の値を誤って変更してしまったなどの誤りを防いでくれます.特に,複数の人で作成する大規模なプログラムの場合,同じ変数名を使用しないように管理することはほとんど不可能です.この様な点からも,Ruby のようなインタプリータ言語は,大規模なプログラム作成には不向きです.

        C/C++ における変数の有効範囲(スコープ)は,変数の型宣言された位置や型修飾子によって決まりますが,Ruby における変数の有効範囲は,変数名の最初の 1 文字によって,ローカル変数インスタンス変数クラス変数グローバル変数定数のいずれかに区別されます.2 文字目以降は英数字,または,_ であり,変数名の長さにはメモリのサイズ以外の制限はありません.一般に,識別子(変数名,クラス名,メソッド名など)として,以下に示す予約語キーワード)を使用することはできません.

      BEGIN class ensure nil self when
      END def false not super while
      alias defined? for or then yield
      and do if redo true
      begin else in rescue undef
      break elsif module retry unless
      case end next return until

        小文字,または,_ で始まる識別子はローカル変数,または,メソッド呼び出しです.ローカル変数の有効範囲(スコープ)は,その変数を宣言した位置から,その変数が宣言されたブロックメソッド,または,クラスモジュールの終りまでです.また,$ で始まる変数はグローバル変数であり,プログラムのどこからでも参照できます.

        アルファベット大文字( [ A - Z ] )で始まる識別子は定数です.定数の定義は代入によって行われますが,メソッドの中では定義できません.また,一度定義された定数に再び代入を行おうとすると,警告メッセージが出ます(代入は可能です).定数は,その定数が定義されたクラス/モジュールの中(メソッド本体やネストしたクラス/モジュール定義中を含みます),クラスを継承しているクラス,モジュールをインクルードしているクラス,または,モジュールから参照することができます.C/C++ における const 指定された変数に似ています.
      conts int x = 10;   // 変数 x の値を,以後,修正できない				
        @ で始まる変数は,インスタンス変数であり,特定のクラスのオブジェクトに所属しています.インスタンス変数はそのクラスまたはサブクラスのメソッドから参照できます.インスタンス変数は,クラスから生成されたインスタンス(オブジェクト)毎に別の値を持つことができるため,各オブジェクトの状態を表すためなどに使用されます.このように,インスタンス変数は,C/C++ における protected 指定されたメンバー変数と似た働きをします.従って,そのままでは,クラスの外部から参照することはできません.参照可能にするために,Module クラスのメソッド attr を使用します.@@ で始まる変数はクラス変数と呼びます.クラス変数は,クラス定義の中で定義され,そのクラス,及び,そのサブクラスから参照できます

    3. 擬似変数

        通常の変数以外に,疑似変数と呼ばれる特殊な変数があります.

      • self 現在のメソッドの実行主体
      • nil NilClass クラスの唯一のインスタンス
      • true TrueClass クラスの唯一のインスタンスであり,真の代表値
      • false FalseClass クラスの唯一のインスタンスであり,nil と false は偽を表す
      • __FILE__ 現在のソースファイル名
      • __LINE__ 現在のソースファイル中の行番号

  2. 演算子

    1. 演算子の種類

        Ruby には以下にあげる演算子があります.最初にあげた演算子ほど優先順位が高い演算子です.(*) マークをしたものを除き,C/C++ にも同じような演算子が存在しますが,演算子によっては,多少その機能が異なります.ただし,インクリメント演算子( ++ ),デクリメント演算子( -- )が存在しない点に注意してください.
      演算子 使用目的
      :: クラス内部の定数などの参照  例: Test::Const
      [] 配列要素などの参照  例: x[2]
      +(単項) 数値の符号
      ! 論理否定
      ~ ビット演算子(否定)
      **(*) べき乗  例: 2**0.5
      -(単項) 数値の符号
      * 乗算など
      / 除算,整数どうしの場合は小数点以下切り捨て
      % 余り演算など,浮動小数点どうしの演算も可能
      + 加算,文字列の連結など
      - 減算など
      << ビット演算子(左シフト)など
      >> ビット演算子(右シフト)など
      & ビット演算子(論理積)など
      | ビット演算子(論理和)など
      ^ ビット演算子(排他的論理和)など
      > 比較演算子  例: a > b( a は b より大)
      >= 比較演算子  例: a >= b( a は b 以上)
      < 比較演算子  例: a < b( a は b より小)
      <= 比較演算子  例: a <= b( a は b 以下)
      <=>(*) 比較を行い,正,0,負の判定など
      == 比較演算子  例: a == b( a と b は等しい?)
      ===(*) クラス又はそのサブクラスのインスタンスであるか否かの比較など
      != 比較演算子  例: a != b( a と b は等しくない?)
      =~(*) 正規表現とのマッチ
      !~(*) 正規表現にマッチする場合に false,マッチしない場合に true
      && 論理積
      || 論理和
      ..(*) 範囲式
      ...(*) 範囲式
      ?: 条件演算子
      = 代入演算子
      not 論理否定( ! )
      and 論理積( && )
      or 論理和( || )
    2. 代入演算子

        例えば,
      x = x + 5				
      のような式は
      x += 5				
      のように表現することができます.このような代入を,自己代入と呼びます( C/C++ にも同様の機能).自己代入が可能な演算子には以下に示すようなものがあります.
      +, -, *, /, %, **, &, |, ^, <<, >>, &&, ||				

      プログラム例 2.1] 簡単なプログラム

        ここまでの学習を終えたところで,2 つのデータを与えると,乗算と除算の結果を出力するという簡単なプログラムを書いてみましょう.まず,C/C++ のプログラムによって,その構造を見てみましょう.07 行目~ 08 行目において 2 つのデータを入力し,10 行目~ 11 行目において積と商を計算し,13 行目においてその結果を出力しています.以下に示すプログラムをコンパイルした後,実行し,コマンドプロンプト上から 2 つのデータを入力すれば,コマンドプロンプト上にその結果が表示されるはずです.
      01	#include <stdio.h>
      02	
      03	int main()
      04	{
      05				// データの入力
      06		double x, y;
      07		printf("2つのデータを入力して下さい ");
      08		scanf("%lf %lf", &x, &y);
      09				// 乗算と除算
      10		double mul = x * y;
      11		double div = x / y;
      12				// 結果の出力
      13		printf("乗算=%f 除算=%f\n", mul, div);
      14	
      15		return 0;
      16	}
      				

        Ruby では,整数のような基本的なデータ型をはじめとして,全てのデータをオブジェクトとして統一的に取り扱います.Ruby のプログラムを書くことは,Object クラスを継承したクラスの内容を記述することに相当します.

        プログラムは式を並べたものであり,式と式の間はセミコロン( ; ),または,改行で区切ります.ただし,バックスラッシュ \ )に続く改行は文の区切りにならず,次の行へ継続します.従って,1 行に 1 つの式を記述する場合は,C/C++ とは異なり,必ずしもセミコロンを必要としません.

      01	############################
      02	# 2つのデータの乗算と除算 #
      03	#      coded by Y.Suganuma #
      04	############################
      05				# データの入力
      06	printf "2つのデータを入力して下さい ";
      07	#print "2つのデータを入力して下さい ";
      08	str = gets().strip();   # gets でも良い(以下の関数も同様);
      09	a   = str.split(" ");
      10	x   = Integer(a[0]);
      11	y   = Integer(a[1]);
      12				# 和と差の計算
      13	mul = x * y;
      14	div = x / y;
      15				# 結果の出力
      16	printf "乗算=%d 除算=%d\n", mul, div;
      17	#print "乗算=", mul, " 除算=", div, "\n";
      				
      01 行目~ 04 行目

        プログラム全体に対する注釈コメント)です.Ruby の注釈は,この例のように, # から行末までという形で表現します.複数行にわたるコメントは,以下に示すような形式でも表現できます.
      	=begin
      	  2つのデータの乗算と除算
      	       coded by Y.Suganuma
      	=end					
        注釈は,プログラムの該当する部分における処理内容を説明するのに使用され,人間がプログラムを読む際,理解しやすくするためのものであり,コンパイラ等によって特別な処理はされません.プログラムを実行するのはコンピュータですが,プログラムを書いたり修正したりするのは人間です.従って,プログラムを書く際に最も注意すべきことは,いかに読み易く,かつ,分かり易いプログラムを書くかという点です.できるだけ多くの注釈を入れておいて下さい.そのことにより,他の人がプログラムを理解しやすくなると共に,プログラム上のエラーも少なくなるはずです.

      06 行目,16 行目

        2 つのデータを入力するためだけであれば,08 行目~ 11 行目だけで十分ですが,その場合,何のメッセージも出力されずキーボードからの入力待ちになってしまうため,一見コンピュータが止まってしまったように感じます.また,多くの入力を要求するような場合は,どの入力を要求しているのかが分かりません.そのため,06 行目の組み込み関数 printf が使用されています.また,16 行目は,結果を出力するための printf です.なお,これらの文においては,一つの文の終わりにセミコロン ; 」が付加してありますが,改行だけで文の終わりを示しても構いません.

        標準入出力装置(一般的には,キーボードとディスプレイ)に対して入出力を行う場合,対象とするものは文字列です.キーボードから入力する場合,たとえ数値であっても,2 進数を使用したコンピュータの内部表現ではなく,例えば 123.45 のように,我々が読むことができる文字列として入力します.しかし,数値データを,入力された文字列としてそのまま記憶したならば,演算等を行うことができません.そのため,入力された文字列をコンピュータ内部で使用する表現方法に変換して記憶してやる必要があります.例えば,123.45 という文字列を数値として扱いたければ,浮動小数点表現に変換して記憶しておく必要があります.また,ディスプレイに出力する場合も,メモリ(変数)に記憶されているデータを文字列に変換して出力します.さもなければ,我々は,0 と 1 の並びとして出力される結果を自分自身で解釈しなければなりません.printf を使用する場合,C++ における printf と同様,記憶されたデータを文字列に変換する操作を指定してやる必要があります.例えば,以下のようにして使用します.

        関数は,外部から与えられたデータに基づき,何らかの処理を行いその結果を返します.関数にデータを与える方法の一つが引数です(以下の例において,printf の後ろに書かれた部分).なお,Ruby においては,関数を呼び出す場合,
      	printf("結果は %f %10.3f %d %5d %s\n", d_data, d_data, i_data, i_data, c_data)					
      のように引数の部分を括弧で囲んでも,次の例のように,括弧を使用しなくても,いずれの方法でも構いません.
      	d_data = -12.34456
      	i_data = -12
      	c_data = "abc";
      	printf "結果は %f %10.3f %d %5d %s\n", d_data, d_data, i_data, i_data, c_data					
        上のプログラムを実行すると,まず,"結果は" という文字列が出力されます.printf においては,% で始まる文字列とエスケープシーケンス(後述)以外は,記述された内容がそのまま出力されます.次に,以下の順序で 5 つのデータが出力されます(各データ間には,1 つのスペースが入る).

      1. %f は 浮動小数点型のデータを固定小数点表現の文字列に変換します.なお,1.23x10-3 のような表現方法に対応する 1.23e-03 という方法-浮動小数点表現-も存在します( %e ). % と f の間に何も記述しなければ,全体の桁数や小数点以下の桁数がシステムの標準形式に従います.この場合,d_data の内容が,システムの標準形式に従って出力されます.

      2. 最初のデータと変換方法は同じですが,この場合は,出力形式を指定しています.d_data の内容が,小数点以下 3 桁,全体の桁数 10 桁で出力されます(例: △△△-12.345 ).桁数が 10 桁に満たない場合は,左側にスペースが挿入されます.全体の桁数を指定しない場合は,10 の部分を省略しても構いません.

      3. %d は 整数型のデータを文字列に変換します.% と d の間に何も記述しなければ,システムの標準形式に従って出力されます.この場合,i_data の内容が,システムの標準形式に従って出力されます.

      4. 上の i_data と変換方法は同じですが,この場合は,出力形式を指定しています.i_data の内容が,全体の桁数 5 桁で出力されます(例: △△-12 ).桁数が 5 桁に満たない場合は,左側にスペースが挿入されます.

      5. c_data の内容が文字列として出力されます.なお,1 文字に対する入出力には,「%c」を使用します.

        以上の説明に従うと,06 行目の printf は,入力を促すメッセージを出力するだけです.16 行目では,mul と div の値を出力しています.16 行目の " の中の最後の記号 \n も 1 文字を表し,そのまま出力されますが, \ の付いた記号は,エスケープシーケンスといって,特別な働きをします.例えば, \n が出力されると改行が行われます.また,13 行目,14 行目を省略し,16 行目の mul と div の代わりに,(x * y),(x / y) という式を記述することも可能です.

      07 行目,17 行目

        06 行目,16 行目の代わりに,これらの行を使用することもできます.print においては,カンマで区切られたデータが順番に出力されます.その際,mul や div の値が自動的に文字列に変換されます.

      08 行目

        組み込み関数 gets を利用して,標準入力から 1 行分のデータを文字列として入力し,変数 str に記憶しています.Ruby のようなインタプリタでは,C++ のような型宣言を行う必要がありません.変数にデータを代入することによって,その変数の型が決まります.変数 str には,文字列が代入されていますので,str の型は文字列型になります.なお,strip は,文字列の前後にある空白や改行を取り除くための関数です.

      09 行目

        08 行目において,2 つのデータを半角スペースで区切った 1 行分のデータが,文字列として変数 str に記憶されます.そこで,これらのデータを分離してやる必要があります.split は,String クラスの関数であり,指定した区切り文字で文字列を分割し,配列に入れて返します.ここでは,a[0] と a[1] に入力した 2 つのデータが文字列として入ります.

      10 行目~ 11 行目

        組み込み関数 Integer を利用して,文字列を整数に変換しています.なお,Ruby においては,整数値の大きさに制限はありません.

      13 行目~ 14 行目

        これらの文では,見て明らかなように,乗算と除算の計算をし,結果を変数 mul と div に代入しています.変数 x,及び,y に対しては,それらの値が参照されているだけですので,10 行目~ 11 行目で記憶された値がそのまま保たれています.なお,2 番目( y )の値として 0 を入力しない( 0 で割ることになる)ようにしてください.

        このプログラムを実行し,8 と 3 を,
      2つのデータを入力して下さい 8 3					
      のように入力すると(下線部が入力する部分),以下のような結果が得られます.除算において,結果の小数点以下が切り捨てられていることに注意してください.なお,x または y が浮動小数点数の場合,17 行目の出力文を使用すると,小数点以下も出力されます.
      乗算=24 除算=2					

    3. 範囲式
    4.   範囲式は,演算子 ..,または,... を使用して生成され,Range クラスのインスタンスです... 演算子によって生成された範囲オブジェクトは終端を含み,... 演算子によって生成された範囲オブジェクトは終端を含みません.範囲式で指定する式は,互いに演算子 <=> で比較できる必要があります.また,次の数値または文字列を返すメソッドである Integer クラスの succ,または,String クラスの succ などのメソッドを実行できるものでなければいけません.下に示すのは,範囲式を使用したプログラム例とその出力結果です( for 文に関しては後述).「 .. 」や「 ... 」のような直感的に意味不明の記号をなぜ使用するのか理解できません.Range クラスのインスタンスを使用して,終端は含まない(終端を含む)に統一した方がよっぽど分かりやすいと思います.少なくとも,現在の仕様では,Range.new(1, 3, true),Range.new(1, 3) を使用することを勧めます.なお,範囲式に相当する機能は,C/C++ には存在しませんが,容易に作成可能です. 
      for i in 1 .. 3   # for i in Range.new(1, 3)
      	print " ", i
      end
      print "\n"
      
      for i in 1 ... 3   # for i in Range.new(1, 3, true)
      	print " ", i
      end
      print "\n"
      
      for k in "aa" .. "ac"
      	print " ", k
      end
      print "\n"
      				
      (出力)
       1 2 3
       1 2
       aa ab ac				

    5. 条件演算子

        条件演算子は,
      式1 ? 式2 : 式3				
      のように記述され 式1 が真であれば 式2,偽であれば 式3 を返します.例えば,
      a = (b > 0) ? 1 : 2				
      のように記述した場合,b が正の時は 1,0 以下の時は 2 が,変数 a に代入されます.

  3. 制御文

    1. 分岐

      • if 文
        if 式1 [then]
        	式2
        [elsif 式3 [then]
        	式4 ]
        ・・・
        [else
        	式5 ]
        end					
          式1が真であれば式2,式3が真であれば式4,・・・,そうでなければ式5が評価されます.C/C++ における if 文とほぼ等価です.意味も分かりやすく,たぶん多くの人が使い慣れた「 else if 」から「 elsif 」に変更する必要があるのでしょうか.Ruby は,意味の無い記号に特別な意味を与えるなど,記述を簡単にすることが目的かもしれませんが,理解しにくくなり,誤りを誘発する原因となりかねません.

      • if 修飾子このような機能,必要? 条件演算子で十分?
      • unless 文このような機能,必要?
      • unless 修飾子このような機能,必要?
      • case 文

    2. 繰り返し

      • while 文
      • while 式1 [do]
        	式2
        end					
          式1が真の間,式2を繰り返し実行します.C/C++ における while 文とほぼ同等です.

      • while 修飾子このような機能,必要?
      • until 文このような機能,必要?
      • until 修飾子このような機能,必要?
      • for 文
      • for ループ変数 in 式1 [do]
        	式2
        end					
          式1を評価した結果のオブジェクトの各要素に対して,式2を繰り返して実行します.C/C++ における範囲 for 文と似た機能です.C/C++ における通常の for 文のような機能もあった方が良いのではないでしょうか.プログラム例 3.1 に示すように,複数のループ変数を指定することも可能です(このような機能,必要ですか?.分かり難くなるだけ!).

        プログラム例 3.1] for 文 

        01	############################
        02	# for 文                   #
        03	#      coded by Y.Suganuma #
        04	############################
        05				# Range クラスを利用
        06	for i in Range.new(1, 3)   # Range.new(1, 4, true) でも良い
        07		printf "%d ", i
        08	end
        09	printf "\n"
        10				# 配列の利用
        11	for i in Array[1.5, 5.1, 3.14]
        12		printf "%.2f ", i
        13	end
        14	printf "\n"
        15				# 複数のループ変数( 2 次元配列)
        16				# このような機能は必要ない
        17				# プログラムが読みにくくなるだけ
        18	for i, j in Array[[1, 2], [3, 4], [5, 6]]   # [[1, 2], [3, 4], [5, 6]] でも良い
        19		printf "%d,%d  ", i, j
        20	end
        21	printf "\n"
        					
        06 行目~ 09 行目

          「式1」として,Range クラスを使用した場合です.1 から 3 までの値を出力しています.C/C++ における通常の if 文にほぼ相当しています.

        11 行目~ 14 行目

          「式1」として,1 次元配列を使用した場合です.配列の各要素の値を出力しています.

        18 行目~ 21 行目

          「式1」として,2 次元配列を使用した場合です.複数のループ変数を指定して,配列の各要素の値を出力しています.
        (出力)
        1 2 3
        1.50 5.10 3.14
        1,2  3,4  5,6					

      • break 文
      • break					
          もっとも内側のループを脱出します.
        for i in Array[1, 5, -1, 6, 7]
        	if i < 0
        		break
        	end
        	printf "%d ", i
        end
        printf "\n"					

      • next 文
      • next					
          もっとも内側のループの次の繰り返しにジャンプします.C/C++ における continue に相当します.
        for i in Array[1, 5, -1, 6, 7]
        	if i < 0
        		next
        	end
        	printf "%d ", i
        end
        printf "\n"					

      • redo 文
      • retry 文

    3. 例外処理
      begin
      	式1
      [rescue [error_type,・・・] [=> evar] [then]
      	式2]
      ・・・・・
      [else
      	式n]
      [ensure
      	式m]
      end				

        begin 式内の本体(式1)において例外が発生すると.rescue 節などによって,その例外を処理することができます.raise は,例外を発生させるための組み込み関数であり,
      raise
      raise messageまたはexception
      raise error_type, message
      raise error_type, message, traceback				
      のように記述されます.第一の形式では,直前の例外を再発生させます.第二の形式では,引数が文字列であった場合,その文字列をメッセージとする RuntimeError 例外を発生させます.引数が例外オブジェクトであった場合には,その例外を発生させます.第三の形式では,第一引数で指定された例外を,第二引数をメッセージとして発生させます.第四の形式の第三引数は,組み込み変数 $@ 等で得られるスタック情報であり,例外が発生した場所を示します.

        本体の実行中に例外が発生した場合,rescue 節(複数指定できます)が与えられていれば例外を捕捉できます.発生した例外に一致する rescue 節が存在する時には( error_type を省略するとすべての例外に一致),rescue 節の本体が実行されます.発生した例外は 組み込み変数 $! を使って参照することができます.また,=> によって指定されていれば,変数 evar にも $! と同様に発生した例外が格納されます.

        省略可能な else 節は,本体の実行によって例外が発生しなかった場合に評価されます.ensure 節が存在する時は,begin 式を終了する直前に必ず ensure 節の本体を評価します.

        次の枠内に示すプログラムは,0 による除算,及び,raise によって,2 つの例外,ZeroDivisionError 及び RuntimeError を発生させています.ZeroDivisionError が発生した場合は,rescue 節において変数 x の値を変更した後,retry を実行しています( begin 式の再実行).
      x = 0
      begin
      	y = 10 / x
      	raise "エラー発生!"
      rescue ZeroDivisionError
      	p $!
      	p $@
      	x = 2
      	retry
      rescue
      	p $!
      	p $@
      ensure
      	printf "最後の処理\n"
      end
      printf "y = %d\n", y
      				
      (出力)
      #<ZeroDivisionError: divided by 0>
      ["test.rb:3:in `/'", "test.rb:3"]
      #<RuntimeError: エラー発生!>
      ["test.rb:4"]
      最後の処理
      y = 5				

    4. BEGIN と END

      • BEGIN { 文 }

          初期化ルーチンを登録します.BEGIN ブロックで指定した文は,当該ファイルのどの文が実行されるより前に実行されます.複数の BEGIN が指定された場合には指定された順に実行されます.

      • END { 文 }

          後始末ルーチンを登録します.END ブロックで指定した文は,インタプリタが終了する時に実行されます.複数の END ブロックを登録した場合は,登録したときと逆の順序で実行されます.

  4. 配列

    1. 1 次元配列

        配列は,複合データ型の一種であり,複数のデータを処理する場合に利用されます.Ruby においては Array クラスのインスタンスとして定義されます.C/C++ における配列とは異なり,ほぼ,C++ 標準ライブラリ内の vector クラスに相当します.Ruby において,例えば,
      x1 = Array[1, 2.3, "abc"];   # x1 = [1, 2.3, "abc"]; でも可
      p x1
      x2 = Array.new(3)
      x2[0] = 1
      x2[1] = 2.3
      x2[2] = "abc"
      p x2
      x3 = Array.new()
      x3.push(1)
      x3.push(2.3)
      x3.push("abc")
      p x3
      				
      のように宣言すれば,x1,x2,及び,x3 は,3 つのデータを記憶できる領域が確保され,各要素に記憶される値が「 1,2.3,"abc" 」で初期設定されます.また,変数名と添え字を利用して,x[i] のようにして参照できます( i = 0 ~ 2 ).この例に示すように,各要素は,必ずしも,同じデータ型である必要はありません.しかし,間違いの元になる可能性がありますので,配列は,同じデータ型だけで構成するようにした方が良いと思います.また,プログラムの実行時に,要素の追加,削除等を行うことが可能です.なお,上のプログラムにおける p は,引数を,人間に読みやすい形で出力するための組み込み関数です.

      プログラム例 4.1] 1 次元配列(平均点以下の人数) 

        何人かの試験の点数を入力し,平均点以下の人数を求めるプログラムについて考えてみます.平均点以下の人数を求めるためには,全員のデータを入力し,平均値を計算した後,それを各人の点数と比較する必要があります.そのためには,入力した各人の点数をどこかに記憶しておく必要があります.そこで,配列が必要になってきます.
      01	############################
      02	# 平均点以下の人数         #
      03	#      coded by Y.Suganuma #
      04	############################
      05				# データの入力
      06	printf("人数は? ")
      07	n = Integer(gets().strip())
      08	x = Array.new(n)
      09	for i1 in Range.new(0, n-1)
      10		printf("%d 番目の人に点数は? ", i1+1)
      11		x[i1] = Integer(gets().strip())
      12	end
      13	
      14	=begin
      15	printf("各人の点数は ")
      16	str = (gets().strip()).split(" ")
      17	n   = str.size
      18	x   = Array.new(n)
      19	=end
      20				# 平均点の計算
      21	mean = 0.0;
      22	for i1 in Range.new(0, n-1)
      23	#	x[i1] = Integer(str[i1])
      24		mean += x[i1]
      25	end
      26	mean /= n
      27				# 平均点以下の人数をカウント
      28	ct = 0
      29	for i1 in Range.new(0, n-1)
      30		if x[i1] <= mean
      31			ct += 1
      32		end
      33	end
      34				# 結果の出力
      35	printf("結果: %d 人\n", ct)
      				
      06 行目~ 12 行目

        データを入力している部分です.人数 n を入力( 07 行目)した後,大きさ n の配列を定義し( 08 行目),各人の点数を入力しています( 09 行目~ 12 行目).ここでは,1 行に一つずつのデータを入力しています.06 行目~ 12 行目の代わりに,15 行目~ 18 行目と 23 行目を使用すれば,全てのデータを半角スペースで区切り,1 行で入力することによって,人数 n を入力しなくても済みます.

      21 行目~ 26 行目

        平均点を計算しています.21 行目の初期設定を忘れないでください.

      28 行目~ 33 行目

        平均点以下の人数を数えています.平均点を求める場合と同様,28 行目の初期設定を忘れないでください.

    2. 多次元配列

        多次元の配列を扱うことも可能です.例えば,

      v1 = Array[[10, 20, 30], [40, 50, 60]]
      p v1
      v2 = Array.new(2)
      v2[0] = Array[10, 20, 30]
      v2[1] = Array[40, 50, 60]
      p v2
      v3 = Array.new()
      v3.push([10, 20, 30])
      v3.push([40, 50, 60])
      p v3
      				
      のように,配列の要素を,さらに配列として定義すれば,2 次元の配列を定義できます( 3 次元以上も同様).この例では,いずれの方法においても,2 行 3 列の配列になります.また,変数名と添え字を利用して,x[i][j] のようにして参照できます( i = 0 ~ 1,j = 0 ~ 2 ).

      プログラム例 4.2] 2 次元配列(複数クラスの試験) 

        複数のクラスに対して試験を行ったとします.全体の平均を求めた後,各クラス毎に平均点以下の人数を出力するプログラムです.どのような方法でも記述可能ですが,ここでは,各行にクラス全員の点を半角で区切って入力し,入力が終了した時点で改行だけを入力するといった方法で記述してみます.いずれにしろ,全員の平均点を求めた後でないと,平均点以下の人数を計算できませんので,2 次元配列が必要になってきます.例えば,2 クラス存在し,クラス1 の人数が 3 人,クラス2 の人数が 2 人の場合は,メッセージに対して以下に示すような順で入力します.3 行目は改行だけです.点数は適当に変えてください.
      10 20 30\n
      40 50\n
      \n				
      01	#****************************/
      02	#* 平均点以下の人数         */
      03	#*      coded by Y.Suganuma */
      04	#****************************/
      05				# データの入力
      06	x    = Array.new()   # 各人の点数
      07	n    = 0   # クラスの数
      08	m    = Array.new()   # 各クラスの人数
      09	mm   = 0;   # 全体の人数
      10	mean = 0;   # 全体の平均
      11	
      12	printf("1 番目のクラスにおける各人の点数は?(半角スペースで区切る) ")
      13	a = (gets().strip()).split(" ")
      14	while a.size > 0
      15		m.push(a.size)   # クラスの人数
      16		x.push(Array.new(m[n]))   # 2 次元配列の生成
      17		mm += m[n]   # 全体の人数
      18		for i1 in Range.new(0, m[n]-1)
      19			x[n][i1] = Integer(a[i1])
      20			mean    += x[n][i1]   # 全体の平均
      21		end
      22		n += 1
      23		printf("%d 番目のクラスにおける各人の点数は?(半角スペースで区切る) ", (n+1))
      24		a = (gets().strip()).split(" ")
      25	end
      26				# 平均点の計算
      27	mean /= mm
      28	p mean, mm
      29				# 平均点以下の人数のカウントと出力
      30	for i1 in Range.new(0, n-1)
      31		ct = 0
      32		for i2 in Range.new(0, m[i1]-1)
      33			if x[i1][i2] <= mean
      34				ct += 1
      35			end
      36		end
      37		printf("クラス%d における平均点以下の人数: %d 人\n", (i1+1), ct)
      38	end
      				

        Ruby には,Array クラス以外に,行列を扱う Matrix クラスベクトルを扱う Vector クラスハッシュテーブルによる連想配列を扱う Hash クラスなどが存在します.詳細については,各クラスに対する説明を参照してください.C++ にも,複合型データを扱う様々なクラス(コンテナ)が存在し,Hash クラスは,unordered_map クラスにほぼ相当します.

    3. 代入と複製

        C/C++ において,変数 b に a を代入するとは,a に記憶されている値をコピーして,b に記憶することです.例えば,

      a = 10;
      b = a;
      b = 20;				

      のような操作によって,b は a のコピーですから,b の値を変更しても a の値は変化しません.Ruby の場合は,整数のような基本的なデータ型をはじめとして,全てのデータをオブジェクトとして統一的に取り扱い,変数はそのオブジェクトへの参照(アドレス)を記憶しているはずです.2 行目の代入によって,変数 a の 10 への参照を b にコピーして代入しているはずです.しかし,10 そのものを直接変更する手段がありませんので,基本的なデータ型の場合,C/C++ と同じ結果になります.しかし,

      u1 = Array[1, "abc", 2]
      u2 = Array[1, "abc", 2]
      u3 = u1
      u3[1] = 4
      print u1[1]," ",u2[1]," ",u3[1]   # 4 abc 4				
      のような配列の場合について考えてみます.u1 と u3 の関係は,整数型における a と b との関係と同じです.しかし,上で述べたように,3 行目の代入は,u1 の領域及びそこに記憶されている要素をすべてコピーし,u3 に代入しているわけではありません.ここでは,u1,u2,u3 をポインタとしてとらえた方が理解しやすいと思います.つまり,3 行目の代入によって,u1 に記憶されているアドレス(参照)が u3 に記憶され,u1 と u3 が同じ領域を指しているという意味です.実際,u1 が記憶している参照先である配列の値を変更すれば,u3 の値も変化します(逆も同様).このことを概念的に示せば以下のようになります.
        以下に示すのは,配列の全ての要素を出力した場合です.ただし,初期設定に使用するデータは,C/C++ と比較するために,変更してあります. 
      u1 = Array[1, 2, 3];
      u2 = Array[1, 2, 3];
      u3 = u1;
      print "u1 : ", u1[0], " ", u1[1], " ", u1[2], "\n"
      print "u2 : ", u2[0], " ", u2[1], " ", u2[2], "\n"
      print "u3 : ", u3[0], " ", u3[1], " ", u3[2], "\n"
      print "    u1[0] -> 10,u2[1] -> 20,u3[2] -> 30\n"
      u1[0] = 10
      u2[1] = 20
      u3[2] = 30
      print "u1 : ", u1[0], " ", u1[1], " ", u1[2], "\n"
      print "u2 : ", u2[0], " ", u2[1], " ", u2[2], "\n"
      print "u3 : ", u3[0], " ", u3[1], " ", u3[2], "\n"
      				
      (出力)
      u1 : 1 2 3
      u2 : 1 2 3
      u3 : 1 2 3
          u1[0] -> 10,u2[1] -> 20,u3[2] -> 30
      u1 : 10 2 30
      u2 : 1 20 3
      u3 : 10 2 30				

        次に,2 次元配列について考えてみます.
      v1 = Array[[10, 20, 30], [40, 50, 60]]
      v2 = Array[[10, 20, 30], [40, 50, 60]]
      v3 = v1				
      のような設定を行った場合,3 行目の記述によって,1 次元配列の場合と同様,v3 も,v1 と同じ 2 行 3 列の配列になります.従って,v1 の値を変更すれば,v3 の値も変化します(逆も同様).そのイメージは,以下のようになります.
        また,
      v4 = v1[0];   // v1,v3 の 1 行目				
      のように,各行を 1 次元配列として扱うことも可能です.勿論,v4 と v1[0] は同じ場所を指していますので,例えば,v4[1] の値を変更すれば,v1[0][1] の値も変化します(逆も同様).

        参照だけではなく,全ての要素をコピーして新しい配列を生成する方法はないのでしょうか.一つの方法はArray クラスclonedupnew を使用することです.以下に示すプログラム例とその出力結果を見てください.1 次元配列に対しては( 01 行目~ 10 行目),希望とする結果になっています.しかし,2 次元配列に対しては( 12 行目~ 21 行目),代入と全く同じ結果になっています.これは,clone などによって,上で示した 2 次元配列に対する図における v1[0] と v1[1] だけが複製されたためであると考えられます.
      01	u1 = Array[10, 20, 30]
      02	u2 = Array.new(u1)
      03	u3 = u1
      04	u4 = u1.clone
      05	u3[0] = 100
      06	u4[1] = 200
      07	print "u1 ", u1, "\n"
      08	print "u2 ", u2, "\n"
      09	print "u3 ", u3, "\n"
      10	print "u4 ", u4, "\n"
      11	
      12	v1 = Array[[10, 20, 30], [40, 50, 60]]
      13	v2 = Array.new(v1)
      14	v3 = v1
      15	v4 = v1.clone
      16	v3[1][1] = 100
      17	v4[0][1] = 200
      18	print "v1 ", v1, "\n"
      19	print "v2 ", v2, "\n"
      20	print "v3 ", v3, "\n"
      21	print "v4 ", v4, "\n"
      				
      (出力)
      u1 [100, 20, 30]
      u2 [10, 20, 30]
      u3 [100, 20, 30]
      u4 [10, 200, 30]
      v1 [[10, 200, 30], [40, 100, 60]]
      v2 [[10, 200, 30], [40, 100, 60]]
      v3 [[10, 200, 30], [40, 100, 60]]
      v4 [[10, 200, 30], [40, 100, 60]]				

  5. メソッドとブロック

    1. メソッド(関数)

        メソッド関数)は,与えられた情報に基づき何らかの処理を行い,その結果を返します.メソッドは,独立したプログラムに似ていますので,何らかの方法によって必要な情報をメソッドに渡してやる必要があります.グローバル変数によって渡すことも可能ですが,最も基本的な方法は,引数を利用することです.ここで,引数とは,関数を呼び出した側と関数との間で,情報の受け渡しに使用される変数や定数のことです.メソッドは,一般的に,
      def メソッド名 [ 引数の並び ]
         式
        ・・・
        [rescue [error_type,..] [=> evar] [then]
           式..]..
        [else
           式..]
        [ensure
           式..]
      end				
      のような形式で記述します(あえて,このような複雑な構造にする理由は何でしょうか).rescue 節には,本体の実行によってエラーが発生した場合の処理を記述します.else 節は,本体の実行によって例外が発生しなかった場合に評価されます.また,ensure 節が存在する時は,メソッド評価の最後に ensure 節の本体を評価します.なお,rescue 節,else 節,及び,ensure 節に関しては,例外処理を参照して下さい.

        メソッドによって処理した結果をメソッドを呼んだ側に返したい場合,基本的に,戻り値を通して行います.メソッドの戻り値は,return 節に渡した値です.return が呼び出されなかった場合は,メソッドの本体から ensure 節実行直前までの最後に評価した式の値を返します(このような機能,必要?).return は,
      return [ 式 [ , 式 ... ] ]				
      のように記述され,式が 2 つ以上与えられた時には,それらを要素とする配列をメソッドの戻り値とします(このような機能,必要?).また,式が省略された場合には,nil を戻り値とします.

        次に,下に示した例に基づき,もう少し詳しく見てみます.メソッド fun は,引数として渡された 2 つのデータ a,b から,2 * a + b,2 * a - b,2 * a * b,2 * a / b の値を計算して返すメソッドです.16 行目においてメソッドを呼ぶと,3 つの引数の値がコピーされ,01 行目のメソッドに渡されます.01 行目の a は,11 行目の a のコピーですので,02 行目でその値を変更しても,11 行目の a の値は変化しません(出力結果参照).

        しかし,配列に関する節で説明したように,配列の実体はポインタであり,そのポインタがコピーされて渡されるため,メソッドを呼んだ側とメソッド内では同じ場所を指すことになり,メソッド内で各要素の値を変更すれば,メソッドを呼んだ側の対応する要素の値も変化します.この点は,他のクラスのオブジェクトを引数とした場合も同様です.従って,このメソッドでは,return によって結果を返す必要はありませんが,説明のため,あえて 4 つのデータを返しています( 07 行目).この return 文も,08 行目の記述で十分です.
      01	def fun(a, b, x)
      02		a   *= 2
      03		x[0] = a + b
      04		x[1] = a - b
      05		x[2] = a * b
      06		x[3] = a / b
      07		return x[0], x[1], x[2], x[3]
      08	#	return x
      09	end
      10	
      11	a  = 5
      12	b  = 2
      13	x1 = Array.new(4)
      14	print "関数を呼ぶ前: a = ", a, ", b = ", b, "\n"
      15	
      16	y1 = fun(a, b, x1)
      17	
      18	print "関数を呼んだ後: a = ", a, ", b = ", b, "\n"
      19	print "x1 = ", x1, "\n"
      20	print "y1 = ", y1, "\n"
      				
      (出力)
      関数を呼ぶ前: a = 5, b = 2
      関数を呼んだ後: a = 5, b = 2
      x1 = [12, 8, 20, 5]
      y1 = [12, 8, 20, 5]				
        引数には,例えば,
      def func (par1, par2=10, par3=30)
      	x = par1 + par2 + par3
      	return x
      end
      print func(100, 50)				
      のように,デフォルト値を設定することができます(デフォルト引数).ただし,n 番目の引数にデフォルト値を設定した場合は,それ以降の引数に対してもデフォルト値を設定する必要があります.上の例の場合,3 番目の引数を省略したため,par3 の値は 30 であるとして処理され,結果は 180 になります.なお,この例の場合は,最後に処理された式の値がメソッドの返すべき値であるため,return が無くても,同じ結果になります(このような機能は利用しない方が良い).

        下に示す例のように,最後の仮引数の直前に * がある場合には,残りの実引数はみな配列としてこの引数に格納されます(このような機能は必要でしょうか?.下の例に示すように,配列で十分ではないでしょうか).可変長引数などと呼ばれる機能です.なお,このような引数は,1 つだけ使用できます.
      	# 可変長引数
      def func1 (par, *x)
      	print "par: ", par, "  x:", x, "\n"
      end
      func1(0)          # 出力結果 par: 0  x:[]
      func1(1, 10)      # 出力結果 par: 1  x:[10]
      func1(2, 10, 20)  # 出力結果 par: 2  x:[10, 20]
      	# 配列を使用した場合
      def func2 (par, x)
      	print "par: ", par, "  x;", x, "\n"
      end
      x = Array.new
      func2(0, x)  # 出力結果 par: 0  x;[]
      x.push(10)
      func2(1, x)  # 出力結果 par: 1  x;[10]
      x.push(20)
      func2(2, x)  # 出力結果 par: 2  x;[10, 20]
      				
        最後の仮引数の直前に & があると,このメソッドに与えられているブロックProc クラスのオブジェクトとしてこの引数に格納されます.このような引数も,1 つだけ使用できます.詳細については,次節を参照して下さい.

        メソッド定義において,仮引数はその種類毎に,以下に示す順序でしか指定することはできません.なお,いずれも省略することは可能です.可変長引数を配列に変え,ブロックの定義方法を限定すれば,複数の可変長引数やブロックを引数にできるのではないでしょうか.また,デフォルト引数を除いて,その順番に制限を付ける必要も無いのではないでしょうか.

      • デフォルト式のない引数(複数指定可)
      • デフォルト式のある引数(複数指定可)
      • * を伴う引数(1つだけ指定可)
      • & を伴う引数(1つだけ指定可)

    2. ブロック

        ブロックとは,{ ... },または,do ... end のペアで囲まれた Ruby の文及び式の集まりです.ブロックの先頭には,縦棒 ( | ) で囲み,パラメータをカンマで区切って指定できます.ブロックは,Proc クラスのオブジェクトであり,以下に示すように,Proc クラスの new メソッド,または,組み込み関数である proc や lambda によって生成可能です.なお,ブロックは,C++ におけるラムダ式に相当する機能です.C/C++ においても同様ですが,プログラムを分かりにくくする原因になりかねません.できる限り,使用は避けた方が良いと思います.
      Proc.new do | パラメータ| 式 end
      Proc.new { | パラメータ| 式 }
      proc do | パラメータ| 式 end
      proc { | パラメータ| 式 }
      lambda do | パラメータ| 式 end
      lambda { | パラメータ| 式 }				
        return と break は,lambda と Proc.new や proc では挙動が異なります.例えば return を行った場合,lambda では手続きオブジェクト自身を抜けますが,Proc.new や proc では手続きオブジェクトを囲むメソッドを抜けます.この点は,以下に示す例に従って説明していきます.03 ~ 05 行目において,組み込み関数 lambda を使用してブロックを生成しています.この例においては,複数行で記述していますが,01,02 行目のように,1 行で記述することも可能です(以下においても同様).06 行目では,Proc クラスのメソッド call を利用してブロックを実行しています.また,08 ~ 11 行目では,メソッド内でブロックを生成し,同様のことを行っています.いずれの場合においても,期待した結果が得られています.

        14 ~ 23 行目においては,上と同様のことを Proc クラスの new メソッドを使用して行っています(組み込み関数 proc を使用した場合も同様).15 行目は,04 行目に対応していますが,return を使用した 04 行目と同じ記述では,期待した結果が得られません.最後に処理された式の値がメソッド(この場合は,ブロック)の返すべき値であるという仕様のため,期待した結果になります.メソッドも式の一種あると考えれば,ある程度納得できますが,非常に分かり難いと思います.
      01	# add1 = lambda { |x, y| return x + y }
      02	# add1 = lambda do |x, y| return x + y end
      03	add1 = lambda { |x, y|
      04		return x + y
      05	}
      06	print "lambda : ", add1.call(1, 2), "\n"
      07	
      08	def add2(x, y)
      09		f = lambda { return x + y }
      10		return f.call
      11	end
      12	print "lambda(関数) : ", add2(1, 2), "\n"
      13	
      14	add3 = Proc.new { |x, y|
      15		x + y   # return x + y は No
      16	}
      17	print "Proc.new : ", add3.call(1, 2), "\n"
      18	
      19	def add4(x, y)
      20		f = Proc.new { return x + y }   # f = proc { return x + y }
      21		return f.call
      22	end
      23	print "Proc.new(関数) : ", add4(1, 2), "\n"
      				
      (出力)
      lambda : 3
      lambda(関数) : 3
      Proc.new : 3
      Proc.new(関数) : 3				
        ブロックは,
      method(arg1, arg2, ...)  do [ | パラメータ | ] 式 ... end
      method(arg1, arg2, ...) { [ | パラメータ | ] 式 ... }
      method(arg1, arg2, ..., &proc_object)   # proc_object は別に定義				
      のような形式で,メソッドに引数として渡すことができます.ブロックを引数として受け取るメソッドを,ブロック付きメソッドと呼びます.C++ におけるラムダ式を引数とする場合や 関数名を引数とする場合に似ています.なお,Ruby においては,関数名を引数とすることはできません.

        ブロック付きメソッドは,制御構造の抽象化のために用いられるメソッドであり,最初はループの抽象化のために用いられていたため,特にイテレータと呼ばれることもあります.イテレータとは,簡単に言えば,以下のようなものです.通常の配列であれば,添え字などによって簡単に各要素を参照できます.しかし,その構造が複雑な場合は,それほど簡単ではありません.そこで,それを簡単に行えるようにしたものがイテレータです.

        以下に示すプログラムは,Integer クラスのブロック付きメソッドである times の使用例です.times は,与えられたブロックを self 回だけ( 0 から self-1 まで)繰り返します.1 行目ではパラメータを指定せず,2 行目では 1 つだけ指定しています.また,6 行目では,3 ~ 5 行目でブロックを生成した後,それを引き渡しています.
      3.times {printf "test\n"}
      3.times {|m| printf "m = %d\n", m; printf "   result %d\n", 3*m}
      block = Proc.new {printf "test\n"}
      	# block = proc {printf "test\n"} この方法でも可
      	# block = lambda {printf "test\n"} この方法でも可
      3.times &block				
      (出力)
      test
      test
      test
      m = 0
         result 0
      m = 1
         result 3
      m = 2
         result 6
      test
      test
      test
      				
        yieldは,自分で定義したブロック付きメソッドにおいて,ブロックを呼び出すときに使われ,次のような形式で記述されます.
      yield ( [ 式 [, 式 ・・・ ] ] )
      yield [ 式 [, ' 式 ・・・ ] ]				
      yield に渡された値は,ブロック記法において | と | の間にはさまれた変数(ブロックの引数)に代入されます.また,Proc クラスの call メソッドを使ってブロックを実行することもできます.

      プログラム例 5.1] 様々な引数 

        この例では,整数,デフォルト引数,配列,及び,ブロックを引数とした場合について検討しています.ブロック付きメソッドは,ブロックを 3 つの方法(メソッドの後ろに直接記述する方法,lambda を使用する方法,Proc を使用する方法)で記述しています.また,引数とするブロック名を変更することによって,結果を変更する方法についても示しています.
      01	#************************/
      02	# 様々な引数            */
      03	#   coded by Y.Suganuma */
      04	#************************/
      05	a = 10
      06		#
      07		# ブロック付きメソッド
      08		#
      09	print "ブロック付きメソッド\n"
      10	def add5(x, y = 20, &blk)
      11		x = 5
      12		r = blk.call(x, y)   # yield(x, y)
      13		return r
      14	end
      15	
      16	res = add5(a) { |x, y| x + y }   # return x + y は No
      17	print "   ブロック付きメソッド(直接) : a = ", a, ", sum = ", res, "\n"
      18	
      19	add_l = lambda { |x, y|
      20		return x + y
      21	}
      22	res = add5(a, &add_l)
      23	print "   ブロック付きメソッド(lambda) : a = ", a, ", sum = ", res, "\n"
      24	
      25	add_p = Proc.new { |x, y|
      26		x + y   # return x + y は No
      27	}
      28	res = add5(a, &add_p)
      29	print "   ブロック付きメソッド(Proc.new) : a = ", a, ", sum = ", res, "\n"
      30		#
      31		# 最小値,最大値を求めるブロック付きメソッド
      32		#
      33	print "最小値,または,最大値を求めるメソッド\n"
      34	def min_max(x, &blk)
      35		x[3] = -3
      36		re   = x[0]
      37		for a in x[1...x.length]
      38			re = yield(re, a) ? re : a
      39	#		re = blk.call(re, a) ? re : a
      40		end
      41		return re
      42	end
      43		# ブロック max の定義と利用
      44	max = lambda { |x, y|
      45		return x > y
      46	}
      47	print "   2 > 1 ? ", max.call(2, 1), "\n"
      48		# ブロック min の定義と利用
      49	min = lambda { |x, y|
      50		return x < y
      51	}
      52	print "   1 < 2 ? ", min.call(1, 2), "\n"
      53		# 最大値,最小値の計算
      54	x = [1, 4, 2, 3, 5]
      55	print "   max ", min_max(x, &max), " x ", x, "\n"
      56	print "   min ", min_max(x, &min), " x ", x, "\n"
      				
      05 行目

        a の値を 10 に設定しています.

      09 行目~ 14 行目

        ブロック付きメソッド add5 を定義しています.メソッド add5 の中では,x の値( a に対応)を 5 に変更しています( 11 行目).また,二番目の引数はデフォルト引数となっています.

      16 行目~ 17 行目

        16 行目においてブロックの記述と共に,2 番目の引数を省略して,add5 を呼び出しています.コメントに記したように,return を記述すると正しく動作しません.メソッド add5 の中では,x の値( a に対応)を 5 に変更していますが( 11 行目),17 行目の出力からも明らかなように,a の値に変化はありません.このように,整数型のような基本型をメソッドの引数として渡したとき,メソッド内でその値を変更しても,メソッドを呼んだ側の値は影響を受けません,しかし,和の計算結果は,変更された 5 と,15 行目において 2 番目の引数が省略されているため,デフォルト値 20 の和になっています.

      19 行目~ 23 行目

        19 行目~ 21 行目において lambda を使用してブロックを定義し,22 行目において,それを引数として add5 を呼び出しています.期待通りの結果が得られています.

      25 行目~ 29 行目

        25 行目~ 27 行目において Proc.new を使用してブロックを定義し,28 行目において,それを引数として add5 を呼び出しています.26 行目のコメントにあるように,return を記述すると正しく動作しません.

      33 行目~ 42 行目

        引数として渡されたブロックによって,配列 x 内の最大値,または,最小値を求めています.その際,x[3] の値を -3 に変更しています.このように,ブロックを引数にすることによって,同じメソッドに異なる機能を持たせることが可能になります.

      44 行目~ 47 行目

        最大値を求めるための比較を行うブロックを生成し,その単独動作を確認しています.

      49 行目~ 52 行目

        最小値を求めるための比較を行うブロックを生成し,その単独動作を確認しています.

      54 行目~ 56 行目

        上で作成したメソッド及びブロックを利用して,最大値と最小値を求めています.35 行目で,x[3] の値を変更していますので,54 行目で定義された x の値も変化しています.このように,配列のようなオブジェクトを引数とした場合,メソッド内の変更が,メソッドを呼んだ側にも反映されます.
      (出力)
      ブロック付きメソッド
         ブロック付きメソッド(直接) : a = 10, sum = 25
         ブロック付きメソッド(lambda) : a = 10, sum = 25
         ブロック付きメソッド(Proc.new) : a = 10, sum = 25
      最小値,または,最大値を求めるメソッド
         2 > 1 ? true
         1 < 2 ? true
         max 5 x [1, 4, 2, -3, 5]
         min -3 x [1, 4, 2, -3, 5]
      				

  6. クラスとモジュール

    1. クラス

        クラスとは,ある「もの」に対し,その「もの」に共通する特徴等をもとにして,形式的な定義を与えたものであるといえます.もう少しプログラミング的な感覚でいえば,「もの」に共通するデータと,それらのデータを処理する方法を記述した手続き(メソッド)の集まりです.例えば,「車」というクラスを定義したとすれば,車の構造を記述するデータと車を動かしたりするのに必要な手続きからなっているはずです.また,クラスは「もの」に対する抽象的な定義ですが,インスタンスオブジェクト)はクラスを具体化したものに相当します.例えば,「車」クラスの場合であれば,オブジェクトは,ある特定の人が所有する特定の車になります.

        クラスは,その定義の方法から見て,ユーザーが新たに定義する変数の型と考えても良いと思います.新しい変数の型を定義すれば,その型の操作方法も必要になります.例えば,複素数に対応するような型をクラスによって定義したとします.すると,単純な加算ですら,既存の方法を使用することができません.従って,定義した変数の加算をどのようにして実行するかについても定義してやる必要が出てきます.そこで,クラスの定義には,単にデータだけでなく,そのデータを取り扱う方法を記述したメソッド関数)が必要になってくるわけです.

        クラスの定義は,以下のようにして行われます.なお,< の後は継承するクラスを表すものであり,次節を参照して下さい.
      class 識別子 [ < superclass ]
        式(演算式,メソッド,クラスなど)
          ・・・
      end				
        クラス名はアルファベットの大文字で始まる識別子(定数)です.Ruby では,クラスもオブジェクトの一つであり,Class クラスのインスタンスです.クラス定義は,Class クラスのインスタンスを生成し,その名前を識別子で指定した定数へ代入することに相当します.そのため,C++ や Java とは異なり,後に示す例にも示すように,クラスを定義した時点で式が実行されます.

        クラスが既に定義されているとき,さらに同じクラス名でクラス定義を書くとクラスの定義の追加になります.また,クラス定義は,ネスト(入れ子)にすることもできます.クラスのネストは,意味的に関連するクラスを外側のクラスやモジュールでひとまとまりにしたり,包含関係を表すために使用されますが,継承関係などの機能的な関連はまったくありません.

        クラスのインスタンス(オブジェクト)を生成するためには,次に示すように(クラス Test のインスタンスを生成),Class クラスの new メソッドを利用します.クラス内に Object クラスinitialize メソッドがオーバーライドされているときは,インスタンスの生成時に自動的に呼ばれます(次節の例を参照).initialize メソッドは,C++ におけるコンストラクタと同じような働きをします.
      obj1 = Test.new
      obj2 = Test.new(10, 20)   # 引数がある場合				

      プログラム例 6.1] クラスの定義と参照 

      01	#************************/
      02	# クラスの定義と参照    */
      03	#   coded by Y.Suganuma */
      04	#************************/
      05	v_local_1    = 5
      06	$_global_1   = 15
      07	V_const_1    = 25
      08	@_instance_1 = 35
      09	@@_class_1   = 45   # 警告メッセージ出力
      10	
      11	class Test
      12		v_local_2    = 10
      13		$_global_2   = 20
      14		V_const_2    = 30
      15		@_instance_2 = 40
      16		@@_class_2   = 50
      17	
      18		print "クラス内からクラス外の変数を参照\n"
      19	#	print "     v_local_1    : ", v_local_1, "\n"   # 参照不可(エラー)
      20		print "     $_global_1   : ", $_global_1, "\n"
      21		print "     V_const_1    : ", V_const_1, "\n"
      22		print "     @_instance_1 : ", @_instance_1, "\n"   # 参照不可
      23		print "     @@_class_1   : ", @@_class_1, "\n"
      24	
      25		print "クラス内からクラス内の変数を参照\n"
      26		print "     v_local_2    : ", v_local_2, "\n"
      27		print "     $_global_2   : ", $_global_2, "\n"
      28		print "     V_const_2    : ", V_const_2, "\n"
      29		print "     @_instance_2 : ", @_instance_2, "\n"
      30		print "     @@_class_2   : ", @@_class_2, "\n"
      31				# コンストラクタ
      32		def initialize(par = 100)
      33			@_instance_2 = par
      34		end
      35				# インスタンス変数の出力
      36		def out
      37			print "     @_instance_2 : ", @_instance_2, "\n"
      38		end
      39				# インスタンス変数を外部から参照可能にする
      40		attr("_instance_2", true)
      41	end
      42	
      43	print "クラス外からクラス外の変数を参照\n"
      44	print "     v_local_1    : ", v_local_1, "\n"
      45	print "     $_global_1   : ", $_global_1, "\n"
      46	print "     V_const_1    : ", V_const_1, "\n"
      47	print "     @_instance_1 : ", @_instance_1, "\n"
      48	print "     @@_class_1   : ", @@_class_1, "\n"   # 警告メッセージ出力
      49	
      50	print "クラス外からクラス内の変数を参照\n"
      51	#print "     v_local_2    : ", Test::v_local_2, "\n"   # 参照不可(エラー)
      52	print "     $_global_2   : ", $_global_2, "\n"   # Test:: を付加するとエラー
      53	print "     V_const_2    : ", Test::V_const_2, "\n"
      54	print "     @_instance_2 : ", @_instance_2, "\n"   # 参照不可
      55	#print "     @@_class_2   : ", @@_class_2, "\n"   # 参照不可(エラー)
      56				# インスタンスの生成
      57	obj1 = Test.new
      58	obj2 = Test.new(200)
      59	
      60	print "インスタンスからクラス内の変数を参照\n"
      61	#print "     v_local_2    : ", obj1.v_local_2, "\n"   # 参照不可(エラー)
      62	#print "     $_global_2   : ", obj1.$_global_2, "\n"   # 参照不可(エラー)
      63	#print "     V_const_2    : ", obj1.V_const_2, "\n"   # 参照不可(エラー)
      64	obj1.out()
      65	obj2.out()
      66	#print "     @_instance_2 : ", obj1.@_instance_2, "\n"   # 参照不可(エラー)
      67	print "     @_instance_2 : ", obj1._instance_2, "\n"   # attr による設定が必要
      68	print "     @_instance_2 : ", obj2._instance_2, "\n"
      69	#print "     @@_class_2   : ", obj1.@@_class_2, "\n"   # 参照不可(エラー)
      				
      05 行目~ 09 行目

        クラスの外で,ローカル変数グローバル変数定数インスタンス変数クラス変数を定義しています.インスタンス変数,クラス変数は,本来クラス内で定義すべき変数ですので,ここで定義することにはあまり意味がありません.そのため,09 行目に対しては警告メッセージが出力されます.

      12 行目~ 16 行目

        05 行目~ 09 行目と同様,5 つの変数を定義しています.

      18 行目~ 23 行目

        05 行目~ 09 行目において定義した変数をクラス内から参照しています.その中の,ローカル変数及びインスタンス変数は参照不可能です.グローバル変数及び定数は,定義された以降はどこからでも参照可能な変数ですので,当然の結果だと思います.

      25 行目~ 30 行目

        12 行目~ 16 行目において定義した変数をクラス内から参照しています.同じクラス内の変数ですので,当然,全ての変数を参照可能です.

      32 行目~ 34 行目

        Test クラスのコンストラクタです.Test クラスのインスタンスが生成されたとき( 57,58 行目),最初に呼ばれます.ここでは,インスタンス変数の設定を行っています.

      36 行目~ 38 行目

        インスタンス変数はそのクラスまたはサブクラスのメソッドから参照可能な変数です.従って,クラスの外から 66 行目のような形では参照できません.このメソッドはの目的は,インスタンス変数を参照することです( 64,65 行目).

      40 行目

        インスタンス変数を,66 行目のような形では参照するための処置です.この記述があるため,67,68 行目の参照が可能になります.

      43 行目~ 48 行目

        クラス外からクラス外の変数を参照を参照しています.基本的に,全ての変数を参照可能です.

      50 行目~ 55 行目

        Test クラスのインスタンスを生成する前に,クラス外からクラス内の変数を参照を参照しています.グローバル変数及び定数を参照可能です.定数に対しては,「 Test:: 」を付加しないとエラーになり,グローバル変数に対しては,付加するとエラーになります.

      57,58 行目

        インスタンス変数の値を変えた 2 つのインスタンスを生成しています.

      60 行目~ 69 行目

        インスタンスを通して,5 つの変数を参照しています.「 インスタンス.変数名」の形で参照できるのは,インスタンス変数だけです( 40 行目の処置が必要).勿論,この時点でも,グローバル変数と定数の参照は可能です(参照方法が異なる)
      (出力)
      test.rb:9: warning: class variable access from toplevel
      クラス内からクラス外の変数を参照
           $_global_1   : 15
           V_const_1    : 25
           @_instance_1 :
           @@_class_1   : 45
      クラス内からクラス内の変数を参照
           v_local_2    : 10
           $_global_2   : 20
           V_const_2    : 30
           @_instance_2 : 40
           @@_class_2   : 50
      クラス外からクラス外の変数を参照
           v_local_1    : 5
           $_global_1   : 15
           V_const_1    : 25
           @_instance_1 : 35
      test.rb:48: warning: class variable access from toplevel
           @@_class_1   : 45
      クラス外からクラス内の変数を参照
           $_global_2   : 20
           V_const_2    : 30
           @_instance_2 :
      オブジェクトからクラス内の変数を参照
           @_instance_2 : 100
           @_instance_2 : 200
           @_instance_2 : 100
           @_instance_2 : 200
      				

    2. 派生クラス(継承)

        あるクラスに宣言されているメソッドなどをすべて受け継ぎ(メソッドなどを利用できるようにする),それに新たなメソッドを追加,受け継いだメソッドの修正などを行い,新しいクラスを宣言することができます.これを,クラスの継承と呼びます.このとき,基になったクラスをスーパークラス(親クラス),新たに作成したクラスをサブクラス(子クラス)と呼びます.継承するクラスを示すためには,下の例に示すように, < を使用します.なお,Ruby においては,複数のクラスを継承する多重継承は許されません.

        @@ で始まる変数はクラス変数と呼びます.クラス変数は,クラス定義の中で定義され,そのクラス,及び,そのサブクラスから参照できます( C++ における protected 宣言された変数に似ています).このような点から,クラス変数は,クラスやサブクラスなどで共有されるグローバル変数であるとみなすことができます.

        以下に示すプログラム例において,クラス Test は,クラス P_Test を継承しています.また,Ruby のプログラムを書くことは,Object クラスを継承したクラスを定義することに相当しますので,例に示すプログラム全体が,Object クラスを継承したサブクラスになっています.従って,プログラムのどこからでも,Object クラスに定義されているメソッドやクラス変数を参照可能です.

        クラス P_Test で定義されているクラス変数 @@_class やメソッド func は,クラス P_Test を継承しているクラス Test からも参照可能です.しかし,クラス変数 @@_class は,クラス Test 内にネストされたクラス Test_in やクラスの外からは参照できません.
      class P_Test
      	@@_class = 10
      	print "クラス変数 @@_class = ", @@_class, " (in P_Test)\n"
      	def func
      		print "こんにちは!!"
      	end
      end
      
      class Test < P_Test
      	print "クラス変数 @@_class = ", @@_class, " (in Test)\n", 
      	class Test_in
      #		print "クラス変数 @@_class = ",  @@_class, " (in Test)\n"  参照不可
      	end
      end
      
      #print "クラス変数 @@_class = ", @@_class, "\n"  参照不可
      t_obj1 = P_Test.new
      t_obj1.func
      print " (in P_Test)\n"
      t_obj2 = P_Test.new
      t_obj2.func
      print " (in Test)\n"
      				
      (出力)
      クラス変数 @@_class = 10 (in P_Test)
      クラス変数 @@_class = 10 (in Test)
      こんにちは!! (in P_Test)
      こんにちは!! (in Test)				

        super は現在のメソッドがオーバーライドしているスーパークラスのメソッドを呼び出します.括弧と引数が省略された場合には現在のメソッドの引数がそのまま引き渡されます.引数を渡さずにオーバーライドしたメソッドを呼び出すには super() と括弧を明示します.以下に示すプログラムを実行すると,250 という出力が得られます.
      class P_Test
      	def add(p1, p2=20, p3=30)
      		return p1 + p2 + p3
      	end
      end
      
      class Test < P_Test
      	def add(p)
      		return p + super
      	end
      end
      
      print Test.new.add(100)
      				

    3. モジュール

        Ruby では,多重継承(複数のクラスを継承する機能)を許していません.その代わりに,モジュールという機能があります.モジュールはインスタンスを持たないクラスといっても良いかもしれません.クラスの定義において,Module クラスの include メソッドによりモジュールの機能を取りこんで使用します.このことを,Mix-in と呼びます.下の例では,モジュール Add_m をクラス Test に取り込んでいます.実行すると,30 と 90 という結果が得られます.
      module Add_m
      	Const = 100
      	def add(a, b)
      		return a + b
      	end
      end
      
      class Test
      	include Add_m
      	def sub(a, b)
      		return a - b + Const
      	end
      end
      
      printf "%d\n", Test.new.add(10, 20)
      printf "%d\n", Test.new.sub(10, 20)
      				
        下に示す例のように,クラスではなく特定のインスタンス(オブジェクト)にだけモジュールの機能を追加することも可能です.その際には,Object クラスの extend メソッドを使用します。
      module Add_m
      	Const = 100
      	def add(a, b)
      		return a + b
      	end
      end
      
      class Test
      	def sub(a, b)
      		return a - b
      	end
      end
      
      obj = Test.new
      obj.extend Add_m
      printf "%d\n", obj.add(10, 20)
      printf "%d\n", obj.sub(10, 20)
      				

    4. 演算子の多重定義(オーバーロード)

        ほとんどの演算子は,特別な形式のメソッド呼び出しですので再定義(多重定義オーバーロード)可能です.しかし,一部のものは言語に組み込まれているため再定義できません.

      • 再定義できる演算子(メソッド)
      •     |  ^  &  <=>  ==  ===  =~  >   >=  <   <=   <<  >>
            +  -  *  /    %   **   ~   []  []=  `					
      • 再定義できない演算子(制御構造):  演算子の組合せである自己代入演算子と,!=,!~ も再定義できません.
            =  ?:  ..  ...  !  not  &&  and  ||  or  ::					
        再定義可能な演算子を定義するいくつかの例を以下に示します.
      # 二項演算子
        def +(other)   # obj + other
        def -(other)   # obj - other
      
      # 単項プラス/マイナス
        def +@   # +obj
        def -@   # -obj
      
      # [] と []=
        def [](key)          # obj[key]
        def []=(key, value)      # obj[key] = value
        def []=(key, key2, value)   # obj[key, key2] = value				
        以下,+ 演算子,及び,単項 - 演算子に対するプログラム例と出力例を示します.なお,C++ にも同じような機能が存在しますので,C++ よるプログラム例も添付しておきます.

      + 演算子の多重定義 

      class Comp
      	def initialize(_real = 0, _imag = 0)
      		@_real = _real
      		@_imag = _imag
      	end
      	def out(cr = "")
      		print "(", @_real, ", ", @_imag, ")", cr
      	end
      	def real
      		return @_real
      	end
      	def imag
      		return @_imag
      	end
      	def +(obj)
      		c = Comp.new(@_real+obj.real, @_imag+obj.imag)
      		return c
      	end
      end
      
      a = Comp.new(1, 2)
      b = Comp.new(2, 3)
      a.out("\n")
      b.out("\n")
      c = a + b
      c.out("\n")
      				
      (出力)
      (1, 2)
      (2, 3)
      (3, 5)				

      単項 - 演算子の多重定義 

      class Comp
      	def initialize(_real = 0, _imag = 0)
      		@_real = _real
      		@_imag = _imag
      	end
      	def out(cr = "")
      		print "(", @_real, ", ", @_imag, ")", cr
      	end
      	def -@
      		c = Comp.new(-@_real, -@_imag)
      		return c
      	end
      end
      
      a = Comp.new(1, 2)
      a.out("\n")
      c = -a
      c.out("\n")
      				
      (出力)
      (1, 2)
      (-1, -2)				

    5. 特異クラスと特異メソッドこのような機能,必要?

  7. 入出力

    1. 標準入出力

        入出力を行うためには,組み込み関数の gets,getline,getlinespprintprintf などを使用しますが,デフォルトで,その入出力先は標準入力,標準出力になっていますので,そのままキーボードからの入力,コマンドプロンプトへの出力に利用できます.以下のプログラムでは,入出力関数を使用し,その違いを見ています.

        gets は,入力された 1 行分のデータを文字列として読み込みます.下のプログラムにおいては,読み込んだデータを String クラスのメソッド strip によって前後の余分なスペースを削除した後,split を利用して 2 つのデータに分割し,配列 x に代入しています( 02 行目).03 ~ 10 行目では,いくつかの関数を利用して,配列 x の内容を出力しています.関数 print は,カンマで区切られたデータを順に出力します.データ毎にスペースは入らず,また,改行も行いませんので,自分で設定してやる必要があります.関数 p は,関数 print と似ていますが,データを出力する毎に改行が行われます.関数 puts も,データ毎に改行が行われると共に,配列の場合は,その要素毎に出力されます.関数 printf は,C/C++ の標準関数 printf と同様,フォーマットされて出力されます.11 ~ 12 行目では,String クラスのメソッド to_i を利用して,配列に記憶されている文字列を整数に変換した後,加算処理を行い,その結果を出力しています.
      01	print "データを 2 つ入力してください "
      02	x = (gets().strip()).split()
      03	print "*** 配列 x を print で出力 ***\n"
      04	print "print ", x, "\n"
      05	print "*** 配列 x を p で出力 ***\n"
      06	p "p ", x
      07	print "*** 配列 x を puts で出力 ***\n"
      08	puts "puts ", x
      09	print "*** 配列 x を printf で出力 ***\n"
      10	printf "x[0] %d x[1] %d\n", x[0], x[1]
      11	print "*** x[0] と x[1] の加算結果を print で出力 ***\n"
      12	print "x[0] + x[1] = ", x[0].to_i() + x[1].to_i(), "\n"
      				
      (出力)
      データを 2 つ入力してください 1 2
      *** 配列 x を print で出力 ***
      print ["1", "2"]
      *** 配列 x を p で出力 ***
      "p "
      ["1", "2"]
      *** 配列 x を puts で出力 ***
      puts
      1
      2
      *** 配列 x を printf で出力 ***
      x[0] 1 x[1] 2
      *** x[0] と x[1] の加算結果を print で出力 ***
      x[0] + x[1] = 3
      				

    2. ファイル入出力

        ファイル入出力のためには,IO クラス,または,File クラスを利用します.下に示すプログラムにおいては,ファイル data1.txt の各行には,2 つの整数がスペースで区切って,以下に示すように記述されているものとします.
      1 2
      3 4
      ・・・・・				
        また,11 ~ 12 行目に示すように,標準入出力を示す組み込み変数 $stdin$stdout に対してファイル入出力を設定することも可能です.これを元の状態に戻すには,17 ~ 18 行目に示すように,組み込み定数 STDINSTDOUT を使用します.
      01	inp = File.open("data1.txt", "r")   # data1.txt から入力
      02	out = File.open("data2.txt", "w")   # data2.txt へ出力
      03	while line = inp.gets   # データが無くなるまで繰り返す
      04		x = line.split()
      05		out.print x[0].to_i() + x[1].to_i(), "\n"
      06	end
      07	inp.close   # ファイルを閉じる
      08	out.close   # ファイルを閉じる
      09	
      10	=begin
      11	$stdin  = open("data1.txt", "r")
      12	$stdout = open("data2.txt", "w")
      13	while line = gets
      14		x = line.split()
      15		print x[0].to_i() + x[1].to_i(), "\n"
      16	end
      17	$stdin  = STDIN   # 通常の標準入力に戻す
      18	$stdout = STDOUT   # 通常の標準出力に戻す
      19	=end
      				

  8. 変数の有効範囲(スコープ)

      変数の有効範囲については,プログラム例を使用して説明していきます.

    プログラム例 8.1] 変数の有効範囲(スコープ) 

    01	#***************************/
    02	# 変数の有効範囲(スコープ) */
    03	#      coded by Y.Suganuma */
    04	#***************************/
    05	
    06	#******************/
    07	# クラス Example1 */
    08	#******************/
    09	class Example1
    10		Const    = 1000;
    11		@@_class = 2000;
    12	
    13		def initialize
    14			@_pro = 3000;
    15		end
    16	
    17		def sub1()
    18			printf("sub1 Const %d @@_class %d @_pro %d\n", Const, @@_class, @_pro);
    19		end
    20	end
    21	
    22	#******************/
    23	# クラス Example2 */
    24	#******************/
    25	class Example2 < Example1
    26		public
    27			def sub2()
    28				printf("sub2 Const %d @@_class %d @_pro %d\n", Const, @@_class, @_pro);
    29			end
    30	
    31		attr_accessor("_pro");
    32	end
    33	
    34	#***********/
    35	# 関数 sub */
    36	#***********/
    37	def sub()   # この位置で定義する必要がある
    38		x = 40;
    39		printf("   sub x %d\n", x);
    40		printf("   sub z %d\n", $z);
    41	end
    42	
    43	#***************/
    44	# main program */
    45	#***************/
    46			# ブロック
    47	x  = 10;
    48	$z = 30;
    49	if x > 5
    50		printf("block x %d\n", x);
    51		x = 15;
    52		y = 20;
    53		printf("block x %d\n", x);
    54		printf("block y %d\n", y);
    55	else
    56		printf("block x %d\n", x);
    57		x = -15;
    58		printf("block x %d\n", x);
    59	end
    60	sub();
    61	printf("x %d\n", x);
    62	printf("y %d\n", y);   # 最初の x が 1 の時は,y が未定義のためエラー
    63			# クラス
    64	ex = Example2.new;
    65	ex.sub1();
    66	ex.sub2();
    67	printf("public member( Const ) %d\n", Example2::Const);
    68	printf("public member( @_pro ) %d\n", ex._pro);
    			
      まず,47 行目において,変数 x が定義され(変数 x に値が代入され),10 で初期設定されています.この変数 x は,この位置から main プログラムが終わる 68 行目まで有効になります.50 ~ 54 行目の if ブロック内の 50 行目において,変数 x の値が出力されていますが,当然,その結果は,47 行目で宣言されたときの値になります.しかし,51 行目において,再び,変数 x が宣言されていますが,Ruby の場合は,C++ の場合とは異なり,47 行目で宣言された変数 x に置き換わることになります.従って,ここで宣言された変数 x の有効範囲は,main プログラムの終わりである 68 行目までになります.実際,61 行目における出力文では,51 行目において宣言された変数 x の値が出力されます.同様に,52 行目で宣言された変数 y の有効範囲も 68 行目までとなります.この例では,問題がありませんが,47 行目における変数 x の初期値を 5 以下に設定すると,56 ~ 58 行目が実行されることになります.そのブロック内では,変数 y が使用されていませんので,62 行目の出力文はエラーになってしまいます.変数が定義されているか否か(変数に値が代入されているか否か)の見極めが困難である場合も多いと思いますので,十分注意してください.

      60 行目において関数 sub を呼んでいます.38 行目では,変数 x を宣言し,39 行目において,その値を出力しています.当然,38 行目の宣言を行わなければ,エラーになってしまいますし,また,38 行目の宣言によって,51 行目で宣言された x の値が影響を受けることはありません( 61 行目の出力文に対応する結果参照).しかし,変数 $z は関数 sub 内に宣言されておらず,main プログラムの 48 行目において宣言されています.このように,名前が $ で始まる変数はグローバル変数と呼ばれ,すべての関数等から参照が可能になります.逆に,変数 x や y のように,あるブロック(関数を含む)内だけで有効な変数を,ローカル変数と呼びます.以上,47 ~ 62 行目内の出力文(関数 sub 内の出力文を含む)によって,以下に示すような出力が得られます.
    block x 10
    block x 15
    block y 20
       sub x 40
       sub z 30
    x 15
    y 20				
      次に,クラスに付いて考えてみます.09 ~ 20 行目においてクラス Example1 が定義され,25 ~ 32 行目では,クラス Example1 を継承する形で,クラス Example2 が定義されています.Ruby においては,クラス Example の各変数や関数のアクセス権は,そのままクラス Example2 に引き継がれます.

      64 行目において,Example2 のインスタンス ex を生成し,65 行目では,クラス Example1 から継承した関数 sub1 を通して,3 つの変数を出力しており,3 つの変数の値がそのまま出力されます.また,66 行目では,クラス Example2 に追加された関数 sub2 を通して各変数を出力しています.Ruby においては,大文字で始まる変数名は,定数とみなされ,クラスの外からも 67 行目のような形で参照可能です.また,@@ で始まるる変数名は,クラス変数と呼ばれ,クラス及びそのサブクラスで共通の値を持ちます.ただし,クラスの外からは,参照することはできません.さらに,@ で始まる変数は,インスタンス変数と呼ばれ,各インスタンス毎に異なる値を持つことができます.C++ における private 指定されたメンバー変数に似ています.そのため,そのままの状態では,クラスの外部から参照することはできません.しかし,31 行目のような指定( attr_accessor )を行うことによって,68 行目のような形でクラス外部から参照可能になります.以上,64 ~ 68 行目内の出力文によって,以下に示すような出力が得られます.
    sub1 Const 1000 @@_class 2000 @_pro 3000
    sub2 Const 1000 @@_class 2000 @_pro 3000
    public member( Const ) 1000
    public member( @_pro ) 3000
    				

情報学部 菅沼ホーム 目次 索引