LOGv:20171214

C++ことはじめ

はじめに

C++を始めるに当たって勉強しながら書いた備忘録です。
間違いなどあれば、yokotak0527にメンションもらえると助かります。

事前知識

コンパイル

コンパイラ(処理系とも)呼ばれるプログラムがソースファイルを解析(コンパイル)しオブジェクトファイルという中間ファイルが作られて、中間ファイルを基に実行ファイルが生成される。

コンパイラはフロントエンド/ミドルエンド/バックエンドに分けられソースファイルから実行ファイルまでのコンパイル作業を分担している。
※ ミドルエンドはバックエンドと一緒になっていることもある。

       [ソースファイル]        [ソースファイル]
             ↓                      ↓
             ↓                      ↓
-------------- コンパイラフロントエンド ------------------ 解析
             ↓                      ↓
             ↓                      ↓
      [オブジェクトファイル]  [オブジェクトファイル]
             ↓                      ↓
             ↓                      ↓
---- コンパイラミドルエンド / コンパイラバックエンド ----- リンク(オブジェクトファイルを束ねる作業)など
             ↓                      ↓
             ↓                      ↓
              →→→→→→←←←←←←
                         ↓
                         ↓
                   [実行ファイル]

このように実行ファイルが生成されるまでのコンパイル作業を分割することで間に別の処理を入れたり
多言語への出力を可能にしたりしている。

コンパイラフロントエンドとしてclang(クラン)
コンパイラミドルエンド / コンパイラバックエンドとしてLLVM
の組み合わせがよく使われている。

逐一コマンド叩くのが面倒臭いのでGulpでタスクランナーを作った。
c++task runner

ビットとバイトの関係性

情報の最小単位はビット
ビットは0か1のみを表す。1ビットであれば0か1の2種類のデータしか表せないため
8ビットを1組として単位をバイトと表す。
つまり1バイトだと8ビットなので、

|0|0|0|0|0|0|0|0| ~ |1|1|1|1|1|1|1|1|

0~255までの256種類のパターンを表すことができる。

ヘッダファイル

ソースファイルでincludeディレクティブを使いソースファイルに展開される外部ファイルのことを「ヘッダファイル」という。
ヘッダーファイルには関数プロトタイプや外部リンケージ付きの変数宣言、定数などを定義しておく。
そうすることで複数のソースファイルでそのヘッダファイルをincludeすれば宣言の共有が出来るので便利

詳細は後述

変数

変数は定義位置や記憶領域によって様々な名称がある。

定義位置外部変数 (大域変数、グローバル変数)
内部変数 (局所変数、ローカル変数)
記憶領域静的変数
動的変数 (自動変数)

外部変数

  • 関数の外側に定義された変数
  • 全ての関数から参照出来る
  • 外部変数は静的変数である

内部変数

  • 関数の内側で定義された変数
  • 定義した関数内からしか参照できない
  • 内部変数は動的変数である (static指定子で静的にもなれる)

静的変数

  • メモリ上の静的記憶域(スタティック領域)に割り当てられる
  • 初期化は1度しか行われない
  • 初期化されなければ0で初期化される
  • 値は残り続ける

動的変数

  • メモリ上の自動記憶域(スタック領域)に割り当てられる
  • 初期化は呼び出される度に都度行われる
  • 初期化されなければ値は不定 (エラー)
  • 読み出し元が処理を終了すれば値は消滅する

記憶クラス

変数や関数の保存方法を定義する。
具体的には変数や関数の前に記憶クラス指定子をつけて宣言する。
もし記憶クラス指定子が無ければ変数のデフォルトの保存方法に従う。

記憶クラス指定子保存方法
auto自動記憶域
extern外部参照
static静的記憶域
registerレジスタ域
static int num = 1;

スタックオーバーフロー

自動記憶域(スタック域)は容量があまり大きくない。
メモリ領域からメモリが溢れ出してしまうことをオーバーフローという。
スタックオーバーフローとはスタック域からメモリが溢れた状態を言い、溢れた場合違う領域に書き込みが行われてしまう。


