新規記事の投稿を行うことで、非表示にすることが可能です。
2017年09月25日
《その47》 関数間の配列の受渡し
関数に配列を渡す際は、その要素数を別の引数として別途渡す必要がある。 (p.260)
例えば、値を返さない関数 func に 配列 int a[10] を渡すものとします。その際に必要なものは、配列の先頭要素へのポインタaと要素数10です。これらを、引数として渡します。
具体的には、呼出し側の関数から、関数 func を
func(a, 10);
のようにして呼び出します。
配列を受け取る側の関数 funcでは、この a と 10を
void func(int p[], int n); あるいは
void func(int *p, int n);
の形式で書かれた p と nで受け取ります。
多次元配列を受け取る関数は、最も先頭の添字に相当する n次元の要素数のみが可変であり、(n - 1)次元以下の要素数は定数である。 (p.264)
例えば、値を返さない関数 func に 配列 int a[5][3] を渡すものとします。
配列 a[5][3] は、int型の値3つでできたint[3]型の要素5個でできています。この場合の渡す情報(引数)は、配列の先頭要素へのポインタaと要素数 5です。
具体的には、呼出し側の関数から、関数 func を
func(a, 5);
のようにして呼び出します。
配列を受け取る側の関数 funcでは、この a と 5を
void func(int p[][3], int n); あるいは
void func(int (*p)[3], int n);
の形式で書かれた p と nで受け取ります。呼び出される側の関数は、要素型がint[3]型であることを予め知っていなければなりません。
したがって、呼び出される側の関数func は int[3]型の要素しか受け取れません。受け取る要素の型は決まってしまいますが、その一方で、受け取る要素の数は自由です。
ちょっと分かりにくいのが、int[3]へのポインタ型を
int (*p)[3]
で表すという部分ですが、(;^△^)これは慣れるしかなさそうですね。
--
2017年09月24日
《その46》 ポインタと配列
原則として、配列名は、その配列の先頭要素へのポインタと解釈される。 (p.254)
あまりピンとこないので、自分なりに少し確認してみます。
int a[10];
の場合なら、a は 配列aの先頭要素a[0] へのポインタであるということです。
ポインタの値は、そのポインタが指しているオブジェクトのアドレスなので、
cout << a << '\n';
cout << &a[0] << '\n';
は、同じアドレスを表示するはずです。
&配列名は、「配列全体」へのポインタとなります。 (p.254)
&a は配列a全体を指すポインタということです。つまり、&a の値は、配列aというオブジェクトのアドレスです。
cout << &a << '\n';
は、どんなアドレスを表示するのか・・・。
配列aの先頭要素a[0] へのポインタ と 配列a全体へのポインタ は、同じアドレスのような気もするし、違うのかも という気もします。
プログラムで確認してみました。
#include <iostream>
using namespace std;
int main()
{
int a[10];
cout << "a :" << a << '\n';
cout << "&a[0]:" << &a[0] << '\n';
cout << "&a :" << &a << '\n';
cout << "sizeof(*a) :" << sizeof(*a) << '\n';
cout << "sizeof(*&a):" << sizeof(*&a) << '\n';
}

a と&a[0] は当然ですが、同じ値でした。
そして、&a も同じ値でした。この結果は、やっぱりな という気もしますが、自分としては、なんとなく理解しづらいところもあります。
でも、素直に納得することにします。
sizeof(*a) は int型整数a[0] の大きさで、sizeof(*&a) は 配列全体(int型整数10個分)の大きさなので、
sizeof(*a) が 4で、sizeof(*&a) が 40というのは、スッキリする結果ですね。
配列名が、sizeof演算子およびtypeid演算子のオペランドとして現れたときは、配列全体の大きさや情報を生成します。 (p.254)
上の例でいえば、sizeof(a)は 40ということになります。このときの a は、配列の先頭要素へのポインタではありません。
--
《その45》 関数呼出しとポインタ(p.253演習7-2,演習7-3)
新版明解C++入門編 p.253 演習7-2
List 6-15(p.220)の関数 swap を、参照渡しではなく、ポインタの値渡しによって行うように変更したプログラムを作成せよ。
// p253_演習7-2
#include <iostream>
using namespace std;
void swap(int* x, int* y)
{
int t = *x;
*x = *y;
*y = t;
}
int main()
{
int a, b;
cout << "整数a:"; cin >> a;
cout << "整数b:"; cin >> b;
swap(&a, &b);
cout << "------\n";
cout << "整数a … " << a << '\n';
cout << "整数b … " << b << '\n';
}

