【C++】フレンド関数
- 関数をクラスのメンバにすることなく、関数からクラスの非公開メンバにアクセスすることができる
- 1つの関数から、2つ以上のクラスの非公開メンバにアクセスしたい時などに使われる。
- クラスの宣言の内部にfriendを先頭につけて関数のプロトタイプを含める
stdudy96.cpp
#include <iostream> using namespace std; class myclass { int n, d; public: myclass( int i, int j ) { n = i; d = j; } friend int isfactor( myclass ob ); }; int isfactor( myclass ob ) { if( !( ob.n % ob.d ) ) return 1; else return 0; } int main() { myclass ob1(10, 2), ob2(13, 3); if ( isfactor( ob1 ) ) cout << "2は10の因数\n"; else cout << "2は10の因数ではない\n"; if ( isfactor( ob2 ) ) cout << "3は13の因数\n"; else cout << "3は13の因数ではない\n"; return 0; }
$ ./study96 2は10の因数 3は13の因数ではない
- isfactor()はmyclassのフレンドなので、myclassの非公開メンバにアクセスできる
- ob.nとob.dの両方を直接参照できる
- クラスメンバアクセス演算子(ドット演算子やアロー演算子)を使用してフレンド関数を呼び出すことはできない
- フレンド関数は継承されない
- 複数のクラスのフレンドになることができる
stdudy98.cpp
#include <iostream> using namespace std; class car; class truck; class car { int passengers; int speed; public: car ( int p, int s ) { passengers = p; speed = s; } friend int sp_greater( car c, truck t ); }; class truck { int weight; int speed; public: truck ( int w, int s ) { weight = w; speed = s; } friend int sp_greater( car c, truck t ); }; int sp_greater ( car c, truck t ) { return c.speed - t.speed; } int main() { int t; car c1(6, 55), c2(2, 120); truck t1(10000, 55), t2(20000, 72); cout << "c1とc2を比較\n"; t = sp_greater( c1, t1 ); if ( t < 0 ) cout << "トラックが速い\n"; else if ( t == 0 ) cout << "乗用車とトラックの速度は同じ\n"; else cout << "乗用車が速い\n"; cout << "\nc2とt2を比較:\n"; t = sp_greater( c2, t2 ); if ( t < 0 ) cout << "トラックが速い\n"; else if ( t == 0 ) cout << "乗用車とトラックの速度は同じ\n"; else cout << "乗用車が速い\n"; return 0; }
$ ./study98 c1とc2を比較 乗用車とトラックの速度は同じ c2とt2を比較: 乗用車が速い
プログラムの通り。
引用・参考はこちら
【C++】関数からオブジェクトの返し
関数にオブジェクトを渡すことができるように、返すこともできる。
#include <iostream> #include <cstring> using namespace std; class samp { char s[80]; public: void show() { cout << s << "\n"; } void set( char * str ) { strcpy( s, str ); } }; samp input() { char s[80]; samp str; cout << "文字列の入力"; cin >> s; str.set(s); return str; } int main() { samp ob; ob = input(); ob.show(); return 0; }
$ g++ -o study92 study92.cpp $ ./study92 文字列の入力:こんにちは こんにちは
study93.cpp
このプログラムはコアダンプになる
#include <iostream> #include <cstring> #include <cstdlib> using namespace std; class samp { char *s; public: samp() { s = "\0"; } ~samp() { if( s ) { free( s ); cout << "sを開放する\n"; } } void show() { cout << s << "\n"; } void set( char *str ) { s = ( char * ) malloc( strlen(str) + 1 ); if ( !s ) { cout << "メモリ割り当てエラー\n"; exit(1); } strcpy( s, str ); } }; samp input() { char s[80]; samp str; cout << "文字列の入力:"; cin >> s; str.set(s); return str; } int main() { samp ob; ob = input(); ob.show(); return 0; }
$ ./study93 文字列の入力:hello sを開放する *** Error in `./study93': double free or corruption (fasttop): 0x0000000001b00440 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7ff642cb97e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7ff642cc237a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7ff642cc653c] ./study93[0x400cc7] ./study93[0x400bfe] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7ff642c62830] ./study93[0x400a39] ======= Memory map: ======== 00400000-00402000 r-xp 00000000 08:06 14298074 /home/atsuya/study/cpp/study93 00601000-00602000 r--p 00001000 08:06 14298074 /home/atsuya/study/cpp/study93 00602000-00603000 rw-p 00002000 08:06 14298074 /home/atsuya/study/cpp/study93 01aee000-01b20000 rw-p 00000000 00:00 0 [heap] 7ff63c000000-7ff63c021000 rw-p 00000000 00:00 0 7ff63c021000-7ff640000000 ---p 00000000 00:00 0 7ff642939000-7ff642a41000 r-xp 00000000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7ff642a41000-7ff642c40000 ---p 00108000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7ff642c40000-7ff642c41000 r--p 00107000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7ff642c41000-7ff642c42000 rw-p 00108000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7ff642c42000-7ff642e02000 r-xp 00000000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7ff642e02000-7ff643002000 ---p 001c0000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7ff643002000-7ff643006000 r--p 001c0000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7ff643006000-7ff643008000 rw-p 001c4000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7ff643008000-7ff64300c000 rw-p 00000000 00:00 0 7ff64300c000-7ff643023000 r-xp 00000000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ff643023000-7ff643222000 ---p 00017000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ff643222000-7ff643223000 r--p 00016000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ff643223000-7ff643224000 rw-p 00017000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ff643224000-7ff6433a0000 r-xp 00000000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7ff6433a0000-7ff6435a0000 ---p 0017c000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7ff6435a0000-7ff6435aa000 r--p 0017c000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7ff6435aa000-7ff6435ac000 rw-p 00186000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7ff6435ac000-7ff6435b0000 rw-p 00000000 00:00 0 7ff6435b0000-7ff6435d6000 r-xp 00000000 08:06 13238524 /lib/x86_64-linux-gnu/ld-2.23.so 7ff643782000-7ff643788000 rw-p 00000000 00:00 0 7ff6437d4000-7ff6437d5000 rw-p 00000000 00:00 0 7ff6437d5000-7ff6437d6000 r--p 00025000 08:06 13238524 /lib/x86_64-linux-gnu/ld-2.23.so 7ff6437d6000-7ff6437d7000 rw-p 00026000 08:06 13238524 /lib/x86_64-linux-gnu/ld-2.23.so 7ff6437d7000-7ff6437d8000 rw-p 00000000 00:00 0 7ffe018df000-7ffe01901000 rw-p 00000000 00:00 0 [stack] 7ffe01996000-7ffe01999000 r--p 00000000 00:00 0 [vvar] 7ffe01999000-7ffe0199b000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted (core dumped)
- デストラクタが3回呼び出される
- input()が終了し、ローカルオブジェクトのstrがスコープから外れた時
- input()から返される一時オブジェクトが破棄されるとき
- main()終了時
- 最初のデストラクタが実行されるときにinput()関数で文字列を格納するために割り当てられたメモリが開放される
- すでに開放されているメモリを他の2つが開放しようとする
- 進行中の自動割当システムが破壊される
- したがって、ストラクタやデストラクタがあるときにオブジェクトを返すときは注意が必要
- コピーコンストラクタを使うことで解決できる(こちら)
引用・参考はこちら
【C++】関数へオブジェクトの引き渡し
- 関数に引数としてデータを渡すのと同じように、オブジェクトを渡すこともできる。
study86.cpp
#include <iostream> using namespace std; class samp { int i; public: samp( int n ) { i = n; } void set_i( int n ) { i = n; } int get_i() { return i; } }; void sqr_it( samp o ) { o.set_i( o.get_i() * o.get_i() ); cout << "iのコピーの値は " << o.get_i() << "\n"; } int main() { samp a(10); sqr_it(a); cout << "しかし、a.iはmain()で変更されない:" << a.get_i() << "\n"; return 0; }
$ ./study86 iのコピーの値は 100 しかし、a.iはmain()で変更されない:10
- デフォルトでは 値呼び出し で関数に渡される
- 引数がビット単位でコピーされ、関数ではこのコピーが使われる
study87.cpp
#include <iostream> using namespace std; class samp { int i; public: samp( int n ) { i = n; } void set_i( int n ) { i = n; } int get_i() { return i; } }; void sqr_it( samp *o ) { o->set_i( o->get_i() * o->get_i() ); cout << "iのコピー値は " << o->get_i() << "\n"; } int main() { samp a(10); sqr_it(&a); cout << "今、mainは変更された:" << a.get_i() << "\n"; return 0; }
$ ./study87 iのコピー値は 100 今、mainは変更された:100
- オブジェクトのアドレスを関数に渡すと、関数を呼び出す際に使用した引数を関数内で修正できる
引用・参考はこちら
【C++】オブジェクトの代入
「オブジェクト1=オブジェクト2」のような書き方で代入できる。
study79.cpp
#include <iostream> using namespace std; class myclass { int a, b; public: void set( int i, int j ) { a = i; b = j; } void show() { cout << a << ' ' << b << "\n"; } }; int main () { myclass o1, o2; o1.set( 10, 4 ); o2 = o1; o1.show(); o2.show(); return 0; }
$ g++ -o study79 study79.cpp $ ./study79 10 4 10 4
study80.cpp
このプログラムはコンパイルエラーになる。
#include <iostream> using namespace std; class myclass { int a, b; public: void set( int i, int j ) { a = i; b = j; } void show() { cout << a << ' ' << b << "\n"; } }; class yourclass { int a, b; public: void set( int i, int j ) { a = i; b = j; } void show() { cout << a << ' ' << b << "\n"; } }; int main () { myclass o1; yourclass o2; o1.set( 10, 4 ); o2 = o1; o1.show(); o2.show(); return 0; }
クラスの中身が同じでもクラス名が同じでないと代入できない。
study82.cpp
このプログラムはコアダンプになる。
#include <iostream> #include <cstring> #include <cstdlib> using namespace std; class strtype { char *p; int len; public: strtype( char *ptr) { len = strlen( ptr ); p = ( char * ) malloc( len + 1 ); if( !p ) { cout << "メモリ割り当てエラー\n"; exit(1); } strcpy( p, ptr ); } ~strtype() { cout << "pを開放する\n"; free( p ); } void show() { cout << p << " - 長さ:" << len << "\n"; } }; int main() { strtype s1("This is a test"), s2("I like C++"); s1.show(); s2.show(); s2 = s1; s1.show(); s2.show(); return 0; }
$ g++ -o study82 study82.cpp $ ./study82 This is a test - 長さ:14 I like C++ - 長さ:10 This is a test - 長さ:14 This is a test - 長さ:14 pを開放する pを開放する *** Error in `./study82': double free or corruption (fasttop): 0x0000000000e2ac20 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fe0ad0767e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fe0ad07f37a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fe0ad08353c] ./study82[0x400c64] ./study82[0x400b1b] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe0ad01f830] ./study82[0x4009b9] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 08:06 14298065 /home/atsuya/study/cpp/study82 00601000-00602000 r--p 00001000 08:06 14298065 /home/atsuya/study/cpp/study82 00602000-00603000 rw-p 00002000 08:06 14298065 /home/atsuya/study/cpp/study82 00e19000-00e4b000 rw-p 00000000 00:00 0 [heap] 7fe0a8000000-7fe0a8021000 rw-p 00000000 00:00 0 7fe0a8021000-7fe0ac000000 ---p 00000000 00:00 0 7fe0accf6000-7fe0acdfe000 r-xp 00000000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7fe0acdfe000-7fe0acffd000 ---p 00108000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7fe0acffd000-7fe0acffe000 r--p 00107000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7fe0acffe000-7fe0acfff000 rw-p 00108000 08:06 13238541 /lib/x86_64-linux-gnu/libm-2.23.so 7fe0acfff000-7fe0ad1bf000 r-xp 00000000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7fe0ad1bf000-7fe0ad3bf000 ---p 001c0000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7fe0ad3bf000-7fe0ad3c3000 r--p 001c0000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7fe0ad3c3000-7fe0ad3c5000 rw-p 001c4000 08:06 13238538 /lib/x86_64-linux-gnu/libc-2.23.so 7fe0ad3c5000-7fe0ad3c9000 rw-p 00000000 00:00 0 7fe0ad3c9000-7fe0ad3e0000 r-xp 00000000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fe0ad3e0000-7fe0ad5df000 ---p 00017000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fe0ad5df000-7fe0ad5e0000 r--p 00016000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fe0ad5e0000-7fe0ad5e1000 rw-p 00017000 08:06 13242862 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fe0ad5e1000-7fe0ad75d000 r-xp 00000000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7fe0ad75d000-7fe0ad95d000 ---p 0017c000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7fe0ad95d000-7fe0ad967000 r--p 0017c000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7fe0ad967000-7fe0ad969000 rw-p 00186000 08:06 6686331 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 7fe0ad969000-7fe0ad96d000 rw-p 00000000 00:00 0 7fe0ad96d000-7fe0ad993000 r-xp 00000000 08:06 13238524 /lib/x86_64-linux-gnu/ld-2.23.so 7fe0adb3f000-7fe0adb45000 rw-p 00000000 00:00 0 7fe0adb91000-7fe0adb92000 rw-p 00000000 00:00 0 7fe0adb92000-7fe0adb93000 r--p 00025000 08:06 13238524 /lib/x86_64-linux-gnu/ld-2.23.so 7fe0adb93000-7fe0adb94000 rw-p 00026000 08:06 13238524 /lib/x86_64-linux-gnu/ld-2.23.so 7fe0adb94000-7fe0adb95000 rw-p 00000000 00:00 0 7ffe7c6d7000-7ffe7c6f9000 rw-p 00000000 00:00 0 [stack] 7ffe7c7ac000-7ffe7c7af000 r--p 00000000 00:00 0 [vvar] 7ffe7c7af000-7ffe7c7b1000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted (core dumped)
- s1とs2を作成するときに、両者とも文字列を格納するためのメモリを割り当てる
- メモリのポインタ変数pに保存される
- strtypeオブジェクトを破棄するときにメモリが開放される
- s1にs2を代入すると、s2はs1と同じメモリを指すようになる
- そのため、s1オブジェクトを2度開放しようとする
- s2のpがメモリを示したポインタは開放されない これらの理由でコアダンプとなる。
そのため、オブジェクトを代入するときは注意が必要。
引用・参考はこちら
【C++】インライン関数
インライン関数の特徴
- メリット
- 関数の呼び出しと終了時に伴うオーバーヘッドが発生しないことから、通常の関数よりも早く実行できる。
- デメリット
- ファイルサイズが大きくなる。
- ループ、goto、switchを含めることができない
- 再帰してはいけない
- 関数定義の前にinlineと記述するだけでインライン関数となる。
- 関数の規模が大きい時など、コンパイラが要求を実行できない場合は通常の関数としてコンパイルされる。
- インライン関数もオーバーロードすることができる
#include <iostream> using namespace std; inline int even( int x ) { return !(x%2); } int main() { if( even(10) ) cout << "10は偶数です\n"; if( even(11) ) cout << "11は偶数です\n"; return 0; }
$ g++ -o study67 study67.cpp $ ./study67 10は偶数です
自動インライン化
#include <iostream> using namespace std; class samp { int i, j; public: samp( int a, int b ) { i = a; j = b; } int divisible() { return (i/j); } }; int main() { samp sp( 20, 5 ); cout << sp.divisible() << endl; }
$ g++ -o study72 study72.cpp $ ./study72 4
- ただし、入出力演算子はそもそも遅いので恩恵はあまり得られない
- 短くなるため、恩恵がない場合でもクラスの中に書くことは多い
引用・参考はこちら
【C++】継承②
study54.cpp
#include <iostream> #include <cstring> using namespace std; enum yn { no, yes }; enum color { red, yellow, green, orange }; void out( enum yn x ); char *c[] = { "赤", "黄", "緑", "オレンジ" }; class fruit { public: enum yn annual; enum yn perennial; enum yn tree; enum yn tropical; enum color clr; char name[40]; }; class Apple : public fruit { enum yn cooking; enum yn crunchy; enum yn eating; public: void seta( char *n, enum color c, enum yn ck, enum yn crchy, enum yn e ); void show(); }; class Orange : public fruit { enum yn juice; enum yn sour; enum yn eating; public: void seto( char *n, enum color c, enum yn j, enum yn sr, enum yn e ); void show(); }; void Apple::seta( char *n, enum color c, enum yn ck, enum yn crchy, enum yn e ) { strcpy( name, n ); annual = no; perennial = yes; tree = yes; tropical = no; clr = c; cooking = ck; crunchy = crchy; eating = e; } void Orange::seto( char *n, enum color c, enum yn j, enum yn sr, enum yn e ) { strcpy( name, n ); annual = no; perennial = yes; tree = yes; tropical = yes; clr = c; juice = j; sour = sr; eating = e; } void Apple::show() { cout << name << "りんご:\n"; cout << "一年生植物:"; out( annual ); cout << "多年生植物:"; out( perennial ); cout << "木:"; out( tree ); cout << "熱帯性:"; out( tropical ); cout << "色:" << c[clr] << "\n"; cout << "料理用"; out( cooking ); cout << "かたい"; out( crunchy ); cout << "食用"; out( eating ); cout << "\n"; } void Orange::show() { cout << name << "オレンジ:\n"; cout << "一年生植物:"; out( annual ); cout << "多年生植物:"; out( perennial ); cout << "木:"; out( tree ); cout << "熱帯性:"; out( tropical ); cout << "色" << c[clr] << "\n"; cout << "ジュース用:"; out( juice ); cout << "酸っぱい:"; out( sour ); cout << "食用:"; out( eating ); cout << "\n"; } void out( enum yn x ) { if ( x == no ) cout << "no\n"; else cout << "yes\n"; } int main() { Apple a1, a2; Orange o1, o2; a1.seta("レッドデリシャス", red, no, yes, yes ); a2.seta("ジョナサン", red, yes, no, yes ); o1.seto("ネーブル", orange, no, no, yes ); o2.seto("バレンシア", orange, yes, yes, no); a1.show(); a2.show(); o1.show(); o2.show(); return 0; }
コンパイル
$ g++ -o study54 study54.cpp
実行
レッドデリシャスりんご: 一年生植物:no 多年生植物:yes 木:yes 熱帯性:no 色:赤 料理用no かたいyes 食用yes ジョナサンりんご: 一年生植物:no 多年生植物:yes 木:yes 熱帯性:no 色:赤 料理用yes かたいno 食用yes ネーブルオレンジ: 一年生植物:no 多年生植物:yes 木:yes 熱帯性:yes 色オレンジ ジュース用:no 酸っぱい:no 食用:yes バレンシアオレンジ: 一年生植物:no 多年生植物:yes 木:yes 熱帯性:yes 色オレンジ ジュース用:yes 酸っぱい:yes 食用:no
【C++】継承①
- 継承をすることで、最も汎用的なクラスから、最も特化されたクラスへの流れを示すクラス階層を作成できる。
- あるクラスを別のクラスが継承するとき
- 継承される側のクラスを 基本クラス と呼ぶ
- 継承する側のクラスを 派生クラス と呼ぶ
#include <iostream> using namespace std; class B { int i; public: void set_i( int ); int get_i(); }; class D : public B { int j; public: void set_j( int ); int mul(); }; void B::set_i( int n ) { i = n; } int B::get_i() { return i; } void D::set_j( int n ) { j = n; } int D::mul() { return j * get_i(); } int main() { D ob; ob.set_i( 10 ); ob.set_j( 4 ); cout << ob.mul() << endl; return 0; }
- mul()に注目
- 特にオブジェクトにリンクすることなく、Bクラスのメンバであるget_i()を呼び出している。
- mul()では非公開メンバのiにアクセスできないため、get_i()関数を呼び出す。
コンパイル
$ g++ -o study52 study52.cpp
実行
$ ./study52 40