基本的な構文

int main(){
    return 0;
}

プログラムの始まりはmain関数を用意する必要がある。

前処理指令

#前処理字句から始まる前処理指令(プリプロセッサディレクティブ)を使うとコンパイラがソースファイルを解析するよりも前に処理をすることができる。
外部ファイルの読み込み、マクロの定義やインライン関数などに利用する。

#[ディレクティブ] [命令]

※ 行末のセミコロンは不要

// 例
#define MY_MACRO cout << "hello" << endl

int main(){
  MY_MACRO; // ソース解析を行う前に、この位置に上で定義したマクロが挿入される
}
ディレクティブ説明
#include外部ファイルなどを展開する
#ifndefif not definedの略。if文。もし命令が定義されていなければ
#endifif文の終了
#defineマクロを定義
#undefマクロを無効化する

includeディレクティブ

ファイルの先頭ではincludeディレクティブを使い外部ファイルをソースファイルに「展開」させることが出来る。
具体的には以下のように書く。

#include "myHeader.h"
#include <iostream>

「”xxx”」と書けばプロジェクトのフォルダを優先的に検索し
「<xxx>」と書けば設定されたフォルダを優先的に検索する。

includeディレクティブを使い読み込まれる外部ファイルを「ヘッダファイル」と言い、iostreamなど標準で用意されているヘッダファイルを「標準ヘッダファイル」、標準ヘッダファイルの中に書かれている関数を「標準関数」という。
※ ヘッダファイルに対して.cppのファイルをソースファイルという。

マクロ

includeディレクティブのように外部ファイルをソースファイルに展開するではなく、指定した位置にテキストを展開する仕組みのことをマクロという。
defineディレクティブで定義する。

#define [識別名] [定義]
#define [識別名](仮引数) [定義]

決まりではないがマクロは大文字を使用する

// 例
#define MY_MACRO cout << "hello" << endl
MY_MACRO; // これがマクロ
// ソース解析を行う前に、この位置に上で定義したマクロが挿入される

テキストに置換されることを「マクロ展開」と言い、関数のようにマクロを定義することもできる。

#define MY_MACRO(c, n) for(int c = 0; c < n; c++)
MY_MACRO(i, 5){
    cout << i << endl; // 0,1,2,3,4
};

複文をマクロにしたい場合はブロックを使い1つの文として認識させる

#define MY_MACRO \
{ \
    string str = "hello";\
    cout << str << endl;\
     \
}
MY_MACRO;

マクロとインライン関数の違いは以下の通り

マクロコンパイル前に指定位置に展開
インライン関数コンパイル中に処理を埋め込む

変数/関数

// 変数の宣言と初期化
int num1 = 10;

int& num2 = num1; // 参照渡しは型の後に&を付ける

int arr1[2]; // 配列の宣言はは変数名の後に[]を付けて中にエレメント数を指定する
arr1[0] = 1;
arr1[1] = 2;

int arr2[] = {1,2,3};    // 配列作成時に{}を使って初期化することが出来る。この場合エレメント数は省略できる。
cout << arr2[1] << endl; // 2

char string[] = "hello"; // 文字の配列の場合は{}すら省略できる。
cout << string[0] << endl; // h
cout << string[2] << endl; // l
// 関数の定義と呼び出し
int add(int a = 0, int b){ // デフォルト引数の定義が出来る。
    return a + b;
}
void func(){
    cout << "not return anything" << endl;
}

cout << add(1,2) << endl; // 3

void func(int num){ // 引数が違えば同じ名前の関数を定義出来る。(オーバーロード)
    cout << "same function name" << endl;
}

補足:変数について

変数のサイズを確認する

変数のサイズを確認するにはsizeof演算子を利用する

int arr = {1,2,3,4,5};
cout << sizeof arr << endl; // 20

int型のサイズは4バイト 配列「arr」のエレメント数は5なので20が返る
※ sizeofが返す値はsize_t(tはtypeの略)でこいつは符号無しの整数型なので演算するときは注意

バッファオーバーラン

int arr[] = {1,2,3};
cout << arr[10] << endl;

