C++ における配列

疑問に思うことが多いので調べなければならない。

配列とは

メモリ上にある型が連続して保存さらている一つのデータの塊。 配列が定義された変数には配列の要素のポインタが保存されている。

下では charint の配列を定義している。
定義した変数をそれぞれ出力してみると結果が違った。

int aa[] = {1, 2, 3, 4};
cout << aa << endl;  // => 0x7ffeefbac8e0
cout << *aa << endl; // => 1

cahr a[] = "HELLO"; // => char a[] = {'H','E','L','L','O'} と同じ。
cout << a << endl; // => HELLO
cout << &a << endl; // => 0x7ffee376f8b0 

int 型では予想通りポインタが出力されたが、char では文字列、つまり最初の要素から最後まで表示された何故なのか気になって調べた。

それは 全ての文字列を出力するために<< 演算子* char 型にオーバーロードされているからです。char へのポインタから文字列の最後を表す '\0' までの全ての文字列を出力します。

参照:http://www.cplusplus.com/forum/beginner/21/#CH_i48

<< 演算子のパラメータ s

Pointer to a null-terminated sequence of characters (a c-string).

参照:http://www.cplusplus.com/reference/ostream/ostream/operator-free/

実験

以上わかったことを踏まえて実験してみる。 内容は文字列配列をインクリメントする。

char *a = "hello_world";
cout << ++a << endl; // => ello_world

これは、さっきおさらいした通り変数 a には最初の要素へのポインターが入っているので、そのポインターをインクリメントしている。

最初の要素の pointer + 1 は、次の要素の e のポインタであるので、そのポインタから \0 までの要素が出力されるといったところである。

正直、で?ってのが感想なんだけどこれが役に立つ場面がある。

例えば 2進数を別の進数に置き換えたい時らしいですね。 以下では 2進数 の文字列配列を受け取って 10進数を返しています。

unsigned int binToUInt(const char *ptr)
{
    unsigned int val = 0;
    while( *ptr != '\0' ) { // => 文字列の最後の要素の直前まで実行するループ
        switch( *ptr++ ) { // => *ptr++ は文字列リテラルをインクリメントして、インクリメントする前の文字列の最初の要素
            case '0':
                val *= 2; 
                break;
            case '1': 
                val = val * 2 + 1;
                break;
        }
    }
    return val;
}

これは余談なのですが、このコードを見たと時「美しい・・・!」と思いました。
特にそう思ったのは、2進数から10進数への変換をになっているswitch文のなかの処理がたった2つしかないことです。

自分なりの解釈でbinToUint 関数の説明をします。ここではわかりやすく例として文字列 "1010" のポインタを引数にとった場合と仮定しています。
1. 10進数の値を示す val 変数を定義し、0でイニシャライズ。
2. 文字列の最後の要素の直前まで実行するループ (文字列の最後の要素は \0 = NULL) で*ptr 変数(中身は"1" これは "1010" 文字列配列の最初の要素 )評価される。
3. \0 ではないので処理が実行。
4. switch の評価の *ptr++ で文字列のポインタをインクリメントして、返り値としてインクリメントする前のポインタの値が返る。ここでは "1" が返っている。

C++ における文字列

参照: char* vs std:string vs char[] in C++ - GeeksforGeeks
Character sequences - C++ Tutorials

文字列とは

文字列は「文字の連なり」である。
char 型の要素の配列で文字列を定義することができる。

f:id:bitsukun75:20190403151123p:plainf:id:bitsukun75:20190403151127p:plain

null を意味する \0 文字 によって文字列の最後が表されており、灰色になっている部分は未定義の要素を表している。

Ascii Table - ASCII character codes and html, octal, hex and decimal chart conversion

(画像は Character sequences - C++ Tutorials より引用 )

文字列リテラル

文字列は以下のように一文字づつ配列の要素に割り当てることでも定義できるが、

char myword[] = { 'H', 'e', 'l', 'l', 'o', '\0' };  // => 一文字を表すときは ' (シングルクオート) を使う。

以下のように定義することもできる。

char myword[] = "Hello"; 

このダブルクオートに囲まれた "Hello" を文字列リテラルという。

文字列は配列なので以下のように値を代入することはできない。

char A[] = {'H', 'e', 'l', 'l', 'o', '\0'};
char B[] = "Hello";

A = {'A'}; 
A = "WORLD"

配列の要素になら代入できる。

char A[] = {'H', 'e', 'l', 'l', 'o', '\0'};
char B[] = "Hello";

cout << A << endl;
A[0] = 'W';
cout << A << endl;

char はポインタ?

shinzooon.hatenablog.com

C++における ++a, a++ の違い

