Archive for March, 2006

STL Samples : <queue>

Thursday, March 30th, 2006

キュー

classqueue

優先順位キュー

classpriority_queue

source

#include <iostream>
#include <queue>

using namespace std;

void std_queue() {
  cout << "queue" << endl;
  queue<int> que;

  cout << "enqueue... ";
  for ( int i = 0; i < 5; i++ ) {
    que.push(i);
    cout << i << ' ';
  }
  cout << endl << "deque... ";
  while ( !que.empty() ) {
    cout << que.front() << ' ';
    que.pop();
  }
  cout << endl;
}

void std_priority_queue() {
  cout << "priority_queue" << endl;
  priority_queue<int> pque;
  int input[] = { 1,7,6,4,3,2,5 };

  cout << "enqueue... ";
  for ( int i = 0; i < 7; i++ ) {
    pque.push(input[i]);
    cout << input[i] << ' ';
  }
  cout << endl << "deque... ";
  while ( !pque.empty() ) {
    cout << pque.top() << ' ';
    pque.pop();
  }
  cout << endl;
}

STL Samples : <utility>

Thursday, March 30th, 2006

関係演算子

fuctionoperator!= fuctionoperator<= fuctionoperator>fuctionoperator>=

namespace rel_ops {
    template<class T> bool operator!=(const T&, const T&);
    template<class T> bool operator> (const T&, const T&);
    template<class T> bool operator<=(const T&, const T&);
    template<class T> bool operator>=(const T&, const T&);
  };

Tに比較演算==,<が定義されているとき、==から!=を、<から>,<=,>=を生成します。

source

#include <iostream>
#include <utility>
#include "foo.h"

using namespace std;
using namespace std::rel_ops;

void std_rel_ops() {
  cout << "operator!=, <=, >, >=" << endl;
  Foo f1(1);
  Foo f2(2);
  cout << boolalpha;
  cout << f1 << " == " << f2 << "..." << (f1 == f2) << endl;
  cout << f1 << " != " << f2 << "..." << (f1 != f2) << endl;
  cout << f1 << " <  " << f2 << "..." << (f1 <  f2) << endl;
  cout << f1 << " <= " << f2 << "..." << (f1 <= f2) << endl;
  cout << f1 << " >  " << f2 << "..." << (f1 >  f2) << endl;
  cout << f1 << " >= " << f2 << "..." << (f1 >= f2) << endl;
}

pair

structpair fuctionmake_pair

template <class T1, class T2>
  struct pair {
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    pair();
    pair(const T1& x, const T2& y);
    template<class U, class V> pair(const pair<U, V> &p);
  };

型T1,T2をメンバとする"2つ組"です。2つのpairの大小関係はそれぞれのfirstの大小関係に従い、両方のfirstが等しければsecondの大小関係に従います。

template <class T1, class T2>
  pair<T1,T2>
  make_pair(const T1& x, const T2& y);

pair<T1,T2>(x,y)を返します。

source

#include <iostream>
#include <utility>
#include "foo.h"
#include "bar.h"

using namespace std;

void std_pair() {
  cout << "pair, make_pair" << endl;
  pair<Foo,Bar> p(1,2);
  cout << "pair<Foo,Bar>: ["
       << p.first << "," << p.second << ']' << endl;
  p = make_pair(Foo(3), Bar(4));
  cout << "pair<Foo,Bar>: ["
       << p.first << "," << p.second << ']' << endl;
}

<stx/cstring>

Thursday, March 30th, 2006

DDJ-J 1998年5月号で紹介したSTL拡張コンポーネント”STX”の改版です。C Magazine Jan 2000で公開したtokenizerをreviseしました。

c_tokenizer<class charT>

Cで用いられる文字列(char*, unsigned char*, wchar_t*)をトークン分割するクラスです。テンプレート引数にはchar, unsigned char, wchar_tを与えることができ、それぞれSBCS(ascii), MBCS(Shift-JIS/EUC), DBCS(UNICODE)に対応したトークン分割を行ないます。

※ c_tokenizer はトークン分割対象および区切り文字集合のコピーを生成しません。したがって、c_tokenizer を利用している間にそれら2つの文字列の内容が変更されたときの動作は保証されません。

template<class charT>
class c_tokenizer {
public:
  typedef token_traits<charT> traits;
  typedef traits::value_type value_type;
  typedef traits::pointer pointer;
  typedef traits::const_pointer const_pointer;
  typedef traits::range_type range_type;
  typedef traits::size_type size_type;
  explicit c_tokenizer(const_pointer str =0, const_pointer del=0, bool ret=false)
    : str_(str), del_(del), cur_(str_), ret_(ret) {}
  bool       empty();
  pointer    next();
  range_type next_range();
  pointer    substr(range_type range);
  size_type  count() const;
  void set_str(const_pointer str);
  void set_delimiter(const_pointer del, bool ret =false);
private:
  const_pointer str_;
  const_pointer del_;
  const_pointer cur_;
  bool          ret_;
  void skip();
};
// sample
// "apple,banana,cherry" を "," で区切る
stx::c_tokenizer<char> tok("apple,banana,cherry", ",");
while ( !tok.empty() ) {
  char* token = tok.next();
  cout << token << " ";
  delete[] token;
}
cout << endl;

c_tokenizer (コンストラクタ)

template<class charT>
c_tokenizer<charT>::c_tokenizer(const_pointer str =0, const_pointer del=0, bool ret=false)
    : str_(str), del_(del), cur_(str_), ret_(ret) {}
str
:トークン分割対象となる文字列
del
:トークンを区切る文字の集合
ret
:区切り文字をトークンとして扱うならtrue

ret == true のとき、区切り文字をトークンとして扱います。たとえば、

// sample
// "apple_banana__cherry_" を "_" で区切る
bool ret = false;
stx::c_tokenizer<char> tok("apple_banana__cherry_", "_", ret);
while ( !tok.empty() ) {
  char* token = tok.next();
  cout << token << " ";
  delete[] token;
}
cout << endl;

では “apple”, “banana”, “cherry” が切り出されますが、ret = true とすると、”apple”, “_”, “banana”, “_”, “_”, “cherry”, “_” が切り出されます。

※ Shift-JIS, EUS などのマルチバイト文字列を分割するときは、stx::c_tokenizer<unsigned char> を用います。このとき、文字列はすべて unsigned char* となります。したがって必要に応じて char -> unsigned char のキャストが必要になります。

// sample
// "りんご、みかん、バナナ" を "、" で区切る
typedef unsigned char uchar;
stx::c_tokenizer<uchar> tok((uchar)"りんご、みかん、バナナ", (uchar)"、");
while ( !tok.empty() ) {
  uchar* token = tok.next();
  cout << token << " ";
  delete[] token;
}
cout << endl;

skip (private)

template<class charT>
void c_tokenizer<charT>::skip() {
  if ( !ret_ && cur_ )
    cur_ += traits::span(cur_,del_);
}

empty

template<class charT>
bool c_tokenizer<charT>::empty() {
  skip();
  return !cur_ || *cur_ == charT(0);
}

切り出すトークンが見つからないとき true を返します。

next

template<class charT>
c_tokenizer<charT>::pointer c_tokenizer<charT>::next() {
  return substr(next_range());
}

切り出されたトークンを返します。

※ 切り出されたトークンはヒープ領域から operator new によって取得した領域です。したがって、得られた文字列は明示的に delete[] しなければなりません。

next_range

template<class charT>
c_tokenizer<charT>::range_type c_tokenizer<charT>::next_range() {
  skip();
  const_pointer start = cur_;
  cur_ += traits::cspan(cur_, del_);
  if ( ret_ && start == cur_ && traits::span(cur_,del_) )
    cur_ = traits::advance(cur_);
  return range_type(start-str_, cur_-start);
}

トークンを切り出し、その位置と長さを表す std::pair<String::size_type,String::size_type> rangeを返します。range.first はトークンの開始位置、range.second はトークンの長さです。

substr

template<class charT>
c_tokenizer<charT>::pointer c_tokenizer<charT>::substr(range_type range) {
  pointer p = new value_type[range.second+1];
  for ( int i = 0; i < range.second; ++i )
    p[i] = str_[range.first + i];
  p[range.second] = value_type(0);
  return p;
}

引数 range が表すトークン文字列を返します。

※ 切り出されたトークンはヒープ領域から operator new によって取得した領域です。したがって、得られた文字列は明示的に delete[] しなければなりません。

set_str

template<class charT>
void c_tokenizer<charT>::set_str(const_pointer str) {
  str_ = str;
  cur_ = str;
}

トークン分割対象となる文字列を設定します。トークン分割の開始位置を0に設定します。

set_delimiter

template<class charT>
void c_tokenizer<charT>::set_delimiter(const_pointer del, bool ret) {
  del_ = del;
  ret_ = ret;
}

区切り文字の集合を設定します。区切り文字をトークンとして扱うときは第2引数を true としてください。

count

template<class charT>
c_tokenizer<charT>::size_type c_tokenizer<charT>::count() const {
  size_t count = 0;
  const_pointer currpos = cur_;
  while ( *currpos ) {
    if ( !ret_ )
      currpos += traits::span(currpos, del_);
    if ( !*currpos )
      break;
    const_pointer start = currpos;
    if ( *currpos )
    currpos += traits::cspan(currpos, del_);
    if ( ret_ && start == currpos && traits::span(currpos,del_) )
      currpos = traits::advance(currpos);
    ++count;
  }
  return count;
}

現在の位置から切り出されるトークンの数を返します。

st_tokenizer / mbs_tokenizer / wcs_tokenizer

typedef c_tokenizer<char>          str_tokenizer;
typedef c_tokenizer<unsigned char> mbs_tokenizer;
typedef c_tokenizer<wchar_t>       wcs_tokenizer;

使用頻度の多い c_tokenizer<char>, c_tokenizer<unsigned char> および c_tokenizer<wchar_t> をそれぞれ str_tokenizer, mbs_tokenizer, wcs_tokenizerとtypedefしました。

(実装メモ : traitsについて)

異なる文字セット(SBCS,MBCS,DBCS)に対応したc_tokenizerをひとつの実装で実現するため、traitsと呼ばれるテクニックを用いました。