上のように範囲外の配列を指定したりするとバッファオーバーラン(バッファオーバーフロー)が発生し別の変数にアクセスしてしまう可能性があるので注意

配列が返すもの

※ この項では後述のポインタ操作をしている

int arr[] = {1,2,3};
cout << arr << endl; // 0x7fff55c5e6ccなど

配列そのものを出力するとアドレスが返る。
では以下のようにするとどうなるか

cout << *arr << endl; // 1

結果は「arr[0]」の値が出力された。
ということは

cout << arr << endl;     // 0x7fff55c5e6cc
cout << &arr[0] << endl; // 0x7fff55c5e6cc

同じアドレスになる。
つまり配列が返すアドレスは配列そのもののアドレス(無い)ではなく、1つめのエレメントのアドレスを返す。
配列のエレメントはメモリ上に連続して存在しているのでこれで問題がない。

配列のエレメント数を調べたい

配列のサイズ / 1つの配列のサイズ で出す。

int arr = {1,2,3,4,5};
cout << sizeof arr / sizeof arr[0] << endl; // 5

文字の配列の注意点

char arr[] = "hello"; // 文字列は「"」で括る
cout << sizeof arr << endl; // 6

まず、char型は1バイトなのでsizeof演算子 = 文字数のはず。
「hello」は5文字なので「5」が出力されるのを期待するが、結果は6
これは文字の配列の最後に文字の終わりを表すヌルターミネータ(コード番号0の文字)が入る仕様のため。
それぞれの文字のコード番号を出してみると

// char型をint型にキャストして文字コードを出す
cout << (int) arr[0] << endl; // 104
cout << (int) arr[1] << endl; // 101
cout << (int) arr[2] << endl; // 108
cout << (int) arr[3] << endl; // 108
cout << (int) arr[4] << endl; // 111
cout << (int) arr[5] << endl; // 0

と確かに0が入っている。
※ 0は正確には\0

補足:定義順と関数プロトタイプ

以下の例はエラーになる。

int func1(int num){
    return func2(); // func1定義時にfunc2はまだ定義されていないためエラー
}
int func2(int num){
    return c * num; // func2定義時に変数cはまだ定義されていないためエラー
}
int c = 2;

int main(){
    int num = func1();
    return 0;
}

JSみたく関数の巻き上げなどは発生しない。
解決方法として

  1. 変数:c
  2. 関数:func2
  3. 関数:func1
  4. 関数:main

の順に書く方法があるが、他の例として変数は「リンケージ」を利用。関数は「関数プロトタイプ」を利用するという方法がある。

extern int c; // リンケージについては後述
int func2(int num);

int func1(int num){
    return func2();
}
int func2(int num){
    return c * num;
}
int c = 2;

int main(){
    int num = func1();
    return 0;
}

関数プロトタイプは関数のブロック部分がないもので、関数の定義を後回しに出来る。
※ デフォルト引数がある場合、関数プロトタイプと関数の定義両方にデフォルト引数を定義するとエラーになるため、関数プロトタイプにデフォルト引数を定義する。

多重配列

配列の宣言時に初期化をすることでサイズを省略することができるが、階層は1階層目だけという制限がある。
そのため、多重配列など以下の場合だとエラーになる。

int arr[][] = {1,2,3};

この場合

int arr[][3] = {1,2,3};

とする必要がある。

リンケージ

ファイルを超えて利用出来るかどうかをリンケージという。
リンケージは「外部リンケージ」(ファイルを超えて利用可能)と「内部リンケージ」(そのファイルでのみ利用可能)の2種類がある。
リンケージの利用は記憶クラス指定子を利用する。

名称記憶クラス指定子保存方法利用可能範囲
外部リンケージextern外部参照ファイルを超えて利用可能
内部リンケージstatic静的記憶域そのファイルでのみ利用可能

内部リンケージでは変数は静的記憶域に保存される。
つまりmain関数が実行される前にメモリが割り当てられるのでクロージャみたいなことができる。

