Aritalab:Lecture/Programming/Cpp

From Metabolomics.JP
< Aritalab:Lecture | Programming(Difference between revisions)
Jump to: navigation, search
m
m (Garbage Collection)
 
(24 intermediate revisions by one user not shown)
Line 1: Line 1:
 
{{Lecture/Header}}
 
{{Lecture/Header}}
 +
 +
<big>"C makes it easy to shoot yourself in the foot; C&#43;&#43; makes it harder, but when you do it blows your whole leg off."
 +
<div Align="right">Bjarne Stroustrup</div></big>
 +
 
==C&#43;&#43;プログラミング==
 
==C&#43;&#43;プログラミング==
C&#43;&#43;はC言語の完全な上位互換ですが、オブジェクト指向型という点でC言語とは全く違うプログラミング思想に基づいています。
+
C&#43;&#43;はC言語の完全な上位互換ですが、オブジェクト指向型という点でC言語とは全く違うプログラミング思想に基づいています。簡単に言うと、JavaはC&#43;&#43;の(思想の)利点を活かせるように新開発された言語です。
簡単に言うと、JavaのようなプログラムをCで書く為に作られたと言ってよいと思います。
+
  
===マクロの利用===
+
このページでは、C&#43;&#43;の基本原則を書いています。より細かいプログラミングテクニックやサンプルプログラムが[[Aritalab:Lecture/Programming/Cpp/Tips|Tips]]のページにもあります。
C言語同様、マクロ
+
 
 +
===マクロ, inlineの利用===
 +
C言語でマクロが果たしていた役割を、C&#43;&#43;ではinline関数を用いて実現できます。
 +
関数定義を
 +
<pre>
 +
inline int cmp(const char& x, const char& y)
 +
{ if (x < y) return -1; else if (x > y) return 1; else return 0; }
 +
</pre>
 +
と書いておくと、<tt>cmp(x,y)</tt>という呼び出しはコンパイル時に全てソース中に展開されます。
 +
実行ファイルが大きくなるのは欠点ですが、処理速度を犠牲にせずに可読性が上がります。
  
 
===ヘッダーファイル===
 
===ヘッダーファイル===
利用する関数の型情報は<tt>.h</tt>や<tt>.hh</tt>という拡張子を持つヘッダーファイルに記述し、それをプログラムの先頭で読み込みます。一番有名なヘッダーは<tt>stdio.h</tt>と<tt><iostream></tt>でしょう。
+
利用する関数の型情報は<tt>.h</tt>や<tt>.hh</tt>という拡張子を持つヘッダーファイルに記述し、それをプログラムの先頭で読み込みます。一番有名なヘッダーは<tt><stdio.h></tt>と<tt><iostream></tt>でしょう。
 
<pre>
 
<pre>
 
#include <stdio.h>
 
#include <stdio.h>
Line 18: Line 30:
 
</pre>
 
</pre>
 
二行目のusing以下は、<tt>iostream</tt>で定義される標準入出力 std::cin, std::cout を、std::というネームスペース名無しで利用するために記述します。
 
二行目のusing以下は、<tt>iostream</tt>で定義される標準入出力 std::cin, std::cout を、std::というネームスペース名無しで利用するために記述します。
 +
C&#43;&#43;でCの入出力を使うには
 +
<pre>
 +
#include <cstdio>
 +
</pre>
 +
としてください。
  
 
ヘッダーファイルはクラスを定義するソースプログラム毎に用意します。ディレクトリ内に<tt>.c</tt>や<tt>.cpp</tt>等のファイルと混在してくると面倒です。例えばinclというディレクトリを作り、その中にヘッダーを集めておくと便利でしょう。(コンパイル方法は後述。)
 
ヘッダーファイルはクラスを定義するソースプログラム毎に用意します。ディレクトリ内に<tt>.c</tt>や<tt>.cpp</tt>等のファイルと混在してくると面倒です。例えばinclというディレクトリを作り、その中にヘッダーを集めておくと便利でしょう。(コンパイル方法は後述。)
Line 23: Line 40:
 
各ヘッダーファイルは、プログラム中で複数回読み込むとエラーになります。ヘッダーファイルの先頭に
 
