標準変換
C++ 言語では、基本型間での変換が定義されています。 また、ポインター、参照、およびメンバーへのポインターの派生型についても変換が定義されています。 これらの変換を "標準変換" といいます。
このセクションでは、次の標準変換について説明します。
整数の上位変換
整数の変換
浮動小数点の変換
浮動小数点と整数の変換
算術変換
ポインター変換
参照変換
メンバーへのポインター変換
次のコードは、変換 (この例では整数の上位変換) の実行例を示しています。
long long_num1, long_num2;
int int_num;
// int_num promoted to type long prior to assignment.
long_num1 = int_num;
// int_num promoted to type long prior to multiplication.
long_num2 = int_num * long_num2;
参照型を生成する場合のみ、変換の結果が左辺値になります。 たとえば、operator int&()
として宣言されたユーザー定義変換は参照を返し、それは左辺値になります。 しかし、operator int()
として宣言された変換はオブジェクトを返し、それは左辺値にはなりません。
整数の上位変換
整数型のオブジェクトは別のより大きな整数型 (つまり、より大きな値のセットを表現できる型) に変換できます。 この拡大型の変換は、"整数の上位変換" と呼ばれます。 整数の上位変換を使用すると、式で他の整数型を使用できる任意の場所で、次の型を使用できます。
char
とshort int
型のオブジェクト、リテラル、定数列挙型
int
ビット フィールド列挙子
上位変換後の値は上位変換前の値と同じであることが保証されるため、C++ の上位変換では "値保持" されます。 値保持の上位変換では、より短い整数型のオブジェクト (ビット フィールドや char
型のオブジェクトなど) は、int
が元の型の全範囲を表すことができる場合、int
型に上位変換されます。 int
で値のすべての範囲を表すことができない場合、オブジェクトは unsigned int
型に昇格されます。 この方法は標準 C によって使用される方法と同じですが、値保持の変換ではオブジェクトの "符号の有無" は保持されません。
値保持の上位変換および符号の有無を保持する上位変換は、通常、同じ結果を生成します。 ただし、上位変換されたオブジェクトが次のように現れる場合は、異なる結果になる可能性があります。
/
、%
、/=
、%=
、<
、<=
、>
、または>=
のオペランドこれらの演算子は、結果を判断するために符号に依存します。 これらのオペランドに適用した場合、値保持と符号保持の上位変換により、異なる結果が生成されます。
>>
または>>=
の左オペランドこれらの演算子は、シフト演算で符号付き数量と符号なし数量を別々に扱います。 符号付き数量の場合、右シフト演算では符号ビットが空いたビット位置に反映されます。一方、符号なし数量では、空いたビット位置は 0 で埋められます。
引数照合のためにそのオペランドの型の符号の有無に依存する、オーバーロードされた関数への引数、またはオーバーロードされた演算子のオペランド。 オーバーロードされた演算子の詳細については、オーバーロードされた演算子に関する記事を参照してください。
整数の変換
"整数の変換" は、整数型間での変換です。 整数型には、char
、short
(または short int
)、int
、long
、long long
が含まれます。 これらの型は signed
または unsigned
で修飾できます。また、unsigned
はそれだけで unsigned int
の短縮形として使用できます。
signed から unsigned へ
符号付き整数型のオブジェクトは、対応する符号なし型に変換できます。 そのような変換が発生しても、実際のビット パターンは変更されません。 ただし、データの解釈は変わります。 次のコードを考えてみます。
#include <iostream>
using namespace std;
int main()
{
short i = -3;
unsigned short u;
cout << (u = i) << "\n";
}
// Output: 65533
前の例では、signed short
、i
が定義され、負の数値に初期化されます。 式 (u = i)
では、i
が u
に代入される前に、unsigned short
に変換されます。
unsigned から signed へ
符号なし整数型のオブジェクトは、対応する符号付き型に変換できます。 ただし、次の例に示すように、符号なし値が符号付き型の表現可能な範囲外にある場合は、正しい値が生成されません。
#include <iostream>
using namespace std;
int main()
{
short i;
unsigned short u = 65533;
cout << (i = u) << "\n";
}
//Output: -3
前の例で、u
は、式 (i = u)
を評価するために符号付き数量に変換する必要がある unsigned short
の整数オブジェクトです。 その値は、signed short
で正しく表現できないため、データはここで示したように、誤って解釈されます。
浮動小数点の変換
浮動小数点型のオブジェクトは、より正確な浮動小数点型に安全に変換できます。つまり、変換によって有意性が失われる可能性はありません。 たとえば、float
から double
への変換や double
から long double
への変換は安全であり、値は変更されません。
浮動小数点型のオブジェクトは、より精度の低い型で表現できる範囲にある場合はその型に変換できます。 ( 浮動型の 範囲については、「フローティング制限」を参照してください)。元の値が正確に表現できない場合は、次の高い値または次の低い表現可能な値に変換できます。 このような値がない場合、結果は未定義です。 次の例を確認してください。
cout << (float)1E300 << endl;
型 float
で表される最大値は 3.402823466E38 で、1E300 よりもはるかに小さい数値です。 したがって、数は無限大に変換され、結果は "inf" です。
整数型と浮動小数点型の変換
特定の式は、浮動小数点型のオブジェクトから整数型のオブジェクトへの変換、またはその逆の変換が発生する場合があります。 整数型のオブジェクトが浮動小数点型に変換され、元の値を正確に表すことができない場合、結果はそれより大きいか小さくて最も近い表現可能な値になります。
浮動小数点型のオブジェクトが整数型に変換されるときは、小数部分が "切り捨てらる"、または 0 方向に丸められます。 1.3 などの数値は 1 に変換され、- 1.3 などの数値は -1 に変換されます。 切り捨てられた値が表現可能な最大の値より大きい場合、または表現可能な最小の値より小さい場合、結果は未定義になります。
算術変換
多くの 2 項演算子 (「2 項演算子を含む式」を参照) では、オペランドの変換が発生し、結果も同様に生成されます。 これらの演算子により発生する変換は、"通常の算術変換" と呼ばれます。 異なるネイティブ型のオペランドの算術変換は、次の表に示すように行われます。 typedef 型は、基になるネイティブ型に従って動作します。
型変換の条件
満たされる条件 | 変換 |
---|---|
どちらかのオペランドが long double 型。 |
もう一方のオペランドが long double 型に変換されます。 |
上の条件が満たされず、どちらかのオペランドが double 型。 |
もう一方のオペランドが double 型に変換されます。 |
上の条件が満たされず、どちらかのオペランドが float 型。 |
もう一方のオペランドが float 型に変換されます。 |
上の条件が満たされていない (どちらのオペランドも浮動小数点型ではない) | オペランドに対して、次のような整数の上位変換が行われます。 - どちらかのオペランドが unsigned long 型の場合、もう一方のオペランドは unsigned long 型に変換されます。- 上の条件が満たされず、どちらかのオペランドが long 型で、もう一方が unsigned int 型である場合、両方のオペランドが unsigned long 型に変換されます。- 上の 2 つの条件が満たされず、どちらかのオペランドが long 型である場合、もう一方のオペランドは long 型に変換されます。- 上の 3 つの条件が満たされず、どちらかのオペランドが unsigned int 型である場合、もう一方のオペランドは unsigned int 型に変換されます。- 上の条件がどれも満たされない場合、両方のオペランドは int 型に変換されます。 |
次のコードは、表で説明している変換規則を示しています。
double dVal;
float fVal;
int iVal;
unsigned long ulVal;
int main() {
// iVal converted to unsigned long
// result of multiplication converted to double
dVal = iVal * ulVal;
// ulVal converted to float
// result of addition converted to double
dVal = ulVal + fVal;
}
上記の例の最初のステートメントは、2 つの整数型、iVal
と ulVal
の乗算を示しています。 満たされる条件は、どちらのオペランドも浮動小数点型ではなく、一方のオペランドが unsigned int
型であることです。 したがって、もう一方のオペランドである iVal
は unsigned int
型に変換されます。 その後、結果は dVal
に代入されます。 ここで満たされる条件は、一方のオペランドが double
型であることです。したがって、乗算の unsigned int
の結果は double
型に変換されます。
上記の例の 2 番目のステートメントは、float
と整数型、つまり fVal
と ulVal
の加算を示しています。 ulVal
変数は float
型に変換されます (表の 3 番目の条件)。 加算の結果は、double
型に変換され (表の 2 番目の条件)、dVal
に代入されます。
ポインター変換
ポインターは、代入、初期化、比較、および他の式の中で変換できます。
クラスへのポインター
クラスへのポインターを基底クラスへのポインターに変換できる 2 つのケースがあります。
最初のケースは、指定した基底クラスがアクセス可能であり、変換が明確である場合です。 あいまいな基底クラスの参照の詳細については、「複数の基底クラス」を参照してください。
基底クラスにアクセスできるかどうかは、派生で使用される継承の種類によって決まります。 次の図に示す継承について考えてみましょう。
この図は、基底クラス A を示しています。クラス B は、プライベート保護されたパブリックを介して A から継承します。 クラス C は、パブリック B を介して B から継承します。
基底クラスのアクセシビリティを示す継承グラフ
次の表は、図で示す状況に対する基底クラスのアクセシビリティを示します。
関数の型 | 派生 | 変換 (B* 法的に A* ? |
---|---|---|
外部 (非クラス スコープ) 関数 | プライベート | いいえ |
Protected | いいえ | |
パブリック | はい | |
B のメンバー関数 (B のスコープ内) | プライベート | はい |
Protected | はい | |
パブリック | はい | |
C のメンバー関数 (C のスコープ内) | プライベート | いいえ |
Protected | はい | |
パブリック | はい |
クラスへのポインターを基底クラスへのポインターに変換できる 2 番目のケースは、明示的な型変換を使用する場合です 明示的な型変換の詳細については、明示的な型変換演算子に関する記事を参照してください。
このような変換の結果は "サブオブジェクト" へのポインターです。サブオブジェクトは、基底クラスによって完全に記述されたオブジェクトの一部です。
次のコードでは、2 つのクラス A
と B
を定義しています。B
は A
から派生しています。 (継承の詳細については、「派生クラス」を参照してください)。次に、 型の B
オブジェクトと、 オブジェクトを指す 2 つのポインター (pA
と pB
) を定義bObject
します。
// C2039 expected
class A
{
public:
int AComponent;
int AMemberFunc();
};
class B : public A
{
public:
int BComponent;
int BMemberFunc();
};
int main()
{
B bObject;
A *pA = &bObject;
B *pB = &bObject;
pA->AMemberFunc(); // OK in class A
pB->AMemberFunc(); // OK: inherited from class A
pA->BMemberFunc(); // Error: not in class A
}
ポインター pA
は、"A
型のオブジェクトへのポインター" を意味するものとして解釈できる型 A *
です。bObject
のメンバー (BComponent
、BMemberFunc
など) は、B
型において一意であり、pA
からアクセスできません。 pA
ポインターは、クラス A
で定義されているオブジェクトのこれらの特性 (メンバー関数とデータ) にのみアクセスを許可します。
関数へのポインター
関数へのポインターは、void *
型がそのポインターを保持するために十分に大きい場合は、void *
型に変換できます。
void へのポインター
void
型へのポインターは他の任意の型へのポインターに変換できますが、(C の場合とは異なり) 明示的な型キャストを使用する場合に限られます。 任意の型へのポインターは、void
型のポインターに暗黙的に変換できます。 型の不完全オブジェクトへのポインターは、void
へのポインターに変換でき (暗黙)、逆向きにも変換できます (明示的)。 このような変換の結果は、元のポインターの値と同じです。 オブジェクトを宣言しても、そのサイズまたは基底クラスを決定するための十分な情報がない場合は、そのオブジェクトは不完全であると見なされます。
ではない const
オブジェクトへのポインター。または volatile
型の void *
ポインターに暗黙的に変換できます。
const ポインターと volatile ポインター
C++ では、const
または volatile
型から const
または volatile
ではない型への標準変換を提供していません。 ただし、どの種類の変換も、明示的な型キャストを使用して指定できます (安全でない変換も含む)。
Note
メンバーへの C++ のポインター (静的メンバーへのポインターを除く) は、通常のポインターとは異なり、同じ標準変換がありません。 静的メンバーへのポインターは通常のポインターであり、通常のポインターと同じ変換を持ちます。
null ポインターの変換
評価するとゼロになる整数定数式、またはそのような式をポインター型にキャストした結果は、"Null ポインター" というポインターに変換されます。 このポインターは、すべての有効なオブジェクトや関数へのポインターと比較すると、常に等しくないと判定されます。 例外は、同じオフセットを持っていても別のオブジェクトを指すことができる、ベース付きのオブジェクトへのポインターです。
C++11 では、C スタイルの null ポインターよりも nullptr 型を優先する必要があります。
ポインター式の変換
配列型の式は、同じ型のポインターに変換できます。 変換の結果は最初の配列要素へのポインターです。 次のコードは、この変換を示す例です。
char szPath[_MAX_PATH]; // Array of type char.
char *pszPath = szPath; // Equals &szPath[0].
特定の型を返す関数となる式は、次の場合を除き、その型を返す関数へのポインターに変換されます。
式がアドレス演算子 (&) のオペランドとして使用されています。
式が関数呼び出し演算子のオペランドとして使用されています。
参照変換
次のケースでは、クラスへの参照を基底クラスへの参照に変換できます。
指定した基底クラスにアクセスできます。
変換は明確です。 (あいまいな基底クラスの参照の詳細については、「複数の基底クラス」を参照してください。)
変換の結果は、基底クラスを表すサブオブジェクトへのポインターです。
メンバーへのポインター
クラス メンバーへのポインターは、代入、初期化、比較、および他の式の中で変換できます。 このセクションでは、以下のポインターからメンバーへの変換について説明します。
基底クラスのメンバーへのポインター
基底クラスのメンバーへのポインターは、次の条件を満たす場合に、そのクラスから派生したクラスのメンバーへのポインターに変換できます。
派生クラスへのポインターから基底クラスへのポインターへの逆変換がアクセス可能である。
派生クラスは、基底クラスから事実上継承されません。
左のオペランドがメンバーへのポインターである場合、右のオペランドはメンバーへのポインター型であるか、0 に評価される定数式である必要があります。 この代入は、次の場合にのみ有効です。
右のオペランドが、左のオペランドと同じクラスのメンバーへのポインターである。
左のオペランドが、右のオペランドのクラスからパブリックかつ明確に派生したクラスのメンバーへのポインターである。
メンバーへの Null ポインターの変換
評価するとゼロになる整数定数式は、Null ポインターに変換されます。 このポインターは、すべての有効なオブジェクトや関数へのポインターと比較すると、常に等しくないと判定されます。 例外は、同じオフセットを持っていても別のオブジェクトを指すことができる、ベース付きのオブジェクトへのポインターです。
次のコードは、クラス i
のメンバー A
へのポインターの定義を示しています。 ポインター pai
が 0、つまり null ポインターに初期化されます。
class A
{
public:
int i;
};
int A::*pai = 0;
int main()
{
}