たとえば文字列の中から区切り文字を探し出すのに、 SBCSならstrspn / MSCSなら_mbsspn / DBCSならwcsspn を呼ばなければなりません。このとき、traits と呼ばれるヘルパ関数の集合を定義し、それらtraitsのヘルパ関数を呼び出すことで文字セットに応じた関数の呼び分けを実現しています。

template<class charT> struct token_traits {};

// traits for SBCS(ascii)
template<> struct token_traits<char> {
  typedef char value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef size_t size_type;
  typedef std::pair<size_type,size_type> range_type;
  static size_type span(const_pointer x, const_pointer y)
    { return strspn(x,y); }
  static size_type cspan(const_pointer x, const_pointer y)
    { return strcspn(x,y); }
  static const_pointer advance(const_pointer x)
    { return x+1; }
};

// traits for MBCS(Shift-JIS, EUC)
template<> struct token_traits<unsigned char> {
  typedef unsigned char value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef size_t size_type;
  typedef std::pair<size_type,size_type> range_type;
  static size_type span(const_pointer x, const_pointer y)
    { return _mbsspn(x,y); }
  static size_type cspan(const_pointer x, const_pointer y)
    { return _mbscspn(x,y); }
  static const_pointer advance(const_pointer x)
    { return _mbsinc(x); }
};

// traits for DBCS(UNICODE)
template<> struct token_traits<wchar_t> {
  typedef wchar_t value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef size_t size_type;
  typedef std::pair<size_type,size_type> range_type;
  static size_type span(const_pointer x, const_pointer y)
    { return wcsspn(x,y); }
  static size_type cspan(const_pointer x, const_pointer y)
    { return wcscspn(x,y); }
  static const_pointer advance(const_pointer x)
    { return x+1; }
};

 

stx::basic_symbol<T>

Thursday, March 30th, 2006

より速い検索を行なうには…

STLが提供するset(multiset)/map(multimep)は要素の大小関係に基づいてコンテナ内の要素を2進木上に配置します。このとき、挿入/削除/検索に要する時間計算量は(コンテナ内の要素数をNとして)logNです。

以下に示すコードは、int/stringをキーとするmapの検索に要する時間を計測するものです。

#include <windows.h>
#include <iostream>
#include <string>
#include <map>

using namespace std;

const int N = 1000;

int                int_table[N];
string             string_table[N];

void init() {
  char buf[32];
  string key;
  for ( int i = 0; i < N; ++i ) {
    int_table[i] = i;
    sprintf(buf,"string as comparison key:%d",i);
    string_table[i] = buf;
  }
}

template<class Container, class T>
long trial(Container& container, const T* table, int repeat) {
  int i;
  // table[0..N-1]をcontainerに挿入する。
  for (i = 0; i < N; ++i)
    container[table[i]] = i;

  // table[0..N-1]をcontainerから検索し、その処理時間を求める。
  long t = GetTickCount();
  while ( repeat-- )
    for ( i = 0; i < N; ++i)
      container.find(table[i]);
  return GetTickCount() - t;
}

int main() {

  map<int,int>           intmap;
  map<string,int>        strmap;

  init();

  cout << "intmap " << trial(intmap, int_table,    100) << " [ms]\n";
  cout << "strmap " << trial(strmap, string_table, 100) << " [ms]\n";

  return 0;
}
実行結果
intmap 90 [ms]
strmap 1212 [ms]

このように、同じmapであっても、intとstringでは処理速度に大きな差があることがわかります。stringをキーとするmapはなぜこんなに遅いのでしょう。

それは、stringの比較に要する時間がintに比べ非常に大きいからです。

2つのstring a, bを比較するには、 (i = 0 を初期値として)a[i]とb[i]を比較します。このとき a[i] != b[i] であればその時点で大小の判断が完了しますが、a[i] == b[i]であったときは、さらにその次の文字a[i+1]とb[i+1]について比較しなければなりません。

すなわち、文字列の比較は、比較される文字列が長いほど時間がかかることになります。上の例のように末尾だけが異なるような文字列ではその傾向が顕著です。

このように、大小の比較に時間のかかる要素をキーとするsetやmapはかなり遅いものとなります。もし要素の大小比較が瞬時にして完了するなら、set/mapからの要素の挿入/削除/検索スピードを劇的に改善できそうです。

たとえば文字列に対しユニークな数値を与える、すなわち文字列から数値への変換が定義できれば、set<string>set<int> と同程度のスピードが見込めることでしょう。ただしこの変換を行なうとset内ではもはや文字列の大小関係に基づいた要素管理は行なわれませんから、コンテナ内の要素の格納順は予測できないものとなります。

文字列から数値への変換の最も高速なものはポインタです。すなわち、set<string>の代わりにset<string*>を使えば、set<int>並みのスピードが期待できます。

set<string*> spset;
string value = "apple";
spset.insert(&value);
...
set<string*>::iterator it = spset.find(&value);
...

しかしこれでは問題が発生します。

set<string*> spset;
string value = "apple";
spset.insert(&value);
...
string key = "apple";
set<string*>::iterator it = spset.find(&key);
...

上記のコードにおいてkeyとvalueの値は等しい(”apple”)のですが、ポインタ値は異なるので、set<string*>からvalueを検索できません。等しい要素からは等しい数値(ポインタ値)を対応付けなければなりません。

それを実現するのがbasic_symbolなのです。

ソースコード

#include <set>
#include <string>

namespace stx {

  template<class Key, class Comp =std::less<Key> >
  class basic_symbol {
  public:
    typedef unsigned long            hash_type;
    typedef Key                      key_type;

    basic_symbol()
      { k = &(*key_pool().insert(key_type()).first); }
    basic_symbol(const key_type& key)
      { k = &(*key_pool().insert(key).first); }
    basic_symbol(const basic_symbol& sym)
      : k(sym.k) {}

    basic_symbol& operator=(const basic_symbol& sym)
      { k = sym.k; return *this; }
    basic_symbol& operator=(const key_type& key)
      { k = &(*key_pool().insert(key).first); return *this; }

    const key_type& key() const  { return *k; }
    hash_type hash() const       { return hash_type(k); }

  private:
    typedef std::set<key_type, Comp> pool_allocator;
    static pool_allocator& key_pool();
    const key_type* k;

  };

  template<class Key, class Comp>
  basic_symbol<Key,Comp>::pool_allocator&
  basic_symbol<Key,Comp>::key_pool() {
    static pool_allocator pool;
    return pool;
  }

  template<class K, class C>
  inline bool operator==(const basic_symbol<K,C>& x, const basic_symbol<K,C>& y)
      { return x.hash() == y.hash(); }

  template<class K, class C>
  inline bool operator!=(const basic_symbol<K,C>& x, const basic_symbol<K,C>& y)
      { return x.hash() != y.hash(); }

  template<class K, class C>
  inline bool operator<(const basic_symbol<K,C>& x, const basic_symbol<K,C>& y)
      { return x.hash() < y.hash(); }

  template<class K, class C>
  inline bool operator<=(const basic_symbol<K,C>& x, const basic_symbol<K,C>& y)
      { return x.hash() <= y.hash(); }

  template<class K, class C>
  inline bool operator>(const basic_symbol<K,C>& x, const basic_symbol<K,C>& y)
      { return x.hash() > y.hash(); }

  template<class K, class C>
  inline bool operator>=(const basic_symbol<K,C>& x, const basic_symbol<K,C>& y)
      { return x.hash() >= y.hash(); }

  typedef basic_symbol<std::string>  string_symbol;
  typedef basic_symbol<std::wstring> wstring_symbol;

}

basic_symbolのからくり

basic_symbol<T>はその内部にset<T>のインスタンス’pool’をstaticメンバとして持っており、これが生成されたすべてのsymbolを抱き込んでいます。

symbolが生成されるとき、同じsymbolが既にpool内にあればそのsymbolのポインタを返します。そうでなければ新たに生成してpoolに登録し、そのポインタを返します。

こうすることでstring_symbol x("apple")string_symbol y("apple")は同じ値(ポインタ値)を持つわけです。

コンストラクタ

basic_symbol()

key_type()を基にsymbolを生成します(したがってkey_typeはデフォルトコンストラクタが定義されていなければなりません)。

basic_symbol(const key_type& key)

keyに基づいてユニークな値を持つsymbolを生成します。

basic_symbol(const basic_symbol& sym)

symの持つsymbol値(ポインタ値)がコピーされます。

コピー演算子

basic_symbol& operator=(const basic_symbol& sym)

symの持つsymbol値(ポインタ値)がコピーされます。

basic_symbol& operator=(const key_type& key)

keyに基づいてユニークな値を持つsymbolがセットされます。

key / hash

const key_type& key() const

symbol値に対応するkey_typeを返します。

hash_type hash() const

symbol値を返します。

サンプルコード

冒頭に示したコードをbasic_symbolで書き換えてみました。

#include <windows.h>
#include <iostream>
#include <string>
#include <stx/symbol>
#include <map>

using namespace std;

const int N = 1000;

string             string_table[N];
stx::string_symbol symbol_table[N]; 

void init() {
  char buf[32];
  string key;
  for ( int i = 0; i < N; ++i ) {
    sprintf(buf,"string as comparison key:%d",i);
    string_table[i] = buf;
    symbol_table[i] = stx::string_symbol(buf);
  }
}

template<class Container, class T>
long trial(Container& container, const T* table, int repeat) {
  // ...省略...
}

int main() {

  map<std::string,int>        strmap;
  map<stx::string_symbol,int> symmap;

  init();

  cout << "strmap " << trial(strmap, string_table, 100) << " [ms]\n";
  cout << "symmap " << trial(symmap, symbol_table, 100) << " [ms]\n";

  return 0;
}
実行結果
strmap 1162 [ms]
symmap 70 [ms]

…効果は歴然ですね。

<stx/string>

Thursday, March 30th, 2006

DDJ-J 1998年5月号で紹介したSTL拡張コンポーネント”STX”の改版です。C Magazine Jan 2000で公開したtokenizerをreviseしました。

tokenizer<class String>

標準C++ライブラリが提供する文字列クラス basic_string<charT> をトークン分割するクラスです。テンプレート引数にはstd::string もしくは std::wstring を与えることができます。

※ マルチバイト文字(Shift-JIS, EUCなど)は正しく分割できません。一旦DBCS(UNICODE)に変換しstd::wstringで扱うか、もしくは stx::c_tokenizer<unsigned char> をお使いください。