いい加減覚えようと思いまして、記録します。 参照: Operators - C++ Tutorials

先に結論

++ 演算子前後の 変数の状態をインクリメントする」ということは共通で、違うのは返る値が「インクリメントされた後」なのか「インクリメントされる前」なのかの違い。

++a

++a の場合、つまり++ 演算子 が変数の前にある場合は、変数 a の状態がインクリメントされ、帰る値は 変数 a 内のインクリメント された後の 値。

    int a = 1, b = ++a;
    cout << "a is " << a << endl;
    cout << "b is " << b << endl;
    cout << "a == b is " << std::boolalpha << (a == b) << endl;

/* output 

a is 2
b is 2 <= 注目
a == b is true

*/

a++

++a の場合、つまり++ 演算子 が変数の前にある場合は、変数 a の状態がインクリメントされ、帰る値は 変数 a 内のインクリメント される前の 値。

    int a = 1, b = a++;  // <= 注目 
    cout << "a is " << a << endl;
    cout << "b is " << b << endl;
    cout << "a == b is " << std::boolalpha << (a == b) << endl;

/* output

a is 2
b is 1 <= 注目
a == b is false

*/

文字列をインクリメントしたらどうなるか

下では、文字列配列 a の一つ目の要素をインクリメントしています。

char *a = "HELLO"; // => c++ では 文字列リテラルは const で定義するべきなのでコンパイル時に depressed と警告される。
char *p = &a[0]; // => ポインタ変数 p に配列 a の 1 つ目の要素へのポインタを代入。
cout << *++p << endl; // => E

これはつまり下と同じですね。

char *a = "HELLO";
cout << a[0 + 1] << endl; // => E 

そのた

ループでは ++variable を使う。

Atcoder を解く(ABC001)

ABC001 を解く

c++ で解いてます。理由は先輩が c++ は最高の言語だというからです。

問題A

簡単な引き算なので省略

問題B

まだできてマシェん

#include <iostream>
#include <sstream>

using namespace std;
 
int main()
{
    float d;
    std::stringstream stream;
    cin >> d;
    d /= 1000;
    if (d < 0.1)
    {
        cout << "00" << endl;
    }
    else if (0.1 <= d && d <= 5)
    {
        d *= 10;
        if (d < 10)
        {
            stream << "0" << d;
            cout << stream.str() << endl;
        }
        else
        {
            cout << d << endl;
        }
    }
    else if (6 <= d && d <= 30)
    {
        cout << d + 50 << endl;
    }
    else if (35 <= d && d <= 70)
    {
        cout << (d - 30) / 5 + 80 << endl;
    }
    else if (75 <= d)
    {
        cout << "89" << endl;
    }
    return 0;
}

test_09.txt と test_32.txt だけ解けない!

問題C

この問題はやることが大きく分けて二つあるって、
①.与えられた角度を元に方角出すこと。
②.与えられた風程を元に風力階級を出すこと。
だと考えました。

それぞれ分けて考えていきます。

①のやーつ

考え方

問題の条件を見ると、風向きが本来の角度を 10倍した整数で与えられる とあったので「それをさらに10倍して最大で5桁の数字(例:360度 -> 36000)になるようにしよう」と思いました。なぜなら、そのほうが計算しやすいからです。

そしてそのあとすぐ、value に方位(N~NNWの16個)を持つ配列を用意しようと思いました。

    string direction[size];

    direction[0] = "N";
    direction[1] = "NNE";
    direction[2] = "NE";
    direction[3] = "ENE";
    direction[4] = "E";
    direction[5] = "ESE";
    direction[6] = "SE";
    direction[7] = "SSE";
    direction[8] = "S";
    direction[9] = "SSW";
    direction[10] = "SW";
    direction[11] = "WSW";
    direction[12] = "W";
    direction[13] = "WNW";
    direction[14] = "NW";
    direction[15] = "NNW";

で、次に方位を示すインデックスを返す関数を考えました。 その際ににまず、頭の中に方位を表すマップを想像しました。 すると、下の図の赤の線を基準線にして 0度 としたとき、N が 基準線を包含していることに気づきました。

f:id:bitsukun75:20190330184357j:plain

このままだと、例えば 358度も 5度も同じ Nであるので計算がしづらいので基準線に合わせるように方位マップを時計回りに回転させました。言い換えれば、基準線に Nの左端を を一致させました。上の画像で 基準線より左方向にはみ出していたNは 11.25 度分なので 11.25度時計回りに回転させます。

その時の頭の中の図が下で

f:id:bitsukun75:20190330190412j:plain

コードに置き換えたのが下です。adjust 関数全体が上の画像で行なっている内容を実装しています。

