7839

雑草魂エンジニアブログ

【C/C++】C++言語文法(2)

前回のC++言語文法の続き。

C言語との違い

関数の引数

  • C言語:値渡し、ポインタ渡し
  • 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言語:原則、関数の先頭でローカル変数を定義しなければいけない
  • C++言語:どこで宣言してもいい

構造体

  • 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言語:存在しない
  • C++言語:bool型が追加された

クラスの相互参照

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修飾子を用いると、関数の部分が処理の部分に直接埋め込まれるため、処理呼び出しなどのオーバーヘッドが少なくなり、処理速度が向上するメリットがある
  • 頻繁に使われる、かつ処理が長い場合には、ビルドされたソースコードが大きくなりすぎる等のデメリットが生じることがある

f:id:serip39:20211220011807j:plain

演算子オーバーロード

戻り値の型 operator演算子(引数…)

// 例) =演算子のオーバーロード
Vector2 &Vector2::operator=(const Vector2 &v)
{
  x = v.x;
  y = v.y;
  return *this;
}

オーバーロードすることで、処理をシンプルに実装することができる。

まとめ

C++言語の基礎文法について、一通り学ぶことができた。STLなど、まだ全てを理解できていないので、これから実践を通して理解を深めていきたい。
競技プログラミングで、アルゴリズムなども学びつつ、腕を磨いていきたい。