野良C++erの雑記帳 このページをアンテナに追加 RSSフィード Twitter

2010-02-02 たまにはどうでもいい記事を書いてみる

C++/Boost 基礎文法最速マスター

参考: http://d.hatena.ne.jp/faith_and_brave/20100201/1264997004


C++0xになると、C++03 でごちゃごちゃした部分がだいぶすっきり書けるようになる」

らしいですが、C++0xを待たなくてもBoostを使えばだいぶすっきり書けるので、

BoostでのC++入門はこんな感じだよー、という気持ちで以下略。

この記事はC言語をある程度理解していることが前提です。



1. Hello World

C++/Boostでの出力はC++標準の IOStream ライブラリBoost.Format を組み合わせて行います。

例として、C言語のprintfを用いた Hello World を、C++/Boostを使って書き直してみます。

#include <stdio.h>

int main()
{
  printf( "%s\n", "Hello, World" );
  return 0;
}

以上のコードをC++/Boostで書くと、以下のようになります。

#include <iostream>         // std::cout を使うのに必要
#include <boost/format.hpp> // boost::format を使うのに必要

int main()
{
  std::cout << boost::format("%s\n") % "Hello, World";
  return 0;
}

std::cout というのは標準出力です。Cで言う stdout です。

「標準出力(console out)に "Hello, World" を整形(format)して出力する」と読みます。

boost::format に渡す文字列は、C言語の printf と同じスタイルで書くことが出来ます。

複数の引数を渡すには、

std::cout << boost::format("%s, %s\n") % "Hello" % "World";

のように % 演算子をつなげて書きます。

またそれとは別のスタイルとして、

std::cout << boost::format("%1%, %2%\n") % "Hello" % "World";

このように、何番目の引数を表示させるかを明記して書くことも出来ます。

その場合、

std::cout << boost::format("1, 2, %1%, 4, %2%, %1%, 7, 8, %1%, %2%\n") % "Fizz" % "Buzz";

このように引数を何回も使い回したりできます。

使い回さない場合でも、この書き方なら いちいち型を指定する必要がないのでオススメです。


2.

Boost.Format に渡す値は、別に文字列である必要はありません。

#include <iostream>
#include <boost/format.hpp>

int main()
{
  std::cout << boost::format("%1% = %2%\n") % "1 + 1" % ( 1 + 1 );
  return 0;
}

このように書くことで、式の計算結果を表示させることも出来ます。

()でくるんであるのは % 演算子の優先順位の関係です。 Boost.Format に渡す引数が複雑な場合は()でくるんで渡した方が悩まないで済むと思います。

C言語と同様、整数同士の割り算を行う場合には注意が必要です。

#include <iostream>
#include <boost/format.hpp>

int main()
{
  std::cout << boost::format("%1%\n") % ( 3 / 2 );      // 端数が切捨てされる
  std::cout << boost::format("%1%\n") % ( 3.0 / 2.0 );  // きちんと端数も考慮される
  
  return 0;
}

複雑な計算を行わせることも出来ます。

#include <iostream>
#include <boost/format.hpp>
#include <cmath>  // std::sin を使うのに必要

int main()
{
  std::cout << boost::format("%1%\n") % ( 1 * 2 + 2 * 3 );        // * は + より優先される
  std::cout << boost::format("%1%\n") % ( 10 * ( 10 - 1 ) / 2 );  // () を用いて優先順位を変えられる
  std::cout << boost::format("%1%\n") % std::sin(1.0);            // sinを求める。単位はラジアン

  return 0;
}

3. 変数

C言語と同様ですが、C++/Boostでは基本的に const を付けて宣言することが望ましいです。

#include <string> // std::string を使うのに必要

#include <iostream>
#include <boost/format.hpp>

int main()
{
  const int         i = 23;     // 3 という整数(int)に a という名前を割り当てる
  const std::string s = "abc";  // こちらは文字列

  std::cout << boost::format("i の値は'%1%', s の値は'%2%'.\n") % i % s;

  return 0;
}

このように "const変数=;" のように書ことで、その値に対して名前をつけることが可能になります。