新版明解C++入門編 p.253 演習7-3
List 6-16(p.222)の関数 sort を、参照渡しではなく、ポインタの値渡しによって行うように変更したプログラムを作成せよ。
// p253_演習7-3
#include <iostream>
using namespace std;
void swap(int* x, int* y)
{
int t = *x;
*x = *y;
*y = t;
}
void sort(int* a, int* b, int* c)
{
if (*a > *b) swap(a, b);
if (*b > *c) swap(b, c);
if (*a > *b) swap(a, b);
}
int main()
{
int a, b, c;
cout << "変数a:"; cin >> a;
cout << "変数b:"; cin >> b;
cout << "変数c:"; cin >> c;
sort(&a, &b, &c);
cout << "------\n";
cout << "変数a … " << a << '\n';
cout << "変数b … " << b << '\n';
cout << "変数c … " << c << '\n';
}

--
《その44》 アドレス演算子と間接演算子を適用した式の評価(p.249演習7-1)
まだ慣れていないので忘れそうな気がするため、いちおう書いておくことにします。
ポインタを宣言するときの
*を書く位置は
int *p = &x; // C の一般的なスタイル
int* p = &x; // C++の一般的なスタイル
のどちらでもよい。
int* p, q; // int* p; int q; の意味になる。
int* p; int* q; // 両方とも int*型にする場合の宣言は、このように書く。
新版明解C++入門編 p.249 演習7-1
List 7-2(p.244)に &ptr の表示を追加したプログラムを作成せよ。
// p249_演習7-1
#include <iostream>
using namespace std;
int main()
{
int n = 135;
cout << "n :" << n << '\n';
cout << "&n :" << &n << "番地\n";
int* ptr = &n;
cout << "ptr :" << ptr << "番地\n";
cout << "*ptr:" << *ptr << '\n';
cout << "&ptr:" << &ptr << "番地\n"; // ←演習7-1の指示
cout << "\n以下は付け足し\n";
cout << "*&ptr: " << *&ptr << "番地\n";
cout << "**&ptr:" << **&ptr << '\n';
}

--
2017年09月23日
《その43》 関数の多重定義(p.233演習6-20,演習6-21),インライン関数(p.235演習6-22)
新版明解C++入門編 p.233 演習6-20
二つのint型変数 a, b の最小値、三つのint型変数 a, b, c の最小値を求める、以下に示す多重定義された関数群を作成せよ。
int min(int a, int b);
int min(int a, int b, int c);
// p233_演習6-20
#include <iostream>
using namespace std;
int min(int a, int b)
{
return a < b ? a : b;
}
int min(int a, int b, int c)
{
int min = a;
if (b < min) min = b;
if (c < min) min = c;
return min;
}
int main()
{
int x, y, z;
cout << "xの値:"; cin >> x;
cout << "yの値:"; cin >> y;
cout << "xとyの最小値は" << min(x, y) << '\n';
cout << "zの値:"; cin >> z;
cout << "x, y, zの最小値は" << min(x, y, z) << '\n';
}

