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

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


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言語では、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


練習問題

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

#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 の対応終了。

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



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

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

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る