以降は、23といった具体的な値の代わりに、i といった宣言した変数の名前を書くことで、具体的な値を書いたのと同じように振舞わせることが出来ます。

"const変数= 値1, 変数= 値2, 変数= 値3;" のように、複数の変数を定義することもできます。

#include <iostream>
#include <boost/format.hpp>

int main()
{
  const int mikan = 30, apple = 100;  // みかんとりんごの値段
  
  std::cout << boost::format("みかん7個とりんご3個で、合計 %1% 円\n") % ( mikan * 7 + ringo * 3 );

  return 0;
}

4. 関数

関数を使うことで処理に名前を付けることが出来ます。

"戻り値の型 関数(引数リスト ) { 処理 }" のように書きます。

// 引数を取らない例
int the_Answer_to_life_the_universe_and_everything()
{
  // "return 値;" で値を返す
  return 42;
}
// 引数ひとつを取る例
int square( int x )
{
  return x * x;
}
// 複数の引数を取る例
double ave2( double x, double y )
{
  return ( x + y ) / 2;
}

関数を呼び出すには、 "関数( 引数リスト )" のように書きます。

// さっきの関数定義がここにあるとする

#include <iostream>
#include <boost/format.hpp>

int main()
{
  // 関数の戻り値に名前をつける
  const int i = the_Answer_to_life_the_universe_and_everything();
  const int s = square(i);
  
  std::cout << boost::format("%1% の二乗は %2% です。\n") % i % s;
  // 直接使うことも出来る
  std::cout << boost::format("%1% と %2% の平均は %3% です。") % i % s % ave2( i, s );
  
  return 0;
}

C言語と同様、何も値を返さない関数を定義することも出来ます。その場合は void という特別な型を使います。

#include <iostream>
#include <boost/format.hpp>

void disp(const std::string& s)
{
  std::cout << boost::format("%1%\n") % s;
}

int main()
{
  disp("Hello World");
  return 0;
}

またBoostには関数をラップした Boost.Function というものも存在し、これを使うことで、関数変数として扱うことが出来ます。

#include <iostream>
#include <boost/format.hpp>
#include <cmath>  // for std::sqrt
#include <boost/function.hpp>

int the_Answer_to_life_the_universe_and_everything()
{
  return 42;
}

void disp(const std::string& s)
{
  std::cout << boost::format("%1%\n") % s;
}

double norm2( double x, double y )
{
  return std::sqrt( x * x + y * y );
}

int main()
{
  // the_Answer_to_life_the_universe_and_everything に別名を付ける
  const boost::function<int()> f1 = the_Answer_to_life_the_universe_and_everything;
  std::cout << boost::format("%1%\n") % f1(); // f1() は the_An(中略)ing() と同じ
  
  // disp に別名を付ける
  const boost::function<void(const std::string&)> f2 = disp;
  f2("Hello World"); // disp("Hello, World"); と同じ
  
  // norm2 に別名を付ける
  const boost::function<double (double, double)> f3 = norm2;
  std::cout << boost::format("%1%\n") % f3( 3, 4 ); // norm2( 3, 4 ) と同じ
  
  return 0;
}

型名は"boost::function< 戻り値の型 ( 引数型リスト )>"となります。


5.

C++/Boost には以下のような型が用意されています(一部です)。

基本データ型
int    : 整数型(0, 1, 2, 3, -1, etc...)
char   : 文字型('a', 'b', 'c', '1', etc...)
double : 浮動小数点数型(0.1, 3.14, etc...)
bool   : 論理型(true, false)

複合データ型
boost::array<T, N>         : 配列(<boost/array.hpp>をインクルード)
std::vector<T>             : リスト(<vector>をインクルード)
std::string                : 文字列(<string>をインクルード)
std::map<Key, Value>       : 辞書(<map>をインクルード)
std::set<T>                : 集合(<set>をインクルード)
boost::function<Signature> : 関数(<boost/function.hpp>をインクルード)
boost::tuple<T1,T2, ...>   : 複数の値の詰め合わせ(<boost/tuple/tuple.hpp>をインクルード)
boost::shared_ptr<T>       : ポインタ(<boost/shared_ptr.hpp>をインクルード)
boost::optional<T>         : T型か無効値(<boost/optional.hpp>をインクルード)
boost::variant<T1,T2, ...> : 可変型(Cでいうunion)(<boost/variant.hpp>をインクルード)
boost::any                 : 任意型(<boost/any.hpp>をインクルード)