新版明解C++入門編 p.233 演習6-21
short型整数xの絶対値、int型整数xの絶対値、・・・を求める、以下に示す多重定義された関数群を作成せよ。
short absolute(short x);
int absolute(int x);
long absolute(long x);
float absolute(float x);
double absolute(double x);
long double absolute(long double x);
プログラムがちょっと長くなってしまいますが、多重定義された関数群の中のどれが使用されたのかが分かるようにしました。
例えば、int absolute(int x) が使われた場合は、「int用の関数が使われました。」と表示されます。
// p233_演習6-21
#include <iostream>
using namespace std;
short absolute(short x) {
cout << " short "
<< "用の関数が使われました。\n 絶対値は ";
if (x < 0) x *= -1; return x;
}
int absolute(int x) {
cout << " int "
<< "用の関数が使われました。\n 絶対値は ";
if (x < 0) x *= -1; return x;
}
long absolute(long x) {
cout << " long "
<< "用の関数が使われました。\n 絶対値は ";
if (x < 0) x *= -1; return x;
}
float absolute(float x) {
cout << " float "
<< "用の関数が使われました。\n 絶対値は ";
if (x < 0) x *= -1; return x;
}
double absolute(double x) {
cout << " double "
<< "用の関数が使われました。\n 絶対値は ";
if (x < 0) x *= -1; return x;
}
long double absolute(long double x) {
cout << " long double"
<< "用の関数が使われました。\n 絶対値は ";
if (x < 0) x *= -1; return x;
}
int main()
{
cout << "① short\n";
short a = -1234 ; cout << absolute(a) << '\n';
cout << "② int\n";
int b = -1234 ; cout << absolute(b) << '\n';
cout << "③ long\n";
long c = -1234 ; cout << absolute(c) << '\n';
cout << "④ float\n";
float d = -1.234; cout << absolute(d) << '\n';
cout << "⑤ double\n";
double e = -1.234; cout << absolute(e) << '\n';
cout << "⑥ long double\n";
long double f = -1.234; cout << absolute(f) << '\n';
}

新版明解C++入門編 p.235 演習6-22
xの2乗を求めるインライン関数、3乗を求めるインライン関数を作成せよ。
inline double square(double x);
inline double cube(double x);
// p235_演習6-22
#include <iostream>
using namespace std;
inline double square(double x) {
return x * x;
}
inline double cube(double x) {
return x * x *x;
}
int main() {
double x;
cout << "実数値 : "; cin >> x;
cout << "2乗は " << square(x) << '\n';
cout << "3乗は " << cube(x) << '\n';
}

--
2017年09月22日
《その42》 参照を返却する関数(p.231演習6-19)
新版明解C++入門編 p.231 演習6-19
List 6-21 の関数rを、不正な添字に対して安全に動作するものに書きかえよ。静的記憶域期間をもつint型の変数を関数内部で定義して、idxが 0以上a_size未満でなければ、その変数への参照を返却すること。
「静的記憶域期間をもつint型の変数を関数内部で定義せよ」という指示なので、配列aの要素を1つ増やし、それを充てることにしました。
つまり、配列aの要素は a[0], a[1],・・・, a[a_size - 1] ですが、もうひとつ 要素a[a_size]を用意してそれを使うということです。
// p231_演習6-19
#include <iomanip>
#include <iostream>
using namespace std;
const int a_size = 5;
int& r(int idx)
{
static int a[a_size + 1];
if (idx > a_size || idx < 0)
idx = a_size;
return a[idx];
}
int main()
{
for (int i = -5; i < a_size + 5; i++)
r(i) = i;
for (int i = -5; i < a_size + 5; i++)
cout << "r(" << setw(2) << i << ") = " << r(i) << '\n';
}

--
《その41》 記憶域期間(p.229演習6-16,演習6-17,演習6-18)
新版明解C++入門編 p.229 演習6-16
静的記憶域期間をもつ配列の全要素が 0で初期化されることを確認するプログラムを作成せよ。
// p229_演習6-16
#include <iostream>
using namespace std;
int f[2][5]; // 静的記憶域期間 + ファイル有効範囲
int main()
{
static int s[4][5]; // 静的記憶域期間 + ブロック有効範囲
int a[2][2]; // 自動記憶域期間 + ブロック有効範囲
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 5; j++)
cout << "f[" << i << "][" << j << "] = " << f[i][j] << " ";
cout << '\n';
}
cout << '\n';
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 5; j++)
cout << "s[" << i << "][" << j << "] = " << s[i][j] << " ";
cout << '\n';
}
cout << '\n';
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++)
cout << "a[" << i << "][" << j << "] = " << a[i][j] << " ";
cout << '\n';
}
}

