『Cの延長としての C++ 入門 第2版』無料サンプル | Programming Place Plus e-Book Project

トップページe-Book Project『Cの延長としての C++ 入門 第2版』


このページでは、『Cの延長としての C++ 入門 第2版』の無料サンプル版を公開しています。

第0章 はじめに

本書の内容

本書は、C言語 (以降、単に「C」と表記します) を習得済みの人に向けた、C++ の入門書です。C を使った開発を経験されていることが望ましいですが、C の一般的な入門書を理解できていれば、読むことができるレベルだと思います。

本書の目的は、C を捨てて C++ へ移行することを本気で検討していただくことです。

開発のターゲットにする環境向けの C++コンパイラが存在しないなどの事情がないかぎりは、C にとどまり続けることにはほとんど意味がありません。

オブジェクト指向プログラミングのような、訳の分からない(!?)スタイルを導入しなければならないと言っているのではないのです(それも有用だとは思いますが)。C++ は C に多くの機能を追加したというだけではなく、プログラムの安全性の強化が図られているという面もあります。たとえば、C で書いたプログラムを、ソースコードには一切手を付けずに、C++ のコンパイラでコンパイルしなおすだけで、バグや、バグにつながりそうな問題点が見つかることがあります。

この例はあまりに小さなステップのようですが、この程度の導入のしかたでも「C++ への移行」(の一環である)という考え方をしてみてはいかがでしょうか? (ソースコードは実質、C のままですよ?)

本書では、C の考え方の延長線上で導入できる C++ の機能に焦点を当てます。オブジェクト指向プログラミングに関わる解説はほとんどありません。そのため、一般的な入門書に載っている内容を網羅しているわけでもありません。分かりそうなところから少しずつ C++ への移行を果たせないだろうか? それを検討してみてほしいと思います。

初版との違い

本書は、同名の著書『C の延長としての C++ 入門』の第2版です。

基本的な方針に違いはありませんが、全面的に文章の見直しを図り、ほぼすべてに手直しが入っています。

C++ の対応バージョンが、C++11 から C++14 に変更されました。これでも最新バージョンというわけではないのですが、C++11 からもうすぐ 10年を迎えようとしていることもあって、1つバージョンを進めることにしました。これに伴って、C++14 の新機能から、以下の2つを追加しました。

また、C++14 のタイミングで、C プログラマーも慣れ親しんでいるであろう rand関数が非推奨となったことを受けて、乱数の話題をあつかう第9章を新設しました。

そのほか、初版の解説の見直しを図った結果、いくつか新しい話題が加えられています。

コラムについて

本書の解説中には、以下のように文章が囲まれた COLUMN(コラム)というコーナーが登場します。ここには、少し高度な解説や補足事項が書かれています。読み飛ばしても、その先の内容を理解することに支障はありません。

このような囲み記事がコラムです。

C と C++ の歴史

C も C++ も、国際的な標準規格が制定されており、何度か改訂が行われています。

C の標準規格は、1989年の ISO/IEC 9899:1990 に始まり、1995年、1999年、2011年、2017年に改訂が行われています。それぞれの年号を取って、C89、C95、C99、C11、C17 と表現されることがあります(C11 と C17 はほとんど同じです)。本書でもこの表現を使います。

C++ の標準規格は、1998年の ISO/IEC 14882:1998 に始まり、2011年、2014年、2017年に改訂が行われています。さらにこの先も、3年ごとの改訂が予定されています。2003年にも小さな改訂が行われていますが、1998年のものとそれほど違いはありません。C の規格と同様に、年号を取って、C++98、C++03、C++11、C++14、C++17 のように表現されることがあり、本書でもこの表現を使います。

C++ は C の機能を含んでいますが、上記のように C も C++ もそれぞれの歴史を歩んでいますから、C++ の規格バージョンごとに、含まれる C の規格バージョンも変わっています。C++11、C++14 は C99 の機能を含んでおり、C++17 からは C11 の機能を含んでいます。ただし、C のすべての機能が使えるというわけではなく、差異もあります。

本書は、C89 時点の C の知識を持っている方を想定し、C++14 相当の機能を解説しています。C++14 は最新バージョンよりもやや古いですが、すべての開発プロジェクトで最新バージョンが使えるわけでもないでしょうし、現実的な下限値として設定しました。同じ考え方をして、筆者の Webサイト「Programming Place Plus」でも、C++14 を使った入門記事を書いています(2020年7月現在、未完成)。バージョンが一致するので、こちらも参考になさってください。

【Programming Place Plus】

【Programming Place Plus – 新C++編】

コンパイラについて

本書は、C++14 規格の範囲内で解説しており、標準規格に含まれない話題はあつかいません。そのため、C++14 規格に対応していれば、どのコンパイラを使っても問題ないはずです。

本書は、以下のコンパイラで動作確認を行いました。

C++ が難しいというのは本当か

世間の C++ のイメージは何といっても「難しい」のようですが、本当にそんなに難しいのでしょうか?

