57
C++ 言語講習会第 0 回資料 書いた人:@sunaemon0(回路屋 2 )

C++ lecture-0

Embed Size (px)

Citation preview

Page 1: C++ lecture-0

C++言語講習会第0回資料

書いた人:@sunaemon0(回路屋2年)

Page 2: C++ lecture-0

1 目標

• linukairoのソースコードを読むことが出来るようになる• 効率的で安全なコードを書くための基礎知識を身につける

– 例外安全性を保つ– 資源管理をおこなう– 未定義動作を避ける

Page 3: C++ lecture-0

2 C++ことはじめ

C++は、C++98, C++03, C++11と進化してきました。

CもK&R, C89, C99, C11と進化してきました。

Page 4: C++ lecture-0

初期においてはC++はCの上位互換だったのですが、今ではそうではありません。

ただ、libkairoで使われている構文なら、ほとんどがC++でもそのまま動きます。

今回の講習会ではC89の知識を前提とした上で、C++03およびC++11はそれよりどのように違うのかという事を説明していく予定です。

Page 5: C++ lecture-0

3 コンパイルの方法について

このpdfファイルに同梱されているソースコードは以下のようにコンパイルできます。

1 # g++ --std=c++11 example0.cpp -W -Wall

Page 6: C++ lecture-0

4 C89とC++03の違い1

• 一行コメントが追加された。 //コレです• 関数内のブロックで先頭以外でも宣言を行えるようになった。• stdint.hがある。• bool型がある。

以上はC99に入っているので使ったことのあるひとも多いでしょう。

Page 7: C++ lecture-0

list 1 example0.cpp1 #include <stdio.h>23 int main(int, char **) // you can omit unused variable4 {

5 printf("input values:");

67 int val[10]; // declears variable after function call

89 for (int i = 0; i < 10; i++) // declears variable in the init expression

10 scanf("%d", val + i);

1112 for (int i = 0; i < 10; i++)13 printf("%d ", val[i]);

1415 printf("\n");

16 return 0;17 }

Page 8: C++ lecture-0

5 C++03とC89との違い2

• 構造体を使いやすくするのに、Cでは typedeをよく使いましたが、typedefなしで同じことができるようになりました。• 引数を取らない関数について、int f(void)でなく、int f()と宣言することになりました。Cの int f()のように引数を具体的に指定しない宣言はできなくなりました。

Page 9: C++ lecture-0

6 iostream詳しいことは次回やりますが、C++ではprintf/scanfの代わりに次のように書きます。

list 2 iostream0.cpp1 #include <string>2 #include <iostream >34 int main(int, char**) {5 std::string s;

6 std::cin >> s; // read from standard input

7 std::cout << s << std::endl; // write to standard output

8 std::cerr << "no error" << std::endl; // write to standard error output

9 }

std::cout <<なんちゃらでprintf的なことができるという認識で実用上問題ありません。

std::stringというのは可変長の文字列を保持できる型です。

Page 10: C++ lecture-0

以降、とりあえず::について解説します。

Page 11: C++ lecture-0

7 名前空間

C++ではライブラリの間の名前の衝突を防止するため、namespaceというキーワードを使って名前空間を分離することができるようになっています。それぞれの名前空間にアクセスするには::演算子を使います。

Page 12: C++ lecture-0

list 3 namespace.cpp1 #include <iostream >23 namespace foo4 {

5 int f() {return 1;}6 }

78 namespace bar9 {

10 int f() {return 2;}11 }

1213 int main(int, char **)14 {

15 std::cout << foo::f() << std::endl; // 1

16 std::cout << bar::f() << std::endl; // 2

17 // std::cout << f() << std::endl;

18 // f was not declared in this scope

19 return 0;20 }

Page 13: C++ lecture-0

大域スコープにはこのようにアクセスします。

list 4 namespace1.cpp1 #include <iostream >23 int f() {return 1;}45 int main(int, char **)6 {

7 std::cout << ::f() << std::endl; // 1

8 std::cout << f() << std::endl; // 1

910 return 0;11 }

Page 14: C++ lecture-0