各ヘッダーファイルは、プログラム中で複数回読み込むとエラーになります。ヘッダーファイルの先頭に
 
<pre>
 
<pre>
#ifndef LIST_HH
+
#ifndef LIST_H
#define LIST_HH
+
#define LIST_H
#include "iterator.hh"
+
#include "iterator.h"
 
   :
 
   :
 
#endif
 
#endif
 
</pre>
 
</pre>
 
のように、ifndefのおまじないを入れておきましょう。
 
のように、ifndefのおまじないを入れておきましょう。
 
+
<!----
 
===コンパイルの仕方===
 
===コンパイルの仕方===
 
統合環境を使わない場合は、Makefileを作りましょう。Makeではタブが意味を持つので注意します。下の例では、ヘッダーをinclディレクトリに記述する場合です。(タブが重要なので、下の内容を単にcopy&pasteしても動きません。)
 
統合環境を使わない場合は、Makefileを作りましょう。Makeではタブが意味を持つので注意します。下の例では、ヘッダーをinclディレクトリに記述する場合です。(タブが重要なので、下の内容を単にcopy&pasteしても動きません。)
Line 41: Line 58:
 
OBJS = $(wildcard *.o)
 
OBJS = $(wildcard *.o)
  
all clean:
+
clean:
 
       rm -f main
 
       rm -f main
  
Line 48: Line 65:
 
</pre>
 
</pre>
 
これを用意しておくと、<tt>make main</tt> や <tt>make clean</tt>と打つだけでコンパイルや片づけができます。
 
これを用意しておくと、<tt>make main</tt> や <tt>make clean</tt>と打つだけでコンパイルや片づけができます。
 +
--->
 +
 +
===ファイルの入出力===
 +
C&#43;&#43;では入出力にオペレータ<tt><<, >></tt>を使います。
 +
;与えられたファイルを全て結合して表示する cat.cpp
 +
<pre>
 +
#include <iostream>
 +
#include <fstream>
 +
#include <string>
 +
#include <vector>
 +
using namespace std;
 +
 +
