C++
  1. 標準入出力

  2. スコープ解決演算子

  3. インライン関数

  4. const 修飾子

  5. 参照

  6. 列挙型

  7. typedef 指定子

  1. クラス(基本)

  2. クラス(初期化と破棄)

  3. クラス(ファイルの分割)

  4. クラス(ポインタ)

  5. クラス(静的メンバ)

  6. クラス(フレンド)

  7. クラス(継承)


Win32 Console Application としてプロジェクトを作成し、ソースファイルの拡張子は cpp とする
Microsoft VisualC++ 4.0 付属の [C++ 入門]より

ソースのダウンロード



1.標準入出力

#include <iostream.h>
void main()
{
    // 標準出力
    cout << "Hello, world\n";
    // 標準出力(endlは\nと同じ意味)
    cout << "It's " << 6 << " cents." << endl;
    // 標準出力(hexは16進数表示、8進数はoct)
    int amount = 123;
    cout << hex << amount << endl;
    //標準エラー出力
    cerr << "Error!\n";
    // 標準入力(数値)
    int amount2;
    cout << "数値を入力してください\n";
    cin >> amount2;
    cout << amount2 << "が入力されました" << endl;
    // 標準入力(文字列)
    char name[20];
    cout << "名前を入力してください\n";
    cin >> name;
    cout << name << "が入力されました" << endl;
}

[出力結果]
Hello, world
It's 6 cents.
7b
Error!
数値を入力してください
1
1が入力されました
名前を入力してください
???
???が入力されました
		


2.スコープ解決演算子

変数名の前にスコープ解決演算子(::)を付けると、
ローカル変数ではなく、グローバル変数が使われる

#include <iostream.h>
int amount = 123;
void main()
{
    int amount = 456;
    // グローバル変数を出力
    cout << ::amount << endl;
    // ローカル変数を出力
    cout << amount << endl;
}

[出力結果]
123
456
		


3.インライン関数

inline関数は、#defineマクロとほぼ同じ意味として使える
つまり、実行ファイル作成時に関数を直接、その場所に埋め込むため、
関数呼び出しがない分だけ高速化を図れる
ただし、長めの関数を頻繁に使用するような場合、
実行ファイルサイズが大きくなって却って遅くなる場合もある
#defineマクロとは、少し動作が異なる場合もある

#define MAX(A,B) (A > B ? A : B)

inline int max(int a, int b)
{
    if(a > b) return a;
    return b;
}
		


4.const 修飾子

const 修飾子を使うと、変数を定数に変換できる
#define による定数定義と同じように使える

#include <iostream.h>
void main()
{
    const int size = 6;
    char      cs[size];
}
		


5.参照

参照は、変数などをポインタのアドレスみたいにして使うようなものである

#include <iostream.h>
void main()
{
    int actualint = 123;
    int &otherint = actualint;
    otherint ++;
    cout << actualint << endl;
    cout << otherint << endl;
}

[出力結果]
124
124

大きな構造体は、実体を引数とするよりも、
構造体の参照を引数とした方がオーバーヘッドを減らせる
		


6.列挙型

列挙型は、複数の整数変数の宣言と初期化をまとめて行いたいときに使用する
列挙型の各要素には、0から始まる連番が割り当てられる
そのため、以下の列挙型の例では、
int red = 0;
int yellow = 1;
int green = 2;
int blue = 3;
と記述したのと同じ結果が得られる

enum color {red, yellow, green, blue};
void main()
{
    color myColor = blue;
    switch(myColor)
    {
    case red:
        break;
    case yellow:
        break;
    case green:
        break;
    case blue:
        break;
    }
}
		


7.typedef 指定子

構造体に typedef 指定子を使うと、構造体の変数とポインタを宣言できるようになる

struct STUFF
{
    int  id;
    char name[32];
};

STUFF myStuff1;

typedef struct{
    int  id;
    char name[32];
}STUFF2,*LPSTUFF2;

STUFF2   myStuff2;
LPSTUFF2 myStuff3; // STUFF2 *myStuff3 と同じ意味
		


8.クラス(基本)

クラスは、変数だけでなく関数も使えるように拡張した構造体のようなものである

#include <iostream.h>
// Time クラスの宣言
class Time
{
public:
    Time(int hr, int mn, int sc); // コンストラクタ
    void display() const;         // 自作関数
    ~Time();                      // デストラクタ
private:
    int hour,minute,second;       // プライベート データ メンバ
};

 クラス内の関数は、メンバ関数、変数はデータ メンバと呼ぶ。
 public:に記述するのは、クラス外の関数からも利用可能な関数や変数である。
 public:に変数は、あまり記述しない方が良い。
 private:に記述するのは、クラス内部の関数のみで使用可能な関数や変数である。
 つまり、クラス外の関数からは呼び出せない。
 以下のコードの myTime や yourTime は、クラスの変数みたいな物であり、
