■C++: クラスを使ったプログラム
■基本的なクラス
クラスの作成例
メンバ変数(プロパティ)に値を代入して表示している
$ vi class.cpp
#include <iostream>
using namespace std;
class Hello {
public:
//char *str;
const char* str;
} obj;
int main()
{
obj.str = "Hello, Class!";
cout << obj.str << endl;
return 0;
}
「char *str」部分で警告が表示されたので、「const char *str」とした
[C++] ダブルクォーテーションで囲った文字列を関数に渡すことで警告が出るときの理由と対処 - Qiita
https://qiita.com/limb/items/c1f4e921b0911dfce3dc
メンバ関数(メソッド)を定義し、コンストラクタも定義し、
オブジェクト名も任意のものを指定できるように
$ vi class2.cpp
#include <iostream>
using namespace std;
// クラスの定義
class Hello {
// 外部からアクセスできない変数と関数
private:
int point;
// 外部からアクセスできる変数と関数
public:
// メンバ変数
const char* str;
// コンストラクタ
Hello(const char* str);
// デストラクタ
~Hello();
// メンバ関数
void print();
};
Hello::Hello(const char* str) {
this->point = 0;
this->str = str;
cout << "[constructor: " << this->str << "]" << this->point << endl;
}
Hello::~Hello() {
this->point++;
cout << "[destructor]" << this->point << endl;
}
void Hello::print() {
this->point++;
cout << "[" << this->str << "]" << this->point << endl;
cout << "[" << Hello::str << "]" << Hello::point << endl; // 「this->str」は「Hello::str」と書くこともできる
cout << "[" << str << "]" << point << endl; // メンバ変数名だけでも変数を参照できる
}
int main()
{
// クラスの利用
Hello obj("Hello, Class.");
obj.print();
obj.str = "Hello, Class!";
cout << obj.str << endl;
// 以下の書き方でもクラスを利用できる
Hello obj2 = Hello("Hello, Class 2.");
obj2.print();
obj2.str = "Hello, Class 2!";
cout << obj2.str << endl;
// 以下の書き方でもクラスを利用できる(メモリの動的確保)
Hello *obj3 = new Hello("Hello, Class 3.");
obj3->print();
obj3->str = "Hello, Class 3!";
cout << obj3->str << endl;
delete obj3;
return 0;
}
C++ クラス 入門
http://vivi.dyndns.org/tech/cpp/class-basic.html
ロベールのC++教室 - 第4章 引数付きの構築 -
http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02004.html
ロベールのC++教室 - 第19章 動的オブジェクト -
http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02019.html
■オーバーライド
同名のメンバ関数を定義し、引数によって処理先を変える
$ vi class3.cpp
#include <iostream>
using namespace std;
class Hello {
public:
void print();
void print(const char* str);
};
void Hello::print() {
cout << "[print]" << endl;
}
void Hello::print(const char* str) {
cout << "[" << str << "]" << endl;
}
int main()
{
Hello obj;
obj.print();
obj.print("Hello!");
return 0;
}
■継承
クラスの機能を引き継いで新しいクラスを作る
「class Bye : public Hello」を「class Bye : private Hello」にすると、Helloクラス内の命令はByeクラス内でのみ使用できるようになる
$ vi class4.cpp
#include <iostream>
using namespace std;
class Hello {
public:
void print();
void say();
};
void Hello::print() {
cout << "[print]" << endl;
}
void Hello::say() {
cout << "[Hello!]" << endl;
}
class Bye : public Hello {
public:
void say();
void test();
};
void Bye::say() {
cout << "[Bye!]" << endl;
}
void Bye::test() {
cout << "[TEST]" << endl;
}
int main()
{
Hello hello;
Bye bye;
hello.print();
hello.say();
bye.print();
bye.say();
bye.test();
return 0;
}
■多重継承
ClassA と ClassB を継承して ClassC を作る
のように、複数のクラスを継承できる
$ vi class5.cpp
#include <iostream>
using namespace std;
class ClassA {
public:
ClassA();
};
ClassA::ClassA() {
cout << "[ClassA]" << endl;
}
class ClassB {
public:
ClassB();
};
ClassB::ClassB() {
cout << "[ClassB]" << endl;
}
class ClassC : public ClassA, public ClassB {
public:
ClassC();
};
ClassC::ClassC() {
cout << "[ClassC]" << endl;
}
int main()
{
ClassA a;
ClassB b;
ClassC c;
return 0;
}
■多重継承での仮想基本クラス
Base を継承して ClassA と ClassB を作り、
その ClassA と ClassB を継承して ClassC を作り、
その ClassC のメンバ変数を扱う場合、複数の Base を継承しているために変数が曖昧な状態となる
具体的には、c.name にアクセスしようとすると「request for member ‘name’ is ambiguous」というエラーになる
この場合、以下のように「virtual public Base」と宣言すると仮想基本クラスとなり、Baseのコピーは一つしか持たなくなる
$ vi class6.cpp
#include <iostream>
using namespace std;
class Base {
public:
const char* name;
Base();
};
Base::Base() {
cout << "[Base]" << endl;
}
class ClassA : virtual public Base {
public:
ClassA();
};
ClassA::ClassA() {
cout << "[ClassA]" << endl;
}
class ClassB : virtual public Base {
public:
ClassB();
};
ClassB::ClassB() {
cout << "[ClassB]" << endl;
}
class ClassC : public ClassA, public ClassB {
public:
ClassC();
};
ClassC::ClassC() {
cout << "[ClassC]" << endl;
}
int main()
{
ClassA a;
ClassB b;
ClassC c;
c.name = "Taro";
cout << c.name << endl;
return 0;
}
■抽象クラス
「void print()」という宣言を「virtual void print() = 0」のようにすると純粋仮想関数となり、子クラスで再定義しなければ継承できなくなる
純粋仮想関数を持つクラスを抽象クラスと呼び、抽象クラスから直接オブジェクトを作ることはできない
継承されて初めて使うことができるようになる
$ vi class7.cpp
#include <iostream>
using namespace std;
class ClassA {
public:
ClassA();
virtual void print() = 0;
};
ClassA::ClassA() {
cout << "[ClassA]" << endl;
}
class ClassB : public ClassA {
public:
ClassB();
void print();
};
ClassB::ClassB() {
cout << "[ClassB]" << endl;
}
void ClassB::print() {
cout << "[print]" << endl;
}
int main()
{
//ClassA a;
ClassB b;
return 0;
}
■テンプレートクラス
テンプレート関数のクラス版
$ vi class8.cpp
#include <iostream>
using namespace std;
template <typename X> class Stack {
X box[64];
int index;
public:
bool push(X var) {
if (index > 63) return false;
box[index] = var;
index++;
return true;
}
X pop() {
if (index == 0) return NULL;
return box[--index];
}
Stack() { index = 0; }
};
int main()
{
Stack<int> i_obj;
i_obj.push(10);
i_obj.push(100);
cout << i_obj.pop() << endl;
cout << i_obj.pop() << endl;
Stack<const char*> s_obj;
s_obj.push("Hello!");
s_obj.push("Template!");
cout << s_obj.pop() << endl;
cout << s_obj.pop() << endl;
return 0;
}
テンプレート関数とは異なり、テンプレートクラスの場合は
「Stack<int> i_obj」や「Stack<const char*> s_obj」のように、扱う型を明示しないとエラーになるので注意
C++テンプレートことはじめ - Qiita
https://qiita.com/a-beco/items/7709ab80a05ea2d09ea8
■例外処理
$ vi class9.cpp
#include <iostream>
using namespace std;
int main()
{
try {
throw "Exception!";
cout << "Hello, World!" << endl;
}
catch (const char *str) {
cout << str << endl;
}
cout << "Complete!" << endl;
return 0;
}
$ vi class10.cpp
#include <iostream>
using namespace std;
class Hello {
public:
void print(int num);
};
void Hello::print(int num) {
if (num < 1) {
throw "Exception!";
}
cout << "[" << num << "]" << endl;
}
int main()
{
try {
Hello obj;
obj.print(2);
}
catch (const char *str) {
cout << str << endl;
}
try {
Hello obj;
obj.print(-1);
}
catch (const char *str) {
cout << str << endl;
}
return 0;
}
■make
クラスの宣言をヘッダファイルに書き、クラスの実装をソースコードに書く
クラスを使いたいときは、ヘッダファイルをインクルードする
二重定義を防ぐために、ヘッダファイルには二重定義防止コードを書いておく
$ vi main.cpp
#include <iostream>
#include "Test1.h"
#include "Test2.h"
using namespace std;
int main()
{
Test1 a;
a.func();
Test2 b;
b.func();
cout << "Hello, World!" << endl;
return 0;
}
$ vi Test1.h
#ifndef __TEST1_H_INCLUDED__
#define __TEST1_H_INCLUDED__
class Test1
{
public:
void func();
};
#endif
$ vi Test1.cpp
#include <iostream>
#include "Test1.h"
using namespace std;
void Test1::func()
{
cout << "Test1::func" << endl;
}
$ vi Test2.h
#ifndef __TEST2_H_INCLUDED__
#define __TEST2_H_INCLUDED__
class Test2
{
public:
void func();
};
#endif
$ vi Test2.cpp
#include <iostream>
#include "Test2.h"
using namespace std;
void Test2::func()
{
cout << "Test2::func" << endl;
}
makeを使わずにビルドする場合
毎回適切にビルドを実行する必要がある
$ g++ -c -o main.o main.cpp
$ g++ -c -o Test1.o Test1.cpp
$ g++ -c -o Test2.o Test2.cpp
$ g++ -o main main.o Test1.o Test2.o
$ ./main
makeを使ってビルドする場合
Makefileというファイルに処理条件を書いておくと、makeと書くだけで適切にビルドが実行される
$ vi Makefile
#@range_begin(rules)
CC = g++
CFLAGS = -g -Wall
OBJS = main.o Test1.o Test2.o
main: $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS)
main.o: main.cpp
$(CC) -c $(CFLAGS) -o $@ $<
Test1.o: Test1.cpp
$(CC) -c $(CFLAGS) -o $@ $<
Test2.o: Test2.cpp
$(CC) -c $(CFLAGS) -o $@ $<
#@range_end(rules)
$ make
$ ./test
ロベールのC++教室 - 第7章 ファイルを分けよう -
http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02007.html
ロベールのC++教室 - 第70章 仰山のファイル -
http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01070.html
ヘッダーと.cppを作ってコンパイル
http://marupeke296.sakura.ne.jp/IKDADV_PI_CPP2.html