|
|
アドレス演算子(Address Operator)
int x;
|
&x
|
解答
#include <iostream>
using namespace std; int main() { int x; cout << "Address = " << &x; } |
実行結果
参照とはある変数の別名を持った変数であり,次の構文で宣言される.
データ型& 参照する名前 = 変数名;
|
int n = 44; int& rn = n; |
解答
#include <iostream>
using namespace std; int main() { int n = 44; int& rn=n; //rnはnの参照 cout << "n = " << n << ", rn = " << rn << endl; -n; cout << "n = " << n << ", rn = " << rn << endl; rn *= 2; cout << "n = " << n << ", rn = " << rn << endl; } |
実行結果
参照は宣言されたときに,初期化されなければならない.しかし,定数の初期化と違い,参照は変数を用いて初期化されなければならない.つまり,
int& rn = 44;
|
練習問題 7..1 int &rn = 44とすると,どんなエラーがでるか確かめよ.
|
C++では,参照演算子&は2つの異なる目的に利用される.オブジェクト名の前に用いられると,そのオブジェクトのアドレスを意味する.しかし,型Tの後に用いられると"Tへの参照型"という派生型の意味になる.例えば,int&と書くと整数型への参照型となる.つまり,上の例題で
int& rn = n;
|
練習問題 7..2
参照の使い方
例題7.2のプログラムを実行せよ. |
参照演算子&は変数のメモリアドレスを返す.このアドレスを格納できる変数の型をポインタという.ポインタ変数は"型Tへのポインタ"という派生型を持っていて,T*と表される.例えば,整数型の変数のアドレスを格納するポインタ変数はint*の型を持っている.
&xで得られたアドレスを保持できるポインタ変数pを定義すると
int* p;
|
解答
#include <>iostream>
using namespace std; int main() { int n; int* pn; n = 10; pn = &n; cout << "nのアドレス =" << pn << endl; } |
実行結果
変数nは10に初期化され,そのアドレスは0x22fedcである.変数pnはnのアドレスを値として初期化された.したがって,pnの値は0x22fedcとなり,確かに,pnはnのアドレスを値として持っている変数であることが分かる.
間接参照演算子(Dereference Operators)
pnがnのポインタであるとき*pnは,ポインタ変数pnが指し示す内容(この場合,変数nの値)を直接pnから得ることができる. この「*」を間接参照演算子(dereferencing operator)と呼ぶ.
練習問題 7..3 上の例題で,pnの値はいくつか.プログラムを書いて確かめよ.
|
解答 変数nのアドレスは&nで取り出すことができる.また,pnをnのポインタ変数とすると,つまり,pnはnのアドレスを格納する変数とすると,pnはnのアドレスを頼りに,変数nの値を表す.つまり,pnはnの別名である.では,プログラムを作成しよう.
#include <iostream>
using namespace std; int main() { int n = 10; int* pn = &n; cout << "nのアドレス =" << pn << endl; cout << "nの値 =" << *pn << endl; } |
実行結果
間接参照演算子によって,pnが指し示す場所の内容.つまり,nの値10が得られた.
ポインタへのポインタ(Pointers to Pointers)
ポインタは別のポインタを指すことができる.
==============================================
#include iostream
using namespace std;
int main()
{
int n = 10;
int pn = &n;
cout "nのアドレス =" pn endl;
cout "nの値 =" *pn endl;
int ppn = &pn;
cout "ppn =" ppn endl;
cout "&ppn =" &ppn endl;
cout "*ppn =" *ppn endl;
cout "**ppn =" **ppn endl;
}
==============================================
実行結果
変数ppnはpnを指し,pnはnを指す.したがって,*ppnはpnの別名で,*pnはnの別名となる.したがって,**ppnはnの別名である.
参照と間接参照は表裏(Referencing is the opposite of Dereferencing)
解答 pnはnのアドレスを格納し, rpnはpnの別名に注意すると,
==============================================
#include iostream
using namespace std;
int main()
{
int n = 10;
int pn = &n;
cout "nのアドレス =" pn endl;
cout "nの値 =" *pn endl;
int& rpn = pn;
cout "rpn =" rpn endl;
cout "&rpn =" &rpn endl;
}
==============================================
実行結果
変数pnはnを指し,rpnは*pnの参照によって初期化されている.つまり,rpnはnの別名ということである.したがって,rpn = 10となる.
練習問題 7..4
参照,間接参照
int n = 44; int* pn = &n; int& rn = n; int m = (*pn)++; int* q = pn - 1; r = *(–pn) + 1; ++*q; のとき,n,m,&n, *pn, rn, *qの値をプログラムを組まずに求めたあと,プログラムを書いて確認せよ.ただし,&n = 0x22fedcとする. |
派生型(Derived Types)
参照演算子&と同様に,間接参照演算子*も2つの異なる目的に用いられる.オブジェクトの前に用いられている場合,つまり,オブジェクトへのポインタとして用いられるときは,そのオブジェクトの持っている値を表す.また,型Tの後ろに用いられる場合は,"Tへのポインタ"型を表す.
C++には次のような5つの派生型がある.
const int C = 33; | // 整数型の定数 |
int& rn = n; | // 整数型への参照 |
int* pn = n; | // 整数型へのポインタ |
int a[] = {33, 44}; | // 整数型の配列 |
int f() = {return 55;} | // 整数型の戻り値 |
派生型はどの型からも作ることができるので,いろいろな組み合わせが可能である.
int* const pn = 44; | // 整数型の定数ポインタ |
const int* pn = &n; | // 整数型定数へのポインタ |
const int* const pn = &n; | // 整数型定数への定数ポインタ |
float& a[] = {x,y}; | // フロートへの2つの参照配列 |
float* a[] = {&x,&y}; | // フロートへの2つのポインタ配列 |
ポインタと配列(Pointers and Arrays)
C++ではポインタと配列の間に強い関係がある.配列の添え字を使って実行できるような操作は,ポインタでできるし,ポインタを使うほうが一般に高速である.しかし,初心者にとっては分かりにくいといわれている.どうだろうか.
宣言
int a[10];
|
は,大きさが10の配列aを定義するものである.これはa[0],a[1],…a[9]という名の10個連続したオブジェクトからなるブロックと考えることができる. a[i]と書くと,先頭からi番目の位置の配列要素を参照する.
paが整数へのポインタだとすると,
int* pa;
|
pa = &a[0];
|
int x = *pa;
|
次に, *(pa + 1)はどんなことを意味するのだろうか.
まず,paはa[0]を指し示している.そして,配列の要素が整数型なら,pa+1はpa+1の値を整数型のサイズ分だけ増やす.したがって,pa+1はa[1]を指し示す.よって,*(pa+1)はa[1]の内容を参照する.また,pa+1はa[1]のアドレスである.
pa = &a[0];
|
pa = a;
|
new演算子(new Operator)
ポインタが
float* p;
|
*p = 2.7182;
|
float x = 2.7182;
float* p = &x; cout << *p; |
この問題を回避するもう一つの方法は,ポインタ自身へメモリを明示的に割り当てる方法である.これはnew 演算子を用いて行う.
float* q;
q = new float; *q = 2.7182; |
new演算子は,メモリ上の割り当てられていないフロート型(浮動小数点型)のサイズsバイトのアドレスを返す.そのアドレスをqに代入する*qは他の変数により使われていることはない.
float* q;
q = new float; |
float* q = new float;
|
float* q = new float(2.7182);
|
練習問題 7..5 次のコードの間違いを見つけよ.
int* p = new int; int* q = new int; cout " p = " p ", p + q =" p + q endl; |
delete演算子(delete Operators)
delete演算子はnew演算子と反対のことを行う.つまり,割り当てられたメモリを開放する.delete演算子はnew演算子と対で用いる.例えば,
float* q = new float(2.7182);
delete q; *q = 2.7182; |
定数へのポインタは削除してはいけない. |
const int* p = new int;
delete p; |
delete演算子を基本型(char, int, float, double, etc)に用いることは推奨できない.
動的配列(Dynamic Arrays)
静的結合
float a[20];
float* const p = new float[20];
と宣言すると,aとpは20個のfloat型の要素を持つ配列への定数ポインタである.これらのポインタは,例えプログラム実行中一度も使われなくても,コンパイル時にメモリに確保される.これを静的結合という.
動的結合
float* p = new float[20];と宣言すると,pは20個のfloat型の要素を持つ動的配列へのポインタである.ポインタpはメモリの確保をプログラムが実行されるまで延期する.このようにして宣言された配列を動的配列(dynamic array)という.
解答
int n = 44; により変数nはint型でその値は44となる
int* p = &n; によりpはnへのポインタとなる
++(*p);により(*p)つまりnの値を1増やす
++p;によりポインタpの値,つまりnのアドレスを1増やす
int* const cp = &n;によりcpはnへの定数ポインタ
++(*cp);により(*cp)つまりnの値を1増やす
++cp;によりポインタcpを1増やすといいたいが,cpは定数なので不可能
ポインタの配列(Array of Pointers)
|
|
解答
#include <iostream>
using namespace std; void print(double* a[],int); void sort(double* a[],int); int main() { double* a[8]; a[0] = new double(55.4); a[1] = new double(22.1); a[2] = new double(66.5); a[3] = new double(44.3); a[4] = new double(99.8); a[5] = new double(88.7); a[6] = new double(77.6); a[7] = new double(33.2); print(a,8); sort(a,8); print(a,8); } void sort(double* a[], int n) { double* temp; for (int i=1; i < n; i++){ for(int j=0;j<n-i;j++){ if(*a[j] > *a[j+1]) { temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } } } } void print(double* a[], int n) { for (int i=0;i < n;i++){ cout << *a[i] << " "; } cout << endl; } |
実行結果
このプログラムでは,ポインタの移動を行うことにより,値の移動を行っている.
ポインタと関数引数(Pointers and Function Argument)
C/C++では関数に対して,引数を値で渡すから,呼ばれた関数のほうで,呼び出した関数内の変数を直接変更することはできないことはすでに学んだ.また,標準C++汎用アルゴリズムとしてのswap()関数を用いて,実引数の値を変更することもすでに学んだ.さらには,練習問題と練習問題でアドレス渡しおよび参照を用いたswap()関数の作成を行なった人もいるだろうが,もう一度,ユーザ関数としてのswap()関数を作成する.
解答
呼び出し側のプログラムで変更すべき値のアドレスを渡すようにする必要がある.つまり,
swap(&a,&b);
演算子&は変数のアドレスを与えるから,&aはaへのポインタである.ポインタ引数を使えば,関数の中でそれを呼んだ関数の中のオブジェクトをアクセスし変更することが可能になる.次に示すように,swap自身内では引数はポインタとして宣言され,実際の被演算数はそのポインタを通して間接的にアクセスされる.
#include <stdio.h>
void swap(int *,int *); //プロトタイプ宣言 int main() { int a,b; printf("整数を2つ入力してください\n"); scanf("%d %d",&a ,&b); if(a < b){ swap(&a,&b); } printf("%d %d",a,b); } void swap(int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; } |
実行結果
解答 C++では,コンパイラに指示してポインタ値による呼び出しコードを自動的に生成させ,関数の特定なパラメター(値ではない)に参照で値を渡すことができる.それには,関数宣言の中で,パラメター名の前に&を付ければよい.
void swap(int&x, int& y);
でパラメターは参照パラメターで与えられている.
呼び出しはswap(a,b)と実引数を指定する.
#include <iostream>
using namespace std; void swap(int &x,int &y); int main() { int a,b; cout << "整数を2つ入力してください" << endl; cin >> a >> b; if(a < b){ swap(a,b); } cout << a << " " << b << endl; } void swap(int &x, int &y) { int temp; temp = x; x = y; y = temp; } |
実行結果
関数へのポインタ(Pointer to Function)
配列の名前が定数ポインタであるように,関数の名前も定数ポインタである.関数へのポインタは関数名のアドレスを値とするポインタである.関数名自身がポインタなので関数へのポインタは定数ポインタへのポインタである.例えば,
int f(int); // 関数fの宣言
int (*pf)(int); // 関数へのポインタpfの宣言
pf = &f; // fのアドレスをポインタpfに代入
つまり,図で表すと次のようになる.
解答 関数fへのポインタpfと整数nを引数に持つユーザ関数sum()を考える.つまり,
int sum(int (*pf)(int), int n);を考える.この関数を呼び出すには,
sum(f, n);が必要となる.
==============================================
int sum(int (*)(int), int);
int square(int);
int main()
{
cout sum(square,4) endl;
}
int sum(int (*pf)(int), int n)
{
int s=0;
for(int i = 1; i = n; i++)
s += (*pf)(i);
return s;
}
int square(int k)
{
return k*k;
}
==============================================
実行結果
1. 変数のアドレスはどのように呼び出すことができるか.
2. ポインタ変数に格納されたアドレスを持っている変数の値はどのようにして呼び出すことができるか.
3. 次の宣言の違いを説明せよ.
int n1 = n;
int& n2 = n;
4. 次の&の使い方の違いを説明せよ.
int& r = n;
p = & n;
5. 正しいか間違っているか理由を述べよ.
a. x == yならば &x == &y
b. x == yならば *x == *y
6. 次のコードはなぜいけないのか.
a. int& r = 22;
b. int* p = &44;
c. char p = & c;
7. 次のコードは何がいけないのか.
short a[20];
for(int i=0; i 20; i++)
*a++ = i*i;
8. 次のコードは何がいけないのか.
float x = 3.1415;
float* p = &x;
short d = 44;
short* q = &d;
p = q;
9. pとqがint型のポインタでnはint型のとき,どちらが可能か.
p + q;
p - q;
10. 次の事柄を宣言しなさい.
a. 8個のfloat型を要素にもつ配列
b. float型へのポインタを要素にもつ配列
c. 8個のfloat型を要素にもつ配列へのポインタ
d. 8個のfloat型へのポインタを要素にもつ配列へのポインタ
1. double型の配列をコピーする関数をポインタを用いて作成せよ.
2. Riemann和を用いて関数 を積分するriemann()関数を実装せよ.
3. 関数 のにおける微分係数を数値微分を用いて計算する関数を作成せよ.
4. riemann()関数を用いて次の関数を指定された範囲で積分せよ.
a. sqrt() [1,4]
b. [0, ]
c. [0,1]
d. [1,e]
5. 2分法で方程式を求めるため,次の関数を完成せよ.
double root(double (*pf)(double), double a, double b, int n)
ここで,pfはfのポインタである.