Rainbow Engine

IT技術を分かりやすく簡潔にまとめることによる学習の効率化、また日常の気付きを記録に残すことを目指します。

C++

ポインタと参照の違いについてサンプルPGを使ってご紹介

投稿日:2022年2月2日 更新日:

 

<目次>

(1) ポインタと参照の違いについてサンプルPGを使ってご紹介
 (1-1) ポインタと参照の概要
 (1-2) 両者の違い①:宣言/初期化
 (1-3) 両者の違い②:再代入
 (1-4) 両者の違い③:メモリ領域
 (1-5) 両者の違い④:間接参照
 (1-6) 「ポインタ」の補足
 (1-7) 「ポインタ」と「参照」の主な使いどころ

(1) ポインタと参照の違いについてサンプルPGを使ってご紹介

(1-1) ポインタと参照の概要

基本的には両方とも、ある変数が別の変数にアクセスするために使用します。
 

●ポインタ

「ポインタ」は、ある変数の「メモリアドレス」を格納するための変数です。

ポインタは「*」演算子を用いてデリファレンス(※注1)します。
 

●参照

「参照」は、既に存在している、ある変数の「別名」を格納するための変数です。
 
内部的には対象となる変数のアドレスを保持する点はポインタと似ていますが、一度値を代入したら変更できない点から、ポインタの定数版のような動きになっています。
 
(図111)

(※注1)デリファレンスとは?
まずリファレンスが変数が格納されているメモリのアドレス値の事です(≒ポインタ)。一方で、リファレンスが指すメモリに格納された値そのものを取得する事をデリファレンスと呼びます。
 

(1-2) 両者の違い①:宣言/初期化

それぞれの初期化のお作法は以下の通りです。
 

●ポインタ

ポインタ「*ptr1」にint型変数「i1」のアドレス「&i1」を代入します。
int i1 = 10;
int *ptr1 = &i1;

//# あるいは以下のように分けて記述可能
//# (OK↓)
//# int *ptr1;
//# ptr1 = &i1;

●参照

参照「&rfr1」にint型変数「i1」を代入します。
int i1 = 10;
int &rfr1 = i1;

//# ちなみに、参照の場合は変数定義と代入を分けて書くのはNG
//# (NG↓)
//# int &rfr1;
//# rfr1 = i1;

●サンプルプログラム

 
(サンプル)
#include <time.h>
#include <iostream>
using namespace std;

int main ()
{

    int i1 = 10;

    //# ポインタの宣言/初期化
    int *ptr1 = &i1;

    //# (1)&ptr1: ポインタ自体のアドレス
    //# (2)*ptr1: ポインタが参照するアドレスに格納された値(デリファレンス)
    //# (3) ptr1: ポインタが参照するアドレス値(リファレンス)
    cout << &ptr1 << " - " << *ptr1 << " - " << ptr1 << endl;

    //# 参照の宣言/初期化   
    int &rfr1 = i1;

    //# (4)&rfr1: 参照先のアドレス
    //# (5)*&rfr1: 参照先のアドレスに格納された値(デリファレンス)
    //# (6) rfr1: 参照先の値
    cout << &rfr1 << " - " << *&rfr1 << " - " << rfr1 << endl;

    //# (参考) 「*rfr1」はエラーとなります(値10に対するデリファレンスのため)
    
    return 0;

}
(図121)

(実行結果)上記プログラムの(1)~(6)の通りの値が出力されている
0x7ffc225682b8 - 10 - 0x7ffc225682c4
0x7ffc225682c4 - 10 - 10
(図122)

目次にもどる

(1-3) 両者の違い②:再代入

ポインタは再代入して使えるのに対して、参照は再代入できません(コンパイルエラーになる)。
 

●ポインタ

ポインタは値の再代入が可能です。これは、LinkedList(値と次要素のアドレスを連結した配列)等の実装などで役に立ちます。

int a1 = 9;
int a2 = 7;
int *ptr;
ptr = &a1;
ptr = &a2;
 

●参照

