/*
* 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"」の長さは = です。 $