8 using

いちいち名前空間を指定するのが面倒な場合、usingディレクティブを使って名前空間を指定しなかった時に検索する名前空間を登録することができます。

この際、関数名が衝突して曖昧さが発生した場合は、その関数の使用時にコンパイルエラーが出ます。

Page 15: C++ lecture-0

list 5 using0.cpp1 #include <iostream >23 namespace foo4 {

5 int f() {return 1;}6 }

7 namespace bar8 {

9 int f() {return 2;}10 }

1112 using namespace foo;1314 int main(int, char **)15 {

16 std::cout << foo::f() << std::endl; // 1

17 std::cout << bar::f() << std::endl; // 2

18 std::cout << f() << std::endl; // 1

19 return 0;20 }

Page 16: C++ lecture-0

9 Koenig Lookup(飛ばして良い)

Page 17: C++ lecture-0

このコードはコンパイルエラーが出ます。

list 6 koenig0.cpp1 #include <iostream >23 struct test {int a;};4 namespace foo5 {

6 int f(test dummy) {return 1;}7 }

89 int main(int, char **)

10 {

11 test tmp;

12 std::cout << f(tmp) << std::endl;

13 // f was not declared in this scope

1415 return 0;16 }

Page 18: C++ lecture-0

これだとコンパイルできます。

list 7 koenig1.cpp1 #include <iostream >23 namespace foo4 {

5 struct test {int a;};6 int f(test dummy) {return 1;}7 }

89 int main(int, char **)

10 {

11 foo::test tmp;

12 std::cout << f(tmp) << std::endl;

13 // ok!

1415 return 0;16 }

Page 19: C++ lecture-0

どうして2つ目の例だとうまく行くかというと、C++のコンパイラはスコープ指定子がない関数の呼び出しが有った場合に、大域スコープと無名スコープ、usingディレクティブで導入されたスコープ、usingで導入されたシンボル以外にも、引数の型が属する名前空間も検索するからです。

このように引数の型に依存した検索を行うことを、ADL(Argument Dependent Lookup)または、Konig Lookupと言います。

Page 20: C++ lecture-0

10 標準ライブラリについて

c++03からはcおよびc++のライブラリを読み込むときに1 #include <stdio.h>2 #include <iostream.h>

でなく、1 #include <cstdio>2 #include <iostream >

とします。後者の方が新しい書き方です。

前者だと、それぞれのシンボルは大域名前空間に入っていますが、後者ではstd名前空間に入っています。

Page 21: C++ lecture-0

11 関数のオーバーロード

c++では引数の型が違う同名の関数を定義できます。

list 8 overload0.cpp1 #include <stdio.h>23 int f(int c) {return 1;}4 int f(char i) {return 2;}5 int f(int i, char d) {return 3;}67 int main(int, char **)8 {

9 char c = 0;10 int i = 0;1112 std::cout << f(i) << std::endl; // 1

13 std::cout << f(c) << std::endl; // 2

14 std::cout << f(c,c) << std::endl; // 3

1516 return 0;17 }

Page 22: C++ lecture-0

型がぴったり合う関数定義があれば、それが呼ばれます。型変換を行うことで呼び出せる関数があれば、それが呼ばれます。

Page 23: C++ lecture-0

12 extern “C”gccでコンパイルすると、.oファイルのシンボル名と、Cで書いた関数名が一致します。nm a.outとして調べてみるとわかり安いです。やったことがある人なら解ると思いますが、違う翻訳単位で同名の関数を定義するとリンクするときに怒られます。

Page 24: C++ lecture-0

C++では同名でも型の違う関数が定義できます。これでなんで ldに怒られないかというと単純にシンボル名に型の情報を含めているためです。

例えば、1 can_packet make_can_motor_packet(uint8_t, uint16_t, motor_mode);

2 // _ZN12liblinukairo21make_can_motor_packetEhtNS_10motor_modeE

3 std::string get_short_name(bool, uint8_t);4 // _ZN12liblinukairo14get_short_nameEbh

みたいな感じです。c++filtというコマンドを使うとこの暗号を解読できます。