新版明解C++入門編 p.229 演習6-17
呼び出された回数を表示する関数 put_count を作成せよ。
void put_count();
// p229_演習6-17
#include <iostream>
using namespace std;
void put_count()
{
static int n;
cout << "put_count : " << ++n << "回目\n";
}
int main()
{
for (int i = 0; i < 5; i++)
put_count();
}

新版明解C++入門編 p.229 演習6-18
0以上9以下の乱数を返却する関数 rand1 を作成せよ。複数回呼び出された場合に、連続して同じ値を返さないようにすること(たとえば 1回前に呼び出された際に 5を返却していれば、5以外の値を返却しなければならない)。
int rand1();
// p229_演習6-18
#include <ctime>
#include <cstdlib>
#include <iomanip>
#include <iostream>
using namespace std;
int rand1()
{
static int n0;
int n;
do {
n = rand() % 10;
} while (n == n0);
n0 = n;
return n;
}
int main()
{
srand((unsigned int)time(NULL));
for (int i = 0; i < 20; i++) {
cout << setw(2) << i + 1 << "回目 : " << rand1() << '\n';
}
}

--
《その40》 参照渡し(p.221演習6-13),三値のソート(p.223演習6-14,演習6-15)
新版明解C++入門編 p.221 演習6-13
List 6-14(p.219)のプログラムに a = y; を追加して、プログラムの挙動を確認せよ。
// p221_演習6-13
#include <iostream>
using namespace std;
int main()
{
int x = 1;
int y = 2;
int& a = x;
cout << "a = " << a << '\n'; // a … 1
cout << "x = " << x << '\n'; // x … 1
cout << "y = " << y << '\n'; // y … 2
cout << '\n';
a = 5; // x = 5 を意味する。
cout << "a = " << a << '\n'; // a … 5
cout << "x = " << x << '\n'; // x … 5
cout << "y = " << y << '\n'; // y … 2
cout << '\n';
a = y; // x = y を意味する。
cout << "a = " << a << '\n'; // a … 2
cout << "x = " << x << '\n'; // x … 2
cout << "y = " << y << '\n'; // y … 2
}

新版明解C++入門編 p.223 演習6-14
a, bを昇順にソートする(a ≦ b となるように並べかえる)関数 sort を作成せよ。
void sort(int& a, int& b);
// p223_演習6-14
#include <iostream>
using namespace std;
void sort(int& a, int& b)
{
if (a > b) {
int t = a;
a = b;
b = t;
}
}
int main()
{
int x, y;
do {
cout << "◆整数x (999で終了) : "; cin >> x;
if (x == 999) break;
cout << " 整数y : "; cin >> y;
sort(x, y);
cout << " 昇順にソートしました。\n";
cout << " 変数xの値は " << x << "です。\n";
cout << " 変数yの値は " << y << "です。\n";
} while (true);
}

新版明解C++入門編 p.223 演習6-15
x時y分の時刻を、そのdy分後の時刻へと更新する関数 spend を作成せよ。なお時刻の表現は24時間制であるものとする。
たとえば、23時59分の2分後の時刻は 0時1分となる。
void spend(int& x, int& y, int dy);
// p223_演習6-15
#include <iostream>
using namespace std;
void spend(int& x, int& y, int dy)
{
x = (x + (y + dy) / 60) % 24;
y = (y + dy) % 60;
}
int main()
{
int h, m, dm;
cout << "時 :"; cin >> h;
cout << "分 :"; cin >> m;
cout << "何分後?:"; cin >> dm;
cout << '\n';
cout << h << "時" << m << "分の" << dm << "分後は" << '\n';
spend(h, m, dm);
cout << h << "時" << m << "分です。" << '\n';
}