オブジェクトと呼ぶ。
 コンストラクタは、オブジェクトの宣言(つまり作成)時に
プライベート データ メンバを初期化する関数である。
 コンストラクタは、クラス名と同名の関数名にする。
 デストラクタは、プライベート データ メンバが
スコープから外れたときに呼び出され、それらの解放処理などを行う。
 デストラクタは、クラス名にチルダ(~)がついた関数名にする。
 メンバ関数にconstを付けると、その関数内ではデータ メンバの値を変更できなくなる。
 つまり、読み込み専用関数となる。

次に、メンバ関数を記述(インプリメント)する。

// コンストラクタ
Time::Time(int hr, int mn, int sc)
{
    if(hr < 0)       hour = 0;
    else if(hr > 23) hour = 23;
    else             hour = hr;
    if(mn < 0)       minute = 0;
    else if(mn > 59) minute = 59;
    else             minute = mn;
    if(sc < 0)       second = 0;
    else if(sc > 59) second = 59;
    else             second = sc;
}
// 表示関数
void Time::display() const
{
    cout << hour << ':' << minute << ':' << second << endl;
}
// デストラクタ
Time::~Time()
{
    // 何もしない
}
// main関数内で、Timeクラスを使用する
void main()
{
    Time myTime(10,20,30);     // 10:20:30の場合
    Time yourTime(20,1000,40); // 値が無効な宣言
    myTime.display();
    yourTime.display();
}

[出力結果]
10:20:30
20:59:40
		


9.クラス(初期化と破棄)

// コンストラクタとデストラクタの動作
#include <iostream.h>
#include <string.h>
class Demo
{
public:
    Demo(const char *nm);
    ~Demo();
private:
    char name[20];
};
Demo::Demo(const char *nm)
{
    strncpy(name, nm, sizeof(name));
    cout << "Constructor called for " << name << endl;
}
Demo::~Demo()
{
    cout << "Destructor called for " << name << endl;
}
void func()
{
    Demo localFuncObj("localFuncObj");
    static Demo staticObj("staticObj");
}
Demo globalObj("globalObj");
void main()
{
    Demo localMainObj("localMainObj");
    func();
}

[出力結果]
Constructor called for globalObj
Constructor called for localMainObj
Constructor called for localFuncObj
Constructor called for staticObj
Destructor called for localFuncObj
以下はスコープ外なので実際は表示されない
Destructor called for localMainObj
Destructor called for staticObj
Destructor called for globalObj
		


10.クラス(ファイルの分割)

通常、クラスごとにソースファイルを作成する。
また、クラスの宣言はヘッダファイルに、
インプリメントはソースファイルに記述する。

// Time.h
#if !defined(_TIME_H_)
#define _TIME_H_
class Time
{
public:
    Time(int hr, int mn, int sc);
    void display();
    ~Time();
private:
    int hour,minute,second;
};
#endif // _TIME_H_

// Time.cpp
#include <iostream.h>
#include "Time.h"
Time::Time(int hr, int mn, int sc)
{
    // 省略
}
void Time::display() const
{
    // 省略
}
Time::~Time()
{
    // 省略
}
void main()
{
    // 省略
}
		


11.クラス(ポインタ)

#include <iostream.h>
#include <string.h>
class String
{
public:
    String(const char *s);
    void display() const {cout << buf;} // 宣言内でのインプリメントも可能
    ~String();
private:
    int  length;
    char *buf;
};
String::String(const char *s)
{
    length = strlen(s);
    buf = new char [length + 1];
    strcpy(buf, s);
}
String::~String()
{
    delete[] buf;
    buf = 0;
}
void main()
{
    String myString("abcdefg");
    myString.display();
}

[出力結果]
abcdefg

『代入演算子(=)の意味の再定義』

通常、文字列に = を使うと、同じアドレス(メモリ領域)を共有することになるが、
operatot= というメンバ関数を使うと、= を文字列のコピーとして使えるようになる。
operatorは、演算子の動作を変更するメンバ関数である。
class String
{
public:
    String(const char *s);
    void operator=(const String &other);
    // 以下省略
};
void String::operator=(const String &other)
{
    length = other.length;
    delete[] buf;
    buf = new char [length + 1];
    strcpy(buf, other.buf);
}
void main()
{
    String myString("abcdefg");
    String yourString("hijklmn");
    yourString = myString;
    yourString.display();
}