// 内部リンケージ
int func(){
    static int num;
    // 静的変数は初期化しなければ0で初期化される。
    // 静的変数の初期化は1度しか行われない。
    return num++;
}

int main(){
    func(); // 0
    func(); // 1
}

関数プロトタイプのリンケージ

関数プロトタイプは今までの宣言や関数の実体のリンケージを継承し、今までにリンケージがなければ外部リンケージになる。
※ でもまぁexternって明記した方がわかりやすいような。。

定数のリンケージ

定数は内部リンケージを持つ

ポインタ

int num = 1;
cout << &num << endl; // 0x7fff5763b6c8など。 変数の前に&をつけると変数のアドレスを返す

int* p = &num; // ポインタは型と変数の間に「*」を付ける。つまり int * pでもint *pでも良い。
std::cout << *p << std::endl; // 1 // ポインタの変数に「*」を付けるとそのアドレスにある値を返す

ポインタに文字列リテラル

ポインタに文字列リテラルを渡すと文字列リテラルのアドレスが代入される

const char* str = "hello";

リテラルなので静的記憶域に置かれており、値が破棄されることはない。
また、文字列リテラルを直接書き換えるようなことをしてはいけない。

ただし以下のようの配列を使った場合は文字列を直接指していない。

char str[] = "hello";

この場合は文字の配列になるため、ローカル変数の場合だと関数の処理が終わったタイミングで破棄される。
ちなみに、文字リテラルは「const char[n]」という配列型

(まだ)参照渡しとポインタ渡しの違い

定数

定数はconstを付けて指定する

const int num = 10;

ここで注意したいのは「constの右側にあるものが定数になる」という点。
例えば以下の場合エラーが発生しない。

int num1 = 1;
int num2 = 2;
const int* p = &num1;
p = &num2;
cout << *p << endl;

変数pの宣言部分は
「const int* 」となっている。
constの右側にあるものはintを取るポイント型つまりnum1のアドレスであり、
この指定ではポインタpを経由したnum1の変更ができなくなり、p自体は変数のままである。
変数p自身も定数にする場合は以下のようにする。

const int* const p = &num1;

構造体

構造体はJSで言うとこのオブジェクト (ただしkey/valueではない)
構造体を定義してインスタンスを作成するので、型に相当する
※ つまりインスタンスを作成するまでメモリ上にはない。

int main(){
    struct User {
        string name;
        int age;
    };

    Uesr yokota;
    yokota.name = "yokota";
    yokota.age  = 31;

    User yamada = {
        "yamada";
        21
    };
}

インスタンスを作成時に初期化も行えるがメンバ変数を定義した順に指定する必要がある。

列挙体

指定した値しか受け付けない
列挙体変数は整数値を持つ。整数値以外は不可
値は連続していないても良い

enum Sex {
    MALE   = 0,
    FEMALE = 1,
    OTHER  = 2
};
// 行末はカンマで最後の列挙体変数の行末にはカンマは不要。
// あってもエラーにはならない。

enum Country {
    JAPAN, // 0
    CHINA  // 1
};
// 値を省略した場合0から定義される

Sex yokota = MALE;

構造体と同じようにインスタンスを作成しないとメモリ上にはない。

new と delete

プログラムの処理中に動的にメモリを確保したい場合がある。
そういった場合はnewを使いメモリを動的に確保する。

int* p = new int;
*p = 1;
delete p;

// 配列だけ少し特殊
int* arr = new int[10];

delete [] arr;

new をするとポインタが返るので値はポインタ型にしておく必要がある。
動的に確保されたメモリはフリーストア域(≒ヒープ)に書き込まれ、deleteするまで残る。
つまり不要になれば確実に削除する必要がある。

インライン関数

ヘッダファイルに書く内容

1つのヘッダーファイルが複数のソースファイルからインクルードされることは普通にある。
そうすると2重適宜になりエラーになるのでヘッダーファイルの先頭で

#ifndef XXXXXXX
#define XXXXXXX
...
#endif

