SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

突然ですがクイズです!(AD)

突然ですがクイズです!-その1

Delphi、C++、Java、C#、PHP、Rubyの6言語の動作の違いをクイズで考察

  • このエントリーをはてなブックマークに追加

この記事では、Delphi、C++、Java、C#、PHP、Rubyの6言語を取り上げ、同じような動作を記述していながら、結果が異なるケースを紹介し、言語ごとの挙動の違いを考察し、プログラミング言語に対する理解を深めます。

  • このエントリーをはてなブックマークに追加

はじめに

 職務上、さまざまなプログラミング言語を扱うことがあるのですが、言語によって文法や挙動の違いに戸惑ったり、うっかりミスをしたりすることがあります。でも、1つの言語だと当たり前に思ってしまうことも、複数の言語を並べることで、逆によく見えてくることもあります。この記事では、Delphi、C++、Java、C#、PHP、Rubyの6言語を取り上げ、同じような動作を記述していながら、結果が異なるケースを紹介し、言語ごとの挙動の違いを考察します。

 なお、この記事は、CodeGearブログに掲載されたクイズに正解とその解説を加えて再構成したものです。

最初のクイズ(変数のスコープ)

問題

 最初のクイズは、変数のスコープに関するものです。グローバル変数やstaticなメンバを使ったコードを6つの言語で書いてみましたが、1つだけ出力結果が違うものがありました。それはどれでしょうか?

Delphi
program Project1;
{$APPTYPE CONSOLE}
var 
  x: Integer;

procedure test;
begin
  Write(x);
end;

begin
  x := 1;
  test;
end.
C++
#include <iostream>

int x;

void test() {
  std::cout << ::x;
}

int main(int argc, char* argv[])
{
  ::x = 1;
  test();
  return 0;
}
Java
package project1;
public class Project1 {
  public static void test() {
   System.out.print(x);
  }
  public static void main(String[] args) {
    x = 1;
    test();
  }
  private static int x;
}
C#
namespace project1 {
public class Project1 {
  public static void test() {
    System.Console.Write(x);
  }
  public static void Main() {
    x = 1;
    test();
  }
  private static int x;
}
}
PHP5
<?php
global $x;

function test() {
  echo $x;
}

$x = 1;
test();
?>
Ruby
def test
  print $x
end

$x = 1
test
解説

 正解は「PHP」。

 PHPでは、期待した「1」は出力されません。それ以外の言語では正しく「1」が出力されます。Delphi/C++/Java/C#については特にうっかりミスを起こすことはないでしょう。Rubyでは、グローバル変数を使用する場合には、変数の先頭に「$」を追加する必要があります。PHPでは、変数のスコープは関数の中と外とで異なります。そのため、以下に示すコードのように、グローバル変数を利用する場合には関数の中でもglobalの宣言が必要になります。これで期待した「1」が出力されます。

function test() {
  global $x;
  echo $x;
}

文字の長さ

問題

 続いて文字の長さについて。6つの言語で「あ」の長さを取得してみました。しかし、やはり1つだけ出力結果が違うものがありました。それはどれでしょうか?

 それぞれ、ソースファイルの文字コードやコンパイルオプションなどは正しく設定されているハズなのですが…。

Delphi for Win32 - ソースコードはSJISで保存
program Project1;
{$APPTYPE CONSOLE}
var
  str: WideString;
begin 
  str := 'あ';
  Write(Length(str));
end.
C++Builder - ソースコードはSJISで保存
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
  wstring str = L"あ";
  cout << str.length();
  return 0;
}
Java - ソースコードはUTF-8で保存
package project1;
public class Project1 {
  public static void main(String[] args) {
     String str = "あ";
     System.out.print(str.length());
  }
}
C#.NET - ソースコードはSJISで保存
namespace project1 {
public class Project1 {
  public static void Main() {
    string str = "あ";
    System.Console.Write(str.Length);
  }
}
}
PHP5 - ソースコードはUTF-8で保存
<?php
ini_set('mbstring.internal_encoding', 'UTF-8');
$str = 'あ';
echo mb_strlen($str);
?>
Ruby1.8.6 - ソースコードはUTF-8で保存
$KCODE='u'
str = 'あ'
print str.length
解説

 正解は「Ruby」。

 Rubyでは、期待した「1」ではなく「3」が出力されます。それ以外の言語では正しく「1」が出力されます。Delphi/C++/Java/C#では基本的に1文字を16bitのUnicodeで管理することが可能です。特にうっかりミスを起こすことはないでしょう。

 PHPでは、文字を処理する際のエンコード(UTF-8やSJISなど)を設定することができます。問題のコードでは、UTF-8でエンコードされた「あ」をmb_strlen関数を利用して正しく1文字として認識しています。しかし、Rubyでは文字列の長さを取得するlengthメソッドはUTF-8に対応しておらず、バイト単位のサイズが返されます。そのため「3」が出力されました。

 そこでRubyでは、以下に示すコードのようにUTF-8に対応したライブラリ「jcode」または「RailsのActiveSupport」を利用することで正しく「1」が出力されます。

