PPPUC++#5

Objects, Types, and Values

"Fortune favors the prepared mind." パスツールの言葉だったんだ!(そんなのばっか)。運がイイ!って何も努力してないってことじゃない。
結構好きな言葉です。静的型付言語では宣言がこの prepared mindになるのかな。定義か?

3.1 Input
  • Hello, World は入力が無い。同じこと吐くだけでつまらん。
    • よーし入力やっちゃうぞー
  • 入力のためには、読み込む場所が必要。
    • オブジェクトと呼ぶことにする。
  • オブジェクトはメモリ上の場所と型をもつ。例えるなら箱(ただし決まったものしか入らない電池ボックスみたいなの)
  • 名前付きのオブジェクトは「変数」と呼ぶ。
  • string変数を使った例とか。
  • (ほら、今いれた文字が返ってきましたよとか << は連結できますよとか、こういったことに感動を覚えなくなってきている自分が悲しい><)
  • 繰り返しは避ける。コードが増えれば増えるほど、エラーが入り込む余地が出てくる(空のプログラムにはエラーの入りようがないですねたしかに)。
3.2 Variables

特記事項とかはなし。

  • さっきの話の繰り返し。変数データを保持するところがオブジェクト。名前がついたオブジェクトが変数。変数は型を持っていて、云々
  • 変数の定義時に初期値を与えることができる。
  • 多数の型があるけれど、とりあえずは int, double, string, char, bool の5つでいんじゃね
3.3 Input and type
  • cinは型センシティブなんで、入れる値間違えてハマらないように(うーむ)
  • 注)cin ストリームのデリミタは改行ではなく空白文字です。 とか

このあたりはたるいなー

3.4 Operations and operators
  • 型は、値の種類とともに、もう一つ、可能な操作(演算)とその意味も規定する
  • いかな強力である型システムであっても、その数値の意味までは規定できない。つまり年齢 -100 才と入力する事を防ぐことはできない、とかいう話。いやunsigned にすりゃあ、、、というかそういう型(クラス)をつくりゃあ。。。というのはKYなのね
  • bool,char,int,double, string について可能な操作の表がある。けどほとんどの人は見なくても分かるだろうなー。とか。
  • よく考えたら string の subtraction ってあってもいいんじゃ? とか思った。C++哲学的によろしく無いのだろうか(いろんなsubがありえる+効率が悪い普段使わないコードを抱き込む)
  • 全ての演算子が揃っている分けではなくって一部は関数として用意されている。

余談だけど、string.swap()って知った。今まで愚直にやってた。。。

Try This
void test_int()
{
    cout << "Please enter a integer value: ";
    int n;
    cin >> n;
    double d = n;
    cout << "n == " << n
         << "\nn+1 == " << n+1
         << "\nthree times n == " << 3*n
         << "\ntwice == " << n+n
         << "\nn squared == " << n*n
         << "\nflooring of n/2 == " << n/2
         << "\nreminder of n/2 == " << n%2
         << "\nsquare root of n == " << sqrt(d)
         << endl;
}

一応 sqrt(double)に渡すために int -> double の転送せい、と書いてあったのでそうしているけれども、暗黙の型変換で不要だよね。

3.5 Assignment and initialization

普通に代入とかはまあ見た通りだからいいけど a=a+b だけはキモいよねー、とか。僕に教えてくれた人は<-と思え、と教えてくれたけど、そういえばRとかはそのまんま。
あと代入と初期化は違うんだよ、という話。代入は前の値の消去から入るよね、という当たり前といえば当たり前の話。しかし、このままの説明だと、なんもしなくても勝手に代入された古い値を開放してくれるかのように見えて微妙な気もする。まあどうせcopy assignmentあたりで再掲されるのかな。

3.5.1 Anexample: detect repeated words

スペルチェッカなんかでもよくある、繰り返し発見器。

#include <iostream>
#include <string>

using namespace std;

main()
{
    string previous = " "; // previous word; initialized to "not a word" 
    string current;    // current word
    while (cin >> current) { // read a stream of word
        if (previous == current) // check if the word is the same as last
            cout << "repeated word: " << current << '\n';
        previous = current;
    }
}

代入の使用例なんだそうな。"not a word"はスペースになってる。デリミタが空白文字だからねー。なんだけど別に空文字でもいいんちゃうか、とか思ったり。

  • プログラムのフローを理解する一つの手として、”コンピュータを演じる”というのがある。ってそういえば開発でも各ブロックになりきってロールプレイするのありますね。変数の箱なりを用意して、一行ずつコンピュータになりきって演じる、っておもしろそう。
TRY THIS

ということで早速ロールプレイやってみたけど結構虚しい。。。

TRY THIS