--
2017年09月21日
《その39》 引数を受け取らない関数(p.215演習6-10,演習6-11),デフォルト実引数(p.217演習6-12)
新版明解C++入門編 p.215 演習6-10
「正の整数値:」と表示して、キーボードから正の整数値を読み込んで、その値を返却する
関数 read_pint を作成せよ。0や負の値が入力されたら再入力させること。
int read_pint();
// p215_演習6-10
#include <iostream>
using namespace std;
int read_pint()
{
int a;
do {
cout << "正の整数値:"; cin >> a;
} while (a < 1);
return a;
}
int main()
{
cout << "受け取った正の整数値は … " << read_pint() << '\n';
}

新版明解C++入門編 p.215 演習6-11
List 6-11 を拡張して、以下の4種類の問題をランダムに出題するプログラムを作成せよ。
x + y + z
x + y - z
x - y + z
x - y - z
// p215_演習6-11
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
bool confirm_retry()
{
int retry;
do {
cout << "もう一度?<Yes…1/No…0>:"; cin >> retry;
} while (retry != 0 && retry != 1);
return static_cast<bool>(retry);
}
int main()
{
srand(static_cast<int>(time(NULL)));
do {
int x = rand() % 900 + 100;
int y = rand() % 900 + 100;
int z = rand() % 900 + 100;
int n = rand() % 4;
switch (n) {
case 0: while (true) {
int a;
cout << x << " + " << y << " + " << z << " = "; cin >> a;
if (a == x + y + z) {
cout << "正解!\n"; break;
}
cout << "\a不正解!\n";
}
break;
case 1: while (true) {
int a;
cout << x << " + " << y << " - " << z << " = "; cin >> a;
if (a == x + y - z) {
cout << "正解!\n"; break;
}
cout << "\a不正解!\n";
}
break;
case 2: while (true) {
int a;
cout << x << " - " << y << " + " << z << " = "; cin >> a;
if (a == x - y + z) {
cout << "正解!\n"; break;
}
cout << "\a不正解!\n";
}
break;
case 3: while (true) {
int a;
cout << x << " - " << y << " - " << z << " = "; cin >> a;
if (a == x - y - z) {
cout << "正解!\n"; break;
}
cout << "\a不正解!\n";
}
break;
}
} while (confirm_retry());
}

新版明解C++入門編 p.217 演習6-12
b以上a以下の全整数の和を求める関数 sum を作成せよ。なお、bに対する実引数が省略されて呼び出された場合は、bを 1とみなして合計を求めること。
int sum(int a, int b);
// p217_演習6-12
#include <iostream>
using namespace std;
int sum(int a, int b = 1)
{
int t = 0;
for (int i = b; i <= a; i++)
t += i;
return t;
}
int main()
{
int a, b, total, yes_no;
cout << "b以上a以下の全整数の和を求めます。\n";
cout << "最後の整数a : "; cin >> a;
cout << "最初の整数bを指定しますか?\n";
do {
cout << " する(1),しない(0) : "; cin >> yes_no;
} while (yes_no != 1 && yes_no != 0);
if (yes_no == 1) {
cout << "最初の整数b : "; cin >> b;
total = sum(a, b);
}
else
total = sum(a);
cout << "和 … " << total << '\n';
}


--
2017年09月20日
《その38》 他の関数の呼出し(p.213演習6-7,演習6-8,演習6-9 )
新版明解C++入門編 p.213 演習6-7
引数 mで指定された月の季節を表示する関数 print_season を作成せよ。mが 3, 4, 5であれば「春」、6, 7, 8であれば「夏」、9, 10, 11であれば「秋」、12, 1, 2であれば「冬」と表示し、それ以外の値であれば何も表示しないこと。
void print_season(int m);
// p213_演習6-7
#include <iostream>
using namespace std;
void print_season(int m)
{
switch (m) {
case 12:
case 1:
case 2: cout << "冬\n"; break;
case 3:
case 4:
case 5: cout << "春\n"; break;
case 6:
case 7:
case 8: cout << "夏\n"; break;
case 9:
case 10:
case 11: cout << "秋\n"; break;
}
}
int main()
{
int month;
do {
cout << "何月(99で終了):"; cin >> month;
print_season(month);
} while (month != 99);
}