void adjust(int *a)
{
    *a *= 10; // 計算しやすいように10倍する。
    if (*a > 34875) // 基準線をN極の左端に置いたので、それに一致させるよう角度も変化させる。(360000 - 1125 = 34875)
    {
        *a -= 34875;
        return;
    }
    else
    {
        *a += 1125;
        return;
    }
    return;
}

あとは、先ほど用意した配列と adjust させた角度の関連付けるんですがここが一番悩みました。アイデアが浮かばなかったんです。

結論は「角度 x から 1 方位分の 11.25 度を n 回 引く。n を配列のキーとする。」で方位をあらわそうと思いました。例えば、与えられた風向きが 275度 であれば、275から11.25 を 13 回引けるので それを配列のキーとして指定すれば方位が出ると思いました。

上に対応する関数が下です。

int calDeg(int a, int size)
{
    int i, b;
    adjust(&a);

    for (i = 1; i < size + 1; ++i)
    {
        b = a - i * 2250;
        if (b < 0)
        {
            return i - 1;
        }
    }
    return 0;
}

以上を組み合わせて下を作り、サンプルテキストを流し込んでみたら上手いこと言ったので「①.与えられた角度を元に方角出すこと。」の要件は満たしました。

#include <iostream>
using namespace std;

void adjust(int *a)
{
    *a *= 10;
    if (*a > 34875)
    {
        *a -= 34875;
        return;
    }
    else
    {
        *a += 1125;
        return;
    }
    return;
}

int calDeg(int a, int size)
{
    int i, b;
    adjust(&a);

    for (i = 1; i < size + 1; ++i)
    {
        b = a - i * 2250;
        if (b < 0)
        {
            return i - 1;
        }
    }
    return 0;
}

int main()
{
    int deg, size = 16, n;
    cin >> deg;
    string direction[size];

    direction[0] = "N";
    direction[1] = "NNE";
    direction[2] = "NE";
    direction[3] = "ENE";
    direction[4] = "E";
    direction[5] = "ESE";
    direction[6] = "SE";
    direction[7] = "SSE";
    direction[8] = "S";
    direction[9] = "SSW";
    direction[10] = "SW";
    direction[11] = "WSW";
    direction[12] = "W";
    direction[13] = "WNW";
    direction[14] = "NW";
    direction[15] = "NNW";

    cout << direction[calDeg(deg, size)] << endl;
}

②のやーつ

よっし、次は②です。その前に風呂入ってきます。

詰まったとこ

負の数は true

C++ では 0, null pointer value, null member pointer value 以外は全て true で評価されるんですねー。

Javascript 触ってた癖で 0false だと思い込んでいました。( ;∀;)

4.12 Boolean conversions [conv.bool] 1 A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true. For direct-initialization (8.5), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false. (C++ 基準より引用)

証明

    int a = -1;
    if (a)
    {
        cout << "negative number is true" << endl; 
    }
    else
    {
        cout << "negative number is false" << endl;
    }

// => negative number is true

おことわり

Terms of service - AtCoder より 自分に著作権が属するもののみブログに表示しています。 よって問題文は表示しません。

printf について

ふと、printf() について気になったので調べたら知らないことがたくさんわかったのでメモします。

参考:

printf(3): formatted output conversion - Linux man page

printf() は関数で、第一引数に format charactor を受ける。% ではない普通の文字はそのまま output ストリームにコピーされる。conversion specification% の文字で行い conversion specifier で終わる。 この間では、左から順に0個以上のフラグ、オプションでfield widthprecisionlengthmodifier を指定することができる。

タイ一人旅予算

タイで一人旅してきました。貧乏旅のため予算を超えないように、毎日使った金額をメモしていたので予算がきになる人は参考になるかもしれません。

期間は 3/7~3/24 の18日の旅行。
最終日の 3/24 の午前1:00 に離陸しているので、実質は17日間の旅行。

使用した金額はなんと ฿ 20500 。 日本円にして約 71000円。 (3/24 レートの ฿ 1 = ¥ 3.47)

0日目 3/6

13:00 飛行機に乗り遅れ 28000円のロス。
20:00 check in

以降は全てバースで表記してあります。

1日目 3/7

使用金額

simカード 599
水 10
飯 45
切符 5
マッサージ 150
水 6
宿 200
洗剤とか 176
昼飯 45
寄付 20
飯 60
サソリ 80
ズボン 150
計 1596

ちなみに

旅行先輩の助言を受け両替はタイ現地で行いました。
正直に話すと両替についてこの助言を盲信してそのほかは考えず、日本円を握りしめて現地に到着しています。

両替した後になって本当に現地両替がお得なのか心配になって計算してみたが本当だった。疑ってすみませんでした。●っしーさん。

行動記録

その当時のぼくの行動を記録します。

