C++で文字列のSplit

C++でtsvっぽいファイルを読みたいなあと思ったんですが、
どうも標準ライブラリにSplitをする関数がないみたいなので、
自分で書いてみた。
getline()で読み込んだ一行に対して処理してます。
#で始まる行はコメントとして無視してます。

STLのコンテナのメモリ管理がどうなっているのかよく分かっていないのでどきどきです。

    std::list<std::string> bufs;
    std::string buf = "";
    std::string::iterator p = str.begin();

    if (*p == '#') return b;

    for(; p != str.end() ;p++) {
        if (*p == '\t') {
            bufs.push_back(buf);
            buf = "";
            continue;
        }else{
            buf += *p;
        }
    }
    if(bufs.size() > 2) {
         //...
    }

排他的論理和をつかったオブジェクトの交換

 x ^= y;
 y ^= x;
 x ^= y;

という感じで、二変数の値を入れ替えるのに排他的論理和を使うと一時変数を使わなくていいのですが、オブジェクトでやってみるとどうなるのかなあと思ってやってみた。

class test_obj{
    int i;
public:
    test_obj(int a) {i = a;};
    void show(){std::cout << i << "\n";}
};

int main() {
    test_obj x(0),y(1);
    x ^= y;
    y ^= x;
    x ^= y;

    x.show();
    y.show();
    
    return 0;
}

んな演算子ねえよ!! ってコンパイラに怒られました。
そりゃそーだなあ。なんかクラスの中身を無理矢理ビットとして演算するやり方はないかな。

共用体を使ってみる

というので共用体を使えば、出来るんじゃないかと思ってやってみた。

class test_obj{
    int i;
public:
    void set(int a) {i = a;};
    void show(){std::cout << i << "\n";}
};

int main() {
    union {
        test_obj x;
        unsigned char cx[sizeof(test_obj)];
    };
    union {
        test_obj y;
        unsigned char cy[sizeof(test_obj)];
    };
    x.set(1);
    y.set(0);

     for (int i = 0; i < sizeof (test_obj); i++){
        cx[i] ^= cy[i];
        cy[i] ^= cx[i];
        cx[i] ^= cy[i];
    }

     
    x.show();
    y.show();
    
    return 0;
}
1
0
[Enter キーを押すとウィンドウが閉じます]

やった! 上手くいった!
大分フールな感じだし、誰も幸せになれそうにないけど、上手くいってうれしい!

あと共用体の中で宣言するオブジェクトのクラスは、コンストラクタを持っているとコンパイルエラーが出ることがわかった。

配列

オブジェクトの配列も通常の配列と同様に宣言する。={..}の中でコンストラクタ関数を呼び出すことで初期化も可能。
コンストラクタ関数の引数が一つの時は引数を並べたものを渡せばいい

class-name array-name[array-length] = {class-name(a1,a2..), ...}//引数が複数あるとき

class-name array-name[array-length] = {a,b,c,}//引数が一つの時

class-name array-name[array-length1][array-length2]  = {...}
//二次元配列
//初期化用に与える値は多次元配列でも一次元で渡す

cの配列って2×3の配列aがあったとき、メモリ上にはa[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]という感じで順番に並んでいるというのを、すっかり忘れてた。なんか多次元配列って、ポインタの配列だとばかり。

オブジェクトのポインタ,thisポインタ

構造体と同様メンバには->でアクセスする。オブジェクトの配列へのポインタをインクリメントした場合、次の要素を差すポインタになる。
クラス内でメンバを呼び出すときは暗黙的に呼び出し元のオブジェクトを差すthisポインタが参照されている。
なんとなくこのあたりの感覚はjavascriptに似ている気がする。

newとdelete

C++ではメモリを割り当てる方法として、mallocとfreeの代わりに、より安全なnewとdeleteという演算子が用意されている。

p-var new type;
p-var new type(initial-value);//initial-valueを初期化
delete p-var;

