プログラム言語 ポインタ

ポインタとは?

pointer_purin

pointer_memory
ポインタ=ポインタに格納されたリンク先を遠隔操作する機能

通常変数のポインタ

ポインタ変数pを宣言
int *p ;

注意
*p がポインタではない。pがポインタ
ポインタに代入するのはアドレス
宣言時の型は操作する変数の型。ポインタに代入するアドレスはint型。
(=変数マイコンピュータの型。ポインタにはメモリ番地であるint型数値が格納される)

p = (操作する変数のアドレス)

変数var のアドレスは、&var で表される。
よってポインタpにvar のアドレスを格納する場合は、
p = &var ;

変数の操作(ポインタのリンク先var を遠隔操作)
*p = 3 ;
⇒ var : 3

// ポインタ変数宣言
int *p;
int val = 10;

// valのメモリアドレスを格納
p = &val;

int *p = 10;
※エラー。ポインターには変数のアドレスしか格納できない。
リテラルを格納する事はできる(リテラルの先頭アドレスが格納される)。
プログラム言語 データ型:文字型・文字列型」参照

// 16進数表示
printf("0x%x\n", p);
// ⇒0xbdef30(メモリアドレスが表示される)
Console::WriteLine(*p);
// ⇒10

// 遠隔操作
// pのメモリアドレスの変数(=val)を操作
*p = 100;
// 16進数表示
printf("0x%x\n", p);
// ⇒0xbdef30(メモリアドレスが表示される)
Console::WriteLine(*p);
// ⇒100(メモリアドレス:同じ、値が変わっている)

サブルーチンへのポインタ渡し
int v = 10;
fnFunc(&v);
→v:11

void fnFunc(int *p)
{

 引数受け取り時の処理
 vのアドレスを格納
 p = &v

 
 アドレス元を遠隔操作
 *p += 1;
 
 cout << *p << '\n';
 →11
 
 return;
}

宣言方法
int* p
int *p
※どちらもポインタ変数。

int* p1, p2 ※p2は通常変数
int *p1, *p2 ※両方ポインタ変数

ヌルポインター
int *p = 0;
アドレスが設定されていない状態を表す

int *p = 0;
if (p == 0)
{
 cout << "アドレス未設定\n";
}
else
{
 cout << "アドレス設定済\n";
}

メモリ領域の動的確保
メモリアドレスをポインタ変数に格納
int *p = new int;
ロード時でなく実行時にメモリ領域が確保されるのでメモリの節約ができる。

メモリ領域を遠隔操作
*p = 5;

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"
 (Destination As Any, Source As Any, ByVal Length As Long)

Dim A As Long
Dim B As Long

A = 5
B = 0

VarPtr(A):Aのポインタ
VarPtr(B):Bのポインタ

'B ← A にコピー
CopyMemory VarPtr(B), VarPtr(A), Len(B)
MsgBox B
→5

my $x1 = "aaa";

#「$x1」のリファレンスを取得(メモリアドレスを取得)
my $ref = \$x1;
⇒ $ref : SCALAR(0x1111111)

デリファレンス
リファレンスを使用して対象にアクセス(遠隔操作)する事

my $x = 5;
#リファレンス取得
my $ref = \$x;
#$xの参照先アドレスを操作(デリファレンス)
$$ref *= 10;
⇒ $x : 50

ref()関数
※リファレンスか否か?を判定
my $x = 5;
my $ref = \$x; #リファレンス取得
if (ref($ref)){
  ここを通る
}

参照(リファレンス)

int i = 0;
int &r = i;
※参照変数:rにiのアドレスを格納

r = 10;
※ rはiを参照している→i=10

int ret = i;
※ret:10

サブルーチンへの参照渡し
int v = 10;
fnFunc(v);
→v:11

void fnFunc(int &p)
{
 引数受け取り時の処理
 vを参照
 &p = v

 
 参照元を遠隔操作
 p += 1;
 
 cout << p << '\n';
 →11
 return;
}

ポインタと参照の違い
ポインタ
・参照先を何度も変更できる。
・参照先アドレスを設定する場合は「&」を付ける。
参照先アドレスを表す「&」と参照変数を表す「&」は関係無し。
・参照先にアクセスする場合は「*」を付ける。

