背景
ポインタ、それはCやC++のつまずくポイント。
具体的なポインタの説明はせず、
ただひたすらにサンプルコードを載せる。
実際に動かして、徐々に理解することにする。
今後も追記して、サンプルコードを増やすかもしれません。
実行環境
Visual Studio 2019
32 bit
1 ポインタと単一変数
単一変数:変数が1つ
&:アドレスを取得できる
#include <iostream>
int main()
{
//int型の変数
int num = 80;
//int型のポインタ変数
int* ptr;
//numのアドレスを代入
ptr = #
std::cout << "ptr " << ptr;
//ptr 010FFA90 この値は毎回変わります
}
###2 ポインタと配列
配列全体にアクセスするときは&はいらない。
#include <iostream>
int main()
{
//int型の配列
int array_[3] = { 0, 0, 0 };
//int型のポインタ変数
int* ptr;
//array_のアドレスを代入
ptr = array_;
std::cout << "ptr " << ptr;
//ptr 0055F8D8 配列の一番最初のアドレス
//std::cout << "ptr " << ;
////ptr 0055F8D8 配列の一番最初のアドレス
//std::cout << "ptr " << ptr++;
////ptr 0055F8D8 配列の一番最初のアドレス
}
###3 ポインタと配列
配列全体にアクセスするときは&はいらない。
#include <iostream>
int main()
{
//int型の配列
int array_[3] = { 0, 0, 0 };
//int型のポインタ変数
int* ptr;
//array_のアドレスを代入
ptr = array_;
std::cout << "ptr " << ptr;
//ptr 0055F8D8 配列の一番最初のアドレス
}
###4 ポインタと単一変数と配列
単一変数と配列の扱い方の確認。
#include <iostream>
int main()
{
//int型の配列
int array_[3] = { 0, 0, 0 };
//int型のポインタ変数
int* ptr_array;
//int型のポインタ変数
int* ptr;
//配列
ptr_array = array_;
//単一変数
ptr = &array_[0];
//どちらもアドレスの1番目を示す
std::cout << "ptr_array " << ptr_array << std::endl;
//ptr_array 0099FC60
std::cout << "ptr " << ptr << std::endl;
//ptr 0099FC60
}
###5 ポインタと配列と配列インクリメント
単一変数と配列の扱い方の確認。
#include <iostream>
int main()
{
//int型の配列
int array_[3] = { 0, 0, 0 };
//int型のポインタ変数
int* ptr_array;
//int型のポインタ変数
int* ptr;
//配列
ptr_array = array_;
std::cout << "ptr_array " << ptr_array << std::endl;
//ptr_array 0055F894
ptr_array++;//インクリメント
std::cout << "ptr_array " << ptr_array << std::endl;
//ptr_array 0055F898
ptr_array++;//インクリメント
std::cout << "ptr_array " << ptr_array << std::endl;
//ptr_array 0055F89C
}
0055F894 → 0055F898 → 0055F89C
16進数 4ずつずれている。
int型は4バイト。(OSなどに依存)
###6 ポインタとアスタリスト(*)
イメージとして、(-1)に(-1)を掛けて(+1)
#include <iostream>
int main()
{
//int型の変数
int num = 80;
//int型のポインタ変数
int* ptr;
//ポンタの中にnumのアドレスが代入
ptr = #
std::cout << "ptr " << ptr << std::endl;
//ptr 0073FC18
//ポインタ変数にポインタをつけると、参照したアドレスの値
//イメージとして、(-1)に(-1)を掛けて(+1)
std::cout << "*ptr " << *ptr << std::endl;
//*ptr 80
}
###7 ポインタを使って値を変更
アスタリスト(*)とポインタ変数で値を変更。
#include <iostream>
int main()
{
//int型の変数
int num = 80;
std::cout << "num " << num << std::endl;
//num 80
//int型のポインタ変数
int* ptr;
//ポンタの中にnumのアドレスが代入
ptr = #
//アドレスの先の値に800を代入
*ptr = 800;
std::cout << "ptr " << ptr << std::endl;
//ptr 001CFCE8
std::cout << "*ptr " << *ptr << std::endl;
//*ptr 800
std::cout << "num " << num << std::endl;
//num 800
}
###8 ポインタと配列
#include <iostream>
int main()
{
//int型の配列
int array_[3] = { 0, 10, 100 };
int array_len = sizeof(array_) / sizeof(int);
//int型のポインタ変数
int* ptr_array;
//配列
ptr_array = array_;
for (int i = 0; i < array_len; i++) {
std::cout << "*ptr_array "<< *ptr_array <<std::endl;
ptr_array++;
}
/*
*ptr_array 0
*ptr_array 10
*ptr_array 100
*/
}
###9 変数が書き込まれているアドレスのアドレスを取得
何を言っているのかさっぱり分からない人がいると思います。
アニメ銀魂でいうと、たしか桂小太郎が言った
「リーダーを決めるリーダーを決めよう」
とニュアンスが似ていると思います。
#include <iostream>
int main()
{
//int型の配列
int num = 1;
int* ptr_num;
//ポインタ変数にch変数のアドレスを代入
ptr_num = #
std::cout << "ptr_ch " << ptr_num << std::endl;
//ptr_ch 00F3FEA4
std::cout << "*ptr_ch " << *ptr_num << std::endl;
//*ptr_ch 1
int** ptr_ptr_ch;
ptr_ptr_ch = &ptr_num;
std::cout << "ptr_ptr_ch " << ptr_ptr_ch << std::endl;
//ptr_ptr_ch 00F3FE98
std::cout << "ptr_ptr_ch " << *ptr_ptr_ch << std::endl;
//ptr_ptr_ch 00F3FE98
}
###10 2変数の値をポインタで交換
何を言っているのかさっぱり分からない人がいると思います。
アニメ銀魂でいうと、たしか桂小太郎が言った
「リーダーを決めるリーダーを決めよう」
とニュアンスが似ていると思います。
//tmp←x ,x←y, y←tmp
void swap_(int* ptr_x, int* ptr_y) {
//仮の変数
int tmp;
// ptr_xのアドレスの値(*ptr_x)をtmpに代入
tmp = *ptr_x;
// ptr_yのアドレスの値(*ptr_y)をptr_xに代入
*ptr_x = *ptr_y;
// tmpの値をptr_xの値に代入
*ptr_y = tmp;
}
int main()
{
//交換する2変数
int x = 2;
int y = 3;
std::cout << "x : " << x << ", y : " << y << std::endl;
//xとyの値を渡すのではなく、xとyのポインタを渡す
//xとyの変数のアドレスを代入
swap_(&x, &y);
std::cout << "x : " << x << ", y : " << y << std::endl;
/*
x : 2, y : 3
x : 3, y : 2
*/
}
追記 2020/10/17
#include <iostream>
void swap_arry(int* xy) {
//仮の変数
int tmp;
tmp = xy[0];
xy[0] = xy[1];
xy[1] = tmp;
}
int main()
{
//配列値を交換
int xy[] = {2,3};
std::cout << "xy["<< xy[0] << "], xy[" << xy[1] << "]"<< std::endl;
swap_arry(xy);
std::cout << "xy["<< xy[0] << "], xy[" << xy[1] << "]"<< std::endl;
/*出力結果
xy[2], xy[3]
xy[3], xy[2]
*/
}
追記 2020/10
結果を知ることで、逆説的に理解をする。
今回はC言語。C++で実行してもできるはず。
#include <stdio.h>
int main(){
static int N = 1;
static int* pN;
static int** ppN;
printf("N=%d\n", N);
printf("メモリアドレス &N=%p\n", &N);
printf("メモリアドレス &pN=%p\n", &pN);
printf("メモリアドレス &ppN=%p\n", &ppN);
pN=&N;
printf("*pN=%d\n", *pN);//渡されたメモリアドレスの値
ppN=&pN;
printf("*ppN=%d\n", **ppN);//渡されたメモリアドレスのメモリアドレスの値
N = 2;
printf("N=%d\n", N);
printf("*pN=%d\n", *pN);//渡されたメモリアドレスの値
printf("*ppN=%d\n", **ppN);//渡されたメモリアドレスのメモリアドレスの値
*pN=3;
printf("N=%d\n", N);
printf("*pN=%d\n", *pN);//渡されたメモリアドレスの値
printf("*ppN=%d\n", **ppN);//渡されたメモリアドレスのメモリアドレスの値
**ppN=4;
printf("N=%d\n", N);
printf("*pN=%d\n", *pN);//渡されたメモリアドレスの値
printf("*ppN=%d\n", **ppN);//渡されたメモリアドレスのメモリアドレスの値
return 0;
}
出力結果
N=1
メモリアドレス &N=0x7f4f2fbad010
メモリアドレス &pN=0x7f4f2fbad020
メモリアドレス &ppN=0x7f4f2fbad028
*pN=1
*ppN=1
N=2
*pN=2
*ppN=2
N=3
*pN=3
*ppN=3
N=4
*pN=4
*ppN=4
変数もポインタ変数もダブルポインタ変数もメモリアドレスが存在する。これを知るだけでも大事。
またポインタの使い方をいったん公式として覚えておくのもあり。
数学においても問題を解くためには、先に暗記したほうがいいケースもありますよね。
ポインタで値を書き換えるときは要注意。。。
参考サイト
サンプルコードは以下を参考にまとめました。神動画です。
1.5倍速でのC、C++入門 その6~多次元配列、ポインタ~
こちらのサイトは、PC内のメモリの挙動を分かりやすく説明しています。
[【プログラミング講座】第38回 C言語のメモリとポインタについて【独り言】]
(https://www.youtube.com/watch?v=4IkLk-KGizA)
メモリとポインタの関係を図示で分かりやすく解説しています。
アドレス演算子&:メモリのアドレスを取得する演算子
ポインタ:変数名を使わずにアドレスを使ってメモリにアクセスする手法
ポインタ = アドレス + 領域の型(ポインタの型,intなど)
【新しいC言語講座】ポインタの基本(1)
【新しいC言語講座】ポインタの基本(2)
【新しいC言語講座】ポインタを関数の引数に渡す