[出力結果]
abcdefg

『this ポインタ』

this ポインタは、そのオブジェクト自身を意味するポインタである。
例えば、以下のようにすると、文字列の自己代入を防げる。
void String::operator=(const String &other)
{
    if(&other == this)
        return;
    length = other.length;
    // 以下省略
}

hisString = yourString = myString;
このような代入式を可能にしたいときは、以下のようにする。
class String
{
public:
    String(const char *s);
    String &operator=(const String &other);
    // 以下省略
};
String &String::operator=(const String &other)
{
    if(&other == this)
        return *this;
    length = other.length;
    // 以下省略
    return *this;
}

『コピー コンストラクタ』

コピー コンストラクタは、同じ型のオブジェクトを引数とするコンストラクタである。
String yourString(myString);
この場合、myString と同じ値のデータメンバを持つ、yourString が作成される。
データ メンバがポインタの場合、コピー コンストラクタをインプリメントしなかったら、
2つのオブジェクトが同じアドレス(メモリ領域)を共有する不具合が発生する。
以下のようにインプリメントする。
class String
{
public:
    String(const char *s);
    String(const String &other);
    // 以下省略
};
String::String(const String &other)
{
    length = other.length;
    buf = new char [length + 1];
    strcpy(buf, other.buf);
}
コンストラクタが複数あるのは、オーバーロードである。

原則として、ポインタメンバを持つクラスは、必ず、代入演算子と
コピー コンストラクタの両方をインプリメントする。
		


12.クラス(静的メンバ)

プライベート データ メンバに static と宣言すると、
クラス内だけで使用できるグローバル変数にできる。
public、private に関わらず、静的データ メンバの初期化は、
コンストラクタではできないので、クラス外(private はファイルスコープ)で行う。
クラスの静的データ メンバだけにアクセスできるメンバ関数は、static と宣言できる。
#include <iostream.h>
class AirPlane
{
public:
    AirPlane() {count ++;}
    static int howMany() {return count;}
    ~AirPlane() {count --;}
private:
    static int count;
};
// 静的プライベート データ メンバをファイル スコープで初期化
int AirPlane::count = 0;
// 表示
void main()
{
    AirPlane myAirPlane; // 引数が無い場合
    int count = myAirPlane.howMany();
    cout << count << endl;
}

[出力結果]
1
0 ではなく 1 になるのは、デストラクタはメイン関数を出た後に実行されるため。
引数が無いコンストラクタはデフォルト コンストラクタと呼ぶ。
		


13.クラス(フレンド)

friend キーワードを使うと、あるクラスのプライベート メンバを
他のクラスや関数から直接操作できる。
あまり、多用しないこと。

『フレンド クラス』

class MyClass
{
friend class YourClass;
public:
    MyClass() {secret = 0;}
private:
    int secret;
};
class YourClass
{
public:
    void change(MyClass mc);
};
void YourClass::change(MyClass mc)
{
    mc.secret ++;
}
void main()
{
    MyClass   myClass1;
    YourClass yourClass1;
    yourClass1.change(myClass1);
}

『フレンド関数』

class MyClass
{
friend void YourFunc(MyClass mc);
public:
    MyClass() {secret = 0;}
private:
    int secret;
};
void YourFunc(MyClass mc)
{
    mc.secret ++;
}
void main()
{
    MyClass myClass1;
    YourFunc(myClass1);
}
		


14.クラス(継承)

クラスの継承を使うと、既存のクラスに自作のメンバを追加したクラスを作成できる。

『社員データベースの場合』
Class Employee    :社員のクラス
Class Manager     :固定給の社員のクラス
Class WageEmployee:時給ベースの社員のクラス
Class SalesPerson :営業職(時給ベース)

class Employee
{
public:
    Employee();
    Employee(const char *nm);
    const char *getName() const;
private:
    char name[30];
};
class WageEmployee : public Employee
{
public:
    WageEmployee(const char *nm);
    void setWage(double wg);
    void setHours(double hrs);
private:
    double wage;
    double hours;
};

Employee が基本クラス、WageEmployee が派生クラスになる。
派生クラスからは、基本クラスのプライベート メンバにアクセスできないため、
基本クラスのパブリック関数を使用する。

void WageEmployee::display() const
{
    cout << "名前: " << getName() << endl;
}

SalesPerson クラスと Manager クラスを追加する

