基本組み込み型#
異なるコンパイラやプラットフォームによって差異がある場合があります。以下の表は Windows_X64 環境で MinGw GCC 9.2.0 32_bit を使用した例です:
型 | 意味 | 最小サイズ | 範囲 |
---|---|---|---|
bool | ブール型 | 8bits | true または false |
char | 文字型 | 8bits | -2^7 ~ 2^7-1 |
wchar_t | ワイド文字 | 16bits | 0 ~ 2^16 - 1 |
char16_t | Unicode 文字 | 16bits | 0 ~ 2^16 - 1 |
char32_t | Unicode 文字 | 32bits | 0 ~ 2^32 - 1 |
short | 短整数型 | 16bits | -2^15 ~ 2^15-1 |
int | 整数型 | 32bits | -2^31 ~ 2^31-1 |
long | 長整数型 | 32bits | -2^31 ~ 2^31-1 |
long long | 長整数型 | 64bits | -2^63 ~ 2^63-1 |
float | 単精度浮動小数型 | 32bits | 約 7 桁の有効数字 |
double | 倍精度浮動小数型 | 64bits | 約 15 桁の有効数字 |
long double | 拡張精度浮動小数型 | 96bits | より高い有効数字 |
以下のコードを IDE でコンパイルして実行し、ローカルの算術型のサイズを確認できます。
// Windows_X64環境でMinGw GCC 9.2.0 32_bitを使用した例
// 1Byte = 8bit
#include<iostream>
using namespace std;
int main(){
cout<< "int * "<<sizeof(int*)*8<<endl; // 32 bits
cout<< "bool "<<sizeof(bool)*8<<endl; // 8 bits
cout<< "char "<<sizeof(char)*8<<endl; // 8bits
cout<< "wchar_t "<<sizeof(wchar_t)*8<<endl; // 16bits
cout<< "char16_t "<<sizeof(char16_t )*8<<endl; // 16bits
cout<< "char32_t "<<sizeof(char32_t )*8<<endl; // 32bits
cout<< "short "<<sizeof(short )*8<<endl; // 16bits
cout<< "int "<<sizeof(int )*8<<endl; // 32bits
cout<< "long "<<sizeof(long )*8<<endl; // 32bits
cout<< "long long"<<sizeof(long long)*8<<endl; // 64bits
cout<< "float "<<sizeof(float)*8<<endl; // 32bits
cout<< "double "<<sizeof(double)*8<<endl; // 64bits
cout<< "long double "<<sizeof(long double)*8<<endl; //96bits
return 0;
}
組み込み型の機械実装#
バイト (Byte) はアドレス可能な最小メモリブロックで、1 byte = 8 bit (ビット)
ワード (Word) はストレージの基本単位で、1 Word = 4 バイトまたは 8 バイト
メモリ内の各バイトは 1 つの数字(アドレス address と呼ばれる)に関連付けられています。特定のアドレスを使用して、そのアドレスから始まる異なるサイズのビット列を表すことができます。たとえば、アドレス 736424 のワードまたはアドレス 736424 のバイト。しかし、特定のアドレスに格納されているデータ型を知っている必要があります。そうしないと、そのアドレスにメモリに明確な意味を与えることはできません。型はデータが占めるビット数と、これらのビットの内容をどのように解釈するかを決定します。
符号付き型と符号なし型#
他の整数型
-
ブール型と拡張文字型を除くと、他の整数型は符号付きと符号なしに分類できます。
-
int、short、long、long long は符号付きです。
-
型名の前に
unsigned
を追加すると符号なし型になります。 -
例:
unsigned long
-
unsigned int は unsigned と省略できます。
文字型
-
3 種類に分かれます: char、signed char、unsigned char
-
char が符号付きか符号なしかはコンパイラによって決まります。
型の選択方法#
- 数値が負でない場合(例:年齢、長さなど)は、
符号なし型
を選択します。 - 整数計算式には
int
型を使用します。一般にlong
のサイズはint
と同じですが、short
は小さすぎます。大きな整数を処理する場合はlong long
を選択するべきです。 - 算術式では
bool
やchar
を使用しないでください。符号に問題が生じやすいです。 - 浮動小数点データの演算には
double
を使用します。
型変換#
型が表す値の範囲が変換プロセスを決定します:
- 非ブール型をブール型に代入すると、初期値が 0 の場合は結果は false、そうでなければ true になります。
#include<iostream>
using namespace std;
int main() {
int i = 42;
if (i) {
i = 0;
}
cout << "i = " << i << endl; // i=0
return 0;
}
- ブール型を非ブール型に代入すると、初期値が false の場合は結果は 0、初期値が true の場合は結果は 1 になります。
- 浮動小数点数を整数型に代入すると、近似処理が行われ、小数点前の部分が保持されます。
- 整数型を浮動小数点型に代入すると、小数部分は 0 として記録され、整数型が浮動小数点型の容量を超えると精度が損なわれる可能性があります。
- 範囲を超える値を符号なし数に代入すると、結果は初期値を符号なし型が表す最大値で割った余りになります。
- 範囲を超える値を符号付き数に代入すると、結果は未定義になります。
//test01.cpp
#include<iostream>
using namespace std;
int main() {
int i = 42;
if (i) {
i = 0;
}
cout << "i = " << i << endl; // i=0
return 0;
}
//test02.cpp
#include<iostream>
int main() {
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; //-84
std::cout << u + i << std::endl; //4294967264
return 0;
}
//test03.cpp
#include<iostream>
int main() {
for (int i = 10; i >= 0; --i) {
std::cout << i << std::endl;
}
return 0;
} //正常出力
//test04.cpp
#include<iostream>
int main() {
for (unsigned u = 10; u >= 0; --u) { //符号なし数は0未満にならない
std::cout << u << std::endl; //無限ループ、ctrl+cで終了
}
return 0;
}
//test05.cpp
#include<iostream>
int main() {
unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl; //32
std::cout << u - u2 << std::endl; //4294967264
int i = 10, i2 = 42;
std::cout << i2 - i << std::endl; //32
std::cout << i - i2 << std::endl; //-32
std::cout << i - u << std::endl; //0
std::cout << u - i << std::endl; //0
return 0;
}
リテラル定数 (Literal Constants)#
- 整数リテラル定数:
- 十進数 :42
- 八進数: 052 (0 で始まる)
- 十六進数: 0x2A または 0X2A (0x または 0X で始まる)
- 二進数:
0b101010
または0B101010
(0b または 0B で始まる)
エラー例:
#include<iostream>
int main() {
int month = 9, day = 7;
int month1 = 09, day1 = 07; //0で始まると8進数として扱われる
// [Error] invalid digit "9" in octal constant
return 0;
}
- 浮動小数点リテラル定数:
- 十進数:
3.14
または2.0
- 科学的記数法:
3.0e8
(3.0 × 10 の 8 乗を表す)
- 文字型リテラル定数:
- 文字:
'A'
または'x'
- エスケープ文字:
'\n'
(改行文字を表す)または'\\'
(バックスラッシュを表す)
エスケープシーケンス:
改行'\n' 水平タブ'\t' ビープ'\a'
垂直タブ'\v' バックスペース'\b' 二重引用符'\"'
バックスラッシュ'\\' 疑問符'\?' 単一引用符'\'' キャリッジリターン'\r'
一般化エスケープシーケンス:'\x'の後に1つまたは複数の16進数、または'\'の後に1、2、3個の8進数
'\7'ビープ '\12'改行 '\40'スペース
'\0'空文字 '\115'文字M '\x4d'文字M
//test05.cpp
#include<iostream>
int main() {
std::cout << '\n'; //改行
std::cout << "\tHi!\n"; //タブ,Hi!,改行
std::cout << "\v\?\abc\b\n"; //垂直タブ,?,ビープ,bc,バックスペース,改行
std::cout << "Hi \x4dO\115!\n"; //Hi MOM!,改行
std::cout << '\115' << '\n'; //M,改行
return 0;
}
- 文字列リテラル定数:文字列リテラル定数は一連の文字を表し、二重引用符で囲まれます。例えば:
- 文字列:
"Hello, World!"
文字列の実際の型は文字配列で、末尾に '\0' 文字が追加されます。文字列型は実際には定数文字で構成された配列で、末尾に '\0' で終了するため、文字列型の実際の長さは内容よりも 1 つ多くなります。
-
ブールリテラル定数:ブールリテラル定数は真または偽を表し、
true
またはfalse
です。 -
ポインタリテラル: nullptr
-
指定リテラル型:
文字と文字列リテラル:
前置詞 | 意味 | 型 |
---|---|---|
u | Unicode16 文字 | char16_t |
U | Unicode32 文字 | char32_t |
L | ワイド文字 | wchar_t |
u8 | UTF-8 (文字列リテラル) | char |
整数リテラル:
接尾辞 | 最小一致型 |
---|---|
u または U | unsigned |
l または L | long |
ll または LL | long long |
浮動小数点リテラル:
接尾辞 | 型 |
---|---|
f または F | float |
l または L | long double |
変数#
変数はプログラムが操作できる名前付きのストレージスペースを提供します。C++ では変数とオブジェクトは一般的に互換的に使用されます。
定義#
-
定義形式:型指定子(type specifier) + 1 つまたは複数の変数名からなるリスト;
-
初期化(initialize):オブジェクトが作成されるときに特定の値が与えられます。
-
- 初期化は代入ではありません。初期化 = 変数の作成 + 初期値の付与、代入 = オブジェクトの現在の値の消去 + 新しい値で置き換え
-
リスト初期化:波括弧 {} を使用します。
-
リスト初期化で初期値に情報損失のリスクがある場合、コンパイラはエラーを報告します。
int a{3.14}; // error: narrowing conversion from ‘double’ to ‘int’
-
デフォルト初期化:定義時に初期値が指定されていない場合はデフォルト初期化されます。
-
関数体内の組み込み型変数は初期化されません。
-
クラスのオブジェクトが明示的に初期化されていない場合、その値はクラス定義によって決まります。
#include<iostream>
std::string global_str; //グローバル変数global_strを定義、初期値は空文字列
int global_int; //グローバル変数global_intを定義、初期値は0
int main() {
int local_int; //ローカル変数は初期化されず、初期値は未定義
std::string local_str;
//local_strはstringクラスのオブジェクトで、その値はクラスによって決まります。空文字列です。
return 0;
}
すべての組み込み型変数を初期化することをお勧めします!
変数宣言と定義の関係#
プログラムを複数の論理部分に分割して記述します。C++ は分離コンパイルメカニズムをサポートしており、プログラムは複数のファイルに分割され、各ファイルは独立してコンパイルできます。分離コンパイルをサポートするために、C++ は宣言と定義を区別します。
宣言は名前をプログラムに知らしめ、定義は名前に関連付けられた実体を作成します。
宣言は変数の型と名前を規定します。定義はストレージスペースを要求し、変数に初期値を与えることもあります。変数を宣言したいが定義したくない場合は、変数名の前にキーワードextern
を付けて、変数を明示的に初期化しないでください。
変数は一度だけ定義できますが、何度も宣言できます。定義は 1 つのファイルにのみ存在し、他のファイルでその変数を使用する場合は宣言が必要です。
extern int i; //iを宣言するが定義しない
int j; //jを宣言し定義する
// 明示的な初期化を含む宣言は定義になります:
extern double pi = 3.14; //定義
// 関数体内でexternキーワードでマークされた変数を初期化しようとするとエラーが発生します。
extern int ix = 1024; //定義
int iy; //定義
extern int iz; //宣言
変数識別子と命名規則#
識別子:
- アルファベット、数字、アンダースコアで構成され、アルファベットまたはアンダースコアで始まる必要があります。
-
長さに制限はありません。
-
大文字と小文字は区別されます。
-
予約語は識別子として使用できません。
-
ユーザー定義の識別子
- 連続して 2 つのアンダースコアを使用できません。
- アンダースコアの後に大文字のアルファベットで始めることはできません。
- 関数体の外で定義された識別子はアンダースコアで始めることはできません。
- 小キャメルケース命名法を使用します。
//C++キーワード
alignas continue friend register true
alignof decltype goto reinterpret_cast try
asm default if return typedef
auto delete inline short typeid
bool do int signed typename
break double long sizeof union
case dynamic_cast mutable static unsigned
catch else namespace static_assert using
char enum new static_cast virtual
char16_t explicit noexcept struct void
char16_t export nullptr switch volatile
class extern operator template wchar_t
const false private this while
constexpr float protected thread_local
const_cast for public throw
//C++演算子の代替名
and bitand compl not_eq or_eq xor_eq
and_eq bitor not or xor
命名規則:
- 実際の意味を反映させること
- 変数名と関数名は小キャメルケース命名法を使用します。例:
myVariable
- 自作クラス名は大文字で始めます。例:
Sales_item
- 識別子が複数の単語で構成される場合、単語間に明確な区切りを設けます。例:
student_loan
、studentLoan
、studentloan
は使用しない。
int double = 3.14; //エラー
int _;
int catch-22; //エラー
int 1_or_2 = 1; //エラー
double Double = 3.14;
名前のスコープ(namescope)は{}
で区切られます。
- グローバルスコープ
- ブロックスコープ
最初に変数を使用する際に定義する方が見つけやすく、比較的合理的な初期値を与えることができます。
//scope.cpp
#include <iostream>
// このプログラムは説明目的のみです:関数がグローバル変数を使用し、同時に同じ名前のローカル変数を定義するのは悪いスタイルです。
int reused = 42; // reusedはグローバルスコープ
int main() {
int unique = 0; // uniqueはブロックスコープ
// 出力 #1: グローバルreusedを使用; 42 0を出力
std::cout << reused << " " << unique << std::endl;
int reused = 0; // 新しいローカルオブジェクトreusedがグローバルreusedを隠します。
// 出力 #2: ローカルreusedを使用; 0 0を出力
std::cout << reused << " " << unique << std::endl;
//この時、グローバル変数は同名のローカル変数に覆われます。
// 出力 #3: 明示的にグローバルreusedを要求; 42 0を出力
std::cout << ::reused << " " << unique << std::endl;
//グローバル変数を使用
return 0;
}
ネストされたスコープ:
グローバル変数とローカル変数が同時に存在する場合、定義されたローカル変数のスコープ内で::
を使用してグローバル変数に明示的にアクセスできます。グローバル変数を使用する場合は、同名のローカル変数を使用しないようにします。
#include <iostream>
int i = 42;
int main() {
int i = 100;
int j = i;
// jの値は100で、ローカル変数iがグローバル変数iを覆っています。
std::cout << j << std::endl; //100
return 0;
}
#include <iostream>
int main() {
//以下のプログラムは合法ですか?合法であれば、何を出力しますか?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i; //この時の変数iはforループ内でのみ有効
std::cout << i << " " << sum << std::endl;
//合法出力:100 45
return 0;
}
左値と右値#
-
左値(l-value)は代入文の左側または右側に出現できるもの、例えば変数です。
-
右値(r-value)は代入文の右側にのみ出現できるもの、例えば定数です。
参照#
一般に言う参照は左値参照を指します。
参照:参照はオブジェクトに別の名前を付けることです。参照型:別の型を参照(refer to)します。
int &refVal = val;
- 参照は初期化が必要です:
int &refVal2; //エラー、参照は初期化されなければなりません
- 参照とその初期値はバインド bindされており、コピーではありません。一度定義されると、他のオブジェクトにバインドを変更することはできません。
- 参照型はバインドされるオブジェクトと一致する必要があります。
- 参照はオブジェクトにのみバインドでき、リテラルや式の計算結果にバインドすることはできません。
詳細な説明は、C++ における参照を参照してください。
ポインタ#
ポインタは、別の型を「指す(point to)」複合型です。本身はオブジェクトであり、定義時に値を設定する必要はありません。
ポインタ型の定義#
int *ip1,*ip2;
// ip1とip2はどちらもint型オブジェクトのポインタです。
double dp,*dp2
// dp2はdouble型オブジェクトを指すポインタで、dpはdouble型です。
オブジェクトのアドレスを取得#
ポインタは特定のオブジェクトのアドレスを格納します。オブジェクトのアドレスを取得するには:
int i=42;
int *p = &i;
&
はアドレス取得演算子です。 ポインタの型は指すオブジェクトの型と一致する必要があります(すべて同じ型 int、double など)。
int ival = 42;
int *p = &ival; //pはivalのアドレスを格納し、pはvalを指すポインタです。
double dval;
double *pd = &dval;//正しい
double *pd2 = pd;//正しい
int *pi = pd;//エラー、型不一致
pi = &dval;//エラー、doubleオブジェクトのアドレスをintポインタに代入しようとしています。
ポインタの値(つまりアドレス)の 4 つの状態:
- オブジェクトを指す。
- 隣接するオブジェクトの次の位置を指す。
- 空ポインタ。
- 無効なポインタ。
無効なポインタに対する操作はすべてエラーを引き起こします。
2 番目と 3 番目は有効ですが、ポインタオブジェクトにアクセスする行動の結果は予測できません。
ポインタでオブジェクトにアクセス#
#include <iostream>
int main() {
// ポインタがオブジェクトを指している場合、間接参照演算子(演算子'*')を使用してオブジェクトにアクセスします。
int ival = 42;
int *p = &ival;
std::cout << *p;// pポインタが指すオブジェクトのデータを出力します。
*p = 0;
std::cout << ival; // 0
return 0;
}
間接参照操作は、実際にオブジェクトを指している有効なポインタにのみ適用されます。
空ポインタ#
int *p1=nullptr; //空ポインタを使用します。
int *p2 = 0;//p2はリテラル定数0で初期化されます。
// include<cstdlib>
int *p3 = NULL;//int *p3=0 NULLプリプロセッサ変数
int zero = 0;
p1 = zero; //エラー、int変数をポインタに直接代入することはできません。
すべてのポインタを初期化することをお勧めします!
ポインタと参照の違い#
- 参照はオブジェクトの別名ですが、ポインタ自体はオブジェクトです。
- 参照は初期化が必要で、定義後は他のオブジェクトにバインドを変更できません。
- ポインタにはこの制限はありません。
//ポインタと参照の主な違いを説明します。
//参照は別のオブジェクトの別名ですが、ポインタ自体はオブジェクトです。
//参照は初期化が必要で、一度定義された参照は他のオブジェクトにバインドを変更できません。
//ポインタは定義時に初期値を設定する必要はなく、他のオブジェクトを指すように再代入できます。
int i = 42;
int *pi = 0;//piは初期化されておらず、何も指していません。
int *pi2 = &i;//pi2はiを指します。
int *pi3;//もしpi3がブロック内で定義されている場合、pi3の値は不明です。
pi3 = pi2;//pi3はpi2と同じオブジェクトiを指します。
pi2 = 0;//pi2はオブジェクトを指していません。
// 代入文は常に左側のオブジェクトを変更します。
pi = &ival;//piはivalを指します。
*pi = 0;//ivalの値が変更され、piは変更されません。
//以下の定義を説明してください。これらの定義の中に不正なものはありますか?もしあれば、なぜですか?
int i = 0;
(a) double* dp = &i;
(b) int *ip = i;
(c) int *p = &i;
(a): 不正。double型のポインタをintに指すことはできません。
(b): 不正。int変数をポインタに代入することはできません。
(c): 正当。
その他のポインタ操作#
int ival = 1024;
int *p1=0;
int *p2=&ival;
// ポインタ値0、条件はfalse
if(p1)
//...
// p2ポインタは0でない、条件値はtrue
if(p2)
//...
// ポインタ比較
if(p1==p2)
//...
無効なポインタは予測できない結果を引き起こします!
// 1つの文で異なる型の変数を定義します。
int i=1024,
*p=&i,&r=i;
int* p;//合法ですが誤解を招く可能性があります。
int* p1,p2;//p1はポインタ、p2はintです。
int *p1,*p2;//p1とp2は両方ともポインタです。
または
int *p1;
int *p2;
ポインタを指すポインタ#
#include <iostream>
using namespace std;
int main() {
int ival = 1024;
int *pi = &ival; //piはint型を指します。
int **ppi = π //ppiはint型ポインタを指します。
// 間接参照
cout << "The value of ival\n"
<< "direct value: " << ival << "\n"
<< "indirect value: " << *pi << "\n"
<< "doubly indirect value: " << **ppi
<< endl;
return 0;
//The value of ival
//direct value: 1024
//indirect value: 1024
//doubly indirect value: 1024
}
ポインタを指す参照#
右から左に読み取ると r の定義がわかります。
int i=42;
int *p;
int *&r = p; //rはポインタpの参照です。
r = &i; //pはiを指します。
*r=0; //i=0
次の変数の型と値を説明してください。
(a) int* ip, i, &r = i;
(b) int i,*ip = 0;
(c) int* ip, ip2;
(a): ipはint型のポインタ、iはint、rはiの参照です。
(b): iはint、ipは空ポインタです。
(c): ipはint型のポインタ、ip2はintです。
void ポインタ#
void
ポインタは任意のオブジェクトのアドレスを格納できます。型がないため、メモリ空間を操作するだけで、void *
ポインタが指すオブジェクトを直接操作することはできません。
double obj = 3.14,
*pd = &obj;
void *pv = &obj; //objは任意の型のオブジェクトです。
pv = pd;//void*は任意の型を格納でき、使用時に強制的に型変換できます。
CONST 修飾子#
const:変更できない値の変数を定義します。 const オブジェクトは作成されると値が変更されないため、初期化が必要であり、変更できません。
const int bufSize = 512;
bufSize = 512;//エラー、constオブジェクトに値を書き込もうとしています。
const int i= getSize();
const int j=42;
const int k;//エラー、kは初期化されていない定数です。
int i=42;
const int ci=i;
int j=ci;
CONST オブジェクトはファイル内でのみ有効です:
-
複数のファイルに同名の const 変数が存在する場合、それは異なるファイルで独立した変数として定義されます。
-
複数のファイルで const 変数を共有するには、定義と宣言の両方に extern キーワードを追加します。
//file_1.ccで定義された初期化定数は他のファイルからアクセス可能です。
extern const int bufSize = fcn();
//file_1.h ヘッダーファイル
extern const int bufSize ;//file_1.ccで定義されたbufSizeと同じです。
const int buf; // 不合法, constオブジェクトは初期化されなければなりません。
int cnt = 0; // 合法
const int sz = cnt; // 合法
++cnt; ++sz; // 不合法, constオブジェクトは変更できません。
const の参照#
- 参照を const オブジェクトにバインドすると、定数の参照と呼ばれます。
- 通常の参照とは異なり、定数の参照はバインドされたオブジェクトを変更するために使用できません。
const int ci=1024;
const int &r1=ci;
r1=42; //エラー、r1は定数の参照です。
int &r2 = ci; //エラー、非常数参照が定数オブジェクトを指そうとしています。
初期化と const の参照#
参照型はバインドされるオブジェクトの型と一致する必要があります。
- 例外:定数参照を初期化する際、任意の式を初期値として使用することが許可されます。式の結果が参照の型に変換できる限り。
- 定数参照に非常数オブジェクト、リテラル、一般的な式をバインドすることが許可されます。
int i=42;
const int &r1=i; //const int&をintオブジェクトにバインドすることが許可されます。
const int &r2=42;//r2は定数参照です。
const int &r3=r1*2;//r3は定数参照です。
int &r4=r1*2;//エラー、r4は普通の非常数参照です。
//定数参照が別の型にバインドされるとどうなるか?
double dval = 3.14;
const int &ri=dval;
//コンパイラは上記のコードを次のように変換します。
const int temp=dval;//一時的な整数変数を生成します。
const int &ri=temp;//riはこの一時変数にバインドされます。
- 一時オブジェクト(temporary):コンパイラが式の結果の値を一時的に保存するために必要な未命名の変数を一時的に作成します。
- 一時オブジェクトへの参照は不正な行為です:
double dval = 3.14;
int &ri = dval; //エラー
const の参照は非 const オブジェクトを参照する可能性が高いです。
- 定数参照は参照可能な操作にのみ制限を設けますが、参照されるオブジェクト自体が定数であるかどうかは制限されません。
int i = 42;
int &r1 = i; //r1はiをバインドします。
const int &r2 = i; //r2はiをバインドします。r2を通じてiを変更することはできません。
r1 = 0; //ok、r1は非常数です。
r2 = 0; //エラー、r2は定数参照です。
定数参照と同様に、定数を指すポインタも同様に、指されるオブジェクトが必ずしも定数である必要はありません。単にポインタを通じてオブジェクトの値を変更できないことが要求されます。
const ポインタ#
定数ポインタは初期化が必要で、ポインタを定数として定義することができます:
int errNumb=0;
int *const curErr=&errNumb;// curErrは定数で、常にerrNumbを指します(アドレスは変更できません)。
const double pi=3.1415;
const double *const pip= π//pipは定数で、*pipも定数です。
//pipは定数オブジェクトを指す定数ポインタです(値とアドレスの両方を変更できません)。
*pip = 2.72;//エラー *pipは定数です。
if(*curErr){
errorHandler();
*curErr=0;//ok *curErrは定数ではありません。
}
//以下の初期化は合法ですか?理由を説明してください。
int i = -1, &r = 0; // 不合法、rはオブジェクトを参照する必要があります。
int *const p2 = &i2; // 合法、定数ポインタです。
const int i = -1, &r = 0; // 合法
const int *const p3 = &i2; // 合法
const int *p1 = &i2; // 合法
const int &const r2; // 不合法、r2は参照であり、トップレベルのconstがありません。
const int i2 = i, &r = i; // 合法
//以下の定義は何を意味しますか?不合法なものを選んでください。
int i,*const cp; // 不合法、constポインタは初期化されなければなりません。
int *p1,*const p2; // 不合法、constポインタは初期化されなければなりません。
const int ic, &r = ic; // 不合法、const intは初期化されなければなりません。
const int *const p3; // 不合法、constポインタは初期化されなければなりません。
const int *p; // 合法。const intを指すポインタです。
//前の練習で定義された変数があると仮定すると、以下の文は合法ですか?理由を説明してください。
i = ic; // 合法、定数を普通の変数に代入します。
p1 = p3; // 不合法、p3はconstポインタで、普通のポインタに代入することはできません。
p1 = ⁣ // 不合法、普通のポインタは定数を指すことはできません。
const int * const p3 = ⁣ // 合法、p3は定数ポインタで、定数を指します。
p3 = ⁣ //この時不合法、p3は定数です。
int * const p2 = p1; // 合法、普通のポインタを定数ポインタに代入できます。
p2 = p1;//この時不合法、p2は定数です。
const int ic = *p3; // 合法、p3から値を取得した後、icに代入します。
ic = *p3;//この時不合法、icは定数です。
トップレベル const#
- トップレベル const:ポインタ自体が定数です。
- ボトムレベル const:ポインタが指すオブジェクトが定数です。コピー時には同じボトムレベル const 資格が厳密に要求されます。
int i=0;
int *const p1=&i;//p1は定数で、トップレベルconstです。
const int ci=42;//ciは定数で、トップレベルconstです。
const int *p2=&ci;//*p2は定数で、ボトムレベルconstです。
const int *const p3=p2;//右側はトップレベルconst、左側はボトムレベルconstです。
const int &r =ci;//参照のために使用されるconstはすべてボトムレベルconstです。
// コピー時、トップレベルconstは影響を受けません。
i=ci;//ciの値をコピーし、ciのトップレベルconstは影響を受けません。
p2=p3;//p2とp3は指すオブジェクトの型が同じで、p3のトップレベルconst部分は影響を与えません。
// コピー時にはボトムレベルconst資格が厳密に要求され、データ型の変換が可能です。
int *p =p3;//エラー、p3はボトムレベルconstを含んでおり、pは含んでいません。
p2=p3;//ok、p2とp3はボトムレベルconstで、コピー時にはトップレベルconstを無視します。
p2=&i; //ok、int*はconst int*に変換できます。
int &r=ci;//エラー、普通のint&はint定数にバインドできません。
const int &r2=i;//ok、const int&はintにバインドできます。
//次の文に対して、オブジェクトがトップレベルconstまたはボトムレベルconstとして宣言されているかを説明してください。
const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2,
*const p3 = &i, &r2 = v2;
//v2はトップレベルconst、p2はボトムレベルconst、p3はトップレベルconstでボトムレベルconstでもあり、r2はボトムレベルconstです。
//前の練習で定義された変数があると仮定すると、以下の文は合法ですか?理由を説明してください。
r1 = v2; // 合法、トップレベルconstはコピー時に影響を受けません。
p1 = p2; // 不合法、p2はボトムレベルconstで、p1はボトムレベルconstでなければなりません。
p2 = p1; // 合法、int*はconst int*に変換できます。
p1 = p3; // 不合法、p3はボトムレベルconstで、p1はボトムレベルconstでなければなりません。
p2 = p3; // 合法、p2とp3はボトムレベルconstで、コピー時にはトップレベルconstを無視します。
constexpr と定数式#
- 定数式:値が変更されず、かつコンパイル時に計算結果が得られる式です。
//オブジェクト(式)が定数式であるかどうかは、データ型と初期値によって決まります。
const int max_file=30; //max_fileは定数式です。
const int limit = max_file+1; //limitは定数式です。
int staff_size = 7; //staff_sizeは定数式ではありません。
const int sz = get_size(); //実行時に取得されるため、szは定数式です。
- C++11 の新しい標準では、変数を
constexpr
型として宣言することが許可され、コンパイラがその値が定数式であるかどうかを検証します。
constexpr int mf = 20; //20は定数式です。
constexpr int limit = mf+1; //mf+1は定数式です。
constexpr int sz = get_size(); //get_size()がconstexpr関数であるかどうかに依存します。
リテラル型#
定数式の値はコンパイル時に計算され、型が単純で、値が得やすいものを「リテラル型」(literal type)と呼びます。
- 算術型
- 参照、ポインタ、nullptr、0 または固定位置
ポインタと constexpr#
- constexpr は定義されたオブジェクトをトップレベルに置きます。
const int *p=nullptr;//pは定数を指すポインタです。
constexpr int *q=nullptr;//qは定数ポインタです。
//constexprは定義されたオブジェクトをトップレベルに置きます。
constexpr int *nq=nullptr;//nqは定数ポインタです。
int j=0;
constexpr int i=42;//iは定数です。
//i,jは関数外で定義されています。
constexpr const int *p=&i;//pは定数ポインタで、整数定数iを指します。
constexpr int *p1=&j;//p1は定数ポインタで、整数jを指します。
型の処理#
- プログラムが複雑になるにつれて、型も複雑になります。型のスペルが難しく、目的の意味が明確でなく、必要な型がわからなくなります。
型エイリアス#
-
従来のエイリアス:
typedef
を使用して型の同義語を定義します。typedef double wages; wages hour,weekly; //double hour,weekly;
-
新しい標準エイリアス:エイリアス宣言(alias declaration):
using SI = Sales_item; //(C++11) SI items;//Sales_item items;
ポインタ、定数、型エイリアス#
// 複合型(ポインタなど)に対して元の式に戻して理解することはできません。
typedef char *pstring; // pstringはchar*のエイリアスです。
const pstring cstr = 0; // cstrはcharを指す定数ポインタです。
const pstring *ps;//psはconst charを指すポインタです。
//基本データ型はポインタではなくconst charです。
AUTO 型指定子 (C++ 11)#
式の型を明確に知ることは容易ではないため、auto
が導入されました。
- auto 型指定子:コンパイラが型を自動的に推論します。明らかに auto で変数を定義するには初期値が必要です。
- 一つの文には一つの基本データ型しか存在できません:
auto sz = 0, pi =3.14;//エラー、型が一致しません。
複合型、定数、auto#
int i = 0, &r = i;
auto a = r; //aの型はintと推論されます。
//トップレベルのconstは無視されます。
const int ci = i;
auto b = ci;//intと推論され、トップレベルのconstは無視されます。
const int ci = 1; const auto f = ci;
//型はintと推論され、トップレベルのconstが必要な場合は自分でconstを追加する必要があります。
DECLTYPE 型ヒント (C++ 11)#
decltype
は C++11 で導入されたキーワードで、式の型を取得するために使用されます。構文は次のとおりです:
decltype(expression)
ここで、expression
は変数、関数呼び出し、型変換などの式です。
decltype
の目的は、式の型に基づいて変数または式の型を推論し、その型を返すことです。例えば:
int x = 42;
decltype(x) y; // yの型はintです。
double square(double value) {
return value * value;
}
decltype(square(3.14)) result; // resultの型はdoubleです。
struct Point {
int x;
int y;
};
Point p;
decltype(p.x) a; // aの型はintです。
上記の例では、decltype(x)
はint
型を返します。なぜなら、式x
の型はint
だからです。decltype(square(3.14))
はdouble
型を返します。なぜなら、式square(3.14)
の戻り値の型はdouble
だからです。decltype(p.x)
はint
型を返します。なぜなら、p.x
はPoint
構造体のメンバー変数だからです。
decltype
は、ジェネリックコードを書く際に非常に便利で、引数の型に基づいて関数の戻り値の型を推論でき、手動で戻り値の型を指定する必要がなくなります。例えば:
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
int main() {
int x = 5;
double y = 3.14;
auto result = multiply(x, y); // resultの型はdoubleです。
return 0;
}
上記の例では、multiply
関数はdecltype(a * b)
を使用して戻り値の型を推論します。渡された引数の型に基づいて、戻り値の型は自動的にdouble
として推論されます。
decltype
は非常に便利なキーワードで、コンパイラが式の型に基づいて変数や関数の型を推論できるようにし、コードの柔軟性と可読性を向上させます。
-
式の型から定義する変数の型を推論します。
-
decltype:オペランドのデータ型を選択して返し、式の値を計算しません。
decltype(f()) sum = x; //sumの型は関数fの戻り値の型と推論されます。
- トップレベル const を無視しません。
- 変数に括弧を付けると、コンパイラはそれを式と見なします。
decltype((i))
は参照を返します。 - 代入は左値の型を生成する典型的な式です。つまり、i が int であれば、式 i=x の型は int & です。
自作データ構造 STRUCT#
クラス定義とオブジェクト定義を一緒にすることは避けてください。例:struct Student {} xiaoming,xiaofang;
- クラスはキーワード struct で始まり、クラス名とクラス体が続きます。
- クラスデータメンバー:クラス体はクラスのメンバーを定義します。
- C++11:クラスデータメンバーにクラス内初期値(in-class initializer)を提供できます。
以下の例を示します:
- Sales_data クラスの定義:
//Sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif
- sales_data クラスの使用
//Sales_data.cpp
#include <iostream>
#include <string>
#include "Sales_data.h"
int main() {
Sales_data data1, data2;
// data1とdata2に読み込むコード
double price = 0; // 書籍ごとの価格、総収益を計算するために使用されます。
// 最初の取引を読み取ります:ISBN、販売された書籍の数、価格
std::cin >> data1.bookNo >> data1.units_sold >> price;
// 価格と販売数から総収益を計算します。
data1.revenue = data1.units_sold * price;
// 2番目の取引を読み取ります
std::cin >> data2.bookNo >> data2.units_sold >> price;
data2.revenue = data2.units_sold * price;
// data1とdata2が同じISBNを持っているかどうかを確認するコード
// もしそうであれば、data1とdata2の合計を印刷します。
if (data1.bookNo == data2.bookNo) {
unsigned totalCnt = data1.units_sold + data2.units_sold;
double totalRevenue = data1.revenue + data2.revenue;
// 印刷:ISBN、総販売数、総収益、書籍ごとの平均価格
std::cout << data1.bookNo << " " << totalCnt
<< " " << totalRevenue << " ";
if (totalCnt != 0)
std::cout << totalRevenue / totalCnt << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0; // 成功を示します
} else { // 取引は同じISBNではありませんでした
std::cerr << "データは同じISBNを参照する必要があります"
<< std::endl;
return -1; // 失敗を示します
}
}
自作ヘッダーファイルの作成#
- ヘッダーファイルには、通常、1 回だけ定義されるエンティティが含まれます:クラス、const および constexpr 変数。
プリプロセッサの概要:
- プリプロセッサ(preprocessor):ヘッダーファイルが複数回含まれても安全に機能します。
- プリプロセッサが
#include
マークを見つけると、指定されたヘッダーファイルの内容で#include
を置き換えます。 - ヘッダーファイル保護符(header guard):ヘッダーファイル保護符は、プリプロセッサ変数の状態に依存します:定義済みと未定義。
- #indef が定義済みの場合は真
- #ifndef が未定義の場合は真
- ヘッダーファイル保護符の名前は一意であり、すべて大文字である必要があります。良い習慣を身につけ、ヘッダーファイルが含まれているかどうかに関係なく、保護符を追加してください。
#ifndef SALES_DATA_H //SALES_DATA_Hが未定義の場合は真
#define SALES_DATA_H
strct Sale_data{
...
}
#endif