/* * CDATE FILENAME * * キーボードから一文字入力し、その文字によって異る国の挨拶をする */ #include <stdio.h> /* * hello ( char contry ) * char contry : 国を表す一文字 * j : 日本 * e : 英 * c : 中国 * f : フランス * g : ドイツ */ void hello ( char cmd ) { /* 国を表す一文字 : 'j' / 'e' / .. / 'g' */ if ( cmd == 'j' ) { /* 'j' の時は、日本語にする */ printf ( "こんにちは\n" ); } else if ( cmd == 'e' ) { /* 'e' の時は、英語にする */ printf ( "Hello\n" ); } else if ( cmd == 'c' ) { /* 'c' の時は、中国語にする */ printf ( "ニイハオ\n" ); } else if ( cmd == 'f' ) { /* 'f' の時は、フランス語にする */ printf ( "Bonjour\n" ); } else if ( cmd == 'g' ) { /* 'g' の時は、ドイツ語にする */ printf ( "Guten tag\n" ); } else { /* どれでもなければ.. */ printf ( "???\n" ); } } /* * main */ int main( void ) { printf ( "国を表す文字を入力してください\n" ); printf ( "\tj\t日本\n" ); printf ( "\te\t英語\n" ); printf ( "\tc\t中国\n" ); printf ( "\tf\tフランス\n" ); printf ( "\tg\tドイツ\n" ); printf ( "> " ); hello ( getchar() ); /* getchar() で文字を入力し、それに対応する結果を出す */ return 0; }
#include <stdio.h> int main(void) { printf ( "Hello, World\n" ); return 0; }
#include <stdio.h> int main(void) { printf ( "Hello, World\n" ); printf ( "Hello, World\n" ); printf ( "Hello, World\n" ); return 0; }
#include <stdio.h> int start(void) { /* main を start */ printf ( "Hello, World\n" ); return 0; }
#include <stdio.h> /* 関数 : print_hello を作る (関数定義) 目的 : 命令 : 「printf ( "Hello, World\n" );」 に対し、 名前 : 「print_hello」 を付ける 効果 : 関数呼び出し : 「print_hello();」 によって、 命令 : 「printf ( "Hello, World\n" );」 が、実行できるようになる。 関数定義の手順 1. 名前を決める print_hello 2. その名前を main 関数の外に書く 3. 前に void、後ろに (void) { 改行 } とする => 関数の頭部 4. { ? } に、名前を付けたい命令書く => 関数の本体 関数呼び出し 1. 関数名を書き 2. 関数名の後ろに () と、;を書く */ /* * 関数 print_hello の(関数)定義 */ void print_hello ( void ) { printf ( "Hello, World\n" ); } /* * main */ int main(void) { print_hello(); /* 関数呼び出し */ return 0; }
#include <stdio.h> /* * 関数 foobar の(関数)定義 */ void foobar (void) { printf ( "Hello, World\n" ); } /* * main */ int main(void) { foobar(); /* 関数呼び出し */ foobar(); /* 関数呼び出し */ foobar(); /* 関数呼び出し */ return 0; }
#include <stdio.h> /* 関数 : print_hello を作る (関数定義) 目的 : 命令 : 「printf ( "Hello, World\n" );」 に対し、 名前 : 「print_hello」 を付ける 効果 : 関数呼び出し : 「print_hello();」 によって、 命令 : 「printf ( "Hello, World\n" );」 が、実行できるようになる。 関数定義の手順 1. 名前を決める print_hello 2. その名前を main 関数の外に書く 3. 前に void、後ろに (void) { 改行 } とする => 関数の頭部 4. { ? } に、名前を付けたい命令書く => 関数の本体 関数呼び出し 1. 関数名を書き 2. 関数名の後ろに () と、;を書く */ /* * 関数 print_hello_you の(関数)定義 * void print_hello_you ( char *You ) * 引数に指定された文字列に対し、Hello とする */ void print_hello_you ( char *You ) { /* You は仮引数変数で、のちに実引数の値に置き換わる */ printf ( "Hello, " ); printf ( You ); /* 仮引数変数を利用できる */ /* You には何が入っているかは、ここではわからない */ /* You は色々と変化 => 「変数」 */ /* 変数 You には、何かの値が入っている */ printf ( "\n" ); /* printf ( "Hello, World\n" ); => printf ( "Hello," ); printf ( "World" ); <= 固定 ( 具体的 ) printf ( "\n" ); => printf ( "Hello," ); printf ( You ); <= 変数化 (抽象化) printf ( "\n" ); */ } /* * main */ int main(void) { print_hello_you( "World" ); /* 関数呼び出し */ /* 実引数("World")を指定 */ /* print_hello_you( "World" ); => 仮引数変数 You <-> 実引数 "World" (対応関係) printf ( "Hello, " ); printf ( You ); printf ( "\n" ); => 仮引数変数が実引数に置き変わる printf ( "Hello, " ); printf ( "World" ); printf ( "\n" ); => Hello, World\n */ print_hello_you( "世界" ); /* print_hello_you( "世界" ); => 仮引数変数 You <-> 実引数 "世界" (対応関係) printf ( "Hello, " ); printf ( You ); printf ( "\n" ); => 仮引数変数が実引数に置き変わる printf ( "Hello, " ); printf ( "世界" ); printf ( "\n" ); => Hello, 世界\n */ print_hello_you( "You" ); return 0; }
#include <stdio.h> /* * 関数 print_hello_you の(関数)定義 * void print_hello_you ( char *You ) * 引数に指定された文字列に対し、Hello とする */ void print_hello_you ( char *You ) { printf ( "こんにちは, " ); /* 関数の一部変更する */ printf ( You ); printf ( "\n" ); } /* * main */ int main(void) { print_hello_you( "World" ); /* 関数呼び出し */ print_hello_you( "世界" ); print_hello_you( "あなた" ); return 0; }
#include <stdio.h> /* * main */ int main(void) { /* p-006.c の関数を利用しない版 */ printf ( "Hello, World\n" ); printf ( "Hello, 世界\n" ); printf ( "Hello, you\n" ); return 0; }
#include <stdio.h> /* * main */ int main(void) { /* p-008.c の関数を利用しない版 */ /* 変更部分が三か所に変化する */ printf ( "こんにちわ, World\n" ); printf ( "こんにちわ, 世界\n" ); printf ( "こんにちわ, you\n" ); return 0; }
#include <stdio.h> int main(void) { printf ( "Hello, World\n" ); /* 『文字列』を引数とする */ printf ( 'H' ); /* 『文字』引数とする */ return 0; }
#include <stdio.h> int main(void) { putchar ( 'H' ); /* 『文字』引数とする */ putchar ( "H" ); /* 『文字列』引数とする */ return 0; }
#include <stdio.h> /* * 『文字』を引数とする関数の例 * 与えられた文字の次の文字を出力する関数 */ /* * void putnchar ( char ch ) * 引数で指定された『文字』の「次『文字』」を出力する * 'a' => 'b' * '3' => '4' */ void putnchar ( char ch ) { /* 仮引数変数 ch の宣言が char ( char * でなく ) になっている */ putchar ( ch + 1 ); /* ch の次の『文字』は ch + 1 */ /* 『文字』を出力するには、putchar 関数 */ } int main(void) { putnchar ( 'A' ); /* 'A' の次なので 'B' が出力 */ /* 関数の引数の型は「char」で宣言 => 『文字』型 実引数にも『文字』を指定する */ return 0; }
#include <stdio.h> /* * 『文字』を引数とする関数の例 * 与えられた文字の次の文字を出力する関数 */ /* * void putnchar ( char ch ) * 引数で指定された『文字』の「次『文字』」を出力する * 'a' => 'b' * '3' => '4' */ void putnchar ( char ch ) { /* 仮引数変数 ch の宣言が char ( char * でなく ) になっている */ putchar ( ch + 1 ); /* ch の次の『文字』は ch + 1 */ /* 『文字』を出力するには、putchar 関数 */ } int main(void) { putnchar ( getchar() ); /* 入力した文字の次の文字が表示される */ return 0; }
前回(2020/06/19)の内容 「Hello, World」再び 最も単純なプログラム : Hello, World 差分プログラミング すでに動作がわかっていて、正しく動作するものから始める 少しずつ修正を加えながら改良して行く => 間違いの影響を小さくして、効率よりプログラム作成ができる 「車輪の発明」 => すでに、発明済の「車輪」を再度、苦労して発明しようとしている 無駄な行為 プログラムを作成する場合 すでにあるものは、そのまま「再利用」する => 既存のプログラム改良 「Hello, World」の謎 御呪い: 「#include」 #include <stdio.h> => printf の宣言がはいっていて、これを利用している #include の機能そのものは、 /usr/include/stdio.h を読み込む main 関数宣言 => C 言語で記述された実行プログラムには、 必ず、main 関数が一つだけ含まれている必要がある ソースコードファイルに、関数の定義をする事は可能だが、 その中に、main 関数がないとダメ 二周目なので、一周目ではおこなわない、色々な説明を追加 「int」/「return 0;」 は何をしている ?? => すこし、この内容触れる予定 [2020/06/26] 「関数」という考え方 関数の定義とは(What) ? 「プログラムの一部」に「名前」を付ける事 「名前」を「関数名」と呼ぶ 「プログラムの断片」を「関数の本体」と呼ぶ 関数をどうやって利用する(How to) ? 「関数名」を指定するだけで「関数本体」が実行される (関数呼出し) 関数を定義する理由は (Why) ? 「プログラムの断片」に「名前」が付けられるので、分かり易い もちろん、「断片の内容に対応した分り易い名前をつければ..」だが.. 「関数の名前」を指定するだけで「関数本体」が実行される 何度も同じ事をする場合に便利(プログラムが短くなる) # もとの命令より、関数名が短い場合 # 関数の本体には、複数の命令がかけるので、通常は、短くなる 「引数」を利用する事により 「色々な断片」を「一つの関数本体」にまとめられる 何度も「似たような事」をする場合に便利(プログラムが短くなる) 関数の定義の「使いまわし」が可能になる 「使いまわせる」 => 色々な所で利用できる ( 違いを実引数で補う ) 「汎用」になっている <= (一部が..) 抽象化されている 「抽象化」されている所「変数」の部分 一箇所の「関数本体」を直すだけで、多数の場所の命令を直す効果がある 関数呼び出しは、同一の関数定義の本体を呼び出す 同じ名前の関数呼び出しが行われるたびに、同じ関数の本体が呼び出される => 関数の定義を変更した影響は、関数呼び出しの個数の影響になる 同じ事をするときに、 「コピペ」を利用する事ができる ここで、「もしコピーするまえの内容に誤りがあった」ならどうなるか ? コピペの結果、「誤り」もコピーされてしまう... 「コピペ」がバグの増殖を促す 「コピペ」よりよい、「関数呼び出し」という方法 => コピーしないので、「誤り」が増えない 関数」の表現方法 (復習) 関数定義(の文法) 「関数定義」は、「関数頭部」と「関数本体」に分けられる 例: 関数定義 void print_hello ( void ) { printf ( "Hello, World\n" ); } 関数頭部 : void print_hello ( void ) 関数本体 : {printf ( "Hello, World\n" );} 「関数頭部」は、「関数宣言」「関数名」「仮引数宣言」に分けられる 例: 関数頭部 void print_hello ( void ) 「関数宣言」は、void(これまで)/int(main だけ) 「関数名」は、自由に决めて良い(他と重複すると駄目だが..) print_hello 「仮引数宣言」は、「(」+「仮引数宣言並び」+「)」 ( void ) : 引数が無い場合 ( char *You ) : 引数が一つ ( 仮引数変数 You ) 「仮引数宣言並び」は、 「void」か、 「char *仮引数変数名」のカンマ(,)区切 「関数本体」は、「{」+「命令列」+「}」 関数本体 : {printf ( "Hello, World\n" );} 関数呼出し(の文法) 「関数呼出し」は、「関数名」+ 「実引数並び」 例: print_hello_you( "World" ); 関数名:print_hello_you 実引数並び: "World" 「実引数並び」は、 '()' : 引数が無い場合 か、 '(' + 「式」のカンマ並び + ')' 引数の個数だけ、並べる => それぞれ、その順に、仮引数変数と対応付けられる [文字を引数に持つ関数と型宣言] これまでの関数 引数が「無い」か、「文字列」を引数としていた 無い => void 文字列 => char *仮引数変数名 「char *」をお呪いとし、 関数を呼び出す時に、実引数として、「文字列」を指定 仮引数変数には(実引数で指定した)文字列が入っているとして、考える 例 : p-006.c の関数 print_hello_you 関数定義の頭部 void print_hello_you ( char *You ) 関数呼び出し print_hello_you( "World" ); 仮引数変数 You に実引数の文字列 "World" が入る !!! 実は、「char *」は、「文字列」に対応している !!! <<注意>> 「文字列」の時には、「char *」書くが、 !!! 「char *」は「文字列」を表しているわけではない !!! 当分は、「表していると『誤解』していて」もよい 「これまで」 引数として、文字列しか扱わなかった => そのために必要な「char *」は、「いつも同じもの」でよいので、 意味を説明する必要がない cf. #include <stdio.h> => おまじない、いつもいれる ところが #include <string.h> が増えた => strcmp を利用するため <= stdio.h は ??? => printf を利用するため (『文字列』ではなく)『文字』を引数に持つ関数の場合 例 : putchar() : 引数は『文字』である 引数宣言に(「char *」ではなく)「char」とする必要がある 仮引数変数宣言に現れる 「char *」/「char」は実は、「引数の型」を表現していた 型宣言 「char *」は『文字列』が入る仮引数変数の宣言に利用する 「char」は『文字』が入る仮引数変数の宣言に利用する 関数の引数は、 その型 ( char 型 : 値が『文字』 / char * 型 : 値が『文字列』 ) が 関数の仮引数変数の宣言の時点で定まっている => 関数呼び出しの実引数では、その型に沿った値を指定する必要がある 関数の仮引数変数に、 その型と異る(実引数で指定した)値を入れようとすると「エラー」になる => 適切な結果にならない 「型」と「演算」 型 : 値の集合と、その値を利用した演算の組(数学) 例 : n 次元実ベクトル線形空間 値の集合 : n 次元実ベクトル集合 演算 : 「定数倍」と、「和」 # 「n 次元実ベクトル集合」 # = { (x1,x2,..,xn) | xi \in R } # => 集合だけでは、「空間」にならない # 「(線形)空間」にするには # 「(線形)空間」固有の演算(線形空間の場合は、定数倍と和) # と一緒にする必要がある 『文字』に 「1 を加える」と、「次の『文字』」 'A' + 1 => 'B' '3' + 1 => '4' 『文字』の集合は「ASCII コード表に記載されているもの」 # 1 byte で表現できるから、最大 256 種類 『文字列』に 「1 を加える」と、「短くなった『文字列』」 "abc" + 1 => "bc" 『文字列』の集合は 長さが有限な、文字並びなので 長さ 0 : "" => 1 通り 長さ 1 : "a" => 256 通り 長さ 2 : "ab" => 256 x 256 通り .. 長さ n : 256^n .. +) ------------------- 1 + 256 + 256^2 + .. + .. 同じ「1 を加える」という「演算」でも、「意味」が異る 「演算」と「型」は「一組」で考える必要がある # 数学の「空間」
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20200626-01.c
/* * 20200626-01-QQQQ.c * 英小文字を英大文字に、英大文字を英小文字に入れ替える関数 */ #include <stdio.h> /* * char change_up_lower ( char ch ) * 引数で指定された文字が大英文字なら英小文字に、英小文字なら英大文字にする */ char change_up_lower ( char ch ) { /* 英大文字の処理 */ if ( 'A' <= ch ) { /* もし、 ch が 'A' 以上で .. */ if ( ch <= 'Z' ) { /* しかも、 'Z' 以下 なら .. */ /* 引数は英大文字だったので、英小文字に変換し、値を返す */ return ch - 'A' + 'a'; } } /* 英小文字の処理 */ if ( 'a' <= ch ) { /* もし、 ch が 'a' 以上で .. */ if ( ch <= 'z' ) { /* しかも、 'z' 以下 なら .. */ /* 引数は英小文字だったので、英大文字に変換し、値を返す */ /* ** この部分を完成させなさい */ } } /* 何れでもない場合 */ return ch; /* 引数の値をそのまま、関数の値として返す */ } /* * main */ int main ( void ) { /* 英大文字 ( 'Q' ) の場合 */ printf ( "英大文字 ( 'Q' ) の場合 「" ); putchar ( change_up_lower ( 'Q' ) ); printf ( "」となります。\n" ); /* 英小文字 ( 'h' ) の場合 */ printf ( "英小文字 ( 'h' ) の場合 「" ); /* ** この部分を完成させなさい */ printf ( "」となります。\n" ); /* 何方でもない ( '4' ) の場合 */ printf ( "何方でもない ( '4' ) 場合 「" ); putchar ( change_up_lower ( '4' ) ); printf ( "」となります。\n" ); return 0; }
$ ./20200626-01-QQQQ.exe 英大文字 ( 'Q' ) の場合 「q」となります。 英小文字 ( 'h' ) の場合 「H」となります。 何方でもない ( '4' ) 場合 「4」となります。 $
Download : 20200626-02.c
/* * 20200626-02-QQQQ.c * 指定した文字列の中の英小文字を英大文字に、英大文字を英小文字に入れ替えて出力する関数 */ #include <stdio.h> #define EOS '\0' /* マクロ(EOS) を、NULL 文字 '\0' で定義 */ /* 以下の結果は、20200626-01-QQQQ.c の内容を、そのまま利用する */ /* * char change_up_lower ( char ch ) * 引数で指定された文字が大英文字なら英小文字に、英小文字なら英大文字にする */ char change_up_lower ( char ch ) { /* 英大文字の処理 */ if ( 'A' <= ch ) { /* もし、 ch が 'A' 以上で .. */ if ( ch <= 'Z' ) { /* しかも、 'Z' 以下 なら .. */ /* 引数は英大文字だったので、英小文字に変換し、値を返す */ return ch - 'A' + 'a'; } } /* 英小文字の処理 */ if ( 'a' <= ch ) { /* もし、 ch が 'a' 以上で .. */ if ( ch <= 'z' ) { /* しかも、 'z' 以下 なら .. */ /* 引数は英小文字だったので、英大文字に変換し、値を返す */ /* ** この部分を完成させなさい */ } } /* 何れでもない場合 */ return ch; /* 引数の値をそのまま、関数の値として返す */ } /* * void print_change_up_lower_string ( char *string ) * 引数で指定した文字列の中の英小文字を英大文字に、 * 英大文字を英小文字に入れ替えて出力する関数 */ void print_change_up_lower_string ( char *string ) { if ( *string == EOS ) { /* 空文字列 ("") だった.. */ /* 何もしない */ } else { /* 取り敢えず、先頭の文字だけ変換を行って、出力 */ putchar ( change_up_lower ( *string ) ); /* 残りの文字列は、再帰で処理 */ print_change_up_lower_string ( string + 1 ); } } /* * main */ int main ( void ) { printf ( "Aa0BB11cc@+\nXy" ); print_change_up_lower_string ( "Aa0BB11cc@+Xy\n" ); return 0; }
$ ./20200626-02-QQQQ.exe Aa0BB11cc@+ XyaA0bb11CC@+xY $
Download : 20200626-03.c
/* * 20200626-03-QQQQ.c * 10 未満の長さの文字列の長さを表す一文字の数字を返す関数 */ #include <stdio.h> #define EOS '\0' /* マクロ(EOS) を、NULL 文字 '\0' で定義 */ /* * char length_of_string ( char *string ) * 引数の文字列の長さを表す、一桁の数字を返す関数 * 文字列の長さは一桁の場合しか、「適切に動作」しない */ char length_of_string ( char *string ) { if ( *string == EOS ) { /* 空文字列なら、長さは 0 */ return '0'; /* 長さ「0」を表す『文字(数字)』「'0'」を返す */ } else { /* 一文字分短い『文字列』の長さを再帰で求め、それに 1 を加える */ /* ** この部分を完成させなさい */ } } /* * main */ int main ( void ) { printf ( "文字列「\"abc\"」の長さは " ); putchar ( length_of_string ( "abc" ) ); printf ( " です。\n" ); printf ( "文字列「\"123456\"」の長さは " ); putchar ( length_of_string ( "123456" ) ); printf ( " です。\n" ); /* 長さが 10 を越える場合は、不適切な結果になる */ printf ( "文字列「\"1234567890123\"」の長さは " ); putchar ( length_of_string ( "1234567890123" ) ); printf ( " です。\n" ); return 0; }
$ ./20200626-03-QQQQ.exe 文字列「"abc"」の長さは 3 です。 文字列「"123456"」の長さは 6 です。 文字列「"1234567890123"」の長さは = です。 $