プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機...
TRANSCRIPT
C言語入門
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情
報処理言語Ⅰ(実習を含む。) 第1週~第15週
1 CLangI2015S1
C言語入門 第1週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
2
本日の内容
• 授業予定日の確認
• 授業教材の指示
• C言語用プログラミング環境の整備
• C言語プログラミングの導入部分
3
授業予定日の確認
• 授業予定日の変更があります • 本来の授業予定日:
• 毎週金曜日 7,8,9,10 時限 4/10、 4/17、 4/24、 5/1、 5/15、 5/22、 5/29、 6/5、 6/12、 6/19、 6/26、 7/3、 7/10、 7/17、 7/24、 7/31 以上16回、最終日は期末試験
• ただし以下の授業予定日は出張のため、翌日または翌々日に変更になります。 • 金曜日: 4/24、5/22、6/19、7/10 休講 • 土曜日: 4/25、5/23、6/20、7/11 補講候補日1 • 日曜日: 4/26、5/24、6/21、7/12 補講候補日2
4
授業変更日の確認
4月 日 月 火 水 木 金 土 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 5月 日 月 火 水 木 金 土 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
6月 日 月 火 水 木 金 土 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 7月 日 月 火 水 木 金 土 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
5
教材のページ
• 修学支援システムの講義情報から Moodle【学内外版】へ
6
コマンドプロンプト
• キーボードから直接命令を入力して実行する時に使います。本講義ではC言語によるプログラムのソースコードをコンパイル(=機械語に翻訳)する場合等に利用する。
コマンドプロンプトを開く
• キーボードから「田」を押したまま「R」を押して「ファイル名を指定して実行」を開く。 「田+R」のように表記された場合、慣例として上記のような複数キー同時押しを意味する。
• 「ファイル名を指定して実行」 の「名前」の欄に「cmd」と入力し、 「ENTER」キーを押すか、「OK」ボタンをクリックする。
「スタート」ボタンから、「すべてのプログラム」→「アクセサリ」→「コマンドプロンプト」でも開ける。
コマンドプロンプトの設定項目
• 「簡易編集モード」をONにしておくと、マウスの左右ボタンだけでコピペ出来る。
コマンドプロンプトの設定
• ウインドウ左上のアイコンをクリックするとメニューが出る。
「プロパティ」で現在開いているプロンプトのみ、「規定値」で次回以降開くプロンプト全てに対する設定を変更出来ます。
コマンドプロンプトと作業ディレクトリ
• 「作業ディレクトリ」とはコマンドプロンプトで作業した際に、ファイルが入出力されるディレクトリの事。
通常、コマンドプロンプトを開いた初期状態では、ユーザーのホームまたはプロファイルのディレクトリが作業ディレクトリ。
ディレクトリとはフォルダの別の言い方。 つまり「ディレクトリ=フォルダ」
作業ディレクトリとコマンド
• コマンドプロンプトで「explorer .」と入力して ENTER キーを押す。
• 現在の作業ディレクトリが explorer で開く。
「explorer」とは、Windows上で、
いつもファイルを操作しているこのウインドウの事。
「explorer」がコマンド名(≒ プログラムの実行ファイル名)で、スペースで区切って与えた「.」はコマ
ンドライン引数と言います。この場合「.」は、現在の作業ディレクトリを表す。
本来のディレクトリ名
• explorer 上では本来のディレクトリ名が日本語も用いて分かり易く(?)表示されている。
ここをクリックすると、コマンドプロンプト上の作業ディレクトリの表示と一致していることが確認出来る。
これが本来のディレクトリ名 explore 以外ではこちらを使う。
異なる名前に見える
「C:¥Users¥kou」は C ドライブの Users フォルダの中にある kou フォルダを意味する。
プログラミング言語、計算機言語、 情報処理言語とは?
• コンピュータに命令するための人工言語
• AWK, BASIC, C, C++, C#, D, ECMAScript, Erlang, Fortran, F#, Haskell, Java, JavaScript, Lisp, Objective-C, OCaml, Perl, PHP, Python, Ruby, Smalltalk, Tcl, 等々
14
プログラムとは?
• 値の保存・参照、各種演算、条件分岐等を組み合わせた計算の手順
処理
データ
保存
データ
参照
条件判定
処理
処理
処理
真
偽
15
プログラムが出来ると 何が良いのか?
• コンピュータに仕事をやってもらえる!!! • ルーチンワークからの解放
• 工作の世界も広がる!!! • 最近は Arduino 等の安くて高機能なキットがある • http://thinkit.co.jp/story/2013/02/12/3960
• 楽しい!!! • 遊び方は遊ぶ人次第
• 仕事にもあぶれない? • Facebook元役員「プログラミングを学ぶのなら、生
涯仕事に困らないことを私が保証しよう。」 • http://goo.gl/I8nCDm
16
C言語
• AT&T の Bell 研で UNIX を作ってた Brian Wilson Kernighan と Dennis MacAlistair Ritchie らによって UNIX を作成するための言語として生まれた。
• K&R 「プログラミング言語C」がC言語のバイブルと言われる所以。
17
世界で最も有名なプログラム
• K&R の最初に出て来るプログラム hello.c
18
hello.c #include <stdio.h> main() { printf("hello, world¥n"); }
1 2 3 4 5 6
mintty + bash + GNU C $ gcc hello.c && ./a hello, world
教科書 p.37., [1] p.8.
世界で最も有名なプログラム
• K&R の最初に出て来るプログラム hello.c
19
hello.c #include <stdio.h> main() { printf("hello, world¥n"); }
1 2 3 4 5 6
ヘッダファイル(stdio.h)の取り込み
mintty + bash + GNU C $ gcc hello.c && ./a hello, world
プログラムのソースコードを コンパイルして実行
文字列の表示
main関数の定義
ヘッダファイル stdio.h
• 多くの入門書ではおまじないとして紹介
20
hello.c #include <stdio.h> main() { printf("hello, world¥n"); }
1 2 3 4 5 6
ヘッダファイル(stdio.h)の取り込み
mintty + bash + GNU C $ gcc hello.c && ./a hello, world
・C言語は言語本体だけでは、四則演算、条件分岐、繰り返し等の基本的な処理以外ほとんど何も出来ない。
・文字列の入出力や数学の関数等、基本的かつ必要性の高い機能は、標準ライブラリと呼ばれる、サブルーチン集として用意されている。 ・printf 関数は stdio.h (= STanDard Input Output Header file) で提供されている。
printf 関数の呼び出し
C言語のプログラム
• コンパイラにより実行形式に変換する
.c ファイル
.h ファイル .h ファイル
.h ファイル
.c ファイル .c ファイル
Source files
C compiler
Preprocessor
linker
.o ファイル
Object files
.o ファイル .o ファイル
実行ファイル
Executable file
21
C コンパイラによるコンパイル
• Cygwin の mintty で bash から gcc で行った例
mintty + bash + GNU C $ ls hello.c $ gcc hello.c $ ls a.exe hello.c $ ./a hello, world
ファイル一覧の表示
コンパイル
ファイル一覧の表示
作成した実行ファイルを実行
22
C コンパイラによるコンパイル
• コマンドプロンプトからBorland C++で行った例
コマンドプロンプト + Borland C++ >dir /B hello.c >bcc32 hello.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland hello.c: 警告 W8070 hello.c 6: 関数は値を返すべき(関数 main ) Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland >dir /B hello.c hello.obj hello.tds hello.exe >hello hello, world
ファイル一覧の表示
コンパイル
ファイル一覧の表示
作成した実行ファイルを実行
23
C/C++ リファレンス
• http://www.cppll.jp/cppreference/ Windows Help (.chm) 版を入
れておくと便利です。
24
C 言語の規則
• 各処理の末尾はセミコロン「;」で終端する。
25
hello.c #include <stdio.h> main() { printf("hello, world¥n"); }
1 2 3 4 5 6
これがないとコンパイル時にエラーとなる。
セミコロン「;」を忘れたエラーの例
• 各処理の末尾はセミコロン「;」で終端する。
26
hello_err.c #include <stdio.h> main() { printf("hello, world¥n") }
1 2 3 4 5 6
本当はここに「;」が必要。
mintty + bash + GNU C $ gcc hello_err.c hello_err.c: 関数 ‘main’ 内: hello_err.c:6:1: エラー: expected ‘;’ before ‘}’ token } ^
セミコロン「;」を忘れたエラーの例
• 各処理の末尾はセミコロン「;」で終端する。
27
hello_err.c #include <stdio.h> main() { printf("hello, world¥n") }
1 2 3 4 5 6
本当はここに「;」が必要。
コマンドプロンプト + Borland C++ >bcc32 hello_err.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland hello_err.c: エラー E2379 hello_err.c 6: ステートメントにセミコロン(;)がない(関数 main ) 警告 W8070 hello_err.c 6: 関数は値を返すべき(関数 main )
エラーの例
• hello.c の 関数「main」の内にある 6 行目の1文字目にあるトークン「}」の前に「;」が(あることが)予想されますよ、と言っている。
28
mintty + bash + GNU C $ gcc hello_err.c hello_err.c: 関数 ‘main’ 内: hello_err.c:6:1: エラー: expected ‘;’ before ‘}’ token } ^
C 言語の規則
• { } で複数の処理をまとめる。
29
hello1.c #include <stdio.h> main() { printf("hello"); printf(", world¥n"); }
1 2 3 4 5 6 7
{ } の間に ; で終端
された複数の処理を書いてよい。
C 言語の規則
• " " で囲まれた部分は文字列
30
hello1.c #include <stdio.h> main() { printf("hello"); printf(", world¥n"); }
1 2 3 4 5 6 7
{ } の間に ; で終端
された複数の処理を書いてよい。
C 言語の構造
• プログラムは関数の形でまとめる。
31
関数定義の書式 戻り値の型 関数名(引数の宣言, ...) { // 処理; // ... // return 戻り値; }
1 2 3 4 5 6
関数は上記のように定義し、( ) の中に書かれた引数で(呼び出し元や呼び出し先の)他の関数と値の受け渡しを行う。
C 言語の規則
• プログラムは main 関数から実行される。
32
main.c main() { // ここに処理を書く }
1 2 3 4
mintty + bash + GNU C $ gcc main.c && ./a
これは何もしないで終わるだけのプログラム。
// から行末までと /* から */ までは プログラムとして解釈はされない。 コメントと呼ばれる要素で、
メモや覚書として主に人間が読む際の注釈を書くために使う他、一時的にプログラムの一部を無効にするために使う。
宿題
• 次回までに以下の事をやっておくこと。
• 教科書の第1章までを読み、指示された操作を試して動作を確認する。
• 不明な点、疑問点についてメモし、次回の授業に持参する。または、本講義の Moodle コース上にある第1週宿題用フォーラムに書き込んでおく。
33
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
34
C言語入門 第2週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
35
先週の復習1
explorer の使い方
36
explorer.exe
• ファイルの操作を行うソフト
• デスクトップもエクスプローラーの一部
ここに「explorer」と入力し ENTERキーを叩いても
explorer が表示できます
これが「explorer」 デスクトップも「explorer」
コントロールパネルも 「explorer」
37
ポップアップ(popup)メニューの表示
• 表示させたい場所でマウス右クリック
• 例えばデスクトップの何もない場所で
• メニューの右にある三角印
• サブメニューが出る
ポップアップメニュー
サブメニュー
38
ファイルの作成とオープン
• explorer の
• 作成したい場所で マウス右クリック
• ポップアップメニューが出る
• 新規作成→テキストドキュメント
• 出来たファイルは 適当に名前を付けても良い
• マウス左ダブルクリック →メモ帳で開かれる
39
フォルダの作成
• 作成したい所で右クリック
• ポップアップメニューで
• →新規作成
• →フォルダ
• 「新しいフォルダ」ができた
40
フォルダを開く
• フォルダアイコンの上で左ダブルクリック
• explorer が開く
開いているフォルダ名
41
ファイルの移動
• 「テキストドキュメント」を
• 「新しいフォルダ」の中に移動
• 「ドラッグ&ドロップ」すれば良い
ドラッグ&ドロップ 移動しました
42
ファイルのコピー
• Ctrl を押したまま 「ドラッグ&ドロップ」
コピーができました
43
ファイルの選択
• ファイルを単純にクリック
• ファイル左側のチェックボックスをクリック
• CTRL キーを押したままクリック(複数選択)
• ファイルを囲むようにドラッグ(複数選択)
選択された
44
ファイルの削除
• 選択したファイル上で右クリック
• →削除
• ファイルを選択して「Delete」キーでも良い
ファイルが消えた
45
CUI作業環境 (端末: terminal)
• フォルダをSHIFT+右クリック
コマンドプロンプト
mintty + bash
46
右クリックしたフォルダを作業ディレクトリにして 端末(コマンドプロンプトやmintty)が開かれる
cmd と bash の主なコマンド
cmd bash
マニュアルの表示 help [コマンド名] man [コマンド名]
ファイル一覧 dir ls
メッセージの表示 echo メッセージ echo メッセージ
ファイルの内容を表示 type ファイル名 cat [ファイル名]
作業ディレクトリの移動 cd ディレクトリ名 cd [ディレクトリ名]
作業ディレクトリの表示 cd pwd
ファイルコピー copy コピー元 コピー先 cp コピー元 コピー先
ファイル移動 move コピー元 コピー先 mv コピー元 コピー先
ファイル削除 del ファイル名 rm ファイル名
ディレクトリの作成 mkdir ディレクトリ名 mkdir ディレクトリ名
ディレクトリの削除 rmdir ディレクトリ名 rmdir ディレクトリ目
47
cmd と bash の主なキー操作コマンド
48
cmd bash 動作
行頭へ移動
行末へ移動
行末まで削除
ファイル名の補完
コマンド履歴の再利用
↑
tab |← →|
↓
Home
END
Ctrl K
+ Shift End +
Ctrl E
+
Ctrl A
+
ファイルの場所の記述方法(パス)
• 以下の図のフォルダ構成を仮定する
教科書 pp.30-31. 49
C:
Users
kou
Desktop
CLangI CLangII
week01 week01
hello.c hellogui.c
作業フォルダは CLangI
作業フォルダ
• カレントディレクトリ(current directory)とも言う
ここに表示されているのが 作業フォルダ
50
コマンドプロンプト
mintty + bash
Windows の絶対パス、相対パス
• フォルダの階層を ¥ で区切る
• 現在の作業フォルダは以下の場所 • C:¥Users¥kou¥Desktop¥CLangI
• 絶対パスはルートデバイスを基準に • C:¥Users¥kou¥Desktop¥CLangI¥week01¥hello.c
• C:¥Users¥kou¥Desktop¥CLangII¥week01¥hellogui.c
• 相対パスは現在の作業フォルダを基準に • week01¥hello.c
• ..¥CLangII¥week01¥hellogui.c
教科書 pp.30-31. 51
Cygwin の絶対パス、相対パス
• ディレクトリの階層を / で区切る
• 現在の作業ディレクトリは以下の場所 • /cygdrive/c/Users/kou/Desktop/CLangI
• 絶対パスはルートディレクトリ / を基準に • /cygdrive/c/Users/kou/Desktop/CLangI/week01/hello.c
• /cygdrive/c/Users/kou/Desktop/CLangII/week01/hellogui.c
• 相対パスは現在の作業ディレクトリを基準に • week01/hello.c
• ../CLangII/week01/hellogui.c
52
特別なフォルダ名
• 「..」
• 1つ親のディレクトリを意味する
• 「.」
• 現在のディレクトリを意味する
• UNIX では実行ファイルの検索パスに作業ディレクトリが含まれない
• 作業ディレクトリ内にある実行ファイルを実行する際 ./ を実行ファイル名の前に付ける必要がある
53
補足
• フォルダとディレクトリは同じ概念 • Windows 流ではフォルダと呼ぶ • UNIX 流ではディレクトリと呼ぶ
• /cygdrive は cygwin 特有の仮想ディレクトリ • Windows のドライブが配置されている • 通常 UNIX ではパスに : (コロン)を使わないため
• ネットワークドライブもアクセス出来る • UNC 表記を用いる • ¥¥fs.cc.yamaguchi-u.ac.jp¥YUアカウント名 • //fs.cc.yamaguchi-u.ac.jp/YUアカウント名
54
コマンドの検索パス
• 環境変数 PATH に設定されたパスからコマンドを探して実行する
• cmd での確認方法
• 複数のパスは ; (セミコロン)で区切って与える
• bash での確認方法
• 複数のパスは : (コロン)で区切って与える
> echo %PATH%
$ echo $PATH
55
ファイル拡張子の表示 Windows 8 以降
• explorer.exe から 「表示」→「ファイル名拡張子」→ON
ファイル名末尾に .○○○と表示される
教科書 p.27. 56
ファイル拡張子の表示 Windows 7 以前
• explorer.exe から
• 「Alt」→「ツール」→「オプション」→「表示」 →「登録されている拡張子は表示しない」→OFF
Alt叩くとメニュー出る
ファイル名末尾に .○○○と出る
教科書 p.27. 57
ファイル拡張子とは?
• ファイルの種類を表している • アプリケーションとの関連付けに用いられる
• 実行ファイルは .exe や .com • テキスト文書は .txt • Word2007以降は .docx それより前は .doc • Excel2007以降は .xlsx それより前は .xls • PowerPoint2007以降は .pptx それより前は .ppt • 圧縮ファイルは .zip .lzh .tgz .cab 等々 • 等々
• 変更すると開けなくなる • 普通は変更する必要はない • メール等で添付されて来たファイルには注意
• 文書ファイルに見えて実行ファイル(実はウィルス)ということも
58
C コンパイラによるコンパイル
• cmd.exe から bcc32 で行った例 コマンドプロンプト + Borland C++ C:¥Users¥kou¥Desktop¥CLangI2014>dir /B hello.c C:¥Users¥kou¥Desktop¥CLangI2014>bcc32 hello.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland hello.c: 警告 W8070 hello.c 6: 関数は値を返すべき(関数 main ) Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland C:¥Users¥kou¥Desktop¥CLangI2014>dir /B hello.c hello.exe hello.obj hello.tds C:¥Users¥kou¥Desktop¥CLangI2014>hello hello, world
ファイル一覧の表示
コンパイル
ファイル一覧の表示
作成した実行ファイルを実行
教科書 pp.36-42. 59
C コンパイラは何をしているのか?
• デバッグ情報付きコンパイルし逆アセンブル
• .rdata セクション部分 mintty + bash
$ gcc -g hello.c $ objdump -d -S -s a.exe a.exe: ファイル形式 pei-x86-64 ... セクション .rdata の内容: 100403000 63796767 636a2d31 342e646c 6c005f4a cyggcj-14.dll._J 100403010 765f5265 67697374 6572436c 61737365 v_RegisterClasse 100403020 73000000 00000000 00000000 00000000 s............... 100403030 68656c6c 6f2c2077 6f726c64 00000000 hello, world.... 100403040 4743433a 2028474e 55292034 2e382e31 GCC: (GNU) 4.8.1 ... セクション .text の逆アセンブル: ... 00000001004010d0 <main>: #include <stdio.h> 埋め込まれた文字データ 文字データの配置アドレス
文字データの16進表現
教科書 p.17. 60
C コンパイラは何をしているのか?
• .text セクション部分 セクション .text の逆アセンブル: ... 00000001004010d0 <main>: #include <stdio.h> main() { 1004010d0: 55 push %rbp 1004010d1: 48 89 e5 mov %rsp,%rbp 1004010d4: 48 83 ec 20 sub $0x20,%rsp 1004010d8: e8 73 00 00 00 callq 100401150 <__main> printf("hello, world¥n"); 1004010dd: 48 8d 0d 4c 1f 00 00 lea 0x1f4c(%rip),%rcx # 100403030 <.rdata> 1004010e4: e8 77 00 00 00 callq 100401160 <puts> } 1004010e9: 48 83 c4 20 add $0x20,%rsp 1004010ed: 5d pop %rbp 1004010ee: c3 retq 1004010ef: 90 nop ...
アセンブラコードを アセンブルして得られた マシン語のバイトコード
C言語を コンパイルして得られた アセンブラコード
配置アドレス
61
元々の C言語のコード
文字データ"hello, world¥n"の 配置アドレス(前項参照)
サブルーチン(printf)の 呼び出し
C言語入門(変数と定数)
62
基礎知識
63
テキスト編集系ソフトの種類
• テキストエディタ (メモ帳, 秀丸, etc,,,)
• 基本的にプレーンな文字情報のみ、装飾なし
• リッチテキストエディタ (ワードパッド, etc,,,)
• 字体、色、サイズ等の装飾が可能に
• ワードプロセッサ (Word, 一太郎, etc,,,)
• 更に高度な、文書作成支援機能
• DTPツール (InDesign, Publisher, etc,,,)
• 印刷用の版下作成、特に割付へ特化
64
プログラミング向け テキストエディタの機能
• 構文解析機能
• シンタックスハイライト機能
• 行番号表示機能
• 入力補完機能
• マクロ機能
• 正規表現対応の検索機能
• タグジャンプ機能
• 等々
65
テキストエディタ
• 窓の杜 • オフィス / 文書作成 / テキストエディター • http://www.forest.impress.co.jp/library/nav/genre/offc/document_txteditor.html
• 学習・プログラミング / プログラミング / プログラム向けエディター • http://www.forest.impress.co.jp/library/nav/genre/stdy/program_progeditor.html
• Vector • Windows / 文書作成 / テキストエディタ • http://www.vector.co.jp/vpack/filearea/win/writing/edit/
66
テキストエディタ
• サクラエディタ
• http://sakura-editor.sourceforge.net/
• xyzzy
• http://xyzzy-022.github.io/
67
統合開発環境
• Microsoft Visual Studio
• http://www.visualstudio.com/ja-jp/
• Eclipse
• Pleiades - Eclipse プラグイン日本語化プラグイン
• http://mergedoc.sourceforge.jp/
• NetBeans
• https://ja.netbeans.org/
68
コメント
• プログラムとしては解釈されない
• 後で読む人用に注釈をしておく機能
• /* ~ */ の間がコメント
• // から行末までがコメント
教科書 p.46. 69
comment_test.c #include <stdio.h> main() { /* ここがコメント */ printf("hello, world¥n"); // ここもコメント }
1 2 3 4 5 6 7 8 9
緑の部分がコメントとして 扱われる
printf 関数の初歩
• 括弧の中にカンマ「,」で区切って複数の引数(パラメータ)を与える。
• 1つ目の引数は書式
• 書式内の %d %f %s 等の箇所には2つ目以降の引数が順に埋め込まれる
70
printf("1 + 2 = %d¥n", 1 + 2);
1 + 2 = ¥n
ここには int 型の整数型データとして解釈した 2つ目の引数の値(上記の例では1+2の計算結果)が 符号付き10進数にして印字される
教科書 pp.61, 64-66, 98.
¥n は改行として機能する
%d は整数、 %f は実数、 %s は文字列の埋め込み用
演習: 値を表示してみよう(1/2)
• printf の1つ目の引数に "%d¥n", "%f¥n", "%s¥n" を与え、2つ目の引数に、整数、実数、
文字列を与えて、それぞれの組み合わせで、どうなるか試してみよう。
71
printf_practice_11.c #include <stdio.h> void main() { printf("%d¥n", 123); }
1 2 3 4 5 6
コマンドプロンプト + Borland C++ >bcc32 printf_practice_11.c && printf_practice_11 Borland C++ 5.5.1 for Win32 Copyright (c) 1993, ... printf_practive_11.c: Turbo Incremental Link 5.00 Copyright (c) 1997, ... 123
mintty + bash + gcc $ gcc printf_practice_11.c && ./a 123
5行目を書き変えて 計9つのファイルを作りましょう。
演習: 値を表示してみよう(2/2)
72
• 結果を調べて表にまとめてみよう。
• ファイル名は整理番号を用いて printf_practice_XY.c のように付けてください。
• 例えば※2の欄は printf_practice_32.c となります。
整理番号Y 1 2 3
整理番号X 第1引数\第2引数 123 123.456 "123.456"
1 "%d¥n" 123と表示
2 "%f¥n"
3 "%s¥n" ※2
SI接頭辞
名前 記号 乗数
キロ(kilo) K 10001 =103
メガ(mega) M 10002=106
ギガ(giga) G 10003=109
テラ(tera) T 10004=1012
ペタ(peta) P 10005=1015
エクサ(exa) E 10006=1018
ゼタ(zetta) Z 10007=1021
ヨタ(yotta) Y 10008=1024
73
2進接頭辞(IEC/IEEE)
名前 記号 乗数
キビ(kibi) Ki 10241=210
メビ(mebi) Mi 10242=220
ギビ(gibi) Gi 10243=230
テビ(tebi) Ti 10244=240
ペビ(pebi) Pi 10245=250
エクスビ(exbi) Ei 10246=260
ゼビ(zebi) Zi 10247=270
ヨビ(yobi) Yi 10248=280
74
bit と byte
• b – bit
• 2進数 1 桁
• 通信速度や IC の容量表記等
• 例: • 100Mbps (100 Mega bits per seconds)
• B – Byte
• 8bit (半角英数1文字分に相当)
• 記憶メディアの容量表記等
• 例: • 32GB (32 Giga Bytes)
0
1
1bitの記憶素子には 2進数の1桁つまり 0 または 1 のみ 記憶できる
0 0 0 1 1 0 1 1
1bit毎では 単位が小さ過ぎて使い辛い 通常は 8桁を1まとめにして扱う
1bit
8bit = 1byte
8bit = 1byte は 28 = 256 通りの整数を表現可能 符号なし: 0~255 符号あり: −128~127
教科書 pp.50-55. 75
16進数
2進数 10進数 16進数 2進数 10進数 16進数
0b0000 0 0x0 0b1000 8 0x8
0b0001 1 0x1 0b1001 9 0x9
0b0010 2 0x2 0b1010 10 0xa
0b0011 3 0x3 0b1011 11 0xb
0b0100 4 0x4 0b1100 12 0xc
0b0101 5 0x5 0b1101 13 0xd
0b0110 6 0x6 0b1110 14 0xe
0b0111 7 0x7 0b1111 15 0xf
2進数 4桁 → 16進数 1桁 2進数 8桁 → 16進数 2桁 2進数16桁 → 16進数 4桁 2進数32桁 → 16進数 8桁 2進数64桁 → 16進数16桁
2進数 4桁 → 16進数 1桁 に対応 2進数から変換するとキリが良い バイト単位のデータを表す際、読み易い 例: 0b0001001000110100 = 4660 = 0x1234
教科書 pp.50-55. 76
8 bit 整数の N 進数の表現 2進数 符号なし10進数 符号あり10進数 16進数
0b00000000 0 0 0x00
0b00000001 1 1 0x01
0b00000010 2 2 0x02
0b00000011 3 3 0x03
: : : :
0b01111111 127 127 0x7f
0b10000000 128 -128 0x80
: : : :
0b11111100 252 -4 0xfc
0b11111101 253 -3 0xfd
0b11111110 254 -2 0xfe
0b11111111 255 -1 0xff
符号あり整数の場合は最上位ビットを符号ビットとして扱う(2の補数表現)
教科書 pp.50-55.
符号ありは ここで 正負が 入れ替わる
77
16 bit 整数の N 進数の表現 2進数 符号なし10進数 符号あり10進数 16進数
0b0000000000000000 0 0 0x0000
0b0000000000000001 1 1 0x0001
0b0000000000000010 2 2 0x0002
0b0000000000000011 3 3 0x0003
: : : :
0b0111111111111111 32767 32767 0x7fff
0b1000000000000000 32768 -32768 0x8000
: : : :
0b1111111111111100 65532 -4 0xfffc
0b1111111111111101 65533 -3 0xfffd
0b1111111111111110 65534 -2 0xfffe
0b1111111111111111 65535 -1 0xffff
符号あり整数の場合は最上位ビットを符号ビットとして扱う(2の補数表現)
符号ありは ここで 正負が 入れ替わる
教科書 pp.50-55. 78
N bit 整数の最大値・最小値
Bit数 符号あり10進数最小値 符号あり10進数最大値 符号なし10進数最大値
8 -128 127 255
16 -32,768 32,767 65,535
32 -2,147,483,648 2,147,483,647 4,294,967,295
64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807 18,446,744,073,709,551,615
𝑁 −2𝑁−1 2𝑁−1 − 1 2𝑁 − 1
79
Bit数 128
符号あり10進数最小値 170,141,183,460,469,231,731,687,303,715,884,105,727
符号あり10進数最大値 -170,141,183,460,469,231,731,687,303,715,884,105,728
符号なし10進数最大値 340,282,366,920,938,463,463,374,607,431,768,211,456
参考: 128bit の場合
符号なし8bit整数の演算
• 整数オーバーフロー
11111111 = 255
+)00000001 = 1
100000000 = 0
• 整数アンダーフロー
100000000 = 0
-)00000001 = 1
11111111 = 255
有効桁の外に1が溢れた
有効桁外の外から1が溢れた
80
符号あり8bit整数の演算
• 整数オーバーフロー
01111111 = 127
+)00000001 = 1
10000000 = -128
• 整数アンダーフロー
10000000 = -128
-)00000001 = 1
01111111 = 127
• 2の補数表現
100000000 = 0
-)00000001 = 1
11111111 = -1
11111111 = -1
+)00000001 = 1
100000000 = 0
有効桁の外から1を借りてくる
有効桁の外に1を捨てる
符号ビットに1が溢れた
符号ビットから1が溢れた
81
RAM (Random Access Memory)
• コンピュータのメインメモリで利用されている
82
メモリの構成
• 1byte単位でアドレスが振られている
• つまり各アドレスには1byteの値を格納出来る
0x00 0x00000000
0x00 0x00000001
0x00 0x00000002
0x00 0x00000003
0x00 0xffffffff
: :
: :
0x00 0x0000000000000000
0x00 0x0000000000000001
0x00 0x0000000000000002
0x00 0x0000000000000003
0x00 0xffffffffffffffff
: :
: :
32bitのOSは32bitのアドレス空間 最大232Bytes=4GiB
64bitのOSは64bitのアドレス空間 最大264Bytes=16EiB
アドレス 格納値 アドレス 格納値
教科書 pp.52-56. 83
大きい値の扱い方
• 複数のアドレスをまとめて変数に割り当てる
0x00 0x~00
0x00 0x~01
0x00 0x~02
0x00 0x~03
: :
: :
0x00 0x~04
0x00 0x~05
0x00 0x~06
: :
: :
0x00 0x~07
0x00 0x~08
8bit 0x00 0x~00
0x00 0x~01
0x00 0x~02
0x00 0x~03
: :
: :
0x00 0x~04
0x00 0x~05
0x00 0x~06
: :
: :
0x00 0x~07
0x00 0x~08
16bit
84
大きい値の扱い方
• 複数のアドレスをまとめて変数に割り当てる
0x00 0x~00
0x00 0x~01
0x00 0x~02
0x00 0x~03
: :
: :
0x00 0x~04
0x00 0x~05
0x00 0x~06
: :
: :
0x00 0x~07
0x00 0x~08
32bit
0x00 0x~00
0x00 0x~01
0x00 0x~02
0x00 0x~03
: :
: :
0x00 0x~04
0x00 0x~05
0x00 0x~06
: :
: :
0x00 0x~07
0x00 0x~08
64bit
85
変数の宣言、値の代入
• 変数は値を格納する箱のようなもの
int a; // (1) 変数の宣言 a = 10; // (2) 値の代入 // (3)
教科書 pp.59-61.
10
a
10
(1) (2)
a
?
a
?
(3)
int 型の変数を作り a という名前を付ける この時点では 中身は未定
変数 a に 10 を代入する
変数 a に 10 が 代入された状態に なっている
86
変数の宣言と初期化
• 宣言と同時に初期化することも出来る
a
10
int a = 10; // (1) 変数の宣言と初期化
(1)
int 型の変数を作り a という名前を付け 10 を代入する
教科書 pp.59-61. 87
変数への値の代入
• 変数への代入は「=」を用います。C言語において「=」は「イコール」や「等号」ではなく、「代入」を意味する記号です。
• 代入は、右辺の計算結果を左辺の変数に格納します。(左辺には必ず単独の変数を書く)
a = 10; // (1) 値の代入 a = a + 1; // (2) 計算結果の代入
1
(2)
a
10
a
10
計算結果で左辺を 上書きする
まず右辺を 計算して
88
+
変数
• 文法
データ型 変数名; // 変数の宣言 データ型 変数名=初期代入値; // 変数の宣言と初期化 データ型 変数名1, 変数名2; // 複数の変数の宣言 データ型 変数名1=初期代入値1, 変数名2=初期代入値2; // 複数の変数の宣言と初期化 変数名 = 値; // 値の代入
教科書 pp.59-61. 89
リテラル
• ソースコードに直接記述された値
• 数値、文字列等
教科書 p.54. 90
wavetest.c int main(int argc, char *argv[]) { if (argc < 4) { printf("Usage: %s output_file micro_sec MIDI_note_No¥n¥n", argv[0]); return EXIT_FAILURE; } FILE *fp; if (fp = fopen(argv[1], "wb")) { fwrite_wav(44100, atoi(argv[2]), atoi(argv[3]), fp); fclose(fp); } return EXIT_SUCCESS; }
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
赤字で示したような部分が リテラルに当たる
整数のデータ型
• signed char, unsigned char • 1バイト、局所的な文字セット内に1文字を保持し得る
• signed short, unsigned short • 少なくとも16ビット
• signed int, unsigned int • 少なくとも16ビット • 通常ホスト計算機の自然な整数サイズ • 現在は 32 or 64 ビットであることが多い
• signed long, unsigned long • 少なくとも32ビット
• 明確にビット数は定められていない • 割り当てビット数の大小関係は short <= int <=long
• 型の前に signed を付けると符号ありの型になる • 通常 signed は省略する(書かない)
• 型の前に unsigned を付けると符号なしの型になる
教科書 pp.53-55., [1] pp.44-45.
コンパイルする環境により 使える値の最大値・最小値が 異なる!
91
整数のデータ型
サイズ 符号あり 符号なし 備考
1バイト signed char unsigned char 主に文字、文字列、バイナリデータ用
16bit以上 signed short unsigned short
16bit以上 signed int unsigned int 通常はこれを使う
32bit以上 signed long unsigned long
教科書 pp.53-55., [1] pp.44-45. 92
整定数のリテラル
• 何も指定しないと int 型
• 末尾に接尾子(U, L)を付けると型指定される
int i = 1234; // int 型 long l = 1234L; // long 型 unsigned int ui = 1234U; // unsigned int 型 unsigned long ul = 1234UL; // unsigned long 型
93
C言語のN進数リテラル
• 0b~ 2進数リテラル(C++14の仕様*1)
• 0~ 8進数リテラル
• 0x~ 16進数リテラル
• *1: 少なくとも gcc4.3, Clang3.2 では実装済み
• 古いコンパイラでは2進数リテラルは使えない
• 例えばBorland C++ で 0b~ は使えない
int dec = 100; // 10進数の100 int bin = 0b100; // 2進数の100=10進数の 4 int oct = 0100; // 8進数の100=10進数の 64 int hex = 0x100; // 16進数の100=10進数の256
教科書 p.54. 94
2015-04-25追加
演習: 値を表示してみよう(1/2)
• printf の1つ目の引数に "%d¥n" を与え、2つ目の引数に、前のページの dec, bin, oct, hex を与えてみよう。
• ファイルは dec, bin, oct, hex の4つ作る。
95
printf_practice_hex.c #include <stdio.h> void main() { int hex = 0x100; printf("%d¥n", hex); }
1 2 3 4 5 6 7
コマンドプロンプト + Borland C++ >bcc32 printf_practice_hex.c && printf_practice_hex Borland C++ 5.5.1 for Win32 Copyright (c) 1993, ... printf_practive_11.c: Turbo Incremental Link 5.00 Copyright (c) 1997, ... 256
mintty + bash + gcc $ gcc printf_practice_hex.c && ./a 256
5,6行目を書き変えて 計4つのファイルを作りましょう。
浮動小数点数のデータ型
• float 単精度浮動小数点数
• double 倍精度浮動小数点数
• long double 拡張精度の浮動小数点数
教科書 pp.55-56. 96
浮動小数点数とは
• IEEE754
• http://ja.wikipedia.org/wiki/IEEE_754
• 以下のようなに表現する方法
±仮数部 × 2指数部
ビット数 符号 指数部 仮数部
単精度 32bit 1bit 8bit 23bit
倍精度 64bit 1bit 11bit 53bit
四倍精度 128bit 1bit 15bit 112bit
指数部の値で 小数点の位置が移動するので 浮動小数点と呼ばれる
教科書 pp.55-56. 97
浮動小数点数定数のリテラル
• 何も指定しないと double 型
• 末尾に接尾子(F, L)を付けると型指定される
• 1.234E5 のような書き方も出来る(指数表現)
• これは1.234 × 105を意味する
float f = 1234F; // float 型 double d = 1234; // double 型 long double ld = 1234L; // long double 型 double e = 1.234E5; // double 型の 123400
98
固定小数点数
• 10進数8桁を4桁ずつに分けた例
• 2進数も8桁も同様に4桁ずつに分けてみる
備考 99
1 2 3 4 . 5 6 7 8
103 102 101 100 . 10−1 10−2 10−3 10−4 の桁
0 0 0 1 . 1 0 1 1
23 22 21 20 . 2−1 2−2 2−3 2−4 の桁
2015-04-25追加
0000.0000~9999.9999 まで表現可能
0b0000.0000~0b1111.1111 まで表現可能
指数表示(浮動小数点数)
• 10進数8桁を4桁ずつに分けた例
• 2進数も8桁も同様に4桁ずつに分けてみる
備考 100
1 2 3 4 ×10^ 1 2 3 4
103 102 101 100 103 102 101 100 の桁
2015-04-25追加
0 0 0 1 ×2^ 0 0 0 1
23 22 21 20 23 22 21 20 の桁
0000×10^0000~9999×10^9999 まで表現可能
0b0000×2^0b0000~0b1111×2^0b1111 まで表現可能
ASCII文字コード表
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI 1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC → ← ↑ ↓ 2 SP ! " # $ % & ' ( ) * + , - . / 3 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 4 @ A B C D E F G H I J K L M N O 5 P Q R S T U V W X Y Z [ ¥ ] ^ _ 6 ` a b c d e f g h i j k l m n o 7 p q r s t u v w x y z { | } ~ DEL 8 9 A B C D E F
上位4ビット
下位4ビット
赤字は制御コード
教科書 p.51.
http://ja.wikipedia.org/wiki/ASCII
101
制御コード HEX Abbr ctrl eseq Name Hex Abbr ctrl eseq Name
0x00 NUL ^@ ¥0 Null 0x10 DLE ^P Data Link Escape
0x01 SOH ^A Start of Heading 0x11 DC1 ^Q Device Control 1
0x02 STX ^B Start of Text 0x12 DC2 ^R Device Control 2
0x03 ETX ^C End of Text 0x13 DC3 ^S Device Control 3
0x04 EOT ^D End of Transmission 0x14 DC4 ^T Device Control 4
0x05 ENQ ^E Enquiry 0x15 NAK ^U Negative Acknowledgement
0x06 ACK ^F Acknowledgement 0x16 SYN ^V Synchronous idle
0x07 BEL ^G ¥a Bell 0x17 ETB ^W End of Transmission Block
0x08 BS ^H ¥b Back Space 0x18 CAN ^X Cancel
0x09 HT ^I ¥t Horizontal Tab 0x19 EM ^Y End of Medium
0x0a LF ^J ¥n Line Feed 0x1a SUB ^Z Substitute
0x0b VT ^K ¥v Vertical Tab 0x1b ESC ^[ ¥e Escape
0x0c FF ^L ¥f Form Feed 0x1c FS ^¥ File Separator
0x0d CR ^M ¥r Carriage Return 0x1d GS ^] Group Separator
0x0e SO ^N Shift Out 0x1e RS ^^ Record Separator
0x0f SI ^O Shift In 0x1f US ^_ Unit Separator
0x20 SP Space 0x7f DEL ^? Delete
教科書 p.51. 102
文字定数のリテラル
• 1文字を単一の引用符(')で囲む
• char型の値になる
char a = 'a'; //「a」の文字コード0x61 char lf = '¥012'; //改行の文字コード0x0aを8進数で char vt = '¥x0b'; //垂直タブの文字コード0x0bを16進数で char cr = '¥r'; //復帰の文字コード0x0dを //エスケープシーケ1ンスで
教科書 p.56. 103
単一の引用符(')は
↑Shift '
7 +
演習: 値を表示してみよう3(1/2)
• printf_practice_hex.c を元にして以下の点を変更して動作を確認せよ。
• ファイル名をコピーして以下の4ファイルを通る。
• printf_practice_a.c
• printf_practice_lf.c
• printf_practice_vt.c
• printf_practice_cr.c
104 2015-04-25追加
演習: 値を表示してみよう3(2/2)
• 5行目はファイル名と対応させて2ページ前のa, lf, vt, cr の宣言と初期化に書き換え。
• 6行目は %d を %x に書き換え。hex は、対応する a, lf, vt, cr に書き換え
105
printf_practice_a.c #include <stdio.h> void main() { char a = 'a'; printf("%x¥n", a); }
1 2 3 4 5 6 7
コマンドプロンプト + Borland C++ >bcc32 printf_practice_a.c && printf_practice_a Borland C++ 5.5.1 for Win32 Copyright (c) 1993, ... printf_practive_11.c: Turbo Incremental Link 5.00 Copyright (c) 1997, ... 61
mintty + bash + gcc $ gcc printf_practice_a.c && ./a 61
5,6行目の赤字部分を書き変え 計4つのファイルを作りましょう。
2015-04-25追加
エスケープシーケンス
¥a 警告(ベル)文字
¥b バックスペース
¥f 改頁(フォームフィード)
¥n 改行
¥r 復帰
¥t 水平タブ
¥v 垂直タブ
¥¥ バックスラッシュ
¥? 疑問符
¥' 単一引用符
¥" 二重引用符
¥ooo 8進数 ooo (*1)
¥xhh 16進数 hh (*2)
教科書 p.56.
(*1) ooo は1桁ないし 3桁の8進数を取る
(*2) hh は1桁あるいは 2桁の16進数を取る
これらの表記は、 1文字(=1バイト)の値を表す。
106
幅広文字定数のリテラル
• char型では表せない拡張文字セット用
• 文字定数の前に L を付ける
• wchar_t型の値になる
wchar_t hira_a = L'あ'; // 「あ」の文字コードU+3043?
備考
Wikipedia / JIS_X_0213非漢字一覧#1面4区 http://ja.wikipedia.org/wiki/JIS_X_0213非漢字一覧#1.E9.9D.A24.E5.8C.BA
107
文字列定数のリテラル
• 0個以上の文字を二重引用符(")で囲む
• char型の配列になる(後述)
二重引用符(")は
↑Shift "
2 +
char emp[] = ""; // 空の文字列 char str[] = "I am a string"; // 文字列 char cat[] = "hello, " "world"; // "hello, world" と同じ // 連続した文字定数リテラルはコンパイル時に連結される
教科書 pp.44, 96-99. 108
宿題
• 次回までに以下の事をやっておくこと。
• 教科書の第2章の終わりまで読み、指示された操作を試して動作を確認する。
• 不明な点、疑問点についてメモし、次回の授業に持参する。または、本講義の Moodle コース上にある第1週宿題用フォーラムに書き込んでおく。
109
C言語入門 第3週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
110
先週の復習
リダイレクト
111
出力のリダイレクト
• 書式: コマンド > ファイル
• コマンドの出力をファイルに繋ぐ
• コマンド実行時の出力をファイルに保存出来る。
• 例: 第1週の hello.c について
112
mintty + bash ./a > hello_result.txt
コマンドプロンプト hello > hello_result.txt
hello_result.txt hello, world
値の表示
printf 関数
113
値の表示
• printf 関数を使う
教科書 pp.61, 64-66, 98.
printftest.c #include <stdio.h> #include <stdlib.h> int main() { int i = 128; double d = 123e-3; char s[] = "hello, world"; printf("i: %d¥n", i); printf("d: %f¥n", d); printf("s: %s¥n", s); return EXIT_SUCCESS; }
mintty+bash+gcc $ gcc printftest.c && ./a i: 128 d: 0.123000 s: hello, world
いろんな値を表示できる。
114
1 2 3 4 5 6 7 8 9 10 11 12 13
printf 関数
• int printf(const char *FORMAT, ...);
• 引数:
• FORMAT: 書式
• ...: 任意の数の引数
• 戻り値:
• 書き出された文字数。
• エラーの場合負の数。
教科書 pp.61, 64-66, 98.
参考: [1] pp.305-306.
115
printf: 書式
• %~変換文字までをフィールドと呼ぶび、テンプレート(穴空き定規)ように扱われる
• フィールドは以下の要素から成る • %[フラグ][最小フィールド幅][.精度][長さ修飾子]変換文
字 printf("1 + 2 = %d¥n", 1 + 2);
1 + 2 = ¥n
ここに、 int型の整数型データとして解釈した 2つ目の引数の値(上記の例では1+2の計算結果)を 符号付き10進数にして印字する
教科書 pp.61, 64-66, 98.
参考: [1] pp.305-306.
116
printf: フラグ
• -: 左揃えで印字
• +: 数を符号付きで印字
• スペース: 最初の文字が符号でない場合スペースを前に付ける
• 0: フィールド幅いっぱいに左側から0を詰める
• #: 別の出力形式を指定。
• o: 先頭の桁を0にする
• x: 0でない結果の先頭を0xにする
• e,f,g: 出力に必ず小数点を付ける
• g: 末尾の0を削除しない
教科書 pp.61, 64-66, 98.
参考: [1] pp.305-306.
117
printf: 最小フィールド幅
• 変換された引数は少なくともこの幅になる。
• 必要ならもっと広い幅のフィールドに印字。
• 変換された引数がフィールド幅よりも短い場合padding(=詰め物)が行われる。
• paddingは通常はスペース。フラグに0が指定された場合は0が用いられる。
• *: 次の引数の値を用いる
教科書 pp.61, 64-66, 98.
参考: [1] pp.305-306.
118
printf: .精度
• 「.」(ピリオド): フィールド幅と精度の分離子(separator)
• 文字列に対しては印字する最大文字数
• e,fの対しては小数点以下に印字すべき桁数
• gに対しては有効数字の桁数
• 整数に対しては印字すべき最小桁数(頭に0が付加される)
• *: 次の引数の値を用いる
教科書 pp.61, 64-66, 98.
参考: [1] pp.305-306.
119
printf: 長さ修飾子
• h: short または float として扱う
• l: long として扱う
• L: long double として扱う
教科書 pp.61, 64-66, 98.
参考: [1] pp.305-306.
120
printf: 変換文字
文字 変換後の引数の型
d, i int; 符号付き10進数
o int; 符号なし8進数
x, X int; 符号なし16進数
u int; 符号なし10進数
c int; unsigned char に変換された後の単一文字
s char *; 文字列を文字列終端('¥0')または指定された桁まで
f double; [-]mmm.dddddd 形の10進数。dの桁数は精度で指定
e, E double; [-]m.dddddde±xx型の10進数。dの桁数は精度で指定
g, G double; 指数が-4より小さいか精度以上の場合%e、それ以外は%f扱い
p void *; ポインタとして印字(処理系依存)
n int *; このprintfでここまでに書き出された文字数を引数に書き込む
% %を印字
参考: [1] pp.305-306.
教科書 pp.61, 64-66, 98. 121
printf の詳細
• ここでは概略しか示せていないのと一部不正確な部分もあるので、詳細は bash から man コマンドを用いて以下の方法で確認すること
• 邦訳は以下のページ • http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/printf.3.html
mintty+bash man sprintf
122 教科書 pp.61, 64-66, 98.
mintty+bash man 3 printf
3 はマニュアルのセクション番号を意味する。 セクション 3 はサブルーチン (つまりライブラリ関数) 関連のマニュアル http://linuxjf.sourceforge.jp/JFdocs/Man-Page-2.html printf はセクション 1 にもあるので sprintf かセクション 3 の printf を引く必要がある。
2015-04-27 修正&追記
printf のマニュアル導入
• printf のマニュアルが引けない場合、 以下のコマンドを mintty+bash から実行
123
cygwin-doc パッケージのインストール
mintty+bash apt-cyg install cygwin-doc
教科書 pp.61, 64-66, 98.
演習: 円の面積を計算せよ(1)
• area_of_a_circle1.c をダウンロードして /*WYCH1*/ の部分を書き変えることで以下のプログラムを完成させなさい。/*WYCH2*/ /*WYCH3*/ の部分は次の演習で変更するので今はまだ書き変えない事。なお WYCH は Write Your Code Here を略している。 • double 型の変数 r に円の半径を代入する。 • 円の面積𝑆は公式𝑆 = 𝜋𝑟2を用いて計算し、結果は小数点以下2桁
まで出力する。出力には printf と "%f" を用いれば良いが、前述の精度を設定する必要がある。
• 円の面積を表示する前に、確認のため計算に用いる半径も表示する。
• r に代入する半径はソースコードにリテラル値として直接埋め込む。異なる半径面積を計算したい時は、ソースコードのリテラル値を書き変えてコンパイルし直すこととする。
• 半径を 1~10まで 1 刻みで増やして 計10 個の面積を計算せよ。
124
2015-04-27 追記
マクロ
• preprocessor のキーワード置換機能
• 書式: #define マクロ名 置換内容
• 定数等に名前を付ける際に使う
125
macrotest1.c #include <stdio.h> #define MSG "world" void main() { printf("hello, %s¥n", MSG); }
教科書 p.68.
1 2 3 4 5 6 7 8
MSGはコンパイル前に "world"で置換される
2015-04-25修正 誤:area_of_a_circle1.c 正:macrotest1.c
マクロ
• コンパイル時のオプション -D で外部から与えることも出来る。 macrotest2.c
#include <stdio.h> void main() { printf("hello, %s¥n", MSG); }
mintty+bash+gcc $ gcc -DMSG="¥"kou¥"" macrotest2.c && ./a hello, kou
126
1 2 3 4 5
コマンドプロンプト+Borland C++
>bcc32 -DMSG="¥"kou¥"" macrotest2.c && macrotest2 Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland macrotest.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland hello, kou
普通はこのままコンパイルしても MSGがないためエラーになる
2015-04-25修正 誤:extmacrotest2.c 正:macrotest2.c
マクロ
• PI に関しては実は math.h で提供されている
127
mintty+bash $ grep M_PI /usr/include/math.h #define M_PI 3.14159265358979323846 #define M_PI_2 1.57079632679489661923 #define M_PI_4 0.78539816339744830962 #define M_TWOPI (M_PI * 2.0)
教科書 p.68.
マクロ
• 先程の EXIT_SUCCESS や SCNd32 もマクロ
128
mintty+bash $ grep "#define.EXIT_" /usr/include/stdlib.h #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 $ grep SCNd32 /usr/include/inttypes.h #define SCNd32 "d"
それぞれの環境に 適切な数値や文字列等が設定されている
教科書 p.68.
ファイルの包含
• preprocessor のファイル取り込み機能
• 別のファイルに記述されたプログラムやマクロ等を取り込む際に使う
• 書式: • #include <ファイル名>//システム提供ファイル
用 • /usr/include 等から探して取り込む
• #include "ファイル名"//ユーザー作成ファイル用 • 作業ディレクトリから探して取り込む
129 教科書 pp.203-206.
grep コマンド
• grep [OPTIONS] PATTERN [FILE ...] • 検索文字列を含むファイルを検索する
• 引数 • PATTERN : 正規表現等による検索文字列 • FILE : 検索対象のファイルやディレクトリ
• OPTIONS • -R : ディレクトリ下のすべてのファイルを検索 • -n : 行番号を表示 • -A NUM : マッチ位置の後NUM行も表示 • -B NUM : マッチ位置の前NUM行も表示 • -C NUM : マッチ位置の前後NUM行も表示
• マニュアル邦訳 • http://linuxjm.sourceforge.jp/html/GNU_grep/man1/grep.1.html
備考: UNIX コマンド 130
正規表現
表記 意味
c 文字c
¥c 文字¥c
. 任意の一文字
[...] []内の任意の一文字
[^...] []内に含まれない任意の一文字
* 直前のパターンが0回以上反復
+ 直前のパターンが1回以上反復
? 直線のパターンが0または1回出現
| 前後の正規表現の何れか
(...) ()内の正規表現をグループ化
^ 行頭にマッチ
$ 行末にマッチ
備考: UNIX コマンド 131
演習: 円の面積を計算せよ(2)
• 先程完成させた area_of_a_circle1.c をコピーして area_of_a_circle2.c を作成し /*WYCH1*/ /*WYCH2*/ の部分を書き変えることで以下のプログラムを完成させなさい。 • r に代入する半径としてマクロ R を代入することで、異
なる半径面積を計算したい時は、コンパイル時に -D オプションを用いて 「-DR=1」 のよう外部から値を与えることで、ソースコードを変更なしに、コンパイルし直すだけで済むように変更せよ。
• 𝜋の値はリテラル値を直接書き込むのではなく math.h で定義されたマクロ M_PI を用いるように変更せよ。
• 半径を 1~10まで 1 刻みで増やして 計10 個の面積を計算せよ。
132
fprintf 関数
• int fprintf(FILE *fp, const char *FORMAT, ...);
• printfの結果をfpへ書き出す
• 引数: • fp: FILE 構造体へのポインタ
• FORMAT: 書式
• ...: 任意の数の引数
• 戻り値: • 書き出された文字数
• エラーの場合負の数
教科書 pp.61, 64-66, 98, 300.
参考: [1] pp.305-306.
133
標準入出力と標準エラー出力
• 以下の入出力が利用できる • stdin : standard input : 標準入力
• stdout: standard output : 標準出力
• stderr: standard error output: 標準エラー出力
• scanf や getchar 等は stdin から入力している
• printf や putchar 等は stdout へ出力している
• stdin, stdout はパイプやリダイレクトの対象だが stderr は標準では対象外(指定すれば対象にすることも可能)
[1] pp.196, 199, 218. 134
標準入出力と標準エラー出力
• パイプやリダイレクトで処理されたくない内容は stderr へ出力すると良い
• fprintf を使うと、出力先を変更出来る
[1] pp.196, 199, 218. 135
stdiotest.c printf("output to stdout with printf¥n"); fprintf(stdout, "output to stdout with fprintf¥n"); fprintf(stderr, "output to stderr with fprintf¥n"); mintty + bash
$ gcc stdiotest.c $ ./a > redirect.txt output to stderr with fprintf $ cat redirect.txt output to stdout with printf output to stdout with fprintf
6 7 8
← 標準出力(stdout)を redirect.txt へリダイレクト ← fprintf で明示的に stderr へ出力した結果(*1) ← redirect.txtに出力された内容を表示 ← printf で暗黙的に stdout へ出力した結果 ← fprintf で明示的に stdout へ出力した結果
以下の例では(*1)がファイルへ出力されず 画面に出力されている事が確認出来る。
標準入出力と標準エラー出力
• stdin,stdout,stderrはstdio.hで定義されている
• stdio.h は standard input / output header
[1] pp.196, 199, 218. 136
値の読み込み
scanf 関数
137
値の読み込み
• scanf関数を使う
教科書 pp.80-83, 254.
mintty+bash+gcc
scanftest.c int i; double d; char s[16]; fprintf(stderr, "i = ?¥b"); scanf("%d", &i); fprintf(stderr, "d = ?¥b"); scanf("%lf", &d); fprintf(stderr, "s = ?¥b"); scanf("%s", s); printf("i: %d¥n", i); printf("d: %f¥n", d); printf("s: %s¥n", s);
$ gcc scanftest.c && ./a i = 1234 d = 1234e-5 s = hello, world i: 1234 d: 0.012340 s: hello,
キーボードから入力した値を 変数に保存して利用出来る
138
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
scanf 関数
• int scanf(const char *FORMAT, ...);
• 引数:
• FORMAT: 書式
• ...: 任意の数の引数 値を格納する変数へのポインタ
• 戻り値:
• 変換され代入された入力項目の数。
• ファイル終端またはエラーの場合EOF。
教科書 pp.80-83, 254.
参考: [1] pp.307-309.
139
scanf: 書式
• スペース、タブ: 無視される
• (%でない)普通の文字: 入力の次の空白でない文字とマッチ
• 変換仕様: • %[*][最大フィールド幅][ターゲット幅]変換文字
int a; scanf("%d", &a);
教科書 pp.80-83, 254.
参考: [1] pp.250, 307-309.
140
入力文字列を10進数として扱い int型の整数型変数へ代入
スカラ変数の前には & を付ける 配列変数、ポインタ変数には不要
&: アドレス演算子 変数へのポインタを得る
scanf: 変換仕様
• *: 入力フィールドはスキップされる 代入抑止
• 最大フィールド幅: 読み込む最大文字数
• ターゲット幅:
• h: int を short に
• l: int を long に、float を double に
• L: float を long doubleに
教科書 pp.80-83, 254.
参考: [1] pp.307-309.
141
scanf: 変換文字
文字 入力データ; 引数の型
d 10進数; int *
i 整数; int * (頭に0,0xが付くと8,16進数とみなす)
o 8進数; int *
u 符号なし10進数; unsigned int *
x 16進数; int *
c 文字; char * (末尾に'¥0'を付加しない)
s 非空白文字の文字列; char * (末尾に'¥0'を付加)
e,f,g 浮動小数点数; float *
p printf("%p") で印字されるポインタ値; void *
n これまでに読み込まれた文字数; int *
[...] [...]+; char * (末尾に'¥0'を付加)
[^...] [^...]+; char * (末尾に'¥0'を付加)
% %; 参考: [1] pp.307-309.
教科書 pp.80-83, 254. 142
scanf の詳細
• ここでは概略しか示せていないのと一部不正確な部分もあるので、詳細は bash から man コマンドを用いて以下の方法で確認すること
• 邦訳は以下のページ • http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/scanf.3.html
教科書 pp.80-83, 254. 143
mintty+bash man scanf
scanf の引数とポインタ
• 値の代入するには変数のアドレスが必要
0x?? 0x~00
0x?? 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
32bit
int a; scanf("%d", &a);
a
0x????????
144
&a
&: アドレス演算子 変数が配置されているメモリ上のアドレスが得られる このアドレスのことをC言語ではポインタと呼ぶ
教科書 pp.80-83, 254.
scanf に値の格納先の アドレスを渡す
scanf: C99 の stdint.h の場合
参考: [1] pp.307-309.
補足 145
• #include<inttypes.h> してSCN~を使う
• "%hd" → "%"SCNd16
• "%d" → "%"SCNd32
• "%u" → "%"SCNu32
実装依存なので 欲しい桁数が扱えないかも?
使う bit 数を確実に保証出来る
buffer overflow の脆弱性
• 確保した配列よりも長い文字列を入力
備考 146
mintty+bash+gcc $ gcc scanftest.c && ./a i = 1234 d = 1234e-5 s = 0123456789abcdefg@@@@@@@@@@@@@@@@ i: 1077952576 d: 32.501961 s: 0123456789abcdefg@@@@@@@@@@@@@@@@
他の変数の領域を 侵食してしまう
buffer overflow の脆弱性の仕組み
• メモリ上の変数の割り当て 0x?? 0x~00
0x?? 0x~0f
0x?? 0x~10
: :
: :
0x?? 0x~17
0x?? 0x~1c
char s[16];
147
0x?? 0x~1f
: :
: :
: :
: :
double d;
int i;
確保したサイズ以上の データを書き込むと 他の変数のデータを 上書きしてしまう。
備考
buffer overflow の脆弱性の対策
• 最大フィールド幅を明記する!
• "%s" → "%15s"
148
終端文字列'¥0'も格納する必要があるため、 最大フィールド幅は 確保したバイト数 -1 以下にする必要がある。 char s[16]; なら最大15文字まで
備考
演習: 円の面積を計算せよ(3)
• 先程完成させた area_of_a_circle2.c をコピーして area_of_a_circle3.c を作成し /*WYCH1*/ /*WYCH2*/ /*WYCH3*/ の部分を書き変えることで以下のプログラムを完成させなさい。 • r に代入する半径を実行時にキーボードから入力する
ことで、コンパイルし直さなくても半径を変更出来るように変更せよ。scanf と "%lf" を利用すれば良い。
• r の入力を求める際は "r = ?¥b" を標準エラー出力に予め表示せよ。なお ¥b はバックスペースを表すエスケープシーケンスである。
• 半径を 1~10まで 1 刻みで増やして 計10 個の面積を計算せよ。
149
演算子
150
sizeof 演算子
• コンパイル時に変数やデータ型の割り当てバイト数を求める演算子
• sizeof オブジェクト
• sizeof(型名)
教科書 p.78, 84, 195. 151
sizeof 演算子の例1
• 例)各データ型の割り当てバイト数
教科書 p.78, 84, 195.
sizeof_ex1.c printf("sizeof(char) : %2d¥n", sizeof(char)); printf("sizeof(wchar_t) : %2d¥n", sizeof(wchar_t)); printf("sizeof(short) : %2d¥n", sizeof(short)); printf("sizeof(int) : %2d¥n", sizeof(int)); printf("sizeof(long) : %2d¥n", sizeof(long)); #ifndef __BORLANDC__ printf("sizeof(long long) : %2d¥n", sizeof(long long)); #endif printf("sizeof(float) : %2d¥n", sizeof(float)); printf("sizeof(double) : %2d¥n", sizeof(double)); printf("sizeof(long double): %2d¥n", sizeof(long double));
152
7 8 9 10 11 12 13 14 15 16 17
各データ型のサイズ(1/5)
• sizeof_ex1.c による比較
32 bit 版 Cygwin + GNU C
64 bit 版 Cygwin + GNU C
$ gcc sizeof_ex1.c && ./a sizeof(char) : 1 sizeof(wchar_t) : 2 sizeof(short) : 2 sizeof(int) : 4 sizeof(long) : 4 sizeof(long long) : 8 sizeof(float) : 4 sizeof(double) : 8 sizeof(long double) : 12
$ gcc sizeof_ex1.c && ./a sizeof(char) : 1 sizeof(wchar_t) : 2 sizeof(short) : 2 sizeof(int) : 4 sizeof(long) : 8 sizeof(long long) : 8 sizeof(float) : 4 sizeof(double) : 8 sizeof(long double): 16
コンパイルする環境により 割り当てビット数や 最大値と最小値が異なる可能性がある
153
各データ型のサイズ(2/5)
• sizeof_ex1.c による比較
Borland C++ 5.5
>bcc32 sizeof_ex1.c && sizeof_ex1 Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland sizeof_ex1.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland sizeof(char) : 1 sizeof(wchar_t) : 2 sizeof(short) : 2 sizeof(int) : 4 sizeof(long) : 4 sizeof(float) : 4 sizeof(double) : 8 sizeof(long double): 10
コンパイルする環境により 割り当てビット数や 最大値と最小値が異なる可能性がある
154
各データ型のサイズ(3/5)
• sizeof_ex1.c による比較
Visual Studio 2013 Express Desktop Windows 32 bit 版 >cl sizeof_ex1.c && sizeof_ex1 Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86 Copyright (C) Microsoft Corporation. All rights reserved. sizeof_ex1.c Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:sizeof_ex1.exe sizeof_ex1.obj sizeof(char) : 1 sizeof(wchar_t) : 2 sizeof(short) : 2 sizeof(int) : 4 sizeof(long) : 4 sizeof(long long) : 8 sizeof(float) : 4 sizeof(double) : 8 sizeof(long double): 8
コンパイルする環境により 割り当てビット数や 最大値と最小値が異なる可能性がある
155
各データ型のサイズ(4/5)
• sizeof_ex1.c による比較
Visual Studio 2013 Express Desktop Windows 64 bit 版 >cl sizeof_ex1.c && sizeof_ex1 Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64 Copyright (C) Microsoft Corporation. All rights reserved. sizeof_ex1.c Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:sizeof_ex1.exe sizeof_ex1.obj sizeof(char) : 1 sizeof(wchar_t) : 2 sizeof(short) : 2 sizeof(int) : 4 sizeof(long) : 4 sizeof(long long) : 8 sizeof(float) : 4 sizeof(double) : 8 sizeof(long double): 8
コンパイルする環境により 割り当てビット数や 最大値と最小値が異なる可能性がある
156
各データ型のサイズ(5/5)
gcc 32bit gcc 64bit bcc32 cl 32bit cl 64bit
char 1 1 1 1 1
wchar_t 2 2 2 2 2
shor 2 2 2 2 2
int 4 4 4 4 4
long 4 8 4 4 4
long long 8 8 - 8 8
float 4 4 4 4 4
double 8 8 8 8 8
long double 12 16 10 8 8
157
データ型のサイズ C99における解決方法
• stdint.h ヘッダファイルを使う
#include <stdint.h> // ... int8_t i8; // 符号付き 8bit整数 uint8_t ui8; // 符号なし 8bit整数 int16_t i16; // 符号付き16bit整数 uint16_t ui16; // 符号なし16bit整数 int32_t i32; // 符号付き32bit整数 uint32_t ui32; // 符号なし32bit整数 int64_t i64; // 符号付き64bit整数 uint64_t ui64; // 符号なし64bit整数
第1週のサンプルプログラム • wavtest.c • bmptest.c でも使っています。
注: Boarland C++ 5.5 は C99 非対応なので stdint.h が使えない。
補足 158
sizeof 演算子の例2
• 例)変数やリテラルの割り当てバイト数
教科書 p.78, 84, 195.
mintty+bash+gcc $ gcc sizeof_ex2.c && ./a sizeof(i) : 4 sizeof(d) : 8 sizeof(s) : 13 sizeof( 1 ): 4 sizeof( 1.): 8 sizeof("1"): 2
sizeof_ex2.c int i = 128; double d = 123e-3; char s[] = "hello, world"; printf("sizeof(i) : %2d¥n", sizeof(i)); printf("sizeof(d) : %2d¥n", sizeof(d)); printf("sizeof(s) : %2d¥n", sizeof(s)); printf("sizeof( 1 ): %2d¥n", sizeof( 1 )); printf("sizeof( 1.): %2d¥n", sizeof( 1.)); printf("sizeof(¥"1¥"): %2d¥n", sizeof("1"));
159
6 7 8 9 10 11 12 13 14
sizeof 演算子の例3
• 例)配列変数の割り当てバイト数
教科書 p.78, 84, 195.
sizeof_ex3.c int a[10]; printf("sizeof(int) : %2d¥n", sizeof(int)); printf("sizeof(a) : %2d¥n", sizeof(a)); printf("sizeof(a[0]) : %2d¥n", sizeof(a[0])); printf("sizeof(a)/sizeof(a[0]) : %2d¥n", sizeof(a)/sizeof(a[0]));
160
mintty+bash+gcc $ gcc sizeof_ex3.c && ./a sizeof(int) : 4 sizeof(a) : 40 sizeof(a[0]) : 4 sizeof(a)/sizeof(a[0]) : 10
7 8 9 10 11 12 13
← int型の割り当てバイト数 ← 配列変数a の割り当てバイト数 ← 変数a[0]の割り当てバイト数 ← 配列変数a の要素数
型変換(cast)演算子
• (変換したい型) 値
教科書 p.70, 84.
casttest.c int a = 1; int b = 2; double x = a / b; double y = a / (double) b; printf("%f¥n", x); printf("%f¥n", y);
mintty+bash+gcc $ gcc casttest.c && ./a 0.000000 0.500000
整数同士の割り算だと 1/2 が 0 になっている。
int型の b の値を double 型に変換
161
cast 演算子 (type) 値 type: 任意のデータ型
6 7 8 9
10 11 12
暗黙の算術変換 (概略)
• 二項演算子の両辺が異なる型の場合 以下の手順で型を変換(符号ありの場合)
• 基本的には大きい方へと型をそろえて行く処理
1. 片方がlong double: 他方をlong doubleに変換
2. 片方がdouble: 他方を doubleに変換
3. 片方がfloat: 他方をfloatに変換
4. char, shortをintに変換
5. 片方がlong: 他方をlongに変換
[1] pp.52-57, 240-244. 162
算術演算子
算術演算子 演算子の機能 書式
単項演算子 + 被演算数の値 + expr
- 被演算数の符号反転 - expr
二項演算子 + 加算 expr1 + expr2
- 減算 expr1 – expr2
* 乗算 expr1 * expr2
/ 除算 expr1 / expr2
% 剰余算 expr1 % expr2
163 教科書 p.69, 84.
代入演算子、複合代入演算子
代入演算子 演算子の機能 書式
代入演算子 = 代入 var = expr
複合代入演算子
+= 加算 var += expr
-= 減算 var –= expr
*= 乗算 var *= expr
/= 除算 var /= expr
%= 剰余算 var %= expr
&= ビット毎のAND var &= expr
^= ビット毎のXOR var ^= expr
|= ビット毎のOR var |= expr
<<= 左シフト var <<= expr
>>= 右シフト var >>= expr
164 教科書 pp.75-79, 84.
複合代入演算子はvar=var+exprのような演算と代入を同時に行う
bit演算子
算術演算子 演算子の機能
単項演算子 ~ 1の補数 ~ expr
二項演算子 << 左シフト expr1 << expr2
>> 右シフト expr1 >> expr2
& ビット毎のAND expr1 & expr2
^ ビット毎のXOR expr1 ^ expr2
| ビット毎のOR expr1 | expr2
165 教科書 pp.78-79, 84.
• 利用可能 bit の外側には 0が充填される
bitシフト(論理シフト) (符号なし整数の場合)
166
0 0 1 1 1 0 0 1 0 0
1 0 0 1 0 0 0 0
<< 2
0 0 1 1 1 0 0 1 0 0
0 0 1 1 1 0 0 1
>> 2
0xe8
0x90
0xe8
0x3a
論理シフトであれば 左シフト、右シフト共に 符号付き、符号なしで結果は共通
教科書 pp.78-79, 84.
• 最上位ビットより上位は符号拡張される
• 符号ビットが0なら0、1なら1が充填される
bitシフト(算術シフト) (符号付き整数の場合?)
167
1 1 1 1 1 0 0 1 0 0
1 1 1 1 1 0 0 1
>> 2
0 0 0 0 0 1 1 0 1 1
0 0 0 0 0 1 1 0
>> 2 0x1b
0x06
0xe8
0xfa
右シフトは最上位ビットの値により 符号付き、符号なしで結果が異なる
環境依存なので、環境によっては 論理シフトになる可能性も 考慮しておくこと。
左シフトは 符号付き、符号なしで結果は共通
教科書 pp.78-79, 84.
• 実際の環境はどうなっているのか?
右シフト
168 教科書 pp.78-79, 84.
bitshifttest.c unsigned int uc = 0xe8000000; // == 0b11101000... signed int sc = 0xe8000000; // == 0b11101000... uc >>= 2 + 8 * 3; sc >>= 2 + 8 * 3; printf("%02x¥n", uc & 0xff); // 0b00111010 == 0x3a printf("%02x¥n", sc & 0xff); // 0b11111010 == 0xfa ?
6 7 8 9 10 11 12 13
2015-05-15修正 誤:0b11100100 正:0b11101000
2015-05-15修正 誤:0b00111001 正:0b00111010
2015-05-15修正 誤:0b11111001 正:0b11111010
• 算術シフトになっている環境が多い?
右シフト
169 教科書 pp.78-79, 84.
64bit 版 cygwin + GNU C 4.8.2
Borland C++ 5.5
Visual Studio 2013 Express Desktop Windows 64bit 版
$ gcc bitshifttest.c && ./a 3a fa
>bcc32 bitshifttest.c && bitshifttest ... 3a fa
>cl bitshifttest.c && bitshifttest ... 3a fa
論理演算
X Y X AND Y X OR Y X XOR Y NOT X
0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0
170
論理演算子 ビット毎の論理演算子 意味 英語表記
&& & 論理積 AND
|| | 論理和 OR
^ 排他的論理和 XOR (exclusive or)
! ~ 論理反転 NOT
教科書 pp.78-79, 84.
論理演算子による演算結果は真(=1)または偽(=0)となる
C言語の論理値(真偽値)
• 数値を論理値として用いている
• 論理演算とビット毎の論理演算に注意
171
論理値 数値 真偽値判定時
偽 0 0のみが偽として扱われる
真 1 0以外はすべて真として扱われる
logictest.c int x = 1; // = 0b01 int y = 2; // = 0b10 printf("x && y = %d¥n", x && y); printf("x || y = %d¥n", x || y); printf("x & y = %d¥n", x & y); printf("x | y = %d¥n", x | y);
mintty+bash+gcc $ gcc logictest.c && ./a x && y = 1 x || y = 1 x & y = 0 x | y = 3
教科書 pp.78-79, 84.
6 7 8 9 10 11
論理演算とビット毎の論理演算
• 論理演算を行う単位が違う
172
1 1 1 0 0 1 0 0
論理演算子
0 0 0 0 0 0 0 1
1 1 1 0 0 1 0 0
ビット毎の論理演算子
0 0 0 0 0 1 0 0
論理演算では 全体を1つの論理値として扱う
ビット毎の論理演算では 各ビットを個別に扱う
教科書 pp.78-79, 84.
bit 毎の AND による bit mask
• bit毎にANDを取った結果が得られる
173
1 1 1 0 0 1 0 0
0 0 0 0
0 0 0 0 0 1 0 0
& 0 0 0 0 1 1 1 1
X & Y で 右辺の値をマスクとして用いた場合 0: 0でクリア 1: 元の値をそのまま通過
教科書 pp.78-79., p.84.
bit 毎の OR による bit mask
• bit毎にORを取った結果が得られる
174
1 1 1 0 0 1 0 0
1 1 1 1
1 1 1 1 0 1 0 0
| 1 1 1 1 0 0 0 0
X | Y で 右辺の値をマスクとして用いた場合 0: 元の値をそのまま通過 1: 1でクリア
教科書 pp.78-79., p.84.
bit 毎の XOR による bit 反転
• bit毎にXORを取った結果が得られる
175
1 1 1 0 0 1 0 0
@ @ @ @
0 0 0 1 0 1 0 0
^ 1 1 1 1 0 0 0 0
X ^ Y で 右辺の値をマスクとして用いた場合 0: 元の値をそのまま通過 1: bit を 0⇔1 反転
同じ値で再度 XOR を取ると元に戻るので 簡易暗号的な使い方も出来る
教科書 pp.78-79., p.84.
1の補数演算子
• 要は単なるビット毎の論理反転
176
1 1 1 0 0 1 0 0
ビット毎の論理反転
0 0 0 1 1 0 1 1
教科書 pp.78-79., p.84.
~
補数とは
• 基数𝑏(𝑏進数) 𝑛桁で表現可能な整数𝑎に対し
• 𝑏𝑛 − 𝑎 : 基数(𝑏)の補数
• 𝑏𝑛 − 𝑎 − 1 : 減基数(𝑏 − 1)の補数
• 例: 2進数8桁で表す1について
• 2の補数
• 0b100000000 – 0b00000001 = 0b11111111
• 1の補数(単なるビット毎の論理反転)
• 0b100000000 – 0b00000001 - 1 = 0b11111110
177
インクレメント、デクレメントの演算子
算術演算子 演算子の機能
前置演算子 ++ インクレメント ++expr
-- デクレメント --expr
後置演算子 ++ インクレメント expr++
-- デクレメント expr--
178
前置演算子は演算後に値を取り出す。 後置演算子は演算前に値を取り出す。
incrtest.c int i = 5; printf("%d¥n", ++i); printf("%d¥n", --i); printf("%d¥n", i++); printf("%d¥n", i--);
mintty+bash+gcc $ gcc incrtest.c && ./a 6 5 5 6
教科書 pp.73-74., p.84.
6 7 8 9 10
比較演算子 (関係演算子、等値演算子) 演算子 比較の意味
関係演算子 < 左辺が小 expr1 < expr2
<= 左辺が小または等しい expr1 <= expr2
> 左辺が大 expr1 > expr2
>= 左辺が大または等しい expr1 >= expr2
等値演算子 == 等しい expr1 == expr2
!= 等しくない expr1 != expr2
179 教科書 pp.117-118, 147.
演算結果は真(=1)または偽(=0)となる
ポインタ演算子
• アドレス演算子
• 書式 : &オブジェクト
• オブジェクトの配置されたアドレスを得る。
• 間接演算子
• 書式 : *ポインタ
• ポインタが指すアドレスに配置されたオブジェクトを得る。
180 教科書 pp.213-218.
• 書式:
• 条件式 ? 式1 : 式2
条件演算子 三項演算子(?:)
181 [1] pp.63-66, 256-257.
条件式 真 偽
式1 式2
condexprtest.c #include <stdio.h> void main() { int i; fprintf(stderr, "i = ?¥b"); scanf("%d", &i); printf("%s¥n", i ? "not zero" : "zero"); }
1 2 3 4 5 6 7 8 9
mintty+bash+gcc $ gcc condexprtest.c && ./a i = 1 not zero $ ./a i = 0 zero
2015-05-01追加修正
演算子の優先度
演算子 結合規則 備考
( ) [ ] -> . 左から右→
! ~ ++ -- + - * & (type) sizeof 右から左← 単項演算子
* / % 左から右→ 二項演算子
+ - 左から右→ 二項演算子
<< >> 左から右→ bitシフト
< <= > >= 左から右→ 関係演算子
== != 左から右→ 等値演算子
& 左から右→ bit毎のAND
^ 左から右→ bit毎のXOR
| 左から右→ bit毎のOR
&& 左から右→ 論理演算子(AND)
|| 左から右→ 論理演算子(OR)
?: 右から左← 三項演算子
= += -= *= /= %= &= ^= |= <<= >>= 右から左← 代入演算子
, 左から右→
[1] p.65. より
高
低
優先度
182
配列変数
183
配列変数
• 同じ変数名で複数の要素を管理する
char a[10]; // 要素数10のchar型変数の宣言
教科書 pp.85-108.
a[0]
?
a[1]
?
a[2]
?
a[3]
?
a[9]
?
...
要素数10の添え字付き変数
初期値式が与えられなかった場合、値は不定
[1] pp.103-104., p.273.
184
配列変数
• 配列変数の要素への代入
char a[10]; // 要素数10のchar型変数の宣言 a[0] = 'a'; // 0番目の要素へ代入
教科書 pp.85-108.
a[0]
'a'
a[1]
?
a[2]
?
a[3]
?
a[9]
?
...
要素数10の添え字付き変数
初期値式が与えられなかったので、値は不定 宣言後の代入
[1] pp.103-104., p.273.
185
配列変数
• 添え字は値が取れれば変数や数式でも良い
int i = 1; char a[10]; // 要素数10のchar型変数の宣言 a[i + 1] = 'a'; // 2番目の要素へ代入
教科書 pp.85-108.
a[0]
?
a[1]
?
a[2]
'a'
a[3]
?
a[9]
?
...
要素数10の添え字付き変数 [1] pp.103-104., p.273.
186
配列変数
• 確保した領域外はアクセスは禁止
char a[10]; // 要素数10のchar型変数の宣言 short b = 0x1234; a[10] = 'a';// 宣言された領域外へのアクセス
教科書 pp.85-108.
a[0]
?
a[1]
?
a[2]
?
a[3]
?
a[9]
?
...
要素数10の添え字付き変数 [1] pp.103-104., p.273.
187
b
0x1234
a[10]
'a'
他の変数が使っていたらその値を壊してしまう
ここに書き込むと何が起こるか分からない
配列変数
• 初期値式による配列変数の初期化
char a[10] = {'a', 'b'}; //初期値式付きの //要素数10のchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
a[2]
0
a[3]
0
a[9]
0
...
要素数10の添え字付き変数
初期値式が要素数より少ない場合、残りは0で初期化 初期値式による初期化
[1] pp.103-104., p.273.
188
配列変数
• 初期値式による配列変数の初期化
char a[] = {'a', 'b'}; //初期値式付きで //要素数を省略したchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
初期値式の要素数分確保される
初期値式による初期化
[1] pp.103-104., p.273.
189
配列変数
• 文字列による初期化(要素数指定)
char a[10] = "ab"; //文字列による初期値付きの //要素数10のchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
a[2]
0
a[3]
0
a[9]
0
...
要素数10の添え字付き変数
初期値式が要素数より少ない場合、残りは0で初期化 文字列と文字列終端の'¥0'
[1] pp.103-104., p.273.
190
配列変数
• 文字列による初期化(要素数自動決定)
char a[] = "ab";//文字列による初期値付きで //要素数を省略したchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
a[2]
0
文字列の文字数+文字列終端'¥0'の1文字分の要素
文字列と文字列終端の'¥0'
[1] pp.103-104., p.273.
191
変数の初期化
• 明示的な初期化がない場合
• 外的変数、静的変数→0
• 自動変数、レジスタ変数→不定
• 初期化する場合
• 外的変数、静的変数←定数式でのみ初期化可
• コンパイル時に1度だけ初期化される
• 自動変数、レジスタ変数←任意の式で初期化可
• 実行時にブロック毎に初期化される
192
[1] pp.103-104., p.273.
配列変数の初期化
• 要素数を与えない場合
• 初期値式の数で配列のサイズが決まる
• 要素数を与えた場合
• 初期値式を与えない場合
• 値は不定
• 初期値式を与える場合
• 要素数を超えるとエラー
• 要素数に足りない部分は0で初期化される
193
[1] pp.103-104., p.273.
制御構造
条件分岐と繰り返し
194
条件分岐
if 文と switch 文
195
条件分岐 (if 文)
• 真偽値による場合分け
196
if (条件式) { // 条件式が真の場合の処理1 }
条件式
処理1
真 偽
教科書 pp.130-133.
条件分岐 (if, else 文)
• 真偽値による場合分け
197
if (条件式) { // 条件式が真の場合の処理1 } else { // 条件式が偽の場合の処理2 }
条件式
処理1 処理2
真 偽
教科書 pp.130-133.
入れ子の条件分岐 (if, else 文)
• 真偽値による場合分け
198
if (条件式1) { // 条件式1が真の場合の処理1 } else { if (条件式2) { // 条件式1が偽かつ // 条件式2が真の場合の処理2 } else { // 条件式1が偽かつ // 条件式2が偽の場合の処理3 } }
条件式1
処理1
真 偽
条件式2
処理2 処理3
真 偽
if は任意の数入れ子に出来ます。
教科書 pp.130-133.
条件分岐 (if, else if, else 文)
• 真偽値による場合分け
199
if (条件式1) { // 条件式1が真の場合の処理1 } else if (条件式2) { // 条件式1が偽かつ // 条件式2が真の場合の処理2 } else { // 条件式1が偽かつ // 条件式2が偽の場合の処理3 }
条件式1
処理1
真 偽
条件式2
処理2 処理3
真 偽
else if は任意の数追加出来ます。
教科書 pp.130-133.
多分岐判断機構 (switch 文)
• 値による場合分け
200
switch (式) { case 値1: // 式が値1の場合の処理1 case 値2: // 式が値2の場合の処理2 default: // 他の条件に // 当てはまらない場合の処理N };
式
処理1 値1
値2
break 文を入れておかないと 次の条件の処理を 連続して実行するので注意。
処理2
処理N default
break
break
break
教科書 pp.134-140.
繰り返し(ループ)
for文, while文, do-while 文によるループと continue 文、break 文によるループの再開と脱出
201
ループの再開と脱出
• continue 文 • while, do while, for 文内で使用可能
• 以降の処理を中断してループ末尾から再開する
• for文では後処理(第3パラメータ)も実行する
• break 文 • while, do while, for, switch 文内で使用可能
• 以降の処理を中断してループを脱出する
• switch文の場合はswitch文から脱出する
202
後判定ループ (do while 文)
• 真偽値による繰り返し
203
do { // 条件式 が真の場合の処理 } while (条件式);
条件式
処理
真
偽
do while 文は、ループ内の処理を 最低1回は実行する。
教科書 p.123.
continue break
前判定ループ (while 文)
• 真偽値による繰り返し
204
while (条件式) { // 条件式が真の場合の処理 }
教科書 pp.119-122.
式2
真
偽
処理 continue break
初期化・更新処理付きループ (for 文)
• 真偽値による繰り返し
205
for (式1; 式2; 式3) { // 式2が真の場合の処理 }; 式2
式1
真
偽
前判定ループだが 式1による初期化と 式3による更新処理を ひとまとめにして書ける。
処理
式3
教科書 pp.124-129.
continue break
for文とwhile文 (前判定ループ)
• 以下のループは等価
• continue時の式3の扱いに注意
206
for (式1; 式2; 式3) { // 式2が真の場合の処理 };
式2
式1
真
偽
処理
式3
教科書 pp.123-129.
for文の continue
break
式1; while (式2) { // 式2が真の場合の処理 式3; };
while文の continue
for文とwhile文 (前判定ループ)
• 以下のループは等価
• continue時の式3の扱いに注意
207
for (i = 0; i < 10; i++) { // ループ内の処理 };
教科書 pp.123-129.
i = 0; while (i < 10) { //ループ内の処理 i++; };
i < 10
i = 0
真
偽
処理
i++
for文の continue
break
while文の continue
後判定ループ (do while 文)
• continue, break 後の処理(iの値)に注目
208
looptest_dowhile.c int i = 0, j = 0, n; fprintf(stderr, "n = "); scanf("%d", &n); do { j++; printf("%d, %d: 1st", i, j); if (j == 2) {printf(" continue¥n"); continue;} printf(" 2nd"); if (j == 4) {printf(" break¥n"); break;} printf(" 3rd¥n"); i++; } while (i < n);
教科書 pp.119-122.
mintty + bash $ ./looptest_dowhile n = 10 0, 1: 1st 2nd 3rd 1, 2: 1st continue 1, 3: 1st 2nd 3rd 2, 4: 1st 2nd break
mintty + bash $ ./looptest_dowhile n = 0 0, 1: 1st 2nd 3rd
do while 文は、 ループ内の処理を 最低1回は実行する。
前判定ループ (while 文)
• continue, break 後の処理(iの値)に注目
209
looptest_while.c int i = 0, j = 0, n; fprintf(stderr, "n = "); scanf("%d", &n); while (i < n) { j++; printf("%d, %d: 1st", i, j); if (j == 2) {printf(" continue¥n"); continue;} printf(" 2nd"); if (j == 4) {printf(" break¥n"); break;} printf(" 3rd¥n"); i++; }
教科書 p.123.
mintty + bash $ ./looptest_while n = 10 0, 1: 1st 2nd 3rd 1, 2: 1st continue 1, 3: 1st 2nd 3rd 2, 4: 1st 2nd break
mintty + bash $ ./looptest_while n = 0
初期化・更新処理付きループ (for 文)
• continue, break 後の処理(iの値)に注目
210
looptest_for.c int i = 0, j = 0, n; fprintf(stderr, "n = "); scanf("%d", &n); for (i = 0; i < n; i++) { j++; printf("%d, %d: 1st", i, j); if (j == 2) {printf(" continue¥n"); continue;} printf(" 2nd"); if (j == 4) {printf(" break¥n"); break;} printf(" 3rd¥n"); }
教科書 pp.124-129.
mintty + bash $ ./looptest_for n = 10 0, 1: 1st 2nd 3rd 1, 2: 1st continue 2, 3: 1st 2nd 3rd 3, 4: 1st 2nd break
mintty + bash $ ./looptest_for n = 0
continue 文
• 以下のループ内に更に小さなループが含まれない場合の continue は goto contin と同義
211
for (...) { // ... contin: ; }
[1] p.281.
do { // ... contin: ; } while (...);
while (...) { // ... contin: ; }
goto文
• 指定した名札付き文へ移動(ジャンプ)する
• 名札(label)は以下のように設定出来る
212
ラベル名: 文
[1] p.281.
do while 文相当
while 文相当
for 文相当 loop: ;
{ // something to do contin: ; } if (expr) goto loop; brk: ;
loop: ; if (expr) { // something to do contin: ; goto loop; } brk: ;
expr1; loop: ; if (expr2) { // something to do contin: ; expr3; goto loop; } brk: ;
goto 文は 余程理由がない限り使わないこと
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
213
宿題
• 次回までに以下の事をやっておくこと。
• 教科書の第3章の終わりまで読み、指示された操作を試して動作を確認する。
• 不明な点、疑問点についてメモし、次回の授業に持参する。または、本講義の Moodle コース上にある第3週宿題用フォーラムに書き込んでおく。
214
C言語入門 第4週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
215
Visual Studio Code https://code.visualstudio.com/
• Microsoft 製プログラミング用テキストエディタ
216
2015-04-29 にリリースされたばかりのテキストエディタ。Windows 以外にも Mac, Linux 用もある。
News
制御構造
条件分岐と繰り返し
217
流れ図(フローチャート)
• プログラムの処理の流れを図示する方法
218 教科書 pp.112-116.
条件分岐
処理
端子
定義済み処理
表示
手操作入力
プログラムの開始と終了
代入や演算等 各ステップにおける処理
サブルーチンや関数等
処理の振り分け
画面等への出力
キーボード等からの入力
各部品を矢印で繋ぐ事で処理の流れを視覚的に表現する。
各部品の内部にはそれぞれのステップで行う内容に書き換えて使う。
結合子 別のフローチャートと結合 2015-05-12追加
条件分岐
if 文と switch 文
219
条件分岐 (if 文)
• 真偽値による場合分け
220
if (条件式1) { // 条件式1が真の場合の処理1 }
教科書 pp.130-133.
条件式1 真 偽
処理1
if 文を使うと特定の条件下で実行する処理を指定出来る。
条件分岐 (if, else 文)
• 真偽値による場合分け
221
if (条件式1) { // 条件式1が真の場合の処理1 } else { // 条件式1が偽の場合の処理2 }
教科書 pp.130-133.
条件式1 真 偽
処理1 処理2
if, else 文を使うと特定の条件下で別の処理を指定出来る。
入れ子の条件分岐 (if, else 文)
• 真偽値による場合分け
222
if (条件式1) { if (条件式2) { // 条件式1が真かつ // 条件式2が真の場合の処理1 } else { // 条件式1が真かつ // 条件式2が偽の場合の処理2 } } else { // 条件式1が偽の場合の処理3 }
条件式1 真 偽
教科書 pp.130-133.
処理3 条件式2 真 偽
処理1 処理2
if, else 文は任意の数入れ子に出来る。 だだし、入れ子が深くなると読み難くなるので注意。
2015-05-15修正 誤:処理2,3,1 正:処理1,2,3
入れ子の条件分岐 (if, else 文)
• 真偽値による場合分け
223
if (条件式1) { // 条件式1が真の場合の処理1 } else { if (条件式2) { // 条件式1が偽かつ // 条件式2が真の場合の処理2 } else { // 条件式1が偽かつ // 条件式2が偽の場合の処理3 } }
条件式1 真 偽
条件式2 真 偽
if, else 文は任意の数入れ子に出来る。 else 側の if は else if で置き換ると入れ子を防げる。
教科書 pp.130-133.
処理1
処理2 処理3
複数条件の条件分岐 (if, else if, else 文)
• 真偽値による場合分け
224
if (条件式1) { // 条件式1が真の場合の処理1 } else if (条件式2) { // 条件式1が偽かつ // 条件式2が真の場合の処理2 } else { // 条件式1が偽かつ // 条件式2が偽の場合の処理3 }
else if は任意の数追加出来る。 入れ子を作らずに複数の条件を追加出来る。
教科書 pp.130-133.
条件式1 真 偽
条件式2 真 偽
処理1
処理2 処理3
多分岐判断機構 (switch 文)
• 値による場合分け
225
switch (式) { case 値1: // 式が値1の場合の処理1 case 値2: // 式が値2の場合の処理2 default: // 他の条件に // 当てはまらない場合の処理N };
教科書 pp.134-140.
break 文を入れておかないと 次の条件の処理を 連続して実行するので注意。
break
break
break
式
値1 処理1
処理2
default
値2
処理N
平方根の計算
条件分岐の例題
226
sqrt 関数
• 書式: #include <math.h> double sqrt(double x);
• 引数: • x: 求める平方根の2乗
• 戻り値: • 0 ≤ 𝑥 の場合 𝑥 を返す。 • 0 ≤ 𝑥 でない場合は処理系依存?
• x が負なら、nan (=非数) を返し、グローバル変数 errno に EDOM を代入する(errno は errno.h を include すると参照出来る)。
• x が 0 なら 0 を、inf (=∞) なら inf を返す。 • x が nan なら nan を返す。
227
平方根の計算
• sqrt() 関数は引数が負の場合計算出来ない。
228
sqrt_practice_1.c #include <stdio.h> #include <math.h> void main() { double x; fprintf(stderr, "x = ?¥b"); scanf("%lf", &x); printf("x: %f¥n", x); printf("sqrt(x): %f¥n", sqrt(x)); }
1 2 3 4 5 6 7 8 9
10 11
mintty+bash+gcc $ gcc sqrt_practice_1.c && ./a x = 2 x: 2.000000 sqrt(x): 1.414214 $ ./a x = -2 x: -2.000000 sqrt(x): nan
演習: 平方根の計算(if文)
• sqrt_practice_1.c を参考に 0 ≤ 𝑥 の場合は実数の平方根 𝑥を、 𝑥 < 0 の場合は虚数の平方根 −𝑥𝑖 を if 文で場合分けして表示する sqrt_practice_if.c を完成させよ。虚数は数値の後に i を表示する事で表現すれば良い。
• 例えば x=-2 の場合は以下のようになる。
229
mintty+bash+gcc $ gcc sqrt_practice_if.c && ./a x = -2 x: -2.000000 sqrt(x): 1.414214i
演習: 平方根の計算(if文) ヒント
• sqrt_practice_1.c の 10 行目は 𝑥 を表示している。これを if 文で場合分けしてやれば良い。
230
条件式 真 偽
𝑥 −𝑥 𝑖
sqrt_practice_1.c printf("sqrt(x): %f¥n", sqrt(x)); 10 𝑥
sqrt_practice_if.c if (条件式) { // 𝑥 を表示 } else { // −𝑥 と 𝑖 を表示 }
10 11 12 13 14
• 書式:
• 条件式 ? 式1 : 式2
条件演算子 三項演算子(?:)
231 [1] pp.63-66, 256-257.
条件式 真 偽
式1 式2
condexprtest.c #include <stdio.h> void main() { int i; fprintf(stderr, "i = ?¥b"); scanf("%d", &i); printf("%s¥n", i ? "not zero" : "zero"); }
1 2 3 4 5 6 7 8 9
mintty+bash+gcc $ gcc condexprtest.c && ./a i = 1 not zero $ ./a i = 0 zero
演習: 平方根の計算(条件演算子)
• 先程の負の数の平方根の計算を条件演算子を用いて解決してみよう。
• 10行目の printf では 2 つ目の引数が %f に 3 つ目の引数が %s へ埋め込まれる。
232
sqrt_practice_condexpr.c double x; fprintf(stderr, "x = ?¥b"); scanf("%lf", &x); printf("x: %f¥n", x); printf("sqrt(x): %f%s¥n", sqrt(/*WYCH*/), /*WYCH*/);
6 7 8 9 10
奇数と偶数の判定
条件分岐の例題
233 2015-05-18追加
奇数と偶数
• 整数のうち 2 で割り切れるのが偶数、2 で割り切れないのが奇数
• 2 で割り切れるとは?
• 整数を 2 で割った余り(剰余)が 0
• 2 で割り切れないとは
• 整数を 2 で割った余り(剰余)が 0 以外
234
C言語の剰余
• 剰余は二項演算子 %
• i % 2 で 2 で割った余りが求まる
• 同じかどうか比較するのは二項演算子 == や !=
• i % 2 == 0 は i を 2 で割った余りが 0 なら真、それ以外は偽
• i % 2 != 0 は i を 2 で割った余りが 0 以外なら真、それ以外は偽
• i % 2 != 0 は単項演算子 ! を用いて !(i % 2 == 0) としても同じ意味になる
235
演算子の優先度
演算子 結合規則 備考
( ) [ ] -> . 左から右→
! ~ ++ -- + - * & (type) sizeof 右から左← 単項演算子
* / % 左から右→ 二項演算子
+ - 左から右→ 二項演算子
<< >> 左から右→ bitシフト
< <= > >= 左から右→ 関係演算子
== != 左から右→ 等値演算子
& 左から右→ bit毎のAND
^ 左から右→ bit毎のXOR
| 左から右→ bit毎のOR
&& 左から右→ 論理演算子(AND)
|| 左から右→ 論理演算子(OR)
?: 右から左← 三項演算子
= += -= *= /= %= &= ^= |= <<= >>= 右から左← 代入演算子
, 左から右→
[1] p.65. より
高
低
優先度
236
演習: 偶数かどうか判定する
• 標準入力から入力された整数値が奇数かどうか判別して、奇数であれば入力された数値に続けて " is even number¥n" そうでなければ " is not even number¥n" と表示するプログラ
ムを作成せよ。奇数でない場合は何も表示しなくて良い。
• if_practice_even.c の /*WYCH*/ の個所を修正すれば良い。
237
演習: 奇数かどうか判定する
• 標準入力から入力された整数値が奇数かどうか判別して、奇数であれば入力された数値に続けて " is odd number¥n" そうでなければ " is not odd number¥n" と表示するプログラム
を作成せよ。奇数でない場合は何も表示しなくて良い。
• if_practice_odd.c の /*WYCH*/ の個所を修正すれば良い。
238
演習: 奇数か偶数か判定する
• 標準入力から入力された整数値が奇数かどうか判別して、奇数であれば入力された数値に続けて " is odd number¥n"、偶数であれば入力された数値に続けて "is even number¥n" と表示するプログラムを作成せよ。
• if_practice_evenodd.c の /*WYCH*/ の個所を修正すれば良い。
239
閏年の判定
入れ子の条件分岐の例題
240
閏年(leap year)とは
• 地球の平均回帰年は365日+約1/4日であるため1年を356日にしているとカレンダー上の日付と季節が4年で1日ずつずれてしまう。これを防ぐのが4年に1回設ける2月29日(閏日)。
• 閏年の求め方
• 西暦を4で割り切れるなら閏年?
241
閏年 判定式
閏年 平年
真 偽
閏年(leap year)の判定
• 4で割り切れる(割れる)とは?
• 4で割った余りが0ということ
• 4で割り切れる : year % 4 == 0
• 4で割り切れない : year % 4 != 0
• C言語では0は偽、0以外は真だったから
• 以下のようにも書けるが・・・
• 4で割り切れる : !(year % 4)
• 4で割り切れない : (year % 4)
242
ぱっと見て意味の分かり易い書き方をしましょう
% : 剰余算演算子
等値演算子 == : 等しい != : 等しくない
! : 論理否定演算子
演算子の優先度
演算子 結合規則 備考
( ) [ ] -> . 左から右→
! ~ ++ -- + - * & (type) sizeof 右から左← 単項演算子
* / % 左から右→ 二項演算子
+ - 左から右→ 二項演算子
<< >> 左から右→ bitシフト
< <= > >= 左から右→ 関係演算子
== != 左から右→ 等値演算子
& 左から右→ bit毎のAND
^ 左から右→ bit毎のXOR
| 左から右→ bit毎のOR
&& 左から右→ 論理演算子(AND)
|| 左から右→ 論理演算子(OR)
?: 右から左← 三項演算子
= += -= *= /= %= &= ^= |= <<= >>= 右から左← 代入演算子
, 左から右→
[1] p.65. より
高
低
優先度
243
演習: 4 で割り切れるか表示する
• leap_year_practice_1.c の /*WYCH*/ を書き換えて、year が 4 で割り切れる場合 "can be divided by 4."、割り切れない場合 "can not be divided by 4." と表示するプログラムを完成せよ。
244
mintty+bash+gcc $ gcc leap_year_practice_1.c && ./a year = 2015 2015 can not be divided by 4. $ ./a year = 2016 2016 can be divided by 4.
2015-05-11修正 誤:dividec 正:divided
閏年(leap year)の定義
• グレゴリオ暦における閏年の定義
• 判定したい年を西暦で表した際
• 4で割り切れる場合は閏年 (条件1)
• 但し100で割り切れる場合は平年 (条件2)
• 但し400で割り切れる場合は閏年 (条件3)
• 地球の平均回帰年は約365.242199日であるため、上記ルールだと約3320年で1日ずれる程度で済む。
245
閏年(leap year)の判定
246
4で 割れる
平年
真 偽
閏年
条件1
閏年(leap year)の判定
247
4で 割れる
平年
真 偽
100で 割れる
閏年
真 偽
平年
条件1 +条件2
閏年(leap year)の判定
248
4で 割れる
平年
真 偽
100で 割れる
閏年
真 偽
400で 割れる
閏年 平年
真 偽
条件1 +条件2 +条件3
完成
演習: 閏年(leap year)の判定
• leap_year_practice_2.c の /*WYCH*/ の部分を書き換えて閏年か判定するプログラムを完成せよ。
• /*WYCH*/ の部分には year に格納された西暦が閏年であれば変数 leap_year_flag に 1 を閏年でなければ 0 を代入するコード作成すれば良い。これは前のページのフローチャートを参考に if 文を 3 重の入れ子にすれば出来る。
• なおここでは紀元前については考慮する必要はない。
249
演習: 閏年(leap year)の判定
• ヒント1
• 前述の条件1~3のフローチャートをif,else文で書くと以下のようになる。
250
条件1 if (/*条件1*/) { /*閏年*/ } else { /*平年*/ }
条件1+条件2 if (/*条件1*/) { if (/*条件2*/) { /*平年*/ } else { /*閏年*/ } } else { /*平年*/ }
条件1+条件2+条件3 if (/*条件1*/) { if (/*条件2*/) { /*条件3のif,else文*/ } else { /*閏年*/ } } else { /*平年*/ }
演習: 閏年(leap year)の判定
• ヒント2
• 閏年なら leap_year_flag に 1 そうでなければ 0 を代入すれば良い。
251
条件1 if (/*条件1*/) { leap_year_flag = 1; /*閏年*/ } else { /*平年*/ }
月の名前
多分岐の例題
252
演習: 月の名前の表示
• monthname_practice_1.c の /*WYCH*/ の部分を変更し、入力した月 month に対応する英語の月名(January, February, March, April, June, July, August, September, October, November, December )を表示せよ。
253
mintty+bash+gcc $ gcc monthname_practice_1.c && ./a month = 1 January
繰り返し(ループ)
for文, while文, do-while 文によるループと continue 文、break 文によるループの再開と脱出
254
ループの再開と脱出
• continue 文 • while, do while, for 文内で使用可能
• 以降の処理を中断してループ末尾から再開する
• for文では後処理(第3パラメータ)も実行する
• break 文 • while, do while, for, switch 文内で使用可能
• 以降の処理を中断してループを脱出する
• switch文の場合はswitch文から脱出する
255
後判定ループ (do while 文)
• 真偽値による繰り返し
256
do { // 条件式 が真の場合の処理 } while (条件式);
教科書 p.123.
条件式
処理
真
偽
continue break
do while 文は、ループ内の処理を 1回以上実行する(=最低1回は実行する)。
前判定ループ (while 文)
• 真偽値による繰り返し
257
while (条件式) { // 条件式が真の場合の処理 }
教科書 pp.119-122.
条件式
真
偽
処理 continue break
while 文は、ループ内の処理を 0回以上実行する(=実行しない場合もある) 。
2015-06-04修正 誤:式2 正:条件式
初期化・更新処理付きループ (for 文)
• 真偽値による繰り返し
258
for (式1; 式2; 式3) { // 式2が真の場合の処理 }; 式2
式1
真
偽
処理
式3
教科書 pp.124-129.
continue break
for 分は前判定ループで 式1による初期化と 式3による更新処理を ひとまとめにしてコンパクトに書ける。
for文とwhile文 (前判定ループ)
• 以下のループは等価
• ただしcontinue時の式3の扱いに注意
259
for (式1; 式2; 式3) { // 式2が真の場合の処理 };
式2
式1
真
偽
処理
式3
教科書 pp.123-129.
for文の continue
break
式1; while (式2) { // 式2が真の場合の処理 式3; };
while文の continue
for文とwhile文 (前判定ループ)
• 以下のループは等価
• continue時の式3の扱いに注意
260
for (i = 0; i < 10; i++) { // ループ内の処理 };
教科書 pp.123-129.
i = 0; while (i < 10) { // ループ内の処理 i++; };
i < 10
i = 0
真
偽
処理
i++
for文の continue
break
while文の continue
後判定ループ (do while 文)
• continue, break 後の処理(iの値)に注目
261
looptest_dowhile.c int i = 0, j = 0, n; fprintf(stderr, "n = "); scanf("%d", &n); do { j++; printf("%d, %d: 1st", i, j); if (j == 2) {printf(" continue¥n"); continue;} printf(" 2nd"); if (j == 4) {printf(" break¥n"); break;} printf(" 3rd¥n"); i++; } while (i < n);
教科書 pp.119-122.
mintty + bash $ ./looptest_dowhile n = 10 0, 1: 1st 2nd 3rd 1, 2: 1st continue 1, 3: 1st 2nd 3rd 2, 4: 1st 2nd break
mintty + bash $ ./looptest_dowhile n = 0 0, 1: 1st 2nd 3rd
do while 文は、 ループ内の処理を 最低1回は実行する。
前判定ループ (while 文)
• continue, break 後の処理(iの値)に注目
262
looptest_while.c int i = 0, j = 0, n; fprintf(stderr, "n = "); scanf("%d", &n); while (i < n) { j++; printf("%d, %d: 1st", i, j); if (j == 2) {printf(" continue¥n"); continue;} printf(" 2nd"); if (j == 4) {printf(" break¥n"); break;} printf(" 3rd¥n"); i++; }
教科書 p.123.
mintty + bash $ ./looptest_while n = 10 0, 1: 1st 2nd 3rd 1, 2: 1st continue 1, 3: 1st 2nd 3rd 2, 4: 1st 2nd break
mintty + bash $ ./looptest_while n = 0
初期化・更新処理付きループ (for 文)
• continue, break 後の処理(iの値)に注目
263
looptest_for.c int i = 0, j = 0, n; fprintf(stderr, "n = "); scanf("%d", &n); for (i = 0; i < n; i++) { j++; printf("%d, %d: 1st", i, j); if (j == 2) {printf(" continue¥n"); continue;} printf(" 2nd"); if (j == 4) {printf(" break¥n"); break;} printf(" 3rd¥n"); }
教科書 pp.124-129.
mintty + bash $ ./looptest_for n = 10 0, 1: 1st 2nd 3rd 1, 2: 1st continue 2, 3: 1st 2nd 3rd 3, 4: 1st 2nd break
mintty + bash $ ./looptest_for n = 0
continue 文
• 以下のループ内に更に小さなループが含まれない場合の continue は goto contin と同義
264
for (...) { // ... contin: ; }
[1] p.281.
do { // ... contin: ; } while (...);
while (...) { // ... contin: ; }
goto文
• 指定した名札付き文へ移動(ジャンプ)する
• 名札(label)は以下のように設定出来る
265
ラベル名: 文
[1] p.281.
do while 文相当
while 文相当
for 文相当 loop: ;
{ // something to do contin: ; } if (expr) goto loop; brk: ;
loop: ; if (expr) { // something to do contin: ; goto loop; } brk: ;
expr1; loop: ; if (expr2) { // something to do contin: ; expr3; goto loop; } brk: ;
goto 文は 余程理由がない限り使わないこと
簡易版 seq コマンドの作成
前判定ループの演習
2015-05-15追加
seq コマンド
• UNIX系のOSに標準で搭載されているコマンド
• 書式:
• seq FIRST LAST
• 機能:
• FIRST から LAST までの整数を 1 刻みで小さい順に表示する。
• 他にも詳細な機能があるが、ここでは省略
JM / seq (1)
演習: while 文による簡易 seq コマンド
• seq_practice_1_while.c の /*WYCH*/ の部分を修正して以下のプログラムを完成させよ。
• 標準入力から int 型の変数 first, last に整数値を読み取り、first 以上、last 以下の整数を1刻みで小さい順に表示せよ。
mintty + bash $ gcc seq_practice_1_while.c && ./a first = 5 last = 9 5 6 7 8 9
演習: for 文による簡易 seq コマンド
• seq_practice_1_for.c の /*WYCH*/ の部分を修正して以下のプログラムを完成させよ。
• 標準入力から int 型の変数 first, last に整数値を読み取り、first 以上、last 以下の整数を1刻みで小さい順に表示せよ。
mintty + bash $ gcc seq_practice_1_for.c && ./a first = 5 last = 9 5 6 7 8 9
素数判定
条件分岐とループの例題
270 2015-05-18追加
素数
• 1と自分以外に正の約数を持たない自然数(正整数)で1でない数
• 調べたい数をiとすると、2以上i/2以下の整数jの全てについてiが割り切れないことを確認すれば良い。
• ※厳密には2以上 𝑖以下の整数jについて調べれば良いが、平方根の計算は除算に比べかなり遅いのでここではi/2以下について確認する。
271
演習: 素数判定
• 標準入力から入力された正整数が素数かどうか判定するプログラムを作成せよ。
• isprime_practice_1.c の /*WYCH*/ の箇所を修正すれば良い。
272
mintty + bash $ gcc is_prime_practice_1.c $ ./a i = 1 1 is not prime number $ ./a i = 2 2 is prime number $ ./a i = 3 3 is prime number $ ./a i = 4 4 is not prime number
演習: 素数判定(ヒント)
• 作成すべきプログラムの素数判定部分のフローチャートは以下の通りである
素数判定部分
273
jを2からi/2の範囲で繰り返し
iがjで 割切れる
非素数
真
偽
break
iは2以上
素数と仮定
1
1
非素数
偽
真
j++
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
274
C言語入門 第4週 補足資料
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
275
条件判定の簡略化
閏年の判定を例に
276
• 3重のif-else文による実装
• 条件1を実装
閏年(leap year)の判定
277
if (year % 4 == 0) { leap_year_flag = 1; } else { leap_year_flag = 0; }
真 偽 4で割れる
平年 閏年
• 3重のif-else文による実装
• 条件2を追加
閏年(leap year)の判定
278
if (year % 4 == 0) { if (year % 100 == 0) { leap_year_flag = 0; } else { leap_year_flag = 1; } } else { leap_year_flag = 0; }
真 偽 4で割れる
平年 100で割れる 真 偽
閏年 平年
• 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; }
279
真 偽 4で割れる
平年 100で割れる
平年
400で割れる
閏年
真
真
偽
偽 閏年
• 3重のif-else文による実装
• 平年フラグを外に追い出してみる
閏年判定の整理
真 偽 4で割れる
280
平年 100で割れる
平年
400で割れる
閏年
真
真
偽
偽 閏年
平年
leap_year_flag = 0; 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; }
• 3重のif-else文による実装
• 平年フラグを外に追い出してみる
閏年判定の整理
真 偽 4で割れる
281
平年 100で割れる
平年
400で割れる
閏年
真
真
偽
偽 閏年
平年
leap_year_flag = 0; if (year % 4 == 0) { if (year % 100 == 0) { if (year % 400 == 0) { leap_year_flag = 1; } } else { leap_year_flag = 1; } }
少し短くなったが 対称性が崩れていて
少し美しくない
• 3重のif-else文による実装
• 逆に閏年フラグを外に追い出してみる
閏年判定の整理
真 偽 4で割れる
282
平年 100で割れる
平年
400で割れる
閏年
真
真
偽
偽 閏年
閏年
leap_year_flag = 1; 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; }
• 3重のif-else文による実装
• 閏年フラグを外に追い出してみる
閏年判定の整理
真 偽 4で割れる
283
平年 100で割れる
平年
400で割れる
閏年
真
真
偽
偽 閏年
閏年
leap_year_flag = 1; if (year % 4 == 0) { if (year % 100 == 0) { if (year % 400 == 0) { ; } else { leap_year_flag = 0; } } else { leap_year_flag = 0; }
やはり 対称性が崩れていて
少し美しくない
真の場合の処理が 空になっているのも 格好が良くない
• 3重のif-else文による実装
• 例外時に結果を随時上書きしてみる
閏年判定の整理 真 偽
4で割れる
284
平年
100で割れる
平年
400で割れる
真
真
偽
偽 閏年
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; } else { leap_year_flag = 0; } } else { leap_year_flag = 1; } } else { leap_year_flag = 0; }
閏年
平年
閏年
平年
leap_year_ex2_1.c
• 3重のif-else文による実装
• 例外時に結果を随時上書きしてみる
閏年判定の整理
285
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; } } }
真 偽 4で割れる
平年
100で割れる
平年
400で割れる
真
真
偽
偽 閏年
閏年
平年
閏年
平年
短く対称性も取れていて 比較的美しい?
西暦と閏年の集合
• 包含の関係 • 西暦⊃X⊃Y⊃Z
X
Y
Z
286
• 分解してみると
閏年判定の整理
287
真 偽 4で割れる 100で割れる 400で割れる
真 真 偽 偽
閏年 平年 閏年 平年
100 or 400で 割れる場合
400で 割れる場合
1
1
2
2
3
3
100 or 400で 割れない場合
400で 割れない場合
• 展開したif文による実装
• 100 と 400 は 4 の倍数 400 は 100 の倍数 である点に着目すると ネスト(入れ子)を展開可能
閏年判定の整理
288
leap_year_ex2_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; }
if文のロジック以外に 各条件間の数学的関係による 暗黙の前提が分からないと 読めないコードになるかも? そういう意味では 良くないコード?
year % 4 != 0 の場合には影響を与えない
year % 4 != 0 および year % 100 != 0 の場合には影響を与えない
適切なコメント等が必要?
• 展開したif文による実装
• 前頁のコードから省略可能な { } を省略
閏年判定の整理
289
leap_year_ex2_2.c int 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;
短くすっきりしていて見易い。 しかし、定義通りになっていないため、 100で割り切れた場合、4で割り切れなかった場合に影響を与えない 400で割り切れた場合、4,100で割り切れなかった場合に影響を与えない 事がわかっていないと混乱するかもしれない?
実行する処理が 1つだけの場合は 省略出来る。
• 3重の条件演算子による実装
• 条件1を実装
• 条件2を追加
• 条件3を追加して完成
閏年判定の整理
290
leap_year_flag = year % 4 == 0 ? 1 : 0;
leap_year_flag = year % 4 == 0 ? (year % 100 == 0 ? 0 : 1) : 0;
leap_year_ex3_1.c leap_year_flag = year % 4 == 0 ? (year % 100 == 0 ? (year % 400 == 0 ? 1 : 0) : 1) : 0;
条件演算子は便利だけど 下手に多用すると読み難くなるので注意
ネスト(入れ子)が深くなると if-else文以上に読み難い
• 3重の条件演算子による実装
• 条件1を実装
• 条件2を追加
• 条件3を追加して完成
閏年判定の整理
291
leap_year_flag = year % 4 == 0 ? 1 : 0;
leap_year_flag = year % 4 == 0 ? 1 : 0; leap_year_flag = year % 100 == 0 ? 0 : leap_year_flag;
leap_year_ex3_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;
これなら leap_year_ex2_2.c の方が読み易いかも?
leap_year_ex2_2.c と同じ方法で 入れ子の条件を展開する例
論理演算
• AND, OR, NOT
• 条件を論理演算する場合に使う
• AND: X && Y
• OR: X || Y
• NOT: !X
X Y X && Y X || Y !X
FALSE FALSE 0 0 1
FALSE TRUE 0 1
TRUE FALSE 0 1 0
TRUE TRUE 1 1
292
閏年判定の整理
• 西暦全体の集合のうち
• X: 4で割り切れる集合(Y,Z を包含)
• Y: 100で割り切れる集合(Z を包含)
• Z: 400で割り切れる集合 X
Y
Z
!Y Y
293
閏年判定の整理
• 論理演算で 考えると
X
Y
Z
Z X && !Y
(X && !Y) || Z
C言語の演算子の優先度的には X && !Y || Z でも良いが ( ) を付けた方が理解が容易かつ 誤解の余地が生じない
!X Y && !Z
!X || (Y && !Z)
294
• if-else文と論理演算による実装
• 論理演算による実装
閏年判定の整理
295
leap_year_ex4_1.c if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { leap_year_flag = 1; } else { leap_year_flag = 0; }
leap_year_ex4_2.c leap_year_flag = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
閏年判定の整理
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
296
year % 4 == 0
year % 100 == 0 year % 400 == 0 閏年
year % 400 != 0 平年
year % 100 != 0 year % 400 == 0 ×
year % 400 != 0 閏年
year % 4 != 0
year % 100 == 0 year % 400 == 0 ×
year % 400 != 0 ×
year % 100 != 0 year % 400 == 0 ×
year % 400 != 0 平年
× の個所は 条件を満たす year が存在しない
2_1, 2_2, 3_2 の例で year % 100 != 0 や year % 400 != 0 が 考慮不要なことが確認出来る。
閏年判定の整理
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
297
year % 4 == 0
year % 100 == 0 year % 400 == 0 閏年
year % 400 != 0 平年
year % 100 != 0 year % 400 == 0
閏年 year % 400 != 0
year % 4 != 0
year % 100 == 0 year % 400 == 0
平年 year % 400 != 0
year % 100 != 0 year % 400 == 0
year % 400 != 0
今の場合前の条件への追加条件なので 全ての組み合わせを考える必要はない
閏年判定の整理
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
298
year % 4 == 0 year % 4 != 0
year % 100 == 0 year % 400 == 0 閏年 ×
year % 400 != 0 平年 ×
year % 100 != 0 year % 400 == 0 × ×
year % 400 != 0 閏年 平年
× の個所は 条件を満たす year が存在しない
長くなる場合は横に展開しても良い
閏年判定の整理
• 全ての条件が網羅出来ているか?
• 条件に漏れはないか?
299
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 平年
× の個所は 条件を満たす year が存在しない
この書き方は Excel 等で sort して調べるのに向いている
閏年判定の整理
• 真偽値による文字列の切り替え
300
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 に埋め込む文字列を変更
同じ処理でも実装は様々
• 同じ処理でも複数の異なる実装が可能
• 効率や可読性等、何を重視するか?
• 読み易い、理解しやすいコードを推奨
301
C言語入門 第5週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
302
入れ子の繰り返し(多重ループ)
303
多重ループとは?
• ループの中に別のループを入れた 入れ子構造のループ
304
mul99_ex1.c for (i = 1; i <= 9; i++) { //行ループ for (j = 1; j <= 9; j++) {//列ループ printf(" %2d", i * j); //掛算九九 } printf("¥n"); //行末尾で改行 }
mintty + bash $ gcc mul99_ex1.c && ./a 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
7 8 9
10 11 12
上記は掛算九九を計算して表を作成する例
掛算九九の例
• 見出し行と見出し列、見出し区切り(-+|)を付けた例
305
mul99_ex2.c printf(" |"); //見出しの区切り for (j = 1; j <= 9; j++) { printf(" %2d", j); //見出し } printf("¥n"); //見出しの改行 printf("--+"); //見出しの区切り for (j = 1; j <= 9; j++) { printf("---"); //見出しの区切り } printf("¥n"); //見出しの区切りの改行
mintty + bash $ gcc mul99_ex2.c && ./a | 1 2 3 4 5 6 7 8 9 --+--------------------------- 1| 1 2 3 4 5 6 7 8 9 2| 2 4 6 8 10 12 14 16 18 3| 3 6 9 12 15 18 21 24 27 4| 4 8 12 16 20 24 28 32 36 5| 5 10 15 20 25 30 35 40 45 6| 6 12 18 24 30 36 42 48 54 7| 7 14 21 28 35 42 49 56 63 8| 8 16 24 32 40 48 56 64 72 9| 9 18 27 36 45 54 63 72 81
7 8 9
10 11 12 13 14 15 16 17
掛算九九の例
• 見出し行と見出し列、見出し区切り(-+|)を付けた例
306
mul99_ex2.c for (i = 1; i <= 9; i++) {//行ループ printf("%2d|", i); //見出しと区切り for (j = 1; j <= 9; j++) {//列ループ printf(" %2d", i * j); //掛算九九 } printf("¥n"); //行末尾で改行 }
mintty + bash $ gcc mul99_ex2.c && ./a | 1 2 3 4 5 6 7 8 9 --+--------------------------- 1| 1 2 3 4 5 6 7 8 9 2| 2 4 6 8 10 12 14 16 18 3| 3 6 9 12 15 18 21 24 27 4| 4 8 12 16 20 24 28 32 36 5| 5 10 15 20 25 30 35 40 45 6| 6 12 18 24 30 36 42 48 54 7| 7 14 21 28 35 42 49 56 63 8| 8 16 24 32 40 48 56 64 72 9| 9 18 27 36 45 54 63 72 81
19 20 21 22 23 24 25
while 文の2重ループの例
307
2重ループ while (条件式1) { // 処理1-1 while (条件式2) { // 処理2 // ... // ... // ... } // 処理1-2 }
真
偽 条件式1
処理1-1
処理1-1
1
2
真
偽
1
2
処理2
条件式2
ループは基本的にいくつでも 入れ子に出来る。
while 文の3重ループの例
308
3重ループ while (条件式1) { // 処理1-1 while (条件式2) { // 処理2-1 while (条件式3) { // 処理3 } // 処理2-2 } // 処理1-2 }
真
偽 条件式1
処理1-1
処理1-1
1
4
真
偽 条件式2
処理2-1
処理2-1
2
3
真
偽
2
3
処理2
条件式2
1
4
入れ子があまり深くなると 分かり辛くなるので注意
異なるループ文で多重ループ
• do-while, while, for ループを必要に応じて組み合わせて入れ子にする
309
異なるループ文で3重ループ for (式1-1; 式1-2; 式1-3) { // 処理1-1 while (条件式2) { // 処理2-1 do { // 処理3 } while (条件式3); // 処理2-2 } // 処理1-2 }
異なるループ文で3重ループ do { // 処理1-1 for (式2-1; 式2-2; 式2-3){ // 処理2-1 while (条件式3) { // 処理3 } // 処理2-2 } // 処理1-2 } while (条件式1);
掛算九九の表
多重ループの例題
310
演習: 掛算九九(範囲指定)
• 標準入力から掛算九九の表の表示範囲 i1 i2 j1 j2 を読み取り、指定された範囲のみ表示する mul99_practice_1.c を作成せよ(/*WYCH*/ の個所を修正すれば良い)。
311
mintty + bash $ gcc mul99_practice_1.c && ./a i1 i2 j1 j2 = 2 5 4 8 | 4 5 6 7 8 --+--------------- 2| 8 10 12 14 16 3| 12 15 18 21 24 4| 16 20 24 28 32 5| 20 25 30 35 40
値が3桁やマイナスになると 表示が破綻する場合があるが、 以下で行う一連の演習では 3桁やマイナスの値は考慮しなくて良い。
演習: 掛算九九(行の反転)
• 完成した mul99_practice_1.c をコピーしてファイル名を mul99_practice_2.c とし、行の見出しが逆順となるよう修正せよ。
312
mintty + bash $ gcc mul99_practice_2.c && ./a i1 i2 j1 j2 = 2 5 4 8 | 4 5 6 7 8 --+--------------- 5| 20 25 30 35 40 4| 16 20 24 28 32 3| 12 15 18 21 24 2| 8 10 12 14 16
演習: 掛算九九(見出しの移動)
• 完成した mul99_practice_2.c をコピーしてファイル名を mul99_practice_3.c とし、列の見出しを表の下に表示するよう修正せよ。
313
mintty + bash $ gcc mul99_practice_3.c && ./a i1 i2 j1 j2 = 2 5 4 8 5| 20 25 30 35 40 4| 16 20 24 28 32 3| 12 15 18 21 24 2| 8 10 12 14 16 --+--------------- | 4 5 6 7 8
演習: 掛算九九(表示値の制限)
• 完成した mul99_practice_3.c をコピーしてファイル名を mul99_practice_4.c とし、i*j<=99の場合以外は結果を "##" と表示するよう修正せよ。
314
mintty + bash $ gcc mul99_practice_4.c && ./a i1 i2 j1 j2 = 8 14 5 15 14| 70 84 98 ## ## ## ## ## ## ## ## 13| 65 78 91 ## ## ## ## ## ## ## ## 12| 60 72 84 96 ## ## ## ## ## ## ## 11| 55 66 77 88 99 ## ## ## ## ## ## 10| 50 60 70 80 90 ## ## ## ## ## ## 9| 45 54 63 72 81 90 99 ## ## ## ## 8| 40 48 56 64 72 80 88 96 ## ## ## --+--------------------------------- | 5 6 7 8 9 10 11 12 13 14 15
画像の生成
多重ループの例題
315
BMP, DIB
• Wikipedia / Windows bitmap
• Windows 標準の画像形式
• 書くのは比較的簡単
• 例: 第1週の bmptest.c 等
• 読むのは、フォーマットがいくつもあるので、全部対応するのは面倒
316
PNM(Portable aNyMap)
• Wikipedia / PNM (画像フォーマット)
• シンプルなフォーマットで読み書きが簡単
317
ファイル識別子 画像形式 データ形式
P1 PBM: Portable Bit Map テキスト
P2 PGM: Portable Gray Map テキスト
P3 PPM: Portable Pix Map テキスト
P4 PBM: Portable Bit Map バイナリ
P5 PGM: Portable Gray Map バイナリ
P6 PPM: Portable Pix Map バイナリ
f.pbm P1 # f.pbm 6 7 0 0 0 0 0 0 0 1 1 1 1 0 0 1 0 0 0 0 0 1 1 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
テキスト形式の PBM Format
• 2値(白黒)画像用
318
feep.pbm P1 # feep.pbm 24 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ファイル識別子
画像サイズ(幅と高さ)
コメント
2値画像データ本体 0または1のデータ
テキスト形式の PGM Format
• 濃淡値(グレイスケール)画像用
319
feep.pgm P2 # feep.pgm 24 7 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ファイル識別子
画像サイズ(幅と高さ)
コメント
濃淡値画像データ本体 数値が大きいものほど明るい
明るさの最大値
テキスト形式の PPM Format
• カラー画像用
320
feep.ppm P3 # feep.ppm 4 4 15 0 0 0 0 0 0 0 0 0 15 0 15 0 0 0 0 15 7 0 0 0 0 0 0 0 0 0 0 0 0 0 15 7 0 0 0 15 0 15 0 0 0 0 0 0 0 0 0
カラー画像データ本体 RGBを1組にして書く
ファイル識別子
画像サイズ(幅と高さ)
コメント
明るさの最大値
PNMからBMPへの変換
• netpbm または ImageMagick を使うと楽
• Wikipedia / netpbm
• Wikipedia / ImageMagick
• インストールは以下
321
mintty + bash apt-cyg install netpbm apt-cyg install ImageMagick
PNMからBMPへの変換
• ImageMagick を使った場合
322
mintty + bash $ ./a | convert - image.bmp
変換結果を保存する BMP ファイル名
パイプした結果を 標準入力から読み込む
pnm を テキスト出力する プログラム
演習: 円の描画(1/2)
• pbm_practice_1.c の /*WYCH*/ の箇所を修正し、中心座標(0,0)半径rの円の内部を0外部を1とし x1,x2,y1,y2 の範囲について P1 形式の PBM 画像を出力するプログラムを作成せよ。
• ここでは円の内部をx2 + y2 < r2を定義し、境界部分は内部に含めない事とする。またx座標は右向き、y座標は下向きを正の方向とする。
323
演習: 円の描画(2/2)
• 出力結果の例
324
mintty + bash $ gcc pbm_practice_1A.c && ./a x1 x2 y1 y2 r = -5 5 -5 5 4 P1 11 11 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
mintty + bash $ gcc pbm_practice_1A.c && ./a | convert - result.bmp x1 x2 y1 y2 r = -5 5 -5 5 4
r
x1 x2
y2
y1
y2-y1+1 (0,0)
発展問題
• pbm_practice_1.c を改造して■▲★等を描いてみよう
325
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
326
C言語入門 第6週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
327
1次元配列
第3週資料の復習
328
配列変数
• 同じ変数名で複数の要素を管理する
char a[10]; // 要素数10のchar型変数の宣言
教科書 pp.85-108.
a[0]
?
a[1]
?
a[2]
?
a[3]
?
a[9]
?
...
要素数10の添え字付き変数
初期値式が与えられなかった場合、値は不定
[1] pp.103-104., p.273.
329
配列変数
• 配列変数の要素への代入
char a[10]; // 要素数10のchar型変数の宣言 a[0] = 'a'; // 0番目の要素へ代入
教科書 pp.85-108.
a[0]
'a'
a[1]
?
a[2]
?
a[3]
?
a[9]
?
...
要素数10の添え字付き変数
初期値式が与えられなかったので、値は不定 宣言後の代入
[1] pp.103-104., p.273.
330
配列変数
• 添え字は値が取れれば変数や数式でも良い
int i = 1; char a[10]; // 要素数10のchar型変数の宣言 a[i + 1] = 'a'; // 2番目の要素へ代入
教科書 pp.85-108.
a[0]
?
a[1]
?
a[2]
'a'
a[3]
?
a[9]
?
...
要素数10の添え字付き変数 [1] pp.103-104., p.273.
331
配列変数
• 確保した領域外はアクセスは禁止
char a[10]; // 要素数10のchar型変数の宣言 short b = 0x1234; a[10] = 'a';// 宣言された領域外へのアクセス
教科書 pp.85-108.
a[0]
?
a[1]
?
a[2]
?
a[3]
?
a[9]
?
...
要素数10の添え字付き変数 [1] pp.103-104., p.273.
332
b
0x1234
a[10]
'a'
他の変数が使っていたらその値を壊してしまう
ここに書き込むと何が起こるか分からない
配列変数
• 初期値式による配列変数の初期化
char a[10] = {'a', 'b'}; //初期値式付きの //要素数10のchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
a[2]
0
a[3]
0
a[9]
0
...
要素数10の添え字付き変数
初期値式が要素数より少ない場合、残りは0で初期化 初期値式による初期化
[1] pp.103-104., p.273.
333
配列変数
• 初期値式による配列変数の初期化
char a[] = {'a', 'b'}; //初期値式付きで //要素数を省略したchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
初期値式の要素数分確保される
初期値式による初期化
[1] pp.103-104., p.273.
334
配列変数
• 文字列による初期化(要素数指定)
char a[10] = "ab"; //文字列による初期値付きの //要素数10のchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
a[2]
0
a[3]
0
a[9]
0
...
要素数10の添え字付き変数
初期値式が要素数より少ない場合、残りは0で初期化 文字列と文字列終端の'¥0'
[1] pp.103-104., p.273.
335
配列変数
• 文字列による初期化(要素数自動決定)
char a[] = "ab";//文字列による初期値付きで //要素数を省略したchar型変数の宣言
教科書 pp.85-108.
a[0]
'a'
a[1]
'b'
a[2]
0
文字列の文字数+文字列終端'¥0'の1文字分の要素
文字列と文字列終端の'¥0'
[1] pp.103-104., p.273.
336
変数の初期化
• 明示的な初期化がない場合
• 外的変数、静的変数→0
• 自動変数、レジスタ変数→不定
• 初期化する場合
• 外的変数、静的変数←定数式でのみ初期化可
• コンパイル時に1度だけ初期化される
• 自動変数、レジスタ変数←任意の式で初期化可
• 実行時にブロック毎に初期化される
337
[1] pp.103-104., p.273.
配列変数の初期化
• 要素数を与えない場合
• 初期値式の数で配列のサイズが決まる
• 要素数を与えた場合
• 初期値式を与えない場合
• 値は不定
• 初期値式を与える場合
• 要素数を超えるとエラー
• 要素数に足りない部分は0で初期化される
338
[1] pp.103-104., p.273.
配列の初期化についての確認
配列の初期値と配列の初期化の範囲
339
確認: a の値はどうなるか?(1/5)
• array_ex1.cを実行し以下の事を確認しなさい • s1: そのまま実行するとどうなるか?
• s2: 6行目の初期化だけアンコメント(途中の/*と*/を削除)するとどうなるか?
• s3: 7行目だけアンコメント(先頭の//を削除)するとどうなるか?
• s4: 上記6行目7行目の両方をアンコメントするとどうなるか
• 上記4つの場合において bcc32 の場合は -O2 オプション gcc の場合は -O3 をオプションを付けてコンパイルすると結果はどう変わるか?
340
-O2, -O3 は最適化(optimization) を施すためのオプションである。
確認: a の値はどうなるか? (2/5)
• 以下の4通りの場合について確認
• 以下の4通りの場合について確認
341
array_ex1.c char a[4]/* = {'a', 'b'}*/; //a[0] = 'c';
6 7
array_ex1.c char a[4] = {'a', 'b'}; //a[0] = 'c';
6 7
array_ex1.c char a[4]/* = {'a', 'b'}*/; a[0] = 'c';
6 7
array_ex1.c char a[4] = {'a', 'b'}; a[0] = 'c';
6 7
mintty + bash + GNU C $ gcc array_ex1.c && ./a
mintty + bash + GNU C $ gcc -O3 array_ex1.c && ./a
cmd + BorlandC++ > bcc32 array_ex1.c && array_ex1
cmd + BorlandC++ > bcc32 -O2 array_ex1.c && array_ex1
全部で 4 * 4 = 16 パターン
s1 s2
s3 s4
c1 c2
c3 c4
確認: a の値はどうなるか?(3/5)
• 例えばs3s3,s4c3の場合以下のようになる
342
mintty + bash + GNU C $ gcc -O3 array_ex1.c && ./a a[0]: 0x63: 'c' a[1]: 0xcb: '▒' a[2]: 0x22: '"' a[3]: 0x00: ''
s3c3
s3 の状態ではa[1]~a[3] に対し初期
化も代入もされていないので、たまたまこの値になっただけである点に注意。 つまりこの値は未定である。
mintty + bash + GNU C $ gcc -O3 array_ex1.c && ./a a[0]: 0x63: 'c' a[1]: 0x62: 'b' a[2]: 0x00: '' a[3]: 0x00: ''
s4c3 s4 の状態でも一見 a[2], a[3] に対し
初期化も代入もされていないように見えるが「初期値式が要素数より少ない場合、残りは0で初期化」という
ルールが適用されるためたため実際には確実に 0x00 となる事が保証される。
確認: a の値はどうなるか?(4/5)
• 例えばs3s3,s3c1の場合以下のようになる
343
mintty + bash + GNU C $ gcc -O3 array_ex1.c && ./a a[0]: 0x63: 'c' a[1]: 0xcb: '▒' a[2]: 0x22: '"' a[3]: 0x00: ''
s3c3
mintty + bash + GNU C $ gcc array_ex1.c && ./a a[0]: 0x63: 'c' a[1]: 0x00: '' a[2]: 0x00: '' a[3]: 0x00: ''
s3c1
ソースコードは同じs1の状態なのにコ
ンパイル時のオプションの有無で値が変わっている点に注意。
このように未定の値は実行してみるまでどうなっているか分からない。
確認: a の値はどうなるか?(5/5)
s 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
c 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4
a[0] 0x63 0x63 0x63
a[1] 0x00 0xcb 0x62
a[2] 0x00 0x20 0x00
a[3] 0x00 0x00 0x00
344
上記で赤字で示した箇所は本来未定となる箇所。
つまりたまたまその値になっただけに過ぎない点に注意。
ベクトルの演算
一次元配列の演習
345
演習: ベクトルの演算
• 𝒂, 𝒃 を𝑥, 𝑦, 𝑧の3次元ベクトルとしたときに以下の計算を行うプログラム作ってみましょう。
• 加算(add)、減算(sub)、 内積(dot product)、外積(cross product) 𝒂 + 𝒃 = 𝑎𝑥 + 𝑏𝑥 , 𝑎𝑦 + 𝑏𝑦 , 𝑎𝑧 + 𝑏𝑧
𝒂 − 𝒃 = 𝑎𝑥 − 𝑏𝑥 , 𝑎𝑦 − 𝑏𝑦 , 𝑎𝑧 − 𝑏𝑧𝒂 ⋅ 𝒃 = 𝑎𝑥𝑏𝑥 + 𝑎𝑦𝑏𝑦 + 𝑎𝑧𝑏𝑧
𝒂 × 𝒃 = 𝑎𝑦𝑏𝑧 − 𝑎𝑧𝑏𝑦 , 𝑎𝑧𝑏𝑥 − 𝑎𝑥𝑏𝑧, 𝑎𝑥𝑏𝑦 − 𝑎𝑦𝑏𝑥
346
演習: ベクトルの演算
• 結果は result[0] または result[0~2] に格納しましょう。numresult を適宜設定してください。
• 雛形(vector_xxx_practice_1.c)をダウンロードして指定された箇所に実装してください。
• ファイル名は、以下の名前にしましょう。 • 加算: vector_add_practice_1.c
• 減算: vector_sub_practice_1.c
• 内積: vector_dot_practice_1.c
• 外積: vector_cross_practice_1.c
347
演習: ベクトルの演算
• 動作テスト用のスクリプト
• 加算: vector_add_practice_1_test.sh
• 減算: vector_sub_practice_1_test.sh
• 内積: vector_dot_practice_1_test.sh
• 外積: vector_cross_practice_1_test.sh
348
演習: ベクトルの演算
• ヒント: 書き方はいろいろある。
349
ループを用いない add result[0] = a[0] + b[0]; result[1] = a[1] + b[1]; result[2] = a[2] + b[2];
ループを用いた add for (i = 0; i < M; i++) { result[i] = a[i] + b[i]; }
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
350
C言語入門 第7週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
351
多次元配列
352
多次元の値も扱いたい
• 例えば行列の演算(1,2次元混合) 𝐴𝑥 = 𝑏
𝑎1,1 … 𝑎1,𝑛⋮ ⋱ ⋮𝑎𝑚,1 … 𝑎𝑚,𝑛
𝑥1⋮𝑥𝑛 =𝑏1⋮𝑏𝑚
行列は2次元 ベクトルは1次元
353
多次元の値も扱いたい
• 例えば白黒や濃淡画像(2次元)
354
0 32 64 128
32 64 128 192
128 128 192 255
0 32 64 128
32 64 128 192
128 128 192 255
x,y座標の各点をマス目で表し 各マス目の明るさを 256段階(8bit)の数値で表す 輝度値や明度値と言う
それぞれのマス目の数値に応じて 光らせる明るさを調整すると 絵が描ける
多次元の値も扱いたい
• 例えば白黒や濃淡画像(3次元)
355
255 0 0 255 255 0 0 255 0
255 0 255 0 0 255 0 255 255
x,y座標の各点の 光の三原色 赤緑青の明るさ(RGB値)を 数値で表す
画面を虫眼鏡で見ると こんな感じに 光っているのが見える
多次元の値も扱いたい
• 例えば白黒や濃淡画像(3次元)
356
255 0 0 255 255 0 0 255 0
255 0 255 0 0 255 0 255 255
遠目で見た場合や ソフトウェア上では 色が混ざって このように見える
画面表示の仕組み
• 方眼紙のような構造
• 光の三原色(RGB=赤緑青)で加法混色
• 通常、各色8bit(256段階)で電圧調整して明暗表現
• 計24bit(16,777,216色)=8bit×3(256×256×256 段階)
357
混色
• 加法混色
• 画面、光
• RGBカラーモデル • Red, Green, Blue
• 減法混色
• 印刷、絵の具
• CMYカラーモデル • Cyan, Magenta, Yellow
M
C
Y R
G B
Bk
R
G B
M
C
Y
W
358
メモリの構成
• メモリのアドレスは1次元でした
0x00 0x00000000
0x00 0x00000001
0x00 0x00000002
0x00 0x00000003
0x00 0xffffffff
: :
: :
0x00 0x0000000000000000
0x00 0x0000000000000001
0x00 0x0000000000000002
0x00 0x0000000000000003
0x00 0xffffffffffffffff
: :
: :
32bitのOSは32bitのアドレス空間 最大232Bytes=4GiB
64bitのOSは64bitのアドレス空間 最大264Bytes=16EiB
アドレス 格納値 アドレス 格納値
教科書 pp.52-56. 359
二次元配列変数
• 1次元のメモリを折り畳んで2次元にして使う
char a[3][4]; // 要素数3*4のchar型変数の宣言
教科書 pp.85-108.
a[0][0]
?
a[0][1]
?
a[0][2]
?
a[0][3]
?
1行分のデータ メモリのアドレスは1次元なので 実際には2行目以降も 直線状に並んでいる
[1] pp.135-137.
360
a[1][0]
?
a[1][1]
?
a[2][3]
?
...
二次元配列変数
• 要素数4の配列を3つ並べた例
char a[3][4]; // 要素数3*4のchar型変数の宣言
教科書 pp.85-108.
a[0][0]
?
a[0][1]
?
a[0][2]
?
a[0][3]
?
論理的には 2次元的に並んでいると 考えて使えば良い。
[1] pp.135-137.
361
a[1][0]
?
a[1][1]
?
a[1][2]
?
a[1][3]
?
a[2][0]
?
a[2][1]
?
a[2][2]
?
a[2][3]
?
行列の行と列
• m行n列の行列
𝑎1,1 … 𝑎1,𝑛⋮ ⋱ ⋮𝑎𝑚,1 … 𝑎𝑚,𝑛
1列 ・・・ n列
1行 : :
m行
362
Row-major と Column-major
• 本来1次元のメモリをどう区切って使うか?
• 行を連続にするか?列を連続にするか?
𝑎1,1 … 𝑎1,𝑛⋮ ⋱ ⋮𝑎𝑚,1 … 𝑎𝑚,𝑛
𝑎1,1 … 𝑎1,𝑛⋮ ⋱ ⋮𝑎𝑚,1 … 𝑎𝑚,𝑛
1列 ・・・ n列
1行 : :
m行
1列 ・・・ n列
Row-major は行方向の要素が メモリ上で連続に並ぶ C言語はこのタイプ
Column-major は列方向の要素が メモリ上で連続に並ぶ Fortran等はこのタイプ
363
a[0][0]
a[0][1]
a[0][2]
a[0][3]
:
a[1][0]
a[1][1]
a[1][2]
a[2][3]
a[0][0]
a[1][0]
a[2][0]
a[0][1]
a[1][1]
a[2][1]
a[0][2]
a[1][2]
a[2][2]
a[0][3]
a[1][3]
a[2][3]
メモリ上の配列と二次元配列(行列)
• Row-major は同じ行が繋がっている
a[0][0]
a[0][1]
a[0][2]
a[0][3]
:
a[1][0]
a[1][1]
a[1][2]
a[2][3]
a[0][0]
a[1][0]
a[2][0]
a[0][1]
a[1][1]
a[2][1]
a[0][2]
a[1][2]
a[2][2]
a[0][3]
a[1][3]
a[2][3]
: : :
C言語はこのタイプ
364
Row-major int a[m * n]; a[n * i + j];
二次元配列 int a[m][n]; a[i][j];
訂正: 2015-06-20 誤: a[m * i + j] 正: a[n * i + j]
a[0][0]
a[1][0]
a[2][0]
a[0][1]
:
a[1][1]
a[2][1]
a[0][2]
a[2][3]
a[0][0]
a[1][0]
a[2][0]
a[0][1]
a[1][1]
a[2][1]
a[0][2]
a[1][2]
a[2][2]
a[0][3]
a[1][3]
a[2][3]
メモリ上の配列と二次元配列(行列)
• Column-major は同じ列が繋がっている
a[0][0]
a[1][0]
a[2][0]
a[0][1]
a[1][1]
a[2][1]
a[0][0]
a[1][0]
a[2][0]
a[0][1]
a[1][1]
a[2][1]
a[0][2]
a[1][2]
a[2][2]
a[0][3]
a[1][3]
a[2][3]
a[0][2]
: :
a[2][3] Fortran等はこのタイプ
365
Column-major int a[m * n]; a[i + j * m];
二次元配列 int a[m][n]; a[i][j];
訂正: 2015-06-20 誤: a[i + j * n] 正: a[i + j * m]
二次元配列のメモリ上の配置
• Row-major と Column-major
教科書 pp.85-108.
a[0][0]
?
a[0][1]
?
a[0][2]
?
a[0][3]
?
1列分のデータ
[1] pp.135-137.
366
a[1][0]
?
a[1][1]
?
a[2][3]
?
...
a[0][0]
?
a[1][0]
?
a[2][0]
?
a[0][1]
?
a[1][1]
?
a[2][1]
?
a[2][3]
?
...
1行分のデータ
Row-major は行方向の要素が連続に並ぶ C言語はこのタイプ
Column-major は列方向の要素が連続に並ぶ Fortran等はこのタイプ
三次元配列変数
• 1次元のメモリを折り畳んで3次元にして使う
char a[2][3][4]; // 要素数2*3*4のchar型変数の宣言
教科書 pp.85-108.
[0][0][0]
?
[0][0][1]
?
[0][0][2]
?
[0][0][3]
?
1行分のデータ メモリのアドレスは1次元なので 実際には2行目以降も 直線状に並んでいる
[1] pp.135-137.
367
[0][1][0]
?
[0][1][1]
?
[1][2][3]
?
...
三次元配列変数
• 要素数3*4の配列を2つ並べた例
char a[2][3][4]; // 要素数2*3*4のchar型変数の宣言
教科書 pp.85-108.
[0][0][0]
?
[0][0][1]
?
[0][0][2]
?
[0][0][3]
?
[1] pp.135-137.
368
[0][1][0]
?
[0][1][1]
?
[0][1][2]
?
[0][1][3]
?
[0][2][0]
?
[0][2][1]
?
[0][2][2]
?
[0][2][3]
?
[1][0][0]
?
[1][0][1]
?
[1][0][2]
?
[1][0][3]
?
[1][1][0]
?
[1][1][1]
?
[1][1][2]
?
[1][1][3]
?
[1][2][0]
?
[1][2][1]
?
[1][2][2]
?
[1][2][3]
?
配列変数の使用例
• 同じ次元・要素数でもいろんな解釈で使える
char a[3][4];
教科書 pp.85-108. 369
char a[2][3][4];
a[ch][t];
a[t][x];
a[y][x];
a[t][y][x];
a[z][y][x];
行列の演算
二次元配列の演習
370
演習: 行列とベクトルの演算
• 𝐴 を3×3の行列、 𝒃, 𝒙 を3次元のベクトルとしたときに以下の計算を行うプログラム作ってみましょう。
• 積(matrix_vector_mul_practice_1.c)
𝒃 = 𝐴𝒙: 𝑏𝑖 = 𝑎𝑖,𝑗𝑥𝑗
𝑛
𝑗=1
371
演習: 行列の演算
• 𝐴, 𝐵 を3×3の行列としたときに以下の計算を行うプログラム作ってみましょう。
• 加算(add)、減算(sub)、要素単位の乗算(times)、行列乗算(mtimes)、 転置(transpose) • 加算: 𝐶 = 𝐴 + 𝐵: 𝑐𝑖,𝑗 = 𝑎𝑖,𝑗 + 𝑏𝑖,𝑗
• 減算: 𝐶 = 𝐴 − 𝐵: 𝑐𝑖,𝑗 = 𝑎𝑖,𝑗 − 𝑏𝑖,𝑗
• 要素単位の乗算: 𝐶 = 𝐴.∗ 𝐵: 𝑐𝑖,𝑗 = 𝑎𝑖,𝑗𝑏𝑖,𝑗
• 行列乗算: 𝐶 = 𝐴 ∗ 𝐵: 𝑐𝑖,𝑗 = 𝑎𝑖,𝑘𝑏𝑘,𝑗𝑚𝑘=1
• 転置: 𝐶 = 𝐴𝑇: 𝑐𝑖,𝑗 = 𝑎𝑗,𝑖
372
訂正: 2015-06-20 誤: 𝐶 = 𝐴 + 𝐵 正: 𝐶 = 𝐴.∗ 𝐵
訂正: 2015-06-20 誤: 𝐶 = 𝐴 + 𝐵 正: 𝐶 = 𝐴 ∗ 𝐵
演習: 行列の演算
• matrix_vector_xxx_practice_1.c を元にして以下のファイル名で作成せよ
• 加算: matrix_vector_add_practice_1.c
• 減算: matrix_vector_sub_practice_1.c
• 要素単位の乗算: matrix_vector_times_practice_1.c
• 行列乗算: matrix_vector_mtimes_practice_1.c
• 転置: matrix_vector_transpose_practice_1.c
373
ループの組み立て
重複した処理をループにまとめる
数式をループに変換する
374
ループの組み立て 重複した処理をまとめる
375
sum = 1 + 1 + 1 + 1 + 1;
追加: 2015-06-20
sum = 1 + 1 + 1 + 1 + 1;
sum = 1; sum += 1; sum += 1; sum += 1; sum += 1;
演算を構成する項を縦に並べる
各項を単文で書き直す sum = 0; // 初期化 sum += 1; sum += 1; sum += 1; sum += 1; sum += 1;
5回繰り返している
sum = 0; // 初期化 for (i = 0; i < 5; i++) { sum += 1; }
積算の場合 初期値を与えて 各単文を 全く同じ処理の 繰り返しにする
ループの組み立て 重複した処理をまとめる
376
sum = 1 + 2 + 3 + 4 + 5;
追加: 2015-06-20
sum = 1 + 2 + 3 + 4 + 5;
sum = 1; sum += 2; sum += 3; sum += 4; sum += 5;
演算を構成する項を縦に並べる
ループカウンタを活用する
sum = 0; // 初期化 sum += 1; sum += 2; sum += 3; sum += 4; sum += 5;
1~5まで増えている
sum = 0; // 初期化 for (i = 1; i <= 5; i++) { sum += i; }
積算の場合 初期値を与えて 各単文を 全く同じ処理の 繰り返しにする
各項を単文で書き直す
ループの組み立て 重複した処理をまとめる
377
sum = a[0]*b[0]+a[1]*b[1]+・・・+a[N-1]*b[N-1];
追加: 2015-06-20
sum = a[0]*b[0] + a[1]*b[1] ⋮ ⋮ + a[N-1]*b[N-1];
sum = a[0]*b[0]; sum += a[1]*b[1]; ⋮ ⋮ sum += a[N-1]*b[N-1];
演算を構成する項を縦に並べる
各項を単文で書き直す sum = 0; // 初期化 sum += a[0]*b[0]; sum += a[1]*b[1]; ⋮ ⋮ sum += a[N-1]*b[N-1];
積算の場合 初期値を与えて 各単文を 全く同じ処理の 繰り返しにする
sum = 0; // 初期化 for (i = 0; i < N; i++) { sum += a[i]*b[i]; }
0~N-1 まで 増えて いる
ループカウンタを活用する
ループの組み立て 数式とC言語の対応を考える
• 数式
• C言語
378
𝑖の積算 s = 0; for (i = 1; i <= n; i++) { s += i; }
𝑠 = 𝑖
𝑛
𝑖=1
1の積算 s = 0; for (i = 1; i <= n; i++) { s += 1; }
𝑠 = 1
𝑛
𝑖=1
積算項: 1 ↔ 𝑖
定石の書き方から変形
追加: 2015-06-20 加筆: 2015-06-26
ループの組み立て 数式とC言語の対応を考える
• 数式
• C言語
379
𝑎𝑖,𝑗𝑥𝑗の積算
b[i] = 0; for (j = 1; j <= n; j++) { b[j] += a[i][j] * x[j]; }
𝑏𝑖 = 𝑎𝑖,𝑗𝑥𝑗
𝑛
𝑗=1
𝑖の積算 s = 0; for (i = 1; i <= n; i++) { s += i; }
𝑠 = 𝑖
𝑛
𝑖=1
結果: 𝑠 → 𝑏𝑖 カウンタ: 𝑖 → 𝑗 積算項: 𝑖 → 𝑎𝑖,𝑗𝑥𝑗
定石の書き方から変形
追加: 2015-06-20 加筆: 2015-06-26
ループの組み立て 数式とC言語の対応を考える
• 数式
• C言語
380
𝑎𝑖,𝑗𝑥𝑗の積算
b[i] = 0; for (j = 0; j < n; j++) { b[j] += a[i][j] * x[j]; }
追加: 2015-06-20 加筆: 2015-06-26
𝑏𝑖 = 𝑎𝑖,𝑗𝑥𝑗
𝑛−1
𝑗=0
要素数𝑛個の数列や配列は 数学では 𝑎1⋯𝑎𝑛 と書く場合が多いが C言語では a[0]⋯a[n-1] になる
C言語に合わせて 添え字を1つずらす
単純に置きかえ 範囲: 1: 𝑛 → 0: 𝑛 − 1 → 0: 𝑛
𝑎𝑖,𝑗𝑥𝑗の積算
b[i] = 0; for (j = 1; j <= n; j++) { b[j] += a[i][j] * x[j]; }
𝑏𝑖 = 𝑎𝑖,𝑗𝑥𝑗
𝑛
𝑗=1
ループの組み立て 数式とC言語の対応を考える
• 数式
• C言語
381
𝑎𝑖,𝑗𝑥𝑗の積算
b[i] = 0; for (j = 0; j < n; j++) { b[j] += a[i][j] * x[j]; }
追加: 2015-06-20 加筆: 2015-06-26
𝑏𝑖 = 𝑎𝑖,𝑗𝑥𝑗
𝑛−1
𝑗=0
全ての𝑏𝑖についてループして計算
𝑎𝑖,𝑗𝑥𝑗の積算
for (i = 0; i < m; i++) { b[i] = 0; for (j = 0; j < n; j++) { b[j] += a[i][j] * x[j]; } }
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
382
C言語入門 第8週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
383
関数定義と関数呼び出し
384
関数の宣言・定義・利用
• 書式:
385
関数の定義 戻り値の型 関数名(引数の宣言, ...) { // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 }
関数の宣言 戻り値の型 関数名(引数の宣言, ...);
関数の利用 変数名 = 関数名(引数, ...);
.h ファイルへ書き出す
.c ファイルへ書き出す
適宜呼び出す
教科書 pp.149-160.
関数(サブルーチン)
• C 言語は処理を関数にしてまとめる
• main も関数
386
c_template.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { // Here is a main routine. return EXIT_SUCCESS; }
フルセットの main 関数
関数名、引数、戻り値の定義
関数の本体
コマンドライン引数の数
コマンドライン引数を格納した 複数の文字列へのポインタ
教科書 pp.149-160.
(ユーザー定義)関数
• 関数は自分で作ることが出来る
• 機能、分量等、ある程度まとまった処理は 関数にまとめる
• 可読性が向上する
• 再利用が容易になる
• 関数の定義の書式:
387
戻り値の型 関数名(引数の宣言, ...) { // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 }
教科書 pp.149-160.
C言語の関数は 0個以上の引数と 0または1個の戻り値を持つ。 引数や戻り値が不要な場合は void を宣言するよう 推奨されている。
return 文
return 戻り値;
• 関数から抜けて呼び出し元へ戻り値を渡す return_ex1.c int sub() { return 123; printf("hello¥n"); } void main() { int x = sub(); printf("%d¥n", x); }
return文に与えた値が 関数の戻り値になり 演算に使用される return文を実行すると
呼び出し元に戻るので それ以降の文は 実行されない
mintty + bash + GNU C $ gcc return_ex1.c && ./a 123
return 文
• 戻り値の型は必要に応じて適切な型を選ぶ
return_ex2.c int sub() { return 1.23; } void main() { double x = sub(); printf("%f¥n", x); }
この例では戻り値の型はintなので return文にdoubleを与えても 戻り値はint型にキャストされるので 整数になる
mintty + bash + GNU C $ gcc return_ex2.c && ./a 1.000000
例えばもし 浮動小数点数の値を 戻したいなら 戻り値の型は doubleでないといけない
訂正2015-06-26 誤: double = sub(); 正: double x = sub();
関数のプロトタイプ宣言
• 関数は使う前に宣言されている必要がある prototype_ex1_err.c void main() { double x = sub(); printf("%f¥n", x); } double sub() { return 1.23; }
mintty + bash + GNU C $ gcc prototype_ex1_err.c prototype_ex1_err.c:9:8: エラー: ‘sub’ と型が競合しています double sub() ^ prototype_ex1_err.c:5:14: 備考: 前の ‘sub’ の暗黙的な宣言はここです double x = sub(); ^
異なる引数や戻り値で同名の関数は 関数名が競合するため利用出来ない
未宣言の関数の引数や戻り値は int型を仮定して暗黙的に宣言される
関数のプロトタイプ宣言
• 関数は使う前に宣言されている必要がある prototype_ex1.c
double sub(); void main() { double x = sub(); printf("%f¥n", x); } double sub() { return 1.23; }
int型以外の引数や戻り値を持つ関数は あらかじめプロトタイプ宣言されていなければならない
mintty + bash + GNU C $ gcc prototype_ex1.c && ./a 1.230000
(ユーザー定義)関数の例
• 閏年の判定を関数にまとめてみる
392
leap_year_func_ex4_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; fprintf(stderr, "year = ?¥b"); scanf("%d", &year); printf("%d is%s leap year.¥n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; }
関数の定義
自分で作成した関数を サブルーチンとして呼ぶ
教科書 pp.149-160.
(ユーザー定義)関数の例
• 関数を使う前にはパラメータの型と数が 分かっている必要がある(コンパイラの都合)
393
#include <stdio.h> #include <stdlib.h> int main() { int year; fprintf(stderr, "year = ?¥b"); scanf("%d", &year); printf("%d is%s leap year.¥n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } int is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; }
使う時点で不明な戻り値の型はintを仮定される。 引数については何も仮定しない。 もし、実際に定義されている関数の型が異なると 型が競合してエラーになる。
教科書 pp.153-160.
型が不明なので、このタイミングで int is_leap_year(int); が仮定される。
[1] pp.86-89.
関数のプロトタイプ宣言
• 関数は使う前にプロトタイプ宣言する
394
#include <stdio.h> #include <stdlib.h> int is_leap_year(int year); int main() { int year; fprintf(stderr, "year = ?¥b"); scanf("%d", &year); printf("%d is%s leap year.¥n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; }
関数のプロトタイプ宣言さえしておけば 関数の定義はどこにあっても良い。 例えば外部のファイルにあっても良い。
教科書 pp.149-160.
関数のプロトタイプ宣言 戻り値の型、引数の数と型を宣言する
関数をファイルに分離する
• 再利用性が高まる(コピペしなくて良くなる)
395
is_leap_year_func_ex4_2.c #include "is_leap_year_func.h" int is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; }
is_leap_year_func.h #ifndef IS_LEAP_YEAR_FUNC_H #define IS_LEAP_YEAR_FUNC_H int is_leap_year(int year); #endif
ヘッダファイル
関数の宣言 他所から使う際に、 関数名、引数、戻り値の 情報を得るために必要
include ガード(二重includeを防ぐ)
関数本体のソースファイル
教科書 pp.203-206.
関数本体の定義
ファイルに分離した関数の利用
• main 関数の部分
396
is_leap_year_main.c #include <stdio.h> #include <stdlib.h> #include "is_leap_year_func.h" int main() { int year; fprintf(stderr, "year = ?¥b"); scanf("%d", &year); printf("%d is%s leap year.¥n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; }
ヘッダファイルの include
外部ファイルで定義した 関数を呼び出し
教科書 pp.203-206.
• コンパイルする複数のCのソースファイルを コンパイラに与えれば良い
分割コンパイル
397
mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_ex4_2.c
コマンドプロンプト + Borland C++ >bcc32 is_leap_year_main.c is_leap_year_func_ex4_2.c
与えるのはCのソースファイルのみ ヘッダファイルは #include 文により 自動的に取り込まれる
プリプロセッサ
• 条件付きコンパイル
398
#if 定数式 #elif 定数式 #else #endif #if 0 // ここはコンパイルされない #endif #if defined(__BORLANDC__) // __BORLANDC__ という名前のマクロが // 定義されていた場合のみコンパイルされる #endif #if defined(__BORLANDC__) && 0x0551 <= __BORLANDC__ // ... #endif
条件に合った箇所のみを コンパイラに渡す構文
定数式の結果は0と非0の真偽値
定数式なら 演算も可能
プリプロセッサ
• 条件付きコンパイル
399
#ifdef __BORLANDC__ // ... #endif #ifndef __BORLANDC__ // ... #endif
#if defined( ) の短縮表記
#if !defined( ) の短縮表記
プリプロセッサ
• 定義済みマクロ
400
__LINE__ 現在の行番号 __FILE__ 現在のファイル名 __func__ 現在の関数名(C99) __FUNC__ 現在の関数名(Borland C++) #ifdef __BORLANDC__ #define __func__ __FUNC__ #endif
Borland C++ で C99互換の定義済みマクロが 使えるようにする 条件付きコンパイル
関数をファイルに分離する
• 作業用変数を用いる場合
401
is_leap_year_func_ex1_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; } return leap_year_flag; }
教科書 pp.203-206.
ここは leap_year_practice_2.c そのまま
結果を戻り値として返す
ローカル変数の宣言
ローカル変数 ブロック内の作業用。 ブロック外に 同じ名前の変数があっても 競合せず安全に使える。
ブロック(複文) { } で囲まれた領域の事
• 関数のプロトタイプ宣言が同じなら関数は差し替えて使える。
関数の差し替え
402
mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_ex4_2.c
mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_ex1_1.c
実装は異なるが プロトタイプ宣言が同じ関数を 定義したファイルで差し替え
変数のスコープ(有効範囲)
• 大域(グローバル)変数
• プログラム全体からアクセス可能
• 局所(ローカル)変数
• ブロック内からのみアクセス可能
403 教科書 pp.161-169.
C言語では { } で 囲まれた部分がブロック
変数のスコープ(有効範囲)
• ローカル変数は{}の中のみで有効
404
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; }
関数外で宣言するとグローバル変数
教科書 pp.161-169.
関数の引数はローカル変数
ブロックを作成するとローカル変数の有効範囲を制限出来る
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
変数のスコープ(有効範囲)
• ローカル変数は{}の外側に影響していない
405
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; }
教科書 pp.161-169.
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
関数の演習
• is_leap_year_func_ex1_1.c、is_leap_year_func_ex4_2.c を参考に以下のファイルの処理を関数にしてファイルに分離してみましょう。 • leap_year_ex1_2.c → is_leap_year_func_ex1_2.c
• leap_year_ex2_1.c → is_leap_year_func_ex2_1.c
• leap_year_ex2_2.c → is_leap_year_func_ex2_2.c
• leap_year_ex3_1.c → is_leap_year_func_ex3_1.c
• leap_year_ex3_2.c → is_leap_year_func_ex3_2.c
• leap_year_ex4_1.c → is_leap_year_func_ex4_1.c
• is_leap_year_main.c と一緒にコンパイルしてみて動作を確認してみましょう。
406 教科書 pp.203-206.
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
407
C言語入門 第9週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
408
関数の設計
409
例題: 1~nの和
• 1~nの和
𝑖
𝑛
𝑖=1
= 1 + 2 +⋯+ 𝑛
を求めるプログラムを考える。
• 知っているやり方 • ループを使ってi=1~nまで増やしながら足す • 和の公式を計算する
𝑖
𝑛
𝑖=1
=𝑛 𝑛 + 1
2
410 教科書 pp.190-197.
例題: 1~nの和
• まず、何も考えずにループで書いてみる
411
sum_ex1_for.c #include <stdio.h> void main() { int i, n; int sum = 0; fprintf(stderr, "n = ?¥b"); scanf("%d", &n); for (i = 1; i <= n; i++) { sum += i; } printf("%d¥n", sum); }
教科書 pp.190-197.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
mintty + bash + GNU C $ gcc sum_ex1_for.c && ./a <<<10 n = 55
main 関数の中に すべての処理を入れた状態
例題: 1~nの和
• main 関数と sum 関数に分割してみる
412
sum_ex2_for.c #include <stdio.h> int sum(int n) { int i; int sum = 0; for (i = 1; i <= n; i++) { sum += i; } return sum; }
教科書 pp.190-197.
sum_ex2_for.c void main() { int n; fprintf(stderr, "n = ?¥b"); scanf("%d", &n); printf("%d¥n", sum(n)); }
1 2 3 4 5 6 7 8 9 10 11 12
14 15 16 17 18 19 20 21 22
ファイルはまだ、1ファイルのまま 分けてない状態
mintty + bash + GNU C $ gcc sum_ex2_for.c && ./a <<<10 n = 55
例題: 1~nの和
• main と sum を 2 ファイルに分割してみる
413
sum_ex3_for.c int sum(int n) { int i; int sum = 0; for (i = 1; i <= n; i++) { sum += i; } return sum; }
教科書 pp.190-197.
sum_ex3_main.c #include <stdio.h> int sum(int n); void main() { int n; fprintf(stderr, "n = ?¥b"); scanf("%d", &n); printf("%d¥n", sum(n)); }
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10 11 12 13
mintty + bash + GNU C $ gcc sum_ex3_main.c sum_ex3_for.c && ./a <<<10 n = 55
例題: 1~nの和
• ヘッダファイルも作ってみる
414
sum_ex4_for.c include "sum_ex4.h" int sum(int n) { int i; int sum = 0; for (i = 1; i <= n; i++) { sum += i; } return sum; }
教科書 pp.190-197.
sum_ex4_main.c #include <stdio.h> #include "sum_ex4.h" void main() { int n; fprintf(stderr, "n = ?¥b"); scanf("%d", &n); printf("%d¥n", sum(n)); }
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
mintty + bash + GNU C $ gcc sum_ex4_main.c sum_ex4_for.c && ./a <<<10 n = 55
sum_ex4.h #ifndef SUM_EX4_H #define SUM_EX4_H int sum(int n); #endif//SUM_EX4_H
1 2 3 4
訂正2015-06-26 追加: 2行目: #define SUM_EX4_H
例題: 1~nの和
• 別の方法で関数を実装し直してみる
415
sum_ex4_formula.c #include "sum_ex4.h" int sum(int n) { return n * (n + 1) / 2; }
教科書 pp.190-197.
sum_ex4_main.c #include <stdio.h> #include "sum_ex4.h" void main() { int n; fprintf(stderr, "n = ?¥b"); scanf("%d", &n); printf("%d¥n", sum(n)); }
1 2 3 4 5 6
1 2 3 4 5 6 7 8 9 10 11 12
sum_ex4.h #ifndef SUM_EX4_H #define SUM_EX4_H int sum(int n); #endif//SUM_EX4_H
1 2 3 4
mintty + bash + GNU C $ gcc sum_ex4_main.c sum_ex4_formula.c && ./a <<<10 n = 55
sum 関数だけ差し替え
訂正2015-06-26 追加: 2行目: #define SUM_EX4_H
再帰(RECURSION)
自分自身を呼び出す関数
416
例題: 再帰による1~nの和
• 1~nの和
𝑖
𝑛
𝑖=1
= 1 + 2 +⋯+ 𝑛
を求めるプログラムを考える。
• 知っているやり方 • ループを使ってi=1~nまで増やしながら足す • 和の公式を計算する
𝑖
𝑛
𝑖=1
=𝑛 𝑛 + 1
2
• 他にもやり方がある?
417 教科書 pp.190-197.
例題: 再帰による1~nの和
• 1~nの和は以下のように1~n-1の和とnに分解出来る
𝑖
𝑛
𝑖=1
= 1 + 2 +⋯+ 𝑛 − 1 + 𝑛
つまり
𝑖 = 𝑖
𝑛−1
𝑖=1
+ 𝑛 : 1 < 𝑛
1 : (𝑛 = 1)
𝑛
𝑖=1
これを関数にしてみると・・・
418 教科書 pp.190-197.
例題: 再帰による1~nの和
• 1~n-1とnに分けて計算してみる
419
sum_ex4_recursion.c #include "sum_ex4.h" int sum(int n) { if (n == 1) { return 1; } return sum(n - 1) + n; }
教科書 pp.190-197.
sum_ex4_main.c #include <stdio.h> #include "sum_ex4.h" void main() { int n; fprintf(stderr, "n = ?¥b"); scanf("%d", &n); printf("%d¥n", sum(n)); }
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10 11 12
mintty + bash + GNU C $ gcc sum_ex4_main.c sum_ex4_recursion.c && ./a <<<10 n = 55
n-1 にして 自分自身を呼んでいる 再帰的処理
sum 関数だけ差し替え
sum_ex4.h #ifndef SUM_EX4_H #define SUM_EX4_H int sum(int n); #endif//SUM_EX4_H
1 2 3 4
訂正2015-06-26 追加: 2行目: #define SUM_EX4_H
階乗(FACTORIAL)の計算
関数の演習
420
階乗 (factorial)関数
• 考え方
• ループの場合
𝑛! = 1 ⋅ 1 ⋅ 2⋯ 𝑛 − 1 ⋅ 𝑛𝑛
• 再帰の場合 𝑛! = 𝑛 ⋅ 𝑛 − 1 !
𝑛 − 1 ! = 𝑛 − 1 ⋅ 𝑛 − 2 !⋮
1! = 1 ⋅ 0!0! = 1
421
再帰の場合は 1つ小さな値を 引数にして 自分で自身を呼ぶ だけ
それ以上小さく 出来なくなったら ルールに沿って 適当な値を返す
ループの場合はこの数列を作り𝑛個積算していく
教科書 pp.190-197.
演習: factorial_practice1_for.c
• int型の整数𝑛について階乗(factorial) 𝑛!を求める関数factorial(n)をforループを用いてfactorial_practice1_for.cに実装せよ
• main関数とヘッダは、factorial_practice1_main.c 及び factorial_practice1.h に用意してある。
• 引数 • int n : 整数
• 戻り値 • 𝑛! をint型で返せ
422 教科書 pp.190-197.
訂正2015-06-26 誤: factoriali 正: factorial
演習: factorial_practice1_recursion.c
• int型の整数𝑛について階乗(factorial) 𝑛!を求める関数factorial(n)を再帰を用いてfactorial_practice1_recursion.c に実装せよ
• main関数とヘッダは、factorial_practice1_main.c 及び factorial_practice1.h に用意してある。
• 引数 • int n : 整数
• 戻り値 • 𝑛! をint型で返せ
423 教科書 pp.190-197.
訂正2015-06-26 誤: factoriali 正: factorial
例題: factorial_practice1_*.c
• 実行例
424
mintty + bash + GNU C $ gcc factorial_practice1_main.c factorial_practice1_for.c && ./a <<<5 n = 120
mintty + bash + GNU C $ gcc factorial_practice1_main.c factorial_practice1_recursion.c && ./a <<<5 n = 120
訂正2015-06-26 誤: factoriali 正: factorial
記憶クラス指定子
通用範囲、格納場所、初期化の違い
425
変数の初期化
• 明示的な初期化がない場合
• 外的変数、静的変数→0
• 自動変数、レジスタ変数→不定
• 初期化する場合
• 外的変数、静的変数←定数式でのみ初期化可
• コンパイル時に1度だけ初期化される
• 自動変数、レジスタ変数←任意の式で初期化可
• 実行時にブロック毎に初期化される
426
[1] pp.103-104., p.273.
記憶クラス指定子による 通用範囲と格納場所と初期化
関数外 関数内 備考
無指定
広域的 メインメモリー 定数でコンパイル時1回
局所的 スタック 変数可、実行時毎回
標準的な 外部変数と内部変数
auto
局所的 スタック 変数可、実行時毎回
register
局所的 レジスタまたはスタック 変数可、実行時毎回
アドレス演算子使用不可
extern
広域的 メインメモリー 宣言のみ可能
広域的 メインメモリー 宣言のみ可能
初期化不可 (定義時に初期化済み)
static
広域的(ファイル内限定) メインメモリー 定数でコンパイル時1回
局所的 メインメモリー 定数でコンパイル時1回
静的変数
427
記憶クラス指定子:無指定
関数外
• 外部変数の定義
• 広域的
• コンパイル時に1回だけ初期化
• 定数でのみ初期化可能
関数内
• 自動変数の定義
• 局所的
• 各ブロックの実行時に毎回初期化
• 変数でも初期化可能
• auto と同義
428
記憶クラス指定子: auto
関数外
• 使用不可
関数内
• 自動変数の定義
• 局所的
• 各ブロックの実行時に毎回初期化
• 変数でも初期化可能
429
記憶クラス指定子: register
関数外
• 使用不可
関数内
• レジスタ変数の定義
• 局所的
• 各ブロックの実行時に毎回初期化
• 変数でも初期化可能
• アドレス演算子&使用不可
430
レジスタ変数は自動変数の一種 優先的にレジスタに割り当てるが レジスタは数が少ないので 必ずしもレジスタに割り当てられるわけではない
記憶クラス指定子: static
関数外
• 静的変数の定義
• 広域的(ただしファイル外からはアクセス出来ない)
• コンパイル時に1回だけ初期化
• 定数でのみ初期化可能
関数内
• 静的変数の定義
• 局所的
• コンパイル時に1回だけ初期化
• 定数でのみ初期化可能
431
記憶クラス指定子: extern
関数外
• 外部変数の宣言
• 広域的
• 定義の側で初期化を行う(宣言の側では初期化出来ない)
関数内
• 外部変数の宣言
• 広域的
• 定義の側で初期化を行う(宣言の側では初期化出来ない)
432
変数のメモリへの割り当て
• 通常の自動変数はスタックに作成される
433
呼び出し元アドレス
int a
int b
int c
#include <stdio.h> void main() { int a; int b; int c; }
実行時に随時スタックに積んで行く 使い終わったらスタックから破棄する
教科書pp.177-179.
変数のメモリへの割り当て
• 外部変数、静的変数はメモリの特定領域へ作成される
434
呼び出し元アドレス
int a
int b
#include <stdio.h> int a; void main() { static int b; int c; }
:
:
コンパイル時にあらかじめ 決められたメモリに配置される
int c
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
• LIFO(Last-In-First-Out)
• FILO(First-In-Last-Out)
• 先入れ後出し
• 後入れ先出し
• サブルーチン呼び出し等で呼び出し元アドレスの保存やローカル変数の作成に使われる
• push (一番上に格納)
• pop (一番上から取り出し)
435
呼び出し元アドレス
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
• LIFO(Last-In-First-Out)
• FILO(First-In-Last-Out)
• 先入れ後出し
• 後入れ先出し
• サブルーチン呼び出し等で呼び出し元アドレスの保存やローカル変数の作成に使われる
• push (一番上に格納)
• pop (一番上から取り出し)
436
呼び出し元アドレス
教科書pp.177-179.
int a
スタックメモリ
• 積み木式データ構造
• LIFO(Last-In-First-Out)
• FILO(First-In-Last-Out)
• 先入れ後出し
• 後入れ先出し
• サブルーチン呼び出し等で呼び出し元アドレスの保存やローカル変数の作成に使われる
• push (一番上に格納)
• pop (一番上から取り出し)
437
呼び出し元アドレス
int a
教科書pp.177-179.
int b
スタックメモリ
• 積み木式データ構造
• LIFO(Last-In-First-Out)
• FILO(First-In-Last-Out)
• 先入れ後出し
• 後入れ先出し
• サブルーチン呼び出し等で呼び出し元アドレスの保存やローカル変数の作成に使われる
• push (一番上に格納)
• pop (一番上から取り出し)
438
呼び出し元アドレス
教科書pp.177-179.
int a
スタックメモリ
• 積み木式データ構造
• LIFO(Last-In-First-Out)
• FILO(First-In-Last-Out)
• 先入れ後出し
• 後入れ先出し
• サブルーチン呼び出し等で呼び出し元アドレスの保存やローカル変数の作成に使われる
• push (一番上に格納)
• pop (一番上から取り出し)
439
呼び出し元アドレス
教科書pp.177-179.
レジスタ
• CPU 内で計算に用いられるメモリの一種
• メインメモリよりも高速だが数が少ない
• x86では32bitの汎用レジスタがたったの8つ(eax, ecx, edx, ebx, esp, ebp, esi, edi)しかない
440
レジスタ
メインメモリー 計算する際は必要な値を レジスタにロードしては戻している
可能なら限り値をレジスタに 置きっ放しにしておくと速くなる
メインメモリ外(CPU内)にあるためアドレスがない
変数の宣言と定義の違い
• 宣言
• 変数の型やサイズを指示する
• メモリへの割り当ては行わない
• 定義
• 変数の宣言を行う
• 実際にメモリへの割り当ても行う
441 [1] p.98.
分割コンパイルする場合、複数のファイルで同じ変数を重複して定義は出来ない 定義はどれか1つのファイルで1回だけ行い あとのファイルは宣言だけして定義済みの変数を参照する。
変数の宣言と定義
• 複数のファイルでの外部変数を共有する
442 [1] p.98.
extern_ex1_main.c #include <stdio.h> void sub(int); // 外部関数の宣言 int sum = 0; // 外部変数の定義と初期化 void main() { int i; for (i = 0; i < 10; i++) { sub(i); } printf("%d¥n", sum); }
extern_ex1_sub.c extern int sum; // 外部変数の宣言 // 外部関数の定義 void sub(int x) { sum += x; }
static 宣言
• static 宣言には 3 種類の意味がある
443 教科書p.164., [1] p.101.
外部的 内部的
変数 外部変数に対する static 宣言 内部変数に対する static 宣言
関数 関数に対する static 宣言
外部変数や関数を 宣言されたファイル外から隠す
内部変数を 静的に割り当てる
外部変数に対する static 宣言
• 複数ファイルでの外部変数共用
444
static_ex1_main.c #include <stdio.h> extern int val1; // 外部変数の宣言 //extern int val2; // 外部変数の宣言 void main() { printf("val1: %d¥n", val1); //printf("val2: %d¥n", val2); // val2 は static 変数なので // ファイル外からアクセス出来ない }
static_ex1_sub.c int val1 = 1; // 外部変数の定義と初期化 static int val2 = 2; // 外部変数の定義と初期化
教科書p.164., [1] p.101.
内部変数に対する static 宣言
• 複数ファイルでの外部変数共用
445
static_ex2_main.c void sub1(); // 外部関数の宣言 void sub2(); // 外部関数の宣言 void main() { sub1(); sub1(); sub2(); sub2(); }
static_ex2_sub.c #include <stdio.h> void sub1() { int i = 0; // 自動変数の定義 printf("sub1: %d¥n", i++); } void sub2() { static int i = 0; // 静的変数の定義 printf("sub2: %d¥n", i++); }
mintty + bash + GNU C $ gcc static_ex1_main.c static_ex1_sub.c && ./a sub1: 0 sub2: 0 sub1: 0 sub2: 1
静的変数はコンパイル時に 1回だけしか初期化されない
教科書p.164., [1] p.101.
関数に対する static 宣言
• 複数ファイルでの外部変数共用
446
static_ex3_main.c #include <stdio.h> void sub1(); // 外部関数の宣言 //void sub2(); // 外部関数の宣言 void main() { sub1(); sub1(); // sub2(); // sub2 は static 関数なので // sub2(); // ファイル外からは呼べない }
static_ex3_sub.c #include <stdio.h> void sub1() { printf("sub1¥n"); } static void sub2() { printf("sub2¥n"); }
教科書p.164., [1] p.101.
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
447
C言語入門 第10週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
448
基本型
449
標準ヘッダ: <limits.h>
450
• 整数型の最小値・最大値は以下のマクロで定義されている
unsigned signed
最小値 最大値 最小値 最大値
char 0 UCHAR_MAX SCHAR_MIN SCHAR_MAX
short 0 USHRT_MAX SHRT_MIN SHORT_MAX
int 0 UINT_MAX INT_MIN INT_MAX
long 0 ULONG_MAX LONG_MIN LONG_MAX
char 型に関する定義
CHAR_BIT char 型のビット数
CHAR_MIN 0 または SCHAR_MIN
CHAR_MAX UCHAR_MAX または SCHAR_MAX
標準ヘッダ: <float.h>
451
• 浮動小数点型に関する定数は以下のマクロで定義されている
float double
指数表現の基数 FLT_RADIX DBL_RADIX
精度の10進桁数 FLT_DIG DBL_DIG
仮数部における基数(RADIX)の桁数 FLT_MANT_DIG DBL_MANT_DIG
1.0 + 𝑥 ≠ 1.0となる最小の𝑥 FLT_EPSILON DBL_EPSILON
最大値 FLT_MAX DBL_MAX
正規化された最小の浮動小数値 FLT_MIN DBL_MIN
RADIX𝑛 − 1が表現可能な𝑛の最大値 FLT_MAX_EXP DBL_MAX_EXP
10𝑛を正規化された浮動小数値として表現可能な𝑛の最小値 FLT_MIN_EXP DBL_MIN_EXP
良くあるエラーの例
エラーメッセージを読みエラーの原因を推測しましょう
452
gcc のエラーメッセージ
• 環境変数LANGを設定すると言語が変わる
453
mintty + bash + GNU C $ LANG=C gcc error1.c error1.c: In function 'main': error1.c:7:3: error: expected ';' before 'printf' printf("world¥n"); ^
mintty + bash + GNU C $ LANG=ja_JP.UTF-8 gcc error1.c error1.c: 関数 ‘main’ 内: error1.c:7:3: エラー: expected ‘;’ before ‘printf’ printf("world¥n"); ^
ロケール(locale)と呼ばれる 多言語化の仕組み JM: locale (7)
mintty の locale の設定
• mintty左上のアイコンからoption→Text
454
ここに Locale: ja_JP Cahaacter set: UTF-8 を設定して「OK」しておく
文末の「;」忘れ
• 文末に「;」を忘れると次の文字でエラーに
455
error1.c int main() { printf("hello, ") printf("world¥n"); return EXIT_SUCCESS; }
4 5 6 7 8 9 10
mintty + bash + GNU C $ gcc error1.c error1.c: 関数 ‘main’ 内: error1.c:7:3: エラー: expected ‘;’ before ‘printf’ printf("world¥n"); ^
エラーが生じたのは7行目の3文字目だが、エラーの原因は6行目の行末。 C言語では、スペースや改行は人間に読み易くするための位置調整に過ぎないので printf("hello, ") printf("world¥n"); は printf("hello, ")printf("world¥n"); と同じ意味。
ここに「;」があるべきだが 「;」の前に「p」が現れたことが エラーの原因
ブロック開始終端の不整合
• 「{」が1つ多い
456
error2_1.c int main() { { printf("hello, world¥n"); return EXIT_SUCCESS; }
4 5 6 7 8 9 10
mintty + bash + GNU C $ gcc error2_1.c error2_1.c: 関数 ‘main’ 内: error2_1.c:10:1: エラー: expected declaration or statement at end of input } ^
「}」が1つ足らない状態で、ファイルの終端(入力の終端)に達している。
ブロック開始終端の不整合
• 「}」が1つ多い
457
エラーが生じたのは8行目の3文字目だが、エラーの原因は7行目。 「}」が1つ多いので、main関数の定義が7行目で終わっている。 つまり、8~9行目は、何もないところにいきなり以下のように書いたのと同じ。
return EXIT_SUCCESS; }
8 9
error2_2.c int main() { printf("hello, world¥n"); } return EXIT_SUCCESS; }
4 5 6 7 8 9
mintty + bash + GNU C $ gcc error2_2.c error2_2.c:8:3: エラー: expected identifier or ‘(’ before ‘return’ return EXIT_SUCCESS; ^ error2_2.c:9:1: エラー: expected identifier or ‘(’ before ‘}’ token } ^
ブロック開始終端の不整合
• 関数名のミススペル
458
コンパイルではなくリンクに失敗したと言われている。 分割コンパイルによる、外部関数のリンクの際、print が見つからなかった。 printf のつもりが print とミススペルしている。 標準ライブラリに print 関数は用意されていない。
error3.c int main() { print("hello, world¥n"); return EXIT_SUCCESS; }
4 5 6 7 8 9
mintty + bash + GNU C $ gcc error3.c /tmp/ccopiKHp.o:error3.c:(.text+0x15): `print' に対する定義されていない参照です /tmp/ccopiKHp.o:error3.c:(.text+0x15): 再配置がオーバーフローしないように切り詰められました: R_X86_64_PC32 (未定義シンボル `print' に対して) /usr/lib/gcc/x86_64-pc-cygwin/4.8.2/../../../../x86_64-pc-cygwin/bin/ld: /tmp/ccopiKHp.o: 誤った再配置アドレス 0x0 がセクション `.pdata' 内にあります /usr/lib/gcc/x86_64-pc-cygwin/4.8.2/../../../../x86_64-pc-cygwin/bin/ld: 最終リンクに失敗しました: 無効な操作です collect2: エラー: ld はステータス 1 で終了しました
コンパイルフェーズではなくリンクフェーズでのエラー。 ~.o ファイルについて参照がみつからないと言われている。
不要な ;
• 関数定義がプロトタイプ宣言になっている
459
main() の直後に ; があるのでそこで文が終わっている。 このため、関数の定義ではなく、関数のプロトタイプ宣言になっている。
error4.c int main(); { printf("hello, world¥n"); return EXIT_SUCCESS; }
4 5 6 7 8 9
mintty + bash + GNU C $ gcc error4.c error4.c:5:1: エラー: expected identifier or ‘(’ before ‘{’ token { ^
ファイルが見つからない
• ファイル名や配置場所を確認する
460
#include しようとしたファイルが見つからない。 #include に与えたファイル名か、ファイルに付けた名前のどちらかが間違っている。 上記の例では stdlib.h が正しいが lib ではなく ilb になっている。
error5.c #include <stdio.h> #include <stdilb.h> int main(); { printf("hello, world¥n"); return EXIT_SUCCESS; }
1 2 3 4 5 6 7 8 9
mintty + bash + GNU C $ gcc error5.c error5.c:2:20: 致命的エラー: stdilb.h: No such file or directory #include <stdilb.h> ^ コンパイルを停止しました。
コンパイルフェーズではなく、プリプロセスフェーズでのエラー
C言語のコンパイル
• コンパイラにより実行形式に変換する
461
.h ファイル .h ファイル
.h ファイル
Source files
.c ファイル .c ファイル
.c ファイル
.o ファイル .o ファイル
.o ファイル
Object files
ライブラリ ライブラリ
ライブラリ
実行ファイル
Executable files
C compiler
Preprocessor
linker
C compiler Cコンパイラは実際には preprocessor compiler linker の3段階に分かれている
エラー時の対処方法
• エラーメッセージをよく読みましょう。
• 行番号と何文字目かも参考になります。
• 同じようなエラーメッセージなら、同じような原因である可能性が高いはずです。
• 原因が分からない場合は、エラーメッセージを Google 等の検索エンジンで検索しましょう。
462
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
463
C言語入門 第11週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
464
いろいろなプログラム
465
列挙子
名前付きの定数
466
定数の種類
• リテラル
• 数値リテラル
• 文字列リテラル
• 列挙定数
467
マジックナンバーになると分かり辛いので、マクロか const 変数にして名前付の定数にする。
自動的に名前付定数の数値を割り振ってくれる
列挙(enumeration) 列挙子(enumerator)
• 書式: • enum 列挙名 {列挙定数名 [= 定整数値], ...}
• 値を設定しないと
• 0から開始
• 前の値から+1ずつ増加
• #define の代わりに使える
468
列挙(enumeration) 列挙子(enumerator)
• 例:
enum boolean {NO, YES};
enum escape { BELL = '¥a', BACKSPACE = '¥b', TAB = '¥t', NEWLINE = '¥n', VTAB = '¥v', RETURN = '¥r'};
enum month { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};
NO = 0 YES = 1
JAN = 1 FEB = 2 MAR = 3 ... DEC = 12
469
列挙(enumeration) 列挙子(enumerator)
• #define で書き変えた場合の例:
#define NO 0 #define YES 1
#define BELL '¥a' #define BACKSPACE '¥b' #define TAB '¥t' #define NEWLINE '¥n' #define VTAB '¥v' #define RETURN '¥r'
#define JAN 1 #define FEB 2 #define MAR 3 // ... #define DEC 12
470
パラメータ付きのマクロ
関数のようなマクロ
471
パラメータ付きのマクロ
• #define で定義するマクロに ( ) を付けるとパラメータを与えることが出来る。
472
macro_ex1.c #include <stdio.h> #define MIN(X,Y) (X<Y?X:Y) void main() { printf("min: %d¥n", MIN(1,2)); }
1 2 3 4 5 6 7 8
コンパイル前 プリプロセッサにより (1<2?1:2)で置換される
パラメータ付きのマクロの注意点
• マクロは単純な置換なので置換結果がどうなるか注意する必要がある。
473
macro_ex2_1.c #include <stdio.h> #define POW2(X) (X*X) void main() { printf("pow2: %d¥n", POW2(2)); }
1 2 3 4 5 6 7 8
コンパイル前 プリプロセッサにより (2*2)と置換される
パラメータ付きのマクロの注意点
• マクロは単純な置換なので置換結果がどうなるか注意する必要がある。
474
macro_ex2_1.c #include <stdio.h> #define POW2(X) (X*X) void main() { printf("pow2: %d¥n", POW2(2+1)); }
1 2 3 4 5 6 7 8
コンパイル前 プリプロセッサにより (2+1*2+1)と置換される
(2+1)*(2+1)=9 ではなく 2+(1*2)+1=5 になってしまう
パラメータ付きのマクロの注意点
• マクロは単純な置換なので置換結果がどうなるか注意する必要がある。
475
macro_ex2_2.c #include <stdio.h> #define POW2(X) ((X)*(X)) void main() { printf("pow2: %d¥n", POW2(2+1)); }
1 2 3 4 5 6 7 8
コンパイル前 プリプロセッサにより ((2+1)*(2+1))と置換される
パラメータはなるべく括弧で括る
パラメータ付きのマクロの注意点
• マクロは単純な置換なので置換結果がどうなるか注意する必要がある。
476
macro_ex2_3.c #include <stdio.h> #define POW2(X) ((X)*(X)) void main() { int x=2; printf("pow2: %d¥n", POW2(++x)); }
1 2 3 4 5 6 7 8 9
コンパイル前 プリプロセッサにより ((++x)*(++x))と置換され 結果として 3*4 になってしまう
マクロのパラメータに 副作用を伴う演算を用いては駄目
値の交換
swap
477
値の交換
• 以下のケースで x,y の値を交換するにはどうしたら良いか?
478
int x = 10; int y = 20; // 以下で x,y の値を入れ替えたい x = y; y = x;
こうするとxは既に 20で上書きされているので、 両方20になってしまう。
値の交換
• 中間変数を用いると良い
479
int x = 10; int y = 20; int tmp; // 以下で x,y の値を入れ替えたい tmp = x; x = y; y = tmp;
こうやると中間変数tmpに保存された xの値10をyに入れることで x,yの値を入れ替える事が出来る。
値の交換
• マクロにしてみる
480
macro_ex3.c #define SWAP(TYPE,X,Y) {TYPE SWAP_tmp=X; X=Y; Y=SWAP_tmp;} int x = 10; int y = 20; // 以下で x,y の値を入れ替えたい SWAP(int, x, y);
ブロック内のローカル変数として 中間変数を作成
// 以下で x,y の値を入れ替えたい {int SWAP_tmp=x; x=y; y=SWAP_tmp;};
プリプロセッサにより 以下のように置換される
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
481
C言語入門 第12週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
482
文字列の基本
483
𝑁進数
• 各桁は𝑁種類の数字(0~𝑁 − 1)で表す
• 下の桁から、 𝑁0の桁、 𝑁1の桁、 𝑁2の桁、 …
• 現在の桁数で表せない数値は桁上がりする
2進数 8進数 10進数 16進数
0 0 0 0
1 1 1 1
10 2 2 2
11 3 3 3
100 4 4 4
101 5 5 5
110 6 6 6
111 7 7 7
1000 10 8 8
1001 11 9 9
1010 12 10 a
1011 13 11 b
1100 14 12 c
1101 15 13 d
1110 16 14 e
1111 17 15 f
10000 20 16 10
484
2の桁へ 桁上がり
4の桁へ 桁上がり
8の桁へ 桁上がり
16の桁へ 桁上がり
8の桁へ 桁上がり
10の桁へ 桁上がり
16の桁へ 桁上がり
2進数3桁 ⇕
8進数1桁
2進数4桁 ⇕
16進数1桁
𝑁進数
2進数 4桁(場合の数 2 4) 16進数 1桁(場合の数16 1) 2進数 8桁(場合の数 2 8) 16進数 2桁(場合の数16 2) 2進数16桁(場合の数 216) 16進数 4桁(場合の数16 4) 2進数32桁(場合の数 232) 16進数 8桁(場合の数16 8) 2進数64桁(場合の数 264) 16進数16桁(場合の数1616)
2進数 8進数 10進数 16進数
11111000 370 248 f8
11111001 371 249 f9
11111010 372 250 fa
11111011 373 251 fb
11111100 374 252 fc
11111101 375 253 fd
11111110 376 254 fe
11111111 377 255 ff
100000000 400 256 100
100000001 401 257 101
100000010 402 258 102
100000011 403 259 103
100000100 404 260 104
100000101 405 261 105
100000110 406 262 106
100000111 407 263 107
100001000 400 264 108
485
256の桁へ 桁上がり
256の桁へ 桁上がり
2進数3桁 ⇕
8進数1桁
2進数4桁 ⇕
16進数1桁
𝑥バイト(8𝑥ビット)の数値は 2𝑥桁の16進数で表すことが出来る
8bit整数の10進表現
2進 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
16進 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0001 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0010 2 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
0011 3 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
0100 4 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
0101 5 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
0110 6 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
0111 7 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
1000 8 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
1001 9 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
1010 A 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
1011 B 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
1100 C 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
1101 D 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
1110 E 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
1111 F 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
上位4ビット
下位4ビット
486
2進数と10進数は桁数の対応で収まりが悪い
8bit整数の2進表現
2進 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
16進 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 0 0000 0000 0000 0001 0000 0010 0000 0011 0000 0100 0000 0101 0000 0110 0000 0111 0000 1000 0000 1001 0000 1010 0000 1011 0000 1100 0000 1101 0000 1110 0000 1111
0001 1 0001 0000 0001 0001 0001 0010 0001 0011 0001 0100 0001 0101 0001 0110 0001 0111 0001 1000 0001 1001 0001 1010 0001 1011 0001 1100 0001 1101 0001 1110 0001 1111
0010 2 0010 0000 0010 0001 0010 0010 0010 0011 0010 0100 0010 0101 0010 0110 0010 0111 0010 1000 0010 1001 0010 1010 0010 1011 0010 1100 0010 1101 0010 1110 0010 1111
0011 3 0011 0000 0011 0001 0011 0010 0011 0011 0011 0100 0011 0101 0011 0110 0011 0111 0011 1000 0011 1001 0011 1010 0011 1011 0011 1100 0011 1101 0011 1110 0011 1111
0100 4 0100 0000 0100 0001 0100 0010 0100 0011 0100 0100 0100 0101 0100 0110 0100 0111 0100 1000 0100 1001 0100 1010 0100 1011 0100 1100 0100 1101 0100 1110 0100 1111
0101 5 0101 0000 0101 0001 0101 0010 0101 0011 0101 0100 0101 0101 0101 0110 0101 0111 0101 1000 0101 1001 0101 1010 0101 1011 0101 1100 0101 1101 0101 1110 0101 1111
0110 6 0110 0000 0110 0001 0110 0010 0110 0011 0110 0100 0110 0101 0110 0110 0110 0111 0110 1000 0110 1001 0110 1010 0110 1011 0110 1100 0110 1101 0110 1110 0110 1111
0111 7 0111 0000 0111 0001 0111 0010 0111 0011 0111 0100 0111 0101 0111 0110 0111 0111 0111 1000 0111 1001 0111 1010 0111 1011 0111 1100 0111 1101 0111 1110 0111 1111
1000 8 1000 0000 1000 0001 1000 0010 1000 0011 1000 0100 1000 0101 1000 0110 1000 0111 1000 1000 1000 1001 1000 1010 1000 1011 1000 1100 1000 1101 1000 1110 1000 1111
1001 9 1001 0000 1001 0001 1001 0010 1001 0011 1001 0100 1001 0101 1001 0110 1001 0111 1001 1000 1001 1001 1001 1010 1001 1011 1001 1100 1001 1101 1001 1110 1001 1111
1010 A 1010 0000 1010 0001 1010 0010 1010 0011 1010 0100 1010 0101 1010 0110 1010 0111 1010 1000 1010 1001 1010 1010 1010 1011 1010 1100 1010 1101 1010 1110 1010 1111
1011 B 1011 0000 1011 0001 1011 0010 1011 0011 1011 0100 1011 0101 1011 0110 1011 0111 1011 1000 1011 1001 1011 1010 1011 1011 1011 1100 1011 1101 1011 1110 1011 1111
1100 C 1100 0000 1100 0001 1100 0010 1100 0011 1100 0100 1100 0101 1100 0110 1100 0111 1100 1000 1100 1001 1100 1010 1100 1011 1100 1100 1100 1101 1100 1110 1100 1111
1101 D 1101 0000 1101 0001 1101 0010 1101 0011 1101 0100 1101 0101 1101 0110 1101 0111 1101 1000 1101 1001 1101 1010 1101 1011 1101 1100 1101 1101 1101 1110 1101 1111
1110 E 1110 0000 1110 0001 1110 0010 1110 0011 1110 0100 1110 0101 1110 0110 1110 0111 1110 1000 1110 1001 1110 1010 1110 1011 1110 1100 1110 1101 1110 1110 1110 1111
1111 F 1111 0000 1111 0001 1111 0010 1111 0011 1111 0100 1111 0101 1111 0110 1111 0111 1111 1000 1111 1001 1111 1010 1111 1011 1111 1100 1111 1101 1111 1110 1111 1111
上位4ビット
下位4ビット
487
2進数は桁が多過ぎて直感的に分かり難い
8bit整数16進表現
2進 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
16進 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 0 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
0001 1 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
0010 2 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
0011 3 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
0100 4 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
0101 5 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f
0110 6 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f
0111 7 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f
1000 8 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f
1001 9 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f
1010 A a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af
1011 B b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf
1100 C c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf
1101 D d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df
1110 E e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef
1111 F f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
上位4ビット
下位4ビット
488
2進数と16進数は相性が良い。2進数4桁が16進数1桁に対応するので直感的に分かり易い
ASCII文字コード表
16進 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI 1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US 2 SP ! " # $ % & ' ( ) * + , - . / 3 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 4 @ A B C D E F G H I J K L M N O 5 P Q R S T U V W X Y Z [ ¥ ] ^ _ 6 ` a b c d e f g h i j k l m n o 7 p q r s t u v w x y z { | } ~ DEL 8 9 A B C D E F
上位4ビット
下位4ビット
赤字は制御コード
教科書 p.51.,第2週資料 p.67.
http://ja.wikipedia.org/wiki/ASCII
489
標準ヘッダ <ctype.h>
// 文字コードの調査(cが該当する文字なら真を返す) int islower(int c); //小文字 int isupper(int c); //大文字 int isdigit(int c); //10進数(数字) int isxdigit(int c);//16進数 int isalpha(int c); //英字 (isupper(c)||islower(c)) int isalnum(int c); //英数字(isalpha(c)||isdigit(c)) int iscntrl(int c); //制御文字(0x00~0x1f, 0x7f) int isspace(int c); //空白文字(' ','¥f','¥n','¥r','¥t','¥v') int isprint(int c); //印字可能文字(0x20~0x7e) int isgraph(int c); //印字可能文字(スペースを除く) isprint(c)&&!isspace(c) int ispunct(int c); //印字可能文字(スペース、英数字を除く)isgraph(c)&&!isalnum(c) // 文字コードの変換 int tolower(int c); //cを小文字に変換 int toupper(int c); //cを大文字に変換
490 [1] pp.303.
JM: isalpha (3) toupper (3) 講義資料の ctype_test.c, ctype_test.xlsx も参照
char型変数と文字コード
• char, unsigned char 型
• 半角文字の文字コードを1つ つまり1バイト(=8ビット)を格納出来るサイズ
• char : -128~127
• unsigned char : 0~255
491 教科書 pp.44, 96-99., 第2週資料 pp.54-63.
c
0110 1000
変数の実体はNバイト(8Nビット)のメモリ つまり内部的には2進数が入っている
16進数リテラルによる初期化 char c = 0x68; // =104='h'
文字コードに対応する文字を表示 printf("%c¥n", 104); // "%c"に文字コードを与えると対応する文字が表示される
char型変数と文字コード
• 以下の変数宣言と初期化は全く同じ結果
492 教科書 pp.44, 96-99., 第2週資料 pp.54-63.
c
0110 1000 どの方法で初期化しようと結局 char型(1バイト)の変数 c を 0b01101000 で初期化している
文字コードを代入したい場合 文字定数リテラルで書くと 数値リテラルで書くよりも 意味が分かり易くなる
結果は同じでも読んだ時 分かり易いのはどれだろう?
必要であれば 読んだ人が分かり易いよう コメントで情報を補うと良い
文字定数リテラルによる初期化 char c = 'h'; // 0x68
10進数リテラルによる初期化 char c = 104; // 'h'
16進数リテラルによる初期化 char c = 0x68; // 'h'
c
104
c
0x68
c
'h'
char型配列と文字列
• 以下の変数宣言と初期化は全く同じ結果
493
文字定数リテラルによる初期化 char s[] = {'h', 'e', 'l', 'l', 'o', '¥0'};
文字列定数リテラルによる初期化 char s[] = "hello";
教科書 pp.44, 96-99., 第2週資料 pp.54-63.
10進数リテラルによる初期化 char s[] = {104, 101, 108, 108, 111, 0};
16進数リテラルによる初期化 char s[] = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00};
s[0]
'h'
s[1]
'e'
s[2]
'l'
s[3]
'l'
s[4]
'o'
s[5]
'¥0' 文字列は配列に格納した 一連の文字コード 終端には 0 を格納する
どれで書いても良いが 普通は面倒だから 文字列の初期化は 文字列定数リテラルを 用いる
char型配列と文字列
• 文字列の表示
494
文字列定数リテラルによる初期化 char s[] = "hello";
教科書 pp.44, 96-99., 第2週資料 pp.54-63.
s[0]
'h'
s[1]
'e'
s[2]
'l'
s[3]
'l'
s[4]
'o'
s[5]
'¥0'
文字列の表示 printf("%s¥n", s); // "%s"に与えたアドレス以降'¥0'が現れるまで // メモリに格納された文字コードに対応する文字が表示される
関数の引数とよく使われる名前
• p, ptr, pointer : ポインタ • c, ch, chr, character : 文字 • s, str, string : 文字列 • s, src, source : 発信元 • d, dst, destination : 送信先 • t, txt, text : テキスト • l, len, length : 長さ • w, wid, width : 幅 • h, hei, height : 高さ • n, num, number : (個)数 • s, size : サイズ • b, buf, buff, buffer : バッファ • i, idx, index : 添え字 • s, stat, status : 状態
495
標準ヘッダ <string.h>
size_t strlen(const char *s); // 文字列の長さ char *strcpy(char *dst, const char *src); // 文字列のコピー char *strncpy(char *dst, const char *src, size_t n); // 文字列のコピー(最長制限付) char *strcat(char *dst, const char *src); // 文字列の連結 char *strncat(char *dst, const char *src, size_t n); // 文字列の連結(最長制限付) char *strcmp(const char *s1, const char *s2); // 文字列の比較 char *strncmp(const char *s1, const char *s2, size_t n); // 文字列の比較(最長制限付) char *strchr(const char *s, char c); // 文字の検索(先頭から) char *strrchr(const char *s, char c); // 文字の検索(末尾から) int strspn(const char *s, const char *t); // 先頭部分の文字列の長さ(∈t) int strcspn(const char *s, const char *t); // 先頭部分の文字列の長さ(∉t) char *strpbrk(const char *s, const char *t); // 文字の検索(∈t) char *strstr(const char *s, const char *t); // 文字列の検索 char *strtok(char *s, const char *t); // 文字列の分割 void *memset(void *s, int c, size_t n); // メモリへ値を設定 void *memcpy(void *dst, const void *src, size_t n); // メモリのコピー void *memmove(void *dst, const void *src, size_t n); // メモリの移動 int memcmp(const void *s1, const void *s2, size_t n); // メモリの比較 void *memchr(const void *s, int c, size_t n); // メモリの検索
496
strlen 関数
• size_t strlen(const char *s); • 文字列 s の長さを返す
• 引数: • s: 長さを調べる文字列
• 戻り値 • 文字列 s の長さを返す
497 教科書 p.312., [1] pp.313-315.
JM: strlen (3)
strcpy 関数
• char *strcpy(char *dst, const char *src); • '¥0'を含めてsrcをdstへコピーする
• 引数: • dst: コピー先のアドレス • src: コピー元の文字列
• 戻り値 • dst を返す • dst が十分な大きさでないとバッファオーバーフローを引き起こす
ので注意
498 教科書 p.308., [1] pp.313-315.
JM: strcpy (3)
strncpy 関数
• char *strncpy(char *dst, const char *src, size_t n); • srcから最大n文字dstへコピーする
• 引数: • dst: コピー先のアドレス • src: コピー元の文字列 • n: コピーする最大文字数
• 戻り値 • dst を返す • src が n 文字より短い場合は、残りの dst の末尾に '¥0' を
詰めて、合計 n バイト書き込む
499 教科書 p.308., [1] pp.313-315.
JM: strcpy (3)
strcat 関数
• char *strcat(char *dst, const char *src); • 文字列dstの末尾に文字列srcを連結する
• 引数: • dst: 連結先の文字列 • src: 連結元の文字列
• 戻り値 • dst を返す • dst が十分な大きさでないとバッファオーバーフローを引き起こす
ので注意
500 教科書 p.309., [1] pp.313-315.
JM: strcat (3)
strncat 関数
• char *strncat(char *dst, const char *src, size_t n); • 文字列dstの末尾に文字列srcを最大n文字連結する
• 引数: • dst: 連結先の文字列 • src: 連結元の文字列 • n: 連結する最大文字数
• 戻り値 • dst を返す • dst は strlen(dst)+min(strlen(stc),n)+1 の大きさがな
ければバッファオーバーフローが発生する
501 教科書 p.309., [1] pp.313-315.
JM: strcat (3)
strcmp 関数
• char *strcmp(const char *s1, const char *s2); • 文字列s1とs2を比較する
• 引数: • s1: 比較する文字列1 • s2: 比較する文字列2
• 戻り値 • s1がs2に比べて小さければマイナスの値を返す • s1とs2が等しければ0を返す • s1がs2に比べて大きければプラスの値を返す
502 教科書 p.309., [1] pp.313-315.
JM: strcmp (3)
strncmp 関数
• char *strncmp(const char *s1, const char *s2, size_t n); • 文字列s1とs2の先頭n文字を比較する
• 引数: • s1: 比較する文字列1 • s2: 比較する文字列2 • n: 比較する最大文字数
• 戻り値 • strcmpに同じ
503 教科書 p.310., [1] pp.313-315.
JM: strcmp (3)
strchr 関数
• char *strchr(const char *s, char c); • 文字列sのなかで最初に現れるcを見つける
• 引数: • s: 検索する文字列 • c: 検索する文字
• 戻り値 • 最初に見つかったcへのポインタを返す • 見つからなかった場合はNULLを返す
504 教科書 p.312., [1] pp.313-315.
JM: strchr (3)
strrchr 関数
• char *strrchr(const char *s, char c); • 文字列sのなかで最後に現れるcを見つける
• 引数: • s: 検索する文字列 • c: 検索する文字
• 戻り値 • 最後に見つかったcへのポインタを返す • 見つからなかった場合はNULLを返す
505 教科書 p.313., [1] pp.313-315.
JM: strchr (3)
strspn 関数
• int strspn(const char *s, const char *t); • 文字列sの先頭から、tに含まれる文字だけからなる文字列の長さ
を返す
• 引数: • s: 検索する文字列 • t: 検索する文字の集合
• 戻り値 • tに含まれる文字からなる接頭子(prefix)の長さを返す
506 [1] pp.313-315.
JM: strspn (3)
strcspn 関数
• int strcspn(const char *s, const char *t); • 文字列sの先頭から、tに含まれない文字だけからなる文字列の長
さを返す
• 引数: • s: 検索する文字列 • t: 検索する文字の補集合
• 戻り値 • tに含まれない文字からなる接頭子(prefix)の長さを返す
507 [1] pp.313-315.
JM: strspn (3)
strpbrk 関数
• char *strpbrk(const char *s, const char *t); • 文字列sのなかで、tに含まれる文字が最初に出てくる位置を見つ
ける
• 引数: • s: 検索する文字列 • t: 検索する文字の集合
• 戻り値 • tに含まれる文字が最初に見つかった位置へのポインタを返す • 見つからなかった場合はNULLを返す
508 [1] pp.313-315.
JM: strpbrk (3)
strstr 関数
• char *strstr(const char *s, const char *t); • 文字列sのなかで、文字列tが最初に現れる位置を見つける
• 引数: • s: 検索する文字列 • t: 検索する文字列
• 戻り値 • 文字列tが最初に見つかった位置へのポインタを返す • 見つからなかった場合はNULLを返す
509 教科書 p.314., [1] pp.313-315.
JM: strstr (3)
strtok 関数
• char *strtok(char *s, const char *t); • 文字列sのなかから、 tに含まれる文字で区切られるトークンを見
つける
• 引数: • s: 検索する文字列 • t: トークンを区切る文字の集合
• 戻り値 • トークンへのポインタを返す • 2つ目以降のトークンは s を NULL にして呼ぶことで順次切り出
される • トークンがなくなったらはNULLを返す • s は区切り文字の位置が終端文字'¥0'で破壊される
510 [1] pp.313-315.
JM: strtok (3)
memset 関数
• void *memset(void *s, int c, size_t n); • sの先頭からnバイトをcで埋めsを返す
• 引数: • s: データで埋める先頭アドレス • c: メモリを埋める1バイトのデータ • n: データcを埋めるバイト数
• 戻り値 • s を返す • 要 string.h
511 [1] pp.313-315.
JM: memset (3)
memcpy 関数
• void *memcpy(void *dst, const void *src, size_t n); • dstにsrcの内容をnバイトコピーする • dstとsrcは領域が重なっていてはならない。重なっている場合は
memmoveを用いる
• 引数: • dst: データのコピー先 • src: データのコピー元 • n: コピーするバイト数
• 戻り値 • dst を返す • 要 string.h
512 [1] pp.313-315.
JM: memcpy (3)
memmove 関数
• void *memmove(void *dst, const void *src, size_t n); • dstとsrcは領域が重なっていても良い点を除けば、memcpyと同じ
• 引数: • dst: データのコピー先 • src: データのコピー元 • n: コピーするバイト数
• 戻り値 • dst を返す • 要 string.h
513
JM: memmove (3)
[1] pp.313-315.
memcmp 関数
• int memcmp(const void *s1, const void *s2, size_t n); • s1とs2の最初のnバイトを比較する
• 引数: • s1: 比較するメモリ1 • s2: 比較するメモリ2 • n: 比較するバイト数
• 戻り値 • strcmpに同じ • 要 string.h
514
JM: memcmp (3)
[1] pp.313-315.
memchr 関数
• void *memchr(const void *s, int c, size_t n); • sの中でcが最初に現れる位置を見つける
• 引数: • s: 検索するメモリ • c: 検索する値 • n: 検索する範囲のバイト数
• 戻り値 • cが最初に見つかった位置へのポインタを返す • 見つからなかった場合はNULLを返す要 string.h
515
JM: memchr (3)
[1] pp.313-315.
標準ヘッダ <stdlib.h>
// 文字列を数値に変換
double atof(const char *s); //文字列をdouble型の数値に変換
int atoi(const char *s); //文字列をint型の数値に変換
long atol(const char *s); //文字列をlong型の数値に変換
double strtod(const char *s, char **endp);
long strtol(const char *s, char **endp, int base);
unsigned long strtoul(const char *s, char **endp, int base);
// 文字列を double, long, unsigned long 型の数値に変換する
// endp が NULL でない場合、
// *endp は変換に用いた最後の文字の次の文字へのポインタとなる
// base は変換元の文字列の基数
// base が 0 の場合、文字列先頭の 0 や 0x を解釈し
// 8 進数や 16 進数として扱う
516 [1] pp.316-317.
JM: atof (3) atoi (3)
strtod (3) strtol (3)
シーザー暗号
文字列の演習
517
シーザー暗号
• アルファベットをN文字シフトする暗号
• 例: IBM→1文字左へ→HAL
518
演習: caesar.c
• 与えられた文字列をシーザー暗号で暗号化する関数caesar(n,s)を作成せよ。 • 文字コードが0x20~0x7eの文字ついてのみ処理し、それ以外の文字について
は処理せずそのまま残すこと。 • 0x20~0x7eからはみ出す値は値域の反対側に繋がるよう循環するよう変換せ
よ。例えばn=1なら0x61('a')は0x62('b')に、0x7eは0x20に変換される変換される。n=-1の場合も同様で、0x20は0x7eに変換される。
• caesar_main.c と共にコンパイルして動作を確認せよ • 引数:
• int n : 暗号鍵(シフトする文字数) • char s[] : 暗号化する文字列(終端が'¥0')
• 戻り値 • なし(void) • 与えた配列 s を上書きしてシーザー暗号で
暗号化した文字列で上書きせよ
519
mintty + bash + GNU C $ gcc caesar_main.c caesar.c && ./a n = 1 s = hal ibm
ヒント
• プロトタイプ宣言の末尾の ; を {} に変えれば関数の定義になる。
• 文字の末尾は ¥0 で終端されている。 • i を増やしながら s[i] != '¥0' の間ループを
続ければ良い。
• s 全体を暗号文で上書きするには各 s[i] を暗号化して s[i] に戻してやれば良い。
520
caerar_main.c void caesar(int n, char s[]); 4
ヒント
• 0x20未満、0x7e超になった場合の処理は幾つかやり方がある
• if 文等で場合分けする方法
• 剰余算を使う方法
• 等々
521
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
522
C言語入門 第13週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
523
メモリ上におけるデータの並び
マルチバイトデータの各バイトはどう並ぶか?
524 備考
変数の割り当てとバイトオーダー
• 32bit int型の場合
0x?? 0x~00
0x?? 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
32bit
int a; // 変数の宣言
a
0x????????
525 備考
変数の割り当てとバイトオーダー
• 32bit int型の場合(Little Endian)
0x78 0x~00
0x56 0x~01
0x34 0x~02
0x12 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
32bit
int a = 0x12345678; // 変数の宣言と初期化
a
0x12345678
Intel の x86 系 CPU 等
526 備考
変数の割り当てとバイトオーダー
• 32bit int型の場合(Big Endian)
0x12 0x~00
0x34 0x~01
0x56 0x~02
0x78 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
32bit
int a = 0x12345678; // 変数の宣言と初期化
a
0x12345678
ネットワーク上を流れるデータ等
527 備考
変数の割り当てとバイトオーダー
• 16bit short型の場合(Little Endian)
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; // 変数の宣言と初期化
a
0x????1234
a
0x1234
16bit
同じ場所から32bitで取ると
Intel の x86 系 CPU 等
528 備考
変数の割り当てとバイトオーダー
• 16bit short型の場合(Big Endian)
0x12 0x~00
0x34 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; // 変数の宣言と初期化
a
0x1234????
a
0x1234
16bit
同じ場所から32bitで取ると
ネットワーク上を流れるデータ等
529 備考
ポインタ
メモリのアドレスを用いて格納値を操作する
530
ポインタ変数
• メモリ上のアドレスを指し示すための変数
• char * 型: char 型の変数へのポインタ
• int * 型: int 型の変数へのポインタ
• 要はアドレスを格納するための変数
• ポインタ型のサイズはアドレス空間のビット数
• sizeof(char *)もsizeof(int *)も同じ
• 32ビットアドレス空間なら4バイト
• 64ビットアドレス空間なら8バイト
531 教科書pp.207-272.
pointer: 指し示す者
メモリの構成
• 1byte単位でアドレスが振られている
• つまり各アドレスには1byteの値を格納出来る
0x00 0x00000000
0x00 0x00000001
0x00 0x00000002
0x00 0x00000003
0x00 0xffffffff
: :
: :
0x00 0x0000000000000000
0x00 0x0000000000000001
0x00 0x0000000000000002
0x00 0x0000000000000003
0x00 0xffffffffffffffff
: :
: :
32bitのOSは32bitのアドレス空間 最大232Bytes=4GiB
64bitのOSは64bitのアドレス空間 最大264Bytes=16EiB
アドレス 格納値 アドレス 格納値
教科書 pp.52-56. 第2週資料 p.49.
ポインタ変数には これらの値が入る
532
ポインタ変数
• 例: 16bit short型little endianの場合
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; short *p = &a;
a
0x1234
a
p に &a を入れておくと *p は a への 参照として機能する C言語ではこれを ポインタと言う
533
p
0x~00
&a
0x~00
&aは 変数aの メモリ上の 先頭アドレス
実際に存在している変数はpで中にはアドレスが格納されている
*pはアドレスpに 置かれた変数(ここではa) への参照
要はリンク みたいなもの
*p
0x1234
0x~00
宣言時に*を付けると ポインタ変数になる
教科書pp.207-272.
pは変数*pの アドレスを指し示す
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
534
pointer_ex1_1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d¥n", *p); printf(" a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書pp.207-272.
変数の宣言で 変数名の前にポインタ宣言子「*」を付けると ポインタ変数になる。
mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2 重要
ポインタ変数 p に 変数 a のアドレス &a を代入すると 変数 a の代わりに *p が使える。
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
535
pointer_ex1_1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d¥n", *p); printf(" a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書pp.207-272.
変数の宣言で 変数名の前にポインタ宣言子「*」を付けると ポインタ変数になる。 この場合、 int 型の変数 *p を宣言した結果、 実際には int 型変数へのポインタである int * 型変数 p が定義された と理解しておくとスッキリする。
mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2 重要
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
536
pointer_ex1_1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d¥n", *p); printf(" a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書pp.207-272.
mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2
0x~
&a
a
1
p
0x~
アドレス演算子「&」を用いて int 型変数 a のアドレス &a を int 型の変数へのポインタ p に代入する
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
537
pointer_ex1_1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d¥n", *p); printf(" a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書pp.207-272.
mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2
int 型の変数へのポインタ p の前に 間接演算子「*」を付けて *p とすると ポインタ変数 p が指し示すアドレスを int 型の変数としてアクセス出来る。 今 p には変数 a のアドレス &a が 入っているので、 *p は変数 a を意味するようになる
0x~ *p⇒a
1
p
0x~ &a
2
実際 *p に代入した値 2 が a に反映されていることが確認出来る。
p
0x~
ポインタ変数
• 変数が配置されたアドレスを扱うための変数
538
pointer_ex1_1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d¥n", *p); printf(" a=%d¥n", a); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書pp.207-272.
mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2
ポインタ変数pには 変数aのアドレス&aが入っている この状態で*pは アドレス&aにある変数 つまり変数aを意味する。
0x~ *p⇒a
1
&a⇒
2
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
• 同じ「*」だが宣言時とそれ以外で意味が異なる
539
宣言時 それ以外
*p pをポインタとして宣言(※1) *pの格納値(※2)
*p=x pにxを代入(※3) *pにxを代入(※4)
要注意
pointer_ex1_1.c
教科書pp.207-272., [1] pp.250,268-269.
pointer_ex1_2.c
int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
• 同じ「*」だが宣言時とそれ以外で意味が異なる
540
宣言時 それ以外
*p pをポインタとして宣言(※1) *pの格納値(※2)
*p=x pにxを代入(※3) *pにxを代入(※4)
要注意
pointer_ex1_1.c
教科書pp.207-272., [1] pp.250,268-269.
pointer_ex1_2.c
int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
int型の変数 *p を宣言した 実際に作成されたのは int型のポインタ変数 p
pが指すアドレスの格納値
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
• 同じ「*」だが宣言時とそれ以外で意味が異なる
541
宣言時 それ以外
*p pをポインタとして宣言(※1) *pの格納値(※2)
*p=x pにxを代入(※3) *pにxを代入(※4)
要注意
pointer_ex1_1.c
教科書pp.207-272., [1] pp.250,268-269.
pointer_ex1_2.c
int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
特に※3に注意 *p に &a を代入している のではなく p に &a を代入している
この2つの代入は 同じ処理を意味する
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
• 同じ「*」だが宣言時とそれ以外で意味が異なる
542
宣言時 それ以外
*p pをポインタとして宣言(※1) *pの格納値(※2)
*p=x pにxを代入(※3) *pにxを代入(※4)
要注意
pointer_ex1_1.c
教科書pp.207-272., [1] pp.250,268-269.
pointer_ex1_2.c int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d¥n", *p);// ※2
int型の変数 *p を宣言して int型のポインタ変数 p に p = &a と代入 つまりpが指すアドレスを&aに
pが指すアドレスの格納値 つまり変数aの格納値を2に
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
• 同じ「*」だが宣言時とそれ以外で意味が異なる
543
ポインタ変数pの宣言 (言い換えると、ある型の変数*pを宣言)し 宣言した変数pに対して アドレスxを代入している
ポインタ変数pが指すアドレスの中身 (言い換えると、ある型の変数*p)を 操作(参照or代入)する
要注意
*は間接演算子 indirection operator または間接参照演算子 dereference operator
教科書pp.207-272., [1] pp.250,268-269.
*はポインタ宣言子 pointer declarator
宣言時 それ以外
*p pをポインタとして宣言(※1) *pの格納値(※2)
*p=x pにxを代入(※3) *pにxを代入(※4)
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
544
pointer_ex2.cpp
6 7 8 9 10 11 12 13 14 15 16 17
教科書pp.207-272.
要注意
mintty + bash + GNU C $ gcc pointer_ex2.c && ./a &a =0x000000000022cb0c p1=0x000000000022cb0c q2=0x000000000022cb0c a =1 *p1=1 *p2=1
int a = 1; int *p1 = &a; int *p2; p2 = &a; printf("&a =%#0*p¥n", sizeof(&a )*2+2, &a ); printf(" p1=%#0*p¥n", sizeof( p1)*2+2, p1); printf(" p2=%#0*p¥n", sizeof( p2)*2+2, p2); printf(" a =%d¥n", a ); printf("*p1=%d¥n", *p1); printf("*p2=%d¥n", *p2);
実際に *p1 と p1 のどちらが &a になっているか 確認してみましょう。
ポインタ変数の宣言と代入
• 「*」と「=」の使い方は要注意
545
pointer_ex2.cpp
6 7 8 9 10 11 12 13 14 15 16 17
教科書pp.207-272.
要注意
mintty + bash + GNU C $ gcc pointer_ex2.c && ./a &a =0x000000000022cb0c p1=0x000000000022cb0c p2=0x000000000022cb0c a =1 *p1=1 *p2=1
int a = 1; int *p1 = &a; int *p2; p2 = &a; printf("&a =%#0*p¥n", sizeof(&a )*2+2, &a ); printf(" p1=%#0*p¥n", sizeof( p1)*2+2, p1); printf(" p2=%#0*p¥n", sizeof( p2)*2+2, p2); printf(" a =%d¥n", a ); printf("*p1=%d¥n", *p1); printf("*p2=%d¥n", *p2);
宣言時は「*」が間接演算子ではなく ポインタ宣言子として働いている 宣言時に初期化すると *p1 ではなく p1 に &a を代入している
p2 に &a を代入している
宣言時以外は「*」が関節演算子として働く ポインタの前に*が付くと、 指し示すアドレスに格納されている値が 操作の対象となる
*p1 ではなく p1 に &a が 代入される事が確認出来る
備考: ポインタの表示
• %p
• void *;ポインタとして印字(処理系依存)
546
pointer_ex2.cpp printf("&a =%#0*p¥n", sizeof(&a )*2+2, &a );
第3週資料pp.24-33.
mintty + bash + GNU C $ gcc pointer_ex2.c && ./a &a =0x000000000022cb0c p1=0x000000000022cb0c p2=0x000000000022cb0c a =1 *p1=1 *p2=1
#: 16進表示が0でない場合、先頭を0xにする 0: フィールド幅いっぱいに左側から0を詰める *: 最小フィールド幅を引数で指定 p: void *;ポインタとして印字
%*pによる最小フィールド幅指定 1バイト=16進数2桁 先頭の0xで更に2桁
11
ポインタ変数の宣言と代入
• 宣言時の「*」が係る場所にも注意
547
pointer_ex3_1.cpp int *p, q; printf("sizeof(p)=%d¥n", sizeof(p)); printf("sizeof(q)=%d¥n", sizeof(q));
6 7 8 9
教科書pp.207-272.
要注意
Cygwin64 + bash + GNU C
$ gcc pointer_ex3_1.c && ./a sizeof(p)=8 sizeof(q)=4
p は「int *」型の宣言(int型へのポインタ変数) q は「int 」型の宣言(int型の変数) 複数の変数を同時に宣言する場合 変数名の直前に ポインタ宣言子「*」を付けた変数だけが ポインタ変数になる 要注意
p,q 共にポインタとして 宣言するには int *p, *q; または int *p; int *q; とする必要がある。
サイズが違うので p,q が別の型だと分かる
ポインタ変数の宣言と代入
• 宣言時の「*」が係る場所にも注意
548
pointer_ex3_1.cpp int *p, q; printf("sizeof(p)=%d¥n", sizeof(p)); printf("sizeof(q)=%d¥n", sizeof(q));
6 7 8 9
教科書pp.207-272.
要注意
Cygwin64 + bash + GNU C $ gcc pointer_ex3_1.c && ./a sizeof(p)=8 sizeof(q)=4
p は「int *」型の宣言(int型へのポインタ変数) q は「int 」型の宣言(int型の変数) 複数の変数を同時に宣言する場合 変数名の直前に ポインタ宣言子「*」を付けた変数だけが ポインタ変数になる 要注意
p,q 共にポインタとして 宣言するには int *p, *q; または int *p; int *q; とする必要がある。
int 型の変数 *p と q を宣言している
int 型の変数 *p と *q を宣言している
ポインタ変数
549
pointer_ex4.c int i, *p, a[] = {0,1,2,3,4,5,6,7,8,9}; printf("&a[0]=%#0*p¥n", sizeof(&a[0])*2+2, &a[0]); printf("p = ? "); scanf("%p", &p); printf("*p = ? "); scanf("%i", p); for (i = 0; i < sizeof(a) / sizeof(*a); i++) { printf("a[%d]=%#0*x¥n", i, sizeof(*a)*2+2, a[i]); }
6 7 8 9 10 11 12 13 14 15 16 17 18
教科書pp.207-272.
Cygwin64+mintty+bash+GNU C $ gcc pointer_ex4.c && ./a &a[0]=0x000000000022aa90 p = ? 0x000000000022aa98 *p = ? 0x12345678 a[0]=0000000000 a[1]=0x00000001 a[2]=0x12345678 a[3]=0x00000003 a[4]=0x00000004 a[5]=0x00000005 a[6]=0x00000006 a[7]=0x00000007 a[8]=0x00000008 a[9]=0x00000009
結果がどうなるかは別として アドレス演算子を使って 変数のアドレスを入れなくても 適当な値を入れても動く。
ポインタ変数
550
pointer_ex4.c int i, *p, a[] = {0,1,2,3,4,5,6,7,8,9}; printf("&a[0]=%#0*p¥n", sizeof(&a[0])*2+2, &a[0]); printf("p = ? "); scanf("%p", &p); printf("*p = ? "); scanf("%i", p); for (i = 0; i < sizeof(a) / sizeof(*a); i++) { printf("a[%d]=%#0*x¥n", i, sizeof(*a)*2+2, a[i]); }
6 7 8 9 10 11 12 13 14 15 16 17 18
教科書pp.207-272.
Cygwin64+mintty+bash+GNU C $ gcc pointer_ex4.c && ./a &a[0]=0x000000000022aa90 p = ? 0x000000000022aa92 *p = ? 0x12345678 a[0]=0x56780000 a[1]=0x00001234 a[2]=0x00000002 a[3]=0x00000003 a[4]=0x00000004 a[5]=0x00000005 a[6]=0x00000006 a[7]=0x00000007 a[8]=0x00000008 a[9]=0x00000009
結果が正しいかどうかは別として 配列の途中のアドレスを ポインタに入れることも出来る
関数の引数とポインタ
値渡しと参照渡し(ポインタ渡し)の用途等
551
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
552
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; }
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
関数の引数は呼び出し元とは別の変数になっていた
第8週資料pp.21-23.
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
553
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100
関数の引数(値渡し、参照渡し)
• 参照渡し: 呼出し元の値の格納場所を渡す
554
call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
scanf で見たことがある書き方! &: アドレス演算子
教科書p.171.
引数で受け取った変数を変更すると 呼び出し元にも反映される
これは正確には ポインタ渡しと言う
変数loのアドレスを 渡している
mintty + bash + GNU C $ gcc call_by_pointer.c && ./a lo=200
関数の引数(値渡し、参照渡し)
• C++における参照渡し
555
call_by_reference.cpp void sub(int &lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; } mintty + bash + GNU C++
$ g++ call_by_reference.cpp && ./a lo=200
4 5 6 7 8 9 10 11 12 13 14 15 16
参考
引数で受け取った変数を変更すると 呼び出し元にも反映される
C++では 本物の参照渡しが 可能になった
C++の参照渡しでは アドレス演算子「&」が不要
値が変化することが 分かり難いという デメリットもある
call by pointer (ポインタ渡し)
• 呼び出し元の変数の内容を変更したい場合
556
swapi_ex1.c void swapi(int *a, int *b) { int c; c = *a; *a = *b; *b = c; }
教科書p.171.
void swapi(int *a, int *b); 関数 *a と *b の値を入れ替える
swapi_test.c int a, b; fprintf(stderr, "a = ? "); scanf("%d", &a); fprintf(stderr, "b = ? "); scanf("%d", &b); swapi(&a, &b); printf("a = %d¥n", a); printf("b = %d¥n", b);
call by pointer (ポインタ渡し)
• 2つ以上の値を返したい場合
• 戻り値は1つしかないので、関数の引数にポインタを渡して、値を返す
557
modf_ex1.c double modf(double x, double *iptr) { *iptr = x < 0 ? ceil(x): floor(x); return x - *iptr; }
教科書p.171.
double modf(double x, double *iptr); 関数 浮動小数点数を整数部と小数部に分ける 戻り値: x の小数部を戻り値に x の整数部を *iptr に返す 戻り値は共に x と同じ符号を持つ JM: modf (3)
modf_test.c double x; double i; double f; fprintf(stderr, "x = ?¥b"); scanf("%lf", &x); f = modf(x, &i); printf("i : %f¥n", i); printf("f : %f¥n", f);
call by pointer (ポインタ渡し)
• 任意の長さの配列を渡したい場合
• 例えば文字列等
558
strlen_ex1.c size_t strlen(const char *s) { size_t len = 0; while(s[len] != '¥0') { len++; } return len; }
教科書p.171.
size_t strlen(const char *s); 関数 '¥0'で終端された文字列の長さを返す
strlen_test.c char s[1024] = ""; fprintf(stderr, "s = ? "); scanf("%1023[^¥n]", &s); printf("strlen(s) = %d¥n", strlen(s));
値が書き変えられては困る場合 const char への * (ポインタ) にしておくと、この関数を使っても 与えた内容が変更されないことを ある程度保証することが出来る
const 修飾子
• const 型: 変数の値を変更出来なくなる
559
const_ex1.c const int i = 0; // 初期化は出来る int const j = 0; // const int も int const も同じ意味 i = 1; // const を付けた変数には代入出来ない j = 1; // const を付けた変数には代入出来ない
6 7 8 9
mintty + bash + GNU C $ gcc const_ex1.c const_ex1.c: 関数 ‘main’ 内: const_ex1.c:8:5: エラー: 読み取り専用変数 ‘i’ への代入です i = 1; // Error: i is const ^ const_ex1.c:9:5: エラー: 読み取り専用変数 ‘j’ への代入です j = 1; // Error: j is const ^
const 修飾子は型修飾子(type-qualifier)の一種 型修飾子は型名の前後どちらにつけても良い
変更しようとするとコンパイル時に エラーになるので 本来書き変えてはいけない値を 書き変えてしまうことで生じるバグを 未然に防げる
教科書p.308,310,314., [1] pp.240,257,261-262.
const 修飾子
• const char 型へのポインタ
560
const_ex2.c char s[] = "hello, world"; const char *p; // ポインタの場合 *p が const になる p = s; // ポインタへは代入出来る *p = 'H'; // ポインタ指し示す先の変数には代入出来ない p[7] = 'W'; // ポインタ指し示す先の変数には代入出来ない *(char *) p = 'H'; // const のない型へ cast してやると代入出来る
6 7 8 9 10 11
mintty + bash + GNU C $ gcc const_ex2.c const_ex2.c: 関数 ‘main’ 内: const_ex2.c:9:6: エラー: 読み取り専用位置 ‘*p’ への代入です *p = 'H'; // Error: *p is const ^ const_ex2.c:10:8: エラー: 読み取り専用位置 ‘*(p + 7u)’ への代入です p[7] = 'W'; // Error: p[x] is const ^
ただし、わざわざ const を付けているということは 変更してはいけない、 または変更しないことを前提としているのであるから 普通は、特別に理由がない限り無理矢理書き変えてはいけない
教科書p.308,310,314., [1] pp.240,257,261-262.
char const * 型 const char * 型
const 修飾子
• char 型への const ポインタ
561
const_ex3.c char s[] = "hello, world"; char *const p = s; // p が const で *p が char になる p = s; // p が const なのでポインタへは代入出来ない *p = 'H'; // ポインタ指し示す先の変数には代入出来る p[7] = 'W'; // ポインタ指し示す先の変数には代入出来る
6 7 8 9 10
mintty + bash + GNU C $ gcc const_ex3.c const_ex3.c: 関数 ‘main’ 内: const_ex3.c:8:5: エラー: 読み取り専用変数 ‘p’ への代入です p = s; // Error: p is const ^
const char *p と char const *p は同じだが char * const p は意味が異なる 前者は *p が const char つまり p は変数で *p が定数 後者は *const p が char つまり p は定数で *p は変数 である
* がどこに係っているか よく考えるましょう
教科書p.308,310,314., [1] pp.240,257,261-262.
char * const 型
配列とポインタ
1次元配列とポインタの相違点
562
ポインタ変数とアドレス演算
• 例: 16bit short型little endianの場合
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; short *pa = &a;
*pa
±1するとsizeof(*pa)単位で アドレスが増減する つまり short 型配列の 0要素目、1要素目、... という意味になる
563
pa
pa+1
pa+2
pa+3
*(pa+1)
*(pa+2)
*(pa+3)
要注意
教科書pp.207-272.
ポインタ変数とアドレス演算
• 例: 32bit int型little endianの場合
0x78 0x~00
0x56 0x~01
0x34 0x~02
0x12 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
int a = 0x12345678; int *pa = &a;
*pa
±1するとsizeof(*pa)単位で アドレスが増減する つまり int 型配列の 0要素目、1要素目、... という意味になる
564
pa
pa+1
*(pa+1)
要注意
教科書pp.207-272.
ポインタ変数の配列的利用法
• 例: 16bit short型little endianの場合
0x34 0x~00
0x12 0x~01
0x?? 0x~02
0x?? 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
short a = 0x1234; short *pa = &a;
pa[0]
配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる
565
&pa[0]
&pa[1]
&pa[2]
&pa[3]
pa[1]
pa[2]
pa[3]
教科書pp.207-272.
ポインタ変数の配列的利用法
• 例: 32bit int型little endianの場合
0x78 0x~00
0x56 0x~01
0x34 0x~02
0x12 0x~03
: :
: :
0x?? 0x~04
0x?? 0x~05
0x?? 0x~06
: :
: :
0x?? 0x~07
0x?? 0x~08
int a = 0x12345678; int *pa = &a;
pa[0]
配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる
566
&pa[0]
&pa[1]
pa[1]
教科書pp.207-272.
1次元配列とポインタ
• 64bit OSで 8bit char 型の例 (little endian)
0x10 0x23cb08
⋮ ⋮ char a[] = { 0,1,2,3, 4,5,6,7, }; char*p = a;
p
567
&p
&a =a =p
a[0]
教科書pp.207-272.
0xcb 0x23cb09
0x23 0x23cb0a
0x00 0x23cb0b
0x00 0x23cb0c
0x00 0x23cb0d
0x00 0x23cb0e
0x00 0x23cb0f
0x00 0x23cb10
0x01 0x23cb11
0x02 0x23cb12
0x03 0x23cb13
0x04 0x23cb14
0x05 0x23cb15
0x06 0x23cb16
0x07 0x23cb17
⋮ ⋮
a
a[1]
a[2]
a[3]
a[4]
a[5]
a[6]
a[7]
sizeof(a) は aの1要素当たりのバイト数×要素数 つまりsizeof(char)*N
sizeof(p) は OSのアドレス空間のビット数/8 つまりsizeof(char *)
&a[4] =&p[4] =p+4
1次元配列とポインタ
• 64bit OSで 16bit short 型の例 (little endian)
0x10 0x23cb08
⋮ ⋮ short a[] = { 0x0001,0x0203, 0x0405,0x0607, }; short *p = a;
p
568
&p
&a =a =p a[0]
教科書pp.207-272.
0xcb 0x23cb09
0x23 0x23cb0a
0x00 0x23cb0b
0x00 0x23cb0c
0x00 0x23cb0d
0x00 0x23cb0e
0x00 0x23cb0f
0x01 0x23cb10
0x00 0x23cb11
0x03 0x23cb12
0x02 0x23cb13
0x05 0x23cb14
0x04 0x23cb15
0x07 0x23cb16
0x06 0x23cb17
⋮ ⋮
a[1]
a[2]
a[3]
a &a[2] =&p[2] =p+2
1次元配列とポインタ
• 64bit OSで 32bit int 型の例 (little endian)
0x10 0x23cb08
⋮ ⋮ int a[] = { 0x00010203, 0x04050607, }; int *p = a;
p
569
&p
&a =a =p
a[0]
教科書pp.207-272.
0xcb 0x23cb09
0x23 0x23cb0a
0x00 0x23cb0b
0x00 0x23cb0c
0x00 0x23cb0d
0x00 0x23cb0e
0x00 0x23cb0f
0x03 0x23cb10
0x02 0x23cb11
0x01 0x23cb12
0x00 0x23cb13
0x07 0x23cb14
0x06 0x23cb15
0x05 0x23cb16
0x04 0x23cb17
⋮ ⋮
a[1]
a &a[1] =&p[1] =p+1
配列 int p[N];
ポインタ int *p;
sizeof(p) ○ ○ 変数pの割り当てバイト数
&p △ ○ 変数pのアドレス(配列の場合&pとpは同じ)
p ○ ○ アドレスp(p[0]のアドレス)
*p ○ ○ アドレスpの格納値(p[0]の格納値)
p[x] ○ ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値
*(p+x) ○ ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値
&p[x] ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
p+x ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
p+=x × ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
1次元配列とポインタ
• 機能としてはほぼ同じ
• ポインタはアドレスを変更可能(少し柔軟)
570
配列は1要素のバイト数×要素数 ポインタはアドレス空間のビット数/8
教科書pp.207-272.
1次元配列とポインタ
571
pointer_ex5.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272.
1次元配列とポインタ
572
pointer_ex5.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272.
配列 int p[N];
ポインタ int *p;
sizeof(p) ○ ○ 変数pの割り当てバイト数
a は int 型 10 個分のサイズ つまり sizeof(int[10])
p は int * 型 1 個分のサイズ つまり sizeof(int *)
1次元配列とポインタ
573
pointer_ex5.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272.
配列 int p[N];
ポインタ int *p;
&p △ ○ 変数pのアドレス(配列の場合&pとpは同じ)
p ○ ○ アドレスp(p[0]のアドレス)
*p ○ ○ アドレスpの格納値(p[0]の格納値)
配列変数aはメモリ上のどこかに確保されている a は配列全体を意味する(実際は先頭アドレス) &a は配列変数aのアドレス(aと同一) ポインタ変数pはメモリ上のどこかに確保されている &pはそのアドレス p はポインタ変数 p に格納されているアドレス この例では配列変数aの先頭アドレス&aまたはa *a, *p は各アドレス a, p に格納されたデータ
0x22aaa0 a[0]
0
0x22aaa4 a[1]
1
⋮ ⋮
0x22aa98 p
0x22aaa0
1次元配列とポインタ
574
pointer_ex5.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272.
1次元配列とポインタ
575
pointer_ex5.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272. 配列 int p[N];
ポインタ int *p;
p[x] ○ ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値
*(p+x) ○ ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値
a の x 番目の要素にアクセスしている
1次元配列とポインタ
576
pointer_ex5.c int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p¥n", sizeof(a)); printf("sizeof(p) = %p¥n", sizeof(p)); printf("&a = %p¥n", &a); printf("&p = %p¥n", &p); printf("a = %p¥n", a); printf("p = %p¥n", p); printf("*a = %d¥n", *a); printf("*p = %d¥n", *p); printf("a[x] = %d¥n", a[x]); printf("p[x] = %d¥n", p[x]); printf("*(a+x) = %d¥n", *(a+x)); printf("*(p+x) = %d¥n", *(p+x)); printf("&a[x] = %p¥n", &a[x]); printf("&p[x] = %p¥n", &p[x]); printf("a+x = %p¥n", a+x); printf("p+x = %p¥n", p+x); //printf("a+=x = %p¥n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p¥n", p+=x);
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
cygwin64 + mintty + bash + GNU C $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
教科書pp.207-272. 配列 int p[N];
ポインタ int *p;
&p[x] ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
p+x ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
p+=x × ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス
pに1を足しているのに 結果が4増えていることが 確認出来る
関数定義の引数と配列
配列を引数に取る関数
577
配列のサイズ
• sizeof で得られるバイト数
0x?? a = &a[ -1]
: :
: :
0x?? a+N = &a[N ]
0x?? a+N+1 = &a[N+1]
sizeof(a) = sizeof(int) * N
int a[N];
578
sizeof(int)
0x?? a = &a[ 0]
0x?? a + 1 = &a[ 1]
0x?? a + 2 = &a[ 2]
0x?? :
0x?? :
0x?? a+N-1 = &a[N-1]
sizeof(a[0]) = sizeof(int)
講義資料 第3週p.42-51.
sizeof_ex3.c int a[10]; printf("sizeof(int) : %2d¥n", sizeof(int)); //int型の割り当てバイト数 printf("sizeof(a) : %2d¥n", sizeof(a)); //配列変数aの割り当てバイト数 printf("sizeof(a[0]) : %2d¥n", sizeof(a[0])); //変数a[0]の割り当てバイト数 printf("sizeof(a)/sizeof(a[0]) : %2d¥n", sizeof(a)/sizeof(a[0])); //配列変数aの要素数
mintty + bash + GNU C $ gcc sizeof_ex3.c && ./a sizeof(int) : 4 sizeof(a) : 40 sizeof(a[0]) : 4 sizeof(a)/sizeof(a[0]) : 10
オレンジ色は 未割当のメモリ
関数定義の引数と配列
• 引数名直後の1次元はポインタ扱いになる
579 [1] pp.121-122.
pointer_ex6.c int main() { // 0 1 2 3 4 5 6 7 8 9 int a[10] = { 2, 3, 5, 7,11,13,17,19,23,29}; int b[2][5] = { 2, 3, 5, 7,11,13,17,19,23,29}; printf("sizeof(a) = %d¥n", sizeof(a)); func_with_array1d(a); func_with_pointer(a); printf("sizeof(b) = %d¥n", sizeof(b)); func_with_array2d(b); func_with_pointer(b); //a += 1; // It's impossible. Because a is an array. //b += 1; // It's impossible. Because b is an array. return EXIT_SUCCESS; }
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
mintty + bash + GNU C $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) ^ sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 sizeof(p) = 8
pointer_ex6.c void func_with_pointer(int *p) { printf("sizeof(p) = %d¥n", sizeof(p)); }
20 21 22 23
関数定義の引数と配列
• 引数名直後の1次元はポインタ扱いになる
580 [1] pp.121-122.
pointer_ex6.c int main() { // 0 1 2 3 4 5 6 7 8 9 int a[10] = { 2, 3, 5, 7,11,13,17,19,23,29}; int b[2][5] = { 2, 3, 5, 7,11,13,17,19,23,29}; printf("sizeof(a) = %d¥n", sizeof(a)); func_with_array1d(a); func_with_pointer(a); printf("sizeof(b) = %d¥n", sizeof(b)); func_with_array2d(b); func_with_pointer(b); //a += 1; // It's impossible. Because a is an array. //b += 1; // It's impossible. Because b is an array. return EXIT_SUCCESS; }
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
mintty + bash + GNU C $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) ^ sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 sizeof(p) = 8
pointer_ex6.c void func_with_pointer(int *p) { printf("sizeof(p) = %d¥n", sizeof(p)); }
20 21 22 23
関数定義の引数と配列
• 引数名直後の1次元はポインタ扱いになる
581 [1] pp.121-122.
mintty + bash + GNU C $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) ^ sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 sizeof(p) = 8
備考より関数の引数に与えた int b[2][5] が int (*)[5] 扱い されていることが分かる
pointer_ex6.c int main() { // 0 1 2 3 4 5 6 7 8 9 int a[10] = { 2, 3, 5, 7,11,13,17,19,23,29}; int b[2][5] = { 2, 3, 5, 7,11,13,17,19,23,29}; printf("sizeof(a) = %d¥n", sizeof(a)); func_with_array1d(a); func_with_pointer(a); printf("sizeof(b) = %d¥n", sizeof(b)); func_with_array2d(b); func_with_pointer(b); //a += 1; // It's impossible. Because a is an array. //b += 1; // It's impossible. Because b is an array. return EXIT_SUCCESS; }
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
pointer_ex6.c void func_with_pointer(int *p) { printf("sizeof(p) = %d¥n", sizeof(p)); }
20 21 22 23
関数定義の引数と配列
• ポインタ扱いなので配列サイズが取れない
582 [1] pp.121-122.
mintty + bash + GNU C $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) ^ sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 sizeof(p) = 8
pointer_ex6.c void func_with_array1d(int x[10]) { printf("sizeof(x) = %d¥n", sizeof(x)); printf("sizeof(x[0]) = %d¥n", sizeof(x[0])); x += 1; // x はポインタなので変更出来る }
4 5 6 7 8 9
pointer_ex6.c void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d¥n", sizeof(y)); printf("sizeof(y[0]) = %d¥n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d¥n", sizeof(y[0][0])); y += 1; // y はポインタなので変更出来る //y[0] += 1; // y[0] は配列なので変更来ない }
11 12 13 14 15 16 17 18
sizeof(int[2][5]) ではなく sizeof(int(*)[5])
sizeof(y[0])は sizeof(int[5])
sizeof(int[10]) ではなく sizeof(int*)
配列へのポインタなので 2次元目以降のサイズは取れる
1次元目はポインタになるので 配列サイズが取れない
関数定義の引数と配列
• ポインタ扱いなのでアドレスが変更出来る
583 [1] pp.121-122.
mintty + bash + GNU C $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) ^ sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 sizeof(p) = 8
pointer_ex6.c void func_with_array1d(int x[10]) { printf("sizeof(x) = %d¥n", sizeof(x)); printf("sizeof(x[0]) = %d¥n", sizeof(x[0])); x += 1; // x はポインタなので変更出来る }
4 5 6 7 8 9
pointer_ex6.c void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d¥n", sizeof(y)); printf("sizeof(y[0]) = %d¥n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d¥n", sizeof(y[0][0])); y += 1; // y はポインタなので変更出来る //y[0] += 1; // y[0] は配列なので変更来ない }
11 12 13 14 15 16 17 18
y はポインタだが、 y[0] は int[5] 配列だから アドレスの変更が出来ない アンコメントして コンパイル出来ない事を確認せよ
定義は配列変数 int x[10] に見えるが 実はポインタ変数 int *p なので アドレスの変更出来る
定義は配列変数 int y[2][5]に見えるが 実はポインタ変数 int (*y)[5] なので アドレスの変更出来る
確認: 関数定義の引数と配列
• 以下の個所を変えてコンパイルし確認せよ
584 [1] pp.121-122.
pointer_ex6.c void func_with_array1d(int x[10]) { printf("sizeof(x) = %d¥n", sizeof(x)); printf("sizeof(x[0]) = %d¥n", sizeof(x[0])); x += 1; // It's possible. Because x is pointer. }
4 5 6 7 8 9
int x[10] ではなく int x[20] や int x[] にしても 問題なくコンパイル出来る事を確認せよ
pointer_ex6.c void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d¥n", sizeof(y)); printf("sizeof(y[0]) = %d¥n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d¥n", sizeof(y[0][0])); y += 1; // It's possible. Because y is a pointer to an array. //y[0] += 1; // It's impossible. Because y is an array. }
11 12 13 14 15 16 17 18
int y[2][5] ではなく int y[20][5], int y[][5]にしても 問題なくコンパイル出来る事を確認せよ int y[2][10] だと コンパイル出来ないことを確認せよ
変数名に一番近い次元はポインタ扱いされるので 要素数が意味を持たなくなっていることを確認せよ
関数定義の引数と配列
• 引数名直後の1次元は要素数を省略すべき
585 [1] pp.121-122.
pointer_ex6.c void func_with_array1d(int x[10]) { printf("sizeof(x) = %d¥n", sizeof(x)); printf("sizeof(x[0]) = %d¥n", sizeof(x[0])); x += 1; // It's possible. Because x is pointer. }
4 5 6 7 8 9
int x[10] という引数の宣言は int *x と同じ意味
pointer_ex6.c void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d¥n", sizeof(y)); printf("sizeof(y[0]) = %d¥n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d¥n", sizeof(y[0][0])); y += 1; // It's possible. Because y is a pointer to an array. //y[0] += 1; // It's impossible. Because y is an array. }
11 12 13 14 15 16 17 18
int y[2][5] という引数の宣言は int (*y)[5] と同じ意味
int x[10] より int x[] int y[2][5] より int y[][5] と書く方が実態に合っている
int *x int (*y)[5] と書くのが最も正確
簡易表記?
無意味な数値は なるべく書くべきでない
関数定義の引数と配列
• 関数定義の仮引数では以下の定義は同義
586 [1] pp.121-122.
int sub(char s[N]) { // ... }
int sub(char s[]) { // ... }
int sub(char s[M][N]) { // ... }
int sub(char s[][N]) { // ... }
int sub(char *s) { // ... }
int sub(char (*s)[N]) { // ... }
= =
= =
int sub(char s[L][M][N]) { // ... }
int sub(char s[][M][N]) { // ... }
int sub(char (*s)[M][N]) { // ... }
= =
...
...
...
1次元配列 2次元配列 3次元配列
関数定義の引数と配列
• 関数定義の仮引数では以下の定義は同義
587 [1] pp.121-122.
int sub(char s[N]) { // ... }
int sub(char s[]) { // ... }
int sub(char s[M][N]) { // ... }
int sub(char s[][N]) { // ... }
int sub(char *s) { // ... }
int sub(char (*s)[N]) { // ... }
= =
= =
int sub(char s[L][M][N]) { // ... }
int sub(char s[][M][N]) { // ... }
int sub(char (*s)[M][N]) { // ... }
= =
...
...
...
1次元配列 2次元配列 3次元配列
関数の引数では 配列の最初の次元は 無視されてポインタ扱いになる
関数定義の引数と配列
• 関数定義の仮引数では以下の定義は同義
588 [1] pp.121-122.
int sub(char s[N]) { // ... }
int sub(char s[]) { // ... }
int sub(char s[M][N]) { // ... }
int sub(char s[][N]) { // ... }
int sub(char *s) { // ... }
int sub(char (*s)[N]) { // ... }
= =
= =
int sub(char s[L][M][N]) { // ... }
int sub(char s[][M][N]) { // ... }
int sub(char (*s)[M][N]) { // ... }
= =
...
...
...
1次元配列 2次元配列 3次元配列
関数の引数では配列の最初の次元は無視されてポインタ扱いになる 配列っぽく書いても良いが実際にはポインタとして処理される
複雑なポインタの宣言
* はどこに係っているのか?
589
演算子の優先度
演算子 結合規則 備考
( ) [ ] -> . 左から右→
! ~ ++ -- + - * & (type) sizeof 右から左← 単項演算子
* / % 左から右→ 二項演算子
+ - 左から右→ 二項演算子
<< >> 左から右→ bitシフト
< <= > >= 左から右→ 関係演算子
== != 左から右→ 等値演算子
& 左から右→ bit毎のAND
^ 左から右→ bit毎のXOR
| 左から右→ bit毎のOR
&& 左から右→ 論理演算子(AND)
|| 左から右→ 論理演算子(OR)
?: 右から左← 三項演算子
= += -= *= /= %= &= ^= |= <<= >>= 右から左← 代入演算子
, 左から右→
[1] p.65. より
高
低
優先度
590
複雑なポインタの宣言
• 配列はポインタ自身か?指している先か?
591
0x22ab98 p2
0x22aaa0
0x22aba0 a2[0]
0
0x22aba4 a2[1]
1
⋮ ⋮
0x22abbb a2[6]
1
pointer_ex7.c int *(p1[7]); // int *p1[7]; と同義 int (*p2)[7];
0x22aa98 a1
0
0x22aaa0 p1[0]
0x22aa98
0x22aaa8 p1[1]
?
⋮ ⋮
0x22aad0 p1[6]
?
int a1; int a2[7]; int *(p1[7]); int (*p2)[7];
教科書.pp.235-239., [1]pp.148-153.
[] は * よりも優先順位の高い演算子
int *(p1[7]); → *(p1[x])がint
int (*p2)[7]; → *p2がint[7]
複雑なポインタの宣言
• * を付けると何になるか?
592
pointer_ex7.c int *(p1[7]); // int *p1[7]; と同義 int (*p2)[7]; printf("sizeof( p1)=%2d¥n", sizeof( p1)); printf("sizeof( p2)=%2d¥n", sizeof( p2)); printf("sizeof(*p1)=%2d¥n", sizeof(*p1)); printf("sizeof(*p2)=%2d¥n", sizeof(*p2));
教科書.pp.235-239., [1]pp.148-153.
mintty + bash + GNU C $ gcc pointer_ex7.c && ./a sizeof( p1)=56 sizeof( p2)= 8 sizeof(*p1)= 8 sizeof(*p2)=28
[] は * よりも優先順位の高い演算子 宣言の読み方
要注意
p1[x]に*が付く、つまり*p1[x]がint型になる 従ってp1[x]はint*型、つまりp1は要素数7のint*型配列
p2に*が付く、つまり*p2が要素数7のint型配列になる 従ってp2は「『要素数7のint型配列』へのポインタ」
int *(p1[7]); → *(p1[x])がint
int (*p2)[7]; → *p2がint[7]
関数へのポインタ
関数をポインタ変数に代入して呼び出す
593
関数へのポインタ
• 関数へのポインタの例
594
pointer_ex8.c int compi(const int *a, const int *b) { return *a - *b; } int main() { int a, b; int (*fnc)(const int *a, const int *b) = compi; fprintf(stderr, "a = ? "); scanf("%d", &a); fprintf(stderr, "b = ? "); scanf("%d", &b); printf("(*fnc)(&a, &b) = %d¥n", (*fnc)(&a, &b)); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
mintty + bash + GNU C $ gcc pointer_ex8.c && ./a a = ? 10 b = ? 20 (*fnc)(&a, &b) = -10
関数への ポインタ変数 fnc に関数 compi を 代入して
fnc 経由で 関数 compi を 呼び出す
関数へのポインタ
• 作り方 • 関数のプロトタイプ宣言を書き写す • 関数名を変数名に書き変える • 変数名を ( ) で囲む • 変数名の前に * を付ける
• 例: • 格納したい関数のプロトタイプ宣言
• int compi(const int *a, const int *b);
• 関数へのポインタの宣言 • int (*fnc)(const int *, const int *);
• 関数へのポインタによる関数の呼び出し • (*fnc)(&a, &b);
595
fnc という変数を宣言 戻り値が int int *型の引数を2つ取る 関数のアドレスを 格納出来る
fnc という変数に 格納されたアドレスにある 関数に引数を渡して実行
引数名は省略可能
関数へのポインタ
• ポインタ変数に * が付いたら何になるか?
596
関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b);
関数の定義 int cmp(int *a, int *b) { return *a - *b; }
関数のプロトタイプ宣言 int cmp(int *a, int *b);
fnc がポインタ変数で *fnc が 戻り値が int 型 引数が (int *a, int *b) の 関数という意味になる
cmp が 戻り値が int 型 引数が (int *a, int *b) の 関数という意味になる
関数へのポインタに代入と呼び出し fnc = cmp; (*fnc)(&x, &y);
*fnc とすれば 関数へのポインタが関数になる 演算子の優先順位は 高*>()低なので*fncに()が必要
関数へのポインタ
• 関数関連の宣言では引数名は省略可能
• 関数のプロトタイプ宣言
• 関数へのポインタ変数の宣言
597
関数へのポインタ変数の宣言
関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b);
関数のプロトタイプ宣言 int cmp(int *a, int *b);
int (*fnc)(int * , int * );
関数のプロトタイプ宣言 int cmp(int * , int * );
関数へのポインタ
• 汎用型の場合キャストが必要
• 例: qsort や bsearch へ渡す比較関数
598
関数へのポインタ変数の宣言 int (*fnc)(void *a, void *b);
関数のプロトタイプ宣言 int cmp(int *a, int *b);
関数へのポインタに代入と呼び出し fnc = (int (*)(void *, void *)) cmp; (*fnc)(&x, &y);
void 型と void 型へのポインタ
• void(=空洞)つまり大きさがない
• 関数に引数や戻り値がないことを意味する
• ポインタの指し示す先の大きさが不明(特定の型に縛られない)であることを意味する
599
変数の宣言 void a; // void 型の変数はないのでコンパイルエラー void *p; // p が void 型へのポインタ
大きさが0バイトのデータ つまり void a; には意味がないが 大きさが不明のデータでも先頭アドレス つまり void *p; には意味がある
0x~0
0x~1
0x~2
0x~3
0x~0
0x~1
0x~2
0x~3
p= 0x~0
void 型と void 型へのポインタ
• void * 型は使用時に大きさを決めて使う
• 適当な型へのポインタとしてキャストする
600
void 型ポインタの例 int a; void *p = &a; // p に a のアドレスが入る *((int *)p) = 1; // p は void* なので *p は void だが // p を int* にキャストすると *p が int になる
0x~0
0x~1
0x~2
0x~3
0x~0
0x~1
0x~2
0x~3
*p は void なので意味がない
*((int*)p) は int なので意味がある p= 0x~0
標準ライブラリの qsort 関数
• void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *));
• 要素サイズsize,要素数nのデータbaseを比較関数cmpの結果に従い並べ替える
• 引数 • base: データへのポインタ
• n : データの要素数
• size: 1要素当りのバイト数
• cmp : 比較に用いる関数へのポインタ
• 戻り値 • なし
601 [1] pp.144-148, 319.
関数へのポインタ 並べ替えの際、 比較を可換にすることで 汎用性を持たせている。
並べ替えの順序 昇順、降順 データ型 int, double, 文字列
void * 型は 任意の方へのポインタ
JM: qsort (3)
qsort 利用の例
• 比較関数を用意すれば任意データに使える
602
qsort_ex1.c int compi(const int *a, const int *b) { return *a - *b; }
28 29 30 31
mintty + bash + GNU C $ gcc qsort_ex1.c && ./a n = ? 10 v[] = 197 22 155 489 71 47 137 364 486 70 v[] = 22 47 70 71 137 155 197 364 486 489
qsort_ex1.c printiv(v, n); qsort(v, n, sizeof(int), (int (*)(const void *, const void *)) compi); printiv(v, n);
47 48 49
const void * を 引数とする関数として キャストする必要がある
int 型のデータの比較関数
int 型のデータの並べ替えの例
qsort 利用の例
• 比較関数を用意すれば任意データに使える
603
qsort_ex2.c int strcmp_wrapper(const char **a, const char **b) { return strcmp(*a, *b); }
47 48 49 50
mintty + bash + GNU C $ gcc qsort_ex2.c && ./a n = ? 2 v[0] = "yocchzcmwhmufrdvde" v[1] = "iipfziuhu" v[0] = "iipfziuhu" v[1] = "yocchzcmwhmufrdvde"
qsort_ex2.c printsv(v, n); qsort(v, n, sizeof(char*), (int (*)(const void*,const void*))strcmp_wrapper); printsv(v, n);
67 68 69
const void * を 引数とする関数として キャストする必要がある
標準ライブラリ関数 strcmp のラッパー関数
配列へのポインタ並べ替えの例
標準ライブラリの2分探索関数
• void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void *keyval, const void *datum));
• ソート済み配列を二分探索(binary search)する • 引数
• key : 検索したい値へのポインタ • base : 検索対象のデータ集合へのポインタ • n : データの要素数 • size : データの1要素当りのバイト数 • cmp : データ比較用の関数へのポインタ
• 戻り値 • マッチした項目へのポインタを返す • マッチした項目がない場合NULLを返す
604 教科書 pp.198-202. JM: bsearch (3)
標準ライブラリの2分探索関数
• 比較関数さえ用意すれば簡単に検索出来る
605 教科書 pp.198-202.
bsearch_ex1.c int cmp(const int *keyval, const int *datum) { return *keyval - *datum; }
4 5 6 7
bsearch_ex1.c p = bsearch(&key, data, sizeof(data)/sizeof(int), sizeof(int), (int (*)(const void*, const void*))cmp); if (p) { printf("data[%d] = %d¥n", p - data, *p); } else { printf("not found.d¥n"); }
29 30 31 32 33 34
bsearch_ex1.c { int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, }; int key; int *p;
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
mintty + bash + GNU C $ gcc bsearch_ex1.c && ./a key = ? 113 data[29] = 113
動的配列
メモリの確保・解放とポインタによる配列
606
動的配列
607
多くの問題では 配列のサイズを事前に (コンパイルの段階では) 決められない。
実行時、プログラムに 与えるデータや パラメータによって 配列のサイズは決まる。
=
プログラムの実行時に 配列のサイズを適宜に決める事が必要
malloc, calloc, realloc, free 関数
malloc 関数
• void *malloc(site_t size); • 動的にメモリを確保する
• 引数: • size : 確保するメモリのバイト数
• 戻り値 • 確保したメモリへのポインタを返す • 確保したメモリの内容は初期化されない • エラーの場合は NULL を返す
608
JM: malloc (3)
calloc 関数
• void *calloc(site_t nobj, site_t size); • 動的にメモリを確保する
• 引数: • nobj : メモリを確保する要素数 • size : 1要素当たりバイト数
• 戻り値 • 確保したメモリへのポインタを返す • 確保したメモリの内容は0で初期化される • エラーの場合は NULL を返す
609
JM: malloc (3)
realloc 関数
• void *realloc(void *p, site_t size); • 動的に確保したメモリのサイズを変更する
• 引数: • p : サイズを変更するメモリへのポインタ • size : 変更後のバイト数
• 戻り値 • 確保したメモリへのポインタを返す • サイズ変更前後で小さい方のサイズまでは
前の内容が維持される • 増加した部分のメモリの内容は初期化されない • エラーの場合は NULL を返す
• 元のブロックは維持される
610
JM: malloc (3)
free 関数
• void free(void *p); • 動的に確保したメモリを解放する
• 引数: • p : 解放するメモリへのポインタ
611
JM: malloc (3)
動的配列の基本
• malloc で確保し free で解放する
612
配列 #define N 10 // 要素数は定数 // ... int a[N]; // 配列の確保 // 確保できないとコンパイルに失敗する a[i] = x; // 変数に対する操作 // 配列はスコープの終わりで // 自動的に開放される
動的配列 int N = 10; // 要素数は変数も可 // ... int *a = malloc(sizeof(int) * N); // メモリの確保 if (a == NULL) {// 確保できないと a に NULL が入る fprintf(stderr, "Error: malloc failed.¥n"); exit(EXIT_FAILURE); // エラー処理が必要 } a[i] = x; // 変数に対する操作 free(a); // メモリの解放 // 動的配列は解放しないといつまでも確保され続ける
動的配列の基本
• malloc で確保し free で解放する
613
2次元画像の例 unsigned char *img; // 画像用の動的配列 (unsigned char 型へのポインタ) int w, h; // 画像の縦横サイズ // 動的にサイズを決める fprintf(stderr, "w = ?"); scanf("%d", &w); fprintf(stderr, "h = ?"); scanf("%d", &h); if ((img = malloc(3 * w * h)) == NULL) { // メモリーの動的確保 fprintf(stderr, "Error: in %s line %d: malloc failed¥n", __FILE__, __LINE__); exit(EXIT_FAILURE); } // ... // ここで確保したメモリーに対する処理を行う free(img); // 使い終わって不要になったメモリーの解放
動的配列の例
• 1週目の bmptest.c と同じグラデーション
614
dynamic_array_ex1.c void print_ppm(unsigned char *img, int w, int h, int q); int main() { unsigned char *img; int w, h, x, y; fprintf(stderr, "w = ? "); scanf("%d", &w); fprintf(stderr, "h = ? "); scanf("%d", &h); if ((img = malloc(3 * w * h)) == NULL) { fprintf(stderr, "Error: in %s line %d: malloc failed¥n", __FILE__, __LINE__); exit(EXIT_FAILURE); } for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { img[3 * (w * y + x) + 0] = 255; // R img[3 * (w * y + x) + 1] = 255 * x / w; // G img[3 * (w * y + x) + 2] = 255 * y / h; // B } } print_ppm(img, w, h, UCHAR_MAX); free(img); return EXIT_SUCCESS; }
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
mintty + bash + GNU C $ gcc dynamic_array_test.c print_ppm.c && ./a | convert - a.bmp w = ? 255 h = ? 255
a.bmp
malloc で確保したメモリは 普通に1次元配列のように使える
動的配列によるカラー画像
• 1次元配列を3次元配列のように使う
615
dynamic_array_ex1.c for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { img[3 * (w * y + x) + 0] = 255; // R img[3 * (w * y + x) + 1] = 255 * x / w; // G img[3 * (w * y + x) + 2] = 255 * y / h; // B } }
18 19 20 21 22 23 24
R G B R G B ... R G B
R G B R G B ... R G B
R G B R G B ... R G B
: : : : : : ⋱ : : :
R G B R G B ... R G B
w*3
h
x
y
img
img[3 * (w * y + x) + 0]
unsigned char *img; を unsigned char img[h][w][3]; と同じように使う
動的配列は1次元的にしか取れないため、 要素の位置は自分で計算する必要がある。
テキスト形式PPM画像の書き出し
• PPMの書き出しを関数化してみる
616
print_ppm.c #include <stdio.h> void print_ppm(unsigned char *img, int w, int h, int q) { int c, x, y; printf("P3¥n"); printf("%d %d¥n", w, h); printf("%d¥n", q); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf(" "); for (c = 0; c < 3; c++) { printf(" %3d", img[3 * (w * y + x) +c]); } } printf("¥n"); } }
関数のプロトタイプ宣言 // 作った関数の本体(並括弧の中身)を取り除き // 末尾に ; (セミコロン)を付ける void print_ppm(unsigned char *img, int w, int h, int q);
R G B R G B ... R G B
R G B R G B ... R G B
R G B R G B ... R G B
: : : : : : ⋱ : : :
R G B R G B ... R G B
c
y
x
y, x, c のループで書き出す
テキスト形式PPM画像の書き出し
• PPMの書き出しを関数化してみる
617
print_ppm.c #include <stdio.h> void print_ppm(unsigned char *img, int w, int h, int q) { int c, x, y; printf("P3¥n"); printf("%d %d¥n", w, h); printf("%d¥n", q); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf(" "); for (c = 0; c < 3; c++) { printf(" %3d", img[3 * (w * y + x) +c]); } } printf("¥n"); } }
関数のプロトタイプ宣言 // 作った関数の本体(並括弧の中身)を取り除き // 末尾に ; (セミコロン)を付ける void print_ppm(unsigned char *img, int w, int h, int q);
c
y
x
B B B ... B
B B B ... B
B B B ... B
: : : ⋱ :
B B B ... B
G G G ... G
G G G ... G
G G G ... G
: : : ⋱ :
G G G ... G
R R R ... R
R R R ... R
R R R ... R
: : : ⋱ :
R R R ... R
テキスト形式PPM画像の読み込み
• 読み込んだパラメータに応じて大きさを変更
618
read_pnm_p3_core.c unsigned char *read_pnm_p3(int *w, int *h, int *q) { char s[10]; unsigned char *img; int y, x, c; scanf("%9s%d%d%d", s, w, h, q); if ((img = calloc(*h * *w * 3, 1)) == NULL) { return NULL; } for (y = 0; y < *h; y++) { for (x = 0; x < *w; x++) { for (c = 0; c < 3; c++) { scanf("%d", &img[3 * (*w * y + x) + c]); } } } return img; }
パラメータの読み込み
パラメータに応じて 動的配列の確保
配列に格納する値の 読み込み
確保し値を読み込んだ 動的配列を返す
実用上は、これ+エラー処理が必要 詳細は read_pnm_p3.c 参照
配列の添え字調整
配列の添え字の範囲 0~N-1 をずらして使う
619
ポインタの応用: 配列の添え字調整
C言語における配列の宣言 型名 変数名[要素数];
例: 要素数N個でint型の配列a int a[N];
利用可能な要素は a[0]~a[N-1]
添え字0~N-1の範囲を自由変更出来ないか?
↓
ポインタを利用すれば可能
620
ポインタの応用: 配列の添え字調整
• 1元配列とメモリ上の配置
621
⋮
a[-1]
a[ 0]
a[ 1]
a[ 2]
a[ 3]
⋮
... 確保されたメモリ
... 確保されたメモリ外
⋮
p[-1]
p[ 0]
p[ 1]
p[ 2]
p[ 3]
⋮
=
p = &a[0] とした場合
⋮
p[-2]
p[-1]
p[ 0]
p[ 1]
p[ 2]
⋮
=
p = &a[1] とした場合
int a[3]; int *p; として
p[0] が a のどこに対応するか 調整してやれば添え字の範囲を 自由に調整して使える
a[ 0]~a[ 2] を p[-1]~p[ 1] として使う
⋮
p[ 0]
p[ 1]
p[ 2]
p[ 3]
p[ 4]
⋮
=
p = &a[-1] とした場合
a[ 0]~a[ 2] を p[ 1]~p[ 3] として使う
ポインタの応用: 配列の添え字調整
• オフセットを与えてポインタを格納する
622
array_offset_ex1.c int a[3] = {2,3,5}; int *p; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d¥n", &a[i], i, a[i]); } p = a + 1; for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d¥n", &p[i], i, p[i]); } p = a - 1; for (i = 1; i <= 3; i++) { printf("%p: p[% d] = %d¥n", &p[i], i, p[i]); }
mintty + bash + GNU C $ gcc array_offset_ex1.c && ./a 0x23cb00: a[ 0] = 2 0x23cb04: a[ 1] = 3 0x23cb08: a[ 2] = 5 0x23cb00: p[-1] = 2 0x23cb04: p[ 0] = 3 0x23cb08: p[ 1] = 5 0x23cb00: p[ 1] = 2 0x23cb04: p[ 2] = 3 0x23cb08: p[ 3] = 5
a[i] と p[i] の対応を 確認しましょう
ポインタの応用: 配列の添え字調整
• オフセットを与えてポインタを格納する
623
array_offset_ex1.c int a[3] = {2,3,5}; int *p; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d¥n", &a[i], i, a[i]); } p = a + 1; for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d¥n", &p[i], i, p[i]); } p = a - 1; for (i = 1; i <= 3; i++) { printf("%p: p[% d] = %d¥n", &p[i], i, p[i]); }
mintty + bash + GNU C $ gcc array_offset_ex1.c && ./a 0x23cb00: a[ 0] = 2 0x23cb04: a[ 1] = 3 0x23cb08: a[ 2] = 5 0x23cb00: p[-1] = 2 0x23cb04: p[ 0] = 3 0x23cb08: p[ 1] = 5 0x23cb00: p[ 1] = 2 0x23cb04: p[ 2] = 3 0x23cb08: p[ 3] = 5
a に対してオフセットを 1 加える p は a[1] のアドレスを指す つまり p[0] が a[1] 結果、以下の要素が対応する a[ 0], a[ 1], a[ 2] p[-1], p[ 0], p[ 1]
ポインタの応用: 配列の添え字調整
• オフセットを与えてポインタを格納する
624
array_offset_ex1.c int a[3] = {2,3,5}; int *p; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d¥n", &a[i], i, a[i]); } p = a + 1; for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d¥n", &p[i], i, p[i]); } p = a - 1; for (i = 1; i <= 3; i++) { printf("%p: p[% d] = %d¥n", &p[i], i, p[i]); }
mintty + bash + GNU C $ gcc array_offset_ex1.c && ./a 0x23cb00: a[ 0] = 2 0x23cb04: a[ 1] = 3 0x23cb08: a[ 2] = 5 0x23cb00: p[-1] = 2 0x23cb04: p[ 0] = 3 0x23cb08: p[ 1] = 5 0x23cb00: p[ 1] = 2 0x23cb04: p[ 2] = 3 0x23cb08: p[ 3] = 5
a に対してオフセットを 1 減らす p は a[-1] のアドレスを指す つまり p[0] が a[-1] 結果、以下の要素が対応する a[ 0], a[ 1], a[ 2] p[ 1], p[ 2], p[ 3]
ポインタの応用: 配列の添え字調整 2次元配列の場合
• 2次元配列とメモリ上の配置
625
⋮
a[ 0][ 0]
a[ 0][ 1]
a[ 0][ 2]
a[ 1][ 0]
a[ 1][ 1]
a[ 1][ 2]
a[ 2][ 0]
a[ 2][ 1]
a[ 2][ 2]
⋮
... 確保されたメモリ
... 添え字の範囲外だが 確保されたメモリ内
... 確保されたメモリ外
⋮
a[ 1][-3]
a[ 1][-2]
a[ 1][-1]
a[ 1][ 0]
a[ 1][ 1]
a[ 1][ 2]
a[ 1][ 3]
a[ 1][ 4]
a[ 1][ 5]
⋮
⋮
a[ 0][ 0]
a[ 0][ 1]
a[ 0][ 2]
a[ 0][ 3]
a[ 0][ 4]
a[ 0][ 5]
a[ 0][ 6]
a[ 0][ 7]
a[ 8][ 8]
⋮
⋮
a[ 2][-6]
a[ 2][-5]
a[ 2][-4]
a[ 2][-3]
a[ 2][-2]
a[ 2][-1]
a[ 2][ 0]
a[ 2][ 1]
a[ 2][ 2]
⋮
=
多次元配列も 結局は1次元の メモリアドレスに 割り振られる
添え字の範囲外でも メモリ上で連続であれば アクセスは可能
ポインタの応用: 配列の添え字調整 2次元配列の場合
• 2次元配列とポインタのオフセット
626
⋮
a[ 0][ 0]
a[ 0][ 1]
a[ 0][ 2]
a[ 1][ 0]
a[ 1][ 1]
a[ 1][ 2]
a[ 2][ 0]
a[ 2][ 1]
a[ 2][ 2]
⋮
⋮
(*p)[-1][-1]
(*p)[-1][ 0]
(*p)[-1][ 1]
(*p)[ 0][-1]
(*p)[ 0][ 0]
(*p)[ 0][ 1]
(*p)[ 1][-1]
(*p)[ 1][ 0]
(*p)[ 1][ 1]
⋮
=
int [3][3] へのポインタ p を作り p が a[ 1][ 1] を指すよう調整すれば a[ 0][ 0]~ a[ 2][ 2] を (*p)[-1][-1]~(*p)[ 1][ 1] として使える!
int a[3][3]; int (*p)[3][3]=(int (*)[3][3])&a[1][1];
ポインタの応用: 配列の添え字調整 2次元配列の場合
• 2次元配列とポインタのオフセット
627
⋱ ⋮ ⋮ ⋮ ⋮ ⋮ ⋰
... a[-1][-1] a[-1][ 0] a[-1][ 1] a[-1][ 2] a[-1][ 3] ...
... a[ 0][-1] a[ 0][ 0] a[ 0][ 1] a[ 0][ 2] a[ 0][ 3] ...
... a[ 1][-1] a[ 1][ 0] a[ 1][ 1] a[ 1][ 2] a[ 1][ 3] ...
... a[ 2][-1] a[ 2][ 0] a[ 2][ 1] a[ 2][ 2] a[ 2][ 3] ...
... a[ 3][-2] a[ 3][ 0] a[ 3][ 1] a[ 3][ 2] a[ 3][ 3] ...
⋰ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱
p = a とした時の (*p)[-1][-1]~(*p)[1][1]の範囲
... 確保されたメモリ
... 添え字の範囲外だが確保されたメモリ内
... 確保されたメモリ外
p = &a[1][1] とした時の (*p)[-1][-1]~(*p)[1][1]の範囲
ポインタの応用: 配列の添え字調整 2次元配列の場合
• オフセットを与えてポインタを格納する
628
array_offset_ex2_1.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int (*p)[3][3] = (int (*)[3][3]) &a[1][1]; int x, y; printf("sizeof(a) = %d¥n", sizeof(a)); printf("sizeof(*p) = %d¥n", sizeof(*p)); for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d¥n", &a[y][x], y, x, a[y][x]); } } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: (*p)[% d][% d] = %d¥n", &(*p)[y][x], y, x, (*p)[y][x]); } }
int [3][3] へのポインタは int (*)[3][3]
a[1][1]は int[3][3]型ではなく int型なのでキャストが必要
ポインタの応用: 配列の添え字調整 2次元配列の場合
• オフセットを与えてポインタを格納する
629
array_offset_ex2_2.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int (*p)[3] = (int (*)[3]) &a[1][1]; int x, y; printf("sizeof(a) = %d¥n", sizeof(a)); printf("sizeof(*p) = %d¥n", sizeof(*p)); for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d¥n", &a[y][x], y, x, a[y][x]); } } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: p[% d][% d] = %d¥n", &p[y][x], y, x, p[y][x]); } }
int [3][3] へのポインタは int (*)[3][3] だが int (*)[3] でも格納可能 使う時は *p としなくて良いので 後者の方が使い易い?
1次元配列でも int [3] への ポインタではなく int 型への ポインタを 使っていた
ポインタの応用: 配列の添え字調整 2次元配列の場合
630
mintty + bash + GNU C $ gcc array_offset_ex2_2.c && ./a sizeof(a) = 36 sizeof(*p) = 12 0x22aa90: a[ 0][ 0] = 2 0x22aa94: a[ 0][ 1] = 3 0x22aa98: a[ 0][ 2] = 5 0x22aa9c: a[ 1][ 0] = 7 0x22aaa0: a[ 1][ 1] = 11 0x22aaa4: a[ 1][ 2] = 13 0x22aaa8: a[ 2][ 0] = 17 0x22aaac: a[ 2][ 1] = 19 0x22aab0: a[ 2][ 2] = 23 0x22aa90: p[-1][-1] = 2 0x22aa94: p[-1][ 0] = 3 0x22aa98: p[-1][ 1] = 5 0x22aa9c: p[ 0][-1] = 7 0x22aaa0: p[ 0][ 0] = 11 0x22aaa4: p[ 0][ 1] = 13 0x22aaa8: p[ 1][-1] = 17 0x22aaac: p[ 1][ 0] = 19 0x22aab0: p[ 1][ 1] = 23
mintty + bash + GNU C $ gcc array_offset_ex2_1.c && ./a sizeof(a) = 36 sizeof(*p) = 36 0x22aa90: a[ 0][ 0] = 2 0x22aa94: a[ 0][ 1] = 3 0x22aa98: a[ 0][ 2] = 5 0x22aa9c: a[ 1][ 0] = 7 0x22aaa0: a[ 1][ 1] = 11 0x22aaa4: a[ 1][ 2] = 13 0x22aaa8: a[ 2][ 0] = 17 0x22aaac: a[ 2][ 1] = 19 0x22aab0: a[ 2][ 2] = 23 0x22aa90: (*p)[-1][-1] = 2 0x22aa94: (*p)[-1][ 0] = 3 0x22aa98: (*p)[-1][ 1] = 5 0x22aa9c: (*p)[ 0][-1] = 7 0x22aaa0: (*p)[ 0][ 0] = 11 0x22aaa4: (*p)[ 0][ 1] = 13 0x22aaa8: (*p)[ 1][-1] = 17 0x22aaac: (*p)[ 1][ 0] = 19 0x22aab0: (*p)[ 1][ 1] = 23
sizeof(*p)は異なる int (*p)[3][3]; だと sizeof(*p) は sizeof(int[3][3]) int (*p)[3]; だと sizeof(*p) は sizeof(int[3])
ポインタの応用: 配列の添え字調整 2次元配列の場合
• 配列のサイズが必要になるのが欠点
631
array_offset_ex2_2.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int (*p)[3] = (int (*)[3]) &a[1][1]; int x, y; printf("sizeof(a) = %d¥n", sizeof(a)); printf("sizeof(*p) = %d¥n", sizeof(*p)); for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d¥n", &a[y][x], y, x, a[y][x]); } } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: p[% d][% d] = %d¥n", &p[y][x], y, x, p[y][x]); } }
最後の次元以外は要素数が既知でないと 配列へのポインタを作れない
つまり動的配列に対して使えない
ポインタの応用: 配列の添え字調整 2次元配列の場合
• 動的配列の場合は自力でアドレスを計算する
632
array_offset_ex3.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int *p = &a[1][1], w = 3, h = 3; int x, y; for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d¥n", &a[y][x], y, x, a[y][x]); } } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: p[w * % d + % d] = %d¥n", &p[w * y + x], y, x, p[w * y + x]); } }
ポインタを1次元配列として用いる 但しアドレスを計算し易いオフセットを あらかじめ設定しておく
アドレスは自力で計算
確認問題
• 以下の状況で9行目の時点の *p と p の値を16進数で答えよ。
633
hoge.c int a = 0x12345678; int *p = &a; printf("&a = %p¥n", &a);
mintty + bash + GNU C $ gcc hoge.c && ./a &a = 0x22aac4
6 7 8 9
確認問題
• int 型へのポインタ変数 a, b を宣言する場合正しいのは以下のうちどれか?
634
hoge.c int a, b; // (1) int *a, b; // (2) int a, *b; // (3) int *a, *b; // (4)
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
635
C言語入門 第14週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
636
復習
関数の引数(値渡し、参照渡し)
637
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
638
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; }
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
639
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
gl
100
0x00000001004071c0
0x000000000023cb24
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
640
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201 0x000000000023cb18
gl
100
0x00000001004071c0
0x000000000023cb20
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
641
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
呼び出し元アドレス
scopetest.c 22 行目
0x000000000023cb18
lo
300
0x000000000023cb20
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
gl
100
0x00000001004071c0
0x000000000023cb14
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
642
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
呼び出し元アドレス
scopetest.c 22 行目
0x000000000023cb18
lo
300
0x000000000023cb20
lo
401
0x000000000023cb14
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
gl
101
0x00000001004071c0
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
643
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
呼び出し元アドレス
scopetest.c 22 行目
0x000000000023cb18
lo
300
0x000000000023cb20
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
gl
101
0x00000001004071c0
0x000000000023cb14
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
644
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
呼び出し元アドレス
scopetest.c 22 行目
0x000000000023cb18
lo
301
0x000000000023cb20
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
gl
102
0x00000001004071c0
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
645
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201 0x000000000023cb18
gl
102
0x00000001004071c0
0x000000000023cb20
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 変数のスコープ(有効範囲)
646
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; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
第8週資料pp.21-23.
呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
201
0x000000000023cb24
mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
gl
103
0x00000001004071c0
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
647
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
648
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
0x000000000023cb24
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
649
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
100
0x000000000023cb24
0x000000000023cb18
0x000000000023cb20
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
訂正2015-07-25 誤: 11行目 正: 12行目
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
650
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
100
0x000000000023cb24
呼び出し元アドレス
cal_by_value.c 12 行目
0x000000000023cb18
lo
200
0x000000000023cb20
注: rbpレジスタの退避や メモリ配置時のアライメントの問題等もあるため 実際のメモリーの状況とは若干異なります。
訂正2015-07-25 誤: 11行目 正: 12行目
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
651
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
100
0x000000000023cb24
0x000000000023cb18
0x000000000023cb20
注: 実際にはrbpレジスタの退避等もあるため もう少し余分な物もスタックに積まれます
訂正2015-07-25 誤: 11行目 正: 12行目
関数の引数(値渡し、参照渡し)
• 値渡し: 呼出し元の値のコピーを渡す
652
call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
引数で受け取った変数を変更しても 呼び出し元には反映されない
第8週資料pp.21-23. 教科書p.171.
mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
100
0x000000000023cb24
関数の引数(値渡し、参照渡し)
• 参照渡し: 呼出し元の値の格納場所を渡す
653
call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
scanf で見たことがある書き方! &: アドレス演算子
教科書p.171.
引数で受け取った変数を変更すると 呼び出し元にも反映される
これは正確には ポインタ渡しと言う
変数loのアドレスを 渡している
mintty + bash + GNU C $ gcc call_by_pointer.c && ./a lo=200
関数の引数(値渡し、参照渡し)
• 参照渡し: 呼出し元の値の格納場所を渡す
654
call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書p.171.
mintty + bash + GNU C $ gcc call_by_pointer.c && ./a lo=200 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
100
0x000000000023cb24
0x000000000023cb14
0x000000000023cb1c
関数の引数(値渡し、参照渡し)
• 参照渡し: 呼出し元の値の格納場所を渡す
655
call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書p.171.
mintty + bash + GNU C $ gcc call_by_pointer.c && ./a lo=200 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
呼び出し元アドレス
cal_by_pointer.c 12 行目
0x000000000023cb18
lo
0x000000000023cb24
0x000000000023cb18
ポインターを介して呼び出し元の lo を書き換え
関数の引数(値渡し、参照渡し)
• 参照渡し: 呼出し元の値の格納場所を渡す
656
call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }
4 5 6 7 8 9 10 11 12 13 14 15 16
教科書p.171.
mintty + bash + GNU C $ gcc call_by_pointer.c && ./a lo=200 呼び出し元アドレス
0x0000000180048551
0x000000000023cb28
lo
200
0x000000000023cb24
0x000000000023cb14
0x000000000023cb1c
文字列操作とポインタ操作
標準ライブラリ関数を例にした実例
657
ポインタを用いた文字列操作の例
• strlen 関数の大まかな仕組み
658
strlen_with_idx.c size_t strlen(const char *s) { size_t len = 0; while (s[len] != '¥0') len++; return len; }
文字列の長さは 先頭から終端文字('¥0')の手前までの 文字数
strlen_with_ptr1.c size_t strlen(const char *s) { const char *s0 = s; while (*s != '¥0') s++; return s - s0; }
strlen_with_ptr2.c size_t strlen(const char *s) { const char *s0 = s; while (*(s++) != '¥0') ; return s - s0 - 1; }
ポインタを用いた文字列のコピーの例
• strcpy 関数の大まかな仕組み
659
strcpy_with_idx.c char *strcpy(char *dst, const char *src) { int i; for (i = 0; (dst[i] = src[i]) != '¥0'; i++) ; return dst; }
strcpy_with_ptr.c char *strcpy(char *dst, const char *src) { char *dst0 = dst; while ((*(dst++) = *(src++)) != '¥0') ; return dst0; }
文字列のコピーは 先頭から終端文字('¥0')までを コピーすれば良い
ポインタを用いた文字列のコピーの例
• strncpy 関数の大まかな仕組み
660
strncpy_with_idx.c char *strncpy(char *dst, const char *src, size_t n) { size_t i = 0; for (; i < n && (dst[i] = src[i]) != '¥0'; i++) ; for (; i < n; i++) dst[i] = '¥0'; return dst; }
strncpy_with_ptr.c char *strncpy(char *dst, const char *src, size_t n) { char *dst0 = dst; while(0 < n-- && (*(dst++) = *(src++)) != '¥0') ; while(0 < n--) *(dst++) = '¥0'; return dst0; }
strncpy は strcpy に加えて 終端文字('¥0')以降を'¥0'で埋める
論理演算は左から右に評価され、 真偽値が確定すると評価を終了する。 つまり i < n や dst < dst0 + n が偽なら、 そこで真偽値が確定するので それより右にある (dst[i] = src[i]) != '¥0' や (*(dst++) = *(src++)) != '¥0' は 実行されない。
ポインタを用いた文字列の比較の例
• strcmp 関数の大まかな仕組み
661
strcmp_with_idx.c int strcmp(const char *s1, const char *s2) { size_t i; for (i = 0; s1[i] != '¥0' && s2[i] != '¥0' && s1[i] == s2[i]; i++) ; return s1[i] - s2[i]; }
strcmp_with_ptr.c int strcmp(const char *s1, const char *s2) { while (*s1 != '¥0' && *s2 != '¥0' && *s1 == *s2) { s1++; s2++; } return *s1 - *s2; }
どちらかが終端文字('¥0')になるか 異なる値が出てくるまで比較し 終了位置を比較すれば良い
ポインタを用いた文字列の比較の例
• strncmp 関数の大まかな仕組み
662
strncmp_with_idx.c int strncmp(const char *s1, const char *s2, size_t n) { size_t i; if (n <= 0) return 0; for (i = 0; i < n - 1 && s1[i] != '¥0' && s2[i] != '¥0' && s1[i] == s2[i]; i++) ; return s1[i] - s2[i]; }
strncmp_with_ptr.c int strncmp(const char *s1, const char *s2, size_t n) { if (n <= 0) return 0; while (0 < --n && *s1 != '¥0' && *s2 != '¥0' && *s1 == *s2) { s1++; s2++; } return *s1 - *s2; }
strncmp は strcmp の 比較文字数を最大n文字に限定する
ポインタを用いた文字列の連結の例
• strcat 関数の大まかな仕組み
663
strcat_with_idx.c char *strcat(char *dst, const char *src) { int i, len = strlen(dst); for (i = 0; (dst[len + i] = src[i]) != '¥0'; i++) ; return dst; }
strcat_with_ptr.c char *strcat(char *dst, const char *src) { char *dst0 = dst; dst += strlen(dst); while ((*(dst++) = *(src++)) != '¥0') ; return dst0; }
dst の終端位置に src をコピーする
ポインタを用いた文字列の連結の例
• strncat 関数の大まかな仕組み
664
strncat_with_idx.c char *strncat(char *dst, const char *src, size_t n) { int i, len = strlen(dst); for (i = 0; i < n && src[i] != '¥0'; i++) dst[len + i] = src[i]; dst[len + i] = '¥0'; return dst; }
strncat_with_ptr.c char *strncat(char *dst, const char *src, size_t n) { char *dst0 = dst; dst += strlen(dst); while (0 < n-- && *src != '¥0') *(dst++) = *(src++); *dst = '¥0'; return dst0; }
strncat は strcat の 連結文字を最大n文字に限定する ただしsrcがn文字以上の場合 終端文字が+1文字され 合計n+1バイト追記される
ポインタへのポインタ
• 関数の引数でポインタを返したい場合はポインタ変数へのポインタを用いる
665
strtoui.c unsigned int strtoui(const char *s, char **endp, int base) { int v; unsigned int r = 0; while (0 <= (v = basetoint(*(s++), base))) { r = r * base + v; } if (endp != NULL) *endp = (char *) s; return r; }
教科書 pp.243-250.
main.c char s[] = "ffz"; char *endp; printf("strtoui(s, &endp, 16);
この例だと endp に "z" へのポインタ つまり&s[2]が返ってくる
コマンドライン引数と文字列
main 関数の引数
666
コマンドライン引数
• main 関数の引数として取得出来る。
667
arg_ex1.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i; printf("argc = %d¥n", argc); for (i = 0; i < argc; i++) { printf("argv[%d] = ¥"%s¥"¥n", i, argv[i]); } return EXIT_SUCCESS; }
main の引数名は 自由につけて良いが 以下の名前を使うのが 慣例になっている argc: ARGument Count argv: ARGument Vector
教科書 pp.265-272., [1] pp.139-144., 第8週資料 p.4.
argument は英語で 引数を意味する
コマンドライン引数
• main 関数の引数として取得出来る。
668
コマンドプロンプト >arg_ex1 a b "c d" "e¥"f" argc = 5 argv[0] = "C:¥Users¥kou¥Desktop¥CLangI2015S1¥week14¥arg_ex1.exe" argv[1] = "a" argv[2] = "b" argv[3] = "c d" argv[4] = "e"f"
mintty + bash $ ./a a b "c d" "e¥"f" argc = 5 argv[0] = "./argtest" argv[1] = "a" argv[2] = "b" argv[3] = "c d" argv[4] = "e"f"
教科書 pp.265-272., [1] pp.139-144., 第8週資料 p.4.
コマンドライン空白で分割されて argvに格納される。 argvに空白を含めたい場合は 「"」(ダブルクォーテーション)で囲む 「"」を含めたい場合は「¥」でエスケープする argv[0]は実行中のコマンド名
コマンドライン引数
• char *argv[] は char* 型の配列
669
mintty + bash $ ./a a b "c d" "e¥"f" argc = 5 argv[0] = "./argtest" argv[1] = "a" argv[2] = "b" argv[3] = "c d" argv[4] = "e"f"
教科書 pp.265-272., [1] pp.139-144., 第8週資料 p.4.
'a' '¥0' 'b' '¥0' 'c' ' ' 'd' '¥0' 'e' '"' f '¥0'
argv[1]
argv[2]
argv[3]
argv[4]
正確には 関数の引数で最初の [] は * と同じだったので char *argv[] は char **argv と同じ つまり char 型へのポインタへのポインタ
文字列とポインタ
670
ポインタ配列の初期化
• char * の配列の初期化
671
char *s[] = {"one", "two", "three"};
教科書.pp.235-239., [1]pp.148-153.
0x~00 o
0x~01 n
0x~02 e
0x~03 ¥0
0x~04 t
0x~05 w
0x~06 o
0x~07 ¥0
0x~08 t
0x~09 h
0x~0a r
0x~0b e
0x~0c e
0x~0d ¥0
0x~00 00
0x~01 ~
0x~02 ~
0x~03 ~
0x~04 04
0x~05 ~
0x~06 ~
0x~07 ~
0x~08 08
0x~09 ~
0x~0a ~
0x~0b ~
s[0]
s[1]
s[2]
s[0] は "one" s[1] は "two"
s[0][0] は 'o' s[0][1] は 'n' s[0][2] は 'e' s[0][3] は '¥0'
s は char* 型で要素数3の配列 s[x] は char* 型 *s[x] は char 型
メモリ上のどこかに配置された 文字列
配列とポインタの初期値と文字列
• 配列とポインタで扱いが異なることに注意
672
pointer_ex9.c void sub() { char s[] = "hello"; char *p = "world"; ...
教科書.pp.235-239., [1]pp.148-153.
objdump の結果 セクション .rdata の内容: ... 403060 776f726c 64007320 3d202225 73220a00 world.s = "%s".. ... void sub(void) { ... char s[] = "hello"; 401196: c7 45 ee 68 65 6c 6c movl $0x6c6c6568,-0x12(%ebp) 40119d: 66 c7 45 f2 6f 00 movw $0x6f,-0xe(%ebp) char *p = "world"; 4011a3: c7 45 f4 60 30 40 00 movl $0x403060,-0xc(%ebp) ...
sへは"hello"の文字コード 68,65,6c,6c,6fが代入されているが pへは.rdataセクションに予め 用意してある文字列"world"の アドレス0x403060が代入されている
.rdataセクションに用意されたデータは 書き変えてはいけない
一般にポインタに初期値として与えた 文字列定数は書き変えてはいけない
配列とポインタの初期値と文字列
• 配列とポインタで扱いが異なることに注意
673
pointer_ex9.c void sub(void) { char s[] = "hello"; char *p = "world"; printf("s = ¥"%s¥"¥n", s); printf("p = ¥"%s¥"¥n", p); s[0] = 'H'; p[0] = 'W'; } int main() { sub(); sub(); return EXIT_SUCCESS; }
教科書.pp.235-239., [1]pp.148-153.
Cygwin + GNU C $ gcc pointer_ex9.c && ./a s = "hello" p = "world" Segmentation fault (コアダンプ)
Borland C++ >bcc32 pointer_ex9.c && pointertest6 Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland pointer_ex9.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland s = "hello" p = "world" s = "hello" p = "World"
sub()が実行された際、 sは毎回"hello"だが pは2回目以降"World"になってしまう もしくは.rdataへの不正な書き込みで 異常終了してしまう
演習
数値を表す文字列を数値に変換する
674
ASCII文字コード表
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI 1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC → ← ↑ ↓ 2 SP ! " # $ % & ' ( ) * + , - . / 3 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 4 @ A B C D E F G H I J K L M N O 5 P Q R S T U V W X Y Z [ ¥ ] ^ _ 6 ` a b c d e f g h i j k l m n o 7 p q r s t u v w x y z { | } ~ DEL 8 9 A B C D E F
上位4ビット
下位4ビット
赤字は制御コード
教科書 p.51., 第2週資料 p.67.
http://ja.wikipedia.org/wiki/ASCII
675
10進法文字列を数値に変換する
• "1234" のような文字列があった時 1234 に変換したい。 • "1234" は char 型の配列に'¥0'で終端された文字コード • {0x31, 0x32, 0x33, 0x34, 0x00} という値を持つ。
• まず、先頭から数字('0'~'9')のみを取り出す • {0x31, 0x32, 0x33, 0x34} を得る
• 各文字コードから 0x30(='0') を引く • {0x31-0x30, 0x32-0x30, 0x61-0x30, 0x62-0x30} より • {1, 2, 3, 4} が得られる
• 次に末尾から𝑛桁目(0 ≦ 𝑛)に10𝑛を掛ける • {1*1000, 2*100, 3*10, 4*1} より • {1000, 200, 30, 4} が得られる
• 各値を加算する • 1000+200+30+4=1234 が得られる
676
16進法文字列を数値に変換する
• "0x12ab" のような文字列があった時 0x12ab=(4779) に変換したい。 • "0x12ab" は char 型の配列に'¥0'で終端された文字コード • {0x30, 0x78, 0x31, 0x32, 0x61, 0x62, 0x00} という値を持つ。
• まず、先頭から16進ヘッダを取り除いて、16進法の数字('0'~'9','a'~'f')のみを取り出す • {0x31, 0x32, 0x61, 0x62} を得る
• 各文字コードのうち'0'~'9'からは 0x30(='0') を引き、'a'~'f'からは 0x61(='a') を引き 0xa(=10) を足す • {0x31-0x30, 0x32-0x30, 0x61-0x61+0xa, 0x62-0x61+0xa} より • {1, 2, 10, 11} が得られる
• 次に末尾から𝑛桁目(0 ≦ 𝑛)に16𝑛を掛ける • {1*4096, 2*256, 10*16, 11*1} より • {4096, 512, 160, 11} が得られる
• 各値を加算する • 4096+512+160+11=4779 が得られる
677
訂正2015-07-17 誤:0x61-0x60+0xa 正:0x61-0x61+0xa
N進法文字列を数値に変換する
• "12ab" のような文字列があった時、これをN進法とみなして数値に変換したい。 • "12ab" は char 型の配列に'¥0'で終端された文字コード • {0x31, 0x32, 0x61, 0x62, 0x00} という値を持つ。
• まず、先頭からN進法の数字('0'~'9','a'~'z')のみを取り出す • {0x31, 0x32, 0x61, 0x62} を得る
• 各文字コードのうち'0'~'9'からは 0x30(='0') を引き、'a'~'f'からは 0x61(='a') を引き 0xa(=10) を足す • {0x31-0x30, 0x32-0x30, 0x61-0x61+0xa, 0x62-0x61+0xa} より • {1, 2, 10, 11} が得られる
• 次に末尾から𝑛桁目(0 ≦ 𝑛)に𝑁𝑛を掛ける • {1*𝑁3, 2*𝑁2, 10*𝑁1, 11*𝑁0} が得られる
• 各値を加算する • 1*𝑁3+2*𝑁2+10*𝑁1+11*𝑁0 が得られる
678
結局10進の場合も、16進の場合も N進の場合で一般化出来る
↓ N進変換のルーチンさえ作れば 全て変換出来る
訂正2015-07-17 誤:0x61-0x60+0xa 正:0x61-0x61+0xa
訂正2015-07-17 誤: {0x30, 0x78, 0x31, 0x32, 0x61, 0x62, 0x00} 正: {0x31, 0x32, 0x61, 0x62, 0x00}
N進整数文字列の一般化
• 文字列による整数の表現は以下のようになるはず • [符号][N進数ヘッダ]N進法表現による文字列
• ここで • 符号:
• '+', '-' の何れかで省略の場合は'+'扱い
• N進法ヘッダ: • 基数が与えられていな場合(基数=0の場合)に利用 • 2,8,16進法に対して"0b","0","0x"の何れかで省略時は10進法扱い
• N進法表現による文字列: • Nを2~36とすると1桁の数値は'0'~'9','a'~'z','A'~'Z'の何れか
の文字
• 例: • 2進法: "0b11"(=3), 8進法: "077"(=63), 10進法: "99",
16進法: "0xff"(=255), 36進法: "zz"(=1295) • 符号の有無: "+0x123", "-0x123", "0x123"
679
N進整数文字列の数値化
• N進整数の文字列sに対して
680
符号の処理
N進法ヘッダの処理
N進整数数値化処理本体
符号があれば読み飛ばし、+1 または -1 を得る
N進法ヘッダがあれば読み飛ばし、基数を得る
N進整数文字列を数値に変換する
N進整数文字列の数値化
• N進整数の文字列sに対して
681
*s は?
s++
s++ 符号は-1
'-'
'+'
その他 符号は+1
符号の処理
何らかの方法で呼び出し元に 決定した符号を返してやる必要がある
何らかの方法で呼び出し元に sの現在位置を返してやる必要がある
戻り値が2つ必要
N進整数文字列の数値化
• N進整数の文字列sに対して
682
N進法ヘッダの処理
*s は?
s++ '0'
その他 10進
*s は?
s++ 'b'
s++ 'x'
8進 その他
基数の指示が ある?
Y
N
16進
2進
strtobase.c はこの部分 strtoi.c では この部分も必要となる
N進整数文字列の数値化
• N進整数の文字列sに対して
683
s++
N倍した変換済み数値に加算
変換済み数値に符号を付加
Y
N
*s を数値化
*s は N進文字
か?
N進整数数値化処理本体
変換済み数値を返して終了
'0'~'9' から 0x30(='0')を引く 'A'~'Z' から 0x41(='A')を引き 0xa(=10)を足す 'a'~'z' から 0x61(='a')を引き 0xa(=10)を足す
ヒント
例えば "0x1234" なら基数 N=16 であり result = 0; result += result * 16 + 4; // result は 0x4 result += result * 16 + 3; // result は 0x34 result += result * 16 + 2; // result は 0x234 result += result * 16 + 1; // result は 0x1234 とすると result に 0x1234 が得られる
訂正2015-07-17 1,2,3,4 の加算順が逆だった(次項参照)
N進整数文字列の数値化
• N進整数の文字列sに対して
684
s++
N倍した変換済み数値に加算
変換済み数値に符号を付加
Y
N
*s を数値化
*s は N進文字
か?
N進整数数値化処理本体
変換済み数値を返して終了
'0'~'9' から 0x30(='0')を引く 'A'~'Z' から 0x41(='A')を引き 0xa(=10)を足す 'a'~'z' から 0x61(='a')を引き 0xa(=10)を足す
ヒント
例えば "0x1234" なら基数 N=16 であり result = 0; result = result * 16 + 1; // result は 0x1 result = result * 16 + 2; // result は 0x12 result = result * 16 + 3; // result は 0x123 result = result * 16 + 4; // result は 0x1234 とすると result に 0x1234 が得られる
訂正2015-07-17 1,2,3,4 の加算順が逆だった(前頁参照)
訂正2015-07-25 誤: result += result * ... 正: result = result * ...
例題: strtosign.c
• 文字列sの先頭1文字を確認し、符号の識別子('-'または'+')の有無に応じて-1,+1の何れかを返す関数 strtosign を作成せよ
• 関数のプロトタイプ宣言はmyfunc_week14.hに作成せよ • strtosign_test.c と共にコンパイルして動作を確認せよ • 引数
• const char *s: 確認する文字列 • char **endp: 未処理の文字列へのポインタを返すために用いる
• 戻り値 • 符号の識別子がある場合'-'なら-1、'+'なら+1、それ以外なら+1をint
型で返す • endp が NULL 以外の時は以下の値を*endpに返す
• 符号の識別子がなかった場合先頭文字(つまりs[0])へのポインタ • 符号の識別子があった場合符号識別子の次の文字(つまりs[1])へのポインタ
685
例題: strtosign.c
• フローチャートから書き起こす
686
strtosign.c int strtosign(const char *s, char **endp) { int sign; switch (*s) { case '-': s++; sign = -1; break; case '+': s++; default: sign = +1; break; } if (endp != NULL) *endp = (char *) s; return sign; }
*s は?
s++
s++ 符号は-1
'-'
'+'
その他 符号は+1
符号の処理
mintty + bash + GNU C $ gcc strtosign_test.c strtosign.c && ./a s = ? -5 s = "-5"(0x22c710) strtosign(s, &endp) = -1 endp = "5"(0x22c711)
例題: strtosign.c
• ヒント:
• *endp には s[0] または s[1] へのポインタが入る
• s[0] へのポインタは s, s[1] へのポインタは、s++ した後の s でも良い
687
mintty + bash + GNU C $ gcc strtosign_test.c strtosign.c && ./a s = ? -5 s = "-5"(0x22c710) strtosign(s, &endp) = -1 endp = "5"(0x22c711)
演習: strtobase.c
• 文字列sの先頭2文字を確認し、0b,0,0xなら2,8,16進数、それ以外なら10進数と判別する関数 strtobase を作成せよ
• 関数のプロトタイプ宣言はmyfunc_week14.hに作成せよ • strtobase_test.c と共にコンパイルして動作を確認せよ • 引数
• const char *s : 確認する文字列 • char **endp : 未処理の文字列へのポインタを返すために用いる
• 戻り値 • 文字列sの先頭2文字に応じて、2,8,10,16の何れかをint型で返す • endp が NULL 以外の時は以下の値を*endpに返す
• 基数識別子(N進数ヘッダ: 0, 0b, 0x)がない場合、先頭文字(つまりs[0])へのポインタ
• 基数識別子がある場合、基数識別子の次の文字(つまりs[1]またはs[2])へのポインタ
688
演習: strtobase.c
• ヒント:
• 前出の「N進法ヘッダの処理」のフローチャートを見てみよう
689
mintty + bash + GNU C $ gcc strtobase_test.c strtobase.c && ./a s = 0x123 s = "0x123"(0x22c710) strtobase(s, &endp) = 16 endp = "123"(0x22c712)
演習: base36toint.c
• 36進法で用いられる'0'~'9','A'~'Z','a'~'z' (文字コード: 0x30~0x39, 0x41~0x5a, 0x61~0x7a) までの文字をint型の数値0~35に変換する関数 base36toint を作成せよ
• 関数のプロトタイプ宣言は myfunc_week14.h に作成せよ • エラーの際、DEBUG マクロが定義されていたら、標準エラー出力に警告
メッセージを表示せよ • base36toint_test.c と共にコンパイルして動作を確認する事 • 引数
• int c : 数値に変換する文字コード
• 戻り値 • cで与えられた文字コードに対応する数値0~35をint型で返す • cが'0'~'9'を0~9,'A'~'Z'と'a'~'z'は共に10~35に変換し、そ
のいずれでもない場合はエラーとなる • エラーの場合は-1を返す
690
mintty + bash + GNU C $ gcc base36toint_test.c base36toint.c && ./a c = ? z 35
演習: base36toint.c
• ヒント:
• cが'0'~'9'である場合、c-'0'とすると、0~9の数値に変換出来る
• cが'a'~'z'の場合、 c-'a'はいくらだろう?
• もし場合分けが面倒なら A~Z と a~z は tolower 関数または toupper 関数 で大文
字か小文字に変換してしまうと大文字小文字の場合分けが必要なくなる
691
mintty + bash + GNU C $ gcc base36toint_test.c base36toint.c && ./a c = ? z 35
演習: base36toint.c
• ヒント:
• デバッグ出力は以下のようにすれば良い
692
mintty + bash + GNU C $ gcc base36toint_test.c base36toint.c -DDEBUG && ./a c = ? @ Warning: in file base36toint.c line 15: invalid value: c = '@'(=0x40) -1
デバッグ出力 #ifdef DEBUG fprintf(stderr, "Warning: in file %s line %d: invalid value: c = '%c'(=%#04x)¥n", __FILE__, __LINE__, c, c); #endif
演習: basetoint.c
• 「0~9,A~Z,a~z」の文字をN進数表現の1桁としてint型の数値に変換する関数 basetointを 作成せよ
• 関数のプロトタイプ宣言はmyfunc_week14.hに作成せよ • エラーの際、DEBUGマクロが定義されていたら、標準エラー出力に警告
メッセージを表示せよ • basetoint_test.cと共にコンパイルして動作を確認する事 • 引数
• int c : 数値に変換する文字コード • int base : N進数表現の基数(つまりN=base)、2~36
• 戻り値 • cで与えられた文字コードに対応する数値0~base-1をint型で返す • 変換結果やbaseの値が範囲外の場合はエラーとなる • エラーの場合は-1を返す
693
mintty + bash + GNU C $ gcc basetoint_test.c basetoint.c base36toint.c && ./a c = ? z base = ? 10 -1
演習: basetoint.c
• ヒント
• base36toint.c を用いると簡単に作成出来る
• base36toint の結果が
• -1 なら 36 進法で使えない数字
• N 以上なら N 進法で使えない数字
694
訂正2015-07-17 誤: N 以下 正: N 以上
演習: strtoi.c
• N進整数を表現した文字列をint型の値に変換する関数 strtoi を作成せよ
• 関数のプロトタイプ宣言はmyfunc_week14.hに作成せよ • strtoi_test.cと共にコンパイルして動作を確認する事 • 引数
• const char *s : 変換する文字列 • char **endp : 未処理の文字列へのポインタを返すために用いる • int base : N進数表現の基数(つまりN=base)、2~36
• 戻り値 • 文字列sを基数baseとして数値に変換した結果をint型で返す • 文字列先頭に符号識別子('-','+')がある場合、変換結果の±に反映
される • baseに0が与えられた場合、文字列先頭に0b,0,0xがあれば、2,8,16進
数、それ以外なら10進数として扱う。 • endp が NULL 以外の時は以下の値を*endpに返す
• 変換出来た最後の文字の次の文字へのポインタ
695
演習: strtoi.c
ヒント:
strtosign.c, strtobase.c, basetoint.c を利用すると比較的簡単に作成できる
696 講義資料 第10週 p.62.から移動
mintty + bash + GNU C $ gcc strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c && ./a s = -0xff base = 0 s = "-0xff"(0x22a6c0) str(s, &endp, base) = -255 endp = ""(0x22a6c5)
演習: strtoi.c
• ヒント
697
s++
N倍した変換済み数値に加算
変換済み数値に符号を付加
Y
N
*s を数値化
*s は N進文字
か?
N進整数数値化処理本体
変換済み数値を返して終了
base36toint(*s) の結果が -1 なら36進文字以外 N 以上なら N 進文字以外
base36toint(*s) の結果を そのまま使える
basetoint(*s, N) の結果が -1 なら N 進文字以外
符号の処理
N進法ヘッダの処理
N進整数数値化処理本体
全体が strtoi
C言語の開発支援ツール
698
分割コンパイル
• ファイルが多くなるとコンパイルが大変
699
mintty + bash + GNU C $ gcc strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c $
cmd + Borland C++ >bcc32 strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland strtoi_test.c: strtoi.c: strtosign.c: strtobase.c: basetoint.c: base36toint.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
一度実行すれば カーソルキーの上下で コマンドの実行履歴から 選べるが、最初が面倒
1ファイルしか変更してないのに 全ファイルコンパイルし直すのは非効率
make コマンド
• 依存関係を記述し必要な処理だけ行う
• Makefile に依存関係と作成方法を記述する
700
Makefile.cygwin.strtoi_test,1 all: strtoi_test.exe strtoi_test.exe: strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c $(CC) -o $@ $^ clean: -rm strtoi_test.exe
Makefile.bcc32.strtoi_test all: strtoi_test.exe strtoi_test.exe: strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c $(CC) strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c clean: -DEL strtoi_test.exe *.obj *.tds
作成するファイル: 材料のファイル ... タブ...→作成方法
make コマンド
• make と打つだけで自動的にコンパイル
701
mintty + bash + GNU C $ make -f Makefile.cygwin.strtoi_test,1 cc -o strtoi_test.exe strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c
cmd + Borland C++ >make -f Makefile.bcc32.strtoi_test MAKE Version 5.2 Copyright (c) 1987, 2000 Borland bcc32 strtoi_test.c strtoi.c strtosign.c strtobase.c basetoint.c base36t oint.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland strtoi_test.c: strtoi.c: strtosign.c: strtobase.c: basetoint.c: base36toint.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
make コマンド
• 依存関係の記述
702
Makefile.cygwin.strtoi_test,2 all: strtoi_test.exe strtoi_test.exe: strtoi_test.o strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o $(CC) -o $@ $^ strtoi_test.o: strtoi_test.c myfunc_week14.h strtoi.o: strtoi.c myfunc_week14.h strtosign.o: strtosign.c myfunc_week14.h strtobase.o: strtobase.c myfunc_week14.h basetoint.o: basetoint.c myfunc_week14.h base36toint.o: base36toint.c myfunc_week14.h clean: -rm strtoi_test.exe strtoi_test.o strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o
作成するファイル: 材料のファイル ... タブ...→作成方法
標準的な作成方法で良い場合は 作成方法は省略出来る
訂正2015-07-25 誤: myfunc_week10.h, myfunc_week10.h, myfunc_week12.h 正: myfunc_week14.h
make コマンド
• 依存関係の解決
703
mintty + bash + GNU C $ make -f Makefile.cygwin.strtoi_test,2 cc -c -o strtoi_test.o strtoi_test.c cc -c -o strtoi.o strtoi.c cc -c -o strtosign.o strtosign.c cc -c -o strtobase.o strtobase.c cc -c -o basetoint.o basetoint.c cc -c -o base36toint.o base36toint.c cc -o strtoi_test.exe strtoi_test.o strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o $ make make: Nothing to be done for 'all'. $ rm strtosign.o $ make cc -c -o strtosign.o strtosign.c cc -o strtoi_test.exe strtoi_test.o strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o
直前に削除したため改めて コンパイルが必要になった ファイルだけ処理し直して てくれている
コンパイルが完了しているので 改めてコンパイルする 必要がなかった
make コマンド
• 依存関係の解決
• 必要に応じて適宜作成方法を実行する
• 例:
• 材料のファイルが不足している場合
• 作成するファイル(旧)、材料のファイル(新)の場合
704
make コマンド
• 同じmakeという名前が付いていて基本は同じだが方言があり、細かい違いがある
• Wikipedia / make
• GNU Make
• Embarcadero / MAKE
• JM / make (1)
• FreeBSD 9.0-RELEASE-K / make (1)
• MSDN / NMAKE Reference
705
その他のビルドツール
• Autotools • autoconf, automake, libtool の総称 • UNIX 系のソフトウェアでは標準的なビルドツール • 以下の標準的な手順でビルド出来るようになる
• 書籍 • GNU AUTOCONF, AUTOMAKE, AND LIBTOOL
https://sourceware.org/autobook/ (無料オンライン版)
• Wikipedia / Autotools
706
mintty + bash + GNU C $ ./configure $ make $ make install
その他のビルドツール
• CMake - http://www.cmake.org/
• マルチプラットフォームな Makefile 作成ツール
• Wikipedia / CMake
707
ライブラリの自作
• 標準ライブラリ関数はアーカイブやライブラリと呼ばれる複数のオブジェクトファイルを1つにまとめたファイルとして提供されている
• cygwinでは/usr/lib/libc.a等
• Borland C++ではC:¥boland¥bcc32¥Lib¥cw32.lib等
• 標準ライブラリはコンパイル時に自動的にリンクされる
• 但し <math.h> 等は -lm 等として明示的に /usr/lib/libm.aをリンクする必要がある場合もある
708 教科書pp.203-206.
ライブラリの自作
• 複数のオブジェクトファイルを1つのファイルにまとめるにはアーカイバやライブラリアンと呼ばれるツールを用いる
• JM / ar (1) • 作成方法:
ar q ライブラリ名 オブジェクトファイル名 ...
• embarcadero / ライブラリマネージャ TLIB.EXE • 作成方法:
tlib ライブラリ名 -+オブジェクトファイル名 ...
709
ライブラリの自作(Cygwin)
• 複数のオブジェクトファイル(.oファイル)を1つの.aファイルにまとめる
• まとめたファイルをアーカイブ(archive)またはスタティックライブラリ(static library)と呼ぶ
710
mintty + bash + GNU C $ gcc -c strtoi.c strtosign.c strtobase.c basetoint.c base36toint.c $ ar q myfunc.a strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o ar: myfunc.a を作成しています $ gcc strtoi_test.c myfunc.a
必要な .o ファイルを myfunc.a から探してリンクし 実行ファイルを作成
複数の .o ファイルをまとめた .a ファイルの作成
ライブラリの自作(Borland C++)
• 複数のオブジェクトファイル(.objファイル)を1つの.libファイルにまとめる
• まとめたファイルをライブラリと呼ぶ
711
cmd + Borland C++ >bcc32 /c strtoi.c strtosign.c strtobase.cbasetoint.c base36toint.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland strtoi.c: strtosign.c: strtobase.c: basetoint.c: base36toint.c: >tlib myfunc.lib -+strtoi.obj -+strtosign.obj -+strtobase.obj -+basetoint.obj -+base36toint.obj TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation >bcc32 strtoi_test.c myfunc.lib Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland strtoi_test.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
必要な .obj ファイルを myfunc.lib から探してリンクし 実行ファイルを作成
複数の .o ファイルをまとめた .lib ファイルの作成
ライブラリの自作
• make によるアーカイブの作成
712
Makefile.cygwin.myfunc.a all: myfunc.a myfunc.a: strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o $(AR) q $@ $^ strtoi_test.o: strtoi_test.c myfunc_week14.h strtoi.o: strtoi.c myfunc_week14.h strtosign.o: strtosign.c myfunc_week14.h strtobase.o: strtobase.c myfunc_week14.h basetoint.o: basetoint.c myfunc_week14.h base36toint.o: base36toint.c myfunc_week14.h clean: -rm myfunc.a strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o
訂正2015-07-25 誤: myfunc_week10.h, myfunc_week10.h, myfunc_week12.h 正: myfunc_week14.h
ライブラリの自作
• make によるアーカイブの作成
713
mintty + bash + GNU C $ make -f Makefile.cygwin.myfunc.a cc -c -o strtoi.o strtoi.c cc -c -o strtosign.o strtosign.c cc -c -o strtobase.o strtobase.c cc -c -o basetoint.o basetoint.c cc -c -o base36toint.o base36toint.c ar q myfunc.a strtoi.o strtosign.o strtobase.o basetoint.o base36toint.o ar: myfunc.a を作成しています
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
714
C言語入門 第15週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
715
乱数
サイコロを振るようにランダムな値を得る
716
乱数: ランダムな数値を得る
• rand 関数 : 疑似乱数整数の生成
• srand 関数 : 疑似乱数系列の初期化
• time 関数 : 現在時刻の取得 毎回違う値を得るため 乱数系列初期化に用いる
717 教科書 p.318, 322.
rand 関数
• int rand(void)
• [0:RAND_MAX]の範囲で整数の疑似乱数を返す
• 戻り値:
• 疑似乱数の整数を返す
• 値の範囲は 0 以上 RAND_MAX 以下
• RAND_MAX は stdlib.h で定義されている
• RAND_MAX は少なくとも 32767 以上である
• RAND_MAX + 1 はオーバーフローするかもしれない
718
JM / rand(3)
教科書 p.322.
srand 関数
• void srand(unsigned int seed)
• 疑似乱数系列の初期化を行う
• 引数:
• seed: 疑似乱数の新しい系列の種 初期値は1
• 同じ種からは毎回同じ疑似乱数系列が生成される。
719
JM / rand(3)
教科書 p.322.
疑似乱数とは?
• 演算で生成する疑似的な乱数
• POSIX 1003.1-2003 で挙げられている実装例
static unsigned long next = 1; /* RAND_MAX を 32767 と仮定 */ int myrand(void) { next = next * 1103515245 + 12345; return((unsigned)(next/65536) % 32768); } void mysrand(unsigned int seed) { next = seed; }
計算式は決まっているので、 同じseedなら 毎回同じ計算になるため 毎回同じ乱数系列が生成される
720
乱数系列の確認
• seed の値で乱数系列がどうなるか確認
721
rand_ex1.c int seed, i; printf("seed = "); scanf("%d", &seed); srand(seed); printf("RAND_MAX: %d¥n", RAND_MAX); for (i = 0; i < 10; i++) { printf("%d¥n", rand()); }
乱数系列の初期化
6 7 8 9
10 11 12 13 14 15 16
Cygwin64 mintty + bash $ gcc rand_ex1.c && ./a seed = 1 RAND_MAX: 2147483647 1481765933 1085377743 1270216262 1191391529 812669700 553475508 445349752 1344887256 730417256 1812158119
seed が同じなら 毎回同じ乱数系列が生成される
乱数の初期化
実行毎に異なる乱数を得る
722
実行毎に異なる乱数系列に初期化
• seed の値で乱数系列がどうなるか確認
723
rand_ex2.c int i; srand(time(NULL)); printf("RAND_MAX: %d¥n", RAND_MAX); for (i = 0; i < 10; i++) { printf("%d¥n", rand()); }
6 7 8 9
10 11 12 13
Cygwin64 mintty + bash $ gcc rand_ex2.c && ./a RAND_MAX: 2147483647 408068090 654635880 1819541412 1080013827 1356279002 1536746152 352225876 1197042546 1830476305 459739427
毎回 seed が異なるため 毎回違う乱数系列が生成される
time関数は 現在時刻を返す関数
time 関数
• time_t time(time_t *t)
• 現在時刻を UNIX time で得る
• 引数:
• t: 通常はNULLで良い NULLでない場合*tにも戻り値を格納する
• 戻り値:
• 現在時刻を UNIX time で返す。
724
JM / time(2)
教科書 p.318.
UNIX time (UNIX時間、UNIX時刻)
• UNIX epoch (UNIX 紀元)
• 1970-01-01 00:00:00 UTC
• UNIX time
• UNIX epoch からの経過秒数
• 2038年問題
• 2038-01-19 03:14:07 UTC = UNIX time: 2,147,483,647秒 = UNIC time: 0x7fffffff秒
• time_tが符号付き32bitの環境でオーバーフロー
725
EppochConverter
2038年問題
• 2038-01-19 03:14:07 UTC = UNIX time: 2,147,483,647秒 = UNIC time: 0x7fffffff秒
• time_t が符号付き 32bit の環境
• time_t がオーバーフロー
• 以降、正しい日時が処理できなくなる!
• 対策
• time_t の 64bit 化等の対応が必要
726
未対策の環境はあるのか?
• SOURCEFORGE.JP MAGAZINE / 2014-05-02: 2038年問題に対応した「OpenBSD 5.5」リリース
• http://sourceforge.jp/magazine/14/05/02/160000
• OpenBSD はセキュリティ面で非常に定評のある OS
• そんな OS でもつい最近になってようやく対応している状況もある。
727
time_t の確認
• 0x7fffffff秒,0x80000000秒,-1秒を確認
time_t_test.c char buf[1024]; time_t t = 0x7fffffff; struct tm *tm; printf("sizeof(time_t): %d¥n", sizeof(time_t)); printf("time_t has sign: %s¥n", (~(time_t) 0) < (time_t) 0 ? "YES" : "NO"); tm = gmtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); printf("%20luUL: %11ldL: %s¥n", (unsigned long) t, (long) t, buf); t++; tm = gmtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); printf("%20luUL: %11ldL: %s¥n", (unsigned long) t, (long) t, buf); t = -1; tm = gmtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); printf("%20luUL: %11ldL: %s¥n", (unsigned long) t, (long) t, buf);
728
strftime 関数
• size_t strftime(char *s, size_t max, const char *format, const struct tm *tm) • 日付と時刻を文字列に変換する
• 引数: • s: 変換結果の格納先(通常はchar型配列) • max: sのサイズ • format: 変換の書式 • tm: time_t 型の値をlocaltime関数または
gmtime関数を用いて変換した日付と時刻情報
• 戻り値: • 終端文字列'¥0'を含めた変換結果のサイズ • 格納先のサイズが不足していた場合は0
729
JM / strftime(3) JM / ctime(3)
各環境のtime_tの状況
730
Cygwin64 + GNU C $ gcc time_t_test.c && ./a sizeof(time_t): 8 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: 2147483648L: 2038-01-19 03:14:08 UTC 18446744073709551615UL: -1L: 1969-12-31 23:59:59 UTC
Cygwin32 + GNU C
$ gcc time_t_test.c && ./a sizeof(time_t): 4 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 1901-12-13 20:45:52 UTC 4294967295UL: -1L: 1969-12-31 23:59:59 UTC
各環境のtime_tの状況
731
Borland C++ 5.5.1 >bcc32 time_t_test.c && time_t_test Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland time_t_test.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland sizeof(time_t): 4 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 2038-01-19 03:14:08 UTC 4294967295UL: -1L: 2106-02-06 06:28:15 UTC
各環境のtime_tの状況
Visual Studio 2013 Express Desktop Windows 32bit版 >cl time_t_test.c && time_t_test Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86 Copyright (C) Microsoft Corporation. All rights reserved. time_t_test.c Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:time_t_test.exe time_t_test.obj sizeof(time_t): 8 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 2038-01-19 03:14:08 UTC 4294967295UL: -1L: 1969-12-31 23:59:59 UTC
VC は long が 32bit だったので 64bit 表示出来てない点には注意
732
各環境のtime_tの状況
Visual Studio 2013 Express Desktop Windows 64bit版 >cl time_t_test.c && time_t_test Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64 Copyright (C) Microsoft Corporation. All rights reserved. time_t_test.c Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:time_t_test.exe time_t_test.obj sizeof(time_t): 8 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 2038-01-19 03:14:08 UTC 4294967295UL: -1L: 1969-12-31 23:59:59 UTC
VC は long が 32bit だったので 64bit 表示出来てない点には注意
733
乱数の範囲調整
任意の範囲の乱数を得る
734
[0:1) の実数の乱数生成法
• rand(): [0:RAND_MAX]の整数の乱数を生成
• [0:1) を得るには?
• 実数にして RAND_MAX + 1 で割れば良い
• RAND_MAX って幾つ?
• RAND_MAX + 1 だとオーバーフローするかも?
• RAND_MAX + 1.0 なら大丈夫
#define frand() (rand() / (RAND_MAX + 1.0))
[1] p.205.
暗黙の算術変換により 全てdoubleに型変換されて 計算される。
735
[0:N-1]の整数の乱数生成法
• [0:1) の実数の乱数を生成してNを掛けた後 整数に変換する
int x; x = frand() * N;
なぜ以下の計算方法では駄目か? x = rand() / RAMD_MAX * N; x = rand() / RAMD_MAX * (N – 1); x = frand() * (N – 1); ヒント: • 生成される値の範囲は? • N が出る確率は?
[0:N-1] の整数の乱数 = [0:N) の整数の乱数
736
N面体のサイコロ
• [1:N] の整数が等確率で欲しい
int x; x = frand() * N + 1;
[1:N] の整数の乱数 = [0:N-1] + 1 の整数の乱数 = [0:N) + 1 の整数の乱数
737
教科書の例
• 実は間違っている
738
test_p322.c #include <stdio.h> #include <stdlib.h> #include <time.h> #define RANGE_MIN 0 #define RANGE_MAX 10 void main() { int rand10; // 0以上未満 srand( (unsigned)time(NULL) ); rand10=(int)(((double) rand() / (double) RAND_MAX) * RANGE_MAX + RANGE_MIN); printf("求まった乱数は %d¥n", rand10); }
0~9 までは (RAND_MAX / 10) / (RAND_MAX + 1) の確率で出現するので 0以上10以下の乱数を意図したとしても 出現確率のバランスが悪い
rand() は 0 以上 RAND_MAX 以下 の値を返すので、この実装では 1/(RAND_MAX+1) の確率で 10 が出現してしまう
ここのコメントも おかしいが、 0以上10未満でも 0以上10以下でも やってはいけない実装
教科書 p.322.
乱数に関してよく見られる 有名な間違いです。
もっと質の良い疑似乱数
• random 関数 (POSIX.1-2001.)
• 非線形加法フィードバック
• JM / random(3)
• drand48 関数 (POSIX.1-2001.)
• 線形合同法+48bit整数
• JM / drand48(3)
• メルセンヌツイスタ
• Wikipedia / メルセンヌツイスタ
739
ファイル操作
ファイルに対する入出力
740
標準入出力と標準エラー出力
• 以下の入出力が利用できる • stdin: STanDard INput: 標準入力
• stdout: STanDard OUTput: 標準出力
• stderr: STanDard ERRor output: 標準エラー出力
• scanf や getchar 等は stdin から入力している
• printf や putchar 等は stdout へ出力している
• stdin, stdout はパイプやリダイレクトの対象だが stderr は標準では対象外
[1] pp.196, 199, 218. 741
標準入出力と標準エラー出力
• パイプやリダイレクトで処理されたくない内容は stderr へ出力する
• fscanf や fprintf を使うと、入出力先を自由に選択出来る
[1] pp.196, 199, 218. 742
stdiotest.c printf("output to stdout with printf¥n"); fprintf(stdout, "output to stdout with fprintf¥n"); fprintf(stderr, "output to stderr with fprintf¥n");
mintty + bash $ ./stdiotest > redirect.txt output to stderr with fprintf $ cat redirect.txt output to stdout with printf output to stdout with fprintf
標準入出力と標準エラー出力
• stdin,stdout,stderrはstdio.hで定義されている
• stdio.h は standard input / output header
[1] pp.196, 199, 218. 743
fprintf 関数
• int fprintf(FILE *fp, const char *FORMAT, ...);
• printfの結果をfpへ書き出す
• 引数: • fp: FILE 構造体へのポインタ
• FORMAT: 書式
• ...: 任意の数の引数
• 戻り値: • 書き出された文字数
• エラーの場合負の数
教科書 pp.61, 64-66, 98, 300.
参考: [1] pp.305-306.
744
fscanf 関数
• int fscanf(FILE *fp, const char *FORMAT, ...); • fpからデータを読み込む
• 引数: • fp: FILE 構造体へのポインタ • FORMAT: 書式 • ...: 任意の数の引数
値を格納する変数へのポインタ
• 戻り値: • 変換され代入された入力項目の数 • ファイル終端またはエラーの場合EOF
教科書 pp.80-83, 254.
参考: [1] pp.307-309.
745
fopen 関数
• FILE *fopen(const char *filename, const char *mode); • ファイルを開き、FILE構造体へのポインタを得る
• 引数:
• filename: ファイル名(パス)の文字列
• mode: ファイルを開くモード
• 戻り値:
• FILE 構造体へのポインタ
• エラーの場合 NULL
教科書 pp.298-305.
参考: [1] pp.194-198.
746
fopen 関数の mode
mode 読み込み 書き込み 動作
"r" 任意の位置 × ファイルを開く、存在しない場合エラー
"w" × 任意の位置 ファイルを作成し、前の内容は消去する
"a" × ファイル末尾 ファイルを開く、または作成
"r+" 任意の位置 任意の位置 ファイルを開く、存在しない場合エラー
"w+" 任意の位置 任意の位置 ファイルを作成し、前の内容は消去する
"a+" 任意の位置 ファイル末尾 ファイルを開く、または作成
教科書 pp.298-305.
参考: [1] pp.194-198.
747
"r", "w", "a", "r+", "w+", "a+" はテキストモードで読み書きする テキストモードでは改行コード(¥n)の扱いが環境によって異なる • Windows: CR LF (0xd 0xa) • Mac: CR (0xd) • UNIX: LF (0xa) バイナリモードにするには、"rb", "wb", "ab", "r+b", "w+b", "a+b" のように "b" を追加する
fclose 関数
• int fclose(FILE *fp);
• ファイルを閉じます
• 引数:
• fp: FILE構造体へのポインタ
• 戻り値:
• 0 を返す
• エラーの場合 EOF を返す
教科書 pp.298-305.
参考: [1] pp.194-198.
748
ファイル入力の例
749
fprintf_ex1.c #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; // ファイル入出力用のポインタ int value = 123; fp = fopen("sample.txt", "w"); // 書き込みモードでファイルを開く if (fp == NULL) { // エラー処理 fprintf(stderr, "Error: fopen: sample.txt¥n"); exit(EXIT_FAILURE); } fprintf(fp, "%d¥n", value); // fp に value の値を出力 fclose(fp); // 使い終わったファイルを閉じる return EXIT_SUCCESS; }
ファイル出力の例
750
fscanf_ex1.c #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; // ファイル入出力用のポインタ int value; fp = fopen("sample.txt", "r"); // 読み込みモードでファイルを開く if (fp == NULL) { // エラー処理 fprintf(stderr, "Error: fopen: sample.txt¥n"); exit(EXIT_FAILURE); } fscanf(fp, "%d", &value); // fp から符号付き整数の文字列を読み込む printf("%d¥n", value); // 読み込んだ値を表示 fclose(fp); // 使い終わったファイルを閉じる return EXIT_SUCCESS; }
テスト
プログラムの動作を検証する
751
ユニットテスト(単体テスト)
• 標準ライブラリヘッダ <assert.h>
• assert マクロの利用
• ユニットテストツールの利用
• CUnit http://cunit.sourceforge.net/
752 教科書pp.188-189.
CUnit のインストール (cygwin) apt-cyg install CUnit
assert マクロ
void assert(int expression) • expression がゼロの場合以下のメッセージを stderr に出力し abort する
Assertion failed: expression, file filename, line nnn
• <assert.h>をインクルードする時点で NDEBUG マクロが定義されていると assert マクロは無視される
• ユニットテストだけでなくデバッグ時のみ有効にする不正値のチェック等でも利用される
753
JM / assert(3)
assert マクロ
• 専用のテストルーチンで使用した例
754
is_leap_year_assert.c void test_leap_year() { assert(is_leap_year(-400) == 1); assert(is_leap_year(- 56) == 1); assert(is_leap_year(- 4) == 1); assert(is_leap_year( 0) == 1); assert(is_leap_year( 4) == 1); assert(is_leap_year( 56) == 1); assert(is_leap_year( 400) == 1); assert(is_leap_year(1996) == 1); assert(is_leap_year(2000) == 1); assert(is_leap_year(2004) == 1); }
is_leap_year_assert.c void test_normal_year() { assert(is_leap_year(-300) == 0); assert(is_leap_year(-200) == 0); assert(is_leap_year(-100) == 0); assert(is_leap_year(- 3) == 0); assert(is_leap_year(- 2) == 0); assert(is_leap_year(- 1) == 0); assert(is_leap_year( 1) == 0); assert(is_leap_year( 2) == 0); assert(is_leap_year( 3) == 0); assert(is_leap_year( 100) == 0); assert(is_leap_year( 200) == 0); assert(is_leap_year( 300) == 0); assert(is_leap_year(1900) == 0); assert(is_leap_year(1997) == 0); assert(is_leap_year(1998) == 0); assert(is_leap_year(1999) == 0); assert(is_leap_year(2001) == 0); assert(is_leap_year(2002) == 0); assert(is_leap_year(2003) == 0); }
is_leap_year_assert.c int main() { test_leap_year(); test_normal_year(); return EXIT_SUCCESS; }
assert マクロ
• 専用のテストルーチンで使用した例
• エラーがなければ何も起きない
• エラーがあるとそこで実行が中断する
755
mintty + bash + GNU C
mintty + bash + GNU C
$ gcc is_leap_year_assert.c is_leap_year_func_ex4_2.c && ./a
$ gcc is_leap_year_assert.c is_leap_year_func_practice1.c && ./a assertion "is_leap_year(-300) == 0" failed: file "is_leap_year_assert.c", line 21, function: test_normal_year Aborted (コアダンプ)
CUnit
• 専用のテストルーチンを作成して使用
756
is_leap_year_cunit.c void test_leap_year() { CU_ASSERT_EQUAL(is_leap_year(-400), 1); CU_ASSERT_EQUAL(is_leap_year(- 56), 1); CU_ASSERT_EQUAL(is_leap_year(- 4), 1); CU_ASSERT_EQUAL(is_leap_year(1996), 1); CU_ASSERT_EQUAL(is_leap_year(2000), 1); CU_ASSERT_EQUAL(is_leap_year(2004), 1); }
is_leap_year_cunit.c void test_normal_year() { CU_ASSERT_EQUAL(is_leap_year(-300), 0); CU_ASSERT_EQUAL(is_leap_year(-200), 0); CU_ASSERT_EQUAL(is_leap_year(-100), 0); CU_ASSERT_EQUAL(is_leap_year(- 3), 0); CU_ASSERT_EQUAL(is_leap_year(2002), 0); CU_ASSERT_EQUAL(is_leap_year(2003), 0); }
is_leap_year_cunit.c static CU_TestInfo test_is_leap_year[] = { {"leap year", test_leap_year}, {"normal year", test_normal_year}, CU_TEST_INFO_NULL, }; static CU_SuiteInfo suites[] = { {"is_leap_year test", NULL, NULL, test_is_leap_year}, CU_SUITE_INFO_NULL, }; int main() { CU_initialize_registry(); CU_register_suites(suites); CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_cleanup_registry(); return EXIT_SUCCESS; }
CUnit
• 専用のテストルーチンを作成して使用
• ユニットテストの達成状況がレポートされる
757
mintty + bash + GNU C $ gcc is_leap_year_cunit.c is_leap_year_func_ex4_2.c -lcunit && ./a CUnit - A unit testing framework for C - Version 2.1-2 http://cunit.sourceforge.net/ Suite: is_leap_year test Test: leap year ...passed Test: normal year ...passed Run Summary: Type Total Ran Passed Failed Inactive suites 1 1 n/a 0 0 tests 2 2 2 0 0 asserts 29 29 29 0 n/a Elapsed time = 0.000 seconds
テストの通過状況の 統計が表示される
CUnit
758
mintty + bash + GNU C $ gcc is_leap_year_cunit.c is_leap_year_func_practice1.c -lcunit && ./a CUnit - A unit testing framework for C - Version 2.1-2 http://cunit.sourceforge.net/ Suite: is_leap_year test Test: leap year ...passed Test: normal year ...FAILED 1. is_leap_year_cunit.c:21 - CU_ASSERT_EQUAL(is_leap_year(-300),0) 2. is_leap_year_cunit.c:22 - CU_ASSERT_EQUAL(is_leap_year(-200),0) 3. is_leap_year_cunit.c:23 - CU_ASSERT_EQUAL(is_leap_year(-100),0) 4. is_leap_year_cunit.c:30 - CU_ASSERT_EQUAL(is_leap_year( 100),0) 5. is_leap_year_cunit.c:31 - CU_ASSERT_EQUAL(is_leap_year( 200),0) 6. is_leap_year_cunit.c:32 - CU_ASSERT_EQUAL(is_leap_year( 300),0) 7. is_leap_year_cunit.c:33 - CU_ASSERT_EQUAL(is_leap_year(1900),0) Run Summary: Type Total Ran Passed Failed Inactive suites 1 1 n/a 0 0 tests 2 2 1 1 0 asserts 29 29 22 7 n/a Elapsed time = 0.000 seconds
テストの通過状況の 統計が表示される
変更箇所の管理
比較、差分、パッチ、バージョン管理等
759
diff のインストール
• mintty+bash から以下のコマンドを実行
760
mintty + bash apt-cyg install diff
diff
• UNIX系のファイル比較コマンド
761
mintty + bash $ diff is_leap_year_func_practice1.c is_leap_year_func_ex4_2.c 5c5 < return year % 4 == 0; --- > return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
上記の例では ファイル間の相違点を 一覧として表示している
diff
• 並列表示
• --side-by-sideオプションによる比較
762
mintty + bash $ diff is_leap_year_func_practice1.c is_leap_year_func_ex4_2.c --side-by-side #include "is_leap_year_func.h" #include "is_leap_year_func.h" int is_leap_year(int year) int is_leap_year(int year) { { return year % 4 == 0; | return (year % 4 == 0 && year % 100 != 0) || year % 400 == } }
fc
• Windows 標準添付のファイル比較コマンド
763
コマンドプロンプト >fc is_leap_year_func_practice1.c is_leap_year_func_ex4_2.c ファイル is_leap_year_func_practice1.c と IS_LEAP_YEAR_FUNC_EX4_2.C を比較しています ***** is_leap_year_func_practice1.c { return year % 4 == 0; } ***** IS_LEAP_YEAR_FUNC_EX4_2.C { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } *****
WinMerge
• GUIによるテキストファイルの比較ツール
• 相違点の合成も出来る • http://www.forest.impress.co.jp/library/software/winmerge/
764
UNIX 環境の GUI 版の比較ツールだと meld や tkdiff 等がある
Rekisa
• GUIによる複数テキストファイルの比較ツール • http://www.forest.impress.co.jp/library/software/rekisa/
765
差分とパッチ
• diff : 差分比較、パッチ作成ツール
• JM / diff (1)
• Wikipedia / diff
• patch : 差分適用(パッチ適用)ツール
• JM / patch (1)
• Wikipedia / patch
766
単一ファイルのパッチの作成 $ diff -c myfile.orig myfile > myfile.patch
パッチの適用 $ patch < myfile.patch
ディレクトリ以下のパッチの作成 $ diff -crN mydir.orig mydir > mydir.patch
カレントディレクトリへのパッチの適用 $ patch -p0 -d. < mydir.patch
ファイル間の差異を パッチ(=絆創膏)として取り出し 適用することで変更箇所を反映させる
バージョン管理ツール
• RCS • Wikipedia / Revision Control System
• CVS • Wikipedia / Concurrent Version System
• Subversion • Wikipedia / Apache Subversion
• Mercurial • Wikipedia / Mercurial
• Bazaar • Wikipedia / Bazaar
• git • Wikipedia / git
767
github の登場で 人気になっている
過去の改変の記録を残したり 複数人で共同で作業する際に 役立つ
テキスト画面の簡易制御
tty_getchar.c
768
getchar 関数
• int getchar(void) • 入力 stream から1文字読み込む
• stream というのはバッファのようなもの
• 通常はENTERが押されるまで入力streamには値が入って来ない。入力ストリームに値がない場合は値が入ってくるまで待機する
• 戻り値: • 入力された文字の文字コード返す
• ファイル終端やエラーの場合はEOFを返す
769
JM / fgetc(3)
getchar 関数の動作
• ENTERが押されるまで一気に読み込む
770
getchartest.c #include <stdio.h> #include <stdlib.h> int main() { int c; while ((c = getchar()) != EOF) { printf("%#04x¥n", c); } return EXIT_SUCCESS; }
バッファリングと言います。 読み込み処理を 高速化するための仕組みです。
バッファリングに溜めてある 入力文字を1文字ずつ取り出します。 バッファが空になると ENTERが押されるまで 入力待ちの状態になります。
tty_getkey
• ENTER待ちなしのキーボード入力
771
tty_getkey_ex1.c #include "tty_getkey.h" #include "msleep.h" int main() { int c; tty_begin(); // 開始処理 while(tty_iskeyhit() == 0) { // 打鍵待ちループ msleep(1); // CPU に負荷をかけずに 1 msec 待つ // tty_ishitkey() は即座に値を返すので空ループだと CPU に負荷がかかる } c = tty_getkey(); // 打鍵キーの取得 tty_printf("%#x key was hit.¥n", c); // tty 用の printf tty_printf("Hit ESC key to exit.¥n"); while(KEY_ESC != tty_getkey()) { // 打鍵待ちループ ; // tty_ketkey() はキー入力があるまで待機するため空ループでも CPU に負荷をかけない } tty_end(); // 終了処理 return EXIT_SUCCESS; }
tty_getkey
• ENTER待ちなしのキーボード入力
• Windows 系の環境
• conio.h ライブラリを利用
• embarcadero / RAD Studio / conio.h
• MSDN / Console and Port I/O
• UNIX 系の環境
• curses ライブラリを利用
• http://ja.wikipedia.org/wiki/Curses
772
tty_getkey を利用したプログラムの コンパイル
• サンプルプログラム
• tty_getkey_ex1.c : サンプルプログラム本体
• 必要なファイル
• msleep.h : ミリ秒 sleep 用ヘッダ
• tty_getkey.h : tty_getkey ヘッダファイル
• tty_getkey.c : tty_getkey 本体
773
mintty + bash + GNU C $ gcc tty_getkey_ex1.c tty_getkey.c -lcurses
コマンドプロンプト + Borland C++ >bcc32 tty_getkey_ex1.c tty_getkey.c
gcc では -lcurses オプションが必要 これには ncurses ライブラリが必要
tty_getkey 利用前の準備 Cygwin の場合
• ncurses の開発用ライブラリが必要
• 以下のコマンドを入力してインストール
• Borland C++ では、標準添付の conio というライブラリを使っているので前準備は不要
774
Cygwin64 mintty + bash apt-cyg install libncursesw-devel
Cygwin32 mintty + bash apt-cyg install libncurses-devel
Cygwinが何bit版か確認する方法
• uname コマンドに -a オプションを付けて実行
775
Cygwin64 mintty + bash $ uname -a CYGWIN_NT-6.1 EX58EXTREME 1.7.27(0.271/5/3) 2013-12-09 11:54 x86_64 Cygwin
Cygwin32 mintty + bash $ uname -a CYGWIN_NT-6.1-WOW64 EX58EXTREME 1.7.27(0.271/5/3) 2013-12-09 11:57 i686 Cygwin
i686 なら 32bit 版
x86_64 なら 64bit 版
tty_getkey 初期化関数
• int tty_begin(void)
• tty_getkey の初期化処理を行います
• int tty_end(void)
• tty_getkey の終了処理を行います
776
tty_getkey キー待ち受け関数
• int tty_iskeyhit(void)
• キー入力の有無を調べます。
• キー入力があれば 1 なければ 0 を返します。
• int tty_getkey(void)
• キー入力を取得します。キー入力がない場合、キー入力が発生するまで待機します。
• 通常のキーは'a'や'A'等の文字コードを返します。
• 特殊キーの場合はKEY_UPやKEY_DOWN等のマクロで定義されたキーコードを返します。
777
tty_getkey() が返すキーコード
• KEY_INSERT
• KEY_DELETE
• KEY_HOME
• KEY_END
• KEY_PAGEUP
• KEY_PAGEDOWN
• KEY_UP
• KEY_DOWN
• KEY_LEFT
• KEY_RIGHT
• KEY_ESC
• KEY_TAB
• KEY_SPACE
• KEY_BS
• KEY_ENTER
• KEY_F1 ~ KEY_F48
通常のキーは 'a', 'A' 等の文字定数リテラルが対応
778
tty_getkey 出力関数
• int tty_printf(char *fmt, ....)
• 書式付の出力を行います。
• 画面制御を伴うためtty_begin()~tty_end()の間では、通常のprintfは使わないでください。
• int tty_setxy(int x, int y)
• カーソルの座標を(x,y)に移動します。
779
tty_getkey 画面情報関数
• int tty_getx(void)
• カーソルの x 座標を返します。
• int tty_gety(void)
• カーソルの y 座標を返します。
• int tty_getw(void)
• カーソルが移動可能な画面の幅を返します。
• int tty_geth(void)
• カーソルが移動可能な画面の高さを返します。
780
tty_getkey_ex2.c
• カーソルキーで移動、ESC キーで終了
• 移動した場所に * を表示する
781
tty_getkey_ex2.c
782
tty_getkey_ex2.c while (KEY_ESC != (c = tty_getkey())) { switch (c) { case KEY_UP: y--; break; case KEY_DOWN: y++; break; case KEY_RIGHT: x++; break; case KEY_LEFT: x--; break; } x = x < 1 ? 1 : w - 2 < x ? w - 2 : x; y = y < 1 ? 1 : h - 2 < y ? h - 2 : y; tty_setxy(0, 0); tty_printf("(%2d,%2d) : %#06x", x, y, c); tty_setxy(x, y); tty_printf("*"); }
入力された カーソルキーの方向に応じて 座標を上下左右に移動
画面から はみ出さないように 移動範囲を制限
tty_getkey_ex3.c
• 6面体サイコロの例
• 開始するとサイコロが転がり始める
• 何かキーを押すと3秒待って終了する
783
tty_getkey_ex3.c while(tty_iskeyhit() == 0) { d = frand() * 6 + 1; tty_setxy(0, 0); tty_printf("%d", d); msleep(1); } msleep(3000);
値域 [1:6] の乱数生成 = 6面体サイコロ
総合実習
プログラムで遊んでみる
784
tetris.c
• テトリスの簡易版
• 操作方法
• 移動: ← →
• 落下: ↓
• 回転: z x SPACE
• 修了: ESC
785
mintty + bash + GNU C $ gcc tetris.c tty_getkey.c -lcurses && ./a
コマンドプロンプト + Borland C++ >bcc32 tetris.c tty_getkey.c && tetris
mintty + bash + GNU C ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c
• フィールド
786
描画用フィールド field[0][y][x]
固定ブロック用フィールド field[1][y][x]
浮動ブロック b[y][x]
ブロック形状 block[spec][y][x]
仮り配置
選択して回転
落下したら固定
描画用フィールドにコピー
tetris.c の改造
• 以下の改造を考えてみよう
• 「p」でポーズ/解除する
• 点数を表示する
• 次のブロックをランダムに決める
• 次のブロックを表示する
• 上まで積み上がったら ゲームオーバーにする
• ハイスコアを記録・表示してみる
787
mintty + bash + GNU C ## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++--------++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++--------++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c の改造 ヒント
• 「p」でポーズ/解除する
• ポーズフラグ用の変数が必要
• 「p」キーでポーズフラグを反転する
• ポーズフラグがONなら continue してループを先頭からやり直す
788
フラグの反転 // 以下の処理はいずれも // pause == 0 なら 1 // pause != 0 なら 0 // となる pause = pause ? 0 : 1; pause = !pause; pause = pause == 0;
tetris.c の改造 ヒント
• 点数を表示する
• 点数用の変数が必要
• ブロックの落下や 1列消した場合に スコアを加算する
• 適当な位置にスコアを表示する
• 位置調整は tty_setxy()
• 表示は tty_printf()
789
mintty + bash + GNU C ## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++--------++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++--------++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
例えばこの位置に表示するなら tty_printf() の前に tty_setxy(W*2+2,1); を入れる
tetris.c の改造 ヒント
• 次のブロックをランダムに決める
• spec の値を乱数で決定すれば良い
• 前述の frand() マクロを使うと [0:7) の乱数は frand()*7 で得られる
790
tetris.c の改造 ヒント
• 次のブロックを表示する • 次のブロックを記憶するための変数が必要(※1) • ※1を使って spec を更新する • spec を更新後 ※1 も更新する • ※1 を適当な位置に表示する
• 表示位置は tty_setxy() で 調整する
• 枠は "++--------++" と "||" 以外でも 好きな文字を使えば良い
• block[※1][y][x] の値に応じて " " または "##" を表示する
791
mintty + bash + GNU C ## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++--------++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++--------++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c の改造 ヒント
• 上まで積み上がったらゲームオーバーにする
• y <= 0 でブロックが固定されたら、上まで積み上がっている
• ゲームオーバーになるとどうするか?
• GAME OVER と表示する?
• その後、再度ゲームをスタートするか?プログラムを終了するか?
• 一般に流通しているゲームはどうしているか参考にすると良い?
792
tetris.c の改造
• ハイスコアを記録・表示してみる
• ハイスコア用の変数が必要
• ハイスコアは画面の適当な場所に表示する
• スコアがハイスコアを超えたらハイスコアを更新する必要がある
• ハイスコアはファイルに保存し、ゲーム開始時に読み込み終了時に保存する必要がある
• fopen, fclose でファイルのオープン、クローズが行える
• fprintf, fscanf で値の読み書きが行える
793
まとめ
やったこと、やらなかったこと
794
この授業で扱った事
• C言語の基礎
• 演算、式、変数、制御構造、関数、ポインタ等
• 簡単なプログラムの作成
• 奇数偶数の判定
• 閏年の判定
• 行列の演算
• シーザー暗号
• 等々
この授業で扱わなかったこと
• 構造体と共用体: struct, union
• 型定義: typedef
• 可変長引数: <stdarg.h>
• va_list, va_start, va_arg, va_end
• 非局所ジャンプ: <setjmp.h>
• シグナル処理: <signal.h>
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
797
おしまい
おつかれさまでした。
来週は試験です。しっかり復習しておきましょう。
お
か
だ