ASCIIでcase sensitive だからーという話と、3以上繰り返しがあった場合まとめてくれねーよ、という話かな。まあ

    string current;    // current word
    bool   prevhit = false;    // is previous word hit?
    while (cin >> current) { // read a stream of word
        transform(current.begin(), current.end(), current.begin(), (int (*)(int))tolower);
        if (previous == current && !prevhit) { // check if the word is the same as last
            cout << "repeated word: " << current << '\n';
            prevhit = true;
        } else prevhit = false;

こうすりゃいいんだろうけど。しかしです、tolower とかみんどい。

3.6 Composite assignment operators

複合代入系, ++とか+=とか。

3.6.1 An example: find repeated words

に何ワード目か?を追加するだけ。特記事項は例によってなし。

3.7 Names

有効な名前とかの話。この当たりってプログラマな人にとっては当たり前すぎるけど、初めての人にとってはどう感じるんだろう。
_(アンダースコア)が前についている名前はシステムコードとか機械生成された名前だからつかうなとか。上の例とかだと使っていいのか微妙だけど_tolower()ならオーバーロードされていないのでそのままtransformの引数に渡せるんだけどやっぱ使っちゃダメかな。。。
大文字小文字は区別されるので気をつけろっていうけど、実際のところ Variable と variableを同じコンテキストで使うとか狂気の沙汰なので、よーく考えてみるとcase-insensitiveでもいいような気もしてくる。しかし更に考えてみると、エラーで弾かれなくなったらそういったうっとーし誤用やTYPOが残ったままになるし、国際化を考えると何を同じとみなすのかという面倒くさい議論になる。やはり美しさ、という基準からsensitiveのほうがbetterなのかな。
あとは予約語の話と、ライブラリで使われるような名前は使えるけどやめとき、という話と、わかりやすい名前を心がけるべし、という話。慣習として使う短い名前は良いとか。loop index のiとか(これはiterateの略とかindexの略とかいろいろ言われてるみたいだけど、数学では馴染み深いよね)、サブルーチンのパラメタxとか。
あとはconventionとして (彼らの流儀では) 単語間を _で区切るとか, camelCaseは使わないとか、全部大文字はマクロ定義っぽいから使わないとか、ユーザ定義型は先頭Capitalizeとか、紛らわしいLエルとl小文字エルとかそういうの使わないよーにとか。そんな話。
良かったハンガリアン推奨されなくて。

TRY THIS

大文字小文字とかのTYPOコンパイラをいじめてみるのパート。gcc賢いなーということを痛感する瞬間。自分でエラー処理書く事考えると、ちゃんと全エラーリカバリしてくれるのってすごいなーと思う。

3.8 Types and objects

型という概念について。

  • 定義: 代入可能な値の集合+演算の集合
  • オブジェクトとはある型の値を保持しているメモリのこと
  • 値とは型に応じて解釈されるメモリ上のビット列のこと
  • 変数とはオブジェクトに名前をつけたもの
  • 宣言とはオブジェクトに名前をつける構文
  • 定義とはオブジェクトのためにメモリを確保しておく、という宣言

んーーーまんま。

  • 結局メモリの中にあるものというのは単なるビットの集まりでしかなく、そこに意味を与えるのは我々人間であるということ

プログラミングとは電子を材料にしたオママゴトである-- tkuro

3.9 Type safety

型安全の話。

  • 例えば未初期化変数は型安全でない
  • すべてが型安全であることが理想だけれど、現実はそう甘くない
  • しかしなるべくなら静的な型安全を目指せ!
3.9.1 Safe conversions

型安全な変換

  • 互換性のある型同士の変換
  • char -> int , int -> double 、表現力が大きくなる方向→ 暗黙の変換
  • int -> double の暗黙変換のおかげでintミックスな演算が簡便に書ける

しかし64ビットマシンだと結構簡単に

main()
{
    unsigned long long im_going= -1;
    double  d = im_going;
    cout << hex ;
    cout << im_going << endl;;
    cout << d << endl;
    long long welcomehome = d;
    cout << welcomehome << endl;
}

// [OUTPUT]
// ffffffffffffffff
// 1.84467e+19
// 8000000000000000

とかできる。稀なケースですが。

3.9.2 Unsafe conversions

非型安全な変換

  • 型安全な変換はプログラマにとっても「あれは・・・・いいものだ!!」なのですが、C++はひっじょーに厄介な型非安全な(暗黙)変換もやっちまいます
  • 要するに int -> charなどの小さくなる方向の変換(先程の稀なケース、もこれに入るのかな)。しかもデフォルトでは警告してくれない!(gccだと-Wconversionで警告してくれます。他にfloat -> double の -Wdouble-promotionはあるんだけどこっちは古いマシンでの「あー勝手にdoubleになってるー」という効率の問題とのこと)
  • なんでこうなってるかというとやはりCの亡霊。小さなプログラムで経験豊富なプログラマというコンビならば無問題。
  • 後の章でもやるけど、チェックを入れるのが一応の対応策。
Drill

手紙を書くプログラムに変更するらしい。なるほど。110歳超えで生きてる人もいるかもじゃん、とか、70歳超えても現役かも知んないじゃん、とか思ったりしながら書いたりした。

Review

Excercises

ソートは3つまではif thenでやるのが最速、とかいうの思い出した。

void p( int a, int b, int c)
{
    using namespace std;
    cout << a << "," << b << "," << c << endl;
}

main()
{
    int a,b,c;
    std::cin >> a >> b >> c;

    if (a < b)      // a <<< b
        if (b < c)
            p( a , b , c );
        else if (a < c)
            p( a , c , b );
        else
            p( c , a , b );

    else if (c < b) // b <<< a
        p( c , b , a );
    else if (c < a)
        p( b , c , a );
    else
        p( b , a , c );
}

SICPだと結構早い段階から "$???を指定したcoinで表すと?"みたいなのがあったけど、さすがにc++だとそんなのはもう少し構文覚えないとムリ(反対にcoinを指定して合計金額の問題があったw)。ということがわかったりした。