と前処理指令で囲んでやることでヘッダーファイルの中身を1度だけ読み込ませることが出来る。
またヘッダーファイルには基本的に以下の内容を書く

  • マクロ
  • 定数
  • 関数プロトタイプ
  • 外部リンケージ
  • テンプレート

クラス

class MyClass{ // クラスの定義はclassから始まる
    public: // アクセス修飾子
        int num;
        MyClass(); // コンストラクタは返り値がないので返り値は書かない。
        void show();
};

MyClass::MyClass(){
    num = 10;
}

void MyClass::show(){
    cout << num << endl;
}

int main() {
    MyClass instance;
    // もしくは
    // MyClass instance = MyClass(); これは MyClass instance; とほぼ同等
    instance.show(); // 10
    instance.num = 2;
    instance.show(); // 2
}

classブロックの中で行うのは宣言のみで定義は個別に行う。
そのためメンバ関数(コンストラクタ、デストラクタ含め)関数プロトタイプが必須である。
関数の定義は以下の様に行う。

[クラス名前]::[メンバ関数名](){
}

関数のプロトタイプのため、オーバーロードが可能である。

class MyClass{
    public:
        int num;
        MyClass();
        MyClass(int _num);
        void show();
};

MyClass::MyClass(){
    num = 10;
}

MyClass::MyClass(int _num){
    num = _num;
}

同じアクセス修飾子は複数あっても良い。

class MyClass{
    public:
        int num;
    public:
        int  getNum();
        void show();
};

という風にメンバ種別ごとに分けたら良いかも

動的にクラスを作成する

newを使うことでインスタンスの動的確保を行える。
こっちの方がかなり馴染みがある。

class MyClass{
    public:
        void func();
};

MyClass::func(){
  cout << "hello" endl;
}

int main(){
  MyClass* instance = new MyClass(); // インスタンスの動的確保
  instance->func(); // hello
  delete instance; // 明示的に削除
  return 0;
}

動的にメモリ確保を行うのでポインタ。
ポインタ経由でのメンバ関数の指定はアロー演算子を利用。

コンストラクタ

コンストラクタは返り値が無い。これはvoidと言う意味ではなく何も無い。
引数が無いコンストラクタのことをデフォルトコンストラクタと言い、
引数の有無に関わらずコンストラクタを定義していなければ勝手にデフォルトコンストラクタが作成される。
逆に言うと1つでもコンストラクタが定義されていれば自動で作成されることはない。

引数がある場合は関数のように定義すれば良い。

  MyClass instance("yokota", 31);
  // もしくは MyClass instance = MyClass("yokota", 31);

デストラクタ

オブジェクトの寿命がきたタイミングで自動で呼ばれる関数
コンストラクタが生ならデストラクタは死

コンストラクタでnewを使い動的にメモリ確保を行った変数などの削除をデストラクタで行うと効率が良い。

class MyClass{
    public:
        ~MyClass();
}

MyClass::~MyClass(){
  //...
}

実際にデストラクタを使う時は後述の仮想関数にした方が良い。

class MyClass{
    public:
        virtual ~MyClass(); // virtualとつける
}

MyClass::~MyClass(){
  //...
}

コピーコンストラクタ

以下の場合エラーになる。

class MyClass{
    public:
        int* num;
        MyClass(int);
        ~MyClass();
};

MyClass::MyClass(int _num){
    num = new int;
    *num = _num;
}

MyClass::~MyClass(){
    delete num;
}

void show(MyClass myClass){
    cout << *myClass.num << endl;
}

int main() {
    MyClass var1 = MyClass(2);
    show(var1);
    return 0;
}

仮引数は関数を呼んだタイミングで変数が作成され、その変数が実引数の値で初期化される。
そのため、関数showの仮引数myClassはvar1を丸々コピーしたものである。

この場合、var1.numはポインタを表しているためコピーされた関数showのmyClassのnumと
var1.numは同じアドレスを指している。

関数showが呼ばれるタイミングあvar1の処理は終了しておりデストラクタでvar1.numは破棄されている状態であるため、関数showはエラーになる。

実はコピーする際にもコンストラクタが呼ばれており、これはコピーコンストラクタと呼ばれる。
こういった状態の時はコピーコンストラクタを使用する。