Page 25: C++ lecture-0

しかし、cで書かれたライブラリとc++で書いたコードをリンクするときにこの機能が悪さをします。

1 int getchar();

みたいにしても変な名前のシンボル Z7getcharvを探しに行ってしまってリンクがうまく出来ません。代わりに

1 extern "C"2 {

3 int getchar();4 }

とすることでちゃんとgetcharというシンボルを呼び出すのでうまく行きます。

Page 26: C++ lecture-0

13 スタックとヒープ

ローカル変数や呼び出し番地などが格納される領域をスタック領域と言います。

Cで言えば、mallocで取って来れる領域のことをヒープ領域と言います。

static変数が配置される領域を静的領域といいます。

Page 27: C++ lecture-0

14 newとdelete

c++ではヒープ領域に変数を確保するのにnewを使います。開放するには、deleteを使います。

list 9 new0.cpp1 #include <cstdio>23 int main(int, char **)4 {

5 int *i = new int;67 std::scanf("%d", i);

8 std::printf("%d\n", *i);

910 delete i;1112 return 0;13 }

Page 28: C++ lecture-0

配列を確保する場合はnew[]を使います。また開放するにはdelete[]を使います。

list 10 new1.cpp1 #include <cstdio>23 int main(int, char **)4 {

5 char *s = new char[100];67 std::scanf("%s", s);

8 std::printf("%s\n", s);

910 delete[] s;1112 return 0;13 }

Page 29: C++ lecture-0

new/deleteはmalloc/freeに比べてキャストが必要がないという点で型安全になっています。

開放するときに使う演算子が違うことに注意して下さい!

Page 30: C++ lecture-0

15 左辺値参照とポインタ

C++では実はポインタはあまり使いません。

Cでポインタを使う場面は多分この4つです。

1. 参照渡しをする。2. mallocを使ってヒープ領域に動的に領域を確保する。3. 文字列を扱う。(2.の特殊例)4. (回路レイヤだけですが)定数からポインタにキャストしてレジスタをゴリゴリする。

Page 31: C++ lecture-0

今回やる左辺値参照というのはざっくり言うと参照渡しをするためのものです。

次回以降やりますが、文字列はstd::stringを使い、ヒープ領域に配列を確保するには普通std::vectorを使います。

C++でポインタが必要になるのは、thisポインタを使うとき、Cで書かれたライブラリをいじるとき、それとメモリを直接ゴニョゴニョするときくらいです。

Page 32: C++ lecture-0

実引数を関数に渡すときに、値をコピーして渡すのを値渡しと言います。

• 呼び出された側は、実引数を変更できません。• 大きなデータを値渡しすると、大きなコストがかかります。

実引数を関数に渡すときに、値をコピーせずそのまま渡すのを参照渡しと言います。

• 呼び出された側は、実引数を変更できます。• 大きなデータを参照渡ししても、コストは小さいデータと変わりません。

Page 33: C++ lecture-0

ポインタで参照渡しを行うと以下の問題があります。

• 値渡しの時と記法が違う (同時に利点でもあります。)• NULLを渡せてしまう

これらを解消したのが左辺値参照です。

Page 34: C++ lecture-0

左辺値参照は基本的にはポインタです。

宣言は1 int a = 0; //local variable2 int &r = a; //reference of a

このように行います。

左辺値参照はこのように、lvalue(ローカル変数などの具体的に名前のついた値)で初期化しないと作れません。

1 int &r;2 // ‘’r declared as reference but not initialized3 int &e = 1;4 // invalid initialization of non-const reference of type

5 // ‘int’& from an rvalue of type ‘’int6 const int &f = 1;7 // ok

歴史的経緯から、定数左辺値参照はなんと領域を確保するの

Page 35: C++ lecture-0

で、rvalue(リテラルなどの名前のついていない一時的な値)で初期化できます。

これはポインタとは全く違った振る舞いです。

Page 36: C++ lecture-0

参照渡ししてみましょう。

list 11 reference0.cpp1 #include <cstdio>23 void f(int &i)4 {