mallocと比べたときの利点は以下

  • new演算子はオブジェクトを格納するのに十分なメモリを自動で割り当てる。mallocのように自分で指定する必要がない。
  • new演算子は指定した型のポインタを自動で返す。mallocの用に明示的にキャストする必要がない。
  • newもdeleteもオーバーロードできる。
new,deleteを使って一次元配列を扱う
p-var new type[size];//動的に割り当てた配列の初期化は出来ない
delete [] p-var;

なんで配列をdeleteするとき [] を付けなくちゃいけないんだと思ったけど、つけないと配列の先頭の要素だけが解放されるからみたいだ。

参照

特殊なポインタで、自動的に間接参照され、参照先のオブジェクトと同義で使うことが出来る。暗黙のポインタ。関数へ引数を渡すときにポインタを渡す代わりに参照で渡すと、関数の中で通常の変数のように扱うことが出来る。
あるいは関数から値を返すときに参照で渡すことも出来る。

samp &f(samp &x){...}

でsampクラスの参照を渡して、その参照を返す関数になる。
参照とポインタの違い

  • 他の参照を参照できない
  • 参照のアドレスは取得できない
  • 参照の配列を作成できない
  • ビットフィールドを参照できない
  • クラスのメンバ、戻り値、関数引数以外では、初期化しなければならない。
class test{
    int i;
public:
    test(int a){i = a;};
    ~test(){cout << "destructer is called";};
};

test_c test_r(test &a){
    return a;
}


int main(){
    test a(112);

    test_re(a);
    return 0;
}

という感じで&付けないで宣言している関数で参照されている変数を返値にしても、参照先の値がコピーされて返されているみたい。
でデストラクタが二回呼ばれた。

参照の返し
class safe_array{
    int *p;
    int raw,column;
public:
    safe_array(int r,int c){
        p = new int [r*c];
        raw = r;
        column = c;
    };
    ~safe_array(){
        delete p;
    }

    int &put(int i,int j);
    int get(int i,int j);
};

int &safe_array::put(int i,int j){
    if(i < 0 || i>= raw || j < 0 ||j >= column){
        exit(1);
    }
    return p[i*raw + column];
}

int safe_array::get(int i,int j){
    if(i < 0 || i>= raw || j < 0 ||j >= column){
        exit(1);
    }
    return p[i*raw + column];
}

int main(){
    safe_array a(3,2);

    a.put(1,1) = 2;
    cout << a.get(1,1) << "\n";
    a.get(100,100);
    return 0;
}

境界チェック機能付きの二次元配列ですが、参照を返すことで、返された参照に直接値を代入できるという、楽しげな事が出来る。ここだと配列の要素を参照として返すことで、呼び出しもとルーチンで配列に値を代入している。何か便利な気分。

独立参照
    int x = 100;
    int &ref = x;

    ref = 10;//xに10が代入される

利点は殆ど無いそうだ。
まとめると参照は

  • 仮引数参照
  • 独立参照
  • 返値の参照

の三つがある。

:第三章 クラスの詳細

クラスの詳細。だんだんこう、C++って考えることが多くて大変! という気分になってきた。

オブジェクトの代入

myclass a,b;
a = b;

とすると、aにbがコピーされる。javaとかと違って変数が同じクラスを参照するわけではなく、配列なども含めたメンバ変数も全部コピーされる。
メンバ変数にポインタを持ち、デストラクタでそれをフリーするようなクラスでオブジェクトの代入が行われた場合、同じ領域が二回freeされるのでエラーがおきる。

関数へのオブジェクトの引き渡し

オブジェクトを引数として渡した場合、デフォルトでは全て値渡し=オブジェクトのコピーが渡される。関数内で引数のオブジェクトに変更を加えても、外には影響がない。
関数から抜けるときに引数のコピーのオブジェクトのデストラクタが呼び出されるので、デストラクタにfree()がある場合、オブジェクトの代入と同様の問題が発生する。(デストラクタが二回呼び出される)

