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

この章の概要

この章の概要です。

C言語との互換性

この章では、C言語との違いについて幾つか説明しておきます。 ただし、あまり細かい部分までは触れません。

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

また、C言語には存在していない C++固有のキーワードを使っていれば、 C++ としては独自の意味があるのですから、当然、C言語のプログラムとしては動作しないでしょう。

仮引数省略の意味

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

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

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

なお、void を省略できるのは、あくまでも仮引数のときだけです。 戻り値の型指定の省略は、C言語と同様に、int型を指定したことになります。

変数宣言位置の自由化

C++ では、変数の宣言を行う位置が非常に柔軟になっており、 C言語のように、ブロックの先頭でなければならないというルールは撤廃されています。 (C99 でもこのルールは撤廃されています)。

#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++ では許されます。
また、次のようなことも可能です。

#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文でも同様のことが可能です。


C++ では、初期値を与えずに変数宣言しても、それは定義として扱われます。 つまり、実際にメモリの割り当てが行われますから、変数宣言はその変数が必要になるタイミングぎりぎりまで遅らせるべきです。 そうすることで、プログラムの実行効率が高まる可能性があります。

文字定数のサイズ

文字定数(文字リテラル)は、C言語では int型として扱われますが(C言語編第20章参照)、 C++ では char型として扱われます。

#include <iostream>

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

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

実行結果:

1
1

このように、C++ では 'A' のサイズは 1Byte になります。 これは 'A' が char型であるためです。

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

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

文字列定数の const性

文字列定数(文字列リテラル)を char*型の変数に受け取ることができます(これ自体は C++ でも可能)。 C言語では、そうして手に入れた char*型のポインタを経由して、文字列定数を書き換えようとすることができました。 ただし、動作の保証はありません。

#include <iostream>

int main()
{
	char* str = "abcde";
	str[2] = 'x';
	
	std::cout << str << std::endl;
}

このように、文字列定数を char*型変数で受け取ることは、C++98/03 では可能ですが、 うまく動作しないと考えて間違いないです。

C++11

C++11 では、文字列定数を char*型変数で受け取ることはできなくなっており、コンパイルエラーになります。

しかし、VisualC++ 2013/2015/2017、clang 3.7 ともに、文字列定数を char*型変数で受け取ることはできてしまいます。


結論として、C言語でも C++ でも、文字列定数を char*型では受け取らず、必ず const を付けるべきです。 そうすれば、どの言語、どの規格であっても、ポインタ経由で書き換えようとしたときにコンパイルエラーになります。

C++ では、std::string を使うのでも構いません(【標準ライブラリ】第2章)。

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言語編第50章参照)でも同様です。

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++ では新しい構文が追加されており(第7章)、 それを使う方が良いのですが、C言語と同じ構文でも目的は果たせます。

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


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

列挙型

C言語では、int型の値を enum の変数へ代入できますが、 C++ では明示的なキャストを必要とします。

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

	Color color = (Color)1;
}

逆に、enum から int型への変換は、C言語でも C++ でも暗黙的に行えます。

C++ では、定義された列挙型を、他の列挙型や整数型とは別の新しい型として扱います。 これは、先ほどのプログラム例でいえば、sizeof(Color) == sizeof(int) を保証しないことを意味しています。

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

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

#include <iostream>

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

	Color color = Color::GREEN;
	
	std::cout << color << std::endl;
}

実行結果:

1

この機能は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます。

C++11 での autoキーワード

C++11

C言語には、auto という、今となっては時代遅れのキーワードが存在しています(C言語編第22章参照)。 C言語および、C++98、C++03 では意味のないキーワードでしたが、C++11 ではまったく異なる目的で使うように意味が変更されました。

C++11 での autoキーワードは、型推論を意味します。 これは、文脈によって、コンパイル時点で型を判断できる場合、具体的な型名を書く代わりに「auto」と書くことによって、 コンパイラが具体的な型を当てはめてくれるというものです。
例えば、

#include <iostream>

int main()
{
	auto num = 100;

	std::cout << num << std::endl;
}

実行結果:

100

このように書けます。 この場合、変数num は初期値 100 が与えられており、100 が int型と判断され、結果、

int num = 100;

と書いたのと同じになります。

コンパイラが型を判断できるだけの情報があれば良いので、例えば、次のような使い方もできます。

#include <iostream>

double func();

int main()
{
	auto num = func();

	std::cout << num << std::endl;
}

double func()
{
	return 3.5;
}

実行結果:

3.5

変数num の初期値は、func() の戻り値によって決まります。 func() の宣言を見れば、戻り値が double型であることが分かるので、型推論可能で、変数num の型は double に決まります。


VisualC++ 2013/2015/2017、clang 3.7 は、型推論の意味での auto に対応しています。


練習問題

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

#include <iostream>

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

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

#include <iostream>

enum Color {
	RED,
	GREEN,
	BLUE
};

int main()
{
	Color color = 0;

	color += 1;

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


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/18 「C言語との互換性」の誤記を修正。

'2014/1/16 「C++11 (型名による修飾)」を追加。

'2014/1/14 VisualC++2008 の対応終了。

'2014/1/13 clang 3.0 に対応。

'2014/1/11 C言語編の C99 に関する解説と重複するので、C++11 の列挙型のコンマの省略に関する項を削除。
C++11 の列挙型の基盤となる型の指定に関する解説を削除(第8章へ移動)。

'2013/2/23 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