int a1 = 9;
int a2 = 7;
int &rfr = a1;
int &rfr = a2;  //# この行でエラーになる

●サンプルプログラム

(サンプル)
#include <time.h>
#include <iostream>
using namespace std;

int main ()
{
        
    int a1 = 9;
    int a2 = 7;
    
    //### ポインタ
	//# ポインタ定義
    int *ptr;
	//# 代入:「a1」のアドレスを代入
    ptr = &a1;
    cout << &ptr << endl << &a1 << endl;
	//# 再代入:「a2」のアドレスを代入
    ptr = &a2;
    cout << &ptr << endl << &a1 << endl;
    
    //### 参照
    //# 参照定義&「a1」を代入
    int &rfr = a1;
    cout << &rfr << endl << &a1 << endl;
	
    //# 参照定義&「a2」を代入(▲コンパイルエラー)
    int &rfr = a2;  //# この行でエラーになる
    cout << &rfr << endl << &a1 << endl;
    
    return 0;

}
 
(図131)

(実行結果)→参照に再代入しようとしたためコンパイルエラー

[admin@ik1-411-37776 tmp_rainbow]$ g++ -g -o ./ReferenceVsPointer2 ./ReferenceVsPointer2.cpp
./ReferenceVsPointer2.cpp: In function 'int main()':
./ReferenceVsPointer2.cpp:20:10: error: redeclaration of 'int& rfr'
     int &rfr = a2;
          ^
./ReferenceVsPointer2.cpp:18:10: error: 'int& rfr' previously declared here
     int &rfr = a1;

(図132)

目次にもどる

(1-4) 両者の違い③:メモリ領域

次はメモリ領域に関する、ポインタと参照の違いです。

●ポインタ

元の変数とは別にメモリ領域を使用します。以下のコードで「&ptr」がポインタ自身のアドレス、「&a1」はポインタが指し示す変数のアドレスで、両者は異なる値になっています。

int a1 = 9;
int *ptr = &a1;
//# 異なるアドレスになる
cout << &ptr << endl << &a1 << endl

●参照

元の変数と同じアドレスを使用します(一部スタック領域も使用します)。そのため、参照のアドレスである「&rfr」と、参照先の変数のアドレス「&a1」は同じ値になっています。

int a1 = 9;
int &rfr = a1;
//# 同じアドレスになる
cout << &rfr << endl << &a1;

●サンプルプログラム

(サンプル)

#include <time.h>
#include <iostream>
using namespace std;

int main ()
{
    int i1 = 10;

    //# ポインタの宣言/初期化
    int *ptr1 = &i1;
	
    //# (1)&ptr1: ポインタ自体のアドレス
    //# (2)&il: 変数のアドレス
    //# →異なるアドレスになる
    cout << &ptr1 << endl << &i1 << endl;

    //# 参照の宣言/初期化
    int &rfr1 = i1;

    //# (3)&rfr: 参照自体のアドレス
    //# (4)&il: 変数のアドレス
    //# →同じアドレスになる
    cout << &rfr1 << endl << &i1 << endl;
    
    //# <想定結果>
    //# (1)≠(2)=(3)=(4)
    
    return 0;

}

(図141)

(実行結果)

0x7ffc98463118
0x7ffc98463124
0x7ffc98463124
0x7ffc98463124

(図142)

目次にもどる

(1-5) 両者の違い④:間接参照

間接参照とは、例えば「ポインタのポインタ」といった具合に、参照を複数回連鎖的につなげる事を言います。ポインタではこれが出来ますが、参照では出来ません。
例えば、ポインタは「**ptr2」のように「*」を2つ繋げて「ポインタのポインタ」を定義できますが、参照は「&&rfr2」のように「&」を2つ繋げる事は出来ません(NG)。

●ポインタ

int a1 = 10;
int *ptr1 = &a1;
int **ptr2 = &ptr1;

●参照

int a1 = 10;
int &rfr1 = a1;
int &&rfr2 = rfr1;  //# エラーになる

●サンプルプログラム

(サンプル)

#include <time.h>
#include <iostream>
using namespace std;

