C言語との相違点

構造体、列挙型、共用体

定義と変数宣言

C言語で構造体列挙型共用体を使用する場合、定義の前にtypedefを記述して、変数宣言時のコーディング量を減らす方法がよく用いられていました。
C++ではtypedefなしでそのまま構造体変数を宣言できます。


//C言語の場合
typedef struct
{
    int n;
} S_c;

//C++の場合
struct S_cpp
{
    int n;
};

int main()
{
	//どちらもOK
    S_c c;
    S_cpp cpp;
}

変数の初期化及び代入

構造体等の変数は{}記号を使用して初期化及び代入が可能です。


struct S
{
    int x;
    short y;
};

union U {
    int x;
    short y;
};

enum E {
    x, y
};

int main()
{
    S s = { 1 };
    s = { 2 };
    s = {};

    U u = { 1 };
    u = { 2 };
    u = {};

    E e = { E::x };
    e = { E::y };
    e = {};
}

C言語では、初期化子リスト({})の中に値を記述して初期化をすることはできます。
C++では代入時にも初期化子リストを使用可能です。
空の初期化子リストを指定すると0を指定したものとみなされます。

ただし、これが可能なのはC言語風の単純なデータ構造を持つ構造体です。
C++では構造体とクラスとにほとんど違いはなく、複雑な内部構造を持つ構造体を作ることも可能で、初期化子リストを渡せない構造体を作ることもできます。
詳しくはオブジェクトの初期化の項で改めて説明します。

列挙型

C++の列挙型には、従来のC言語と(ほぼ)同じ仕様のものと、C++の機能に対応したものがあります。

スコープ付き列挙型

スコープ付き列挙型は、その名の通り列挙子(列挙型のメンバ)にスコープを持たせることができます。


//従来の列挙型
//(スコープなし列挙型)
enum EnumC {
    A,
    B
};

//スコープ付き列挙型
enum class EnumCpp {
    C,
    D
};

//スコープ付き列挙型
enum struct EnumCpp2 {
    E,
    F
};

int main()
{
    EnumC c1 = A;
    EnumC c2 = EnumC::B;

    //EnumCpp cpp1 = C; //エラー
    EnumCpp cpp2 = EnumCpp::D;
}

enumに続いてclassまたはstructキーワードを指定することでスコープ付き列挙型を定義することができます。
classの指定とstructの指定に違いはありません。

従来のスコープなし列挙型の列挙子は、constなどの定数と同じように使用することができます。
さらにC++ではスコープ解決演算子(::)を使用して、列挙型名::列挙子という形式でも使用することができます。

スコープ付き列挙型は列挙型名::列挙子のようにスコープを指定しなければ列挙子にアクセスすることはできません。
不便になったようにも見えますが、これは他の定数や変数等と名前の衝突が起こらないという利点になります。
別の列挙型で同じ列挙子名を使用することもできます。


enum class Direction {
    forward,
    left,
    right,
    back
};

enum class LCR {
    left,	//←同じ列挙子名を使用できる
    center,
    right,
};

int main()
{
    Direction direction = Direction::left;
    LCR lcr = LCR::left;
}

同じことをスコープなし列挙型で記述すると列挙子の再定義となってしまい、エラーになります。

C++ではスコープ付き列挙型を使用したほうが良いでしょう。
なお、スコープ付き列挙型は#defineなどとは明確に異なる記述ができるため、列挙子を大文字にするなどで目印をつける必要はありません。

列挙型の基底型

C言語での列挙型は、内部的にはint型が使用されます。
C++では使用されるデータ型を指定することができます。
これを列挙型の基底型といいます。


enum class RGB : unsigned char {
    R, G, B
};

//スコープ無し列挙型
enum RGB : unsigned char {
    R, G, B
};

enum [class] 列挙型名 : データ型と記述することで列挙型で使用される内部データ型を指定できます。
列挙子の数が少ない場合は小さなデータ型にすることで、容量を節約することができます。

基底型を指定しない場合、スコープ付き列挙型の基底型はint型となります。
スコープなし列挙型の場合、全ての列挙子を表現可能な整数型が自動的に選択されます。
実際に何が選ばれるかは処理系(コンパイラ)に依存しますが、int型、unsigned int型の範囲に収まる場合はそれよりも大きな型が使用されることはありません。

暗黙の型変換

C言語では、数値から列挙型変数の変換、および列挙型変数から数値への変換は暗黙的に行われます。
C++では、スコープなし列挙型から数値への変換のみ暗黙的な型変換が可能です。
それ以外の変換はキャストが必要です。


enum class ScopedEnum {
    x, y
};

enum UnscopedEnum {
    x, y
};

int main()
{
    ScopedEnum se = (ScopedEnum)1;
    int n = (int)se;	//キャストが必要

    UnscopedEnum ue = (UnscopedEnum)1;
    n = ue;				//暗黙の型変換が可能
}

グローバル変数とstatic変数の初期化

C言語では、グローバル変数やstatic変数(静的領域に配置される変数)は「プログラム開始時に初期化され、プログラム終了まで生存する」というものでした。
C++では「最初にアクセスがあった時に初期化される」ように変更されています。
これにより、プログラム実行中に生成される値を初期値に指定することができます。


#include <iostream>

int g1 = 10;

//C言語ではエラーだが
//C++ではOK
int g2 = g1;

int main()
{
}

関数の戻り値などでグローバル変数やstatic変数を初期化することも可能です。


#include <iostream>

int func();

//関数の戻り値でグローバル変数を初期化
int g = func();

int func() 
{
    return 10;
}

int main()
{}

暗黙的な型変換の厳格化

C++ではC言語よりも暗黙的な型変換が厳格化されています。
例えば以下のような変換は暗黙的にはできず、明示的にキャストする必要があります。

  • void*型から他のポインタ型への変換
  • ポインタ型から数値型への変換
  • const型から非const型への変換
  • 文字列リテラルからchar*型への変換

文字列リテラル(const char*型)からchar*型への暗黙的な型変換についてはC++03までは可能でしたが、C++11(2011年)以降は許可されていません。
(キャストは可能です)