第3回テキスト

プログラミング演習
第3回
今日の主なテーマ
関数
再帰関数
変数の記憶クラス
条件判定と繰り返しの総合練習問題
1. 1から100までの整数を表示するプログラムを、do-while
文、while文、for文それぞれで作成せよ。
2. キーボードから整数の入力を何度か受け付け、それら
の合計が100以上になったら終了するプログラムを作成
せよ。どの繰り返し構文を使うのが適切か?
3. 任意の整数入力nに対し、コラッツ予想を確認してみよ
– nが偶数のときは2で割る
– nが奇数のときは3倍し、1を足す
以上の操作を繰り返せば必ず1に到達するというもの
2
2からNまでの素数を列挙せよ
• 2つのアルゴリズムを試してみよ。
1. 1つのnが素数であるかどうかを判定し表示するアルゴリ
ズムを考え、それをnを2からNまで巡回する繰り返し構
文の中に入れる(練習問題4.10 - 4.11)。
2. エラトステネスの篩を用いる。
1.
2.
3.
4.
5.
2からNまでの探索リストを用意する
探索リストの先頭を素数リストに追加する
ステップ2で追加した数の倍数を探索リストから削除する
探索リストの最大値がステップ2で追加した数の平方よりも大き
ければステップ2に戻る。
素数リストと探索リストに残った数が全て素数である
C言語では可変長リストを扱うのが難しいので、要素数
Nのchar型配列を用い、その値が0か1で値が存在する
か否かを表すことにして可変長リストとみなそう。
3
配列の練習問題
• N要素の整数配列を作成し、初期値として適当な値を入
れておく。
1. 全要素を順番に表示せよ。
2. 合計・最大・最小値・平均値を求めよ。
3. 小さい順に表示せよ。
4. 配列の中身が小さい順になるように並べ替えよ。
4
先週のおさらい(1/2)
• プログラムの流れを先へ進める
– if文, if~else文, if~else if~else文
条件式に応じた分岐処理を実行
if((a%b)==0){
printf("%d is a factor", b);
}
– switch~case文
ある式が取り得る値に応じて先に進む
switch(c){
case 'A': printf("Great¥n");
case 'B':
case 'C': printf("You did it¥n");
break;
default:
printf("You lost¥n");
}
5
先週のおさらい(2/2)
• プログラムの流れを繰り返す
– while文,do~while文, for文
ある条件が成立する間、同じ処理を繰り返し実行
while(i>1){
i/=2;
}
do{
i/=2;
} while(i>1);
for(i=0; i<10; i++){
j+=i;
}
6
関数の基本事項(1/4 p.36)
• C言語によるプログラムはmain()関数を発端とした関数
の集まり。
• プログラム中に定義(宣言)された関数は他の関数から
呼び出される。
• 関数が呼ばれるとデータの引渡しが行われ、引き渡され
たデータに基づいて演算が実行される。
• 演算により得られた結果は関数値として呼び出された関
数へ返される。この返される値を戻り値(返戻値)と呼ぶ。
戻り値は1つだけ指定できる。
• 戻り値を呼び出された関数へ渡す時にreturn文が使
われる。戻り値となる変数の型がその関数の型となる。
関数の宣言時には戻り値の型に応じて型宣言しなけれ
ばならない。
7
関数の定義 (2/4)
• 関数を定義するには、以下の項目を( )と{ }で区切りなが
ら書く
関
数
の
型
–
–
–
–
戻り値の型
関数名
受け取る引数の個数と型
関数の実体(処理内容)
int name(char c){...}
• 関数は、全て他の関数の外で定義する
– 関数の中では別の関数を定義できない
• 関数は、呼び出されない限り何もしない
– 実行するには直接あるいは間接的にmain関数から呼び出す
• 引数を受け取らない場合や戻り値を戻さない場合には、
特別な型voidを用いて明示する
8
関数の基本事項(3/4 p.37)
戻り値の
型が一致
#include <iostream>
using namespace std;
仮引数
int sum(int x, int y){
int z;
z=x+y;
return z;
仮引数と
引数が存在しない場合
}
実引数の
int main(void){
個数と型が
int i, j, k;
cout << “Input two integers: ”; 一致
cin >> i >> j;
cout << “The sum of these two is: ”;
k=sum(i, j);
cout << k << endl;
return 0;
実引数
}
9
関数の基本事項(4/4)
• 関数を呼び出す時点で関数の型が分かっていれば、
引数や戻り値の型を自動的に型変換してくれる。
double sum(double x, double y){
return x+y;
}
int main(void){
int i, j, k;
...
k=sum(i, j);
// 自動的に
// k=(int)sum((double)x, (double)y);
// という型変換が行われる
...
return 0;
}
10
プロトタイプ宣言(1/2 p.37)
• 先に関数の型が分かっていないと、型変換せずにデータ
の引渡しを行うことになり、計算結果に致命的な誤りを生
じる。
int main(void){
int i, j, k;
k=sum(i, j);
return 0;
}
double sum(double x, double y){
return x+y;
}
• 常に呼び出される関数を先に記述することは不可能なの
で、関数型だけを先に宣言する方法が用意されている。
これをプロトタイプ宣言という。
double sum(double, double a);
仮引数の名前はあってもなくても良い
11
プロトタイプ宣言(2/2 p.37)
#include <iostream>
double sum(double, double); /* プロトタイプ宣言 */
int main(void){
int i,j,k;
cout << “Input two integers: ”;
cin >> i >> j;
cout << “The sum of these two is: ”;
k=sum(i,j);
cout << k << endl;
return 0;
}
double sum(double x, double y){
int z;
z=x+y;
return z;
}
引数の型や個数、関数値の型
を検証するため、関数の定義
に先だって型宣言を行う
12
引数の受け渡し(1/2 p.38)
関数間での変数の引渡し
•pass by value(値呼び出し)
•値のコピーを渡す
•例: printf(“%d\n”, a);
•pass by reference(参照値呼び出し)
•参照を渡す
•C言語でのpass by reference
•アドレスのコピーを渡す
•例: scanf(“%d\n”, &a);
13
引数の受け渡し(2/2 p.38)
#include <iostream>
using namespace std;
void func(int x, int &y, int *z){
++x; ++y; ++*z;
cout << "x=" << x << ", ";
cout << "y=" << y << ", ";
cout << "z=" << *z << "¥n";
}
int main(void){
int a=10, b=20, c=30;
func(a, b, &c);
cout << "a=" << a << ", ";
cout << "b=" << b << ", ";
cout << "c=" << c << "¥n";
return 0;
}
引数をポインタで受け取ると、
コード中に&や*を付けなけ
ればならない
引数を参照で受け取ると、
厄介な*を付けずに済む
14
デフォルト引数 (p.39)
•C++言語では、引数のデフォルト値を与えることができる
•デフォルト値がある引数は、呼び出し時に省略できる
#include <iostream>
void line(int n, char c='-'){
for(int i=0; i<n; i++){
cout << c;
}
cout << endl;
}
main(void){
line(10, '=');
line(10); // 第二引数を省略している
return 0;
}
15
多重定義 (p.39)
•C++言語の関数には、引数並びさえ異なれば同じ名前を
命名しても良い
#include <iostream>
void print(long x){
cout << "long value: " << x << endl;
}
void print(double x){
cout << "double value: " << x << endl;
}
int main(void){
long l=123;
double d=1.23;
print(l);
print(d);
return 0;
}
16
再帰関数 (p.40)
•C言語の関数は、直接的・間接的に自分自身を呼び出す
ことが可能である。このような呼び出し方のことを再帰呼び
出しと言い、そのような関数を再帰関数と呼ぶ。
#include <iostream>
int fact(int);
int main(void){
int i=10;
cout << fact(i);
return 0;
}
int fact(int n){
if(n==1) return 1;
else return(n*fact(n-1));
}
1
(n  1)