class myclass{
     char *s;
   public:
     myclass(char a){s = (char *)malloc(strlen(a));strcpy(s,a);}
     ~myclass(){free(s);}
};

   myclass f(myclass a){
      return a;
   }//ココでm0のコピーのデストラクタが呼び出され、sが解放される。

main(){
   myclass m0("test");
   f(m0);
   return 0;
}//ココでm0のデストラクタが呼び出され、sをもう一度解放しようとしてエラーになる。

という感じで良いのかな。今適当にかいたから動かなそうなコードだけど。

オブジェクトのポインタを渡せば、関数内でオブジェクトを弄ることが出来る。

関数からのオブジェクトの返し

関数からオブジェクトを返すとき、オブジェクトを呼び出し元に返した後(オブジェクトがコピーされて返される)、オブジェクトは破棄される。その際デストラクタが呼び出されるので、以下略。

例の3.3.2でデストラクタが三回呼び出されると書いてあるけど、実行してみたら二回しか呼び出されなかった。

class samp2{
    char *s;
public:
    samp2() {s = '\0';}
    ~samp2() {cout << "free s\n";if (s) free(s);}
    void show() {cout << s << "\n";}
    int set(char *str);
};

int samp2::set(char* str){
    s = (char *)malloc(strlen(str)+1);
    if(!s){
        cout << "Memory Allocation Error\n";
        exit(1);
    }
    strcpy(s , str);
}


samp2 input(){
    char s[80];
    samp2 str;

    cout << "input string:";
    cin >> s;

    str.set(s);
    return str;//strが一時オブジェクトにコピーされ戻り値として元ルーチンに返される。
}//1.スコープから外れるときにstrのデストラクタが呼び出される

int main(){
    samp2 ob;
    ob = input();//2.一時オブジェクトのデストラクタが呼び出される
    ob.show();
    return 0;
}//3.obのデストラクタが呼び出される。


勿論エラーにはなったけど。どうも2.が実行されていない気がする。
なんだろう、コンパイラの違いかなあ。
そもそも、一時オブジェクトが何かよくわからない。

フレンド関数

class A{
    int private;
  public:
    friend void f(A a);
}

void f(A a){
    return a.private
}

という感じで、あるクラスのフレンド関数は、そのクラスの非公開メンバにアクセス出来る。別のクラスのメンバ関数をフレンド関数にすることも出来る。friend int B::f(A a)とかそういうような。
今一用途が分からないけど、役立つ理由は以下の三点らしい。

また後のページで言及があるみたい。

第二章 クラスの概要

コンストラクタ、デストラク

class-name(param-list); //コンストラクタ
~class-name(); //デストラクタ

それぞれオブジェクトが作られたとき、破棄されるときに呼び出される。コンストラクタでmallocした変数を、デストラクタでfreeすれば幸せになれるみたい。
途中の例題で出てきたctimeのclock()の返値が何時も0になる。謎。

継承

class derived-class-name : access-specifier base-class-name{
....
}

access-specifierで指定された(例題だとpublic)属性の要素が子クラスから見えるらしい。

クラス、構造体、共用体の関連

  • 構造体

クラスとの違いが、デフォルトpublicか、privateかだけ?
だとしたら存在意義がよく分からない。

  • 共用体

共用体がメンバ関数を持つ必要が分からない……。共用体を要素に持つクラスを作れば良いんじゃないかと思ってしまう。
同じメモリ位置を共有するのは多分メンバ変数だけ。
例題のdouble値に含まれるバイナリビットパターンをバイト単位で表示するプログラム。最初見たときよく分からなかったのでメモ。

union bits{
    bits(double n);
    void show_bits();
    double d;
    unsigned char c[sizeof(double)];
};

bits::bits(double n){
    d = n;
}