template<class String>
class tokenizer {
private:
  String::size_type cur_;
  String str_;
  String del_;
  bool   ret_;
  void skip();
public:
  typedef std::pair<String::size_type,String::size_type> range_type;
  tokenizer(const String& str, const String& del, bool ret=false);
  bool empty();
  String next();
  range_type next_range();
  void set_str(const String& str);
  void set_delimiter(const String& del, bool ret=false);
  size_t count() const;
};
// sample
// "apple,banana,cherry" を "," で区切る
stx::tokenizer<string> tok("apple,banana,cherry", ",");
while ( !tok.empty() ) {
  cout << tok.next() << " ";
}
cout << endl;

tokenizer (コンストラクタ)

template<class String>
tokenizer<String>::tokenizer(const String& str, const String& del, bool ret)
  : cur_(0), str_(str), del_(del), ret_(ret) {}
str
:トークン分割対象となる文字列
del
:トークンを区切る文字の集合
ret
:区切り文字をトークンとして扱うならtrue

ret == true のとき、区切り文字をトークンとして扱います。たとえば、

// sample
// "apple_banana__cherry_" を "_" で区切る
bool ret = false;
stx::tokenizer<string> tok("apple_banana__cherry_", "_", ret);
while ( !tok.empty() ) {
  cout << tok.next() << " ";
}
cout << endl;

では “apple”, “banana”, “cherry” が切り出されますが、ret = true とすると、”apple”, “_”, “banana”, “_”, “_”, “cherry”, “_” が切り出されます。

skip (private)

template<class String>
void tokenizer<String>::skip() {
  if ( cur_ == str_.length() )
    cur_ = String::npos;
  if ( !ret_ && cur_ != String::npos ) {
    String::size_type tmp = str_.find_first_not_of(del_, cur_);
    if ( tmp != String::npos )
      cur_ = tmp;
  }
}

empty

template<class String>
bool tokenizer<String>::empty() {
  skip();
  return cur_ == String::npos;
}

切り出すトークンが見つからないとき true を返します。

next

template<class String>
String tokenizer<String>::next() {
  range_type range = next_range();
  return str_.substr(range.first, range.second);
}

切り出されたトークンを返します。

next_range

template<class String>
tokenizer<String>::range_type tokenizer<String>::next_range() {
  skip();
  String::size_type start = cur_;
  String::size_type tmp = cur_;
  if ( cur_ != String::npos ) cur_ = str_.find_first_of(del_,cur_);
  if ( cur_ == String::npos ) return range_type(start,str_.length()-start);
  if ( ret_ && start == cur_ && del_.find(str_[cur_]) != String::npos ) ++cur_;
  return range_type(start,cur_-start);
}

トークンを切り出し、その位置と長さを表す std::pair<String::size_type,String::size_type> range を返します。range.first はトークンの開始位置、range.second はトークンの長さです。

set_str

template<class String>
void tokenizer<String>::set_str(const String& str) {
  str_ = str;
  cur_ = 0;
}

トークン分割対象となる文字列を設定します。トークン分割の開始位置を0に設定します。

set_delimiter

template<class String>
void tokenizer<String>::set_delimiter(const String& delim, bool ret) {
  del_ = delim;
  ret_ = ret;
}

区切り文字の集合を設定します。区切り文字をトークンとして扱うときは第2引数を true としてください。

count

template<class String>
size_t tokenizer<String>::count() const {
  size_t count = 0;
  String::size_type currpos = cur_;
  while ( currpos != String::npos ) {
    if ( !ret_ ) {
      currpos = str_.find_first_not_of(del_,currpos);
      if ( currpos == String::npos ) { ++count; break; }
    } else if ( currpos == str_.length() ) {
      break;
    }
    String::size_type start = currpos;
    if ( currpos != String::npos ) currpos = str_.find_first_of(del_,currpos);
    if ( currpos == String::npos ) { ++count; break; }
    if ( ret_ && start == currpos && del_.find(str_[currpos]) != String::npos ) ++currpos;
    ++count;
  }
  return count;
}

現在の位置から切り出されるトークンの数を返します。

string_tokenizer / wstring_tokenizer

typedef tokenizer<std::string>     string_tokenizer;
typedef tokenizer<std::wstring>    wstring_tokenizer;

使用頻度の多いtokenizer<std::string> および tokenizer<std::wstring>をそれぞれ string_tokenizer,wstring_tokenizerとtypedefしました。

stx::builtin_array<T>

Thursday, March 30th, 2006

‘builtin_array’について

STLが提供するアルゴリズムや関数オブジェクトはvectorやlistに対してだけでなく、char[]やint[]などのビルトイン配列に対しても適用できるように作られています。

しかしながらビルトイン配列をSTLに適用させるときは、たとえば、

// int data[N]の中から1を探す
int data[N];
int* iter = std::find(data, data+N, 1);
if ( iter != data+N ) {
  // found!!
}

のように記述することになります。これをSTLコンテナと同様、

// vector<int> dataの中から1を探す
vector<int> data;
vector<int>::iterator iter = std::find(data.begin(), data,end(), 1);
if ( iter != data.end() ) {
  // found!!
}

のように記述できるよう、ビルトイン配列をwrapする’builtin_array’を作りました。要素のアクセスに関する基本操作だけでなく、要素の反転アクセスのためのreverse_iterator/const_reverse_iteratorをサポートしています。

また、builtin_arrayの比較演算(==, !=, < <=, >, >=)を用意しました。したがってbuiltin_arrayはinsertやeraseなどの要素の挿入/削除に関する操作を持たないことを除いて、vectorと同じ操作ができます。

‘builtin_array’を使えば、前述のコードは以下のように記述できます。

// int data[N]の中から1を探す
int data[N];
stx::builtin_array<int> ba(data,N);
stx::builtin_array<int>::iterator iter = std::find(ba.begin(), ba.end(), 1);
if ( iter != ba.end() ) {
  // found!!
}

ソースコード

/*
 * builtin_array for VC++6
 */

#include <iterator>
#include <algorithm>
#include <stdexcept>

namespace stx {

  template<class T>
  class builtin_array {

    T*     first_;
    T*     last_;
    size_t size_;

    // no-definition to prevent copy
    builtin_array();
    builtin_array(const builtin_array&);
    builtin_array& operator=(const builtin_array&);

  public:
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T*        iterator;
    typedef const T*  const_iterator;
    typedef std::reverse_iterator<iterator,T>       reverse_iterator;
    typedef std::reverse_iterator<const_iterator,T> const_reverse_iterator;

    builtin_array(T* first, T* last)
      : first_(first), last_(last), size_(last-first) {}
    builtin_array(T* first, size_t size)
      : first_(first), last_(first+size), size_(size) {}

    iterator begin()
      { return first_; }
    const_iterator begin() const
      { return first_; }

    iterator end()
      { return last_; }
    const_iterator end() const
      { return last_; }

    reverse_iterator rbegin()
      { return reverse_iterator(last_); }
    const_reverse_iterator rbegin() const
      { return const_reverse_iterator(last_); }

    reverse_iterator rend()
      { return reverse_iterator(first_); }
    const_reverse_iterator rend() const
      { return const_reverse_iterator(first_); }

    size_type size() const
      { return size_; }
    size_type max_size() const
      { return size_; }
    size_type capacity() const
      { return size_; }
    bool empty() const
      { return size_ == 0; }

    reference operator[](size_type n)
      { return *(first_+n); }
    const_reference operator[](size_type n) const
      { return *(first_+n); }

    reference at(size_type n)
      { if ( size_ <= n ) _Xran(); return *(first_+n); }
    const_reference at(size_type n) const
      { if ( size_ <= n ) _Xran(); return *(first_+n); }

    reference front()
      { return *first_; }
    const_reference front() const
      { return *first_; }

    reference back()
      { return *(last_-1); }
    const_reference back() const
      { return *(last_-1); }

    pointer body()
      { return first_; }
    const_pointer body() const
      { return first_; }

    std::pair<pointer,size_type> data()
      { return std::pair<pointer,size_type>(first_,size_); }

    std::pair<const_pointer,size_type> data() const
      { return std::pair<const_pointer,size_type>(first_,size_); }

    void swap(builtin_array& x) {
      std::swap(first_, x.first_);
      std::swap(last_,  x.last_);
      std::swap(size_,  x.size_);
    }

    friend void swap(builtin_array& x, builtin_array& y)
      { x.swap(y); }

    // internal use internary
    bool _Eq(const builtin_array& x) const
      { return size() == x.size() && std::equal(begin(), end(), x.begin()); }

    bool _Lt(const builtin_array& _X) const
      { return std::lexicographical_compare(begin(), end(), x.begin(), x.end()); }

  private:

    void _Xran () const
      { throw std::out_of_range("invalid builtin_array<T> subscript"); }

  };

  template<class T> inline
  bool operator==(const builtin_array<T>& x, const builtin_array<T>& y)
    { return x._Eq(y); }

  template<class T> inline
  bool operator!=(const builtin_array<T>& x, const builtin_array<T>& y)
    { return !(_X == _Y); }

  template<class T> inline
  bool operator<(const builtin_array<T>& x,  const builtin_array<T>& y)
    { return x._Lt(y); }

  template<class T> inline
  bool operator>(const builtin_array<T>& x,  const builtin_array<T>& y)
    { return y < x; }

  template<class T> inline
  bool operator<=(const builtin_array<T>& x, const builtin_array<T>& y)
    { return !(y < x); }

  template<class T> inline
  bool operator>=(const builtin_array<T>& x, const builtin_array<T>& y)
    { return !(x < y); }

}

コンストラクタ

builtin_array(T* first, T* last)
builtin_array(T* first, size_t size)

ビルトイン配列 first からbuiltin_arrayを構築します。

// sample
int data[N];
builtin_array<int> ba(data,data+N); // または ba(data,N)
int n = ba.size(); // n = N

※安全を期するため、デフォルトコンストラクタ/コピーコンストラクタ/コピー演算子はprivate部に宣言することで使えなくしてあります。

イテレータ

iterator begin();
const_iterator begin() const;

最初の要素を指すイテレータを返します。

iterator end();
const_iterator end() const;

最後の要素の直後を指すイテレータを返します。

// sample : 全要素をプリント
int data[N];
stx::builtin_array<int> ba(data,N);
stx::builtin_array<int>::iterator iter = ba.begin();
while ( iter != ba.end() ) {
  cout << *iter << endl;
  ++iter;
}
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