5 i=3;

6 }

78 int main(int argc, char **argv)9 {

10 int i=0;11 f(i);

12 std::printf("%d\n", i);

13 return 0;14 }

ポインタみたいに1 *i = 3;

Page 37: C++ lecture-0

とは書きません。参照はその元の変数と同じように振る舞います。

そのため、ポインタと違って free()やdeleteはできません。

Page 38: C++ lecture-0

16 オブジェクト指向

オブジェクト志向というのは、プログラムをオブジェクトがメッセージを投げ合うという形に表現するパラダイムです。

オブジェクトは内部にデータを保持すると同時に、メッセージを投げられた時にどうしようというプログラムも持ちます。

Page 39: C++ lecture-0

17 クラスと構造体とオブジェクト指向

C++にはclassというキーワードがあります。またC++にもstructというキーワードがあります。C++において両者はほとんど同じです。違いは、メンバがデフォルトでprivateになるかpublicかというだけです。publicとかprivateについては後でやります。

そこで、以降structのC的な用法を構造体、classのC++的な用法をクラスと呼びます。

クラスというのは、C++においては構造体にメンバ関数やカプセル化やコンストラクタ・デストラクタや継承や仮想関数などの概念を付け加えたものです。

オブジェクト指向でいうオブジェクトを、クラスのインスタ

Page 40: C++ lecture-0

ンスで表すのがC++流のオブジェクト指向です。

Page 41: C++ lecture-0

もう少しまともな例を挙げます。

例えば、libkairoのhalがオブジェクト指向の概念を実装しています。細かいところを省くと以下のようなコードになっています。

Page 42: C++ lecture-0

list 12 hal0.cpp1 #include <stdio.h>23 struct hal_adc {4 unsigned int value;5 };

67 void set_adc_value(hal_adc *adc, unsigned int value) {8 adc->value = value;

9 }

1011 unsigned int get_adc_value(const hal_adc *adc) {12 return adc->value;13 }

1415 int main(int, char **) {16 hal_adc adc;

17 set_adc_value(&adc, 3);

18 printf("%d\n", get_adc_value(&adc));

19 return 0;20 }

Page 43: C++ lecture-0

had adc型の変数がオブジェクトです。このオブジェクトに電圧をよこせというメッセージを送ることは、

1 unsigned int get_adc_value(const hal_adc *adc);

にhal adc型の変数を渡して呼び出すという事に相当します。

C++のクラスは、これの書き方を書き換えたものがベースになっています。

Page 44: C++ lecture-0

18 thisポインタとメンバ関数

これをメンバ変数と同じ感じで呼び出せるようにしようというのがC++のメンバ関数です。

hal encを簡略化したものを構造体とクラスで書いて見ました。

Page 45: C++ lecture-0

list 13 hal0.cpp1 #include <stdio.h>23 struct hal_adc {4 unsigned int value;5 };

67 void set_adc_value(hal_adc *adc, unsigned int value) {8 adc->value = value;

9 }

1011 unsigned int get_adc_value(const hal_adc *adc) {12 return adc->value;13 }

1415 int main(int, char **) {16 hal_adc adc;

17 set_adc_value(&adc, 3);

18 printf("%d\n", get_adc_value(&adc));

19 return 0;20 }

Page 46: C++ lecture-0

list 14 hal1.cpp1 #include <cstdio>23 class hal_adc {4 private:5 unsigned int value;6 public:7 unsigned int get_value() {8 return this->value;9 }

10 void set_value(unsigned int value) {11 this->value = value;12 }

13 };

1415 int main(int, char **)16 {

17 hal_adc adc;

18 adc.set_value(3);

19 std::printf("%d\n", adc.get_value());

20 return 0;21 }

これをみれば、thisポインタというのがなにか分かると思います。

Page 47: C++ lecture-0

hal enc型の変数とさっきから書いて来ましたが、正確には変数の中に入っている値こそが議論の対象です。

これをC++ではhal encのインスタンスと言います。

内部的にはやってることは構造体の方と同じです。

Page 48: C++ lecture-0

19 隠蔽と friend