void bits::show_bits(){
    int i,j;

    for(j = sizeof(double)-1; j>= 0 ; j--){
        cout << "バイト単位のビットパターン" << j << ":";
        for(i = 128; i; i >>= 1)
            if(i & c[j]) cout << "1";
            else cout << "0";
        cout << "\n";
    }
}

unsined char は1byteなので、doubleをunsigned charの配列として見ることで、バイト単位でアクセスすることが出来る。複数の型が同じメモリ位置を参照する共用体の特徴を使っている。
各バイトのビットパターンを見る方法。
i = 128は二進数で10000000。i >>= 1は1bitずつ右にシフトするのでfor文を通じてiは

10000000
01000000
00100000
.....

というふうに変わっていく。c[j]とそれぞれの論理和をとることで、各桁のビットが1か0かがわかる。c[j]が10101111だとすると、10000000との論理和は10000000 != 0という感じで8桁目が1であることが分かる。以下同様に8桁全部を見る。

練習問題2.5.2

共用体クラスを使って、short intの上位バイトと下位バイトを入れ替える。

union swap_byte{
    swap_byte(short int i);
    void swap();

    short int integer;
    unsigned char c[2];
};

swap_byte::swap_byte(short int i){
    integer = i;
}

void swap_byte::swap(){
    unsigned char a;
    c[0] = a;
    c[0] = c[1];
    c[1] = a;
}

インライン関数

関数定義の前にinlineを付けると、関数がインライン展開され関数呼び出しのオーバーヘッドが無くなって早くなる。inlineは要求であり、コンパイラは無視する可能性がある。
基本的にはオーバーヘッドが無視できない小さな処理をインライン関数にする。

自動インライン化

クラス定義の中で関数の処理を書くと、自動でインライン化される。
一般的な使用方法はコンストラクタやデストラクタの定義を暮らす定義の中で行う。
性能にはあまり影響が無くても、短い関数はクラス定義ないで書くことがある。

二項係数

二項係数を求めるプログラムを、再帰を使ったのと、公式を使ったの二通りで作ってみた。

long product_n2k(long n,long k){
    if (n == 0){
        return 1;
    } else if (n == k) {
        return n;
    }else {
        return n * product_n2k(n - 1, k);
    }
}

long bi_coefficient(long n, long k){
    if (k == 0)
        return 1;
    else
        return product_n2k(n, n - k + 1) / product_n2k(k, 1);
}

公式\Pi^{k-1}_{i=0}(n-i)/k!を使ったバージョン。product_n2kはnからkまでの数をかけた値を返す関数。ここで再帰つかってるじゃんという話もあります。k = 0のときどうすればいいのか分からなくなったので、場合分けしました。if文使わないでうまいこと書けないのかなあ。

long bi_coefficient_r(long n,long k){
    if (n == 0 || k == 0 || n == k){
        return 1;
    }else {
        return bi_coefficient_r(n-1,k-1) + bi_coefficient(n-1,k);
    }
}

パスカルの三角形的に再帰使って書いたバージョン。うーん、簡潔だ……。
(n k) = (n n-k)になることを使ったら計算量が減る気がする。

int main_b(){
    long i,j;
    long a,ar;
    for(i = 0; i < 20 ;i++) {
        for(j = 0; j <= i; j++) {
            cout << i << "," << j << ":";
            cout << (a = bi_coefficient(i, j)) << " ";
            cout << (ar = bi_coefficient_r(i, j)) << " ";

            if ( a == ar) {
                cout << ": OK\n";
            }else{
                cout << ": NG\n";
            }
        }
    }
}

でちゃんと同じ値になるかチェックしてみた。n=12までは良いんですが、n=13ぐらいから、答えがマイナスになったりして、合わなくなってくる。計算途中で大きすぎる値が出るのかなあ。
あとn=100やってみたら、途中でFloating Point Exceptionはいて終了。階乗の力すごい。