参照
・参照先を1度しか設定できない。

配列のポインタ


配列の利用については「プログラム言語 配列・リスト / 配列」参照

my @x2 = (1,2,3);

#「@x2」のリファレンスを取得(メモリアドレスを取得)
my $ref = \@x2;
⇒ $ref : ARRAY(0x1111111)

my @ary = (1,2,3);
#リファレンス取得
my $ref = \@x;

#デリファレンス
リファレンスの前に「$」または「@」を付ける
$$ref[0] *= 10;
@$ref[1] *= 10;
⇒ $ary[0] : 10
⇒ $ary[1] : 20

※いきなり配列のリファレンスを取得する書き方
my $ref = [ 1,2,3 ]
#デリファレンス
$$ref[0] *= 10;
⇒ $$ref [0] : 10
⇒ @$ref : 1023

my @ary = (1,2,3);
#リファレンス取得
my $ref = \@x;
#配列のデリファレンス
$$ref[0] *= 10; # $ary[0] : 10
${$ref}[1] *= 10; # $ary[1] : 20
$ref->[2] *= 10; # $ary[2] : 30

連想配列(ハッシュ)のポインタ


ハッシュの利用については「プログラム言語 配列・リスト / 連想配列(ハッシュ):key-value型」参照

my %x3 = ("name","yone","age",35);

「%x3」のリファレンスを取得(メモリアドレスを取得)
my $ref = \%x3;
⇒ $ref : HASH(0x1111111)

my %x = ("name","yone","age",35);
#リファレンス取得
my $ref = \%x;
#デリファレンス
$$ref{"name"} = "Mike";
$$ref{"age"} *= 10;
⇒ $x{"name"} : Mike
⇒ $x{"age"} : 350

#リファレンスをDump
print Dumper(\%x);
print Dumper($ref);
⇒{
   name => "yone"
   age => 35
 }

#いきなりハッシュのリファレンスを取得する書き方
my $ref = { "name","yone","age",35 };

#ハッシュのリファレンスを取得
my $ref = {
  name => "yone",
  age => 35
}

#ハッシュのデリファレンス
print %$ref, "\n"; # ⇒ name"yone"age35
print $$ref{name}, "\n"; # ⇒ "yone"
print ${$ref}{name}, "\n"; # ⇒ "yone"
print $ref->{name}, "\n"; # ⇒ "yone"

関数ポインタ

通常の関数では戻り値は1つのみ
main() {
 int var = func(10, 5) ;
}

int func(int x, int y) {
 return x + y ;
}

戻り値が2つ欲しい場合

int x ;
int y ;
main() {
 int var = func(10, 5) ;
}

void func(int a, int b) {
 x = a * 10 ;
 y = b * 10 ;
}

の様にグローバル変数を使う。
しかしこの方法は美しくない(分かり難く、汎用性が低く、ミスも発生しやすい)。