6. 基本演算

C++/Boost演算子は殆どC言語と同様です。

ただし >> 演算子および << 演算子は、従来の意味(ビットシフト)に加えて「ストリーム入出力」という意味を持ちます。

また % 演算子も、従来の意味(剰余演算)に加えて「フォーマット出力」という意味を持ちます。

これらは「演算子多重定義」という仕組みによって実現されていますが、ライブラリ製作者以外はその仕組を学習する必要はないでしょう。


7. 参照

参照は変数に別名を与えます。

// const参照
const int  a = 42;
const int& b = a; // b は a を参照する

std::cout << boost::format("%1%\n") % b;  // boost::format("%1%\n") % a と同じ

// 可変な参照
int  x = 1;
int& y = x; // y は x を参照する

y = 23; // x = 23; と同じ。参照先の x を書き換える

参照は関数引数として使うことが出来ます。

特にconstな参照で渡された変数は、無駄なコピー動作を起こさないので、コストを気にする場合にしばしば用いられます。

また、可変な参照を、複数の値を返すために使うことも出来ます。

// http://d.hatena.ne.jp/faith_and_brave/20100201/1264997004 の例
void get_ip(int& aa, int& bb, int& cc, int& dd)
{
  aa = 127;
  bb = 0;
  cc = 0;
  dd = 1;
}

int a = 0;
int b = 0;
int c = 0;
int d = 0;
get_ip(a, b, c, d);

しかし、その場合はconstではなくなってしまう為、 Boost.Tuple を使った方が良いです:

#include <boost/tuple/tuple.hpp>

boost::tuple<int, int, int, int> get_ip()
{
  return boost::make_tuple( 127, 0, 0, 1 );
}

#include <iostream>
#include <boost/format.hpp>

int main()
{
  const boost::tuple<int, int, int, int> t = get_ip();
  
  const int& a = boost::get<0>(t);
  const int& b = boost::get<1>(t);
  const int& c = boost::get<2>(t);
  const int& d = boost::get<3>(t);
  
  std::cout << boost::format("%1%.%2%.%3%.%4%\n") % a % b % c % d;
}

8. 制御構文

C++/Boost では、C言語の if/switch/while/for に加え、範囲 for 構文を擬似的にサポートしています。

#include <boost/foreach.hpp>  // マクロBOOST_FOREACHを使えるようにする

#include <iostream>
#include <boost/format.hpp>

int main()
{
  const int a[] = {1, 2, 3};
  
  BOOST_FOREACH( const int x, a )
  {
    std::cout << boost::format("%1%\n") % x;
  }
  
  return 0;
}

また、 if 文の条件部や for 文の初期化部等で変数を宣言することも出来ます。

#include <cmath>
#include <boost/optional.hpp>

#include <iostream>
#include <boost/format.hpp>

// boost::optional を使った例
// 詳しく説明すると脱線するので「こんな書き方もある」程度に思って下さい
boost::optional<double> my_sqrt( double x )
{
  if( x < 0 )
  {
    return boost::none;
  }
  else
  {
    return std::sqrt(x);
  }
}

int main()
{
  // if文中で変数を宣言し
  if( const boost::optional<double> x_ = my_sqrt(-1) )
  {
    const double& x = x_.get(); // それを使う
    std::cout << boost::format("%1%\n") % x;
  }
  else
  {
    std::cout << boost::format("%1%\n") % "負の値です!";
  }
  
  return 0;
}

9. クラス

Cで言う構造体です。 C++/Boost では、クラスを使うことで新しい型を定義することが出来ます。

今まで出てきた std::string や boost::function などは全てクラスです。これらは基本型と殆ど同じように扱うことが出来る他、 "変数.メンバ関数名" と書くことにより、そのクラス専用に用意された関数を使うことが出来ます。

