構造体を使ったプログラム例
今日はテスト前で、構造体の全体的な説明も終わり、演習の時間。 以下のようなオブジェクト指向の考え方を取り入れた、 構造体ポインタ渡しのスタンダードなプログラムを示す。
#include <stdio.h> #define SIZE 10 struct Person { char name[ 20 ] ; int age ; } ; int read_Person( struct Person* p ) { return scanf( "%s%d" , p->name , &(p->age) ) == 2 ; } void print_Person( struct Person* p ) { printf( "%s %d\n" , p->name , p->age ) ; } int main() { int i , size ; struct Person table[ SIZE ] ; // データの入力処理 for( i = 0 ; i < SIZE ; i++ ) { if ( !read_Person( &( table[i] ) ) ) break ; } size = i ; // データの出力処理 for( i = 0 ; i < size ; i++ ) print_Person( &( table[i] ) ) ; return 0 ; }
ファイル入出力にも慣れてもらおう
#include <stdio.h> #define SIZE 10 struct Person { char name[ 20 ] ; int age ; } ; int read_Person( struct Person* p , FILE* fp ) { return fscanf( fp , "%s%d" , p->name , &( p->age ) ) == 2 ; } void print_Person( struct Person* p , FILE* fp ) { fprintf( "%s %d\n" , p->name , p->age ) ; } int main() { int i , size ; struct Person table[ SIZE ] ; FILE* fp_in ; if ( (fp_in = fopen( "data.txt" , "rt" )) != NULL ) { for( i = 0 ; i < SIZE ; i++ ) if ( ! read_Person( &( table[i] ) , fp_in ) ) { size = i ; break ; } for( i = 0 ; i < size ; i++ ) print_Person( &( table[i] ) , stdout ) ; } return 0 ; }
オブジェクト指向っぽく
#include <stdio.h> #define SIZE 10 class Person { private: char name[ 20 ] ; int age ; public: int read() { return scanf( "%s%d" , name , &age ) == 2 ; } void print() { printf( "%s %d\n" , name , age ) ; } } ; int main() { int size ; Person table[ SIZE ] ; for( int i = 0 ; i < SIZE ; i++ ) if ( !table[i].read() ) { size = i ; break ; } for( int i = 0 ; i < size ; i++ ) table[i].print() ; return 0 ; }
1年電気回路演習でアクティブラーニングを試す
いつもプログラミングを教えている中、 超久々に高専1年生の電気回路入門?な授業を担当中。 キルヒホッフの法則の話をしようと思った所が、 以外と合成抵抗でも悩む学生さんが多い。
いつもは毎回授業の最後に簡単な問題を解いて 自己採点して提出してもらってるけど、 私の説明以外の方法で(正しい解き方なのに)☓としている人がいる。 電気回路なんて解き方が色々ある…ってことを感じて欲しいので、 試しに「アクティブラーニング」的な方法を試してみた。
グループ毎に解答の確認と解き方の説明を考えてもらい、 グループ代表に前で解説してもらった後、 他のグループの人がもっといい説明あれば、前で説明してもらう。
久々の1年担当で戸惑っているけど、 前での説明を工夫してくれたりしていて、 意外と面白かった。 ただ、理解の速い子には待ち時間が多かったので いつも….とはいかないかな。
安全な入力とdefineマクロ
ファイル処理の最後の説明で、バッファ・オーバーフローと、安全な入力について説明。
安全な入力
一般的なC言語での文字列入力のプログラムは、以下の様なものがテキストにも書かれている。
// memory // [局所変数str][戻り番地][..........] void foo() { char str[ 10 ] ; scanf( "%s" , str ) ; }
バッファオーバーフロー
しかし、こういった処理は極めて危険である。 入力の際に、10文字以上のデータが入力された場合、 一般的な処理系では、str[] の配列の近辺に 「関数foo()の実行後に戻る処理の番地」が格納されている場合が多く、 文字列をはみ出るような入力があった場合、処理番地を書き換えられる可能性がある。 悪意のあるプログラマーは、はみ出す領域に、戻り番地を書き換えるデータと、悪意のある処理を 書くことで、想定外の処理を動かすかもしれない。 このテクニックをバッファオーバーフローと呼ぶ。
このため、最大文字制限の機能を使い、以下のように記述すべきである。
char str[ 10 ] ; scanf( "%9s" , str ) ;
しかし、scanf() には、空白を読み飛ばす機能により「入力が無い場合…」といった 処理が書きにくい、%d入力で実数の"."や文字列を間違って入力したときの処理などの 問題があって、scanf() 単体で複雑な入力に対応することは極めて難しい。
fgets() + sscanf()
このような場合によく使われるのが、fgets()とsscanf()である。
fgets() は、文字配列に1行分のデータ(行末文字"¥n"まで)を、文字配列に読み込む関数である。 また、sscanf() は、文字配列のデータから、scanf() と同じようにデータを読み込む。
FILE* fp ; if ( (fp = fopen( "data.txt" , "rt" )) != NULL ) { char buff[ 100 ] ; while( fgets( buff , sizeof( buff ) , fp ) != NULL ) { int x ; double y ; char z[100] ; if ( sscanf( buff , "%d%lf%s" , &x , &y , z ) != 3 ) break ; // x , y , z を使った処理 } fclose( fp ) ; }
fgets() は、第一引数に、読み込み先の配列アドレス、第2引数に最大読み込みバイト数、第3引数に 読み元のファイルを指定する。文字配列への読み込み時には、第2引数のサイズを超えて 読み込むことはしないので、バッファオーバフローの心配はない。 また、入力データが無い場合には、NULL を返す。
sizeof()は、引数部分の変数のバイト数を返す演算子。
sscanf() は、データの入力が、第一引数の文字列から読み込む以外は、 scanf(),fscanf() と同じ使い方。
注意:fgets では、入力が最大バイト数以下の場合、行末文字まで読み込む。
Tips: 入力が無かったら(空行なら)、標準入力(通常キーボード入力)なら
char buff[ 1024 ] ; while( fgets( buff , sizeof( buff ) , stdin ) != NULL ) ) { // stdin は、標準入力(通常キーボード) if ( buff[ 0 ] == '¥n' ) { // 入力が空行だった場合 } }
fscanf()にfprintfがあるように、sscanfに対してsprintfもある。
char buff[ 1024 ] ; sprintf( buff , "%d %5.1lf %s" , 12 , 34.5 , "abc" ) ; // buff = "12 34.5 abc"となる。
#defineマクロ
scanfで"%d"で数字を入力する際に、文字を入力されると、あとの処理が書きにくい場合が多い。 この場合、fgetsで入力し入力データが正しい文字を使っているかチェックしてから、 sscanf()を使うなどの対処をとることが多い。 ここで、#define マクロを使ってみる
#include <stdio.h> #define isdigit(C) ((C)>='0' && (C)<='9') void main() { char buff[ 1024 ] ; while( fgets( buff , sizeof( buff ) , stdin ) != NULL ) { char* pc ; for( pc = buff ; *pc != '¥0' && isdigit( *pc ) ; pc++ ) /*nothing*/ ; if ( *pc == '¥n' || *pc == '¥0' ) { int x ; sscanf( buff , "%d" , &x ) ; } } }
#define は、通常プログラム中の定数を分かりやすく使う場合に、使われる。
#define PI 3.14159265
ただし、#で始まる行は、C言語によって特殊で、C言語の解析の前に「プリプロセッサ」 でプログラムの内容を書き換える機能。
C言語のプログラムが機械語になるまで:
C言語(#行を含む) ↓ プリプロセッサ C言語(#行なし) ↓コンパイル 中間コード(printfなどの標準関数などが未解決) ↓ リンク処理 ← ライブラリ(標準関数などの中間コードをまとめたもの) ↓ 機械語
#define マクロでは、isdigit() の引数? C が、呼び出し部の *pc となり、 isdigit( *pc ) の部分は、以下のように書き換えられる。
((defineマクロ宣言)) #define isdigit(C) ((C)>='0' && (C)<='9') ((デファインマクロを使ったら)) isdigit(*pc) ↓ C ← *pc ((*pc)>='0' && (*pc)<='9')
ただし、#defineマクロは、プログラムのコンパイル前に、文字列として書き換えを行う。
// 例1 #define begin { #define end } void main() // まるでPASCALのような記述(^_^; begin printf( "Hello¥n" ) ; end // 正しく動くよ // 例2 #define ADD(X,Y) X + Y #define MUL(X,Y) X * Y void main() { printf( "%d" , MUL( 3 , ADD( 4 , 5 ) ) ) ; } // 3 * 4 + 5 に書き換えられるので、17になる。 // 3 * (4+5) の27にはならない。 // 普通の関数のように27の結果が欲しいなら、 // #define ADD(X,Y) ((X)+(Y)) // #define MUL(X,Y) ((X)*(Y)) // と書くべき。
開いた関数と閉じた関数
#defineマクロは、定義しておいた命令への書き換えなので、関数と同じように思うかもしれない。 ただし、機械語を生成する処理の前に書き換えるので、#define マクロで書き換えられる処理が 長い場合は、生成される機械語が大きくなる場合がある。 一方で、#defineマクロを使うと引数の受け渡しが無いので、 isdigit()のような簡単な処理の場合、 生成される機械語の処理が少し速い。
// 開いた関数FOO #define FOO(X) Xを使った処理 void main() { int x ; FOO(x) ; // xを使った処理 FOO(x) ; // xを使った処理 // FOOの中身が長い場合、FOOの機械語が2個作られる。 } // 閉じた関数foo int foo(int z) { zを使った処理 : : } void main() { int x ; foo(x) ; // z←x,fooを呼び出し foo(x) ; // z←x,fooを呼び出し // fooの機械語は1個だけ,引数の受け渡し処理が2個 }
#defineマクロのADD,MULの優先順位の問題が、"醜い"と思う人は、 C++で新しく導入された、"inline"関数を勉強すること。 #defineマクロを使わずに、開いた関数を簡単に記述できる。
絶対PATH,相対PATHの演習
絶対PATH,相対PATHの理解のため、コマンドラインを用いた演習も行う。
ファイル関連コマンドの基本
(( まずは cmd.exe の起動 )) メニュー検索より、"cmd.exe" を探して起動 (( ファイル操作命令の基礎 )) dir PATH ディレクトリの一覧を表示 例:dir C:¥Windows type PATH ファイルの内容を表示 例:type Z:¥sample¥sample.c mkdir PATH ディレクトリを作成 例:mkdir Z:¥foo rmdir PATH ディレクトリを削除 例:rmdir Z:¥foo echo "abc" > Z:¥sample¥data.txt テキストファイルに出力
演習
(( 絶対PATH )) Z: dir ¥ mkdir ¥fukui echo "fukui" > ¥fukui¥fukui.txt type ¥fukui¥fukui.txt mkdir ¥fukui¥sabae echo "sabae" > ¥fukui¥sabae¥sabae.txt dir ¥fukui¥sabae type ¥fukui¥sabae¥sabae.txt (( 相対PATH )) cd ¥fukui¥sabae dir . dir .. type sabae.txt type ..¥fukui.txt dir ..¥..
ファイル入出力
今日は、ファイル入出力のプログラム演習。
ファイル入出力
// 基礎的なファイル入力 #include <stdio.h> void main() { FILE* fp ; if ( (fp = fopen( "data.txt" , "rt" )) != NULL ) { char name[ 100 ] ; int age ; while( fscanf( fp , "%s%d" , name , &age ) == 2 ) { printf( "%s %d\n" , name , age ) ; } fclose( fp ) ; } }
// 入力したデータから条件を満たすデータだけを出力 #include <stdio.h> void main() { FILE* fp_in ; FILE* fp_out ; if ( (fp_in = fopen( "in.txt" , "rt" )) != NULL ) { if ( (fp_out = fopen( "out.txt" , "wt" )) != NULL ) { char name[ 100 ] ; int age ; while( fscanf( fp_in , "%s%d" , name , &age ) == 2 ) { if ( age >= 18 ) fprintf( fp_out , "%s %d\n" , name , age ) ; } fclose( fp_out ) ; } fclose( fp_in ) ; } }
テキストモードとバイナリモード
前述プログラムでは、fopen のファイルモードで、"rt" , "wt" のように 文字"t"をつけている。これは、OSによる行末文字の取り扱いの トラブルを防ぐためのもの。
OSの成り立ちの違いで、Windows と Unix では、行末文字の取り扱い方法 が違う。
Windows では、次の行の先頭に移動するには、"\r"(キャリッジリターン)と "\n"(ラインフィード)が必要となる。
一方、Unix では、次の行の先頭に移動するには、"\n"(ラインフィード)だけ でいい。
この違いがあるため、Unixで開発されたC言語で書いたプログラムを Windows で動かそうとすると、
// Unix Windows printf( "Hello\n" ) ; // Hello Hello printf( "World\n" ) ; // World World
と異なる表示になってしまう。これでは、WindowsでC言語を書く場合は、 printf( "Hello\r\n" ) といった書き換えが必要になってしまう。
これを防ぐために、fopen で "…t" 書くことで「テキストモード」に 指定すると Windows で printf( "\n" ) を出力すれば、 "\r\n" に変換して出力してくれる。入力時にも同様に、"\r\n" があると、 "\n" に変換してくれる。
絶対PATHと相対PATH
前述のプログラムで、fopen( "data.txt" , "rt" ) と指定すると、 プログラムの保存しているディレクトリと同じ場所から、"data.txt" を 探そうとする。(相対PATH)
しかし、別のディレクトリにあるデータを読む場合には、 場所を明記するために、Z:\directory\data.txt のように、 ルートディレクトリからの場所(Windowsではドライブ名付きで)を 指定する。(絶対PATH)
ただし、Windows でのディレクトリ区切り文字 '\' は、C言語の文字列内では、 特殊文字を指定する時に使われる。このため、文字列内では \\ の様に 書く必要がある。
fp = fopen( "Z:\\directory\\data.txt" , "rt" ) ;
この場合、"\\"と2重で書くことで、Unix と Windows のプログラムの 書き換えが必要となるのを防ぐため、上例は、Unixのディレクトリ区切り文字"/"を使って、以下のように書いても良い。
fp = fopen( "Z:/directory/data.txt" , "rt" ) ;
SQLとPHP
SQLとPHPのプログラムの練習。
SQLの基礎
簡単にSQLの文法を勉強したあと、自分で簡単なデータベースを作り検索してみる。 Windowsのエディタで、SQLの命令を入力し、 ブラウザの実験環境のSQLの入力フォームの所にコピー&ペーストで実験する。
PHPでデータベースを読みだす
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>SQLの実験</h1> <form method="GET" action="sample.php"> <input type="text" name="A" /> <input type="submit" value="QUERY" /> </form> <?php // SQLのデータベースファイル $DBFILE = "説明で聞いたファイルの場所を記載/名前.db" ; // フォームの値をもらう $a = $_REQUEST[ "A" ] ; // データベースを開く $db = new SQLite3( "$DBFILE" ) ; // 実行したいSQL $sql = "select * from S where 業者番号='$a' ;" ; // SQL実行 if ( ($query = $db->query( $sql )) !== FALSE ) { // SQL実行に成功 print "<pre>" ; print '$a'." = $a\n" ; // 1件づつ全部読み込み while( ($res=$query->fetchArray(SQLITE3_ASSOC)) !== FALSE ) { // 1行分のデータの配列の全要素の繰り返し // $res[0],$res[1],... foreach( $res as $key => $value ) { print "$key=$value " ; } print "\n" ; } print "</pre>" ; } ?> </body> </html>
アイデアをまとめる方法
4EIの創造工学演習で、今日はアイデア出し。 といっても、発散しがちなアイデアを形にするのは難しい。 一般的な手法を以下にまとめる。
一般的には、アイデアを発散させ、その後一旦アイデアを評価して絞込む。 そしてそのアイデアをより良いものに変えていく。 参考 SlideShareより
KJ法
KJ法: テーマに関するアイデアをカードに書き出し、ある程度出揃ったら似たアイデアを集めてタイトルをつける。 さらに似たものを集めてタイトルをつけ、アイデアをまとめていく。

マインドマップ
マインドマップとは、 最初に掲げたテーマを中心に、発想したキーワードを周囲に書き並べる。さらに、その発想キーワードの周りにさらなる発想、問題点、利点などを キーワード的に書き並べ、ある程度発散してきたら、関連するものを結びつけていく。

マインドマップは、専用のエディタなどもあるので、そういうツールを使うのもあり。
プログラミング応用・ガイダンス
初回の授業ということで、シラバスを配り、今年の授業の進め方を説明。 基本はC言語で行い、(1)C言語の基本、(2)ファイルと入出力、(3)構造体 の説明を行う。
今年の3年生は、JavaScript でプログラミング基礎を行っているので、 C言語に慣れてもらう必要がある。特に型(Type)の概念ができていないと 思われるので、注意が必要と思われる。
プログラム言語の歴史
まずは、JavaScript を習ってきたのに、C言語をするのかを分かって もらうために、プログラム言語の歴史の説明を交えて、 構造化プログラミングの説明。
機械語 計算方法や番地がそのまま数値... アセンブリ言語 機械語は人間に解らないので書きやすく。 FORTRAN 科学技術計算向け言語 COBOL 商用計算向け言語(データの構造化[構造体]) ALGOL 命令の構造化 + データ構造化 while( ) { C言語の命令の構造化 } BCPL,B言語 C言語の前身 C言語 unixを開発するために作られた SIMULA シミュレーション用言語(オブジェクト指向の前身) C++ C言語にオブジェクト指向を取りれ Java Internetでのプログラム用 C# Microsoftの対Java戦略 D BCPL,B,C,C++,今ココ JavaScript ブラウザで動くように
前述の高級言語を動かす場合、コンパイラ方式とインタプリタ方式がある。 コンパイラ方式は、命令を最初にすべて機械語に直す。 インタプリタ方式は、高級言語の意味に合わせて動く言語。 通常は、コンパイラ方式の方が高速。
C言語の導入
#include <stdio.h> int main() { printf( "Hello World\n" ) ; return 0 ; }
と説明をしようと思ったけど、記号の読み方とか読み間違いを防ぐため
# ナンバーサイン(正しくはシャープではない) : コロン ; セミコロン @ アット ^ ハット * アスタリスク
あと、私の板書を読み間違えられても困るので、紛らわしい文字の 話をしておく。
GrWinのライセンスキーの指定方法
プログラミング応用のグラフィックスを用いた演習では、 簡単な設定で使えるようにということで GrWin を使用している。 しかし、昨年度まで利用していたものから、正式バージョン GrWin 1.0 となった際に、 ライセンスキーが必要となった。
GrWinのライセンスキーの設定
総合情報処理センターでは、複数端末の教育利用ライセンスを取得し、 そのキーの情報が端末に保存されています。 プログラムを走らせる場合は、以下のようなコードにてコンパイルして下さい。
#include <GrWin.h> #include "C:\Program Files\GrWin\gwkey.c" int main(){ GWinit(); GWopen(0); GWindow(-1,-1,1,1); GWline(-1,-1,1,1); return 0; } // 以下の C:¥Program Files¥GrWin¥gwkey.c" は、保存済み // /* gwkey.c * * Copyright (C) 1998 - 2014 TAMARIBUCHI, Tsuguhiro * * WWW: http://spdg1.sci.shizuoka.ac.jp/grwin/ja-jp/ * ********************************************** * GrWin Version 1.1.0 用のキー・ファイルです * * このファイルを編集しないでください * ********************************************** */ char GW_key[] = "ライセンスキーの値";
自宅で GrWinを使って演習を行いたい場合は、必要な GrWinサーバインストーラ(非商用)とGrWinライブラリインストーラ、をダウンロード&インストールし、 個人用ライセンス取得ページにて、ライセンスを発行し送られてきたキーを、上記 "C:¥Program Files¥GrWin¥gwkey.c" の GW_key[] の文字配列に記載してください。