関数ポインタ使用例(C#)

unsafe void Main() {
 int x = 10 ;
 int y = 15 ;
 func(&x, &y) ;
 int z = x + y;
 ⇒z:250
}

unsafe void func(int *a, int *b) {
 //ポインタa にx のアドレスを格納(*a = &x)
 //ポインタb にy のアドレスを格納(*b = &y)

 *a = *a * 10 ;
 *b = *b * 10 ;

 //ポインタに格納されているアドレスのリンク先変数を遠隔操作できる
 (x = x * 10)
 (y = y * 10)
}

int main(array<System::String ^> ^args)
{  
  int x = 10;
  int y = 20;
  
  // ポインタ変数へアドレスを渡す
  swap(&x, &y);
  
  Console::WriteLine("x:" + x);
  // ⇒20
  Console::WriteLine("y:" + y);
  // ⇒10
  
  return 0;
}

// 値を入れ替える
void swap(int *a, int *b)
{
  // ↑ 引数受け取り時の動き
  a = &x;
  b = &y;
  
  int tmp = *a; (tmp = x)
  *a = *b; (x=y)
  *b = tmp; (y=tmp)
}

構造体の参照渡し
値渡しでは構造体が全てコピーされて遅くなる為、速度向上を目的としてポインタを渡す
struct MyStruct
{
  int member1 = 10;
  int member2 = 100;
  int member3 = 1000;
} myStruct;

int main(array<System::String ^> ^args)
{  
  myStruct.member1 += 1;
  myStruct.member2 += 1;
  myStruct.member3 += 1;
  
  引数:ポインタの関数へアドレスを渡す
  myFunc(&myStruct);
  
  return 0;
}

void myFunc(MyStruct *p)
{
  ↑ 引数受取時の動き
  p = &myStruct
  
  構造体ポインタのメンバーを参照する場合、アロー演算子「->」を使用する
  p->member1 += 1; // 12
  p->member2 += 1; // 102
  p->member3 += 1; // 1002
  
  return;
}

// 速度向上の為にポインタを渡すが、外部からの変更を禁止する場合
void myFunc(const MyStruct *p)
{
  ~
}

関数:fnFunc1を参照する関数ポインタ
void(*p)(int a) = fnFunc1;

関数ポインタを利用して関数を遠隔実行
p(5);

void fnFunc1(int prm)
{
 return;
}

関数:fnFunc1を参照する関数ポインタ
int(*p)(int a) = fnFunc2;

関数ポインタを利用して関数を遠隔実行
int ret = p(5);
ret:5

int fnFunc2(int prm)
{
 return prm;
}

sub subroutine {
  return 100;
}

#「subroutine」のリファレンス取得
my $ref = \&subroutine;

#デリファレンス(サブルーチン呼び出し)
my $ret = &$ref;
⇒ $ret : 100

値渡しで配列を渡す場合の不具合

my @family = qw/ fam1 fam2 fam3 fam4 /;
my @friend = qw/ fri1 fri2 fri3 fri4 /;

#配列を渡す(値渡し)
myAry(@family,@friend);

sub myAry{
  #配列を受け取る(値渡し)
  my ( @prm1 , @prm2 ) = @_;
  
  print join(',' , @prm1);
  ⇒ fam1,fam2,fam3,fam4,fri1,fri2,fri3,fri4
  print join(',' , @prm2);
  ⇒  #何も表示されない
  
  # @_ において@family と @friend の境が分からない為に、
  配列内の要素が全て@prm1に入ってしまう事が原因。

}

参照渡しによる不具合の改善

#リファレンスを渡す(参照渡し)
myAry( \@family , \@friend );

sub myAry{
  #リファレンスを受け取る(参照渡し)
  my ( $prm1 , $prm2 ) = @_;
  
  print join(',' , @$prm1);
  ⇒ fam1,fam2,fam3,fam4
  print join(',' , @$prm2);
  ⇒ fri1,fri2,fri3,fri4
}

関数への参照渡し/ポインタ渡し

ポインタ渡し
int v = 10;
fnFunc(&v);
→v:11

void fnFunc(int *p)
{
 引数受け取り時の処理
 vのアドレスを格納
 p = &v

 
 アドレス元を遠隔操作
 *p += 1;
 
 cout << *p << '\n';
 →11
 return;
}

参照渡し
int v = 10;
fnFunc(v);
→v:11

void fnFunc(int &p)
{
 引数受け取り時の処理
 vを参照
 &p = v

 
 参照元を遠隔操作
 p += 1;
 
 cout << p << '\n';
 →11
 return;
}

関数からの参照取得/ポインタ取得

戻り値の参照取得
int x = 10;
int y = 10;

int ret = fnPlus(x, y);
ret:23
x:10
y:10

int& fnPlus(int prm1, int prm2)
{
 static int ret;

 prm1 += 1;
 prm2 += 2;
 ret = prm1 + prm2;
 return ret;
}

戻り値のポインタ取得
int x = 10;
int y = 10;

int *ret = fnPlus(&x, &y);
ret:23
x:11
y:12

int* fnPlus(int *prm1, int *prm2)
{
 &prm1 = &x;
 &prm2 = &y;

 int ret;
 *prm1 += 1;
 *prm2 += 2;
 ret = *prm1 + *prm2;

 return &ret;
}

voidポインタ

int x = 10;
void *p1;
int *p2 = &x;

p2 = p1; //×
通常のポインタに、voidポインタを格納できない

p2 = static_cast(p1); //○
voidポインタを格納先ポインタの型にキャストすればOK

p1 = p2; //○
voidポインタには、通常ポインタを格納できる。

取得する値を保持。後で型を指定する。
int x = 10;
void *p = &x;

cout << *p << '\n'; //×
コンパイルエラー:voidポインターは参照先を参照できない
cout << *static_cast(p) << '\n'; //○
voidポインタを参照先ポインタの型にキャストすればOK

int v1 = 1;
char v2 = 'a';
double v3 = 1.23;

void *p;

p = &v1;
cout << *static_cast<int*>(p) << '\n';
→ 1

p = &v2;
cout << *static_cast<char*>(p) << '\n';
→ 'a'

p = &v3;
cout << *static_cast<double*>(p) << '\n';
→ 1.23

メンバーポインター

class MyClass
{
public:
 int myInt;
};

int _tmain(int argc, _TCHAR* argv[])
{

 MyClass m1;
 m1.myInt = 10;
 m1.myInt:10
 
 
 MyClass m2;
 int MyClass::*p2;
 
 p2 = &MyClass::myInt;
 m2.*p2 = 20;
 m2.myInt:20
 
 
 MyClass m3;
 int MyClass::*p3 = &MyClass::myInt;
 
 m3.*p3 = 30;
 m3.myInt:30
 
 return 0;
}

メンバー関数ポインター

class MyClass
{
public:
 int fnPlus(int prm1, int prm2){
  return prm1 + prm2;
 };
};

int _tmain(int argc, _TCHAR* argv[])
{

 MyClass m1;
 int ret = m1.fnPlus(5, 10);
 ret:15
 
 
 MyClass m2;
 int (MyClass::*f2)(int prm1, int prm2);
 
 f2 = &MyClass::fnPlus;
 ret = (m2.*f2)(15, 20);
 ret:35
 
 
 MyClass m3;
 int (MyClass::*f3)(int prm1, int prm2) = &MyClass::fnPlus;
 ret = (m3.*f3)(25, 30);
 ret:55
 
 return 0;
}

ポインタの参照先変更

int var1 ;
int var2 ;
int *p ;

p = &var1 ;
*p = 3 ;
⇒var1 : 3

p = &var2 ;
*p = 5 ;
⇒ var2 : 5

int val1 = 10;
int val2 = 20;

// ポインタ宣言
int *p;
p = &val1;
*p = 100; // val1:100

// 参照宣言(宣言と同時に初期化が必要)
// メモリアドレス未設定のままポインタを使用してシステムエラーが発生するミスを無くせる
int &refx = val1;
refx = 200; // val1:200

p = &val2;
*p = 300; // val2:300

↓エラー:参照はアドレス変更不可
&refx = val2;
refx = 400;

ポインタの問題点

struct Program {
 char* name;
 int experience;
}
Program* p = (Program*)malloc(sizeof(Program)*3);
Program構造体のポインタ変数を宣言
 Program構造体オブジェクト×3個分のメモリ領域をヒープ領域に作成
 この領域のポインタをポインタ変数pへ格納

p[0].name = "php";
p[0].experience = 1;
p[1].name = "Java";
p[1].experience = 2;
p[2].name = "C#";
p[2].experience = 3;

for(i=0; i<10; i++){
 printf(p);アドレスを表示
 printf(p=>name;
 printf(p=>experience;
 p++;ポインタをインクリメント
}

結果
a050210 php 1
a050218 Java 2
a050220 C# 3
a050228 AAA 0 ←関係ないデータが出力される
a050230 CCC 9 ←関係ないデータが出力される

アドレス

a050210 p[0] 名前 経験
a050218 p[1] 名前 経験
a050220 p[2] 名前 経験
a050228 p[3] 関係ないデータ
a050230 p[4] 関係ないデータ

・ポインタの加減によって関係無いメモリを参照できてしまう
 便利、高速の反面、バグ、悪用の原因にもなる
・ヒープ領域のオブジェクトを削除し忘れる