n! 
n  (n  1)! (n  1)
17
名前空間 (1/2 p.40-41)
• C++言語は、複数の名前空間を持つ。
• 大域的な名前空間
• 名前付きの名前空間
namespace test {
• 名前なしの名前空間
void func(void);
(無名名前空間)
void sub(void);
• 名前空間に閉じ込められた物は、
}
スコープ演算子を用いて指し示す void test::func(void){
ことができる
sub();
}
• 名前空間名 :: 対象名
void test::sub(void){
}
int main(void){
test::func();
return 0;
}
18
名前空間 (2/2 p. 41)
• using宣言
– ある名前空間中の一部の名前に関し、名前空間名とスコープ演
算子を省略して表記することを許可する
– 例:using std::cout;
cout << "Hello" << std::endl;
• using指令
– ある名前空間中の全ての名前に関し、名前空間名とスコープ演
算子を省略して表記することを許可する
– 例:using namespace std;
cout << "Hello" << endl;
19
自習、復習
• ここまでに登場してきたサンプルプログラムを入力し、
動作を確認してみよう。
• デバッガを利用してステップ実行することにより、関数呼
び出しの動作の様子を把握しよう。
• 関数宣言の順序、プロトタイプ宣言の必要性を理解しよう。
• 練習問題5.1~5.4
20
変数の有効範囲と生存期間(1/5 p.41-44)
• 関数毎に同じ名前の変数を独立して使いたい(局所性)
– 他の関数の中のことは知らずに済ませたい
• 同じ変数を複数の関数で共有したい(大域性)
• このように相反する要求を満たすため、記憶域クラスとい
う概念が導入されている。変数は、同じ名前を使うことが
頻繁であるから、局所的であることが基本となっている。
• 同様に、関数にもクラスという概念がある。関数は大域的
であることが基本であるが、他のファイルからは呼び出
せない局所関数を定義することができる。
• C++言語では、大域的なものを名前空間に閉じ込めるこ
とができる
21
変数の記憶クラス(2/5 p.41-44)
•自動変数:autoによって定義される変数である。クラス指定を
省略するとautoとみなされる。有効範囲は変数が定義されたブ
ロック内である。ブロックの開始と同時に領域が確保され、ブ
ロックの終了と同時に消滅する。(テキストp.42参照)
•静的変数:変数の有効範囲は自動変数と同じであるが、プロ
グラムの開始と同時に領域が確保され、プログラムの終了まで
存在し続ける。初期値のある場合は1回だけ初期化され、関数
が終わっても変数の値は保持される。
•外部変数:関数の外部で定義された変数を外部変数と呼ぶ。
メモリの記憶領域、寿命に関しては静的変数と同じ。外部変数
はexternを利用すると、異なるファイル間での参照が可能とな
る。それに対し、自動変数、静的変数は内部変数と呼ばれ、定
義された関数(ブロック)の内部でのみ有効である。
22
自動変数と静的変数の違い(3/5 p.42-43)
#include <iostream>
void test(void){
int a=0;
static int s=0;
cout << "a=" << a << " s=" << s << endl;
a++;
同じ関数を3回呼び出しているが
s++;
aは同じ値、sは異なる値が
}
表示されることに注意。
int main(void){
test();
a=0 s=0
test();
a=0 s=1
a=0 s=2
test();
return 0;
}
23
外部変数のautoとstaticの違い(4/5 p. 43)
// source1.cpp
static int abc=5;
extern int xyz;
int func1(void){
cout << "In func1, ";
cout << "abc=" << abc;
cout << ", xyz=" << xyz
<< endl;
return abc;
}
実行結果予想
In func1, abc=5, xyz=1
In func2, xyz=5
// source2.cpp
int xyz=1;
int func1(void);
void func2(void){
cout << "In func2, ";
cout << "xyz=" << xyz
<< endl;
}
int main(void){
xyz=func1();
func2();
return 0;
}
変数の記憶クラス(5/5 p.44)
宣言場所
クラス指定
領域確保と
生存時間
同一ファイル内で
宣言したブロック外
から参照が可能か
他のファイルから
参照が可能か
関数内、ブロック内
関数外
指定なし, auto,
static static 指定なし
register
ブロックに同期
プログラムに同期
不可能
不可能
可能
可能
25
自習、復習
• テキストのソースコード9~16を入力し、それぞれの意図
する事柄を確認せよ
• 練習問題5.5-5.14
26
第4回目の演習問題
1. a, b, c, hの値をキーボードから入力し、練習問題
4.13の矩形則を用いた数値積分を用いて
1
s   (ax  bx  c)dx
2
0
を求めよ。hの値を変えた時の値を記録して、解析解と
の誤差とhの関係を調べてみよ。
2. 問題1.を s=integral(a,b,c,h) として利用でき
る関数として定義せよ。main関数から繰り返し呼び出
すことで、hとsの関係を表示してみよ。
例:h=0.1, s=1.2
h=0.01, s=1.25
h=0.001, s=1.248
27
数値積分(p.35)
• 解析的積分が不可能な関数を積分する手法
• 刻み幅 h に依存して計算精度や計算時間が変わる
– h を小さくし過ぎても、誤差が生じて計算精度が落ちる
場合があることに注意

b
a
a
b
f ( x)dx   f ( x)  h
xa
h
b
28
算術関数を利用するには
• 算術関数は、標準では使われない算術ライブラリ中に
用意されているので、算術関数定義ファイル cmath
をインクルードすることを忘れてはならない。
#include <cmath>
• 知っていて便利な算術関数
三角関数
sin(x), cos(x), tan(x)
指数、対数
exp(x), log(x),
log10(x)
冪乗、根
pow(x,y), sqrt(x)
端数処理、絶対値 ceil(), floor(),
fabs()
29
4
1
3
2
5