最後の要素の直後を指す反転イテレータを返します。

reverse_iterator rend();
const_reverse_iterator rend() const;

最初の要素を指す反転イテレータを返します。

// sample : 全要素を逆順にプリント
int data[N];
stx::builtin_array<int> ba(data,N);
stx::builtin_array<int>::reverse_iterator iter = ba.rbegin();
while ( iter != ba.rend() ) {
  cout << *iter << endl;
  ++iter;
}

empty / size / max_size

bool empty() const;

要素数が 0 なら true を返します。

size_type size() const;
size_type max_size() const;
size_type capacity() const;

いずれもコンストラクト時に設定した要素数を返します。

front / back / operator[] / at / data / swap

reference front()
const_reference front() const

最初の要素を返します。

reference back()
const_reference back() const

最後の要素を返します。

reference operator[](size_type n)
const_reference operator[](size_type n) const

n番目の要素を返します。

reference at(size_type n)
const_reference at(size_type n) const

n番目の要素を返します。n >= size() であるとき、out_of_range例外をthrowします。

std::pair<pointer,size_type> data()
std::pair<const_pointer,size_type> data() const

配列のポインタと要素数(first()とsize())のpairを返します。

void swap(builtin_array& x)

*this と x の全要素を交換します。

[Java入門]3.5 判断と繰り返し

Thursday, March 30th, 2006

3.5.1 if と while

無事に実行できたのはいいのですが、お客が一組しか来ないのでは寂し過ぎますね。 またスタンドのタンクの用量が決まっているのにそれより多くのガソリンがあることにできてしまったり、タンクにあるガソリンの残量よりたくさん給油できてしまたりするのも困りものです。 ちゃんとタンクの容量と残量を考慮するようにプログラムを改良しましょう。 そしてお客さんが何台も来るようにします。 まず、コンストラクタも含めてそのメソッドでどんなことを考慮しなくてはいけないかを整理してみます。

メソッド どんなことを考慮するか
コンストラクタ タンク容量より多くのガソリンを入れておくことはできない
refuel ガソリンの残量より多く給油することはできない

また、タンクが空になるまで給油を繰り返すことにしましょう。 タンクが空になったかどうかはガソリンスタンド自身に確認させます。 つまり、タンクが空かどうかを調べるisNotEmptyメソッドをGasStationクラスに追加することにします。

さて、問題は給油の繰り返しですが、これは GasStationSimulationクラスのdealCustomerを書き換えればよさそうです。 なにはともあれ、これらを考慮に入れるとそれぞれのメソッドがどうなるか見てみましょう。

Java: GasStation.java の変更箇所
  public GasStation(int initial) {
    if ( initial > tankCapacity_ ) {
      remain_ = tankCapacity_;
    } else {
      remain_ = initial;
    }
  }

  public int refuel(int amount) {
    int charged = amount;
    if ( remain_ < amount ) {
      charged = remain_;
    }
    remain_ = remain_ - charged;
    return charged;
  }

  // isNotEmpty メソッドを追加
  public boolean isNotEmpty() {
    return remain_ > 0;
  }
Java: GasStationSimulation.java の変更箇所
public void dealCustomer() {
    while ( aGS_.isNotEmpty() ) {
      System.out.println("\nFor the cusomer:");
      int amount = askInt("  refuel amount? ");
      int chargedAmount = aGS_.refuel(amount);
      int price = aGS_.price(chargedAmount);
      aGS_.addSales(price);
      System.out.println("  amount: " + chargedAmount + " [liter]");
      System.out.println("  price: " + price + " [yen]");
    }
    System.out.println("\nTotal sales: " + aGS_.totalSales() + " [yen]");
}

C++: GasStation.h の変更箇所
class GasStation {

private:
  static const int tankCapacity_;
  static int unitPrice_;
  int remain_;
  int totalSales_;

public:
  explicit GasStation(int initial);
  int refuel(int amount);
  int price(int amount) const;
  void addSales(int price);
  bool isNotEmpty() const; // isNotEmpty メソッドを追加
  static int tankCapacity();
  int totalSales() const;
  static void setUnitPrice(int price);
  static int unitPrice();

};
C++: GasStation.cpp の変更箇所
GasStation::GasStation(int initial) {
  if ( initial < tankCapacity_ ) {
    remain_ = tankCapacity_;
  } else {
    remain_ = initial;
  }
}

int GasStation::refuel(int amount) {
  int charged = amount;
  if ( remain_ < amount ) {
    charged = remain_;
  }
  remain_ = remain_ - charged;
  return charged;
}

bool GasStation::isNotEmpty() const {
  return remain_ > 0;
}
C++: GasStationSimulation.cpp の変更箇所
void GasStationSimulation::dealCustomer() {
  while ( aGS_->isNotEmpty() ) {
    std::cout << "\nFor the customer:" << std::endl;
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_->refuel(amount);
    int price = aGS_->price(chargedAmount);
    aGS_->addSales(price);
    std::cout << "  amount: " << chargedAmount << " [liter]" << std::endl;
    std::cout << "  price:  " << price << " [yen]" << std::endl;
  }
  std::cout << "\nTotal sales: " << aGS_->totalSales() << " [yen]" << std::endl;
}
C#: GasStation.cs の変更箇所
  public GasStation(int initial) {
    if ( initial > tankCapacity_ ) {
      remain_ = tankCapacity_;
    } else {
      remain_ = initial;
    }
  }

  public int refuel(int amount) {
    int charged = amount;
    if ( remain_ < amount ) {
      charged = remain_;
    }
    remain_ = remain_ - charged;
    return charged;
  }

  // isNotEmpty メソッドを追加
  public bool isNotEmpty() {
    return remain_ > 0;
  }
C#: GasStationSimulation.cs の変更箇所
public void dealCustomer() {
  while ( aGS_.isNotEmpty() ) {
    System.Console.WriteLine("\nFor the customer:");
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_.refuel(amount);
    int price = aGS_.price(chargedAmount);
    aGS_.addSales(price);
    System.Console.WriteLine("  amount: " + chargedAmount + " [liter]");
    System.Console.WriteLine("  price:  " + price + " [yen]");
  }
  System.Console.WriteLine("\nTotal sales: " + aGS_.totalSales + " [yen]");
}
VB: GasStation.vb の変更箇所
Public Sub New(ByVal initial As Integer)
  If initial > tankCapacity_ Then
    remain_ = tankCapacity_
  Else
      remain_ = initial
  End If
End Sub

Public Function refuel(ByVal amount As Integer) As Integer
  Dim charged As Integer = amount
  If remain_ < amount Then
    charged = remain_
  End If
  remain_ = remain_ - charged
  Return charged
End Function

' isNotEmpty メソッドの追加
Public Function isNotEmpty() As Boolean
  Return remain_ > 0
End Function
VB: GasStationSimulation.vb の変更箇所
  Public Sub dealCustomer()
    While aGS_.isNotEmpty()
      System.Console.WriteLine(vbCrLf + "For the customer:")
      Dim amount As Integer = askInt("  refuel amount? ")
      Dim chargedAmount As Integer = aGS_.refuel(amount)
      Dim price As Integer = aGS_.price(chargedAmount)
      aGS_.addSales(price)
      System.Console.WriteLine("  amount: " + CStr(chargedAmount) + " [liter]")
      System.Console.WriteLine("  price: " + CStr(price) + " [yen]")
    End While
    System.Console.WriteLine(vbCrLf + "Total sales: " + CStr(aGS_.totalSales) + " [yen]")
  End Sub

if, else, while の3つの単語がキーワードです。 if は条件を判断し、その結果に応じて何をするか換えたいときに使います。refuelメソッドの中では、給油しようとした量がガソリンの残量を超えているときには、タンクが空になるまで、すなわち残量全部しか給油できないようにしています。 これに対してコンストラクタの方では ifelse を組み合わせて使い、もし(if)最初に指定された量がタンク容量を越えていたらタンク容量ぎりぎりまで、そうでなければ(else)指定された量だけタンクに入れるということをしています。 最後に、dealCustomerメソッドのwhileですが、これはまず始めにガソリンスタンドのタンク残量があるかどうかを調べ、タンクが空でない間(while)次のお客さんを受け付けるという風になっています。 そしてタンクが空になったら繰り返しを終了して、売上の合計を表示します。 実際に変更してどのように動作するか試してみてください。

また、何度も繰り返すと画面が見にくくなってしまうので、繰り返すごとに一行あけるようにしています。 売上の合計を表示するときにも一行あけています。 それぞれ、ソース中のどこがそれにあたるかを調べてみてください。 このソースは$chapter3/ex3にあります。

これを反映したプログラムを実行した結果は以下のようになります。

  For the gas station:
    initial amount? 250
    unit price? 108

  For the customer:
    refuel amount? 35
    amount: 35 [liter]
    price:  3780 [yen]

  For the customer:
    refuel amount? 40
    amount: 40 [liter]
    price:  4320 [yen]

  For the customer:
    refuel amount? 38
    amount: 38 [liter]
    price:  4104 [yen]

  For the customer:
    refuel amount? 42

    amount: 42 [liter]
    price:  4536 [yen]

  For the customer:
    refuel amount? 36
    amount: 36 [liter]
    price:  3888 [yen]

  For the customer:
    refuel amount? 45
    amount: 45 [liter]
    price:  4860 [yen]

  For the customer:
    refuel amount? 52
    amount: 14 [liter]
    price:  1512 [yen]

  Total sales: 27000 [yen]

3.5.2 回数を決めて繰り返す 1: while

さて、この例では最初にタンクに用意した量を全部売り尽くすまで給油を繰り返すので、結局売上の合計はタンクに最初に用意した量とガソリンの単価だけで決まってしまいます。 これではシミュレーションらしくないので、給油する代数を決めて、それ以上は給油しないことにしましょう。 このための代表的な方法は、次の2つがあります。

  1. whileを使って、変数を1ずつ増やし、一定数に達したら繰り返しをやめる。
  2. forを使い、1と同様にする。

それぞれの方法でやるとどうなるか、ソースを比べてみましょう。
とりあえず、5台だけ給油することにします。 もちろん、askIntメソッドを使えば、何台給油するかを最初に入力するようにもできます。 是非試してみてください。

まず、whileを使った場合のソースを次に示します。

このソースは$chapter3/ex4/GasStationSimulation-while.javaに格納されています。