あらかじめ、日本で15万を下ろしておきます。 そして、現地で 15万のうち、10万を両替して 26700バーツをゲットしました。両替額を全額にしなかったのは勘です。しかし、結果オーライで良かったです。なぜなら、タイ現地では使用するだけ両替するのがお得だからです。詳しくは下の結論に書きました。

当時のレートが下です。

f:id:bitsukun75:20190324175450p:plain

外貨両替票なんて初めてみたので現地からネットで調べました。

両替相場には「売り」と「買い」があります。
「売り」と「買い」は、銀行(両替所)側から見た行為です。
銀行・両替所側から見て、
外貨を払い出す行為=「売り」
外貨を引き取る行為=「買い」
通貨~両替の「売り」と「買い」って?

ほほう。
買いと売りがわかったので、次は日本円を日本で両替した方がいいのか、タイで両替した方がいいのか調べます。

①日本円→バーツに両替したい場合。

仮に現金で ¥10,000 を持っていたとして、
日本でバースと両替(バースを買ったら) ¥ 10,000 / 3.93 = ฿ 2,544

タイでバースと両替(円を売ったら) ¥ 10,000 * 0.267 = ฿ 2,670

฿ 2,670 - ฿ 2,544 = ฿ 67

タイで両替した方が ฿ 67 お得。

②バーツ→日本円に両替したい場合。

仮に現金で ฿ 10,000 をもっていたとして、
日本でバーツを円と両替(円を買ったら)
฿ 10,000 * 3.11 = ¥ 31,100
฿ 10,000 / 0.2964 = ¥ 29,640

¥ 31,100 - ¥ 29,640 = ¥ 1,460

日本で両替した方が ¥ 1,460 お得。

結論

タイに旅行するときは旅発つ前に日本円を下ろし、タイ現地で使用するだけのバーツを買い、残ったバーツは日本で売るとお得。

その後

ドムーアン空港すぐ横の電車乗ってバンコクまで行きました。

2日目

使用金額

朝飯 35
船 11
飯 60
アイスココア 20
スネークファーム 200
飯 40
石鹸 42
おやつ 36
飯 60
水 5
計 509

ちなみに

日本大使館行きました。日本語を聞けて癒されました。

3日目

使用金額

トゥクトゥク 280
バス 140
飯60
ソフトクリーム 20
ホテル 145
マッサージ 200
風俗 2000 (マッサージ屋のおねいさんと) 計 2845

払ってないけどメモ

パタヤビーチ 水上スキー 30分 1000 スカイダイビング 600

ちなみに

パタヤビーチに行きました。

4日目

使用金額

飯 50
バス 100
水族館 480
バイク150
アイス 20
バイク 140
飯 140
飯 30
バンドエード 20
ホテル 180
水 10
計 1320

5日目

使用金額

飯 70
飯 95
飯 110
バス 10
水 10
水 10
飯 100
計 405

ホテルレビュー

beach spot 最高

ちなみに

パタヤシティの南にある隣町に行ったんですが、最高のビーチでした。 人が少なくビーチで一日読書してました。

6日

使用金額

バス 792
飯 60
アイス 15
氷 8
ココナッツ 40
飯 60
計 975

7日目

使用金額

バイク 100
飯 50
博物館 500
飯 40
アイス 20
洗濯機 50
飯 201
計 961

8日目

使用金額

水氷 15
ツアー 800
バス 50
アイス 25
飯 80
飯80
水 7
コーラ 18
お土産 300
計 1375

9日目

使用金額

船チケット 30
入場料 20
飯 90
パンケーキ 45
シェイク 50
飯 60
パンケーキ 50
マッサージ 1000
計 1345

10日目

使用金額

飯 60
飯 50
飯 50
ジュース 45
銃 890
計 1095

11日目

使用金額

水 6
アイス 75
バス 30
バス 913
飯 60
計 1083

12日目

使用金額

トイレ 3
飯 40
バイク 140
船 15
宿泊代 200
飯 50
飯 60
飯 140
マッサージ 200
計 848

13日

使用金額

ツアー 500
ホテル 200
飯 40
飯 40
洗濯機 150
計 930

14日目

使用金額

トイレ 5
水 10
飯 20
飯 60
バナナ揚げ 90
知恵の輪 70
飯 35
ホテル 300
飯 70
ケバブ 60
マッサージ 160
計 880

15日目

使用金額

ホテル 300
洗濯 150
飯 60
バナナ揚げ 20
飯 50
飯 50
アイス 10
マッサージ 250
飯 70
計 960

16日目

使用金額

水 7
コーラ 40
ポカリ 25
飯 40
飯 90
バナナ揚げ 30
飯 35
計 267