ポインタと参照の決定的な違いの一つは、指す先を後から変更できるかどうかというところにあります。
#include <iostream>
using namespace std;
int main() {
// 参照
int m = 1;
int n = 2;
int& intref = m;
intref = n; // 指す先 m の値を変更しているだけ
cout << "m: " << m << ", " //=> 2
"n: " << n << ", " //=> 2
"intref: " << intref << endl; //=> 2
// ポインタ
int mm = 1;
int nn = 2;
int* intptr = &mm;
intptr = &nn; // 指す先を変更できる
cout << "mm: " << mm << ", " //=> 1 (変更されていない)
"nn: " << nn << ", " //=> 2
"intptr: " << *intptr << endl; //=> 2
return 0;
}
同様にポインタと配列の決定的な違いの一つも、指す先を後から変更できるかどうかというところにあります。
#include <iostream>
using namespace std;
int main() {
int arr[8];
int* ptr;
int n[8];
ptr = n; // ← これは問題ないですが
// arr = n; // ← これはエラーです。
return 0;
}
ポインタと配列のもう一つの重要な違いは、sizeof 演算子が返す値が配列全体のサイズであるか、ポインタ自身がアドレスを格納している領域のサイズであるか、というところにあります。
#include <iostream>
using namespace std;
int main() {
int arr[8];
int* ptr;
ptr = arr;
cout << sizeof arr << endl; //=> 32
cout << 8 * sizeof(int) << endl; //=> 32
cout << sizeof ptr << endl; //=> 4
cout << sizeof (int*) << endl; //=> 4
return 0;
}
そのため、一旦ポインタに入れると、配列サイズが取得できなくなるという事実が有名です。関数の引数で配列の先頭アドレスを渡すときは、サイズも渡すか、もしくは配列に (文字列のヌルターミネータのように) ターミネータを付与しておく必要があります。これは有名なはまりどころです。
#include <iostream>
using namespace std;
int main() {
int arr[8];
int* ptr;
ptr = arr;
cout << sizeof arr / sizeof *arr << endl; //=> 8
cout << sizeof arr / sizeof arr[0] << endl; //=> 8 と同じ意味です
cout << sizeof ptr / sizeof *ptr << endl; //=> 1
cout << sizeof(int*) / sizeof(int) << endl; //=> 1 と同じ意味です
return 0;
}
文字列リテラルに関しても配列とポインタの違いをみることができます。
#include <iostream>
using namespace std;
const char* MyFunc() {
// ポインタの文字列リテラル
// - 静的に確保されたデータのため return してもよいです。
// (関数を抜けた後に領域が解放されないためです)
return "MyFunc";
// 配列の文字列リテラル
// - 後述の通り静的データではないため関数を抜けた時点で
// 値の存在は保証されません。return してはなりません。
// char str[] = "MyFunc";
// return str;
}
int main() {
// ポインタの初期化
// - 静的変数と同様に、プログラム実行時に既にメモリ領域が
// 確保されており、main() 時には内容も初期化済みです。
const char* ptr = "main"; // 注意: その内容も書き換えてはいけない仕様です。
// (const を使用しましょう)
cout << ptr << endl; //=> MyFunc
cout << MyFunc() << endl; //=> main
// 配列の初期化
// - 静的なデータとはなりません
char str[] = "main";
// char str[] = {'m', 'a', 'i', 'n', '\0'}; // と同じ意味です
str[0] = 'M'; // 内容の変更も許されています
cout << str << endl; //=> Main
return 0;
}
ポインタの宣言を複数同時にやろうとすると意図しない宣言になります。
#include <iostream>
using namespace std;
int main() {
// int* p, q; // qはintになってしまいます。
int* p; // 一行ずつ分けて
int* q; // 宣言しましょう。
int n = 8;
p = &n;
q = &n;
cout << *p << endl
<< *q << endl;
return 0;
}
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30};
cout << &arr[2] - &arr[0] << endl; //=> 2 となります。これは、
cout << *(&arr[1] + 1) << endl; //=> 30 が
cout << arr[2] << endl; //=> 30 と等しくなるという事実を知っていると余計に混乱しますが仕様です
return 0;
}
バイトとは char 型のサイズのことです。通常は8ビットです。
#include <iostream>
using namespace std;
int main() {
cout << sizeof(char) << endl; //=> どのような環境でも常に 1 です
return 0;
}
char型の変数用のメモリに格納された内容を、10進数の正の数としてコンソール出力するためにはやや複雑なキャストが必要です。
#include <iostream>
using namespace std;
int main() {
char ch = 'a';
// 1. char型は負の数として扱われる可能性があるため unsigned char にキャスト
// 2. cout は unsigned char を文字として表示してしまうため int にキャスト
cout << (int)(unsigned char)ch << endl; //=> 97
return 0;
}
文字コードに関して '0' - '9' は連続していることが保証されています。そのため '0' を減することで文字を数値に変換できます。
#include <iostream>
using namespace std;
int main() {
char ch = '1';
int n = ch - '0';
cout << ch << endl; //=> 1 (文字として)
cout << n << endl; //=> 1 (数値として)
return 0;
}
ただし、アルファベットについては連続していることが保証されておらず、文字セットによっては「'A' <= ch && ch <= 'Z'」などとできません。そのため以下のようにします。
#include <iostream>
#include <cctype> // isupper, islower, isdigit
using namespace std;
int main() {
char ch;
cin >> ch;
cout << isupper(ch) << endl; // A-Z ならば真
cout << islower(ch) << endl; // a-z ならば真
cout << isdigit(ch) << endl; // '0'-'9' ならば真 ('0' <= ch && ch <= '9' と同じ)
return 0;
}