Java: GasStationSimulation-while.java
import java.io.BufferedReader;
import java.io.InputStreamReader;

class GasStationSimulation {

  private GasStation aGS_;

  private int askInt(String message) {
    try {
      System.out.print(message);
      System.out.flush();
      String line = new BufferedReader(new InputStreamReader(System.in)).readLine();
      return Integer.parseInt(line);
    } catch ( java.io.IOException e ) {
      System.out.println("could not get input. Sorry.");
      return 0;
    }
  }

  public void init() {
    System.out.println("For the gas station:");
    int initial = askInt("  initial amount? ");
    int price = askInt("  unit price? ");
    GasStation.setUnitPrice (price);
    aGS_ = new GasStation(initial);
  }

  public void dealCustomer() {
    int i = 0;
    while ( aGS_.isNotEmpty() && i < 5 ) {
      System.out.println("\nFor the cusomer:");
      int amount = askInt("  refuel amount? ");
      int chargedAmount = aGS_.refuel(amount);
      int price = aGS_.price(chargedAmount);
      aGS_.addSales(price);
      System.out.println("  amount: " + chargedAmount + " [liter]");
      System.out.println("  price: " + price + " [yen]");
      i = i + 1;
    }
    System.out.println("\nTotal sales: " + aGS_.totalSales() + " [yen]");
  }

  public static void main(String[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
  }

}

C++: GasStationSimulation-while.cpp
#include <iostream>

#include "GasStationSimulation.h"

#include "GasStation.h"

GasStationSimulation::~GasStationSimulation() {
  delete aGS_;
}

int GasStationSimulation::askInt(const std::string& message) {
  std::cout << message << std::flush;
  int result;
  if ( std::cin >> result ) {
    return result;
  }
  std::cout << "could not get input.  sorry." << std::endl;
  return 0;
}

void GasStationSimulation::init() {
  std::cout << "For the gas station:" << std::endl;
  int initial = askInt("  initial amount? ");
  aGS_ = new GasStation(initial);
  int unitPrice = askInt("  unit price? ");
  GasStation::setUnitPrice(unitPrice);
}

void GasStationSimulation::dealCustomer() {
  int i = 0;
  while ( aGS_->isNotEmpty() && i < 5 ) {
    std::cout << "\nFor the customer:" << std::endl;
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_->refuel(amount);
    int price = aGS_->price(chargedAmount);
    aGS_->addSales(price);
    std::cout << "  amount: " << chargedAmount << " [liter]" << std::endl;
    std::cout << "  price:  " << price << " [yen]" << std::endl;
    i = i + 1;
  }
  std::cout << "\nTotal sales: " << aGS_->totalSales() << " [yen]" << std::endl;
}
C#: GasStationSimulation-while.cs
class GasStationSimulation {

  private GasStation aGS_;

  private int askInt(string message) {
    System.Console.Write(message);
    System.Console.Out.Flush();
    try {
      return System.Int32.Parse(System.Console.ReadLine());
    } catch ( System.Exception ) {
      System.Console.WriteLine("could not get input.  sorry.");
      return 0;
    }
  }

  public void init() {
    System.Console.WriteLine("For the gas stations:");
    int initial = askInt("  initial amount? ");
    aGS_ = new GasStation(initial);
    GasStation.unitPrice = askInt("  unit price? ");
  }

  public void dealCustomer() {
    int i = 0;
    while ( aGS_.isNotEmpty() && i < 5 ) {
      System.Console.WriteLine("\nFor the customer:");
      int amount = askInt("  refuel amount? ");
      int chargedAmount = aGS_.refuel(amount);
      int price = aGS_.price(chargedAmount);
      aGS_.addSales(price);
      System.Console.WriteLine("  amount: " + chargedAmount + " [liter]");
      System.Console.WriteLine("  price:  " + price + " [yen]");
      i = i + 1;
    }
    System.Console.WriteLine("\nTotal sales: " + aGS_.totalSales + " [yen]");
  }

  [System.STAThread]
  static void Main(string[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
  }

 }
VB: GasStationSimulation-while.vb
Class GasStationSimulation

  Private aGS_ As GasStation

  Private Function askInt(ByVal message As String)
    System.Console.Write(message)
    System.Console.Out.Flush()
    Try
      Return System.Int32.Parse(System.Console.ReadLine())
    Catch e As System.Exception
      System.Console.WriteLine("could not get input.  Sorry.")
      Return 0
    End Try
  End Function

  Public Sub init()
    System.Console.WriteLine("For the customer:")
    Dim initial As Integer = askInt("  initial amount? ")
    GasStation.unitPrice = askInt("  unit price? ")
    aGS_ = New GasStation(initial)
  End Sub

  Public Sub dealCustomer()
    Dim i As Integer = 0
    While aGS_.isNotEmpty() And i < 5
      System.Console.WriteLine(vbCrLf + "For the customer:")
      Dim amount As Integer = askInt("  refuel amount? ")
      Dim chargedAmount As Integer = aGS_.refuel(amount)
      Dim price As Integer = aGS_.price(chargedAmount)
      aGS_.addSales(price)
      System.Console.WriteLine("  amount: " + CStr(chargedAmount) + " [liter]")
      System.Console.WriteLine("  price: " + CStr(price) + " [yen]")
      i = i + 1
    End While
    System.Console.WriteLine(vbCrLf + "Total sales: " + CStr(aGS_.totalSales) + " [yen]")
  End Sub

  Public Shared Sub Main()
    Dim simulation As GasStationSimulation = New GasStationSimulation()
    simulation.init()
    simulation.dealCustomer()
  End Sub

End Class

このdealCustomerメソッドでは、ローカル変数 i を用意してその値を1ずつ増やしています。 そして、whileの条件に i < 5 を追加して、5台に給油したかどうかを調べています。
&& という演算子は、2つ以上の条件を並べて書き、両方が成立しているときだけ成立する、いわゆる論理演算子です。

このプログラムを実行すると以下のように出力されます。

  For the gas station:
    initial amount? 250
    unit price? 108

  For the customer:
    refuel amount? 42

    amount: 42 [liter]
    price:  4536 [yen]

  For the customer:
    refuel amount? 38
    amount: 38 [liter]
    price:  4104 [yen]

  For the customer:
    refuel amount? 45
    amount: 45 [liter]
    price:  4860 [yen]

  For the customer:
    refuel amount? 41
    amount: 41 [liter]
    price:  4428 [yen]

  For the customer:
    refuel amount? 36
    amount: 36 [liter]
    price:  3888 [yen]

  Total sales: 21816 [yen]

こんどはタンクが空にならなくても5回で実行を終了していることがわかります。

3.5.3 回数を決めて繰り返す 2: for

次に、for を使ったもうひとつのやり方を見てみましょう。
次のリストを参照してください。 ソースは$chapter3/ex4/GasStationSimulation-for.javaに格納されています。

Java: GasStationSimulation-for.java
import java.io.BufferedReader;
import java.io.InputStreamReader;

class GasStationSimulation {

  private GasStation aGS_;

  private int askInt(String message) {
    try {
      System.out.print(message);
      System.out.flush();
      String line = new BufferedReader(new InputStreamReader(System.in)).readLine();
      return Integer.parseInt(line);
    } catch ( java.io.IOException e ) {
      System.out.println("could not get input. Sorry.");
      return 0;
    }
  }

  private void init() {
    System.out.println("For the gas station:");
    int initial = askInt("  initial amount? ");
    int price = askInt("  unit price? ");
    GasStation.setUnitPrice (price);
    aGS_ = new GasStation(initial);
  }

  private void dealCustomer() {
    for ( int i = 0; aGS_.isNotEmpty() && i < 5; i = i + 1 ) {
      System.out.println("\nFor the cusomer:");
      int amount = askInt("  refuel amount? ");
      int chargedAmount = aGS_.refuel(amount);
      int price = aGS_.price(chargedAmount);
      aGS_.addSales(price);
      System.out.println("  amount: " + chargedAmount + " [liter]");
      System.out.println("  price: " + price + " [yen]");
    }
    System.out.println("\nTotal sales: " + aGS_.totalSales() + " [yen]");
  }

  public static void main(String[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
  }

}

C++: GasStationSimulation-for.cpp
#include <iostream>

#include "GasStationSimulation.h"

#include "GasStation.h"

GasStationSimulation::~GasStationSimulation() {
  delete aGS_;
}

int GasStationSimulation::askInt(const std::string& message) {
  std::cout << message << std::flush;
  int result;
  if ( std::cin >> result ) {
    return result;
  }
  std::cout << "could not get input.  sorry." << std::endl;
  return 0;
}

void GasStationSimulation::init() {
  std::cout << "For the gas station:" << std::endl;
  int initial = askInt("  initial amount? ");
  aGS_ = new GasStation(initial);
  int unitPrice = askInt("  unit price? ");
  GasStation::setUnitPrice(unitPrice);
}

void GasStationSimulation::dealCustomer() {
  for ( int i = 0; aGS_->isNotEmpty() && i < 5; i = i + 1 ) {
    std::cout << "\nFor the customer:" << std::endl;
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_->refuel(amount);
    int price = aGS_->price(chargedAmount);
    aGS_->addSales(price);
    std::cout << "  amount: " << chargedAmount << " [liter]" << std::endl;
    std::cout << "  price:  " << price << " [yen]" << std::endl;
  }
  std::cout << "\nTotal sales: " << aGS_->totalSales() << " [yen]" << std::endl;
}
C#: GasStationSimulation-for.cs>
class GasStationSimulation {

  private GasStation aGS_;

  private int askInt(string message) {
    System.Console.Write(message);
    System.Console.Out.Flush();
    try {
      return System.Int32.Parse(System.Console.ReadLine());
    } catch ( System.Exception ) {
      System.Console.WriteLine("could not get input.  sorry.");
      return 0;
    }
  }

  public void init() {
    System.Console.WriteLine("For the gas stations:");
    int initial = askInt("  initial amount? ");
    aGS_ = new GasStation(initial);
    GasStation.unitPrice = askInt("  unit price? ");
  }

