C++ ポリモーフィズム
ポリモーフィズムとは文字通り複数の形式を意味します。ポリモーフィズムは、クラス間に階層があり、クラスが継承によって関連付けられている場合に使用されます。
C++ ポリモーフィズムとは、メンバー関数が呼び出されたときに、関数を呼び出すオブジェクトの型に応じて異なる関数が実行されることを意味します。
以下の例では、基本クラス Shape は次のように 2 つのクラスに派生します。
例
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) {
}
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) {
}
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Parent class area :
Parent class area :
間違った出力の理由は、呼び出し元の関数がarea()
コンパイラによって基本クラスのバージョンに設定されるためです。これは静的多態性または静的リンクと呼ばれ、関数呼び出しはプログラムの実行前に準備されます。この関数はプログラムのコンパイル中に設定されるため、これは早期バインディングと呼ばれることもあります。area()
次のように、 Shape クラスarea()
の宣言の前にキーワードを配置して、プログラムにわずかな変更を加えます。virtual
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
変更後、前述のコード例をコンパイルして実行すると、次の結果が生成されます。
Rectangle class area :
Triangle class area :
この時点で、コンパイラーはポインターの型ではなく、その内容を調べます。したがって、*shapeにはクラスtriとrecのオブジェクトのアドレスが格納されているため、それぞれのarea()関数が呼び出されます。
各サブクラスには、関数area()の独立した実装があります。これがポリモーフィズムの一般的な使用方法です。ポリモーフィズムを使用すると、同じ名前で実装が異なる関数をすべて持つ複数の異なるクラスを作成でき、関数のパラメーターを同じにすることもできます。
仮想関数
仮想関数は、キーワードを使用して基本クラスでvirtual
宣言された関数です。基本クラスで定義された仮想関数を派生クラスで再定義するときは、その関数に静的にリンクしないようにコンパイラーに指示します。
私たちが望んでいるのは、プログラムのどの時点でも、呼び出されるオブジェクトのタイプに基づいてどの関数を呼び出すかを選択できることです。この操作は動的リンクまたは遅延バインディングと呼ばれます。
純粋仮想関数は、
基底クラスに仮想関数を定義して、派生クラスの関数をオブジェクトにより適したものに再定義したいと考えていますが、仮想関数を基底クラスに有意義に実装することはできません。純粋な仮想関数。
基本クラスの仮想関数はarea()
次のように書き換えることができます。
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
= 0
関数には本体がなく、上記の仮想関数が純粋な仮想関数であることをコンパイラに伝えます。
C++ ファイルとストリーム
iostream 標準ライブラリを使用します。これには、標準入力からストリームを読み取り、標準出力にストリームを書き込むための メソッドcin
とcout
メソッドがそれぞれ用意されています。
ファイルからストリームを読み取り、ファイルにストリームを書き込む方法には、次の 3 つの新しいデータ型を定義するC++ の別の標準ライブラリfstreamを使用する必要があります。
- ofstream このデータ型は出力ファイル ストリームを表し、情報を作成してファイルに書き込むために使用されます。
- ifstream このデータ型は入力ファイル ストリームを表し、ファイルから情報を読み取るために使用されます。
- fstream このデータ型は通常、ファイル ストリームを表し、ofstream 関数と ifstream
関数の両方を備えています。つまり、ファイルの作成、ファイルへの情報の書き込み、ファイルからの情報の読み取りが可能です。
C++ でファイルを処理するには、ヘッダー ファイル<iostream>
とC++ ソース コード ファイルをインクルードする必要があります<fstream>
。
ファイルを開く ファイル
に情報を読み書きする前に、ファイルを開く必要があります。stream オブジェクトと fstream オブジェクトの両方を使用して、書き込み用にファイルを開くことができます。読み取り用にファイルを開くだけの場合は、このifstream
オブジェクトを使用してください。
以下は、fstream、ifstream、および ofstream オブジェクトのメンバーである open() 関数の標準構文です。
void open(const char *filename, ios::openmode mode);
open() メンバー関数の最初のパラメーターは、開くファイルの名前と場所を指定し、2 番目のパラメーターはファイルを開くモードを定義します。
- ios::app 追加モード。すべての書き込みはファイルの末尾に追加されます。
- ios::ate ファイルを開いた後、ファイルの末尾に位置します。
- ios::in 読み取り用にファイルを開きます。
- ios::out 書き込み用にファイルを開きます。
- ios::trunc ファイルがすでに存在する場合、その内容はファイルを開く前に切り詰められます。つまり、ファイルの長さは 0 に設定されます。
上記の2つ以上のモードを組み合わせて使用することもできる。たとえば、ファイルを書き込みモードで開き、ファイルがすでに存在する場合にファイルを切り詰めたい場合は、次の構文を使用できます。
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );
同様に、読み取りと書き込みのためにファイルを開く場合は、次の構文を使用できます。
ifstream afile;
afile.open("file.dat", ios::out | ios::in );
ファイルを閉じる
C++ プログラムが終了すると、すべてのストリームが自動的に閉じてフラッシュされ、割り当てられたメモリがすべて解放され、開いているファイルがあれば閉じられます。ただし、プログラマは、プログラムが終了する前に、開いているファイルをすべて閉じるという良い習慣を身につけるべきです。
以下は、fstream、ifstream、および ofstream オブジェクトのメンバーである close() 関数の標準構文です。
void close();
ファイルへの書き込み
C++ プログラミングでは、この演算子を使用して画面に情報を出力するのと同じように、ストリーム挿入演算子 (<<) を使用して情報をファイルに書き込みます。唯一の違いは、ここでは cout オブジェクトの代わりに ofstream または fstream オブジェクトが使用されていることです。
ファイルの読み取り
C++ プログラミングでは、キーボードから情報を入力する場合と同じように、ストリーム抽出演算子 ( >> ) を使用してファイルから情報を読み取ります。唯一の違いは、ここでは cin オブジェクトの代わりに ifstream または fstream オブジェクトが使用されていることです。
読み取りと書き込みの例
次の C++ プログラムは、読み取り/書き込みモードでファイルを開きます。ユーザーが入力した情報をファイル afile.dat に書き込んだ後、プログラムはファイルから情報を読み取り、画面に出力します。
例
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[100];
// 以写模式打开文件
ofstream outfile;
outfile.open("afile.dat");
cout << "Writing to the file" << endl;
cout << "Enter your name: ";
cin.getline(data, 100);
// 向文件写入用户输入的数据
outfile << data << endl;
cout << "Enter your age: ";
cin >> data;
cin.ignore();
// 再次向文件写入用户输入的数据
outfile << data << endl;
// 关闭打开的文件
outfile.close();
// 以读模式打开文件
ifstream infile;
infile.open("afile.dat");
cout << "Reading from the file" << endl;
infile >> data;
// 在屏幕上写入数据
cout << data << endl;
// 再次从文件读取数据,并显示它
infile >> data;
cout << data << endl;
// 关闭打开的文件
infile.close();
return 0;
}
上記のコードをコンパイルして実行すると、次の入力と出力が生成されます。
$./a.out
Writing to the file
Enter your name: Zara
Enter your age: 9
Reading from the file
Zara
9
上記の例では、外部から行を読み取る getline() 関数などの cin オブジェクトの追加関数が使用され、ignore() 関数は前の read ステートメントによって残された冗長文字を無視します。
ファイル ロケーション ポインター
istream と ostream は両方とも、ファイル ロケーション ポインターを再配置するためのメンバー関数を提供します。これらのメンバー関数には、istream の seekg (「seek get」) および ostream の lookp (「seek put」) が含まれます。
通常、seekg および Seekp の引数は長整数です。2 番目のパラメータは、検索方向を指定するために使用できます。ios::beg
検索方向は、(デフォルトでは、ストリームの先頭からシーク)、ios::cur
(ストリームの現在位置からシーク)、またはios::end
(ストリームの末尾からシーク)です。
ファイル位置ポインタは、ファイルの先頭からポインタの位置までのバイト数を指定する整数値です。「get」ファイルの場所ポインターを見つける例を次に示します。
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
C++ 動的メモリ
C++ で動的メモリがどのように機能するかを知ることは、優れた C++ プログラマになるために不可欠です。C++ プログラムのメモリは 2 つの部分に分かれています。
- スタック: 関数内で宣言されたすべての変数はスタック メモリを占有します。
- ヒープ: これはプログラム内の未使用のメモリであり、プログラムの実行中に動的にメモリを割り当てるために使用できます。
多くの場合、定義された変数に特定の情報を格納するために必要なメモリの量を事前に予測することはできず、必要なメモリのサイズは実行時に決定する必要があります。
C++ では、割り当てられた領域のアドレスを返す特別な演算子を使用して、実行時に特定の型の変数にヒープ上のメモリを割り当てることができます。この演算子がnew
演算子です。
動的に割り当てられたメモリ領域が不要になった場合は、delete 演算子を使用して、new 演算子によって以前に割り当てられたメモリを削除できます。
new 演算子と delete 演算子
以下は、new 演算子を使用して任意のデータ型に動的にメモリを割り当てるための一般的な構文です。
new data-type;
ここで、data-type には、配列を含む任意の組み込みデータ型、またはクラスや構造体を含む任意のユーザー定義データ型を指定できます。まず、組み込みのデータ型を見てみましょう。たとえば、double
型へのポインタを定義してから、実行時に割り当てられるメモリを要求できます。次のように演算子を使用してnew
これを行うことができます。
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
空きストアが使い果たされると、メモリの割り当てが成功しない可能性があります。new
したがって、演算子が NULL ポインターを返すかどうかを確認し、次の適切なアクションを実行することをお勧めします。
double* pvalue = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
malloc()
関数は C 言語で登場し、C++ にもまだ存在しますが、malloc() 関数はできるだけ使用しないことをお勧めします。malloc() に対する new の主な利点は、new がメモリを割り当てるだけでなく、オブジェクトも作成することです。
動的にメモリが割り当てられた変数が不要になったと感じたときは、次のように、いつでも delete 演算子を使用して変数が占有しているメモリを解放できます。
delete pvalue; // 释放 pvalue 所指向的内存
上記の概念は次の例で使用されており、new 演算子と delete 演算子の使用方法を示しています。
例
#include <iostream>
using namespace std;
int main ()
{
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
*pvalue = 29494.99; // 在分配的地址存储值
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue; // 释放内存
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Value of pvalue : 29495
配列の動的メモリ割り当て
文字配列 (20 文字の文字列) にメモリを割り当てたいとします。次のように、上記の例の構文を使用して配列にメモリを動的に割り当てることができます。
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存
作成したばかりの配列を削除するには、ステートメントは次のようになります。
delete [] pvalue; // 删除 pvalue 所指向的数组
以下は、多次元配列にメモリを割り当てることができる new 演算子の一般的な構文です。
一维数组
// 动态分配,数组长度为 m
int *array=new int [m];
//释放内存
delete [] array;
二维数组
int **array;
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n];
}
//释放
for( int i=0; i<m; i++ )
{
delete [] array[i];
}
delete [] array;
2 次元配列インスタンスのテスト:
例
#include <iostream>
using namespace std;
int main()
{
int **p;
int i,j; //p[4][8]
//开始分配4行8列的二维数据
p = new int *[4];
for(i=0;i<4;i++){
p[i]=new int [8];
}
for(i=0; i<4; i++){
for(j=0; j<8; j++){
p[i][j] = j*i;
}
}
//打印数据
for(i=0; i<4; i++){
for(j=0; j<8; j++)
{
if(j==0) cout<<endl;
cout<<p[i][j]<<"\t";
}
}
//开始释放申请的堆
for(i=0; i<4; i++){
delete [] p[i];
}
delete [] p;
return 0;
}
三维数组
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
array[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array[i][j] = new int [h];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;
3次元配列のテスト例:
例
#include <iostream>
using namespace std;
int main()
{
int i,j,k; // p[2][3][4]
int ***p;
p = new int **[2];
for(i=0; i<2; i++)
{
p[i]=new int *[3];
for(j=0; j<3; j++)
p[i][j]=new int[4];
}
//输出 p[i][j][k] 三维数据
for(i=0; i<2; i++)
{
for(j=0; j<3; j++)
{
for(k=0;k<4;k++)
{
p[i][j][k]=i+j+k;
cout<<p[i][j][k]<<" ";
}
cout<<endl;
}
cout<<endl;
}
// 释放内存
for(i=0; i<2; i++)
{
for(j=0; j<3; j++)
{
delete [] p[i][j];
}
}
for(i=0; i<2; i++)
{
delete [] p[i];
}
delete [] p;
return 0;
}
オブジェクトの動的メモリ割り当て
オブジェクトは単純なデータ型と何ら変わりません。たとえば、次のコードを見てください。このコードでは、オブジェクトの配列を使用してこの概念を明確にしています。
例
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
4 つの Box オブジェクトの配列にメモリを割り当てたい場合、コンストラクターは 4 回呼び出されます。同様に、これらのオブジェクトが削除されると、デストラクターは同じ回数 (4 回) 呼び出されます。
上記のコードをコンパイルして実行すると、次の結果が生成されます。
调用构造函数!
调用构造函数!
调用构造函数!
调用构造函数!
调用析构函数!
调用析构函数!
调用析构函数!
调用析构函数!
C++ 名前空間
クラスに Zara という名前の生徒が 2 人いる場合、それらを明確に区別するために、名前以外に自宅の住所や両親の名前などの追加情報を使用する必要がある状況を想定します。
同じ状況が C++ アプリケーションでも発生します。たとえば、xyz() という関数を作成すると、同じ関数 xyz() が別の利用可能なライブラリに存在します。こうすることで、コンパイラはどの xyz() 関数を使用しているかを認識できなくなります。
そこで、上記の問題を解決するために特別に使用される名前空間の概念が導入され、異なるライブラリ内で同じ名前の関数、クラス、変数などを区別するための追加情報として使用できます。名前空間を使用すると、コンテキストが定義されます。基本的に、名前空間はスコープを定義します。
ネームスペースの定義
ネームスペースは、次のようにキーワード namespace の後にネームスペースの名前を指定して定義します。
namespace namespace_name {
// 代码声明
}
名前空間を使用して関数または変数を呼び出すには、次のように名前空間の名前を前に付ける必要があります。
name::code; // code 可以是变量或函数
名前空間が変数や関数などのエンティティのスコープをどのように定義するかを見てみましょう。
例
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Inside first_space
Inside second_space
using ディレクティブ using
namespace ディレクティブを使用すると、名前空間の名前をプレフィックスとして付けずに名前空間を使用できます。このディレクティブは、後続のコードで指定された名前空間内の名前を使用することをコンパイラーに指示します。
例
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main ()
{
// 调用第一个命名空间中的函数
func();
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Inside first_space
using ディレクティブを使用して、名前空間内の特定の項目を指定することもできます。たとえば、std 名前空間の cout 部分のみを使用する場合は、次のステートメントを使用できます。
using std::cout;
後続のコードでは、cout を使用するときに名前空間名をプレフィックスとして追加する必要はありませんが、std 名前空間の他の項目では、次のように名前空間名をプレフィックスとして追加する必要があります。
例
#include <iostream>
using std::cout;
int main ()
{
cout << "std::endl is used with std!" << std::endl;
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
std::endl is used with std!
ディレクティブを使用して導入された名前は、通常のスコープ規則に従います。名前は、using ディレクティブの先頭からスコープの終わりまで表示されます。この時点で、スコープ外で定義された同じ名前のエンティティは非表示になります。
独立した名前空間
名前空間は複数の異なる部分で定義できるため、名前空間は個別に定義されたいくつかの部分で構成されます。名前空間のさまざまなコンポーネントは、複数のファイルに分散することができます。
したがって、名前空間のコンポーネントが別のファイルで定義された名前を要求する必要がある場合でも、その名前を宣言する必要があります。次の名前空間定義では、新しい名前空間を定義したり、既存の名前空間に新しい要素を追加したりできます。
namespace namespace_name {
// 代码声明
}
ネストされた名前空間
名前空間はネストでき、次のように別の名前空間内に名前空間を定義できます。
namespace namespace_name1 {
// 代码声明
namespace namespace_name2 {
// 代码声明
}
}
:: 演算子を使用して、ネストされた名前空間のメンバーにアクセスできます。
// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
// 访问 namespace_name1 中的成员
using namespace namespace_name1;
上記のステートメントで、namespace_name1 が使用されている場合、次のように、namespace_name2 の要素もこのスコープで使用できます。
例
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
}
using namespace first_space::second_space;
int main ()
{
// 调用第二个命名空间中的函数
func();
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Inside second_space
C++ テンプレート
テンプレートは、特定の型に依存しない方法でコードを記述する汎用プログラミングの基礎です。
テンプレートは、ジェネリック クラスまたは関数を作成するための設計図または式です。イテレータやアルゴリズムなどのライブラリ コンテナは汎用プログラミングの例であり、どちらもテンプレートの概念を使用します。
各コンテナにはベクトルなどの単一の定義があり、 や など、さまざまな種類のベクトルを定義できvector <int>
ますvector <string>
。
関数とクラスはテンプレートを使用して定義できます。
関数テンプレート
テンプレート関数定義の一般的な形式は次のとおりです。
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
ここで、 type は関数で使用されるデータ型のプレースホルダー名です。この名前は関数定義で使用できます。
以下は、最大 2 つの数値を返す関数テンプレートの例です。
例
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World
クラス テンプレート
関数テンプレートを定義するのと同じように、クラス テンプレートも定義できます。ジェネリック クラス宣言の一般的な形式は次のとおりです。
template <class type> class class-name {
.
.
.
}
ここで、type はクラスのインスタンス化時に指定できるプレースホルダー型名です。カンマ区切りリストを使用して、複数の汎用データ型を定義できます。
次の例では、クラス Stack<> を定義し、スタックから要素をプッシュおよびポップする汎用メソッドを実装します。
例
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{
// 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
7
hello
Exception: Stack<>::pop(): empty stack
C++ プリプロセッサ
プリプロセッサは、実際のコンパイルの前にどのような前処理を行うかをコンパイラに指示するディレクティブです。
すべてのプリプロセッサ ディレクティブはシャープ記号 (#) で始まり、プリプロセッサ ディレクティブの前に使用できるのはスペース文字のみです。前処理ディレクティブはC++ ステートメントではないため、セミコロン (;) で終わりません。
これまでのすべての例に #include ディレクティブが存在することがわかりました。このマクロは、ヘッダー ファイルをソース ファイルにインクルードするために使用されます。
C++ は、 #include、#define、#if、#else、#lineなどの多くの前処理ディレクティブもサポートしています。これらの重要なディレクティブを見てみましょう。
#define preprocessing
#define preprocessing ディレクティブは、シンボリック定数を作成するために使用されます。この記号定数は通常マクロと呼ばれ、命令の一般的な形式は次のとおりです。
#define macro-name replacement-text
このコード行がファイルに出現すると、そのファイル内で以降に出現するマクロはすべて、プログラムがコンパイルされる前に置換テキストに置き換えられます。例えば:
#include <iostream>
using namespace std;
#define PI 3.14159
int main ()
{
cout << "Value of PI :" << PI << endl;
return 0;
}
次に、このコードをテストして、前処理の結果を確認してみましょう。ソース コード ファイルがすでに存在すると仮定して、-E オプションを使用してコンパイルし、結果を test.p にリダイレクトします。ここで、test.p ファイルを見ると、すでに多くの情報が含まれており、ファイルの下部の値が次のように変更されていることがわかります。
$ gcc -E test.cpp > test.p
...
int main ()
{
cout << "Value of PI :" << 3.14159 << endl;
return 0;
}
パラメータ マクロ
#define を使用して、次のようにパラメータを含むマクロを定義できます。
#include <iostream>
using namespace std;
#define MIN(a,b) (a<b ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cout <<"较小的值为:" << MIN(i, j) << endl;
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
较小的值为:30
条件付きコンパイル
プログラムのソース コードの一部を選択的にコンパイルするために使用できるディレクティブがいくつかあります。このプロセスは条件付きコンパイルと呼ばれます。
条件付きプリプロセッサの構造は、if 選択構造と非常によく似ています。次のプリプロセッサ コードを考えてみましょう。
#ifdef NULL
#define NULL 0
#endif
デバッグ時のみコンパイルできます。デバッグ スイッチは次のようなマクロで実装できます。
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
#ifdef DEBUG 指令の前に記号定数 DEBUG が定義されている場合、プログラム内の cerr ステートメントはコンパイルされます。次のように #if 0 ステートメントを使用してプログラムの一部をコメントアウトできます。
#if 0
不进行编译的代码
#endif
例
#include <iostream>
using namespace std;
#define DEBUG
#define MIN(a,b) (((a)<(b)) ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
#ifdef DEBUG
cerr <<"Trace: Inside main function" << endl;
#endif
#if 0
/* 这是注释部分 */
cout << MKSTR(HELLO C++) << endl;
#endif
cout <<"The minimum is " << MIN(i, j) << endl;
#ifdef DEBUG
cerr <<"Trace: Coming out of main function" << endl;
#endif
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Trace: Inside main function
The minimum is 30
Trace: Coming out of main function
# 和 ## 运算符
# 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
次のマクロ定義を参照してください。
例
#include <iostream>
using namespace std;
#define MKSTR( x ) #x
int main ()
{
cout << MKSTR(HELLO C++) << endl;
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
HELLO C++
どのように機能するかを見てみましょう。当然のことながら、C++ プリプロセッサは次の行を挿入します。
cout << MKSTR(HELLO C++) << endl;
に変換:
cout << "HELLO C++" << endl;
## 演算子は、2 つのトークンを連結するために使用されます。以下に例を示します。
#define CONCAT( x, y ) x ## y
CONCAT がプログラム内に出現すると、その引数が連結されてマクロの代わりに使用されます。たとえば、次の例に示すように、プログラム内の CONCAT(HELLO, C++) は「HELLO C++」に置き換えられます。
例
#include <iostream>
using namespace std;
#define concat(a, b) a ## b
int main()
{
int xy = 100;
cout << concat(x, y);
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
100
どのように機能するかを見てみましょう。当然のことながら、C++ プリプロセッサは次の行を挿入します。
cout << concat(x, y);
に変換:
cout << xy;
C++ の事前定義マクロ
例
#include <iostream>
using namespace std;
int main ()
{
cout << "Value of __LINE__ : " << __LINE__ << endl;
cout << "Value of __FILE__ : " << __FILE__ << endl;
cout << "Value of __DATE__ : " << __DATE__ << endl;
cout << "Value of __TIME__ : " << __TIME__ << endl;
return 0;
}
上記のコードをコンパイルして実行すると、次の結果が生成されます。
Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48