jocdeのjlengthを使用した例
require 'jcode'
$KCODE='u'
str = 'あ'
print str.jlength
RailsのActiveSupportを使用した例
require 'rubygems' 
require 'active_support'
$KCODE='u'
str = 'あ'
print str.chars.length

文字列の足し算と比較

問題

 次は、文字列の足し算と比較(=、==、===、equals)です、6つの言語で試してみましたが、やはり、1つだけ出力結果が異なるものがあります。どれでしょう?

Delphi
program Project1;
{$APPTYPE CONSOLE}
var
  s1,s2: string;
begin
  s1 := ‘ab’;
  s2 := ‘a’ + ‘b’;
  if s1 = s2 then
    Write(’equal’);
end.
C++
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
  string s1 = "ab";
  string s2 = string("a") + string("b");
  if( s1 == s2 )
    cout << "equal";
  return 0;
}
Java
package project1;
public class Project1 {
  public static void main(String[] args) {
    String s1 = "ab";
    String s2 = "a" + "b";
    if( s1.equals(s2) )
      System.out.print("equal");
  }
}
C#
namespace project1 {
public class Project1 {
  public static void Main() {
    string s1 = "ab";
    string s2 = "a" + "b";
    if( s1 == s2 )
      System.Console.Write("equal");
  }
}
}
PHP5
<?php
$s1 = "ab";
$s2 = "a" + "b";
if( $s1 === $s2 )
  echo "equal";
?>
Ruby
s1 = "ab"
s2 = "a" + "b"
if s1 === s2
  print "equal"
end
解説

 正解は「PHP」。

 PHPでは、期待した「equal」は出力されません。それ以外の言語では正しく「equal」が出力され、2つの文字列が等しい内容であることが確認できます。Delphi/C++/Java/C#/Rubyについては特にうっかりミスを起こすことはないでしょう。

 PHPでは、文字列同士を結合する演算子は「+」ではなく「.(ドット)」です。問題のコードのように「+」を使用して文字列同士を結合しても特にエラーは発生しません。「.(ドット)」を使うことで期待した「equal」が出力されます。

$s2 = "a" . "b";

 なお、Javaでは文字列型を含むクラス型の変数同士の等値性を判定する場合、「==演算子」ではなく「equalsメソッド」を使用します。これとは対照的に、Delphiの「=演算子」、C++の「==演算子」、C#の「==演算子」は、文字列においては変数の参照先を比較するのではなく、文字列の内容の等値性を判定する処理へと置き換えられます。この点、注意が必要です。

文字列で出力

問題

 次は、インスタンスを文字列として表現して出力する場合です。この中でも1つだけ出力結果が違うものがあります。それは、どの言語でしょうか?

Delphi
program Project1;
{$APPTYPE CONSOLE}

type
  TMyClass = record
    class operator Explicit(x: TMyClass): string;
  end;

class operator TMyClass.Explicit(x: TMyClass): string;
begin
  Result := ’TMyClass’;
end;

var
  obj: TMyClass;
begin
  Writeln(string(obj));
end.
C++
#include <iostream>
using namespace std;

class TMyClass {
  friend ostream& operator <<(ostream& os, const TMyClass& obj) {
    os << "TMyClass";
    return os;
  }
};

int main(int argc, char* argv[])
{
  cout << TMyClass() << endl;
  return 0;
}
Java
package project1;

class TMyClass {
  public String toString() {
    return "TMyClass";
  }
}

public class Project1 {
  public static void main(String[] args) {
    System.out.println(new TMyClass());
  }
}
C#
namespace project1 {
class TMyClass {
  public string toString() {
    return "TMyClass";
  }
}

public class Project1 {
  public static void Main() {
    System.Console.WriteLine(new TMyClass());
  }
}
}
PHP5
<?php
class TMyClass {
  public function __toString() {
    return "TMyClass";
  }
}

echo (string)new TMyClass() . "\n";
?>
Ruby
class TMyClass
  def to_s
    "TMyClass"
  end
end

puts TMyClass.new
解説

 正解は「C#」。

 C#では、期待した「TMyClass」ではなく「project1.TMyClass」が出力されます。それ以外の言語では正しく「TMyClass」が出力されます。これは一体どうしたというのでしょう。

 Delphi(record型のみ)では明示的なキャストが可能ですし、C++では演算子オーバーロードを定義することが可能です。JavaではtoString()メソッドを、PHPでは__toString()メソッドを、Rubyではto_sメソッドをオーバーライドすることにより、文字列としての表現方法を独自に実装することが可能です。もちろんC#でも「ObjectクラスのToString()メソッド」をオーバーライドすることで同様の処理が可能です。

 しかし問題のコードでは、オーバーライドが正しく行われていません。コンパイル時には警告もエラーも発生しませんでした。C#ではメソッドの大文字・小文字を区別しますし、メソッドのオーバーライドにはoverride修飾子が必要なのです。

 このため「ObjectクラスのToString()メソッド」が呼び出され、その既定の実装である「クラスの完全限定名」が文字列として返されたのです。正しくは以下のように記述します。