  public void dealCustomer() {
    for ( int i = 0; aGS_.isNotEmpty() && i < 5; i = i + 1  ) {
      System.Console.WriteLine("\nFor the customer:");
      int amount = askInt("  refuel amount? ");
      int chargedAmount = aGS_.refuel(amount);
      int price = aGS_.price(chargedAmount);
      aGS_.addSales(price);
      System.Console.WriteLine("  amount: " + chargedAmount + " [liter]");
      System.Console.WriteLine("  price:  " + price + " [yen]");
    }
    System.Console.WriteLine("\nTotal sales: " + aGS_.totalSales + " [yen]");
  }

  [System.STAThread]
  static void Main(string[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
  }

 }
VB: GasStationSimulation-for.vb
Class GasStationSimulation

  Private aGS_ As GasStation

  Private Function askInt(ByVal message As String)
    System.Console.Write(message)
    System.Console.Out.Flush()
    Try
      Return System.Int32.Parse(System.Console.ReadLine())
    Catch e As System.Exception
      System.Console.WriteLine("could not get input.  Sorry.")
      Return 0
    End Try
  End Function

  Public Sub init()
    System.Console.WriteLine("For the customer:")
    Dim initial As Integer = askInt("  initial amount? ")
    GasStation.unitPrice = askInt("  unit price? ")
    aGS_ = New GasStation(initial)
  End Sub

  Public Sub dealCustomer()
    Dim i As Integer
    For i = 1 To 5
      If Not aGS_.isNotEmpty() Then
        Exit For
      End If
      System.Console.WriteLine(vbCrLf + "For the customer:")
      Dim amount As Integer = askInt("  refuel amount? ")
      Dim chargedAmount As Integer = aGS_.refuel(amount)
      Dim price As Integer = aGS_.price(chargedAmount)
      aGS_.addSales(price)
      System.Console.WriteLine("  amount: " + CStr(chargedAmount) + " [liter]")
      System.Console.WriteLine("  price: " + CStr(price) + " [yen]")
    Next
    System.Console.WriteLine(vbCrLf + "Total sales: " + CStr(aGS_.totalSales) + " [yen]")
  End Sub

  Public Shared Sub Main()
    Dim simulation As GasStationSimulation = New GasStationSimulation()
    simulation.init()
    simulation.dealCustomer()
  End Sub

End Class

今度はfor の後の括弧の中に、変数iの定義や、タンクが空かどうか、5台済んだかどうか、iに1を足すといったことがまとめて書かれています。 この例からわかるように、forを使って書けることはwhileを使っても書くことができます。 どちらも非常によく使われるので覚えておきましょう。

whileを用いた場合と同じ出力が得られるか、実際に試して比べてみてください。

prev next

slist<T, Alloc> リファレンス

Thursday, March 30th, 2006

※ slist<T,Alloc>はSGI版STLが提供するコンテナクラスであり、標準C++ライブラリには含まれていません。
しかしながらこのSGI版STLは大元のHPのリファレンス実装の直系で、STLportなど様々なSTL実装のベースとなっていることから、事実上の標準といっても差支えないものです。gccもこのSGI版STL(もしくはSTLport)を採用しています。

slistは単方向リンクリストです。slist内の各要素は次の要素へのリンクを持っていますが、双方向リンクリスト:std::listのように前の要素へのリンクを持ってはいません。

したがって、(std::listのイテレータが双方向イテレータであるのに対し)slistのイテレータは ‘前方向イテレータ’ であり、イテレータの指す位置を遡ることはできません(operator–()を持っていません)。

また、前の要素へのリンクを持たないことから、std::listにある末尾に対する参照/追加/削除メソッド back / push_back / pop_back をサポートしていません。

sliststd::listに比べて機能的には劣ります。が、双方向イテレータを必要としない用途であれば、管理しなければならないリンクが単方向で済むため、std::listより若干ながらコンパクトかつ高速です。

標準C++が定めるコンテナの要求を満たすため、slistにはstd::listと同等のメソッドが実装されています。
しかしながら前の要素へのリンクを持たないため、insserterasestd::listよりかなり低速(要素数に比例)であることに留意してください。代替メソッドとして、定数時間で処理を行なうinsert_after, erase_afterが用意されています。 できる限りこれらをお使いください。

サンプル

#include <slist>
#include <iostream>
#include <algorithm>

using namespace std;
int main() {
  slist<int> L;
  L.push_front(0);
  L.push_front(1);
  L.insert_after(L.begin(), 2);
  copy(L.begin(), L.end(),    // 出力: 1 2 0
       ostream_iterator<int>(cout, " "));
  cout << endl;

  slist<int>::iterator back = L.previous(L.end());
  back = L.insert_after(back, 3);
  back = L.insert_after(back, 4);
  back = L.insert_after(back, 5);
  copy(L.begin(), L.end(),    // 出力: 1 2 0 3 4 5
       ostream_iterator<int>(cout, " "));
  cout << endl;
  return 0;
}

<slist>

/*
 * このヘッダは STLport-4.5-01119 を基に作成されたものであり、
 * SGIオリジナル(後述のメソッド一覧)と完全には一致していません
 */

template <class _Tp, _STLP_DEFAULT_ALLOCATOR_SELECT(_Tp) >
class slist : protected _Slist_base<_Tp,_Alloc>
{
public:
  typedef _Tp                  value_type;
  typedef value_type*          pointer;
  typedef const value_type*    const_pointer;
  typedef value_type&          reference;
  typedef const value_type&    const_reference;
  typedef size_t               size_type;
  typedef ptrdiff_t            difference_type;
  typedef forward_iterator_tag _Iterator_category;

  typedef _Slist_iterator<_Tp, _Nonconst_traits<_Tp> >  iterator;
  typedef _Slist_iterator<_Tp, _Const_traits<_Tp> >     const_iterator;

  _STLP_FORCE_ALLOCATORS(_Tp, _Alloc)
  typedef typename _Base::allocator_type allocator_type;

public:

  allocator_type get_allocator() const;

  explicit slist(const allocator_type& __a = allocator_type());
  slist(size_type __n, const value_type& __x,
        const allocator_type& __a =  allocator_type());
  explicit slist(size_type __n);
  template <class _InputIterator>
  slist(_InputIterator __first, _InputIterator __last,
        const allocator_type& __a _STLP_ALLOCATOR_TYPE_DFL);
  slist(const _Self& __x);

  _Self& operator= (const _Self& __x);

  ~slist() {}

  void assign(size_type __n, const _Tp& __val);

  template <class _InputIterator>
    void assign(_InputIterator __first, _InputIterator __last);

  iterator before_begin();
  const_iterator before_begin() const;

  iterator begin();
  const_iterator begin() const;

  iterator end();
  const_iterator end() const;

  size_type size() const;
  size_type max_size() const;
  bool empty() const;

  void swap(_Self& __x);

  reference front();
  const_reference front() const;

  void push_front();
  void pop_front();

  iterator previous(const_iterator __pos);
  const_iterator previous(const_iterator __pos) const;

  iterator insert_after(iterator __pos, const value_type& __x);
  iterator insert_after(iterator __pos);
  void insert_after(iterator __pos, size_type __n, const value_type& __x);
  template <class _InIter>
    void insert_after(iterator __pos, _InIter __first, _InIter __last);

  iterator insert(iterator __pos, const value_type& __x);
  iterator insert(iterator __pos);
  void insert(iterator __pos, size_type __n, const value_type& __x);
  template <class _InIter>
    void insert(iterator __pos, _InIter __first, _InIter __last);

  iterator erase_after(iterator __pos);
  iterator erase(iterator __pos);
  iterator erase(iterator __first, iterator __last);

  void resize(size_type new_size, const _Tp& __x);
  void resize(size_type new_size);
  void clear();
  void splice_after(iterator __pos,
                    iterator __before_first, iterator __before_last);
  void splice_after(iterator __pos, iterator __prev);
  void splice_after(iterator __pos, _Self& __x);
  void splice(iterator __pos, _Self& __x);
  void splice(iterator __pos, _Self& __x, iterator __i);
  void splice(iterator __pos, _Self& __x, iterator __first, iterator __last);

  void reverse();

  void remove(const _Tp& __val);
  void unique();
  void merge(_Self& __x);
  void sort();     

  template <class _Predicate>
    void remove_if(_Predicate __pred);
  template <class _BinaryPredicate>
    void unique(_BinaryPredicate __pred);
  template <class _StrictWeakOrdering>
    void merge(slist<_Tp,_Alloc>& __x, _StrictWeakOrdering __comp);