C++ は C の機能に、さらに多くの機能付け加えているので、仕様が「巨大」であることは確かです。しかし、だからといって、すべての機能を知っていなければ使えないというわけではありません。知っている機能だけを部分的・段階的に導入できます。プロジェクトごとに少しずつ機能を導入していくような方法を採ることで、「巨大」な C++ を少しずつ攻略していくことができます。

C のプログラマーの中には、C++ はオブジェクト指向というプログラミングスタイルで書かないといけないと思い、難しく感じている人もおられるようですが、C++ はオブジェクト指向を「強制」していません。オブジェクト指向を「サポート」しているだけです。 むしろ C++ は、色々なプログラミングスタイルを許容する言語であり、C のような手続き型のスタイルで書いても構わないし、一部分だけオブジェクト指向を導入するなど、スタイルを混ぜ合わせても構いません。

長年 C を使ってきた人がオブジェクト指向の考え方に馴染むことは、(個人差も大きいようですが)なかなか大変でしょう。考え方としては間違いなく有用なので、理解する努力はした方がいいとは思いますが、オブジェクト指向が分からないから C++ は使わないというのは、もったいないです。オブジェクト指向を強制しない言語なのに、「C++」と「オブジェクト指向」を一気に導入しようとするから大変なだけで、比較的 C に近いところから少しずつ C++ に世界に入っていけば良いのです。本書でも、オブジェクト指向の考え方が強く要求される部分については、あえて取り上げていません。

C より C++ を使うべき理由

C++ は様々なプログラミングスタイルを「サポート」しています。 しかし、特定のプログラミングスタイルを「強制」はしません。非常に自由度が高い言語です。C に慣れたプログラマは、C の延長線上の機能だけを導入しても良いですし、Java や C# などのオブジェクト指向が使える言語に慣れているのなら、オブジェクト指向の機能を導入しても良いのです。複数のスタイルを混ぜても構いません。

C++ は(大体において)C を内包しています。 C のプログラムは、C++ のプログラムとしてそのまま使えることが多いです(冒頭で書いたとおり、問題のある部分はコンパイルエラーとして発覚するでしょう)。特に、他のプログラミング言語では難しい、ポインタを用いた細かな制御や、アセンブリ言語との連携などは、C++ でも依然として得意分野です。C と同じ機能を使うかぎり、速度面の低下もありません。

C++ は C よりも安全です。 特に、型に関する厳密さが増しているため、C では実行時にならないと発覚しない(あるいは、実行してもすぐには発覚しない)やっかいな問題を、コンパイル時点で見つけ出してくれることが多くなっています。また、安全性を増すための新しい機能もあります。

C++ は汎用的なプログラミングが可能です。 つまり、多くの型(理想的なケースでは、あらゆる型)に対応したコードを記述する能力があります。C では int型版、double型版、char*型版・・・といったように、型ごとに実装しなければならなかった処理を、C++ では1つのコードにまとめられるため、実装にかかる労力が大幅に削減できます。しかも、既存の型だけではなく、未来に追加されるかも知れない未知の型にすら対応する力をもっています。

C++ は C より便利です。 機能が豊富になっているので、単純に便利です。便利であるということは、プログラマの労力も減り、楽ができるということです。たとえば、いまさら連結リストを自作する必要などありません。あらゆる型に対応した、安全で効率的な実装が、標準で用意されています。

第1章 基本的なこと

まず基本的なところから始めていきましょう。

C++ は、細部に違いはあるものの、C に機能を追加した言語であり、C で書いたプログラム(特に C89/C95 辺りのプログラム)の多くは C++ でも動作します。

C99 以降の C は、C++ の事情とは相容れないような独自の進化をしている面があり、C++ を C の上位言語と呼ぶことが、だんだん実態に合わなくなってきています。C99 や C11 で追加された機能には、C++ では使用できないものが割と多く含まれています。

C と C++ に両対応しているコンパイラを使うのなら、C としてコンパイルするのか、C++ としてコンパイルするのかを伝える方法が必要です。最初にそのやり方を取り上げます。次に、C と C++ の主要な「違い」の部分を取り上げます。

ファイルの拡張子

C でも C++ でも、標準規格としては、ソースファイルやヘッダファイルに使う拡張子の取り決めはありませんが、コンパイラは拡張子に応じて、C としてコンパイルするのか、C++ としてコンパイルするのかを決定していることがあります。

ソースファイルについては、C では「.c」を使うことが多いですが、C++ では「.cpp」「.cc」「.cxx」といった拡張子を使うことが多いです。本書では「.cpp」を使用します。

ヘッダファイルに関しては、C でも C++ でも「.h」を使うことが多いですが、C++ の場合は「.hpp」「.hxx」のような拡張子を使うこともあります。本書では「.h」を使用します。

コンパイルの方法

C++ のプログラムをコンパイルする方法を知っておく必要があります。使用するコンパイラによって異なりますから、ここにすべてを紹介することはできませんが、本書で対応している Visual Studio、gcc について挙げておきます。

Visual Studio

デフォルトでは、ファイルの拡張子によって、プログラミング言語の種類が判断されます。

ソースファイルの拡張子を「.cpp」にすれば、自動的に C++ と判断してコンパイルされます。

gcc

C では「gcc」という名前の実行ファイルを使いますが、C++ では「g++」を使います。