class MyClass{
    public:
        int* num;
        MyClass(int);
        MyClass(const MyClass& instance); // コピーコンストラクタ
        ~MyClass();
};

MyClass::MyClass(int _num){
    num = new int;
    *num = _num;
}

// コピーコンストラクタの定義
MyClass::MyClass(const MyClass& instance){
    num  = new int;
    *num = *instance.num;
}

MyClass::~MyClass(){
    delete num;
}

void show(MyClass myClass){
    cout << *myClass.num << endl;
}

int main() {

    MyClass var1 = MyClass(2);
    show(var1);
    // cout << var1.num << endl;

    return 0;
}

コピーコンストラクタはconst参照を取ることが多い。

静的メンバ

static指定子付きのメンバ変数を定義するには以下の通りになる。

class MyClass{
    public:
        static int publicStaticNum;
    private:
        static int privateStaticNum;
}

// 初期化
MyClass::publicStaticNum  = 10;
MyClass::privateStaticNum = 20;

static指定子をつけた場合はメモリの扱いが以下の通りになるので注意

  • メモリ上の静的記憶域(スタティック領域)に割り当てられる
  • 初期化は1度しか行われない
  • 初期化されなければ0で初期化される
  • 値は残り続ける

定数メンバ

class MyClass{
    public:
        const int a = 10;
};

constを返すメンバ関数

class MyClass{
    public:
        const int func();
}

とすると返り値がconstになる。

const変数から呼べる関数

const変数(constオブジェクト)はconstメンバ関数しか呼べない。
constメンバ関数であるとコンパイラに認識させるには以下のように宣言する。

class MyClass{
    public:
        int func() const;
}

ここでのconstはオブジェクトの変数を変更しないことを宣言している。
そのため、constオブジェクトからでもアクセスが出来る。

継承

クラスの継承は以下のように書く

class [派生クラス名] : アクセス修飾子 基底クラス名 {

};

アクセス修飾子はpublicにすると基底クラスの修飾子を継承し、privateにすれば基底クラスのメンバをprivateにする。

コンストラクタとデストラクタの実行順は以下の通り。

  1. 基底クラス コンストラクタ
  2. 派生クラス コンストラクタ
  3. 派生クラス デストラクタ
  4. 基底クラス デストラクタ

明示的にコンストラクタを呼ぶ

基底クラスのコンストラクタは自動で呼ばれるが
ここで呼ばれるコンストラクタはデフォルトのものである。
引数付きのコンストラクタなどコンストラクタを指定したい場合は以下のようにする。

// 派生クラスのコンストラクタの定義部分で
[派生クラス名]::[派生クラス名]() : [基底クラス名](){

}

具体的には

class ParentClass{
};

class ChildClass : public ParentClass{
    public:
        ChildClass();
};

ChildClass::ChildClass() : ParentClass(){
}

protected修飾子

以下の場合、基底クラスのprivateメンバ変数numには派生クラスのfunc関数からアクセスができない。

class MyClass{
    private:
        string name;
    public:
        void func();
        MyClass();
};

MyClass::MyClass(){
    name = "hello";
}

class ChildClass : public MyClass{
    public:
        void func();
};

void ChildClass::func(){
    cout << name << endl;
}

private修飾子は派生クラスでも利用できない。
派生クラスで利用したい場合はprivateではなくprotected修飾子を利用する。

アップキャスト

class MyClass{
    protected:
        string name;
    public:
        MyClass();
        void parentFunc();
};

MyClass::MyClass(){
    name = "hello";
}

void MyClass::parentFunc(){
    cout << "wow" << endl;
}

class ChildClass : public MyClass{
    public:
        void func();
};

void ChildClass::func(){
    cout << name << endl;
}

int main() {
    ChildClass childClass = ChildClass();

    MyClass* p = &childClass;
    p->parentFunc(); // ポインタなのでドット演算子ではなくアロー演算子
}