class SalesPerson : public WageEmployee
{
public:
    SalesPerson(const char *nm);
    void setSales(double sales);
private:
    double salesMade;
};
class Manager : public Employee
{
public:
    Manager(const char *nm);
    void setSalary(double salary);
private:
    double weeklySalary;
};

『継承の階層構造』
Employee ― WageEmployee ― SalesPerson
          Manager

『週給計算』
double WageEmployee::computePay() const
{
    return wage * hours;
}
double SalesPerson::computePay() const
{
    return WageEmployee::computePay() + salesMade;
}
double Manager::computePay() const
{
    return weeklySalary;
}

『派生クラスのコンストラクタ』
WageEmployee::WageEmployee(const char *nm) : Employee(nm)
{
    wage  = 0.0;
    hours = 0.0;
}
SalesPerson::SalesPerson(const char *nm) : WageEmployee(nm)
{
    salesMade = 0.0;
}
Manager::Manager(const char *nm) : Employee(nm)
{
    weeklySalary = 0.0;
}
: Employee(nm) などを基本イニシャライザと呼ぶ。
基本クラスにデフォルトコンストラクタがある場合は、基本イニシャライザは、省略可能。
デフォルトコンストラクタとは、引数がないコンストラクタの事である。

『派生クラスのオブジェクトを基本クラスに変換』
WageEmployee aWorker;
SalesPerson  aSeller("John Smith");
aWorker = aSeller;
基本クラスから派生クラスへの変換は出来ない

『仮想関数』
仮想関数は、派生クラスで再定義(インプリメント)するためのメンバ関数である。
仮想関数は、基本クラスのメンバ関数の場合は、キーワード virtual を付ける。
派生クラスの場合は、virtual キーワードを省略できる。
class Employee
{
public:
    Employee(const char *nm);
    const char *getName() const;
    virtual double computePay() const;
    virtual ~Employee() {}
private:
    char name[30];
};
class WageEmployee : public Employee
{
public:
    WageEmployee(const char *nm);
    void setWage(double wg);
    void setHours(double hrs);
    double computePay() const; // virtual省略
private:
    double wage;
    double hours;
};
class SalesPerson : public WageEmployee
{
public:
    SalesPerson(const char *nm);
    void setSales(double sales);
    double computePay() const; // virtual省略
private:
    double salesMade;
};
class Manager : public Employee
{
public:
    Manager(const char *nm);
    void setSalary(double salary);
    double computePay() const; // virtual省略
private:
    double weeklySalary;
};
double Employee::computePay() const
{
    return 0.0;
}
Employee::computePayは、派生クラスに computePay が無い場合に呼び出される。
派生クラスの computePay は、『週給計算』で書いたものを使う。
void main()
{
    Employee     *emnPtr;
    WageEmployee aWorker("Bill Shapiro");
    SalesPerson  aSeller("John Smith");
    Manager      aBoss("Mary Brown");
    double       salary;
    
    empPtr = &aWorker;
    salary = empPtr->computePay(); // WageEmployee::computePay を呼び出す
    empPtr = &aSeller;
    salary = empPtr->computePay(); // SalesPerson::computePay を呼び出す
    empPtr = &aBoss;
    salary = empPtr->computePay(); // Manager::computePay を呼び出す
}

『純粋仮想関数』
宣言時に = 0 を付けると純粋仮想関数になる。
純粋仮想関数には、関数を定義しない。
メンバに純粋仮想関数を持つクラスは仮想クラスと呼ばれ、宣言ができない。
宣言するには、派生クラスを作って、派生クラスで基本クラスの純粋仮想関数の
定義(インプリメント)を行わなくてはならない。
つまり、仮想クラスは、使用の際には必ず、派生クラスを作らねばならない。

// 基本クラス
class Employee
{
public:
    virtual double computePay() const = 0; // 純粋仮想関数
    // ...
};
// 派生クラス
class PartTimer : public Employee
{
public:
    double computePay() const;
};
// 派生クラスで純粋仮想関数の定義
double PartTimer::computePay()
{
    double val = 0;
    return val;
}

『仮想デストラクタ』
継承を行う可能性がある場合は、基本クラスのデストラクタを仮想関数にした方が良い。
仮想関数を持つクラスを書いたときは、デストラクタが必要ないクラスであっても、
必ず、仮想デストラクタを定義する。
コンストラクタは、仮想関数に出来ない。

『プロテクト メンバ』
public と private 以外に、protected というキーワードがある。
これは、private とは異なり、派生クラスのメンバ関数からアクセスできる。
class Base
{
public:
protected:
    int secret;
private:
};

『プライベートの基本クラス』
基本クラスは、: private BaseClass と書くこともできるが、ほとんど使われない
		


15.