  template <class _StrictWeakOrdering>
    void sort(_StrictWeakOrdering __comp);
};

メソッド一覧

iterator begin() slistの先頭要素を指すiteratorを返します。
iterator end() slistの末尾要素(の次)を指すiteratorを返します。
const_iterator begin() const slistの先頭要素を指すconst_iteratorを返します。
const_iterator end() const slistの末尾要素(の次)を指すconst_iteratorを返します。
size_type size() const

slist内の要素数を返します。※このメソッドが定数時間で完了することを仮定してはいけません。

要素数が0であることを知るには L.size() == 0 ではなく L.empty() することをおすすめします。

size_type max_size() const slistに格納可能な(論理的な)最大要素数を返します。
bool empty() const slist内の要素数が 0 のとき trueを返します。
slist() 空のslistを構築します。
slist(size_type n) n 個の T()を要素とするslistを構築します。
slist(size_type n, const T&t) n 個の tを要素とするslistを構築します。
slist(const slist&) コピー・コンストラクタ

template <class InputIterator>

slist(InputIterator f, InputIterator l)
シーケンス [f, l) を要素とする slist を構築します。
~slist() デストラクタ
slist& operator=(const slist&) 代入演算子。
void swap(slist&) お互い全要素を交換します。
reference front() 先頭要素を返します。
const_reference front() const 先頭要素を返します。
void push_front(const T&) 要素を先頭に挿入します。
void pop_front() 先頭要素を削除します。
iterator previous(iterator pos) ++prev == pos なるprevを返します。
const_iterator previous(const_iterator pos) ++prev == pos なるprevを返します。
iterator insert(iterator pos, const T& x) posが指す位置の直前にxを挿入します。pos.

template<class InputIterator>

void insert(iterator pos, InputIterator f, InputIterator l)
posが指す位置の直前にシーケンス[first, last)を挿入します。
void insert(iterator pos,
size_type n, const value_type& x)
posが指す位置の直前にn個のxを挿入します。
iterator erase(iterator pos) posが指す位置にある要素を削除します。
iterator erase(iterator first, iterator last) 範囲[first, last)にある要素を削除します。
void clear() 全要素を削除します。
void resize(n, t = T()) 要素数がnとなるよう、要素の削除もしくはtの挿入を行ないます。
iterator insert_after(iterator pos) posが指す位置の直後にT()を挿入します。挿入された要素を指すiteartorを返します。
iterator insert_after(iterator pos, const value_type& x)

posが指す位置の直後に xを挿入します。

挿入された要素を指すiteartorを返します。


template<class InputIterator>

void insert_after(iterator pos, InputIterator f, InputIterator l)
posが指す位置の直後にシーケンス[f, l)を挿入します。
void insert_after(iterator pos, size_type n, const value_type& x) posが指す位置の直後に n個のxを挿入します。
iterator erase_after(iterator pos) posが指す位置の直後の要素を削除します。
iterator erase_after(iterator before_first, iterator last) 範囲[before_first+1, last)にある要素を削除します。
void splice(iterator position, slist& L) positionが指す位置の直前にLの全要素を移動します。
void splice(iterator position, slist& L, iterator i) positionが指す位置の直前に iが指すLの要素を移動します。
void splice(iterator position, slist& L, iterator f, iterator l) positionが指す位置の直前にL内のシーケンス[f, l)
void splice_after(iterator pos, iterator prev)

posが指す位置の直後に prevが指す位置の直後の要素を移動します。

prevは他のslistの要素を指すiteratorでも構いません。

void splice_after(iterator pos, iterator before_first, iterator before_last) posが指す位置の直後にシーケンス[before_forst+1, before_last+1)を移動します。
void remove(const T& value) valueと等しい全要素を削除します。

template<class Predicate>

void remove_if(Predicate p)
p(*i) が真となる全要素 *i を削除します。
void unique() 隣接する相等しい要素を削除します。
void merge(slist& L) ソートされた*thisLとを*thisにマージします。

template<class BinaryPredicate>

void merge(slist& L, BinaryPredicate comp)
compによる大小関係に基づいてソートされた*thisLとを*thisにマージします。
void reverse() 全要素の順序を反転します。
void sort() 全要素を昇順にソートします。

template<class BinaryPredicate>

void sort(BinaryPredicate comp)
compによる大小関係に基づいて全要素を昇順にソートします。
bool operator==(const slist&, const slist&) ふたつのslistが等値であればtrueを返します。
bool operator<(const slist&, const slist&) ふたつslistの各要素を辞書順で比較します。

 

STL samples

Thursday, March 30th, 2006

F:function S:struct C:class

<algorithm>


適用
Ffor_each
検索
Ffind Ffind_if Ffind_end
Ffind_first_of Fadjacent_find Fsearch
Fsearch_n Fmismatch Flower_bound
Fupper_bound Fequal_range Fbinary_search
係数
Fcount Fcount_if
比較
Fequal Flexicographical_compare
複写
Fcopy Fcopy_backward
交換
Fswap Fswap_ranges Fiter_swap
変換
Ftransform
置換
Freplace Freplace_if Freplace_copy Freplace_copy_if
充填
Ffill Ffill_n FgenerateFgenerate_n
削除
Fremove Fremove_if Fremove_copy
Fremove_copy_if Funique Funique_copy
反転
Freverse Freverse_copy
回転
Frotate Frotate_copy
攪拌
Frandom_shuffle
分類
Fpartition Fstable_partition Fnth_element
整列
Fsort Fstable_sort Fpartial_sort Fpartial_sort_copy
併合
Fmerge Finplace_mearge
集合
Fset_union Fset_intersection Fset_difference Fset_symmetric_difference
堆積
Fpush_heap Fpop_heap Fmake_heap
Fsort_heap
最大/最小
Fmin Fmax
Fmin_element Fmax_element
順列
Fnext_permutatin Fprev_permutation

<bitset>

固定長bit配列
Cbitset

<deque>

両頭キュー
Cdeque

<functional>

関数オブジェクト base
Sunary_function Sbinary_function
算術演算
Splus Sminus Smultiplies
Sdivides Smodulus Snegate
比較
Sequal_to Snot_equal_to Sgreater
Sless Sgreater_equal Sless_equal
論理演算
Slogical_and Slogical_or Slogical_not
論理反転
Sunare_negate Fnot1 Sbinary_negate
Fnot2
バインダ
Cbinder1st Fbind1st Cbinder2nd
Fbind2nd
関数ポインタ
Cpointer_to_unary_function
Cpointer_to_binary_function
Fptr_fun
メンバ関数ポインタ
Cmem_fun_t Cmem_fun1_t Cmem_fun_ref_t
Cmem_fun1_ref_t Cconst_mem_fun_t Cconst_mem_fun1_t
Cconst_mem_fun_ref_t Cconst_mem_fun1_ref_t
Fmem_fun Fmem_fun_ref

<iterator>

traits,tag
Siterator_traits
Sinput_iterator_tag Soutput_iterator_tag
Sforward_iterator_tag Sbidirectional_iterator_tag
Srandom_access_iterator_tag
iterator操作
Fadvance Fdistance
反転イテレータ
Creverse_iterator
挿入イテレータ
Cback_insert_iterator
Fback_inserter
Cfront_insert_iterator Ffront_inserter
Cinsert_iterator Finserter
ストリームイテレータ
Cistream_iterator Costream_iterator
Cistreambuf_iterator Costreambuf_iterator

<list>

双方向リスト
Clist

<map>

重複を許さない辞書
Cmap
重複を許す辞書
Cmultimap

<memory>

アロケータ
Callocator
テンポラリ領域
Craw_storage_iterator
Fget_temporary_buffer Freturn_temporary_buffer Funinitialized_copy Funinitialized_fill Funinitialized_fill_n
auto_ptr
Cauto_ptr

<numeric>

数値計算
Faccumulate Fadjacent_difference Finner_product Fpartial_sum

<queue>

キュー
Cqueue
優先順位キュー
Cpriority_queue

<set>

重複を許さない集合
Cset
重複を許す集合
Cmultiset

<stack>

スタック
Cstack

<utility>

関係演算子
Foperator!= Foperator<= Foperator> Foperator>=
pair
Spair Fmake_pair

<vector>

可変長配列
Cvector

 

[Java入門]3.4 ガソリンスタンドのシミュレーション

Thursday, March 30th, 2006

ガソリンスタンドのシミュレーションを行なうには、これを実行してくれるオブジェクトを作らなくてはいけません。 このためのクラスGasStationSimulation を定義することにします。 その中で、コンストラクタを使ってガソリンスタンドのオブジェクトを作り、シミュレーションを実行するのです。

3.4.1 GasStationSimulation クラス

GasStationSimulationクラスのメソッドについて考えましょう。 まず、GasStationクラスのオブジェクトを作るメソッドが必要です。 これをinitメソッドとしましょう。 ガソリンスタンドを作る(GasStationクラスのコンストラクタを呼び出す) ためには、蓄えているガソリンの量をどうにかして決めなくてはいけませんね。 ガソリンの単価も必要です。 これらが決まればとりあえずガソリンスタンドを作ることができます。

オブジェクトができたなら、今度はそれを使うメソッドが必要です。 このメソッドでは、お客さんが来たときに給油量を聞いてガソリンを入れます。 さらに、代金を計算して受け取ります。 これをdealCustomerメソッドとしましょう。

ガソリンの単価や最初の備蓄量、毎回の給油量はどのようにして決めればよいでしょうか。 すぐに思いつくのはソースに直接書いてしまうことですが、これでは何回実行しても同じ結果になってしまうので、面白くありません。 ここはひとつ、キーボードから入力することにしましょう。 そのときには、ユーザーが何を入力すればいいのかがわかるような、メッセージを表示するといいですね。 また、必要な情報はすべて整数で表されていますから、整数だけが入力できればいいはずです。 メッセージを表示して数値を入力するメソッドを用意しましょう。 メッセージの内容は、引数で受け渡します。 このメソッドの名前は、askIntということにします。

ここまでのメソッドをまとめると、次のようになります。

メソッド名 内容
init 単価、ガソリンの初期量を決定してガソリンスタンドを作る
dealCustomer 給油量を聞いてガソリンを入れ、代金を計算して受け取る
askInt メッセージを表示して整数を入力させ、結果の整数を返す

さて、メソッドはこれでよさそうですので、変数のことも考えてみましょう。 一時的な変数は必要になったときに作ればいいので、ここではガソリンスタンドのシミュレーション全体に関わるような変数を考えます。

まず、用意したガソリンスタンドを、変数に入れておかないといけません。 他に何か必要になるでしょうか? 単価はガソリンスタンドに保存されていますし、給油量は毎回入力するので保存する必要はありません。 ガソリンスタンド以外の変数は、特に必要なさそうです。

では、ソースを書いてみることにしましょう。 まずクラスを作って、変数を定義します。

class GasStationSimulation {
    private GasStation aGS_;
}

3.4.2 メソッドの中身

askIntメソッドは、ガソリンスタンドのオブジェクトを作るために必要な単価や給油量を決定するために使うので、まずこのメソッドから考えることにします。 さきほどまとめた表を基に概要を書いてみると、次のようになります。

private int askInt(String message) {
    // メッセージを表示する
    // 整数を入力してもらう
    // 入力された整数を結果として返す
}

3つ仕事をする必要がありますが、なんだかどれも難しそうです。 でも、安心してください。 ちゃんとこういうことを行なうためのとても便利な方法が用意されているのです。 とにかく、どうすればできるのか、ソースを見てみましょう。

private int askInt(String message) {
    try {
      System.out.print(message);
      System.out.flush();
      String line = new BufferedReader(new InputStreamReader(System.in)).readLine();
      return Integer.parseInt(line);
    } catch ( java.io.IOException e ) {
      System.out.println("could not get input. Sorry.");
      return 0;
    }
}

よくわからない書き方や単語がたくさんでています。 でも、これらの記述がどういう意味なのかは、今はわからなくてかまいません。 この本を読み終えるころにはちゃんと理解できるようになっているはずです。 今はただ、こう書けばメッセージを表示したり、キーボードからの入力を受け取ったり、それを整数の値とすることができると思ってください。

なお、これらのメソッドを利用するためには、BufferedReader, InputStreamReaderクラスを参照することをimportでコンパイラに知らせる必要がありますので、ソースの先頭に以下の記述を入れておきます。

import java.io.BufferedReader;
import java.io.InputStreamReader;

残りの2つのメソッドがすべきこと自体はどちらも単純です。 今作ったaskIntメソッドを使って書いてみましょう。

public void init() {
    System.out.println("For the gas station:");
    int initial = askInt("  initial amount? ");
    aGS_ = new GasStation(initial);
    aGS_.setUnitPrice(askInt("  unit price? "));
}

public void dealCustomer() {
    System.out.println("For the cusomer:");
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_.refuel(amount);
    int price = aGS_.price(chargedAmount);
    aGS_.addSales(price);
    System.out.println("  amount: " + chargedAmount + " [liter]");
    System.out.println("  price: " + price + " [yen]");
}

これらのメソッドの中で、initial, amount, chargedAmount, price という4つの変数を定義しています。 このように、メソッド内で作られた変数はローカル変数と呼ばれ、そのメソッドの中でしか使えません。 ここでは、入力された値や計算結果の整数を一時的に保持するために使われています。

new 演算子

initメソッドの最後から2行目に注目してください。