ポインタpはMyClass型のポインタであるが初期化の際にMyClassの派生クラスChildClassのインスタンスを参照するようにしている。
にも関わらず、MyClassのメンバ関数をp経由で呼び出すことができている。
これをアップキャストと言い派生クラスから基底クラスへの暗黙のキャストが行われている。
そのため、ChildClassのメンバ関数funcにはp経由でアクセスすることはできない。

関数オーバーライド

class ParentClass{
    public:
        void func();
};

void ParentClass::func(){
    cout << "parent" << endl;
}

class ChildClass : public ParentClass{
    public:
        void func();
};

void ChildClass::func(){
    cout << "child" << endl;
}

int main() {
    ChildClass instance = ChildClass();
    instance.func(); // child
}

上の場合、期待通りに動くがアップキャストを使った場合期待しない動きになる。

// クラス定義は上と同じ

int main() {
    ChildClass instance = ChildClass();
    ParentClass* p = &instance;
    p->func(); // parent
}

これを防ぐには基底クラスのメンバ関数にvirtual修飾子をつける。

class ParentClass{
    public:
        virtual void func();
};

このvirtual修飾子のメンバ関数のことを「仮想関数」という。
仮想関数を使うと、例外を除いてそのオブジェクトの本来の型のものが呼ばれるようになる。
※ 基本virtual修飾子はつけること。

純粋仮想関数

実装を持たない仮想関数を純粋仮想関数という。
以下のように定義する。

class MyClass{
    public:
        virtual void func() = 0;
}

ただし純粋仮想関数を持つクラスはインスタンスを作成することが出来なくなる。
※ 参照やポインタは可能

純粋仮想関数を持つクラスを「抽象クラス」という。

スコープ解決演算子

オーバーライドされる前のメンバ関数を呼びたい時は

[クラス名]::[関数名]()

の形で呼ぶ

class ParentClass{
    public:
        virtual void func();
};

void ParentClass::func(){
    cout << "parent func" << endl;
}

class ChildClass : public ParentClass{
    public:
        ChildClass();
        void func();
};

void ChildClass::func(){
    cout << "child func" << endl;
}

ChildClass::ChildClass(){
    ParentClass::func(); // parent func
}

int main() {
    ChildClass* instance = new ChildClass();
}

仮想デストラクタ

アップキャストを使った場合、以下のコードはChildClassのデストラクタが呼ばれない。

class ParentClass{
    public:
        ParentClass();
        ~ParentClass();
};
ParentClass::ParentClass(){
    cout << "parent constructor" << endl;
}
ParentClass::~ParentClass(){
    cout << "parent desstructor" << endl;
}

class ChildClass : public ParentClass{
    public:
        ChildClass();
        ~ChildClass();
};
ChildClass::ChildClass(){
    cout << "child constructor" << endl;
}
ChildClass::~ChildClass(){
    cout << "child destructor" << endl;
}

int main() {
    ParentClass* instance = new ChildClass();
    // parent constructor
    // child constructor
    delete instance;
    // parent desstructor
}

これはポインタinstanceがParentClass型のインスタンスのアドレスを指しているためで
ChildClassのデストラクタも呼び出したい時はParentClassのデストラクタを仮想関数にしてやる。

class ParentClass{
    public:
        ParentClass();
        virtual ~ParentClass();
};

このため、デストラクタは常に仮想関数にした方が良い。


名前空間

説明省略

namespace name1 {
    void func1(){
        cout << "name1::func1" << endl;
    }
    void func2(){
        func1(); // 同じ名前空間なので関数名だけで良い。
    }
    namespace childname{
        void func1(){
            cout << "name1::childname::func1" << endl;
        }
    }
}

// 名前空間は分割しても良い
namespace name1{
    void func3(){}
}

int main() {
    name1::func2(); // name1::func1
    name1::childname::func1();
}

usingを使うと名前空間を省略出来る。

// using指令(usingディレクティブ) 名前空間の省略
// using namespace [名前空間]
using namespace name1;

// using宣言 特定のオブジェクトだけ
// using [名前空間]::[識別子]
using name1::func;

エイリアスを作成することも出来る。