int main ()
{
    
    int a1 = 10;
    
    //### ポインタは多重にする事が可能
    //# ポインタを定義(OK)
    int *ptr1 = &a1;
    cout << &ptr1 << endl << &a1 << endl;
    //# ポインタのポインタを定義(OK)
    int **ptr2 = &ptr1;
    cout << &ptr2 << endl << &a1 << endl;
    
    //### 参照は多重に出来ない
    //# 参照を定義(OK)
    int &rfr1 = a1;
    cout << &rfr1 << endl << &a1 << endl;
    //# 参照の参照を定義(NG)
    int &&rfr2 = rfr1;  //# エラーになる
    cout << &rfr2 << endl << &a1 << endl;
    
    return 0;

}

(図151)

(実行結果)

./ReferenceVsPointer4.cpp: In function 'int main()':
./ReferenceVsPointer4.cpp:18:9: error: expected unqualified-id before '&&' token
     int &&rfr2 = rfr1;  //# Error at this line
         ^
./ReferenceVsPointer4.cpp:19:14: error: 'rfr2' was not declared in this scope
     cout << &rfr2 << endl << &a1 << endl;

(図152)

目次にもどる

(1-6) 「ポインタ」の補足

(※注1)ポインタ演算とは?
C++ではポインタに対して、int型の「足し算」や「引き算」をする事が可能です。もしポインタ「ptr1」がint型のアドレスを保持している場合、「ptr1+1」は「ptr1」の後の次のint型のアドレスを指し示します。

(例)

int  var[SIZE] = {100, 1000, 10000};
int  *ptr1;
ptr1 = var;
ptr1++; //# *ptr1 = 0xceb099c0
ptr1++; //# *ptr1 = 0xceb099c4
ptr1++; //# *ptr1 = 0xceb099c8

この例では「ptr1++」する毎に「ptr1」が指すアドレスが「0xceb099c0」→「0xceb099c4」→「0xceb099c8」のように変化していきます(int型は4バイトのため、0→4→8)。

目次にもどる

(1-7) 「ポインタ」と「参照」の主な使いどころ

●ポインタの使いどころ
・ポインタ演算をしたり、NULL値を代入する必要があるケースで使用します
(例:配列など。配列へのアクセスはポインタ演算で実装されている)

LinkedListといったデータ構造を実装する際に使用します。
→異なるアドレスを指し示すためには、ポインタが必要となる(再代入なども必要となるため参照ではNG)

●参照の使いどころ
・関数の「引数」や「戻り値」で使用します
(再代入やNULL代入などが必要ない引数等においては、参照を使う事ができます)

目次にもどる

Adsense審査用広告コード


Adsense審査用広告コード


-C++

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

CORBA通信のPOA(Portable Object Adapter)とは?

  <目次> (1) CORBA通信のPOA(Portable Object Adapter)とは?  (1-1) 概要と特徴  (1-2) 一般的なPOAの処理構成  (1-3) POAの …

C++のlocaltime関数がスレッドアンセーフである理由と使用上の注意点

  <目次> (1) C++のlocaltime関数がスレッドアンセーフである理由と使用上の注意点  (1-1) localtime関数がスレッドアンセーフである理由  (1-2) loca …

C++をLinuxでコンパイルする方法について

  <目次> (1) C++をLinuxでコンパイルする方法について  (1-1) STEP0:【事前準備】ターミナル機能の準備(ターミナル、Teraterm、Putty)  (1-2) S …

ValgrindのStill Reachableの意味や実際のサンプルをご紹介

  <目次> (1) ValgrindのStill Reachableの意味や実際のサンプルをご紹介  (1-1) Valgrindの「Still Reachable」はどんな状況?  (1 …

C++で変数にアスタリスク(*)が2つ付いている意味について

  <目次> (1) C++で変数にアスタリスク(*)が2つ付いている意味について  (1-1) 変数にアスタリスク(*)が2つ付いている意味  (1-2) 構文  (1-3) サンプルプロ …

  • English (United States)
  • 日本語
Top