C言語との差異 | Programming Place Plus C++編【言語解説】 第2章

C++編【言語解説】 第2章 C言語との差異

先頭へ戻る

この章と同じ(または似た)情報を扱うページが、Modern C++編 (C++11/14/17 対応) の以下の章にあります。

この章の概要

この章の概要です。


C言語との互換性

この章では、C++ とC言語とで違っている部分について幾つか取り上げておきます。

基本的には、C++ はC言語との互換性を意識しており、大半のコードはそのまま C++ としても有効です。しかし、主に、型に対する厳密さが増していることに起因して、幾つか互換性を破っている部分があります。

また、C言語に存在しない C++ 固有のキーワードを使った C++ のプログラムは、当然、C言語のプログラムとしては動作しないでしょう。

仮引数省略の意味

前の章でも触れましたが、C++ では関数の仮引数の void は省略できます。

C++ では、仮引数が空になっている場合は、C言語で void と指定したときと同じ意味、すなわち引数は無いという意味になります。void と明示的に書いても同じ意味になりますが、C++ では面倒で無意味な入力を避けて、void は省略することが一般的です。

C言語では、仮引数が空であることは、「引数は何でも良い」という意味になります(C言語編第10章)。これは関数プロトタイプとして機能しないことを意味しており、一般的にいって危険な使い方です。ですから、C言語では必ず void と明示するべきです。

なお、「void」という記述を、意味を変えずに省略できるのは、仮引数の場合だけです。戻り値の型指定の省略は、C言語と同様に、int型を指定したことになります。

変数宣言位置の自由化

C++ では、変数を宣言できる箇所が柔軟になっており、(C95までの)C言語のように、ブロックの先頭でなければならないというルールは撤廃されています。