namespace childname = name1::childname;
childname::func1();

無名名前空間

名前のない名前空間を無名名前空間と言い内部リンケージ的な動作をする

namespcae {
  string msg = "hello";
}

テンプレート

関数テンプレート

関数テンプレート名前の通り関数のテンプレートを作成する。
例えば

int add(int a, int b){
  return a + b;
}

とあればint型しか引数出来ない。そのため実引数が小数点の場合などにも対応する場合、関数オーバーロードを利用して

double add(double a, double b){
  return a + b;
}

と定義する必要があるが正直やってられない。
そういった場合関数テンプレートを使い以下のようにまとめることが出来る。

template <typename NUMBER>
    NUMBER add(NUMBER a, NUMBER b){
        return a + b;
    }

「<…>」の部分はテンプレート引数と言い、だと仮引数に相当するので「テンプレート仮引数」とも言われる。
テンプレート仮引数は複数有ってもよく

template <typename NUMBER1, typename NUMBER2>
    NUMBER1 add(NUMBER1 a, NUMBER1 b){
        return a + b;
    }

というようにカンマ続きで定義する。

こうすることで関数が呼ばれた際に実引数を基にNUMBERが必要な型に代わり適宜実体が作成される。
※ 型推論?
関数テンプレートの注意点は以下の通り

  • 関数テンプレートは型に合わせた実体を適宜作成する。(型違いの自動関数オーバーロード的な)
  • 関数テンプレートの定義は呼び出し元から見える必要がある
  • 故に関数テンプレートはヘッダファイルに書く

型が推論出来ない

以下の場合は問題が発生する。

template <typename NUMBER>
    NUMBER add(NUMBER a, NUMBER b){
        return a + b;
    }

add(0.1, 1);

この場合、第1実引数はdouble型などである必要があり、第2実引数はint型でも良い。
そうすると返り値をどちらにすれば良いかがわからない。
こういった場合は呼び出し時に型を指定することが出来る。

add<double>(0.1, 1); // 1.1

クラステンプレート

クラスにも利用出来る
メンバ関数にテンプレートを利用する例

class MyClass{
    public:
        template <typename NUMBER>
            NUMBER func(NUMBER num);
};

template <typename NUMBER>
    NUMBER MyClass::func(NUMBER num){
        return num * 2;
    }

int main(){
    MyClass* instance = new MyClass();
    cout << instance->func(2) << endl; // 4
    delete instance;
}

クラスにテンプレートを適用する例

template <typename NUMBER>
    class MyClass{
        public:
            NUMBER func(NUMBER num);
    };

template <typename NUMBER>
    NUMBER MyClass<NUMBER>::func(NUMBER num){
        return num * 2;
    }

int main() {
    MyClass<int> instance; // 実体作成時にテンプレートの実引数を渡す必要がある。
    cout << instance.func(2) << endl; // 4

    // 動的メモリ確保の場合
    MyClass<int>* instnace = new MyClass<int>();
    cout << instnace->func(5) << endl; // 10
    delete instance;

    return 0;
}

クラステンプレートの場合型推論が出来なくなるので、都度テンプレート実引数を渡す必要がある。


デバッグ

NDEBUG / assertマクロ

assertというマクロは引数に条件式を取り、条件がtrueであれば何もせず
falseであればエラーが発生したファイル名、行番号、条件式を出力しエラー終了する。

このマクロはNDEBUGマクロが定義されていれば何もしない。

#include <cassert> // assetが定義されているヘッダファイル

string name = "yokota";
assert(name == "yamada");
// Assertion failed: (name == "yamada"), function main, file ./main.cpp, line 14.

以下の場合NDEBUGが定義されているのでassertの処理は走らない。
そのため、NDEBUGはcassertの展開より先に定義しておく必要がある。

#define NDEBUG
#include <cassert>

string name = "yokota";
assert(name == "yamada");

CLIでコンパイルする際に「-D」オプションでマクロを定義できる

$ clang++ -DNGDEBUG -o ./main.cpp ./dest/main

-Dの後にスペースは開けない。

open close