const std::string s = "abc";
std::cout << boost::format("%1%\n") % s.length(); // s の長さを求める

ユーザ側で定義することも出来ますが、ライブラリ側で定義されたクラスを使うだけでも、十二分にプログラミングは可能です。

自分でクラスを作ることは落とし穴が多いので、慣れないうちはライブラリ定義のクラスを使いましょう。


10. テンプレート

boost::function<int()> 等で既に触れられている物です。

任意の型に対して同じことをさせたい場合に用います。

例として簡単なテンプレート関数を作ってみると、

template<typename T>
void disp( T x )
{
  std::cout << boost::format("%1%\n") % x;
}

このように書くことで、 disp 関数は、あらゆる型の変数 x を受け入れることが出来るようになります。

例えば、

disp<int>(23);

のように呼び出すと、 dispTint で置き換えた

void disp( int x )
{
  std::cout << boost::format("%1%\n") % x;
}

という関数コンパイラによって自動生成され、呼び出されます。

同様に disp<std::string>("abc"); のように呼び出すと、

void disp( std::string x )
{
  std::cout << boost::format("%1%\n") % x;
}

という関数が呼び出されます。

また、これらの角括弧 <> は省略可能です。

const double d = 3.14;
disp(d);

このように書けば、 d の型である double 版の disp が呼ばれます。

また、前項のクラスも、テンプレートにすることが出来ます。 std::vector<int> v; のように使います。

その場合、角括弧は省略不能です。


11. 固定長配列

C言語の配列はC++/Boostでも使えますが、C++/Boostにはは様々な点で便利な boost::array<T, N> というクラスが用意されています。

このクラスは通常の配列と同様に扱える他、メンバごとのコピーなどを簡単に行えます。

#include <boost/array.hpp>
#include <boost/foreach.hpp>

#include <iostream>
#include <boost/format.hpp>

int main()
{
  const boost::array<int, 3> a = {{ 1, 2, 3 }};
  
  // 要素数取得
  const int size   = a.size();  // 3
  // 要素アクセス
  const int front  = a.front(); // 1
  const int back   = a.back();  // 3
  const int second = a[1];      // 2
  
  // 全部表示
  BOOST_FOREACH( const int x, a )
  {
    std::cout << boost::format("%1%\n") % x;
  }
  
  // コピー
  boost::array<int, 3> b = a;
  
  // 破壊的動作
  // 要素書き換え
  for( std::size_t i = 0; i < b.size(); ++i )
  {
    b[i] = i;
  }
  
  // 全要素を 0 で埋める
  b.assign( 0 );
  
  return 0;
}

12. リスト

C言語では取り扱いの難しかった可変長配列は、C++/Boost では std::vector<T> というクラスにまとめられています。

#include <vector>
#include <boost/assign.hpp>
#include <boost/foreach.hpp>

#include <iostream>
#include <boost/format.hpp>

int main()
{
  // 構築は少し面倒。C++の次世代規格で改善される
  const std::vector<int> a = boost::assign::list_of(1)(2)(3)(4);
  
  // boost::array と同じように扱える
  const int size   = a.size();  // 4
  const int front  = a.front(); // 1
  const int back   = a.back();  // 4
  const int second = a[1];      // 2
  
  
  // 以下、破壊的動作
  std::vector<int> b;
  using boost::assign::operator+=;  // += による要素追加を行うのに必要
  b += 2, 3, 5, 7, 11, 13;  // 連続的要素追加
  
  b.push_back( 17 );  // 後ろに要素を追加する
  b.pop_back();  // 最後の要素を削除する
  
  // 要素書き換え
  // BOOST_FOREACH を使ってみる
  BOOST_FOREACH( int& x, b )  // int& とすることで、書き換えが可能になる
  {
    x *= 2;
  }
  
  // 全部表示
  BOOST_FOREACH( const int x, b )
  {
    std::cout << boost::format("%1%\n") % x;
  }
  
  return 0;
}

13. 連想配列

std::map<Key, Value>, boost::unordered_map<Key, Value> という二種類の連想配列が用意されています。前者は二分木、後者はハッシュテーブルによって実装されています。