main.cpp をコンパイルして、main.exe を生成する場合は、次のようにします。

g++ main.cpp -o main.exe

ソースファイルが複数ある場合は、書き並べて下さい。

g++ main.cpp sub.cpp -o main.exe

コメントのスタイル

古くから、C ではコメント(注釈)に /**/ のペアを使用してきました。これは C++ でも使用できます。

C++ ではこれに加えて、// を使ったコメントが使用できます。// は、その行の行末までをコメントにします。入力のしやすさから、一般にこちらの方が好んで使用されます。

C でも C99 からは // のコメントが使用できます。

次のコードでは、x2 と x3 を宣言している行がコメントアウトされています。

int x1 = 0;
//int x2 = 0;
/*int x3 = 0;*/

仮引数が空の場合の意味

関数の仮引数を空にした場合の意味が、C と C++ とでは異なります。

C では、引数の個数や型を指定しない(そのため、コンパイラは正しさをチェックしない)ことを意味しており、引数がないことを表すためには void と明記する必要があります。

C++ では、引数がないことを意味します。つまり、void と明記した場合とまったく同じ意味になります。このため C++ では、入力量を減らしてコードをすっきりさせるため、void を明記せず、空にしておくことが多いです。次の2つの関数は、引数も戻り値も同一です。

void f1();
void f2(void);

なお、戻り値がない場合は、void と書かなくてはなりません。

main関数の末尾の return文の省略

main関数で、return文が登場することなく、関数の末尾にまで到達した場合、そこに「return 0;」があったかのように扱われます。そのため、0 以外の値を返すことがない場合は、return文を省略できます。

int main()
{
    // return 0; はなくても問題ない
}

C でも C99以降であれば、同様に省略できます。

タグ名の省略

C では、構造体型、共用体型、列挙型を使用するときに、それぞれを表すキーワード (struct、union、enum) に続けて、タグ名を記述する必要がありました。C++ ではキーワードの記述は不要です。

struct Data { /* メンバ省略 */ };
union Node { /* メンバ省略 */ };
enum Color { /* メンバ省略 */ };

/* C では以下のように書く */
struct Data data;
union Node node;
enum Color color;

// C++ では以下のように書ける
Data data;
Node node;
Color color;

記述量が減り、見た目もすっきりします。この仕様の変更によって、タグ名と同じ名前を、関数などの他の要素の名前に使うことができなくなりましたが、問題になるほどのことではないでしょう。

ローカル変数を宣言する位置

C には、ローカル変数(局所変数)の宣言を、関数やブロックの先頭で行ってきた歴史があり、これに慣れている人も多いと思います。C++ にはそのような制約はなく、ローカル変数を関数やブロックの先頭以外で宣言できます。

C でも C99 以降では、関数やブロックの途中で変数を宣言できます。

#include <assert.h>
#include <stdio.h>

// 配列の要素数に置き換わるマクロ
#define SIZE_OF_ARRAY(array) (sizeof(array) / sizeof(array[0]))

int average(const int* array, size_t size)
{
    assert(size >= 1);

    int sum = 0;
    size_t i;

    for (i = 0; i < size; ++i) {
        sum += array[i];
    }
    return sum / size;
}

int main()
{
    const int array[] = {5, 8, 3, 8, 6};

    printf("%d\n", average(array, SIZE_OF_ARRAY(array)));
}
実行結果:
6

C (C99 より前) では、average関数内の2つの変数宣言のところでコンパイルエラーになります。宣言よりも前に assert があるため、変数宣言位置はブロックの先頭ではないためです。

第5章で解説しますが、C++ にはコンストラクタという、変数が定義されるときに、型に応じた専用の初期化処理を実行する機能があります。関数内で処理が分岐している場合のように、定義した変数が結局使われないというケースがあるので、無駄に初期化処理が走らないように、使う直前で定義することが推奨されます。また、変数を参照している可能性があるコードの範囲が狭い方が、プログラムを把握しやすいので、その意味においても、ローカル変数は使う直前で宣言するべきです


さらに、for、while、if、switch においては、( ) の内側で変数を宣言できます。 利用価値が高いのは for と if の場合でしょう。たとえば、次のように使えます。

#include <stdio.h>

int main()
{
    if (FILE* fp = fopen("test.txt", "w")) {
        for (int i = 0; i < 10; ++i) {
            fprintf(fp, "%d\n", i);
        }
        fclose(fp);
    }
}

for の初期化のところで宣言した変数は、その for文の中でだけ使用できます。ループ制御変数をこの位置で宣言する記法は、C++ では非常に一般的なものです。

if、switch、while の場合も、( ) の中で宣言した変数は、同じ if、switch、while の中でだけ使用できます。ただし、条件式を記述する場所なので、真か偽を表す値に変換できる型でなければなりません。

変数が有効である範囲を狭くした方が、プログラムを把握しやすくなりますから、この機能も積極的に使っていきましょう。

C の場合も、C99 では for文の ( ) の内側で変数を宣言できます。





この本の紹介ページへ

Programming Place e-Book Project のトップページへ

Programming Place Plus のトップページへ



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