しかしset adc valueやget adc valueって余計な関数呼び出しが発生するだけじゃね?って適当な人が直接valueを書き換えたとします。

これは後々halの中身を書き換えたり、不正な値が代入されないかチェックしたりということができなくなるのでよくありません。

しかし、Cのコードだと結構簡単にできてしまいます。

Page 49: C++ lecture-0

list 15 access0.cpp1 #include <stdio.h>23 struct hal_adc {4 unsigned int value;5 };

67 void set_adc_value(hal_adc *adc, unsigned int value) {8 adc->value = value;

9 }

1011 unsigned int get_adc_value(const hal_adc *adc) {12 return adc->value;13 }

1415 int main(int, char **)16 {

17 hal_adc adc;

18 adc.value = 3;

19 //set_adc_value(&adc, 3);

20 printf("%d\n", adc.value);

21 //printf("%d\n", get_adc_value(&adc));

22 }

Page 50: C++ lecture-0

libkairoだと構造体をhal adc prv.hというヘッダファイルで定義して、制御屋はそこで定義されてるものは直接使わないようにしようという事にしていますが、強制力はありません。

しかし、それをC++でやってみるとこれはコンパイルが通りません。

Page 51: C++ lecture-0

list 16 access1.cpp1 #include <cstdio>23 class hal_adc {4 private:5 unsigned int value;6 public:7 unsigned int get_value() {8 return this->value;9 }

10 void set_value(unsigned int value) {11 this->value = value;12 }

13 };

1415 int main(int, char **)16 {

17 hal_adc adc;

18 adc.value = 3;

19 // ‘unsigned int hal_adc::’value is private20 std::printf("%d\n", adc.value);

21 // ‘unsigned int hal_adc::’value is private22 return 0;23 }

Page 52: C++ lecture-0

こうして世界は平和になりました。

エラーメッセージを見ると解ると思いますが、仕事してるのは四行目のprivate:というアクセス指定子です。次のアクセス指定子までのメンバ変数をpublicにします。

これによってvalueは外側からは直接触れなくなります。これを隠蔽と言います。

逆にget valueはpublicというアクセス修飾子がついているので外側からアクセスできます。

先述の通りclassのデフォルトのアクセス修飾子はprivateなので本当は四行目は要りません。

Page 53: C++ lecture-0

また、structとclassの違いはデフォルトのアクセス修飾子の違いだけですから、これでも同じ事です。

list 17 access2.cpp1 #include <cstdio>23 struct hal_adc {4 unsigned int get_value() {5 return this->value;6 }

7 void set_value(unsigned int value) {8 this->value = value;9 }

10 private:11 unsigned int value;12 };

1314 int main(int, char **)15 {

16 hal_adc adc;

17 adc.value = 3;

18 // ‘unsigned int hal_adc::’value is private19 std::printf("%d\n", adc.value);

20 // ‘unsigned int hal_adc::’value is private21 return 0;22 }

Page 54: C++ lecture-0

でも特別にこの関数にだけはprivateも触らせてあげようという事もあります。そんな時には friendを使います。

list 18 access3.cpp1 #include <cstdio>23 struct hal_adc {4 unsigned int get_value() {5 return this->value;6 }

7 void set_value(unsigned int value) {8 this->value = value;9 }

10 private:11 unsigned int value;1213 friend int main(int argc, char **argv);14 };

1516 int main(int, char **) {17 hal_adc adc;

18 adc.value = 3;

19 // ok

20 std::printf("%d\n", adc.value);

21 // ok

22 }

Page 55: C++ lecture-0

20 演習問題

• Cで定義した関数をC++から呼び出してみよ。逆にC++で定義した関数をCから呼び出してみよ。• 静的なローカル変数への左辺値参照を関数から返して、呼び出し元でそれを変更してみよ。

Page 56: C++ lecture-0

21 Referencehttp://www.cplusplus.com/

http://en.cppreference.com/w/

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

Page 57: C++ lecture-0

22 LisenceThe text of this document is distributed under the CreativeCommons Attribution-ShareAlike 2.0 License.