#include <boost/unordered_map.hpp>  // 普段は高速なこっちを使う
// #include <map> // BOOST_FOREACH で取得した時にソートされている必要がある場合用
#include <boost/assign.hpp>
#include <boost/foreach.hpp>

#include <iostream>
#include <boost/format.hpp>

int main()
{
  // 少し面倒なので map の型を typedef する
  typedef boost::unordered_map<std::string, int> map_type;
  // typedef std::map<std::string, int> map_type;
  
  // やはり構築は少し面倒。C++の次世代規格で改善される
  const map_type m =
    boost::assign::map_list_of("mikan", 30)("ringo", 100)("meron", 2000);
  
  // m.count(要素) で、その要素が存在しているかチェックできる
  if( m.count("ringo") )
  {
    // りんごの値段は?
    const int ringo = m.at("ringo");
    std::cout << boost::format("%1%\n") % ringo;
  }
  
  // 要素の書き換えとか
  map_type m2 = m;
  
  // 要素アクセス(なければ作る)
  m2["iyokan"] = 100;
  
  // 全部表示する(少し面倒)
  BOOST_FOREACH( const map_type::value_type x, m2 )
  {
    // x.first がキー、 x.second が値
    std::cout << boost::format("%1%: %2%\n") % x.first % x.second;
  }
  
  return 0;
}

14. 名前空間

今まで std::cout や boost::format と使ってきた中の、 std とか boost とかです。

これらは名前空間と呼ばれ、同じ名前の関数やクラスの衝突を避けるために使われます。

namespace A
{
  int f() { return 23; }
}
namespace B
{
  int f() { return 42; }
}

#include <iostream>
#include <boost/format>

int main()
{
  std::cout << boost::format("%1%\n") % A::f(); // 23
  std::cout << boost::format("%1%\n") % B::f(); // 42
}

いちいち何回も std:: や boost:: を書くのが面倒な場合は、 using 宣言というものを行えます。

#include <iostream>
#include <boost/format>

int main()
{
  using std::cout;      // 以降は単に cout で使えるようになる
  using boost::format;  // 同様に format 単独で使えるようになる
  
  cout << format("%1%\n") % "Hello, World";
  return 0;
}

さらに using namespace std; と書くことで、 std 名前空間にある全ての名前を std:: と書かずに使うことが出来ますが、意図しない動作になる可能性もあるため、オススメはしません。


15. その他の細かいこと

この項は随時追加するかもしれません。

  • main 関数に限り、最後の return 0; という記述は省略できます。その場合には最後に return 0; が呼ばれた事になります。
  • 実は単純な出力なら boost::format を使わなくても std::cout << "Hello, World\n"; のように書けます。複雑な出力でも std::cout << "i の値は" << i << std::endl; 的に << 演算子を連続させることで記述できます。ただし読みにくくなるので基本は Boost.Format を使うべきです。なお std::endl は「改行して出力する」という意味です。
  • 実は boost::format の型指定文字 %s は型非依存です。 const int i = 42; std::cout << boost::format("%s\n") % i; というコードもエラーにならず表示されます。

shiracha.rikyushiracha.rikyu 2010/02/14 23:35 Boost.formatは国際化対策にもなりそうですな。

SHOOSHOO 2010/02/23 01:27 6章で ">>" が ">>" になってます。

SHOOSHOO 2010/02/23 01:44 いや、&gt;結構あるぞ…?
読み終わりました。この程度のことは知っていましたが、やはりBoostかわいいですねw
C++0xでautoが使えるようになったらもっとスマートに書けそう。

gintenlabogintenlabo 2010/02/23 02:10 はてなのバグで "gt" がキーワード検索に引っかかったみたいですね。訂正しました。
Boostかわいいです。 正直この程度の記事では書きたりないくらい。基礎文法ということで色々我慢した訳ですが、正直、文字列(正規表現)周りや数学(乱数)周りは紹介してもよかったなーと思います

とおりすがりとおりすがり 2016/03/10 14:01 3.変数の部分なんですが、「3 という整数(int)に a という名前を割り当てる」は「23 という整数(int)に i という名前を割り当てる」ではないでしょうか?

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

リンク元