C++ Primer#
- 単一ファイルコンパイル
g++ -o 出力ファイル名 コンパイル対象ファイル名
- ファイルへの読み込みと出力
プログラム名 < 入力ファイル
プログラム名 > 出力ファイル
データ型#
- 選択の経験:
数値が負でない場合は無符号型 (unsigned) を選択
整数演算にはint
を選択し、範囲を超える場合はlong long
を選択
浮動小数点演算にはdouble
を選択
- 符号付き型と無符号型を混用しないこと。符号付き数は自動的に無符号数に変換され、初期値に対して無符号型が表現できる数値の総数で割った余りとなる。
- 文字型リテラル定数の型はプレフィックスで指定でき、整数型や浮動小数点型はサフィックスで指定できる。
変数#
- 初期化と代入には本質的に大きな違いがある。
- リスト初期化
{}
を使用すると、データが失われる可能性がある場合にコンパイラが警告を出す。 - 宣言 (declaration) と定義 (definition) は異なり、宣言は名前をプログラムに知らしめ、定義は変数にストレージを割り当てたり初期値を設定したりする。
extern
キーワードを使用して変数を宣言する。- 変数は一度だけ定義できるが、複数回宣言することはできる。
- C++ は静的型付け言語であり、コンパイル時に型をチェックする。
- グローバル変数はブロックスコープ内で
::
プレフィックスを使って明示的にアクセスできる(ブロックスコープ内のローカル変数を隠す)。 - ローカル変数はグローバル変数と同名にしない方が良い。
複合型#
- 変数名の前に
&
を付けて参照型を定義し、参照は初期化する必要がある。 - 参照は別名である。
- 参照はオブジェクトにのみバインドできる。
- ポインタはオブジェクトのアドレスを格納し、アドレス演算子
&
を使用してアドレスを取得する。 - ポインタを使用してオブジェクトにアクセスし、逆参照演算子
*
を使用してオブジェクトにアクセスする。 &
と*
が宣言や式に現れる場合の意味は全く異なる。- 空ポインタを初期化する (=nullptr、=0、=NULL)。
- 代入で変更されるのは常に等号の左側のオブジェクトである。
void*
は任意のオブジェクトのアドレスを格納できる。- 型修飾子 (
*
と&
) はその後の最初の変数識別子のみを修飾する。
const 修飾子#
- 定数参照は const の参照である。
- 定数参照を初期化する際には任意の式を初期値として使用でき、その式の結果が参照の型に変換可能であればよい。
- const の参照は const でないオブジェクトを参照することがある。
- いわゆる定数を指すポインタや参照は、ポインタや参照が **「自分は定数だと思っている」** だけであり、意識的に指しているオブジェクトの値を変更しない。
- 宣言の意味を理解する最も効果的な方法は、右から左に読むことである。
*
が const の前にある場合、ポインタは定数である - 不変なのはポインタ自体の値であり、指している値ではない。- ポインタ自体が定数であることは、ポインタを通じて指しているオブジェクトの値を変更できないことを意味しない。
- 非定数は定数に変換できるが、その逆はできない。
- 定数式は値が変わらず、コンパイル時に計算結果が得られる式である。
- オブジェクトが定数式であるかどうかは、そのデータ型と初期値によって決まる。
- トップレベル const はポインタ自体が定数であることを示す;ボトムレベル const はポインタが指しているオブジェクトが定数であることを示す(例示に過ぎず、トップレベルとボトムレベル const はさまざまな型に適用される)。
- オブジェクトのコピー操作を実行する際、トップレベル const とボトムレベル const の違いは明確である。
処理型#
- 型エイリアス:
typedef 前者 後者
—— 後者は前者の同義語である。 - typedef で
*
を使用する場合に注意する(単純な置き換え関係ではない)、const
は与えられた型の修飾である。 - エイリアス宣言:
using 前者 = 後者
—— 前者は後者の同義語である。 auto
は初期値に基づいてデータ型を自動推論する(ボトムレベル const のみ保持する)。トップレベル const は auto の前に修飾を加える必要がある。*
と&
は基本データ型の一部ではなく、特定の宣言子に属する。decltype
は式の型を推論し、初期値として使用しない(変数の全体の型を保持する)。- decltype で使用される式が変数でない場合、decltype は式の結果に対応する型を返す。
- 式の内容が逆参照操作である場合、decltype は参照型を得る。
- decltype で使用される式に対して、変数名に括弧を加えることと加えないことでは得られる型が異なる。
decltype((変数))
の結果は常に参照であり、decltype(変数)
は変数自体が参照である場合にのみ参照である。
カスタムデータ構造#
struct クラス名 クラス体 ;
- プリプロセッサはヘッダーファイルが複数回含まれても安全に動作することを保証する —— ヘッダーファイル保護符。
#define
は名前をプリプロセッサ変数として設定する。#ifdef
は変数が定義されている場合にのみ真である。#ifndef
は変数が未定義の場合にのみ真である。#endif
は結果が真である場合に後続の操作を実行し、この命令が現れるまで続く。- 一般に、プリプロセッサ変数名はすべて大文字にしてその唯一性を保証する。
- ヘッダーファイルが変更された場合、関連するソースファイルは更新された宣言を取得するために再コンパイルする必要がある。
using 宣言#
using namespace::name;
- ヘッダーファイルには using 宣言を含めるべきではない。
string#
std::string
可変長文字列シーケンス。- 読み取り操作を実行する際、string オブジェクトは自動的に先頭の空白(スペース、改行、タブなど)を無視し、最初の実際の文字から読み始め、次の空白に出会うまで読み続ける。
- よく使う操作:
getline(a,b)
——a から 1 行(改行文字で区切られる)を読み取り b に代入する、s.empty()
——s が空かどうかを判断する、s.size()
。 - size 関数の戻り値は無符号整数型(型は
string::size_type
)である。int と unsigned の混用を避けることに注意する。 - string の比較:1. 文字が同じ場合、短い string は長い string より小さい;2. 文字が異なる場合、最初の異なる文字の比較。
- string オブジェクトと文字列リテラルが同じ文に混在する場合、各 + の両側の演算オブジェクトの少なくとも 1 つが string であることを確認する必要がある。
- 文字列リテラルと string は異なる型である。
- C++ バージョンの C 標準ライブラリヘッダファイル
ctype.h
=>cctype
。 - cctype には一連の文字の判断と処理関数が含まれている。
- 範囲ベースの for 文
for (declaration : expression)
は python の for 文に似ている。 - string 内の文字はインデックスでアクセスできる。
- インデックスの合法性(正しい範囲内にあるか)を常に確認することに注意する。
vector#
std::vector
はオブジェクトの集合を表す(すべてのオブジェクトの型は同じ)、コンテナとも呼ばれる。- vector は型テンプレートであり、型ではない。
vector<型> コンテナ名;
- vector の豊富な初期化方法:リスト (
vector<T> v5{a,b,c...}
またはvector<T> v5={a,b,c...}
)、コピー (vector<T> v2(v1)
またはvector<T> v2 = v1
)、構築 (vector<T> v3(n,val)
またはvector<T> v3(n)
)... push_back(値)
は値を vector の末尾要素として追加する。- vector は要素を効率的に迅速に追加できる(容量を指定する必要はない)。
- ループ本体内に vector に要素を追加する文が含まれている場合、範囲 for ループを使用できない。
- empty と size 関数は string と似ている。
- size 関数の戻り値も vector の特別な型
size_type
であるが、vector の要素型を指定する必要がある。 - vector の比較法則も string と似ている。
- vector はインデックスを使用して要素を追加することはできず、既存の要素にのみインデックスでアクセスできる。
イテレータ#
- イテレータの型は、イテレータを返すメンバーを持つ。
begin()
は最初の要素を指すイテレータを返し、end()
は末尾要素の次の位置 **(尾後)** を指すイテレータを返す。- 一般に、イテレータの正確な型は不明である(
auto
を使用して変数を定義する)。 *iter
はイテレータが指す要素の参照を返し、iter->mem
、++iter
/--iter
はコンテナの次の / 前の要素を指示する。- ジェネリックプログラミング:すべての標準ライブラリコンテナのイテレータは
==
と!=
を定義しているため、for ループでは!=
を使用し、<
を使用しない。なぜなら、このプログラミングスタイルは標準ライブラリが提供するすべてのコンテナで有効だからである。 const_iterator
は要素を読み取ることができるが、書き込むことはできない。->
は逆参照とメンバーアクセスの組み合わせであり、it->mem は等価である (*it).mem。- vector の容量を変更する可能性のある操作は、すべてその vector のイテレータを無効にする。
- イテレータを使用したループ本体では、イテレータが属するコンテナに要素を追加しないようにする。
- 2 つのイテレータの差は
difference_type
(符号付き整数型数)である。
配列#
- vector に似ているが、同じ型のオブジェクトを格納するコンテナである。ただし、配列のサイズは固定されており、自由に要素を追加することはできない。
- 配列内の要素の数も配列型の一部であるため、定数式である必要がある。
- 配列の初期化:リスト初期化、コピーは許可されない。
- 文字配列は文字列リテラルで初期化できるが、文字列リテラルの末尾には空文字が自動的に付加されることに注意する。
- デフォルトでは、型修飾子は右から左に順次バインドされるが、配列の場合は **(括弧)内から外に読む ** 方が意味がある。
- 配列インデックスを使用する際、通常は
size_t
型として定義する。 - 配列型のオブジェクトを使用することは、配列の最初の要素を指すポインタを使用することと同じである。
- 配列を auto 変数の初期値として使用する場合、推論される型は配列ではなくポインタである。ただし、decltype ではこの変換は発生しない。
- 配列はインデックスで末尾要素の次に存在しない要素を参照することができる。
begin(配列名)
/end(配列名)
は安全に最初の要素ポインタ / 尾後要素ポインタを返す。- 2 つのポインタの差は
ptrdiff_t
である。 - 2 つのポインタが無関係なオブジェクトを指している場合、比較できない。
- 組み込みのインデックス演算子で使用されるインデックス値は無符号型ではなく、これは vector や string とは異なる。
- C スタイルの文字列は文字配列に格納され、空文字(
\0
)で終了する。 - ヘッダーファイル
cstring
に定義された関数は C スタイルの文字列を操作できる。 - 標準ライブラリの string を使用する方がC スタイルの文字列を使用するよりも安全で効率的である。
- できるだけ標準ライブラリの型を使用し、配列を使用しない。
多次元配列#
- 厳密には、C++ には多次元配列は存在せず、通常言われる多次元配列は配列の配列である。
{}
で括られた値のグループを使用して多次元配列を初期化し、波括弧のネストは完全に等価である(ネストはより明確に読むためのものである)。- 一部の要素のみを初期化でき、他の要素はデフォルト初期化が実行される。
- 範囲 for 文を使用して多次元配列を処理する場合、最内層のループを除くすべてのループの制御変数は参照型であるべきである。
- プログラムが多次元配列の名前を使用する場合、自動的にその配列の最初の要素を指すポインタに変換される。つまり、最初の内側の配列を指すポインタである。
式の基礎#
- 左辺値と右辺値:左辺値は代入文の左側に位置でき、右辺値はできない(C++ ではそれほど単純ではない)。
- オブジェクトが右辺値として使用されるときは、そのオブジェクトの値(内容)が使用される;オブジェクトが左辺値として使用されるときは、そのオブジェクトの識別(メモリ内の位置)が使用される。
- 代入演算子は、左側の演算子として非常に量左辺値を必要とし、得られる結果も左辺値である。
- アドレス演算子は左辺値演算オブジェクトに作用し、その演算オブジェクトを指すポインタを返す。このポインタは右辺値である。
- 逆参照演算子、インデックス演算子の評価結果は左辺値である。
- 複合式では、括弧は優先順位と結合則を無視する。
- 大多数の演算子では評価順序は明確に規定されておらず、
&&
、||
、?:
、,
を除く。
演算子#
- 整数の除算の商は、正負にかかわらず 0 に切り捨てられる(小数部分を捨てる)。
(-m)/n
とm/(-n)
はどちらも-(m/n)
に等しく、m%(-n)
はm%n
に等しく、(-m)%n
は-(m%n)
に等しい。- 必要がない限り、後置のインクリメント・デクリメント演算子を使用しない。
ptr->mem
は(*ptr).mem
に等しい。P.S. 逆参照演算子の優先順位はドット演算子よりも低い。- 条件演算子(
cond?expr1:expr2
)はネスト可能であり、2〜3 層を超えない方が良い。 - 条件演算子の優先順位は非常に低く、通常はその両端に括弧を加える必要がある。
- ビット演算子は無符号型の処理にのみ使用する。
- シフト演算子(IO 演算子)の優先順位は高くも低くもない:算術演算子よりも低く、関係演算子、代入演算子、条件演算子よりも高い。
sizeof
は式または型名が占めるバイト数を返すsizeof (type)
とsizeof expr
。sizeof
は実際にその演算オブジェクトの値を計算しない。- char または char 型の式に対して実行される sizeof 演算の結果は 1 である。
- sizeof 演算は配列をポインタに変換せず、配列内のすべての要素に対して一度 sizeof 演算を実行し、その結果を合計することに等しい。
- string または vector オブジェクトに対して実行される sizeof 演算は、その型の固定部分のサイズのみを返し、オブジェクト内の要素が占めるスペースは計算しない。
- コンマ演算子の実際の結果は右側の式の値である。
型変換#
- 算術変換:ある算術型 -> 別の算術型、演算子の演算オブジェクトは最も広い型に変換され、整数値は浮動小数点型に変換される。
- 整数型の昇格:小さな整数型 -> 大きな整数型、
bool
、char
、short
はint
、long
などに昇格される。 - 強制型変換
cast-name<type>(expression)
。 - 明確に定義された型変換を持つ場合、底層 const を含まない限り、
static_cast
を使用できる。 - より大きな算術型を小さな型に代入する必要がある場合、
static_cast
は非常に便利である。 static_cast
はコンパイラが自動的に実行できない型変換にも非常に便利である。const_cast
は演算オブジェクトの底層 const のみを変更でき、const_cast
のみが式の定数属性を変更できる。reinterpret_cast
を使用することは非常に危険である。- 強制型変換はできるだけ避けるべきである。
- 古いスタイルの強制型変換
type (expr)
と(type) expr
は、明確さに欠け、追跡が困難である。
演算子の優先順位#
条件文#
else
は最も近い未マッチのif
にマッチし、波括弧を使用することで強制的にマッチさせることができる。case
ラベルは整数定数式でなければならない。- どこかで初期値を持つ変数がスコープ外にあり、別の場所でその変数がスコープ内にある場合、前の場所から後の場所へのジャンプは不正な動作である。
反復文#
- 従来の for ループの実行フロー:最初に init-statement を実行し、次に condition を判断する;条件が真であればループ本体を実行し、最後に expression を実行する。
- for 文の init-statement では複数のオブジェクトを定義できるが、宣言文は 1 つだけでなければならないため、すべての変数の基本型は同じでなければならない。
- 範囲 for 文では、範囲変数が参照型である場合にのみ要素に対して書き込み操作を実行できる。
- 範囲 for 文では、
auto
を使用することで型の互換性を保証できる。 - 範囲 for 文は従来の for 文と等価である(範囲 for 文を使用して vector オブジェクトや他のコンテナの要素を追加することはできない)。
do while
はwhile
に非常に似ており、ループ本体を先に実行し、条件をチェックする。
ジャンプ文#
break
は最も近いwhile
、do while
、for
、switch
文を終了させ、その後の最初の文から実行を続ける。continue
は最も近いfor
、while
、do while
ループの現在の反復を終了させ、即座に次の反復を開始する。goto label;
label は文を識別するための識別子である。
try 文ブロックと例外処理#
- プログラムの例外検出部分は
throw
式を使用して例外を引き起こす。 try
ブロックの後に 1 つ以上のcatch
句が続き、try
内でスローされた例外が対応するcatch
句を選択する。- C スタイルの文字列(
const char*
)。 - 例外はプログラムの正常なフローを中断し、例外が発生した際に「クリーンアップ」を正しく実行したプログラムは例外安全なコードと呼ばれる。
stdexcept
ヘッダーファイルは、いくつかの一般的な例外クラスを定義しており、他のいくつかの例外タイプ:exception
、bad_alloc
、bad_cast
。- 後の 3 つの例外はデフォルト初期化の方法でのみ初期化でき、これらのオブジェクトに初期値を提供することは許可されない。他の例外タイプは string オブジェクトまたは C スタイルの文字列を使用してこれらのタイプのオブジェクトを初期化することができ、デフォルト初期化の方法を使用することは許可されない。
- 例外タイプは 1 つのメンバー関数
what
を定義しており、この関数には引数がなく、C スタイルの文字列へのポインタconst char*
を返し、例外に関する情報を提供する。
関数の基礎#
- 関数の形参リストの形参は通常カンマで区切られ、各形参には宣言子の宣言が含まれている。たとえ 2 つの形参の型が同じであっても、必ず2 つの型をすべて書く必要がある。
- 形参名はオプションであり、関数が実際に使用しない形参がある場合、そのような形参は通常名前を付けず、関数体内で使用しないことを示す。
- 関数の戻り値の型は配列型や関数型にはできないが、配列や関数へのポインタとして戻すことはできる。
- 名前にはスコープがあり、オブジェクトにはライフサイクルがある。
- 形参と関数体内部で定義された変数はローカル変数であり、関数のスコープ内でのみ可視であり、外側のスコープに同名の他のすべての宣言を隠す。
- ブロック実行中のみ存在するオブジェクトは自動オブジェクトと呼ばれる。
- ローカル静的オブジェクトはプログラムの実行パスがオブジェクト定義文を初めて通過したときに初期化され、プログラムが終了するまで破棄されない ——
static
。 - 関数名は使用前に宣言する必要があり、関数は一度だけ定義できるが、複数回宣言できる。関数宣言には関数体が必要なく、
;
で代用できる。 - 関数宣言は関数プロトタイプとも呼ばれる。
- 関数宣言を含むヘッダーファイルは、関数を定義するソースファイルに含めるべきである。
- 分離コンパイルはプログラムをいくつかのファイルに分散させ、各ファイルを独立してコンパイルできることを許可する。
引数の渡し方#
- 参照渡しと値渡し。
- C++ では、ポインタ型の代わりに参照型の形参を使用して関数外部のオブジェクトにアクセスすることを推奨する。
- コピーを避けるために参照を使用する。
- 関数が参照形参の値を変更する必要がない場合は、常に定数参照を使用するのが最良である。
- 参照形参を使用して追加情報を返す:関数は 1 つの値しか返せないが、時には関数が複数の値を同時に返す必要があるため、参照形参は複数の結果を一度に返すための有効な手段を提供する。
- 実引数で形参を初期化する際、トップレベル const(オブジェクト自体に作用する)は無視される。
- C++ では、異なる名前の関数を複数定義することが許可されているが、異なる関数の形参リストには明確な違いが必要である。
- 非定数で底層 const オブジェクトを初期化することはできるが、その逆はできない;通常の参照は同じ型のオブジェクトで初期化する必要がある。
- C++ では、リテラルを使用して定数参照を初期化することが許可されている。
- 常に定数参照を使用し、関数が変更しない形参を通常の参照として定義することは一般的な誤りである。
- 配列の特異性:配列をコピーすることは許可されず、配列を使用する際にはポインタに変換される。
- 配列を値渡しの方法で渡すことはできないが、形参を配列のような形式で書くことができ、実質的には配列の最初の要素を指すポインタを渡すことになる。
- ポインタ形参(配列実引数)を管理するための 3 つの技術:1. マークを使用して配列の長さを指定する;2. 標準ライブラリの規約を使用する(配列の最初の要素と尾後要素を指すポインタを渡す);3. 配列サイズを示す形参を明示的に渡す。
- 形参も配列の参照であることができる、例えば
int (&arr)[10]
、配列のサイズは配列型を構成する一部である。 - 多次元配列を渡す場合、例えば
int matrix[][10]
、コンパイラは最初の次元を無視し、それを形参リストに含めない方が良い。matrix の宣言は二次元配列のように見えるが、実際には形参は 10 個の整数を含む配列へのポインタである。 int main(int argc, char *argv[]) {...}
第二の形参は配列であり、その要素は C スタイルの文字列へのポインタである;第一の形参は配列内の文字列の数を示す。int main(int argc, char **argv) {...}
は上記のコードと等価である。- argv 内の実引数を使用する場合、オプションの実引数は
argv[1]
から始まり、argv[0]
はプログラム名を保存する。 - 関数の実引数の数が不明だが、すべての実引数の型が同じである場合、
initializer_list
型の形参を使用でき、使用方法はvector
に似ている。 initializer_list
内の要素は常に定数値であり、initializer_list
オブジェクト内の要素の値を変更することはできない。- 省略符形参は、C++ プログラムが特定の C コード(C 標準ライブラリ varargs を使用)にアクセスするために設定されたものであり、形のように
void foo(parm_list, ...);
やvoid foo(n...)
。
戻り値の型と return 文#
- 戻り値が void の関数には return が必ずしも必要ではない。なぜなら、最後には常に暗黙的に実行されるからである。関数を早期に終了させたい場合は return を使用できる。
- return 文を含むループの後にも return 文が必要であり、さもなければプログラムは誤りとなり、コンパイラが発見するのが難しい。
- ローカルオブジェクトの参照やポインタを返さないようにする。
- 参照を返す左辺値:参照を返す関数を呼び出すと左辺値が得られ、他の戻り値の型は右辺値が得られる。
- C++11 では、関数が
{}
で囲まれた値のリストを返すことができる(戻り値の型はvector<型>
)。 - ヘッダーファイル
cstdlib
には、main 関数の戻り値として使用できる 2 つのプリプロセッサ変数EXIT_FAILURE
、EXIT_SUCCESS
が定義されている。 - 再帰:関数が自分自身を呼び出す(main 関数は自分自身を呼び出すことはできない)。
- 関数は配列を返すことはできないが、配列のポインタや参照を返すことはできる。
- 配列のポインタや参照を返す関数を定義するには、型エイリアスを使用できる。例えば
typedef int arrT[10]
、同等の書き方using arrT = int[10]
、この場合、arrT* func(int i)
の関数は 10 個の整数を含む配列へのポインタを返す。 - 型エイリアスの他に、配列ポインタを返す関数形式は
Type (*function(parameter_list))[dimension]
のようにすることができる。 - 後置戻り型を使用することもでき、例えば
auto func(int i) -> int(*)[10]
。 - または
decltype(配列名)*
を使用して関数を宣言することもできる。
関数のオーバーロード#
- オーバーロード関数:同一スコープ内で同じ関数名だが形参リストが異なる関数(main 関数はオーバーロードできない)。
- オーバーロード関数の戻り値の型は一致する必要があり、同名の関数が異なる型を返すことは許可されない。
- トップレベル const 形参はオーバーロード関数を区別しないが、ボトムレベル const(ポインタ、参照)はオーバーロード関数を区別できる。
- できるだけ非常に似た操作のみをオーバーロードする方が良い。
const_cast
はオーバーロード関数のシナリオで最も役立つ。- 関数マッチングはオーバーロード解決とも呼ばれ、オーバーロード関数を呼び出す際に考えられる 3 つの結果:最適なマッチ、無マッチ、曖昧な呼び出し。
- C++ では、名前の検索は型チェックの前に行われる。
特殊用途の言語機能#
- ある形参にデフォルト値が与えられると、その後のすべての形参もデフォルト値を持たなければならない。
- デフォルト実引数は関数呼び出しで不足している尾部実引数を補う。
- デフォルト実引数を持つ関数を設計する際のタスクの 1 つは、形参の順序を合理的に設定し、デフォルト値をあまり使用しない形参を前に配置し、頻繁にデフォルト値を使用する形参を後に配置することである。
- 与えられたスコープ内で形参は一度だけデフォルト実引数を与えられる。関数の後続の宣言は、以前にデフォルト値を持たない形参にのみデフォルト実引数を追加でき、その形参の右側のすべての形参はデフォルト値を持たなければならない。
- 関数宣言でデフォルト実引数を指定し、その宣言を適切なヘッダーファイルに配置するべきである。
- 表現の型が形参が必要とする型に変換可能であれば、その表現はデフォルト実引数として使用できる;デフォルト実引数として使用される名前は関数宣言が存在するスコープ内で解決され、これらの名前の評価プロセスは関数呼び出し時に発生する。
- インライン関数は関数呼び出しのオーバーヘッドを回避できる。
- 関数の戻り値の型の前に
inline
を加えることで、その関数をインライン関数として宣言できる。 - インラインの指示はコンパイラへのリクエストに過ぎず、コンパイラはこのリクエストを無視することができる。
- インラインメカニズムは、規模が小さく、フローが直接で、頻繁に呼び出される関数に一般的に使用される。
- constexpr 関数は定数式に使用できる関数であり、関数の戻り値の型とすべての形参の型はリテラル型であり、関数体には 1 つの return 文しかない。
- コンパイルプロセス中に、constexpr 関数は暗黙的にインライン関数として指定される。
- constexpr 関数の戻り値は必ずしも定数である必要はない。
- インライン関数と constexpr 関数は通常ヘッダーファイルに配置される。
assert
プリプロセッサマクロ、使用法:assert (expr)
、expr を評価し、偽であれば情報を出力して実行を終了する;真であれば何もしない。- プリプロセッサ名はプリプロセッサマネージャによって管理され、コンパイラによって管理されないため、プリプロセッサ名を直接使用し、using 宣言は不要である。
assert
の動作はNDEBUG
プリプロセッサ変数の状態に依存し、#define NDEBUG
のとき、assert
は何もしない。- コンパイラはプログラムデバッグ用のいくつかのローカル静的変数を定義しており、
__func__
、__FILE__
、__LINE__
、__TIME__
、__DATE__
。
関数マッチング#
- 候補関数:同名の関数、宣言が可視。
- 実行可能関数:形参の数が等しく、形参の型が同じ。
- 最適なマッチを探す。
- どの関数も際立たない場合、コンパイラは曖昧な呼び出しのためにリクエストを拒否する。
- オーバーロード関数を呼び出す際には、強制型変換をできるだけ避けるべきである。実際のアプリケーションで強制型変換が必要な場合は、形参の集合が不合理であることを示している。
- 実引数の型変換の階級:1. 正確なマッチ;2.const 変換によるマッチ;3. 型昇格によるマッチ;4. 算術型変換またはポインタ変換によるマッチ;5. クラス型変換によるマッチ。
- 組み込み型の昇格と変換は、関数マッチング時に予期しない結果を生じる可能性がある。
- すべての算術型変換のレベルは同じである。
関数ポインタ#
- 関数ポインタはオブジェクトではなく関数を指す。
- 関数
bool lengthCompare(const string &, const string &);
、この関数を指すポインタを宣言する、bool (*pf)(const string &, const string &);
。 pf = lengthCompare;
はpf = &lengthCompare
と等価である。bool b = pf("hello","goodbye");
はbool b = (*pf)("hello","goodbye");
はbool b = lengthCompare("hello","goodbye");
と等価である。- 配列と同様に、関数型の形参を定義することはできないが、形参は関数へのポインタを指すことができ、形参は関数型のように見えるが、実際にはポインタとして扱われる;関数を実引数として直接使用することもでき、ポインタに自動的に変換される。
- 型エイリアスと
decltype
を使用して関数ポインタのコードを簡素化できる。例えばtypedef decltype(lengthCompare) Func;
は関数型を定義し、typedef decltype(lengthCompare) *FuncP;
は関数ポインタを定義する。 using F = int(int*, int);
は関数型 F を定義し、using PF = int(*)(int*, int);
は関数型へのポインタ PF を定義する。decltype
が関数に作用すると、関数型を返し、ポインタ型ではなく、ポインタを返すには明示的に*
を加える必要がある。
抽象データ型の定義#
- クラス = データ抽象 + カプセル化;データ抽象 = インターフェース + 実装。
- クラス内部で定義された関数は暗黙的に
inline
関数である。 - メンバー関数の宣言はクラス内部で行う必要があり、その定義はクラス内部でも外部でも行うことができる;インターフェースの一部としての非メンバー関数は、定義と宣言がクラスの外部にある。
- メンバー関数は、呼び出したオブジェクトへのポインタを指す追加の暗黙の引数
this
を介して呼び出される。メンバー関数を呼び出すとき、要求された関数のオブジェクトアドレスでthis
を初期化する。 this
の目的は常に「この」オブジェクトを指すため、this
は定数ポインタであり、this
に保存されたアドレスを変更することは許可されない。- メンバー関数のパラメータリストの後に
const
キーワードを置くと、続くconst
の効果は暗黙のthis
ポインタの型を変更し、this
が定数ポインタであることを示す => このようにconst
を使用するメンバー関数は定数メンバー関数と呼ばれる。 - 定数メンバー関数は、呼び出したオブジェクトの内容を変更できない。
- 定数オブジェクト、および定数オブジェクトの参照またはポインタは、定数メンバー関数のみを呼び出すことができる。
- コンパイラは最初にメンバーの宣言をコンパイルし、その後にメンバー関数体を処理する。したがって、メンバー関数体は、他のメンバーの出現順序を気にせずにクラス内の他のメンバーを自由に使用できる。
- メンバー関数の定義はその宣言と一致しなければならず、クラス外部で定義されたメンバーの名前にはその所属クラス名を含める必要がある。
return *this
は、その関数を呼び出したオブジェクトを返し、関数の戻り値の型は対応する型の参照である。- 非メンバー関数がクラスインターフェースの一部である場合、これらの関数の宣言はクラスと同じヘッダーファイル内にあるべきである。
- コンストラクタはconstとして宣言できない。
- コンパイラが作成するコンストラクタは合成デフォルトコンストラクタとも呼ばれる。
- クラスがコンストラクタを宣言していない場合にのみ、コンパイラは自動的にデフォルトコンストラクタを生成する。
- クラスが組み込み型または適合型のメンバーを含む場合、これらのメンバーがすべてクラス内の初期値を与えられている場合にのみ、このクラスは合成デフォルトコンストラクタを使用するのに適している。
= default
はコンパイラにデフォルトコンストラクタを生成するよう要求する。- コンストラクタ初期化リストは
関数名(引数リスト):
の後、{}
関数体の前にある。 - コンストラクタ初期化リストはメンバー名のリストであり、各名前の後に
()
で括られたメンバー初期値が続き、異なるメンバーの初期化はカンマで区切られる。 - あるデータメンバーがコンストラクタ初期化リストで無視されると、合成デフォルトコンストラクタと同じ方法で暗黙的に初期化される。
- 一般に、コンパイラが生成するコピー、代入、および破棄操作はオブジェクトの各メンバーに対してコピー、代入、および破棄操作を実行する。
- 動的メモリを必要とする多くのクラスは、必要なストレージスペースを管理するために
vector
オブジェクトまたはstring
オブジェクトを使用するべきであり、vector
またはstring
を使用することで、メモリの割り当てと解放に伴う複雑さを回避できる。 - クラスが
vector
またはstring
メンバーを含む場合、そのコピー、代入、および破棄の合成バージョンは正常に動作する。
アクセス制御とカプセル化#
- アクセス指定子(
public
、private
)を使用してクラスのカプセル化を強化する。 class
とstruct
キーワードの唯一の違いは、デフォルトのアクセス権が異なることであり、任意の 1 つを使用してクラスを定義できる。struct
:最初のアクセス指定子の前のメンバーはpublic
である;class
:最初のアクセス指定子の前のメンバーはprivate
である。- クラスは他のクラスや関数がその非公開メンバーにアクセスすることを許可する ->友元。
- 友元宣言は、クラス内に
friend
キーワードで始まる関数宣言文を追加するだけで済む。 - 友元宣言はクラスのインターフェースの一部としての非メンバー関数に適用される。
- 友元宣言はアクセス権を指定するだけであり、通常の意味での関数宣言ではない。クラスのユーザーが特定の友元関数を呼び出せるようにするには、友元宣言の外で関数を再度宣言する必要がある。
- 通常、クラスのヘッダーファイル内で、クラス内部の友元宣言の他に友元関数を独立して宣言する(クラスの外で)。
- 友元を集中して宣言するのが最良である。
クラスのその他の特性#
- 型を定義するためのメンバーは先に定義されてから使用される必要があるため、型メンバーは通常クラスの最初に現れる。
- クラス外部で定義された場合にのみ
inline
を指定する方が、クラスをより理解しやすくする。 - 引数の数や / または型に違いがあれば、メンバー関数をオーバーロードできる。
- クラスのデータメンバーを変更したい場合、たとえそれが
const
メンバー関数内であっても、変数宣言にmutable
を加えることで実現できる。 - クラス内初期値を提供する場合、必ず
=
または{}
を示す必要がある。 const
メンバー関数が*this
を参照として返す場合、その戻り値の型は定数参照となる。- メンバー関数が
const
であるかどうかを区別することで、オーバーロードを行うことができる。 - 推奨:公共コードに対してはプライベート機能関数を使用する -> 同じコードを複数回使用するのを避ける。
- クラスを単に宣言し、定義を一時的に省略することができる(関数のように)、例えば
class Screen;
->前方宣言。 - "宣言の後" "定義の前" のクラス型は不完全型である。
- 前方宣言はクラスのメンバーが自身の型の参照またはポインタを含む場合に適用される。
- クラスが友元クラス
friend class クラス名
を指定すると、友元クラスのメンバー関数はこのクラスのすべてのメンバーにアクセスできる、非公開メンバーを含む。 - 各クラスは自分の友元クラスまたは友元関数を制御する責任があり、友元関係には推移性がない。
- メンバー関数を友元として宣言する場合、そのメンバー関数がどのクラスに属するかを明示的に示す必要がある。例えば
クラス名::メンバー関数名
。 - あるメンバー関数を友元として指定したい場合、宣言と定義の相互依存関係を満たすようにプログラムの構造を注意深く整理する必要がある。
- あるクラスが一連のオーバーロード関数を友元として宣言したい場合、そのグループ内の各関数を個別に宣言する必要がある。
クラスのスコープ#
- クラスはスコープの 1 つであるという事実は、クラスの外部でメンバー関数を定義する際にクラス名と関数名の両方を提供する必要がある理由をよく説明している。
- クラス名に出会った瞬間、定義の残りの部分はクラスのスコープ内にある。
- 戻り値の型はどのクラスのメンバーであるかを示す必要がある(戻り値の型で使用される名前はクラスのスコープの外にある)。
- コンパイラはクラス内のすべての宣言を処理した後にメンバー関数の定義を処理する。
- クラス内でメンバーが外側のスコープの名前を使用している場合、その名前が型を表す場合、クラスはその後にその名前を再定義できない。
- クラスのメンバーが隠されている場合、クラス名を加えるか、明示的に
this
ポインタを使用してメンバーにアクセスすることができる。例えばthis->メンバー変数名
またはクラス名::メンバー変数名
。 - メンバー名をパラメータや他のローカル変数として使用しない方が良い。
- 外部スコープのオブジェクトが隠されている場合、スコープ演算子を使用してアクセスできる。
コンストラクタの再探#
- 初期化と先に定義してから代入することにはいくつかの点で大きな違いがある。
- メンバーが
const
または参照であるか、特定のクラス型に属し、そのクラスがデフォルトコンストラクタを定義していない場合、初期化する必要がある。 - コンストラクタ初期化リストを使用する習慣を身につけるべきである。
- メンバーの初期化順序は、クラス定義内に現れる順序と一致し、コンストラクタ初期化の順序をメンバー宣言の順序と一致させる方が良い。可能であれば他のメンバーを初期化するために使用することは避ける。
- あるコンストラクタがすべての引数にデフォルト実引数を提供している場合、実際にはデフォルトコンストラクタも定義していることになる。
- 委任コンストラクタは、その所属クラスの他のコンストラクタを使用して自身の初期化プロセスを実行し、そのメンバー初期化リストには唯一のエントリがあり、クラス名が続く。例えば
Sales_data(): Sales_data("", 0, 0){関数体}
。 - オブジェクトがデフォルト初期化または値初期化されるとき、デフォルトコンストラクタが自動的に実行される。これらの状況で使用するためには、クラスはデフォルトコンストラクタを含む必要がある。
- 他のコンストラクタが定義されている場合、デフォルトコンストラクタも提供する方が良い。
- コンパイラは自動的に 1 ステップのクラス型変換を実行する。
explicit
はコンストラクタの暗黙的変換を抑制するために使用でき、クラス内でコンストラクタを宣言する際にのみ使用される。explicit
は 1 つの実引数のコンストラクタにのみ有効であり、複数の実引数を必要とするコンストラクタには暗黙的変換に使用できず、指定する必要はない。explicit
キーワードでコンストラクタを宣言すると、それは直接初期化の形式でのみ使用でき、コンパイラは自動変換プロセスでそのコンストラクタを使用しない。- コンパイラは
explicit
コンストラクタを暗黙的変換に使用しないが、こうしたコンストラクタを使用して明示的に強制変換することはできる。例えばstatic_cast
。 - 集合クラス:1. すべてのメンバーが
public
である 2. いかなるコンストラクタも定義されていない 3. クラス内初期値がない 4. 基底クラスもなく、virtual
関数もない。 - 集合クラスは、波括弧で括られたメンバー初期値リストを使用して初期化できる。例えば
Data val1 = { 0, "Anna"}
、初期値の順序は宣言の順序と一致しなければならず、初期値リストの要素数がクラスのメンバー数より少ない場合、後ろのメンバーは値初期化される。 - データメンバーがすべてリテラル型の集合クラスはリテラル定数クラスである。
- もし
- データメンバーがすべてリテラル型である;
- クラスが少なくとも 1 つの
constexpr
コンストラクタを含む; - データメンバーがクラス内初期値を持つ場合、組み込み型メンバーの初期値は定数式であり、またはメンバーが特定のクラス型に属する場合、初期値はメンバー自身の
constexpr
コンストラクタを使用する; - クラスはデストラクタのデフォルト定義を使用する必要がある
ならば、それはリテラル定数クラスでもある。
constexpr
コンストラクタの本体は一般に空であり、前置キーワードを使用してconstexpr
コンストラクタを宣言できる。
クラスの静的メンバー#
static
キーワードを使用すると、メンバーはクラス自体に直接関連し、クラスの各オブジェクトとは関連付けられない。- クラスの静的メンバーは、任意のオブジェクトの外に存在し、オブジェクト内には静的データメンバーに関連するデータは含まれない。
- 静的メンバー関数は、どのオブジェクトにもバインドされず、
this
ポインタを含まず、const
として宣言することはできない。 - スコープ演算子を使用して静的メンバーに直接アクセスでき、クラスのオブジェクト、参照、またはポインタを使用して静的メンバーにアクセスすることもできる。メンバー関数はスコープ演算子を介さずに静的メンバーを直接使用できる。
- 静的メンバー関数は、クラス内でもクラス外でも定義できるが、クラス外で定義する際には
static
キーワードを繰り返してはならない。 - 各静的メンバーを定義し初期化するには、クラスの外部で行う必要がある。
- クラス名から始まる定義文の残りの部分は、すべてクラスのスコープ内にある。
- 静的データメンバーの型は、その所属クラス型であるが、非静的データメンバーはその所属クラスのポインタまたは参照としてのみ宣言できる。
- 静的メンバーと通常のメンバーのもう 1 つの重要な違いは、静的メンバーをデフォルト実引数として使用できるが、非静的メンバーはできない。なぜなら、非静的メンバーの値自体はオブジェクトの一部だからである。
IO クラス#
iostream
、fstream
、sstream
の 3 つのヘッダーファイルは、それぞれストリーム、名前付きファイル、メモリ string オブジェクトの読み書きに使用される型を定義している。- 標準ライブラリは継承メカニズムを通じて、これらの異なるタイプのストリーム間の違いを無視できるようにしている。
- IO オブジェクトをコピーしたり、代入したりすることはできない。
- IO クラスの条件状態を確認する最も簡単な方法は、ストリームオブジェクトを条件として使用することである。例えば、
while
ループで>>
式が返すストリームの状態をチェックする。 - ストリームオブジェクトの
rdstate
メンバーは、ストリームの現在の状態に対応するiostate
値を返す。 - ストリームオブジェクトの
clear
メンバーは、すべてのエラーフラグをリセットする(引数なし)か、ストリームの新しい状態を設定する(引数あり)。 - バッファのフラッシュ:データが出力デバイスまたはファイルに実際に書き込まれる。バッファがフラッシュされる理由は多くある。
endl
は改行を完了し、バッファをフラッシュする;flush
はバッファをフラッシュするが、追加の文字は出力しない;ends
はバッファに空文字を挿入し、バッファをフラッシュする。- プログラムがクラッシュした場合、出力バッファはフラッシュされない。
cout<<unitbuf;
は、各書き込み操作の後にflush
を実行するようにストリームに指示する;cout<<nounitbuf;
は通常のバッファフラッシュメカニズムに戻す。- 入力ストリームが出力ストリームに関連付けられている場合、入力ストリームからデータを読み取る操作は関連する出力ストリームを先にフラッシュする。
cout
とcin
は関連付けられている。 x.tie(&o)
はストリーム x を出力ストリーム o に関連付ける。istream
をostream
に関連付けることも、ostream
をostream
に関連付けることもできる。- 各ストリームは同時に 1 つのストリームにしか関連付けられないが、複数のストリームが同じ
ostream
に同時に関連付けられることができる。
ファイル入出力#
- ヘッダーファイル
fstream
は、ファイル IO をサポートする 3 つの型を定義している:ifstream
- 読み取り;ofstream
- 書き込み;fstream
- 読み書き。 fstream
の各種特有操作。ifstream in(ifile);
はifstream
を構築し、指定されたファイルを開く。open
を呼び出すことで、空のファイルストリームをファイルに関連付けることができる。例えばofstream out;
、out.open(ofile);
。if (out)
を使用してopen
が成功したかどうかを判断できる。- ファイルストリームを別のファイルに関連付けるには、まず既に関連付けられたファイルを閉じる必要がある。例えば
in.close()
、in.open(ifile)
。 fstream
オブジェクトが破棄されるとき、close
が自動的に呼び出される。- ファイルモード:
in
- 読み取りモード;out
- 書き込みモード;app
- 各書き込み操作の前にファイルの末尾に位置する;ate
- ファイルを開いた後にファイルの末尾に位置する;trunc
- ファイルを切り詰める;binary
- バイナリモードで IO を行う。 out
モード(ofstream
のデフォルトモード)でファイルを開くと、既存のデータが失われる。ofstream
が指定されたファイルの内容をクリアしないようにするには、同時にapp
モードを指定する必要がある。例えばofstream app("file", ofstream::out| ofstream::app);
。- ファイルを開くたびにファイルモードを設定する必要があり、そうでなければデフォルト値が使用される。
string ストリーム#
istringstream
- 読み取り;ostringstream
- 書き込み;stringstream
- 読み書き。stringstream
の各種特有操作。istringstream
とostringstream
の使用。- string ストリームは、ファイルから読み込んだ文字列を分割する際に非常に便利である。
順序コンテナの概要#
vector
- 可変サイズ配列;deque
- 両端キュー;list
- 双方向リンクリスト;forward_list
- 単方向リンクリスト;array
- 固定サイズ配列;string
-vector
に似ているが、文字を保存するために特化されている。string
とvector
は要素を連続したメモリ空間に保存する:要素のインデックスを計算するのは非常に速いが、中間位置での追加と削除は非常に時間がかかる。list
とforward_list
は、コンテナの任意の位置での追加と削除操作を非常に迅速に行えるが、要素のランダムアクセスはサポートされず、追加のメモリオーバーヘッドが大きい。deque
はより複雑で、迅速なランダムアクセスをサポートし、中間位置での追加と削除は高コストだが、両端での追加と削除は非常に速い。array
はサイズが固定されており、要素の追加や削除、コンテナサイズの変更はサポートされていない。- 現代の C++ プログラムは、原始的なデータ構造(組み込み配列など)ではなく、標準ライブラリのコンテナを使用するべきである。
- 他のコンテナを選択する良い理由がない限り、
vector
を使用するべきである。 - プログラムに多くの小さな要素があり、空間の追加オーバーヘッドが重要な場合、
list
やforward_list
を使用しないべきである。 - プログラムが要素にランダムアクセスを要求する場合、
vector
やdeque
を使用するべきである。 - プログラムがコンテナの中間で要素を追加または削除する必要がある場合、
list
やforward_list
を使用するべきである。 - プログラムがコンテナの先頭と末尾で要素を追加または削除する必要があるが、中間での追加や削除が必要ない場合、
deque
を使用するべきである。 - プログラムが入力を読み取る際にのみコンテナの中間に要素を挿入する必要があり、その後要素にランダムアクセスする必要がある場合:まず、コンテナの中間に要素を挿入する必要があるかどうかを確認し、入力データを処理する際に
vector
にデータを追加し、標準ライブラリのsort
関数を呼び出してコンテナ内の要素を再配置し、中間に要素を挿入するのを避けることができる;もし中間に要素を挿入する必要がある場合、入力段階でlist
を使用し、入力が完了したらlist
の内容をvector
にコピーすることを考慮する。 - どのコンテナを使用するか不明な場合、プログラム内で
vector
とlist
の共通操作のみを使用することができる:イテレータを使用し、インデックス操作を使用せず、ランダムアクセスを避ける。これにより、必要に応じてvector
またはlist
を選択するのが非常に便利である。
コンテナライブラリの概要#
-
各コンテナは 1 つのヘッダーファイルに定義されており、ファイル名は型名と同じである。コンテナはすべてテンプレートクラスとして定義されており、大部分のコンテナは追加の要素型情報を提供する必要がある。
-
順序コンテナはほぼ任意の型の要素を保存できる。
-
コンテナ操作:型エイリアス、コンストラクタ、代入とスワップ、サイズ、要素の追加と削除、イテレータの取得、逆コンテナの追加メンバー。
-
イテレータの範囲は一対のイテレータで表され、
[begin,end)
は左閉右開区間であり、end が begin の前にないこと、かつ同じコンテナの要素または尾後要素を指す必要がある。 -
型エイリアスを利用することで、コンテナ内の要素型を知らなくても使用でき、これはジェネリックプログラミングに非常に便利である。
-
begin
とend
操作は、コンテナ内の最初の要素と尾後要素を指すイテレータを生成し、コンテナ内のすべての要素を含むイテレータ範囲を形成する。 -
begin
とend
には複数のバージョンがある:list<string> a = {"Milton", "Shakespeare", "Austen"}; auto it1 = a.begin(); // list<string>::iterator auto it2 = a.rbegin(); // list<string>::reverse_iterator auto it3 = a.cbegin(); // list<string>::const_iterator auto it4 = a.crbegin(); // list<string>::const_reverse_iterator
-
書き込みアクセスが不要な場合は、
cbegin
とcend
を使用するべきである。 -
コンテナの定義と初期化:デフォルトコンストラクタ、コピー初期化
c1(c2)
またはc1=c2
、リスト初