思い出そうC++
C++のキーワード(63個:36頁)
- asm C++プログラムに直接アセンブリ言語コードを埋め込むことが出来る
- auto ローカル変数を宣言する(ただし、デフォルトでautoと見なされるので、事実上このキーワードを使うことはまれ)
- const_cast constと(または)volatile属性を明示的に除去する(この機能は危険なので取り扱いに注意が必要)
例)関数内でconst int*型の仮引数の値を変更
int a(const int* x) {
int* v = const_cast<int*>(x);
*v = 100;
return x
}
とすれば、関数の仮引数ではconst指定されている引数に対しても変更が加えられる。
- dynamic_cast ポリモーフィックな型のポインタまたは参照に対してキャストを行う(*D -> *B, *B -> *D (if *B is actually *D))(dynamic_castの項参照)
- enum 名前付き整数定数を定義する(注:Java5.0でも新たにenumキーワードが有効になった)
- explicit コンストラクタの黙示的な変換を禁止する
解説)コンストラクタの仮引数が1個の場合、C++は以下の構文を許可する。
class A {
int a;
public:
A(int x) {
a = x;
}
}
int main() {
A class_1 = 1; // 第1の方法
A class_2(1); // 第2の方法
}
しかし、キーワードexplicitは、第1の方法を禁止する。
- export 分離コンパイル(宣言をヘッダファイルに書き、本体をソースファイルに書くこと)を行いたいクラス・関数テンプレートに対して指定する。
しかし、このキーワードをサポートするコンパイラはまだ少ない。
- extern グローバル変数の仮宣言をする
- inline 関数が呼び出し形式ではなく、実際の処理の中に埋め込まれるようにコンパイラに指示する。
- mutable mutableとして定義されたメンバ変数は、constメンバ関数内からでも変更できる。
- reinterpret_cast
ある型を根本的に別の型にキャストする。
- static 関数内部での変数(ローカル変数)の宣言に用いた場合、関数終了時にも値が破棄されなくなる。
関数外部での変数(グローバル変数)の宣言に用いた場合、グローバル変数のスコープをそのファイル内に限定する。
- static_cast 非ポリモーフィックなキャストを行う。伝統的なキャストと同様である。
- typeid オブジェクトの型を取得する
- typename テンプレートの汎用型を指定する際のキーワード(classキーワードでも同じことが出来る)
- volatile 変数の値が、プログラムで明示的に指定していない方法(例えばマルチスレッドにおける別のスレッドから)でも変更される可能性があることをコンパイラに知らせ、コンパイラによってその変数の処理が最適化されるのを防ぐ。
C++の変数:(41頁)
- ローカル変数 関数内部で宣言された変数
- 仮引数 関数の宣言部で宣言された変数
- グローバル変数 関数の外側で宣言された変数(どの関数からも参照できる)
型修飾子:(45頁)
char, int, doubleは型の前に、signed, unsigned, long, shortの型修飾子をもつことができる。
(32ビット幅での一般的な幅と範囲)
型 |
ビット幅 |
一般的な範囲 |
char |
8 |
-128 から 127 |
unsigned char |
8 |
0 から 255 |
signed char |
8 |
-128 から 127 |
int |
32 |
-2,147,483,648 から 2,147,483,647 |
signed int |
32 |
-2,147,483,648 から 2,147,483,647 |
unsigned int |
32 |
0 から 4,294,967,295 |
short int |
16 |
-32,768 から 32,767まで |
signed short int |
16 |
-32,768 から 32,767まで |
unsigned short int |
16 |
0 から 65,535 |
long int |
32 |
-2,147,483,648 から 2,147,483,647 |
signed long int |
32 |
-2,147,483,648 から 2,147,483,647 |
unsigned long int |
32 |
0 から 4,294,967,295 |
float |
32 |
3.4E-38 から 3.4E+38 |
double |
64 |
1.7E-=308 から 1.7E+308 |
long double |
80 |
3.4E-4932 から 1.1E+4932 |
bool |
|
true または false |
wchar_t |
16 |
0 から 65,535 |
演算子の優先順位:(59頁)
高優先度 |
! |
|
> >= < <= |
|
== != |
|
&& |
低優先度 |
|| |
式内部での型変換:(59頁)
式の中で異なる型の定数と変数が混在すると、これらは同じ型に変換される。
C++コンパイラは、全てのオペランドの型を最も大きいオペランドの型に「上位」変換する。
char ch;
int i;
float f;
double d;
double result;
ch=10;
i=15;
f=2.5;
d=3.5;
result = (ch/i) + (f*d) -
(f+i);
int double float
| | |
|---------------| |
double |
| |
|----------------------|
double
結果は-8.75。Javaでも同じ。
配列:(90頁)
C++では、配列の宣言と定義を同時に行う。
int array[10];
C++では文字列の最後の要素は常に’\0’(ヌル文字)でなくてはならない。
よって、下記はコンパイルできない。
(悪い例)
char
hello[5] = "hello";
これは、
hello[0] = ‘h’
hello[1] = ‘e’
hello[2] = ‘l’
hello[3] = ‘l’
hello[4] = ‘o’
とすると、最後に’\0’を格納できなくなるためである。
文字列操作関数:(97頁)
(代表的な関数)
-
strlen(char*)
文字列の長さを返す。
正確に言うと、char*の割り当てられたメモリ内で先頭から数えて最初に’\0’を見つけた位置の先頭からのオフセットを返す。
例)Hello
St\0ring => 8
-
strcpy(char*, const char*)
2番目の引数に指定された文字列を1番目の引数に指定されたアドレスにコピーする。
正確に言うと、2番目の引数に指定された文字列内で、最初に’\0’を見つけるまでの文字列を、1番目の引数に割り当てられているアドレスの先頭にコピーする。
例)Hello
St\0ring => Hello St\0
-
strcat(char* const char*)
2番目の引数に指定された文字列を1番目の引数に指定されたアドレスに追加する。
正確に言うと、2番目の引数に指定された文字列内で、最初に’\0’を見つけるまでの文字列を、1番目の引数に割り当てられているアドレス内で最初に’\0’を見つけた場所以降にコピーする。
-
strcmp(const char*, const char*)
1番目の引数が2番目の引数よりもASCIIコード順で大きければ、正の値を返す。小さければ、負の値を返す。等しければ0を返す。
注)VC++
.NETでは引数に文字列リテラルを与えたときに思うような結果が得られなかった。一度文字配列もしくは文字列ポインタに代入してから出ないと上手く行かないようだ。
ヌル終端文字:(102頁)
すべての文字の値は非0であり、ヌル終端文字のみ0の値をとる。
具体的には、’\0’がヌル終端文字である。
また、全ての文字列は必ずヌル終端文字で終わらなくてはならず、以下の文は誤りである。
char hello[5] = “Hello”;
これだと、
hello[0] = H
hello[1] = e
hello[2] = l
hello[3] = l
hello[4] = o
しか格納できず、(最後にヌル終端文字が格納できないので)境界エラーを起こす。
よって、
char hello[6] = “Hello”;
が正解である。
ポインタ演算子:(116頁)
&:オペラントのアドレスを返す演算子
*:オペラントに指定したアドレスにある変数の値を返す演算子
ポインタと配列:(123頁)
以下のポインタ演算による文字列トークン化プログラムは完全に有効である。
char
str[80] = "This is a token appication!";
char*
p = 0;
char*
q = 0;
//
p @ str
//
p != '\0'
//
p++
for
(p = str; *p; p++) {
// q stay @p
// p != ' ' || '\0'
for (q = p; (*p != ' ' && *p); p++) {
;
}
*(p)
= '\0'; // OK? -> This is OK but the original literal is
overwritten.
cout
<< q << endl;
}
cout << str <<
endl;
ヌルポインタの慣行:(133頁)
ポインタは常にヌルポインタで初期化する癖をつけよう。そして、ヌルポインタは使わないようにしよう。
char*
p = 0;
if (!p) {
// エラー処理
}
ポインタの無謀な使用:(135頁)
以下は無効なポインタ操作の一例である。絶対にしてはならない。
Ø 初期化されていないポインタ
初期化されていない(メモリが割り当てられていない)ポインタの値をセットしようとする。
Ø 無効なポインタ比較
異なる配列変数を指すポインタ間でのポインタ演算は無効。
Ø ポインタのリセット忘れ
ある繰り返し処理で用いたポインタをリセットせずに繰り返し使い続け、いつか境界をまたいでしまいアクセス違反を起こす。
配列を使った関数呼び出し:(153頁)
以下の関数プロトタイプはいずれもコンパイラにより3番目の宣言に変換される。
void display(int num[10]);
void display(int num[]);
void display(int *num);
つまり、実行時には配列の先頭のアドレスがdisplay()関数に渡される。
引数渡し:(176頁)
C++の引数渡しの方法は2つある。値渡しと参照渡しである。
渡せるのは、値そのもの、ポインタ、そして参照仮引数の3つである。
このうち、ポインタによる参照渡しの実現は、厳密に言うと値渡しに他ならない。関数の仮引数にアドレスの値をコピーしているだけだからである。
しかし、参照仮引数による参照渡しは本物の参照渡しである。int型等の仮引数を関数内部で変更してもその変更は関数外部にも影響を及ぼす。
参照仮引数を使うと、引数の(値ではなく)アドレスが関数に自動的に渡される。関数内部では、参照仮引数に対する操作が自動的に間接参照されるので、ポインタ演算子を使う必要がない。
参照用法の制約:(188頁)
- 参照変数を参照することは出来ない
- 参照の配列を作成することは出来ない
- 参照へのポインタを作成することは出来ない。つまり、参照には&演算子を適用できない
- ビットフィールドには参照が使えない
構造体ポインタと日付時刻関数:(252頁)
/**
* 基本関数 time_t time(time_t *currtime); は現在のカレンダ時間を返す
* 引数には、nullポインタもしくはtime_t*を指定する。time_t*を指定した場合、
* 引数に渡した変数に値をセットすることも出来る。
*
またVC++では typedef long time_tである。
*/
time_t t1;
t1 = time(&t1);
cout << t1 <<
endl;
/**
* カレンダ時間を分解時間に変換する場合はlocaltime()を使う。
* struct
tm *localtime(const time_t *currtime);
*
*/
tm* tt =
localtime(&t1);
cout <<
tt->tm_sec << endl;
cout <<
tt->tm_min << endl;
cout <<
tt->tm_hour << endl;
cout <<
tt->tm_mday << endl;
cout <<
tt->tm_mon << endl;
cout <<
tt->tm_year << endl;
cout <<
tt->tm_wday << endl;
cout <<
tt->tm_yday << endl;
cout <<
tt->tm_isdst << endl;
コピーコンストラクタの作成と用法:(320頁)
以下の局面では、オブジェクトをビットコピー(ハードコピー)としてではなく、明示的に初期化された(ソフトコピー)が必要となる。(さもないと、メモリアロケーション等で不具合が生ずる可能性がある)
- 宣言文の中で、あるオブジェクトの初期化に別のオブジェクトが使われたとき
- オブジェクトが関数への仮引数として渡されたとき
- 関数からの戻り値として一時的なオブジェクトが作成されたとき(注:これは関数内のローカルオブジェクトとも呼び出し側で確保されたオブジェクトのどちらともことなる)
演算子のオーバーロード:(330頁)
メンバ関数を使って2項演算子をオーバーロードすると、演算子の左側のオブジェクトが演算子関数(operator#())を呼び出し、そのオブジェクトが黙示的に関数に渡される(具代的には、thisポインタで参照できる)。また、右側のオブジェクトは仮引数としてその関数に明示的に引き渡される。
インクリメント演算子のオーバーロード:(335頁)
前置き形式のプロトタイプ:Class Class::operator++()
後置き形式のプロトタイプ:Class Class::operator++(int notused) // notusedは関数内で使われることはない
[]演算子のオーバーロード:(351頁)
C++では、[]演算子は2項演算子と見なされる。プロトタイプは以下。
type class::operator[](int index);
または、
type &class::operator[](int index);
参照を返すと、以下のような記述(すなわち代入演算子の左側に添え字付きのオブジェクトを置く)も可能である。
object[i] = 10;
このような場合、オーバーロードの実装は例えば、
int &operator[](int index) {
retrun array[index];
}
と、メンバである配列の参照を返すことで実現できる。
()演算子のオーバーロード:(355頁)
任意の数の仮引数を渡すことが出来、また任意の型の値を返すことが出来る演算子関数を作成する。
オーバーロード関数内ではthisポインタも使える。
dynamic_cast:(507頁)
C++ではRTTI(Run-Time Type Identification、実行時型識別)を行うのにdynamic_castキャスト演算子を用いる。
例)
dynamic_cast<class_name>(pclass)
注1) class_nameはターゲットの型のポインタもしくは参照でなければならない。
注2) pclassはソース型のポインタもしくは参照でなければならない。
注3) キャストに失敗すると、nullポインタが返される(よって、これでキャストが成功したかどうかをチェックすればよい)
注4) VisualC++ではデフォルトでRTTIが有効になっていない。よって、以下のオプションを有効にする必要がある。