C言語入門 - メディア基盤センター

1
C言語入門
第4週
プログラミング言語Ⅰ(実習を含む。),
計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
2
先週の復習1
3
参考
条件演算子
三項演算子(?:)
• 書式:
真
• 条件式 ? 式1 : 式2
式1
条件式
偽
式2
condexprtest.c
int i;
printf("i = "); scanf("%d", &i);
printf("%s\n",i ? "true" : "false");
$ gcc condexptest.c && ./a
i = 1
true
$ ./a
i = 0
false
参考: [1] pp.63-66, 256-257.
4
閏年(leap year)の判定
• 閏年の求め方
• 西暦を4で割り切れるなら閏年?
真
閏年
閏年
判定式
偽
平年
5
閏年(leap year)の判定
• グレゴリオ暦における閏年の定義
• 判別したい年を西暦で表した際
• 4で割り切れる場合は閏年
(条件1)
• 但し100で割り切れる場合は平年 (条件2)
• 但し400で割り切れる場合は閏年 (条件3)
6
閏年(leap year)の判定
• 4で割れる(割り切れる)とは?
• 4で割った余りが0ということ
• 4で割り切れる
• 4で割り切れない
: year % 4 == 0
: year % 4 != 0
% : 剰余算演算子
等値演算子
== : 等しい
!= : 等しくない
• C言語では0は偽、0以外は真だったから
• 以下のようにも書けるが・・・
• 4で割り切れる
• 4で割り切れない
! : 論理否定演算子
: !(year % 4)
: (year % 4)
ぱっと見て意味の分かり易い書き方をしましょう
7
演算子の優先度
優先度
高
演算子
( )
[ ]
->
備考
左から右→
.
右から左←
単項演算子
左から右→
二項演算子
左から右→
二項演算子
左から右→
bitシフト
左から右→
関係演算子
左から右→
等値演算子
&
左から右→
bit毎のAND
^
左から右→
bit毎のXOR
|
左から右→
bit毎のOR
&&
左から右→
論理演算子(AND)
||
左から右→
論理演算子(OR)
?:
右から左←
三項演算子
右から左←
代入演算子
!
~
++
*
/
%
+
-
<<
<
==
=
低
結合規則
,
--
+
-
*
&
(type)
sizeof
>>
<=
>
>=
!=
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
左から右→
[1] p.65. より
8
閏年(leap year)の判定
• プログラムの仕様
• 判別したい西暦をキーボードから入力
• int 型の変数 year に格納
• int 型の変数 leap_year_flag に
平年なら 0 閏年なら 1 を格納
• 閏年か否かを表示
9
閏年(leap year)の判定
• 雛形
is_leap_year_template.c
int year;
int leap_year_flag;
printf("year = ");
scanf("%d", &year);
// Write here your routine to detect the leap year.
if (leap_year_flag) {
printf("%d is leap year.\n", year);
} else {
printf("%d is not leap year.\n", year);
}
10
閏年(leap year)の判定
真
条件1
閏年
4で
割れる
偽
平年
11
閏年(leap year)の判定
真
条件1
+条件2
真
平年
100で
割れる
4で
割れる
偽
閏年
偽
平年
12
閏年(leap year)の判定
真
条件1
+条件2
+条件3
真
完成
真
閏年
400で
割れる
100で
割れる
偽
平年
4で
割れる
偽
閏年
偽
平年
13
閏年(leap year)の判定
• 3重のif-else文による実装
• 条件1を実装
if (year % 4 == 0) {
leap_year_flag = 1;
} else {
leap_year_flag = 0;
}
14
閏年(leap year)の判定
• 3重のif-else文による実装
• 条件2を追加
if (year % 4 == 0) {
if (year % 100 == 0) {
leap_year_flag = 0;
} else {
leap_year_flag = 1;
}
} else {
leap_year_flag = 0;
}
15
閏年(leap year)の判定
• 3重のif-else文による実装
• 条件3を追加して完成
なんかごちゃごちゃしていて
分かり難い
もっと分かり易く
書けないか?
is_leap_year_1_1.c
if (year % 4 == 0) {
if (year % 100 == 0) {
if (year % 400 == 0) {
leap_year_flag = 1;
} else {
leap_year_flag = 0;
}
} else {
leap_year_flag = 1;
}
} else {
leap_year_flag = 0;
}
16
閏年(leap year)の判定
• 3重のif-else文による実装
• 初期値を与え
例外のみを記述してみる
• 条件1を実装
leap_year_flag = 0;
if (year % 4 == 0) {
leap_year_flag = 1;
}
17
閏年(leap year)の判定
• 3重のif-else文による実装
• 初期値を与え
例外のみを記述してみる
• 条件2を追加
leap_year_flag = 0;
if (year % 4 == 0) {
leap_year_flag = 1;
if (year % 100 == 0) {
leap_year_flag = 0;
}
}
18
閏年(leap year)の判定
• 3重のif-else文による実装
• 初期値を与え
例外のみを記述してみる
• 条件3を追加して完成
元の定義に近い書き方?
若干読み易くなった?
更に分かり易く
書けないか?
is_leap_year_1_2.c
leap_year_flag = 0;
if (year % 4 == 0) {
leap_year_flag = 1;
if (year % 100 == 0) {
leap_year_flag = 0;
if (year % 400 == 0) {
leap_year_flag = 1;
}
}
}
19
閏年(leap year)の判定
• 展開したif文による実装
• 100 と 400 は 4 の倍数
400 は 100 の倍数
である点に着目すると
ネスト(入れ子)を展開可能
if文のロジック以外に
各条件間の数学的関係による
暗黙の前提が分からないと
読めないコードになるかも?
そういう意味では
良くないコード?
適切なコメント等が必要?
is_leap_year_2_1.c
leap_year_flag = 0;
if (year %
4 == 0) {
leap_year_flag = 1;
}
if (year % 100 == 0) {
leap_year_flag = 0;
}
if (year % 400 == 0) {
leap_year_flag = 1;
}
year %
4 != 0 および
year % 100 != 0 の場合には影響を与えない
year %
4 != 0 の場合には影響を与えない
20
閏年(leap year)の判定
• 展開したif文による実装
• 前頁のコードから省略可能な { } を省略
is_leap_year_2_2.c
int leap_year_flag =
if (year %
4 == 0)
if (year % 100 == 0)
if (year % 400 == 0)
0;
leap_year_flag = 1;
leap_year_flag = 0;
leap_year_flag = 1;
実行する処理が
1つだけの場合は
省略出来る。
綺麗で見易い。
しかし、定義通りになっていないため、
100で割り切れた場合、4で割り切れなかった場合に影響を与えない
400で割り切れた場合、4,100で割り切れなかった場合に影響を与えない
ことがわかっていないと混乱するかもしれない?
21
閏年(leap year)の判定
• 3重の条件演算子による実装
• 条件1を実装
leap_year_flag = year % 4 == 0 ? 1 : 0;
• 条件2を追加
leap_year_flag = year % 4 == 0 ? (year % 100 == 0 ? 0 : 1) : 0;
• 条件3を追加して完成
is_leap_year_3_1.c
leap_year_flag = year % 4 == 0 ? (year % 100 == 0 ? (year % 400 == 0 ? 1 : 0) : 1) : 0;
条件演算子は便利だけど
下手に多用すると読み難くなるので注意
ネスト(入れ子)が深くなると
if-else文以上に読み難い
22
閏年(leap year)の判定
• 3重の条件演算子による実装
• 条件1を実装
leap_year_flag = year % 4 == 0 ? 1 : 0;
is_leap_year_2_2.c と同じ方法で
入れ子の条件を展開する例
• 条件2を追加
leap_year_flag = year %
4 == 0 ? 1 : 0;
leap_year_flag = year % 100 == 0 ? 0 : leap_year_flag;
• 条件3を追加して完成
is_leap_year_3_2.c
leap_year_flag = year %
4 == 0 ? 1 : 0;
leap_year_flag = year % 100 == 0 ? 0 : leap_year_flag;
leap_year_flag = year % 400 == 0 ? 1 : leap_year_flag;
これなら is_leap_year_2_2.c の方が読み易いかも?
23
論理演算
• AND, OR, NOT
• 条件を論理演算する場合に使う
• AND:
• OR:
• NOT:
X && Y
X || Y
!X
X
Y
X && Y
X || Y
FALSE
FALSE
0
0
FALSE
TRUE
0
1
TRUE
FALSE
0
1
TRUE
TRUE
1
1
!X
1
0
24
閏年(leap year)の判定
• 西暦全体の集合のうち
• X:
4で割り切れる集合(Y,Z を包含)
• Y: 100で割り切れる集合(Z を包含)
• Z: 400で割り切れる集合
X
Y
Z
!Y
Y
25
閏年(leap year)の判定
• 論理演算で
考えると
!X || (Y && !Z)
X
Y
!X
(X && !Y) || Z
X && !Y
C言語の演算子の優先度的には
X && !Y || Z でも良いが
( ) を付けた方が理解が容易かつ
誤解の余地が生じない
Y && !Z
Z
Z
26
閏年(leap year)の判定
• if-else文と論理演算による実装
is_leap_year_4_1.c
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
leap_year_flag = 1;
} else {
leap_year_flag = 0;
}
• 論理演算による実装
is_leap_year_4_2.c
leap_year_flag = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
27
閏年(leap year)の判定
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
year % 100 == 0
year % 4 == 0
year % 100 != 0
year % 100 == 0
year % 4 != 0
year % 100 != 0
× の個所は
条件を満たす
year が存在しない
year % 400 == 0
閏年
year % 400 != 0
平年
year % 400 == 0
×
year % 400 != 0
閏年
year % 400 == 0
×
year % 400 != 0
×
year % 400 == 0
×
year % 400 != 0
平年
2_1, 2_2, 3_2 の例で year % 100 != 0 や year % 400 != 0 が
考慮不要なことが確認出来る。
28
閏年(leap year)の判定
• 全ての条件が網羅出来ているか?
今の場合前の条件への追加条件なので
• 条件に漏れはないか? 全ての組み合わせを考える必要はない
year % 100 == 0
year % 4 == 0
year % 100 != 0
year % 100 == 0
year % 4 != 0
year % 100 != 0
year % 400 == 0
閏年
year % 400 != 0
平年
year % 400 == 0
year % 400 != 0
閏年
year % 400 == 0
year % 400 != 0
year % 400 == 0
year % 400 != 0
平年
29
閏年(leap year)の判定
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
year % 100 == 0
year % 100 != 0
× の個所は
条件を満たす
year が存在しない
year % 4 == 0
year % 4 != 0
year % 400 == 0
閏年
×
year % 400 != 0
平年
×
year % 400 == 0
×
×
year % 400 != 0
閏年
平年
長くなる場合は横に展開しても良い
30
閏年(leap year)の判定
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
× の個所は
条件を満たす
year が存在しない
year % 4 == 0
year % 100 == 0
year % 400 == 0
閏年
year % 4 == 0
year % 100 == 0
year % 400 != 0
平年
year % 4 == 0
year % 100 != 0
year % 400 == 0
×
year % 4 == 0
year % 100 != 0
year % 400 != 0
閏年
year % 4 != 0
year % 100 == 0
year % 400 == 0
×
year % 4 != 0
year % 100 == 0
year % 400 != 0
×
year % 4 != 0
year % 100 != 0
year % 400 == 0
×
year % 4 != 0
year % 100 != 0
year % 400 != 0
平年
この書き方は Excel 等で sort して調べるのに向いている
31
条件演算子の応用
• 真偽値による文字列の切り替え
is_leap_year_template.c
if (leap_year_flag) {
printf("%d is leap year.\n", year);
} else {
printf("%d is not leap year.\n", year);
}
printf("%d is%s leap year.\n", year, leap_year_flag ? "" : " not");
leap_year_flag の値を見て直接
%s に埋め込む文字列を変更
32
同じ処理でも実装は様々
• 同じ処理でも複数の異なる実装が可能
• 効率や可読性等、何を重視するか?
• 読み易い、理解しやすいコードを推奨
33
教科書 pp.149-160.
関数(サブルーチン)
• C 言語は処理を関数にしてまとめる
• main も関数
コマンドライン引数の数
c_template.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Here is a main routine.
コマンドライン引数を格納した
複数の文字列へのポインタ
関数名、引数、戻り値の定義
関数の本体
return EXIT_SUCCESS;
}
フルセットの main 関数
34
教科書 pp.149-160.
(ユーザー定義)関数
• 関数は自分で作ることが出来る
• 機能、分量等、ある程度まとまった処理は
関数にまとめる
• 可読性が向上する
• 再利用が容易になる
• 関数の定義の書式:
C言語の関数は
0個以上の引数と
1つの戻り値を持つ。
引数や戻り値が不要な場合は
戻り値の型 関数名(引数の宣言, ...)
void を宣言するよう
{
推奨されている。
// 関数に行わせる処理
// ...
return 戻り値; // 戻り値の型がvoidの場合は不要
}
35
教科書 pp.149-160.
(ユーザー定義)関数の例
• 閏年の判定を関数にまとめてみる
is_leap_year_with_func_4_2.c
#include <stdio.h>
#include <stdlib.h>
int is_leap_year(int year)
{
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
int main()
{
int year;
printf("year = ");
scanf("%d", &year);
関数の定義
自分で作成した関数を
サブルーチンとして呼ぶ
printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not");
return EXIT_SUCCESS;
}
36
教科書 pp.153-160.
(ユーザー定義)関数の例
• 関数を使う前にはパラメータの型と数が
分かっている必要がある(コンパイラの都合)
#include <stdio.h>
#include <stdlib.h>
int main()
{
int year;
printf("year = ");
scanf("%d", &year);
使う時点で不明な戻り値の型はintを仮定される。
引数については何も仮定しない。
もし、実際に定義されている関数の型が異なると
型が競合してエラーになる。
printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not");
return EXIT_SUCCESS;
}
型が不明なので、このタイミングで
int is_leap_year(int year)
int is_leap_year(int); が仮定される。
{
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
[1] pp.86-89.
37
教科書 pp.149-160.
関数のプロトタイプ宣言
• 関数は使う前にプロトタイプ宣言する
#include <stdio.h>
#include <stdlib.h>
int is_leap_year(int year);
関数のプロトタイプ宣言
戻り値の型、引数の数と型を宣言する
int main()
{
int year;
printf("year = ");
scanf("%d", &year);
関数のプロトタイプ宣言さえしておけば
関数の定義はどこにあっても良い。
例えば外部のファイルにあっても良い。
printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not");
return EXIT_SUCCESS;
}
38
教科書 pp.203-206.
関数をファイルに分離する
• 再利用性が高まる(コピペしなくて良くなる)
is_leap_year_func.h
ヘッダファイル
#ifndef IS_LEAP_YEAR_FUNC_H
#define IS_LEAP_YEAR_FUNC_H
include ガード(二重includeを防ぐ)
int is_leap_year(int year);
#endif
関数の宣言
他所から使う際に、
関数名、引数、戻り値の
情報を得るために必要
is_leap_year_func_4_2.c
関数本体の定義
#include "is_leap_year_func.h"
int is_leap_year(int year)
{
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
39
教科書 pp.203-206.
ファイルに分離した関数の利用
• main 関数の部分
is_leap_year_main.c
#include <stdio.h>
#include <stdlib.h>
#include "is_leap_year_func.h"
int main()
{
int year;
printf("year = ");
scanf("%d", &year);
ヘッダファイルの include
外部ファイルで定義した
関数を呼び出し
printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not");
return EXIT_SUCCESS;
}
40
分割コンパイル
• コンパイルする複数のCのソースファイルを
コンパイラに与えれば良い
mintty + bash + GNU C
$ gcc
is_leap_year_main.c is_leap_year_func_4_2.c
コマンドプロンプト + Borland C++
>bcc32 is_leap_year_main.c is_leap_year_func_4_2.c
与えるのはCのソースファイルのみ
ヘッダファイルは #include 文により
自動的に取り込まれる
41
教科書 pp.203-206.
関数の演習
• is_leap_year_func_4_2.c を参考に以下のファイ
ルの処理を関数にしてファイルに分離してみま
しょう。
•
•
•
•
•
•
•
is_leap_year_1_1.c → is_leap_year_func_1_1.c
is_leap_year_1_2.c → is_leap_year_func_1_2.c
is_leap_year_2_1.c → is_leap_year_func_2_1.c
is_leap_year_2_2.c → is_leap_year_func_2_2.c
is_leap_year_3_1.c → is_leap_year_func_3_1.c
is_leap_year_3_2.c → is_leap_year_func_3_2.c
is_leap_year_4_1.c → is_leap_year_func_4_1.c
教科書 pp.161-169.
変数のスコープ(有効範囲)
• 大域(グローバル)変数
• プログラム全体からアクセス可能
• 局所(ローカル)変数
• ブロック内からのみアクセス可能
C言語では { } で
囲まれた部分がブロック
42
43
教科書 pp.161-169.
変数のスコープ(有効範囲)
• ローカル変数は { } の中のみで有効
scopetest.c
int gl = 100;
関数外で宣言するとグローバル変数
void sub(int lo)
関数の引数はローカル変数
{
{
ブロックを作成するとローカル変数の有効範囲を制限出来る
int lo = 400;
printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo);
}
printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo);
}
int main()
{
int lo = 200;
sub(300);
printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo);
return EXIT_SUCCESS;
}
44
教科書 pp.161-169.
変数のスコープ(有効範囲)
• ローカル変数は{}の外側に影響していない
scopetest.c
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int gl = 100;
void sub(int lo)
{
{
int lo = 400;
printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo);
}
printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo);
}
int main()
mintty + bash + GNU C
{
$ gcc scopetest.c && ./a
int lo = 200;
sub(300);
sub : 14: gl=101, lo=401
printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo);
return EXIT_SUCCESS;
}
sub :
main :
16: gl=102, lo=301
23: gl=103, lo=201
45
プリプロセッサ
• 条件付きコンパイル
#if
定数式
#elif 定数式
#else
#endif
#if 0
// ここはコンパイルされない
#endif
条件に合った箇所のみを
コンパイラに渡す構文
定数式の結果は0と非0の真偽値
#if defined(__BORLANDC__)
// __BORLANDC__ という名前のマクロが
// 定義されていた場合のみコンパイルされる
#endif
#if defined(__BORLANDC__) && 0x0551 <= __BORLANDC__
// ...
#endif
定数式なら
演算も可能
46
プリプロセッサ
• 条件付きコンパイル
defined( ) の短縮表記
#ifdef __BORLANDC__
// ...
#endif
#if
#ifndef __BORLANDC__
// ...
#endif
#if !defined( ) の短縮表記
誤:define
正:defined
47
プリプロセッサ
• 定義済みマクロ
__LINE__
__FILE__
__func__
__FUNC__
現在の行番号
現在のファイル名
現在の関数名(C99)
現在の関数名(Borland C++)
#ifdef __BORLANDC__
#define __func__ __FUNC__
#endif
Borland C++ で
C99互換の定義済みマクロが
使えるようにする
条件付きコンパイル
48
教科書 pp.203-206.
関数をファイルに分離する
• 作業用変数を用いる場合
is_leap_year_func_1_1.c
#include "is_leap_year_func.h"
int is_leap_year(int year)
{
int leap_year_flag;
}
ローカル変数の宣言
if (year % 4 == 0) {
if (year % 100 == 0) {
if (year % 400 == 0) {
leap_year_flag = 1;
} else {
leap_year_flag = 0;
}
} else {
leap_year_flag = 1;
}
} else {
leap_year_flag = 0;
}
ここは
is_leap_year_1_1.c
そのまま
return leap_year_flag;
結果を戻り値として返す
ローカル変数
ブロック内の作業用。
関数外に
同じ名前の変数があっても
競合せず安全に使える。
49
関数の差し替え
• 関数のプロトタイプ宣言が同じなら関数は差
し替えて使える。
mintty + bash + GNU C
$ gcc
is_leap_year_main.c is_leap_year_func_4_2.c
mintty + bash + GNU C
$ gcc
実装は異なるが
プロトタイプ宣言が同じ関数を
定義したファイルで差し替え
is_leap_year_main.c is_leap_year_func_1_1.c
50
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久
訳、プログラミング言語C 第2版 ANSI 規格準
拠、共立出版(1989)