class TMyClass {
  public override string ToString() {
    return "TMyClass";
  }
}

メソッドのオーバーライド

問題

 次は、ちょっと難しくなります。6種類の言語を使って「親クラス」と「子クラス」を定義して、メソッドのオーバーライドの処理を書いてみました。同じような動作を期待したのですが、1つだけ出力結果が違うものがありました。それは、どの言語でしょうか?

Delphi
program Project1;
{$APPTYPE CONSOLE}

type
  TParent = class
  public
    constructor Create;
  protected
    procedure foo; virtual;
  end;

  TChild = class(TParent)
  public
    constructor Create;
  protected
    procedure foo; override;
  end;

constructor TParent.Create;
begin
  Self.foo;
end;

procedure TParent.foo;
begin
  Writeln(’TParent#foo’);
end;

constructor TChild.Create;
begin
  inherited;
end;

procedure TChild.foo;
begin
  inherited;
  Writeln(’TChild#foo’);
end;

begin
  TChild.Create;
end.
C++
#include <iostream>
using namespace std;

class TParent {
public:
  TParent(){
    this->foo();
  }
protected:
  virtual void foo() {
    cout << "TParent#foo" << endl;
  }
};

class TChild : public TParent {
public:
  TChild() : TParent(){
  }
protected:
  virtual void foo() {
    TParent::foo();
    cout << "TChild#foo" << endl;
  }
};

int main(int argc, char* argv[])
{
  new TChild();
  return 0;
}
Java
package project1;

class TParent {
  public TParent() {
    this.foo();
  }
  protected void foo() {
    System.out.println("TParent#foo");
  }
}

class TChild extends TParent {
  public TChild() {
  }
  protected void foo() {
    super.foo();
    System.out.println("TChild#foo");
  }
}

public class Project1 {
  public static void main(String[] args) {
    new TChild();
  }
}
C#
namespace project1 {
class TParent {
  public TParent() {
    this.foo();
  }
  protected virtual void foo() {
    System.Console.WriteLine("TParent#foo");
  }
}

class TChild : TParent {
  public TChild() : base() {
  }
  protected override void foo() {
    base.foo();
    System.Console.WriteLine("TChild#foo");
  }
}

public class Project1 {
  public static void Main() {
    new TChild();
  }
}
}
PHP5
<?php
class TParent {
  public function __construct() {
    $this->foo();
  }
  protected function foo() {
    echo "TParent#foo\n";
  }
}

class TChild extends TParent {
  public function __construct() {
    parent::__construct();
  }
  protected function foo() {
    parent::foo();
    echo "TChild#foo\n";
  }
}

new TChild();
?>
Ruby
class TParent
  def initialize
    foo
  end

  private
  def foo
    puts "TParent#foo"
  end
end

class TChild < TParent
  def initialize
    super
  end

  private
  def foo
    super
    puts "TChild#foo"
  end
end

TChild.new
解説

 正解は「C++」。

 C++以外の言語では期待した、

TParent#foo
TChild#foo

 という出力が得られますが、C++では単に「TParent#foo」とだけ出力されます。問題のコードはメソッドのオーバーライドの機能を使用して、以下のような順番で処理が進むことを期待しています。

  1. TChildクラスのインスタンスを生成
  2. TChildクラスのコンストラクタが呼び出される
  3. TParentクラスのコンストラクタが呼び出される
  4. TParentクラスのコンストラクタ内でfooメソッドを呼び出す
  5. TChildクラスのfooメソッドが呼び出される
  6. TChildクラスのfooメソッドがTParentクラスのfooメソッドを呼び出す

 もちろん一般的には、コンストラクタの処理中は、派生クラスでオーバーライドされる可能性のあるメソッドを呼び出すべきできはありません。構築途中のインスタンスが外部に漏れる危険性があるからです。Delphi/Java/C#/PHP/Rubyでは、TParentクラスのコンストラクタの処理中にTChildクラスのfooメソッドが呼び出されてしまいます。

 しかしC++では「コンストラクタの中から自身の仮想メソッドを呼び出してたとしても、派生クラスのメソッドではなく、自身のメソッドが呼び出される」という仕様になっています。このため、TChildクラスのfooメソッドは呼び出されることなく「TParent#foo」とだけ出力されたのです。

 なお、Rubyに関しては特徴的な動作があります。問題のコードではTParentクラスのfooメソッドは「private」として修飾され、一見すると派生クラスTChildではオーバーライドされないように思えます。しかし実際にはオーバーライドが行われてしまいます。この点、特に注意が必要でしょう。

次の記事

 

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/2289 2008/08/20 13:35

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング