趣旨
ポインタの理論をいくら読んでもすぐ忘れてしまって混乱するので自分の環境で弄り倒して体で覚えてしまって忘れるのを防ぐ目的で記事に残します。
以下の記事を参考に僕が試してみたいものを全部試して結果を残しておき、備忘録にします。有用な記事を残してくださって感謝です。
ポインタ基礎
#include <iostream>
using namespace std;
int main
{
int num = -99;
// output of the value
cout << num << endl; // -99
// output of the address
cout << &num << endl; // 00000021535CF674
// pointer declaration
int* intptr;
intptr = #
cout << intptr << endl; // 00000021535CF674
// dereference
cout << *intptr << endl; // -99
*intptr = 5;
cout << *intptr << endl; // 5
cout << * intptr << endl; // 5
cout << num << endl; // 5
cout << intptr << endl; // 00000021535CF674
cout << &num << endl; // 00000021535CF674
cout << &num + 1 << endl; // 000000E9CC8FF7E8
cout << intptr + 1 << endl; // 000000E9CC8FF7E8
cout << *intptr + 1 << endl; // 6
cout << *&num << endl; // 5
cout << *&num + 1 << endl; // 6
cout << &*&*&num << endl; // 00000021535CF674
}
値渡し参照渡しポインタ渡し
#include <iostream>
using namespace std;
void getOrder(int& donuts)
{
cout << "how many donuts do you want: ";
cin >> donuts; // say cin is 1 here
cout << "okay you want " << donuts << " donuts" << endl;
cout << donuts << endl; // 1
cout << &donuts << endl; // 0000009E0B74F7A4
donuts = donuts + 1;
cout << *&donuts << endl; // 2
}
void getOrderD(int donuts)
{
cout << "how many donuts do you want:" ;
cin >> donuts; // say cin is 1 here
cout << "okay you want " << donuts << " donuts" << endl;
cout << donuts << endl; // 1
cout << &donuts << endl; // 0000009E0B74F720
donuts = donuts + 1;
cout << *&donuts << endl; // 2
}
int main()
{
int jellyDonuts;
getOrder(jellyDonuts);
cout << "After getOrder: " << jellyDonuts << endl;
// After getOrder: 2
int daniDonuts = 0;
getOrderD(daniDonuts);
cout << "After getOrderD: " << daniDonuts << endl;
// After getOrderD: 0
}
配列とポインタの関係
#include <iostream>
using namespace std;
int main()
{
int vals[] = { 4, 7, 11 };
// declaration
int* valptr;
valptr = vals; // equals to &vals[0]
cout << &valptr << endl; // 0000001B780FF618
cout << valptr << endl; // 0000001B780FF5E8
cout << valptr + 1 << endl; // 0000001B780FF5EC
cout << valptr + 2 << endl; // 0000001B780FF5F0
cout << *valptr << endl; // 4
cout << *(valptr + 1) << endl; // 7
cout << *(valptr + 2) << endl; // 11
// vals implies it is the beginning of the address
cout << vals << endl; // 0000001B780FF5E8
cout << *vals << endl; // 4
cout << vals[0] << endl; // 4
cout << vals[1] << endl; // 7
cout << vals[2] << endl; // 11
cout << &vals << endl; // 0000001B780FF5E8
cout << &vals[0] << endl; // 0000001B780FF5E8
cout << &vals[1] << endl; // 0000001B780FF5EC
cout << &vals[2] << endl; // 0000001B780FF5F0
}
関数からポインタを返す
- ユーザーが指定したサイズの整数配列を動的に生成します
- その配列を乱数で埋めます
- 配列の内容を表示します
- 使用後の配列をメモリから解放します
#include <iostream>
#include <ctime> // Include for time()
using namespace std;
int* getRandomNumbers(int num)
{
if (num <= 0) return NULL;
int* array = new int[num];
for (int count = 0; count < num; count++)
array[count] = rand();
return array;
}
int main() {
srand(static_cast<unsigned int>(time(0))); // Cast to avoid warning
int size = 10; // Example size
int* numbers = getRandomNumbers(size);
if (numbers != NULL) {
for (int i = 0; i < size; i++)
cout << numbers[i] << " ";
cout << endl;
delete[] numbers; // Remember to delete the dynamically allocated array
}
return 0;
}
ポインタの重要性
戻り値としてポインタを使用することの重要性は、関数外でも確保されたメモリ領域にアクセスできるようにするためです。new 演算子によってヒープ上に確保されたメモリは、関数が終了しても解放されずに残ります。したがって、このメモリ領域を操作するためのポインタを関数の外部に渡すことができます。
ポインタを使用して動的に割り当てられたメモリを管理する場合、そのメモリを使用後に適切に解放する責任があります。この例では、main 関数内で delete[] numbers; を呼び出して配列を削除し、メモリリークを防いでいます。
乱数生成器を初期化
srand() を呼び出すことでこの問題を解決することができます。srand() を適切な場所で一度だけ呼び出すことで、乱数生成器の初期シードを設定し、その後の rand() 呼び出しで異なる乱数列が生成されるようになります。
srand 関数はシード値として unsigned int 型の引数を取ります。time(0) が返す time_t 型は通常、システム依存で、unsigned int よりも大きいサイズを持つことが多いです(たとえば 64ビットのシステムでは time_t は64ビット整数です)。time_t の値をそのまま srand に渡すと、その型の違いにより警告が発生する可能性があります(特に型が異なる場合、値が切り捨てられるなどのデータ損失の可能性があるため)。これを避けるために、static_cast を用いて明示的に型変換を行い、time_t 型から unsigned int 型への変換を安全に行います。
decltype と関数ポインタの使用方法
初見では何を言ってるのかわからなかったのでコードの中に直接コメントとして解説を組み込みます。
#include <iostream>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
decltype(&add) funcPtr;
/*
decltype(&add) を使用しています。
&add は関数 add のアドレスを表し、decltype はその式の型、
つまり int (*)(int, int) を funcPtr の型として推論します。
これは、2つの int 型の引数を取り、int 型を返す関数を指すポインタの型です。
*/
funcPtr = add;
std::cout << "10 + 5 = " << funcPtr(10, 5) << std::endl;
/*
funcPtr に add 関数を割り当て、そのポインタを使って add 関数を呼び出します。
結果として 10 + 5 の計算が行われ、15 が出力されます。
*/
funcPtr = subtract;
std::cout << "10 - 5 = " << funcPtr(10, 5) << std::endl;
/*
同様に funcPtr に subtract 関数を割り当て、10 - 5 の計算を行います。
結果として 5 が出力されます。
このように、decltype と関数ポインタを組み合わせることで、
コードの柔軟性と再利用性を高め、関数の動的な切り替えを容易に行うことができます。
*/
return 0;
}
なぜdecltype(&subtract)をしなくてもいいのかというと、これらの関数は同じ引数の型(二つの int)を取り、同じ戻り値の型(int)を返すからです。
int (*)(int, int) は、「int 型の引数を2つ取り、int 型を返す関数へのポインタ」を表します。
decltype(&add) は、add 関数のアドレスを取ることからその型を評価しますが、評価された型は int (*)(int, int) です。
この型は subtract 関数にも当てはまります。
つまり、両関数は型が一致しているため、decltype(&add) で評価された型を subtract 関数に使用する関数ポインタ funcPtr でも問題なく動作するのです。