int main(int argc, char *argv[])
 +
  {
 +
    vector<string> lines;
 +
    string line;
 +
    for(int i=1; i < argc; i&#43;&#43;) {
 +
      //argv[0]には実行ファイル名が入っている
 +
      ifstream fin(argv[i], ios::in);
 +
      if(!fin) {// 必ずエラーチェックを入れる
 +
        cout << "Error: cannot open file(" << argv[i] << ")" << endl;
 +
        exit(1);
 +
      }
 +
      while(getline(fin, line))
 +
        lines.push_back(line);
 +
      // fin.close(); ファイルはクローズしなくて良い。
 +
    }
 +
    for(vector<string>::iterator itr=lines.begin();
 +
        itr != lines.end(); &#43;&#43;itr)
 +
      cout << *itr << "\n";
 +
  }
 +
</pre>
 +
これは STL を使うプログラム例なのでファイルの中身をすべて vector の中にコピーしています。
 +
(ファイルの内容を出力するためだけなら、こんなことをしてはいけません。)
  
 
==クラス定義==
 
==クラス定義==
 +
クラスは以下のように定義します。
 
<pre>
 
<pre>
 
class class名 {
 
class class名 {
Line 58: Line 110:
 
};
 
};
 
</pre>
 
</pre>
と定義します。C言語における構造体との主な違いはアクセス制限です。
+
C言語における構造体との主な違いはアクセス制限です。
クラス定義の中では、自分自身を<tt>this</tt>ポインタで参照することができます。
+
 
 +
===クラスと構造体の違い===
 +
以下のようなC構造体を考えましょう。
 +
<pre>
 +
struct list {
 +
  char* name;
 +
  struct list* next;
 +
  struct list* prev;   
 +
};
 +
</pre>
 +
構造体を初期化するには、構造体用のメモリと名前のメモリをそれぞれ malloc しなくてはなりません。
 +
<pre>
 +
  ...
 +
  struct list *p;
 +
  char* sampleName;
 +
  ...
 +
  if ((p = (struct list *) malloc(sizeof(struct list))) == NULL) {
 +
    printf("malloc error\n"); exit(EXIT_FAILURE);
 +
  }
 +
  if (p->name = (char*) malloc(sizeof(char)*20)) == NULL {
 +
    printf("malloc error\n"); exit(EXIT_FAILURE);
 +
  }
 +
  strcpy(p->name, sampleName);
 +
  ...
 +
</pre>
 +
これでは全てのユーザが構造体の中身を熟知していないと使えません。
 +
とりわけ、nameに malloc が必要なことや、20文字で十分かという判断は面倒です。
 +
そこでC&#43;&#43;ではこれをクラスにします。
 +
<pre>
 +
public class Node {
 +
  string name;
 +
  Node* next;
 +
  Node* prev;
 +
 
 +
public:
 +
  Node(string str) : name(str) { prev=next=NULL; } //コンストラクタ
 +
  ~Node() {} //デストラクタ
 +
  ...
 +
  string getName() { return name; }
 +
};
 +
</pre>
 +
コンストラクタが、構造体の malloc に対応します。
 +
Cにおける char* 型はできる限り STLの string で置き換えましょう。こうすると、文字列の長さを気にしなくてよくなります。
 +
実際、上のクラスでも 20 という定数や malloc が消えています。
 +
 
 +
===自分自身を参照する<tt>this</tt>===
 +
クラスの中で、自分自身を参照するには this というポインタを使います。
 +
つまり実体を参照する場合は *this になります。
 +
このポインタはコピーコンストラクタを定義する際に出てきます。
 +
<pre>
 +
public class Node {
 +
  ...
 +
  Node& Operator=(const Node& x)
 +
    { name = x.name; next = x.next; prev = x.prev; return *this; }
 +
  ...
 +
}
 +
</pre>
 +
最後に <tt>return *this;</tt> としている点に注意してください。
 +
自分自身を戻り値として返し、その際に参照渡し(&)をしています。
 +
メンバー変数をたとえば <tt>this->name</tt> と参照することもできます。
 +
(ちなみに、上のオペレータ定義はポインタの値をNodeクラスから持ち出す「不適切なコード」です。実際にはこのようなコードをかくことは無いでしょう。)
 +
 
 +
 
 +
==STL==
 +
STLとは Standard Template Library の略でC&#43;&#43;が標準でサポートするデータ構造ライブラリです。
 +
使えるデータ構造がある場合は積極的に使いましょう。逆に、標準でサポートされないライブラリはあまり使わないようにしましょう。
 +
 
 +
* [http://www.cplusplus.com/reference/algorithm/ C&#43;&#43;.comのSTL解説]
 +
 
 +
===できる限りstringクラスを使う===
 +
単純な文字列でも、できる限りstringクラスを使いましょう。自分で<tt>char*</tt>を使うのを避けましょう。以下は、それでも<tt>char*</tt>を使いたい人に向けたヒントです。
 +
 
 +
* 文字列は文字数+1の長さを宣言する。最後のnull文字分を忘れないように。
 +
* 文字列のメモリはcallocで確保する。callocはmallocした後に0で初期化するのと同じです。
 +
* strncpy, strncmpを利用する。strcpyやstrcmpはnull文字が出てくるまで処理を続けます。できる限り、処理する長さを自分で管理してください。
 +
 
 +
===listはリスト、vectorは配列===
 +
STLでは、vectorクラスでも push_back 等の動的なサイズ変更が可能ですが、これははじめから余分にメモリを確保してあるためで、むやみに挿入や削除をすると効率が悪くなります。
 +
 
 +
===イテレータを使う===
 +
STLにおいて配列、リスト、集合などのクラスはイテレータでスキャンすることができます。ポインタを用いて実装されています。
 +
<pre>
 +
  vector<int> V;
 +
  ...
 +
  for(vector<int>::iterator I = V.begin(); I != V.end(); I&#43;&#43;)
 +
    cout << *I << ' ';
 +
</pre>
 +
ここで終了条件が <tt>V.end()</tt>になっていることに注意してください。beginは最初の要素を返すメソッドですが、endは最後の要素の次を返すメソッドです。
 +
 
  
 
==メモリ管理==
 
==メモリ管理==
 
===コンストラクタとデストラクタ===
 
===コンストラクタとデストラクタ===
C++のクラス定義では、例えば以下のような記述をします。
+
C&#43;&#43;のクラス定義では、例えば以下のような記述をします。
 
<pre>
 
<pre>
 
class list_node;
 
class list_node;
Line 101: Line 241:
 
基本データ型のnew/deleteは、基本的にmalloc/freeと同じですが、void*しか返さないmallocに比較して型キャストの必要がありません。
 
基本データ型のnew/deleteは、基本的にmalloc/freeと同じですが、void*しか返さないmallocに比較して型キャストの必要がありません。
  
ですから、C++らしいプログラムを心がけるには、常にnew/deleteを使うと良いでしょう。
+
ですから、C&#43;&#43;らしいプログラムを心がけるには、常にnew/deleteを使うと良いでしょう。
  
 
; 働きの全く同じ二つの例
 
; 働きの全く同じ二つの例
Line 108: Line 248:
 
delete[] cArray;
 
delete[] cArray;
  
char** cArray2 = (char**) malloc(sizeof(char)*10);
+
char** cArray2 = (char**) malloc(sizeof(char*)*10);
 
free(cArray2);
 
free(cArray2);
 
</pre>
 
</pre>
  
 
===Garbage Collection===
 
===Garbage Collection===
Javaとの最大の違いは、メモリ管理を自分で行う点です。これは単に「遅れている」という訳ではなく、実時間プログラミングのようなGarbage collector(GC)が致命的となる場合でも用いられる「汎用性」と捉えられます。
+
Javaとの最大の違いは、メモリ管理を自分で行う点です。これは単に「遅れている」という訳ではなく、実時間プログラミングのようなGarbage collector(GC)が致命的となる場合にも使える「汎用性」と捉えましょう。C&#43;&#43;の人が GC を使う際には [http://www.boost.org/ Boost Library] にある boost::shared_ptr を使うのが良いでしょう。
メモリ管理を放棄したい人に薦めるとしたら、C++で非常に有名なのが[http://www.hpl.hp.com/personal/Hans_Boehm/gc/ Boehm GC]です。
+
 
 +
* [http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/shared_ptr.htm Shared_ptr] @Boost
 +
 
 +
Shared_ptr の仕組については、Scott Myersの教科書"Effective C++"を読んでください。私自身が昔Cppプログラマだった頃は [http://www.hpl.hp.com/personal/Hans_Boehm/gc/ Boehm Collector]をよく使いました(笑)。

Latest revision as of 14:22, 4 October 2011

Wiki Top Up one level レポートの書き方 Arita Laboratory

Contents

"C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off."

Bjarne Stroustrup

[edit] C++プログラミング

C++はC言語の完全な上位互換ですが、オブジェクト指向型という点でC言語とは全く違うプログラミング思想に基づいています。簡単に言うと、JavaはC++の(思想の)利点を活かせるように新開発された言語です。

このページでは、C++の基本原則を書いています。より細かいプログラミングテクニックやサンプルプログラムがTipsのページにもあります。

[edit] マクロ, inlineの利用

C言語でマクロが果たしていた役割を、C++ではinline関数を用いて実現できます。 関数定義を

inline int cmp(const char& x, const char& y)
{ if (x < y) return -1; else if (x > y) return 1; else return 0; }

と書いておくと、cmp(x,y)という呼び出しはコンパイル時に全てソース中に展開されます。 実行ファイルが大きくなるのは欠点ですが、処理速度を犠牲にせずに可読性が上がります。

[edit] ヘッダーファイル

利用する関数の型情報は.h.hhという拡張子を持つヘッダーファイルに記述し、それをプログラムの先頭で読み込みます。一番有名なヘッダーは<stdio.h><iostream>でしょう。

#include <stdio.h>

iostreamも以前は拡張子(.h)を付けてインクルードしていたのですが、標準化の際に付けないことが決まりました。なので以下のように書いてください。

#include <iostream>
using namespace std;

二行目のusing以下は、iostreamで定義される標準入出力 std::cin, std::cout を、std::というネームスペース名無しで利用するために記述します。 C++でCの入出力を使うには

#include <cstdio>

としてください。

ヘッダーファイルはクラスを定義するソースプログラム毎に用意します。ディレクトリ内に.c.cpp等のファイルと混在してくると面倒です。例えばinclというディレクトリを作り、その中にヘッダーを集めておくと便利でしょう。(コンパイル方法は後述。)

各ヘッダーファイルは、プログラム中で複数回読み込むとエラーになります。ヘッダーファイルの先頭に

#ifndef LIST_H
#define LIST_H
#include "iterator.h"
  :
#endif

のように、ifndefのおまじないを入れておきましょう。

[edit] ファイルの入出力

C++では入出力にオペレータ<<, >>を使います。

与えられたファイルを全て結合して表示する cat.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

int main(int argc, char *argv[])
  {
    vector<string> lines;
    string line;
    for(int i=1; i < argc; i++) {
      //argv[0]には実行ファイル名が入っている
      ifstream fin(argv[i], ios::in);
      if(!fin) {// 必ずエラーチェックを入れる
        cout << "Error: cannot open file(" << argv[i] << ")" << endl;
        exit(1);
      }
      while(getline(fin, line))
        lines.push_back(line);
      // fin.close(); ファイルはクローズしなくて良い。
    }
    for(vector<string>::iterator itr=lines.begin();
        itr != lines.end(); ++itr)
      cout << *itr << "\n";
  }

これは STL を使うプログラム例なのでファイルの中身をすべて vector の中にコピーしています。 (ファイルの内容を出力するためだけなら、こんなことをしてはいけません。)

[edit] クラス定義

クラスは以下のように定義します。

class class名 {
private:
   // 外からアクセスできない変数やメンバー関数
public:
   // 外からアクセス可能な変数やメンバー関数
};

C言語における構造体との主な違いはアクセス制限です。

[edit] クラスと構造体の違い

以下のようなC構造体を考えましょう。

struct list {
  char* name;
  struct list* next;
  struct list* prev;     
};

構造体を初期化するには、構造体用のメモリと名前のメモリをそれぞれ malloc しなくてはなりません。

  ...
  struct list *p;
  char* sampleName;
  ...
  if ((p = (struct list *) malloc(sizeof(struct list))) == NULL) {
    printf("malloc error\n"); exit(EXIT_FAILURE);
  }
  if (p->name = (char*) malloc(sizeof(char)*20)) == NULL {
    printf("malloc error\n"); exit(EXIT_FAILURE);
  }
  strcpy(p->name, sampleName);
  ...

これでは全てのユーザが構造体の中身を熟知していないと使えません。 とりわけ、nameに malloc が必要なことや、20文字で十分かという判断は面倒です。 そこでC++ではこれをクラスにします。

public class Node {
  string name;
  Node* next;
  Node* prev;

public:
  Node(string str) : name(str) { prev=next=NULL; } //コンストラクタ
  ~Node() {} //デストラクタ
  ...
  string getName() { return name; }
};

コンストラクタが、構造体の malloc に対応します。 Cにおける char* 型はできる限り STLの string で置き換えましょう。こうすると、文字列の長さを気にしなくてよくなります。 実際、上のクラスでも 20 という定数や malloc が消えています。

[edit] 自分自身を参照するthis

クラスの中で、自分自身を参照するには this というポインタを使います。 つまり実体を参照する場合は *this になります。 このポインタはコピーコンストラクタを定義する際に出てきます。

public class Node {
  ...
  Node& Operator=(const Node& x)
    { name = x.name; next = x.next; prev = x.prev; return *this; }
  ...
}

最後に return *this; としている点に注意してください。 自分自身を戻り値として返し、その際に参照渡し(&)をしています。 メンバー変数をたとえば this->name と参照することもできます。 (ちなみに、上のオペレータ定義はポインタの値をNodeクラスから持ち出す「不適切なコード」です。実際にはこのようなコードをかくことは無いでしょう。)


[edit] STL

STLとは Standard Template Library の略でC++が標準でサポートするデータ構造ライブラリです。 使えるデータ構造がある場合は積極的に使いましょう。逆に、標準でサポートされないライブラリはあまり使わないようにしましょう。

[edit] できる限りstringクラスを使う

単純な文字列でも、できる限りstringクラスを使いましょう。自分でchar*を使うのを避けましょう。以下は、それでもchar*を使いたい人に向けたヒントです。

  • 文字列は文字数+1の長さを宣言する。最後のnull文字分を忘れないように。
  • 文字列のメモリはcallocで確保する。callocはmallocした後に0で初期化するのと同じです。
  • strncpy, strncmpを利用する。strcpyやstrcmpはnull文字が出てくるまで処理を続けます。できる限り、処理する長さを自分で管理してください。

[edit] listはリスト、vectorは配列

STLでは、vectorクラスでも push_back 等の動的なサイズ変更が可能ですが、これははじめから余分にメモリを確保してあるためで、むやみに挿入や削除をすると効率が悪くなります。

[edit] イテレータを使う

STLにおいて配列、リスト、集合などのクラスはイテレータでスキャンすることができます。ポインタを用いて実装されています。

  vector<int> V;
  ...
  for(vector<int>::iterator I = V.begin(); I != V.end(); I++)
    cout << *I << ' ';

ここで終了条件が V.end()になっていることに注意してください。beginは最初の要素を返すメソッドですが、endは最後の要素の次を返すメソッドです。


[edit] メモリ管理

[edit] コンストラクタとデストラクタ

C++のクラス定義では、例えば以下のような記述をします。

class list_node;
typedef list_node* lnode;

class list_node {
private:
  list_node(const list_node&);
  list_node& operator=(const list_node&);
public:
  void* key;
  lnode list_pred;
  lnode list_succ;
  list_node(GenPtr x=0x0) : key(x), list_pred(0), list_succ(0) {}
  ~list_node() {}
};

この中でlist_node(GenPtr x=0x0)とあるのがコンストラクタ、~list_node()がデストラクタで、それぞれクラスのインスタンス生成、消去時に呼び出されます。コンストラクタ内ではクラス変数の初期化、デストラクタ内では必要なくなったポインタのnull化などをしておくと良いでしょう。(後者は無駄に思えるかもしれませんが、バグをなくすのに役立ちます。)

[edit] コピーコンストラクタと演算子のオーバーロード

上の例でprivate指定でアクセス制限されているのが、コピーコンストラクタlist_node(const list_node&)と演算子=の定義list_node& operator=(const list_node&)になります。 コピーコンストラクタは、

list_node x = 既に定義されているlist_nodeクラス;

と=をつけて初期化する場合と、関数にクラスを(参照渡しではなく)値渡しするときに実行されます。 また、演算子=のほうは、

list_node x,y;
x = y = 既に定義されているlist_nodeクラス;

と書かれたときに実行されます。上の例では、list_nodeクラスがユーザに無闇にコピーされることを防ぐためにprivate指定にしています。

[edit] new と delete

Cにおけるmalloc/freeと、new/deleteは同じではありません。 クラスのnew/deleteでは、コンストラクタとデストラクタが呼ばれることに注意しましょう。 基本データ型のnew/deleteは、基本的にmalloc/freeと同じですが、void*しか返さないmallocに比較して型キャストの必要がありません。

ですから、C++らしいプログラムを心がけるには、常にnew/deleteを使うと良いでしょう。

働きの全く同じ二つの例
char** cArray = new char*[10];
delete[] cArray;

char** cArray2 = (char**) malloc(sizeof(char*)*10);
free(cArray2);

[edit] Garbage Collection

Javaとの最大の違いは、メモリ管理を自分で行う点です。これは単に「遅れている」という訳ではなく、実時間プログラミングのようなGarbage collector(GC)が致命的となる場合にも使える「汎用性」と捉えましょう。C++の人が GC を使う際には Boost Library にある boost::shared_ptr を使うのが良いでしょう。

Shared_ptr の仕組については、Scott Myersの教科書"Effective C++"を読んでください。私自身が昔Cppプログラマだった頃は Boehm Collectorをよく使いました(笑)。

Personal tools
Namespaces

Variants
Actions
Navigation
metabolites
Toolbox