新版明解C++入門編 p.213 演習6-8
List 6-8 の関数 put_stars を、その内部で List 6-9 の関数 put_nchar を呼び出すことによって表示を行うように書きかえたプログラムを作成せよ。
// p213_演習6-8
#include <iostream>
using namespace std;
void put_nchar(int n, char c)
{
while (n-- > 0)
cout << c;
}
void put_stars(int n)
{
put_nchar(n, '*');
}
int main()
{
int n;
cout << "段数は:"; cin >> n;
for (int i = 1; i <= n; i++) {
put_stars(i);
cout << '\n';
}
}

新版明解C++入門編 p.213 演習6-9
a以上 b未満の乱数を生成して、その値を返却する関数 random を作成せよ。内部で乱数を生成する標準ライブラリである rand関数(p.30)を呼び出すこと。
int random(int a, int b);なお、bの値が a未満である場合には、aの値をそのまま返却すること。
演習6-9のプログラムを作って動作を確認する中で、srand関数の使い方について気付いたことがあります。以下に、失敗例 と 改善例 という形にまとめてみました。
【失敗例(srand関数の使い方)】
a以上 b未満の乱数を生成するだけのプログラムです。5回繰り返すので、すべて a = 1000, b = 2000 の値を与えて、乱数を生成させてみました。
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
int random(int a, int b)
{
srand((unsigned int)time(NULL));
return (a + rand() % (b - a));
}
int main()
{
int a, b;
int n = 5;
while (n--) {
cout << "a と b (a < b)を入力 : "; cin >> a >> b;
cout << "生成した乱数 … 『 " << random(a, b) << " 』\n";
cout << "------\n";
}
}

これでは 1000以上 2000未満の乱数とは言えません。すべて 1500代~1600代に集中しています。しかも、値が徐々に増加しているようです。
どこに問題があるのか いろいろ考えましたが、おそらく、srand関数に与えている乱数のタネに問題があるのだと思います。
毎回、時間の値をタネとして与えて rand関数で発生する最初の乱数を表示させているわけですが、与える時間の値がそれほど違っていないので、発生する乱数の最初の値も似かよった数値になってしまうものと思われます。
時間の値が大きくなっていくにつれて 発生する乱数系列の最初の値も大きくなるというのは、欠陥のようにも感じますが、多分、乱数の使い方として、毎回 srand関数を使うという方法が間違っているのだと考えます。
【改善例(srand関数の使い方)】
失敗例での考察から、srand関数の位置を変えて 1回だけ使用し、あとは、その乱数系列の値を rand関数で順に使用するようにしました。
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
int random(int a, int b)
{
// srand((unsigned int)time(NULL));
return (a + rand() % (b - a));
}
int main()
{
srand((unsigned int)time(NULL));
int a, b;
int n = 5;
while (n--) {
cout << "a と b (a < b)を入力 : "; cin >> a >> b;
cout << "生成した乱数 … 『 " << random(a, b) << " 』\n";
cout << "------\n";
}
}


なんとかなったみたいです ε-(^ ^;)
「srand関数の使い方なんか常識でしょ!」という人には、
すみません、おさわがせしました m(_ _;)m
とりあえず、以下が 演習6-9 のプログラムです。
// p213_演習6-9
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
int random(int a, int b)
{
int rnd;
if (a < b)
rnd = a + rand() % (b - a);
else
rnd = a;
return rnd;
}
int main()
{
srand((unsigned int)time(NULL));
int a, b;
cout << "a = 999 で終了\n";
do {
do {
cout << "小さいほうの値a(0以上): "; cin >> a;
if (a == 999) goto Exit;
cout << "大きいほうの値b(0以上): "; cin >> b;
} while (a < 0 || b < 0);
cout << "生成した乱数 … 『 " << random(a, b) << " 』\n";
cout << "------\n";
} while (1);
Exit:
;
}

--