型はデータのコンセプトや,関数が扱うデータの種類を表す.例えばint型は整数を表現した型であり,利用可能な関数や演算子,それを表現するためのデータの大きさを示す.関数の場合,型によって関数に入力するオブジェクト,関数が出力するオブジェクトなどを表す.型の指定は変数や記号定数,関数の宣言や定義の際に行う.
プログラマは各型の値を項とする加算や乗算,比較,別の型への変換,関数呼び出しなどの計算処理(演算)が行える(関数自体は演算ではない.あくまで呼び出すのが演算).演算は演算子によって行われる.各型はそれぞれ利用可能な演算があり,同じ演算子であっても型毎に異なる計算が行われる.また,いくつかの演算子はプログラマによって多重定義(オーバーロード)可能である.
各データが扱えるデータの種類(例えば整数や浮動小数)は型によって決定するが,型の大きさや解釈方法は言語の仕様ではなく,言語処理系に依存する(ここでは,言語処理系といった場合コンパイラかコンピュータのいずれかであることを示す).例えば,整数を2の補数,浮動小数をIEEE754の32ビット表現で表すコンピュータがあったとする,ここで,そのコンピュータの1バイトが8ビットで,整数型と浮動小数型が共に4バイトで表される場合,メモリ内のビット列"00111111110000000000000000000000"は,整数型では1069547520,浮動小数型では1.5と解釈される.ある値が表現可能かどうかは表現方法とサイズによって決まる.すなわちそれは言語処理系に依存するため,プログラマはそれらに依存するようなコードを可能な限りさけるべきである.各型の最大値や最小値などはclimitsヘッダで定義されており,サイズはsizeof演算子(詳細は後述)で取得できる.
型の種類とリテラル(値)
分類 | 型の例 | |||
---|---|---|---|---|
組み込みデータ型 | 算術演算型 | 整数系データ型 | 論理型 | bool |
文字型 | charなど | |||
整数型 | intなど | |||
浮動小数点数型 | doubleなど | |||
void型 | void | |||
派生型 | 配列型 | char[]など | ||
ポインタ型 | int*など | |||
参照型 | int&など | |||
関数型 | int F()など | |||
ユーザ定義型 | クラス型 | class T | ||
構造体型 | struct T | |||
共用体型 | union T | |||
列挙型 | enum T |
- TやFはユーザが定義した名前.
C++では,Cと異なりユーザ定義型の型名は,ユーザ定義型の種類を指定する必要はない(例えば"enum e"を定義した場合,型を指定するには"e"だけでも良い).各型についての概要は以下の通りである.
- 論理型
bool型は真か偽のいずれかの値を持ち,論理演算の結果を表現するために使用される.それらの値のリテラルは,真がtrue,偽がfalseである.定義上,trueは整数に変換すると1,falseは0になる.逆に整数やポインタは暗黙的にbool型に変換される.値が0以外はtrueに,0はfalseに変換される.
- 文字型
-
char型は実装される文字セットの1文字の文字コードを保持できる.多くの場合それは8ビットなので,256種類の値である.また,修飾子signedやunsignedによって符号付きか否かを設定できる.ただし修飾子なしのcharがsignedかunsignedかは言語処理系に依存する.
C++では,Unicodeなどの大規模な文字セットの文字コードを格納するためにwchar_t型が提供される.wchar_tのサイズは実装によって定義され,実際のlocaleがサポートする最大の文字セットを保持できるだけの大きさを持つ.
文字型は整数系データ型であるため,算術,論理演算を適用できる.
文字リテラルは,シングルクォートで囲まれた文字である(例:'a','0').プログラマは,ある文字セットの文字コードに対応する整数よりも文字リテラルを記述することが望ましい(移植性が高いため).また,ワイド文字リテラルは,L'ab'というように頭にLをつける.''の間に入る文字数とその意味はwchar_t型に合わせて実装される.
また,Cではsizeof('a')はsizeof(int)に等しかったが,C++ではsizeof(char)に等しいので注意。
- 整数型
-
整数を扱うための型.整数型には次の6種類がある.
- signed short int
- signed int
- signed long int
- unsigned short int
- unsigned int
- unsigned long int
整数リテラルには次の4種類がある.
種類 接頭辞 記述例 10進リテラル -なし- 1, -12, 89 8進リテラル 0 01, 07, 017 16進リテラル 0x, 0X 0x1, 0x03af, 0x3Fa 文字リテラル -省略- 種類 接尾辞 記述例 long int l, L 3l, -036l, 0x369L unsined u, U 3u, -0x36U 接尾辞がない場合,整数リテラルが10進表記の時は,次の候補の中でその値を表現できる最初の型(上の方)になる.
- signed int
- signed long int
- unsigned long int
- signed int
- unsigned int
- signed long int
- unsigned long int
- 浮動小数点数型
浮動小数点を扱うための型であり,以下の3種類がある.
- float(単精度)
- double(倍精度)
- long double(拡張制度)
- void型
void型のオブジェクトは存在しない(できない).voidは関数が値を返さないことを示す場合や,未知のオブジェクト型へのポインタ(void*)の基本型として使用される(いくつかのポインタをvoid*に暗黙的に型変換できない.詳細は「ポインタ変換と参照変換」で説明する.).
- ポインタ型
Tという型がある時,T*はTのポインタ型である.T*型はTのオブジェクトのアドレスを保持する.ポインタリテラルはnullポインタ,つまり0だけである(NULL).
ポインタ型のオブジェクトを含むメモリアドレスを指すもの全般(例:関数名や配列名など)に対して単にポインタと呼ぶことがあるので,誤解なきよう注意.ポインタのサイズは,各言語処理系のアドレッシングメカニズムによって決定する.
- 配列型
特定の型を持つ長さが固定の連続するオブジェクトの集合である.例えばTという型がある時,T[size]は"T型の要素size個による配列"型を表す.配列名(配列のオブジェクトの名前)はその配列の先頭要素へのポインタとして利用できる.各要素は[]演算子を使って,0からsize-1までの添字でアクセスできる.
配列は値のリストによって初期設定できる.以下に例を示す.
初期設定リストの要素数が配列の要素数を超えた場合,エラーになる.逆に,足りない場合は,足りない要素は皆その型の0(bool型ならばfalse, char型ならばNULL文字といった感じ)で初期化される.また,配列の要素数を指定せずに初期設定リスト付きで宣言した場合,配列の長さは要素の数になる.配列型のサイズは,派生させた型のサイズに要素数で掛けた値である.int v1[3] = {1, 2, 4}; char v2[] = {'a', 'b', 'c', 0};
文字列リテラルの型は,要素数が文字の数に1を加えた文字型の定数の配列であり,Cや昔のC++の定義と互換があるためにchar*に代入できる.そしてその配列の要素数は,末尾に終端記号(NULL文字)'\0'があるため見た目上の文字の数より1多い.
- 参照型
オブジェクトの別名である.参照型はコピー(値渡し)を避ける目的で,主に関数パラメータとして使用される.Tという型がある時,T&はT型への参照型を表す.参照型の定義には必ず初期設定子が必要である.
- 関数
-
関数の詳細は「関数」で説明し,ここでは概要だけ触れる.C++では,関数にも型がある.関数とは複合文のことであり,0個以上の数の項(型もユーザが定義する)を受け取り,定義された文に従った処理を行い,所定の型のオブジェクトを1つ返す(結果を返さないこともできる).関数の型は,入力と出力に用いるオブジェクトの型やその数を表している.
関数に与える項は一般的に引数と呼ばれる.定義した関数に対し,プログラマが行える処理は,関数呼び出し演算によって呼び出すか,関数名から関数の先頭アドレスを取得するかしかない."T func(int, int);"という関数の型を宣言した時,funcは「int型の引数を2つ受け取り,T型のオブジェクトを返す」関数である.
関数が出力するオブジェクトの型として,配列を定義することはできない. そのような場合,ある型のポインタ型のオブジェクトを出力する関数として定義し,配列の先頭アドレスを出力結果とすることで,配列を渡すことができる.(引数としては配列は指定可能).また,関数の型は,ポインタ型や参照型,配列と組み合わせることで,関数のアドレスを保持するデータを生成できる.ただし関数の配列は定義できない.
C++では,特定の規則に従った同じ名前で出力や入力が異なる関数を定義(多重定義)したり,既存の関数を新たに定義した関数で論理的に上書きすることができる.
- 列挙型
ユーザが指定した一連の値を保持できる型である.名前付き整数定数は列挙のメンバ(列挙子)として定義することができる.列挙子は0から順に設定され,明示的に初期値を指定しない限り,各列挙子の値は1つ前のメンバに1を加算した値である.列挙子の型はその列挙型であり,その値はint型への型変換や整数型の昇格によってint型に昇格可能な値でなければならない.また,列挙子の各値は同じであっても良い.
列挙型のメンバは,整数系データ型を返す定数式によって初期化できる.
列挙子の上限は,その列挙型に含まれるすべての列挙子の値よりも大きい2の累乗値マイナス1の中でもっとも小さい値である.下限は,最小値が非負である場合は0,それ以外の場合は最小値よりも小さい2の累乗値プラス1の中で最も大きい値である.以下にそれらの例を示す.
enum e1 {dark, light}; // 範囲 0 : 1 enum e2 {a = 3, b = 9}; // 範囲 0 : 15 enum e3 {min = -10, max = 1000000 }; // 範囲 -1048576 : 1048575
列挙型のサイズは,intやunsigned intで表現できない列挙子が含まれていない限り,int型のサイズ以下であることとだけ保証される.
- クラス
ユーザがオブジェクトプログラミングのオブジェクトを表現するために使用される型である.詳細は「オブジェクト指向プログラミング」で説明するが,クラスは,いくつかの関数や変数,記号定数をメンバとして保持できる.これにより,あるコンセプトに基づくデータやそれを扱う関数を関連付けて表現できる.
また,C++では,定義されたクラスを拡張することで新たなクラスや構造体を定義すること(継承)や,そのクラス型のオブジェクトのメンバをそのクラス以外の関数やクラスから隠すこと(アクセス制御)などができる.クラス型の大きさは,各メンバの型の大きさの合計値とは限らず,言語処理系に依存する.これはマシンが効率的に処理できるように各メンバの位置を変更する可能性があることに起因する.オブジェクトの各メンバにアクセスするためにはドット演算子,ポインタが指す先のオブジェクトの各メンバにアクセスするためにはアロー演算子を用いる.struct String { public: // 以降のメンバを他からアクセス可能に char *value; unsigned int length; void dump(){ std::cout << value << std::endl; } };
クラス型のオブジェクトのメンバは,標準では,そのクラスのスコープの中でしかアクセスできない.また,メンバへのアクセスを明示的に可能にした場合であっても,継承時に特別な指定をしない限り,継承後のクラスや構造体でアクセスできない(フレンドを除く).
- 構造体
C++では,構造体はCと異なる性質を持ち,クラス型の一種である.構造体のオブジェクトのメンバは,標準では,アクセス制限が行われていない.また,継承時に特別な指定をしない限り,元の構造体のアクセス制限されていない全てのメンバにアクセスできる.これらの標準の設定を除き,構造体の性質は,クラス型のそれと等しい.
struct String { char *value; unsigned int length; void dump(){ std::cout << value << std::endl; } };
- 共用体
各要素が同じアドレスを持つstructである.例えば次のような共用体があるとする.
union uni { int i; char c; };
また,共用体は,コンストラクタやデストラクタを持つメンバを保持できない.これはコンパイラが共用体のどのメンバ(型)が使われるかを判断できないためである.さらに,共用体は静的メンバ(あるクラスから生成された全てのオブジェクトで共有されるメンバ)を持つことが出来ない.
ゼロ
0はint型のリテラルであるが,後述する標準変換によって全ての算術演算型とポインタ型に代入することができる.それぞれの型の意味は,次の通りである.
型 | 意味 |
---|---|
論理型 | false |
文字型 | NULL文字(終端記号) |
整数系データ型 | 0 |
浮動小数点型 | 0.0 |
ポインタ型 | NULLポインタ |
サイズ
C++では算術演算型の大きさは言語処理系に依存する.ただし,C++のオブジェクトのサイズはchar型のサイズの倍数によって表現できるので,char型のサイズは定義上1となっている.プログラマはオブジェクトの型のサイズをsizeof演算子によって取得できる.算術演算型のサイズについて次のことが保証されている.ここでNはchar,short int,int,long intの中のいずれかであることを表す.
1 ≡ sizeof(char) ≦ sizeof(short) ≦ sizeof(int) ≦ sizeof(long) 1 ≦ sizeof(bool) ≦ sizeof(long) sizeof(char) ≦ sizeof(wchar_t) ≦ sizeof(long) sizeof(float) ≦ sizeof(double) ≦ sizeof(long double) sizeof(N) ≡ sizeof(signed N) ≡ sizeof(unsigned N)
型 | サイズの下限値 |
---|---|
char | 8ビット |
short int | 16ビット |
long int | 32ビット |
算術演算型とvoid型以外のオブジェクトのサイズは,それぞれの項目で記した通りである.
型宣言
構文
<未完成-_-.>
- declarator
- declarator-id
- ptr-operator declarator
- declarator ( argument-declaration-list ) cv-qualifier-listopt exception-specificationopt
- declarator [ constant-expressionopt ]
- (declarator )
- abstract-declarator
- ptr-operator abstract-declaratoropt
- abstract-declaratoropt ( argument-declaration-list ) cv-qualifier-listopt exception-specificationopt
- abstract-declaratoropt [ constant-expressionopt ]
- ( abstract-declarator )
- declarator-id
- ::opt id-expression
- ::opt nested-name-specifieropt type-name
- ptr-operator
- * cv-qualifier-listopt
- & cv-qualifier-listopt
- nested-name-specifier :: * cv-qualifier-listopt
- cv-qualifier-list
cv-qualifier cv-qualifier-listopt - argument-declaration-list
- argument-declaration
- argument-declaration-list , argument-declaration
- argument-declaration
- decl-specifier-list declarator
- decl-specifier-list declarator = assignment-expression
- decl-specifier-list abstract-declarator
- decl-specifier-list abstract-declarator = assignment-expression
- decl-specifier-list
- decl-specifier-listopt decl-specifier
- decl-specifier
- storage-class-specifier
- type-specifier
- function-specifier
- friend
- typedef
- id-expression
- unqualified-id
- qualified-id
- unqualified-id
-
identifier 名前 operator-function-id 演算子関数名 conversion-function-id 変換関数名 ~ class-name デストラクタ名 template-id テンプレート名 - type-specifier
- simple-type-name
- class-specifier
- enum-specifier
- elaborated-type-specifier
- const
- volatile
- 補足
- cv-qualifier は const かvolatile.
- nested-name-specifierは完全修飾名
- exception-specificationは例外スローの制限を指定.
- qualified-idは"nested-name-specifier templateopt unqualified-id "
- storage-class-specifierは記憶指定子.詳細は「オブジェクト-記憶指定子-」で説明する.
- function-specifierは関数修飾指定子.
typenameによる曖昧な宣言の解消
関数や変数の宣言で型を指定する場合,C++コンパイラはその文が型を意味するのか判断できない場合がある。そのような場合,typenameキーワードを使用して明示的に型であることを示さなければならないことがある。例えば次のコードの"T::v *a"は乗算演算ともT::v型のポインタの定義とも判断可能である(templateやclassについてはそれぞれ「ジェネリックプログラミング」と「オブジェクト指向プログラミング」を参照されたし)。これを解消するため,プログラマは頭にtypenameを付ける必要がある。
class ClsA
{
public:
int v;
};
class ClsB
{
public:
class v{
};
};
template<class T> void func()
{
typename T::v *a;
}
int main()
{
func<ClsB>();
}
意味
ここでは,記述された型の意味を説明する. 優先順位と結合規則
記号 | 結合規則 | 型 | 演算子の位置(名前に対しての相対位置) |
---|---|---|---|
() | 関数 | 後置 | |
[] | → | 配列 | 後置 |
& | ← | 参照 | 前置 |
*const | ← | 定数ポインタ | 前置 |
* | ← | ポインタ | 前置 |
int i; // iはint型のオブジェクト
int *v[10]; // vはint型のオブジェクトへのポインタ型のオブジェクトを10個持つ配列
int (*x)[10]; // xはint型のオブジェクトを10個持つ配列へのポインタ
int array[10][20]; // arrayはint型のオブジェクトを10個持つ配列を20個持つ配列
int func1(); // func1はint型のオブジェクトを返す関数
void func2(int); // func2はint型のオブジェクトを受け取り返り値がない関数
void func3(int c); // func3はint型のオブジェクトを受け取り返り値がない関数
int *func4(); // func4はint型へのポインタ型のオブジェクトを返す関数
int (*func5)(); // func5はint型のオブジェクトを返す関数へのポインタ型のオブジェクト
型修飾子
- const
- 最適化を促すための修飾子。ある型のデータが初期化以降値が変更されないことを示すことで,コンパイラが最適化しやすくする。
- volatile
- 最適化を抑制するための修飾子。 例えばある変数が定義されてから一度も使用されない場合,その変数は存在しない方が初期化も不要であり,かつメモリも浪費しない。このような場合,コンパイラはこの変数をなかったこととして扱うことがある。しかしソースコードが複数に分かれている場合や外部的にその領域が使用される場合,その変数を削除すると不具合が生じる。プログラマはvolatile修飾子を付けることで,そのようなことを防ぐことができる。
演算
演算とは,演算子を含む式によって行われる処理のことである.演算は,演算子といくつかの項(オペランド)を記述することで表現され,結果を値で返したり副作用を引き起こしたりする.演算子は項の型によって振る舞いが異なったり利用不可能であったりする.また,いくつかの演算子は多重定義可能であり,プログラマはユーザ定義型の型に対し新たに振る舞いを定義できる.
演算子の種類
ここでは,標準で定義されている演算子について説明する.内容は優先順位と結 合規則,演算の種類,文法,振る舞いである.優先順位についての詳細は後述す るが,優先順位は数字が少ない程高く,結合規則は⇢が「右から左へ」,←が「左から右へ」に対しての結合を表す.
優先順位 | 結合規則 | 演算の種類 | 文法 | 多重定義可能 | 注釈 | |
---|---|---|---|---|---|---|
0 | → | スコープ解決演算子 | スコープ解決 | class_name::member | クラスや名前空間に属するメンバの名前を指定する. | |
namespace_name::member | ||||||
グローバルスコープ解決 | ::name | グローバルスコープに属する名前を指定する. | ||||
::qualified-name | ||||||
1 | → | 後置式 | メンバ選択 | object.member | ||
pointer->member | ○ | |||||
添字 | pointer [ expr ] | ○ | ||||
関数呼び出し | expr ( expr_list ) | ○ | ||||
後置インクリメント | lvalue++ | ○ | 演算結果は項の値である.演算結果がその文中の全ての演算で使用された後,副作用によって項の値がインクリメントまたはデクリメントされる. | |||
後置デクリメント | lvalue-- | ○ | ||||
型識別 | typeid ( type ) | exprならば,その式が返すオブジェクトの型の,typeならばその型の,情報を保持するstd::type_infoの参照型を返す.std::type_infoは,ヘッダtypeinfoで宣言される.参照型かポインタの被演算子の値が0の時,std::bad_typeid例外を投げる. | ||||
実行時型識別 | typeid ( expr ) | |||||
コンストラクタによる型変換 | type ( expr_list ) | ○ | 別名関数スタイルのキャスト.typeが組み込みデータ型であればコンパイル時チェック付きキャストと同じ意味.ただしポインタへの変換は構文エラーを起こす.項を指定しない場合の値は,その型のデフォルト値である.組み込みデータ型がその型に変換した0(論理型のfalseや,文字型のNULL文字など),ユーザ定義型がデフォルトコンストラクタと定義されている. | |||
実行時チェック付き変換 | dynamic_cast<type > ( expr ) | |||||
コンパイル時チェック付き変換 | static_cast<type > ( expr ) | |||||
チェックなし変換 | reinterpret_cast < type > ( expr ) | |||||
const変換 | const_cast < type > ( expr ) | |||||
2 | ← | 単項演算子 | オブジェクトサイズ | sizeof expr | ||
型のサイズ | sizeof ( expr ) | |||||
前置インクリメント | ++lvalue | ○ | 項をインクリメントまたはデクリメントする.演算結果は算術後の値. | |||
前置デクリメント | --lvalue | ○ | ||||
補数 | ~expr | ○ | 整数の格上げが行われ,演算結果として項の値の1の補数を返す. | |||
論理否定演算子 | !expr | ○ | ||||
単項マイナス | -expr | ○ | ||||
単項プラス | +expr | ○ | 普通は使用しない.演算結果は項の値. | |||
アドレス演算 | &expr | ○ | ||||
関節参照 | *expr | ○ | ||||
new演算子(領域確保) | new type | ○ | ||||
new演算子(領域確保と初期設定) | new type ( expr_list ) | ○ | ||||
new演算子(配置) | new ( expr_list ) type | ○ | ||||
new演算子(配置と初期設定) | new ( expr_list ) type ( expr_list ) | ○ | ||||
解体(領域開放) | delete pointer | ○ | ||||
配列の解体 | delete [] pointer | ○ | ||||
キャスト(型変換) | ( type ) expr | |||||
3 | → | メンバ選択 | object.*pointer_to_member | |||
object->*pointer_to_member | ○ | |||||
4 | → | 乗算演算子 | 乗算 | expr * expr | ○ | |
除算 | expr / expr | ○ | 除数が0の場合かいずれかの項が負の場合,演算結果は実装に依存する. | |||
剰余 | expr % expr | ○ | ||||
5 | → | 加算演算子 | 加算 | expr + expr | ○ | |
減算 | expr - expr | ○ | ||||
6 | → | シフト演算子 | 左シフト | expr << expr | ○ | 項は整数型でなくてはならず,整数の格上げが行われる.そして右の項が負数か左の項のビット長以上である場合,結果は言語処理系に依存する.左シフトの時,空いたビットには0が詰められる.右シフトの時,左の項が非負か符号なし整数型である場合,空いたビットには0が詰められ,それの場合,結果は言語処理系に依存する. |
右シフト | expr >> expr | ○ | ||||
7 | → | 関係演算子 | 未満 | expr < expr | ○ | |
以下 | expr <= expr | ○ | ||||
より大きい | expr > expr | ○ | ||||
以上 | expr >= expr | ○ | ||||
8 | → | 等値演算子 | 等値 | expr == expr | ○ | 2つの項が等しい時trueを,それ以外の時にfalseを返す. |
不等値 | expr != expr | ○ | 2つの項が等しくない時にtrueを,それ以外の時にfalseを返す. | |||
9 | → | ビット単位の論理積演算子 | expr & expr | ○ | ||
10 | → | ビット単位の排他的論理和演算子 | expr ^ expr | ○ | ||
11 | → | ビット単位の内包的論理和演算子 | expr | expr | ○ | ||
12 | → | 論理積演算子 | expr && expr | ○ | いずれかの項がfalseの時にfalseを,それ以外の時にtrueを返す. | |
13 | → | 論理和演算子 | expr || expr | ○ | いずれかの項がtrueの時にtrueを,それ以外の時にfalseを返す. | |
14 | ← | 条件式(3項演算子) | expr ? expr : expr | 例えば条件式が"(expr1)? expr2:expr3"の時,expr1が先に評価され,その結果がtrueの時expr2を,falseの時expr3を評価し,その値を返す. | ||
15 | ← | 代入 | lvalue = expr | ○ | ||
複合代入演算子 | 乗算して代入 | lvalue *= expr | ○ | |||
除算して代入 | lvalue /= expr | ○ | ||||
剰余を代入 | lvalue %= expr | ○ | ||||
加算して代入 | lvalue += expr | ○ | ||||
減算して代入 | lvalue -= expr | ○ | ||||
左シフトして代入 | lvalue <<= expr | ○ | ||||
右シフトして代入 | lvalue >>= expr | ○ | ||||
論理積を代入 | lvalue &= expr | ○ | ||||
論理和を代入 | lvalue |= expr | ○ | ||||
排他的論理和を代入 | lvalue ^= expr | ○ | ||||
16 | ※ | 例外スロー | throw expr | void型の演算式(演算結果はない).副作用として例外を発生させる.項は省略可能であり,void型以外を指定できる.すなわちvoid型の式を項に指定することはできない.それゆえ複数のthrow演算子は結合できない. | ||
17 | → | 連続処理演算子 | expr, expr | ○ |
- exprは式.
- expr_listは0個以上の式の列で,各式はカンマで区切られる.
- class_nameはクラス名.
- namespace_nameは名前空間名.
- objectはクラス型(構造体や共用体を含む)のオブジェクト.
- memberはメンバ名.
- pointerはポインタを返す式.
- lvalueは非定数オブジェクト.
- typeは括弧で囲われてるときに限り一般的な意味での型名,それ以外の場合は制限がある.
演算の計算順序
演算の計算順序は,演算子の優先順位と結合規則に従い計算される. まず演算子の優先順位を比較し,同じ優先順位の場合に結合規則によって再度比較する. 例えば,演算"v = f(i) + g(i) - j;"は,代入と加算,減算,関数呼び出しを行なっている. この時,優先順位に従うと,=演算子が最も優先順位が低い. +演算子と-演算子は優先順位は同じであるが,結合規則が「左から右へ」であるため,左にある+演算子が先に計算される.
各項もまた式であるが,C++では,各項の評価順序は定められていない.そのため前述の例では,関数fとgがどちらが先に呼び出されるかは言語処理系に依存する.これは変数iがfやgがiの値を書き換えるような関数の場合,プログラマが演算結果を予測できない事態を招く.同様の理由により関数の引数もまた評価順序は定められていない.
数学と同様,()で囲われた式は演算子の持つ優先順位に関係なく,最も優先順位が高い.そのため"v = (1 + 3) / 4;"のような式があった時,除算演算よりも先に加算演算を行う.
組み込みデータ型の利用可能な演算子
演算の際のlvalueやexprは,標準では決められた型しか利用できない.また演算の際,いくつかの型は暗黙的な型変換が行われる.以下に各組み込みデータ型で利用可能な演算子を箇条書きで示す.
- lvalueは否定数オブジェクトである.
- 関数呼び出しのexprは,関数のアドレスを持つものに限り,expr_listの各型はその関数に依存する.
- 代入と複合代入演算の右辺の項は,左辺の項と同じ型か暗黙的に型変換可能なものに限る.
- delete演算子のpointerはオブジェクトを指すものに限る.
- 整数系データ型は,全ての演算子のexprとlvalueとして利用できる.
- 浮動小数点数型は,添字とビット単位の論理演算と補数演算,剰余,シフト演算,複合代入演算(剰余,補数演算,ビット単位の論理演算)以外の演算子のlvalueとexprとして利用できる.
- void型はすべての演算子で利用できない.
- ポインタ型は,整数系データ型との加算演算と複合代入演算(加算),例外スロー,連続処理演算のexprとして,インクリメントとデクリメントのlvalueとして利用できる.
- 配列や関数型は,整数系データ型との加算演算や例外スロー,連続処理演算のexprとして利用できる.
- 参照型はある型の別名であるため,その型が利用可能な演算子で利用できる.
演算子の多重定義(オーバーロード)
C++では,ユーザ定義の型の項のためにいくつかの演算子の意味を定義することができる. 詳細は,オブジェクト指向プログラミングの「演算子の多重定義」で述べる.
型変換(キャスト)
型変換には,言語処理系が自動的に行う暗黙的な型変換とユーザが演算子や関数を用いて行う明示的な型変換がある.言語処理系は,初期設定子や関数引数,式の項,繰り返し文や選択文を制御する式らが要求する型と一致しない型指定した際に,それを暗黙的に正しい型へと型変換しようとする(正しい型に変換できなければ言語処理系はエラーとする).例えば初期設定子は,初期値として,初期化しようとしている型の値を要求するが,初期値としてその型以外の値を指定した場合,言語処理系はそれを正しい型へと型変換する.
暗黙的な型変換
暗黙的な型変換では,値はなるべく情報が失われないように変換される.しかしながら値を無効にするような変換(安全でない変換)も暗黙的に実行されてしまう.変換には次の4種類がある.
- 単純変換(trival conversions)
型とはコンセプトを表現したものである.一部の型はその過程で,異なるコンセプトの値を持つ.例えば,ある配列型の名前はその配列の最初要素の先頭アドレスを指すし,Tの参照型はTと同じ値を保持する.このような時,値をその型に変換できる.この変換は,言語処理系が多重定義の曖昧性を解消する際に 一部を除いて変換しない場合と同じく最も高い優先度で扱われる.以下にそれらの例を示す(Tは型,"(*)"は後置きの任意の型を表す ).
- T → T&
- T& → T
- T[] → T*
- T(args) → T(*)(args)
- T → const T
- T → volatile T
- T → const T&
- T → volatile T&
- T* → volatile T*
- T* → const T*
- 格上げ
各型は通常異なるコンセプトとサイズを持つが,整数系データ型と浮動小数点型は同じコンセプトで異なるサイズを持つ.格上げは,ある型を同じコンセプトの型のより大きいサイズの型への変換である.例えば,整数系データ型にはboolとchar,short int, int, long int,浮動小数点数型にはfloatとdoubleがある.この時,整数系データ型の格上げを整数格上げ,浮動小数点型の格上げを浮動小数格上げと呼ぶ.なお,変換前と変換後の符号の有無が異なる場合は,格上げではない.格上げは,ある型から別の型に変換する際に,常に値を変更せずに型を変換できる(格上げを変換とは区別することもある).
- 標準変換
単純変換や格上げ以外のC++で定められた型変換のこと.標準変換には,変換後に情報が失われて値が変化する安全でない変換と,変換後でも値が変わらない安全な変換がある.標準変換の例には,後述する浮動小数点型から整数型への変換やその逆や,ある型のポインタからvoidポインタへの変換,派生クラスから基底クラスへの変換がある.多重定義で優先度が問われ,クラスBがクラスAの派生クラスである場合,B*からA*への変換は,B*からvoid*への変換よりも優先される.また,派生クラスから基底クラスへの変換は,階層の近いもの(詳細は「オブジェクト指向プログラミング」を参照)の方が優先度が高い.ある組み込みデータ型から別の組み込みデータ型への標準変換についての詳細は後述する.
- ユーザ定義変換
プログラマが定義したコンストラクタや変換関数は,明示的に利用することも可能だが,言語処理系によって暗黙的に利用される.多重定義で優先度が問われる場合,ユーザ定義変換は最も優先度が低い.
- コンストラクタを用いた型変換
ある型Tを引数とするコンストラクタを定義した時,それはTからそのクラスへ型変換として暗黙的に利用される.コンストラクタを定義するための文法は, 「コンストラクタ」を参照.以下に例を示す.
コンストラクタを用いた明示的な型変換は,関数宣言と文法が似ているために曖昧である.このような曖昧な箇所は,すべて関数宣言として扱われる.class Hoge { public: Hoge(){} Hoge(int i){} ~Hoge(){} int i; }; int main() { int i; Hoge h = i; // Hoge::Hoge(int i)が呼び出され,暗黙的に変換される. return 0; }
- 変換関数による型変換
変換関数とは,次のような文法で定義された関数のことである.
- conversion-function-name
- operator conversion-type-name
- conversion-type-name
- type-specifier-list ptr-operatoropt
- type-specifier-list(型や型指定の列)
- ユーザ定義型やtypedefされた名前は指定できない.
- ptr-operator
- あるクラスから基本型への変換を定義できる.
- 新しいクラスAから以前に定義されたクラスBへの変換をBのソースコードを修正せずに定義できる.
class Hoge { public: Hoge(){} ~Hoge(){} operator int(); }; //int Hoge::operator int(){}は誤り Hoge::operator int() // Hogeからintへの型変換 { std::cout << "hoge" << std::endl; return 100; // 返り値の型はint } int main() { Hoge h; int i = h; // 上で定義した変換関数が呼び出される. std::cout << i << std::endl; return 0; }
暗黙的なユーザ定義変換は,曖昧でない場合にだけ使用される.
組み込みデータ型間の型変換
- 整数系データ型変換
列挙値は整数型に変換できる.整数は別の整数型に変換できる.変換後の型がunsignedである場合,変換前の型のビット数は変換後の型に収まるものとなる(必要に応じて,上位ビットが破棄される).変換後の型が符号付きで,値を変換後の型で表せる場合,値は変化しない.変換後の値で表せない値は言語処理系に依存する.また,boolとcharも整数系データ型であることに注意.
- 浮動小数点数変換
浮動小数点型は別の浮動小数点型に変換できる.変換前の値を変換後の型で完全に表せる場合,値は不変である.変換前の値が変換後の型で表現可能な隣接する2つの値の間にある場合,結果はそのいずれかになる.そうでない場合,結果は言語処理系に依存する.
- ポインタ変換と参照変換
-
オブジェクト型へのポインタはvoid*に暗黙的に変換できる.派生クラスへのポインタ(参照)はアクセス可能な曖昧でない基底クラスに暗黙的に変換できる.関数ポインタやメンバポインタを暗黙のうちにvoid*に変換することはできないので注意.0に評価される定数式は,任意のポインタかメンバへのポインタに暗黙的に変換できる.任意の型TのポインタT*はconst T*に暗黙的に変換できる.
T&はconst T&に暗黙的に変換できる.
- メンバポインタの変換
派生クラスのメンバポインタに基底クラスのメンバポインタに安全に変換できる.ただしその逆は成り立たない(この特徴を逆変と呼ばれる).メンバを指すポインタや参照型は,このルールを守る形で暗黙のうちに変換できる.
- Boolean変換
ポインタおよび算術演算型は,bool型に変換できる.この時,値が0でない場合はtrueに,0の場合はfalseに変換される.また,当然だがbool型は整数系データ型であるため,整数系データ型の型変換が適用される.この場合,trueは1として,falseは0として扱われる.
- 浮動小数点数型と整数系データ型の間での変換
-
浮動小数点数型から整数系データ型に変換する際,小数部は切り捨てられる.切り捨てた後の値を変換後の型で表せない場合の振る舞いは未定義である.
整数系データ型から浮動小数点数型に変換する場合,その値がどれくらい正確かはハードウェアに依存する.整数値を浮動小数点数型の値として完全に表せない場合は,精度が失われる.
- 算術変換
-
2項演算子の2つの項を共通の型に,そしてそれを結果の型として使用するために変換が行われる.変換は条件に一致するものの中から優先順位の高いものが行われる(上の方が優先順位が高い).
- 浮動小数点数型
- どちらかの項がlong double型で,もう片方が次のいずれかの型の場合,それはlong doubleに変換される.
- どちらかの項がdouble型なら,もう片方の項もdouble型に変換される.
- 両方の項に対して整数の格上げが実行される.
- 整数系データ型
- そのあとで,項のどちらかがunsigned long int型ならもう片方の項もunsigned long int型に変換される.
- 非演算子のどちらかの方がsigned long int型で,もう片方の項がunsigned int型の場合,signed long int型でそれらの項を表現可能な時にそれに,それ以外の時にunsigned long int型に変換される.
- どちらかの項の型がunsigned int型ならば,もう片方もunsigned int型に変換される.
- どちらの項もsigned int型である.
- クラス間の型変換
クラス間の型変換には,ユーザ定義変換を除くと後述の3種類に分類できる.ここでいうクラスとは構造体を含む.本ページでは,基本的に基底クラスと単に言った場合,直接的なものと間接的なもの両方を指す.
- アップキャスト
派生クラスから基底クラスへの型変換.アップキャストは暗黙的に行われる.ただし,ある派生クラスから非公開派生された基底クラスへの型変換はできない.多くの場合,アップキャストは安全である.
- ダウンキャスト
基底クラスから派生クラスへの型変換.多くの場合,ダウンキャストは安全でない.
- クロスキャスト
ある派生クラスが複数の基底クラスを継承している場合の基底クラス間の型変換のこと.多くの場合,クロスキャストは安全でない.
明示的な型変換
プログラマが演算子や関数を用いて明示的に記述した型変換のこと.Cでは必要な明示的な型変換であっても,C++の場合ほとんど不要である.
- コンパイル時チェック付き型変換
- static_cast<type > ( expr )関連性のある型間の型変換.例えば,同じクラス階層に属するクラス間,列挙型から整数系データ型,浮動小数点型と整数系データ型間などがある.static_castはコンパイル時に最小限の型チェックが行う.型変換できない場合,コンパイラはエラーを返す(例えば整数型からポインタといった関連性のない型変換).実行時チェック付き変換の始めに示す例のような場合,型変換可能であるが安全かどうかはコンパイル時には検知できず,エラーは返されない.一部のstatic_castには移植性がある.
void *p = static_cast<void*>(0xABCDEF); // エラー std::cout << static_cast<int>(3.14) << std::endl;
- チェックなし型変換
- reinterpret_cast < type > ( expr )関連性のない型間の型変換.開発者いわく「もっとも乱暴で始末に負えない結果をもたらす可能性がある」.変換後の型を指定する引数は,ほとんどの型が利用可能であり,引数とまったく同じビットパターンを持つ値を単純に生成する.例えば,整数とポインタ間の型変換で使用する.移植性のあるreinterpret_castは極めて少ない.また,この型変換後の結果が安全に利用できるのは,型変換前と同じ型である時のみである.
void *p = reinterpret_cast<void*>(0xABCDEF);
- constを取り除く型変換
- const_cast < type > ( expr )constを取り除く型変換.型変換の結果を安全に利用できるのは,型変換前のオブジェクトがもともとconst宣言されていない場合だけである.
- Cスタイルの型変換
- ( type ) exprCスタイルの型変換は,static_cast,reinterpret_cast,const_castを組み合わせれば表現できる.Cスタイルの型変換を使用した場合,プログラマは型変換の意図を読み取り難い.
- 実行時チェック付き型変換
- dynamic_cast<type > ( expr )実行時に型変換の安全性を確認できる型変換.dynamic_castで指定可能な型は,ポインタか参照型のみである.dynamic_castは,プログラム実行中にチェックのためのオーバーヘッドが発生する.
- ポインタへの型変換
ある型のポインタ型T*への型変換が可能で,実行時チェック付き変換を行った時,安全な場合T*型を返し,そうでない時0(NULL)を返す.以下にダウンキャストの例を示す.
実行時チェック付き変換は,void*からの変換には対応していない.class Base { public: Base(){} ~Base(){} virtual void func(){} }; class Sub : public Base { public: Sub(){} ~Sub(){} void func(){} }; void conv(Base *b) { Sub *s = dynamic_cast<Sub*>(b); std::cout << (s? "OK" : "NG") << std::endl; return; } int main() { Base *b1 = new Base; Base *b2 = new Sub; conv(b1); // NG conv(b2); // OK delete b1; delete b2; return 0; }
- 参照型への型変換
ある型の参照型T&への変換が可能で,実行時チェック付き変換を行った時,安全な場合,T&型を返し,そうでない時(typeinfo.hで宣言された)std::bad_cast例外を起こす(try-block文については「例外」を参照).
try { Base b3; Sub& s3 = dynamic_cast<Sub>(b3); std::cout << "OK" << std::endl; }catch( std::bad_cast ){ std::cout << "NG" << std::endl; } try { Sub s4; Sub& s5 = dynamic_cast<Sub>(s4); std::cout << "OK" << std::endl; }catch( std::bad_cast ){ std::cout << "NG" << std::endl; }
- 変換関数による型変換
- コンストラクタによる型変換
ユーザ定義変換を参照.
typedef
プログラマはtypedef機構を使ってある型の別名を定義できる.typedefによって付けられた名前の型は,新しく定義された訳ではないため,多重定義の際に元の名前の型と同じものとして扱われる.まず,typedefを用いた定義の構文を示し,次にその例を示す.
typedef int benefit; // int型 => benefit
typedef void (*function)(); // 返り値と引数なしの関数のポインタ型 => terminate_handler
- 事前に実行する環境チェッカ
#include <stdio.h> int main(int argc, char *argv[]) { printf("-DUINT_SIZE=%u -DULLINT_SIZE=%u", sizeof(unsigned int), sizeof(unsigned long long int)); return 0; }
- ヘッダ
#if UINT_SIZE == 4 typedef unsigned int word_t; #elif ULLINT_SIZE == 4 typedef unsigned long long int word_t; #endif
- コンパイル
user% gcc -c `./checker` source.c
なお,exception-specification文は関数の型の一部ではないため,typedef文に含まれていてはならない.