C99 でもこのルールは撤廃されています(C言語編第22章

#include <iostream>
#include <cstring>

int main()
{
    const char* str = "abcde";
    std::cout << str << std::endl;

    std::size_t len = std::strlen(str);
    std::cout << len << std::endl;
}

実行結果:

abcde
5

変数len は、ブロック(この場合、main関数)の先頭にありませんが、C++ では許されます。

また、次のように for文の初期設定式の中で宣言することも可能です。

#include <iostream>

int main()
{
    for (int i = 0; i < 5; ++i) {
        std::cout << i << std::endl;
    }
}

実行結果:

0
1
2
3
4

この場合、変数 i のスコープは for文の内側だけに限定されます。C++ では、これは一般的な書き方です。

なお、if文でも同様のことが可能です。

#include <iostream>

int func()
{
    return 10;
}

int main()
{
    if (int a = func()) {
        std::cout << a << " is true" << std::endl;
    }
    else {
        std::cout << a << " is false" << std::endl;
    }
}

実行結果:

10 is true

C++ では、変数を定義している部分を実行するときに、C言語よりも多くの処理が行われることがあります。そのため、無駄な処理を実行しないようにするため、変数は実際に必要になるタイミングの直前で定義するようにすべきです

ここで実行される処理はコンストラクタと呼ばれるものです。第13章で取り上げます。

文字定数の大きさ

文字リテラルは、C言語では int型ですが(C言語編第19章参照)、C++ では char型です。そのため、大きさも異なります。

#include <iostream>

int main()
{
    char a = 'A';

    std::cout << sizeof('A') << "\n"
              << sizeof(a) << std::endl;
}

実行結果:

1
1

C++ では 'A' の大きさは 1バイトになります。これは 'A' が char型であるためです。

このルール変更は、関数のオーバーロード(第8章)をうまく行うために必要だからです。'A' が 65 という int型とみなされてしまうのでは不便になってしまいます。

ところで、上のプログラムで、std::cout を使って整数が出力できていることが分かると思いますが、このように、型の判断までも自動的に行ってくれることが、printf関数と決定的に異なるところです。


struct、enum、unionキーワードの省略

C言語では、構造体タグを使って構造体変数を宣言する際、structキーワードが必要です(C言語編第26章参照)が、C++ では省略できるようになりました。

struct Point2D {
    int  x;
    int  y;
};

int main()
{
    Point2D point;

    // 以下、省略
}

上のプログラムで、main関数の中で構造体変数 point を宣言する際に、structキーワードがありません。C言語であれば、

struct Point2D point;

このように書かないといけません。

このルールは、enum(C言語編第50章参照)や union(C言語編第55章参照)でも同様です。

voidポインタ型から任意のポインタ型への変換

C言語では、voidポインタ型から任意のポインタ型へ暗黙的に変換できますが、C++ では明示的なキャストを必要とします。

#include <iostream>
#include <cstdlib>

int main()
{
    int* p = (int*)std::malloc(sizeof(int));

    *p = 100;
    std::cout << *p << std::endl;

    std::free(p);
}

実行結果:

100

malloc関数の戻り値は void* です。C言語でこの関数を使う際、明示的なキャストせずとも他の型の変数で結果を受け取れますが、C++ ではキャストが必要になります。

実際には、C++ では malloc関数を使うことはまずありません。代わりに new演算子というものを使います(第14章)。

キャストに関しても、C++ では新しい構文が追加されており(第7章)、それを使う方が良いのですが、C言語と同じ構文でも目的は果たせます。

逆に、任意のポインタ型から voidポインタへの変換は、C言語でも C++ でも暗黙的に行えます

ヌルポインタの表現

C++ でヌルポインタを表現するとき、C言語と同様に、NULLマクロを使うことはできますが、色々事情があって避けられることも多くあります。

C++ の NULL はC言語とは違って、voidポインタ型に置換されることはありません。前の項で取り上げた通り、C++ では、voidポインタ型から他のポインタ型への暗黙的な型変換が行われなくなったからです。もし、voidポインタ型に置換されてしまったら、次のようなコードがコンパイルできないため非常に不便になります。

int* p = NULL;  // NULL が voidポインタであったとしたら、
                // コンパイルエラーになってしまう

そのため、C++ の NULL は必ず整数型の定数に置換されます。型は int かも知れませんし、long int かも知れません。

#define NULL 0
#define NULL 0L

定数の 0 はヌルポインタとして扱われるので、これできちんと機能しますが、やや不可解に思えるプログラムを書けてしまいます。

void f(int n) {}

int main()
{
    f(NULL);
}

f関数の仮引数の型は int ですが、実引数にヌルポインタを渡しています。NULL の置換結果は整数型ですから、これでもコンパイルが通ります。「NULL」と書いている以上、ポインタのつもりで渡していると思われますが、果たしてプログラマーの意図通りなのでしょうか?

また、これに関連する話として、第8章で取り上げる関数オーバーロードの機能を使った場合に起こる問題もあります(第8章)。

一時期には、NULL を使うと生じる混乱を避けるため、NULL ではなく単に 0 と書くようにしているプログラムも多くありました。ただ、標準規格に沿ったコンパイラならば、結局どちらでも同じです。しかし、ソースファイル上に「NULL」とあれば「ポインタ」を意図していることが明らかですし、「0」ならば「ヌルポインタか、整数値としての 0」なのか不明確であるといえます。この辺り、かなり意見が分かれるところです。

C++11 になって、ヌルポインタにまつわる混乱を解消するために nullptr(Modern C++編)が導入されました。C++11 以降ならば、すべてを忘れて、nullptr に一本化するべきです。

C++11(nullptr)

(→Modern C++編

列挙型

C言語では、int型の値を enum の変数へ代入できますが、C++ では明示的なキャストを必要とします。逆に、enum から int型への変換は、C言語でも C++ でも暗黙的に行えます。

#include <iostream>

int main()
{
    enum Color {
        RED,
        GREEN,
        BLUE,
    };

    Color color = (Color)1;
    int v = BLUE;  // キャスト不要

    std::cout << color << "\n"
              << v << std::endl;
}

実行結果:

1
2

C++11 (型名による修飾)

C++11 では、列挙定数にアクセスする際、列挙型の型名で修飾することが可能になりました。通常、列挙定数の名前は、他の名前と衝突してしまいますが、この機能を使うことで衝突を回避できるようになります。

#include <iostream>

int main()
{
    enum Color {
        RED,
        GREEN,
        BLUE,
    };

    Color color = Color::GREEN;

    std::cout << color << std::endl;
}

実行結果:

1


練習問題

問題① 次のプログラムは、文字と整数のどちらを出力しますか?

#include <iostream>

int main()
{
    std::cout << 'A' << std::endl;
}

問題② 次のプログラムを、C言語(C95) でも C++(C++03) でもコンパイルでき、同じ実行結果になるように書き換えて下さい。

#include <iostream>

enum Color {
    RED,
    GREEN,
    BLUE
};

int main()
{
    Color color = 0;

    color += 1;

    std::cout << color << std::endl;
}


解答ページはこちら

参考リンク



更新履歴

'2018/8/14 「ヌルポインタの表現」の項を、第7章から移動してきた。内容は全面的に修正。
「(C++11) autoキーワード」の項を、第7章へ移動。

'2018/7/10 全体的に見直し修正。

'2018/4/5 VisualStudio 2013 の対応終了。

'2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

'2018/2/22 「サイズ」という表記について表現を統一。 型のサイズ(バイト数)を表しているところは「大きさ」、要素数を表しているところは「要素数」。

≪更に古い更新履歴を展開する≫



前の章へ(第1章 Hello, Worldプログラム)

次の章へ(第3章 名前空間)

C++編のトップページへ

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS