C言語は1972年に開発された大変歴史があるプログラミング言語のひとつであり,
ハードウエアの性能向上とともに,現在も改良が続けられている.(C89, C99, C11, ...)
しかし,ソフトウエアの規模拡大や並列化など,近代的なプログラムにおいては「純粋な」C言語の体系だけでは,やや力不足・不便となってきた.
そこで,C言語の拡張である C++ では,C言語では扱いがやや難しい機能や,バグの原因になりやすかった機能が大幅に整理・強化された.
さらに「オブジェクト指向」と呼ばれる開発手法ができるように拡張された.
C++の拡張は非常に多岐にわたり,その全てを網羅することはできない. ここでは,最近のCプログラミングに必要と思われる「C++」言語の拡張された部分について基本を学ぶ.
C++は,数年おきにバージョンアップがおこなわれ,西暦の年号の下2桁をとって C++11, C++14, C++17 と呼ばれる. 実際の応用プログラミングでは,これらの新しい機能を利用すると便利である.
c++の詳しい情報はhttps://isocpp.org/ のWebサイトにて閲覧できる.
C++という名称は,「C」 に 「++」(インクリメント演算子)がつけられている.
C言語において「++
」は「1増やす」というイメージであるが,控えめに見ても ++
どころではないレベルの,劇的な拡充が行われている.
また,C#(C++++?)という言語も提唱されているが,これはC/C++言語とはかなり様子が異なる.
C++では,画面出力やキーボード入力,およびファイル入出力にたいして,ストリームという概念が導入されている.
ストリームにより,様々な変数やオブジェクトの入出力を統一的な方法で記述することができる.
定番,まずはC++版の Hello World プログラムのソースコードを眺めてみよう.
#include <iostream> // C++の標準入出力用ヘッダファイル
using namespace std; // std という名前空間を使用する,という指示
int main(void)
{
cout << "Hello World!" << endl;
return 0;
}
上のプログラムを実行して動作を確認してみよう.
注意:ソースファイル名の拡張子を「.cpp」としておくと,コンパイラが自動的にC++ソースコードと判定する.
「.c」ではエラーが出る場合がある.
#include <iostream>
は,標準ストリーム入出力用のヘッダファイルで,ほとんどのC++プログラムで必要な宣言.cout
は,コンソール(=コマンドプロンプト画面)への出力を表す.cout
に続く <<
は,ストリーム演算子と呼ばれ,右から左へと変数や定数を流れるように出力する記号である.(streamの概念)endl
は改行(end line) の意味で,'\n'
とほぼ同じ意味である.
プログラム中に出てくる using namespace std;
は,C++の新しい機能である「名前空間(namespace)」のデフォルト値として std
を使用する,という宣言である.
コンソール画面への出力方法として,cout
(標準出力) のほかに cerr
(標準エラー出力) がある.
#include <iostream>
using namespace std;
int main(void)
{
cout << "coutです." << endl;
cerr << "cerrです." << endl;
return 0;
}
どちらも標準では画面へ出力されるが,出力をファイルにリダイレクトした際に異なる結果となる.
z:\.windows2000> test.exe coutです. cerrです. z:\.windows2000> test.exe > out.txt cerrです.
下の例は 出力のリダイレクト と呼ばれ,本来画面に出力される文字や数値を,別ファイル(ここではout.txt)に書き出すものである.
この場合,「coutです.」は,画面には表示されず,ファイル out.txt に出力される.
たとえば,エラーメッセージ(ファイルが開けない,入力した値の範囲エラー)などの出力は必ず画面に表示したいので, cerr
を用いると良い.
上のプログラムを実行して,リダイレクト時の動作を確認してみよう.
いろいろなストリーム出力例
#include <iostream>
#include <cstdio> // Cのstdio.hに相当するヘッダファイル.stdio.hでもOKかも.
using namespace std;
int main(void)
{
// 文字列(定数)
cout << "Hello World!" << endl;
// 整数
int i = 100;
cout << "i = " << i << endl;
printf("i = %d --- by printf()\n", i); // もちろんprintf()もOK.
// 実数
double r = 3.1415;
cout << "r = " << r << endl;
// 文字列(変数)
char str[] = "Oh! Meiji";
cout << str << endl; // 文字列を表示
cout << str[0] << endl; // 先頭の1文字を表示
// ポインタ変数
int* p = &i;
cout << "p = " << p << endl;
return 0;
}
このように,ストリーム出力では,出力したい変数や文字列を,順次 <<
で並べて記述すればよく,数値や文字列など型を気にせずに出力することができる.
逆に,表示桁数や表記法を細かく指定したい場合は,従来通り printf
を使えば良い.
上のプログラムを実行して,出力を確認してみよう.
キーボードからの入力については,C言語では scanf()
を使用たが,C++では,ストリームを用いて記述できる.
ソースコード中では,出力とは逆向きの記号 >>
を用いる.
scanf()
とは異なり,%d, %f
などの指定が不要である.
ストリーム機能によるキーボード入力の例
#include <iostream>
using namespace std;
int main(void)
{
int i;
cout << "i = ? ";
cin >> i;
cout << "i = " << i << endl;
double r;
cout << "r = ? ";
cin >> r;
cout << "r = " << r << endl;
char str[100];
cout << "Input string: ";
cin >> str;
cout << "String is " << str << endl;
return 0;
}
実行例: n=? 20 20 is even. n=? 15 15 is odd.
cout,cerr
を用いたストリーム出力では,変数の型を気にすることなく画面に数値や文字列を表示できるが,工学では有効桁などの都合で書式を制御したい場合が多い.
ストリーム出力で,表示する数値全体の「桁数」や,小数点以下の表示「桁数」,16進数表示など,書式を指定するには,マニピュレータ機能 iomanip
で定義されている関数群 setw(), setprecision(), setfill()
を用いる.
注:以下の例の通り,必ずしも便利というわけでない.
printf()
を使用した方が簡潔な場合も多い.
// 整数の書式指定例
#include <iostream>
#include <iomanip> // マニピュレータ 用
using namespace std;
int main(void)
{
int i = 100;
cout << "i = " << i << endl; // 標準では10進数
cout << "i(hex) = " << hex << i << endl; // 16進数
cout << "i(oct) = " << oct << i << endl; // 8進数
cout << "i(dec) = " << dec << i << endl; // 10進数
cout << "i = " << setw(10) << i << endl; // 出力幅を設定
cout << "i = " << setw(10) << setfill('0') << i << endl; // 出力幅と空白を埋める文字を設定
return 0;
}
// 実数の書式指定の例
#include <iostream>
#include <iomanip> // マニピュレータ 用
using namespace std;
int main(void)
{
double pi = 3.14159265358979;
cout << "pi = " << pi << endl;
cout << "pi = " << setw(20) << pi << endl; // 数値の表示幅を指定
cout << "pi = " << setprecision(15) << pi << endl; // 小数点以下の桁数を指定
cout << "pi = " << setw(20) << setprecision(15) << pi << endl; // 両方指定
cout << "pi(scientific) = " << scientific << setw(20) << setprecision(15) << pi << endl; // 指数
return 0;
}
C++では,ファイル入出力もストリーム機能として利用できる.
実際の手順は,基本的にはC言語と同様であり,「ファイルを開く」「読み書き」「ファイルを閉じる」のステップからなる.
復習:C言語では,「ファイルを開く=fopen()
」「読み書き=fprintf(), fscanf(), fgetc(), fputc()...
」,「ファイルを閉じる=fclose()
」でした.
ファイル入出力のためには,まずファイルストリームを作成し,ファイルを開く必要がある.
ファイルストリームには,出力用の出力ストリーム ofstream
と,入力用の入力ストリーム ifstream
がある.
以下にテキストモードでのファイル出力のサンプルを示す.
ファイルストリームを開いてしまえば,cout
などに出力する方法と基本的に同じであり,わかりやすい.
// テキストファイル出力のサンプル
#include <iostream>
#include <fstream> // ファイル入出力用
#include <cstdlib> // exit()用. C の stdlib.h のC++版
using namespace std;
int main(void)
{
ofstream ofs; // ofstream の変数(インスタンス)を生成.
ofs.open("data.txt"); // ファイルを開く.fopen() に相当.
if( ! ofs ) { // ファイルが開けない場合の処理
cerr << "File open error ! " << endl;
exit(1);
}
ofs << "Hello world!" << endl; // ofs に出力
ofs << "This is a pen." << endl; // ofs に出力
ofs.close(); // ファイルを閉じる.fclose()に相当.
return 0;
}
ファイル出力に対しても,cout, cerr
と同様,マニピュレータを使用して表示桁数や小数点以下桁数の設定が可能である.
ファイルに書き出すサンプルプログラムを実行して,出力ファイルに情報が正しく書かれているか確認してみよう.
同様に,テキストモードでのファイル入力のサンプルを示す.
// テキストファイル入力のサンプル
#include <iostream>
#include <fstream> // ファイル入出力用
#include <cstdlib> // exit()用
using namespace std;
int main(void)
{
ifstream ifs; // ifstream の変数(インスタンス)を生成.
ifs.open("data.txt"); // ファイルを開く.fopen() に相当.
if( ! ifs ) { // ファイルが開けない場合の処理
cerr << "File open error ! " << endl;
exit(1);
}
char data[100]; // データを読み込むバッファ
ifs >> data; // ifs から入力
cout << data << endl; // 確認のため画面に出力
ifs.close(); // ファイルを閉じる.fclose()に相当.
return 0;
}
なお,ストリーム変数の定義と,ファイルを開く部分は,まとめて ifstream ifs("data.txt");
と書くこともできる.
さきのファイルに書き出すサンプルプログラムを実行した際に出力される data.txt を,逆に読み込んで画面に表示してみよう.
ファイルの中身が全て読み出せますか?
バイナリファイル入出力のサンプルを示す.
基本的な手順はテキストファイルと同等である.
違いは,バイナリモードでは,open()
にバイナリ指定を表す引数を追加をしたうえで,関数read(), write()
関数を用いる.
バイナリ入出力についても,C言語のfread()
, fwrite()
を使用してもよい.
// ファイル出力のサンプル(バイナリモード)
#include <iostream>
#include <fstream> // ファイル入出力用
#include <cstdlib> // exit()用
using namespace std;
int main(void)
{
ofstream ofs; // ofstream の変数(インスタンス)を生成.
ofs.open("data.bin", ios::binary); // ファイルを開く.fopen(... , "wb") に相当.
if( ! ofs ) { // ファイルが開けない場合の処理
cerr << "File open error ! " << endl;
exit(1);
}
int data[5]={1,2,3,4,5}; // データ
ofs.write(data, sizeof(int)*5); // ofs に 4 * 5 byte 分書き出し
ofs.close(); // ファイルを閉じる. fclose()に相当.
return 0;
}
// ファイル入力のサンプル(バイナリモード)
#include <iostream>
#include <fstream> // ファイル入出力用
#include <cstdlib> // exit()用
using namespace std;
int main(void)
{
ifstream ifs; // ifstream の変数(インスタンス)を生成.
ifs.open("data.txt", ios::binary); // ファイルを開く.fopen(???, "rb") に相当.
if( ! ifs ) { // ファイルが開けない場合の処理
cerr << "File open error ! " << endl;
exit(1);
}
int data[5]; // データを読み込むバッファ
ifs.read(data, sizeof(int)*5); // ifs から 4 * 5 byte 入力
ifs.close(); // ファイルを閉じる. fclose()に相当.
return 0;
}
C++でのファイル入出力の要点を整理すると,
ifstream
は,ファイルからの入力を行う型(クラス)である.ofstream
は,ファイルへの出力を行う型(クラス)である.ifs, ofs
にストリーム入力・出力することにより,ファイルに情報が読み書きされる.(テキストファイル)ifs.read(), ofs.write()
により,ファイルに情報が読み書きされる.(バイナリファイル)close()
を呼び出し,ファイルを閉じる.
C++言語の一番の拡張の一つは,「クラス(class)」であろう.
クラスとは,簡単に言えば,構造体に「メンバ変数」だけでなく「メンバ関数」を持たせることができる点にある.
C言語の「構造体」は,ユーザーがデータの組み合わせを自由に定義して,プログラムで扱いたいデータをまとめる機能であったが,「クラス」はさらに,そのデータに対してどのような処理を行うかを関数として一緒にパッケージする機能である. (例:複素数,ベクトル,行列,テンソル,画像の色=三原色RGB,住所録など)
class クラス名
{
public: // アクセス指定子
// メンバ変数
型名 変数名;
型名 変数名;
...
// メンバ関数
戻り値型 関数名( 引数1, ...);
戻り値型 関数名( 引数1, ...);
...
};
C++のクラスにおいて,どの部分が拡張されたかというと,
struct
でなく class
を用いる.public
以外にもprotected, private
が指定できる.)である.
では,実際にそれぞれの定義部分を比較してみよう.
// C 言語的な構造体の使用法
#include <iostream>
using namespace std;
// 構造体
struct PersonalData
{
int age;
};
int main(void)
{
PersonalData data; // 構造体 PersonalData型 の変数 data を宣言
data.age = 20; // メンバ変数の値を直接設定
cout << data.age << endl;
return 0;
}
次に,C++でのオブジェクト指向的なプログラミングに変更してみよう.
クラスの名前は,クラスであることが分かりやすいよう,「C」から始まる名称としている.
// C++ 的なクラスの使用法
#include <iostream>
using namespace std;
// クラスの定義
class CPersonalData
{
private: // private指定すると,外部から変更できない.=カプセル化
int age;
public:
// メンバ関数はpublic指定.外部から呼び出し可
void set(int n)
{
if(0<=n && n<=120) // 値の範囲チェック
age = n;
}
void disp_age(void)
{
cout << age << " years old." << endl;
}
};
int main(void)
{
CPersonalData data; // クラス CPersonalData のオブジェクト(変数の実体) data を生成
data.age = -100; // コンパイルエラー! privateメンバの値は直接設定できない.
data.set(20); // 値を設定する専用のメンバ関数 set() を使用.これはOK
data.disp_age(); // メンバ関数を呼び出す
return 0;
}
コンパイル例:
test.cpp:27:7: error: 'age' is a private member of 'CPersonalData'
data.age = -100;
上記2つのソースファイルをコンパイル,実行し,動作を確認せよ.
(コンパイルエラーの出る箇所を確認したら,コメントアウトして再度コンパイルする.)
この例では,クラスのメンバ関数をクラス定義の外に記述している.
#include <iostream>
using namespace std;
// クラスの定義
class CPersonalData
{
private: // private指定
int age;
public:
// メンバ関数の宣言
void set(int n);
void disp_age(void);
};
// クラスのメンバ関数の定義
void CPersonalData::set(int n)
{
if(0<=n && n<=120) // 値の範囲チェック
age = n;
}
void CPersonalData::disp_age(void)
{
cout << age << " years old." << endl;
}
int main(void)
{
CPersonalData data;
data.set(20);
data.disp_age();
return 0;
}
ここで,::
は,スコープ解決演算子である.
その意味は,日本語の「の」と考えればよく,上記の CPersonalData::set
は,CPersonalDataクラス の set 関数,という意味である.
上のプログラムを実行して,出力を確認してみよう.
C++では,クラスが導入されたおかげで,C言語の基本機能だけでは不十分であったいろいろな関数が「クラスライブラリ」として追加されている.
代表的なものでは,標準テンプレートライブラリ(STLと呼ばれる)は基本的にクラス機能を用いて作成されているため,自分でクラスを設計しないにしても,その使用方法などを知っておくことは必要である.
例えば,複素数クラスstd::complex
や,可変長配列クラスstd::vector
などは科学技術計算などで重要である.
また,C++以外でも,PythonやJavaをはじめとする実用的なプログラミング言語では,このクラスの概念を中心に据えた考え方で設計されており,その理解と利用法の習得は必須である.