  aGS_ = new GasStation(initial)

この行では、new演算子でGasStationクラスのコンストラクタを呼び出しています。 つまり、新しいガソリンスタンドのインスタンスを作り、aGS_という変数に代入しているのです。

そして、dealCustomer, initメソッドには、aGS_.refuel(amount)aGS_.setUnitPrice(askInt(& unit price? &amp)といった記述が見られますが、これはaGS_という変数に保持されているガソリンスタンドオブジェクトのメソッドを呼び出しているところなのです。

メソッドsetUnitPriceGasStationクラスのオブジェクトすべてに共通するメソッドなので、オブジェクトのかわりにクラス名を指定して GasStation.setUnitPrice(askInt(& unit price? &amp)とすることもできます。 通常、クラス全体で共通なメソッドを呼び出すときはこのようにクラス名を用いますので、これ以降の例ではこの方法をとることにしましょう。

3.4.3 すべては main メソッドから始まる

これでクラスの定義はほぼできましたが、ひとつ重要なことを忘れています。 GasStationクラスのインスタンスは、GasStationSimulationのインスタンスが作ります。 では、GasStationSimulationのインスタンスはいったい誰がどのように作ればいいのでしょう? 実はJavaではクラスごとに mainメソッドを呼ばれる特別なメソッドを用意することができます。

このメソッドは、インタープリタがそのクラスを実行するときに、最初に呼び出されることになっています。 つまり、javaコマンドで引数にクラスを指定して実行すると、そのクラスで定義されているmainメソッドがまず呼び出されます。 HelloWorldクラスを思い出してください。 HelloWorldクラスはmainメソッドだけでできていましたね。

さて、GasStationSimulationクラスにmianメソッドを定義しましょう。 ソースは次のようになります。

public static void main(String[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
}

public static void main(String[] args)というのは決り文句だと思ってください。 また、mainメソッドにはargsという引数がひとつだけあります。 これは、javaコマンドを起動したとき、クラス名以降のコマンドライン引数が順に格納される文字列の配列です。 配列については後ほど 3.6.3 で実際に使う例が出きますので、ここでは説明しません。

メソッドの中身はどうなっているでしょうか。 まず、GasStationSimulationのコンストラクタを呼び出してインスタンスを作っています。 この引数をとらないコンストラクタは、ひとつもコンストラクタを定義しなかった場合に用意されるデフォルトのコンストラクタです。 そして、作ったGasStationSimulationオブジェクトを変数 simulationに代入し、initメソッドとdealCustomerメソッドを順に呼び出しています。

3.4.4 GasStationSimulation クラスのソース

これでGasStationSimulationクラスの定義が出来上がったので、ファイル全体がどうなるか一度見てみることにします。 クラスごとにファイルを分けた方がわかりやすいので、GasStationSimulation.javaとしましょう。 ソースは、$chapter3/ex2/GasStationSimulation.javaに格納されています。

Java: GasStationSimulation.java
import java.io.BufferedReader;
import java.io.InputStreamReader;

class GasStationSimulation {

  private GasStation aGS_;

  private int askInt(String message) {
    try {
      System.out.print(message);
      System.out.flush();
      String line = new BufferedReader(new InputStreamReader(System.in)).readLine();
      return Integer.parseInt(line);
    } catch ( java.io.IOException e ) {
      System.out.println("could not get input. Sorry.");
      return 0;
    }
  }

  public void init() {
    System.out.println("For the gas station:");
    int initial = askInt("  initial amount? ");
    GasStation.setUnitPrice(askInt("  unit price? "));
    aGS_ = new GasStation(initial);
  }

  public void dealCustomer() {
    System.out.println("For the cusomer:");
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_.refuel(amount);
    int price = aGS_.price(chargedAmount);
    aGS_.addSales(price);
    System.out.println("  amount: " + chargedAmount + " [liter]");
    System.out.println("  price: " + price + " [yen]");
  }

  public static void main(String[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
  }

}

C++: GasStationSimulation.h
#ifndef GASSTATIONSIMULATION_H__
#define GASSTATIONSIMULATION_H__

#include <string>
#include "GasStation.h"

class GasStationSimulation {

private:
  GasStation* aGS_;

  int askInt(const std::string& message);

public:
  ~GasStationSimulation();
  void init();
  void dealCustomer();

};

#endif
C++: GasStationSimulation.cpp
#include <iostream>

#include "GasStationSimulation.h"
#include "GasStation.h"

GasStationSimulation::~GasStationSimulation() {
  delete aGS_;
}

int GasStationSimulation::askInt(const std::string& message) {
  std::cout << message << std::flush;
  int result;
  if ( std::cin >> result ) {
    return result;
  }
  std::cout << "could not get input.  sorry." << std::endl;
  return 0;
}

void GasStationSimulation::init() {
  std::cout << "For the gas station:" << std::endl;
  int initial = askInt("  initial amount? ");
  aGS_ = new GasStation(initial);
  int unitPrice = askInt("  unit price? ");
  GasStation::setUnitPrice(unitPrice);
}

void GasStationSimulation::dealCustomer() {
  std::cout << "For the customer:" << std::endl;
  int amount = askInt("  refuel amount? ");
  int chargedAmount = aGS_->refuel(amount);
  int price = aGS_->price(chargedAmount);
  aGS_->addSales(price);
  std::cout << "  amount: " << chargedAmount << " [liter]" << std::endl;
  std::cout << "  price:  " << price << " [yen]" << std::endl;
}
C++: main.cpp
#include "GasStationSimulation.h"

int main() {
  GasStationSimulation simulation;
  simulation.init();
  simulation.dealCustomer();
  return 0;
}
C#: GasStationSimulation.cs
class GasStationSimulation {

  private GasStation aGS_;

  private int askInt(string message) {
    System.Console.Write(message);
    System.Console.Out.Flush();
    try {
      return System.Int32.Parse(System.Console.ReadLine());
    } catch ( System.Exception ) {
      System.Console.WriteLine("could not get input.  sorry.");
      return 0;
    }
  }

  public void init() {
    System.Console.WriteLine("For the gas stations:");
    int initial = askInt("  initial amount? ");
    aGS_ = new GasStation(initial);
    GasStation.unitPrice = askInt("  unit price? ");
  }

  private void dealCustomer() {
    System.Console.WriteLine("For the customer:");
    int amount = askInt("  refuel amount? ");
    int chargedAmount = aGS_.refuel(amount);
    int price = aGS_.price(chargedAmount);
    aGS_.addSales(price);
    System.Console.WriteLine("  amount: " + chargedAmount + " [liter]");
    System.Console.WriteLine("  price:  " + price + " [yen]");
  }

  [System.STAThread]
  static void Main(string[] args) {
    GasStationSimulation simulation = new GasStationSimulation();
    simulation.init();
    simulation.dealCustomer();
  }

}
VB: GasStationSimulation.vb
Class GasStationSimulation

  Private aGS_ As GasStation

  Private Function askInt(ByVal message As String)
    System.Console.Write(message)
    System.Console.Out.Flush()
    Try
      Return System.Int32.Parse(System.Console.ReadLine())
    Catch e As System.Exception
      System.Console.WriteLine("could not get input.  Sorry.")
      Return 0
    End Try
  End Function

  Public Sub init()
    System.Console.WriteLine("For the customer:")
    Dim initial As Integer = askInt("  initial amount? ")
    GasStation.unitPrice = askInt("  unit price? ")
    aGS_ = New GasStation(initial)
  End Sub

  Public Sub dealCustomer()
    System.Console.WriteLine("For the customer:")
    Dim amount As Integer = askInt("  refuel amount? ")
    Dim chargedAmount As Integer = aGS_.refuel(amount)
    Dim price As Integer = aGS_.price(chargedAmount)
    aGS_.addSales(price)
    System.Console.WriteLine("  amount: " + CStr(chargedAmount) + " [liter]")
    System.Console.WriteLine("  price: " + CStr(price) + " [yen]")
  End Sub

  Public Shared Sub Main()
    Dim simulation As GasStationSimulation = New GasStationSimulation()
    simulation.init()
    simulation.dealCustomer()
  End Sub

End Class

3.4.5 コンパイルと実行

ソースが用意できたらコンパイルしてみてください。 コマンドラインから次の下線部の通りに入力して、javacの引数に今作った2つのファイルを指定します。

% javac GasStation.java GasStationSimulation.java

正しく入力されていれば何もメッセージが出力されずにコンパイルが終了します。 もし、エラーメッセージが出力された場合は、もう一度ソースを見直して間違いを修正してください。

コンパイルが終了したら、クラスファイルができていることを確認してください。 クラスファイルができていたら javaコマンドを使って実行しましょう。 実行するには、クラスファイルを作成したディレクトリが、環境変数$CLASSPATHに含まれていなくてはいけません。 現在の作業ディレクトリをあらわす&.&を含めておきましょう。

コマンドラインから次の下線部のように入力して、GasStationSimulationクラスを実行します。

% java GasStationSimulation

メッセージが表示されて、整数の入力待ちになるので、?の後に数字を入力して改行キーを押します。

% java GasStationSimulation
  For the gas station:
    initial amount? 500

    unit price? 120
  For the cusomer:
    refuel amount? 30
    amount: 30 [liter]
    price: 3600 [yen]

ちゃんと実行できたでしょうか? 何度か試してみて、代金の計算が正しいことを確認してください。

prev next