【C/C++】C++言語文法(2)
前回のC++言語文法の続き。
- C言語との違い
- クラスの相互参照
- const修飾子
- テンプレート
- STL(Standard Template Library)
- 仮想関数(virtual修飾子)
- 純粋仮想関数
- 抽象クラス
- inline修飾子
- 演算子のオーバーロード
- まとめ
C言語との違い
関数の引数
ポインタ渡し
引数 | 関数の呼び出し | 関数の定義 |
---|---|---|
変数のアドレス, ポインタ | func1(&a)//a:int型変数func1(p)//p:int型ポインタ | void func1(int *a)ポインタで受け取る |
参照渡し
引数 | 関数の呼び出し | 関数の定義 |
---|---|---|
変数の値 | func2(a)//a:int型変数func2(*p)//p:int型ポインタ | void func2(int &n)アドレスで受け取る |
ポインタ渡しと参照渡しの違い
- 呼び出す際に、参照渡しの場合は、変数の名前をそのまま記入すればいい
- 関数定義の引数には、
&<-name->
を用いる
変数宣言
構造体
- C言語:使用時には、
struct
をつける(構造体の宣言時に、typedef
を使うことで、型として定義することができる。) - C++言語:使用時には、
struct
をつける必要はない(typedefをせずにも、型として使うことができる。)
struct DATA { int n; double d; }; int main(){ struct DATA dt; // C style structure declaration DATA dt; // C++ style structure declaration }
bool型
true
/false
を定義する
クラスの相互参照
C++言語では、多数のクラスが存在し、互いに参照する。どちらか一方が他方を一方的に利用・参照する場合は、#inlude "他方.h"
するだけでいい。しかしながら、互いに参照し合う場合に、互いに#include
すると、ビルドエラーが発生する。(#includeのループが発生して、終わらない。)
そのため、以下の方法で実装する必要がある。
- ヘッダファイルでは、参照するクラスのクラス名を記述するだけ(前方宣言)で、クラスを参照することができる。
- ソースファイルで必要なヘッダファイルを
#include
する。
#ifndef _ASAMPLE_H_ #define _ASAMPLE_H_ class BSample; //クラスBSampleへの参照 class ASample { private: BSample *m_pBsample; public: ASample(); ~ASample(); void test(); } #endif // _ASAMPLE_H_
#include "asample.h" #include "bsample.h" //includeで読み込む ASample::ASample() { m_pBsample = new BSample(this); // thisポインタ:自分自身を表すポインタ } ASample::~ASample() { delete m_pBsample; } ASample::test() { cout << "testA" << endl; }
#ifndef _BSAMPLE_H_ #define _BSAMPLE_H_ class ASample; //クラスASampleへの参照 class BSample { private: ASample *m_pAsample; public: BSample(ASample *pAsample); void test(); } #endif // _BSAMPLE_H_
#include "bsample.h" #include "asample.h" //includeで読み込む BSample::BSample(ASample *pAsample) { m_pAsample = pAsample; } BSample::test() { m_pAsample->test(); }
const修飾子
定義した変数を不変(後から別の値を代入することができない)にする。JavaScriptのconstと意味的にも似ている。
- コンパイラは最適化をしやすくなり、 処理速度を向上させたり、メモリを効率的に使用することができる
- 変数の使用方法に制限をつけることで、プログラミングの誤りを未然に防ぐことができる
.ex | 説明 | |
---|---|---|
const変数 | const int a = 23; | 定数の定義。定数として値を変更できない。 |
const引数 | void func1(const int a); | 引数の変更不可能。関数内では、引数の状態が変化しない。 |
constメンバ関数 | int func2() const; | メンバ変数の変更不可能。関数内では、メンバ変数の状態が変化しない。 |
ポインタと constを組み合わせる場合に、constの位置に注意。
*
の前にconst
を付けた場合、ポインタを指してる先が不変になる*
の後にconst
を付けた場合、ポインタ変数そのもの(アドレス)が不変になる
int value = 42; const int* p1 = &value; p1 = &value; // OK *p1 = 10; // NG int* const p2 = &value; p2 = &value; // NG *p2 = 10; // OK const int* const p3 = &value; p3 = &value; // NG *p3 = 10; // NG
テンプレート
template <typename T> T add(T x, T y) { return x + y; } // インスタンス化 add<int>(2,3);
- テンプレートクラス
template <typename T> class Foo{}; // インスタンス生成 Foo<int> foo;
自分でテンプレートを使うことはあまりない。次で説明する、STLを基本的には使う。
STL(Standard Template Library)
- テンプレートを用いた標準的なC++のライブラリ
名前 | 役割 |
---|---|
vector | 動的配列・可変長配列 |
list | 双方向連結リスト(indexで管理できない) |
map | 連想配列(key, value) |
set | 集合(データの重複を許さない) |
stack | スタック(LIFO (Last-In-First-Out)) |
queue | キュー(FIFO(First-In-First-Out)) |
イテレータ
抽象化されたポインタのことで、要素を指し、移動、要素を参照・変更することが出来る
// イテレータの宣言 list<int>::iterator itr;
*演算子
により、イテレータが指す先を参照・変更することが出来るbegin()
、end()
でイテレータを取得できる++演算子
により、イテレータを進めることが出来る
仮想関数(virtual修飾子)
virtual
修飾子がついたメンバ関数を、仮想関数と呼ぶ。- 仮想関数は、スーパークラスから、サブクラスに実装されたメンバ関数を呼び出したい場合に用いる。(オーバーライドは、スーパークラスを継承したサブクラス内に同一の関数があった場合に、サブクラスのメンバ関数で上書きすること。)
- 継承して利用される可能性のあるクラスのデストラクタは、必 ず仮想デストラクタ(仮想関数にしたデストラクタ)にする
純粋仮想関数
virtual void func()=0;
- 仮想関数に、
=0
がついている場合、実装が省略されている、純粋仮想関数という - 純粋仮想関数は、メソッドそのものは存在するが、実装がないメソッドである
- 実装は、このクラスを継承したサブクラス(子クラス)にされることが前提となる
抽象クラス
- 純粋仮想関数を一つでも持つクラスのことを、抽象クラスという
- 抽象クラスの最大の特徴は、インスタンスを作ることが出来ない
インターフェース
- 純粋仮想関数のみからなるクラスを、インターフェースと呼ぶ
- 具体的な定義は、サブクラスに任せており、共通の型を提供する(インターフェースは、そこから派生したクラスが「何ができるか」を表現するものである。can-do関係と呼ばれる。)
- インターフェースを利用することのメリットは、ポインタを渡す相手のクラスに対して、機能を制限したい場合などに有効に働く
- よくある命名規約として、インターフェースの名前の先頭に、Interface を表す「I」を付ける
class IInf1 { public: virtual void func1() = 0; virtual void func2() = 0; }; class IInf2 { public: virtual void func3() = 0; virtual void func4() = 0; }; class CSample : public IInf1, public IInf2 { public: void func1() { cout << "func1" << endl; } void func2() { cout << "func2" << endl; } void func3() { cout << "func3" << endl; } void func4() { cout << "func4" << endl; } }; int main() { IInf1* inf1 = new IInf1(); //func1, func2のみアクセス可能 IInf2* inf2 = new IInf2(); //func3, func4のみアクセス可能 inf1->func1(); inf1->func2(); inf2->func3(); inf2->func4(); return 0; }
inline修飾子
inline
修飾子が、先頭につくと、その関数はコンパイル時にインライン展開される- inline宣言されていない普通の関数は、コンパイルされたアセンブラの中で、プログラムの流れの中と別の部分に記述され、必要な時だけ呼び出される
- inline修飾子を用いると、関数の部分が処理の部分に直接埋め込まれるため、処理呼び出しなどのオーバーヘッドが少なくなり、処理速度が向上するメリットがある
- 頻繁に使われる、かつ処理が長い場合には、ビルドされたソースコードが大きくなりすぎる等のデメリットが生じることがある
演算子のオーバーロード
戻り値の型 operator演算子(引数…) // 例) =演算子のオーバーロード Vector2 &Vector2::operator=(const Vector2 &v) { x = v.x; y = v.y; return *this; }
オーバーロードすることで、処理をシンプルに実装することができる。
まとめ
C++言語の基礎文法について、一通り学ぶことができた。STLなど、まだ全てを理解できていないので、これから実践を通して理解を深めていきたい。
競技プログラミングで、アルゴリズムなども学びつつ、腕を磨いていきたい。