構造体と共用体
今回は構造体と共用体のおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.
構造体(structure)は,複数のデータを含み,ユーザが定義することで新しいデータ型が作れる機能です.プログラムを作るうちに1つの概念に対して複数のデータを扱うことが多い.そのため関連あるデータをまとめて管理したほうが効率がいい.例えば,学生のデータを作るとき,その学生の名前,年齢,学生番号といったデータをひとまとめにして扱うことになる.
このような複数データを1まとめにしたものを複合データ構造体と呼びます
構造体
先ほどの例として学生のデータベースを構造体にすると以下のようになります
struct student{ char name[100];//名前 int age; //年齢 int num; //学生番号 };
このような,構造体の定義を構造体テンプレートと言います.structが構造体を表して,studentが構造体の名前になります.
構造体を書く上で注意点として最後に;(セミコロン)をつけるのを忘れないでください.
構造体へのアクセス
構造体へのアクセスの仕方は変数名にドット演算子をつけ,その後にメンバ名を書きます.
struct student data;//dataが変数名 data.age = 18; //dataの年齢に18を代入 strcpy(data.name,"理工太郎");//dataの名前に理工太郎を代入
実際に構造体に代入し,出力するプログラムを見ていきましょう
#include <stdio.h> #include <string.h> // 学生のデータを入れる構造体 struct student{ char name[100];//名前 int age; //年齢 int num; //学生番号 }; void main(){ struct student data; strcpy(data.name,"明治麻衣"); // 名前を設定 data.age = 25; // 年齢を設定 data.num = 1; // 番号を設定 // 画面に表示 printf("名前:%s 年齢:%d学生番号:%d ¥n",data.name,data.age,data.num); }
構造体配列
同じ構造体を100人に使いたいという時に,配列をとって100個分を記録して処理を行う方法があります.
#include <stdio.h> #include <string.h> // 学生のデータを入れる構造体 struct student{ char name[100];//名前 int age; //年齢 int num; //学生番号 }; void main(){ int i; struct student data[4] = { { "明治麻衣",18,1 }, { "生田優子",19 ,2}, { "理工太郎",18 ,3}, { "機械翔",18,4 } }; // 画面に出力 for(i = 0; i < 4; i++){ printf("名前:%s 年齢:%d学生番号:%d ¥n",data[i].name,data[i].age,data[i].num); } }
構造体へのポインタ
構造体へのアクセスには,多くの場合,ポインタを用いて行われることもあります.構造体へのポインタの宣言は, いままでの変数のポインタの宣言と変わらずに使えます.
ただし,このポインタにアクセスするには,、「構造体->メンバ名」のように アロー演算子(->)を用いる.
struct student *data; data->age = 20; data++; // 次のdataに行く
以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.
- 5組
1.ファイル 3d_points.csv には,50個の3次元座標値が,カンマ区切りで記述されている.ダウンロードして,エディタで開いてみよ.ダブルクリックするとExcelが起動する.
まず,
(1)float または double 型 3 つの変数で 3 次元座標を表す構造体 Point を定義し
(2)ファイルから50個のデータを読み込む関数 load を作成
(3)これらの重心座標を返す calc 関数を用い
重心位置を画面に表示するプログラムを作成せよ.
main関数は下記の通りとする.
ヒント:ファイル内には1行あたり,カンマ区切りで実数が3つ並んでいるので, fscanf(fp, "%f,%f,%f", &..., &..., &...);とすれば,1行分の値3つの読み込みができる.
double 型の場合は,%lf を使用すること.
(1)double型の変数を3つ格納できる構造体を作成しましょう.
(2)まず,ファイルポインタを宣言し,ファイルを読み込みモードで開きましょう.
次に,for文を用いて配列pntの各要素のメンバに値を格納していきましょう.
最後に,ファイルを閉じましょう.
(3)まず,重心を表す変数を0で初期化して宣言しましょう.重心も3次元座標を表す構造体です.
次にfor文を用いて,読み込んだデータのメンバの値を重心に足しこんでいきましょう.
そして,重心をデータの個数で割って,その値を返しましょう.
2. ファイルrugby_matchup.csv には,13行のデータが含まれており,各行に
1.明治大学のチーム名(A or B の 1 文字)
2.対戦相手(文字列)
3.得点(整数)
4.失点(整数)
が,順次記されている.
このファイルを読み込み,これら各要素に加え勝敗(Win, Lose, Draw)をあらわす文字列を格納する構造体を作成せよ.
次に,チーム名を入力するとそのチームの結果を全て表示するプログラムを作成せよ.
ヒント: ファイルの読み込み(一行分)は,fscanf(fp, "%c , %[^,] , %d , %d", &..., , &..., &..., &...); とする.
まず,チーム名,対戦相手,得点,失点,勝敗の5つの変数を格納できる構造体を定義しましょう.
次に,ファイルを読み込む関数を作成しましょう.%[^,]とすることでカンマまでの文字列を格納できます.
次に,勝敗を判定する関数を作成しましょう.得点が失点よりも大きければ"Win",同じならば"Draw",小さければ"Lose"という文字列を構造体の勝敗を表すメンバに格納しましょう.この際strcpyを用いると良いでしょう.
main文では,配列を宣言し,これらの関数を呼び出しましょう.また,表示するチーム名をscanfで入力させ,for文,if文を用いて必要なデータを全て表示させましょう.
3. 5人分の
名前(char型)
身長[cm](float型)
体重[kg](float型)
の順に記述されたファイル health_data.csv をプログラムから読み込み, 以下の手順でプログラムを作成しなさい.
1.名前, 身長, 体重に加え, BMI指数(定義は各自で調べること) を格納することのできる構造体を作成.
2.ファイルから読み込んだ値を用い,それぞれのBMI指数を計算した後,作成した構造体に格納する.
3.「名前,身長,体重,BMI指数」の順番で各生徒のデータを画面に表示する.
まず,名前,身長,体重,bmiの4つの変数を格納できる構造体を定義しましょう.
次に,ファイルを読み込む関数を作成しましょう.読み込みは課題2と同様にscanfを用いると良いでしょう.
次に,bmiを計算する関数を作成しましょう.計算式をググって,bmiを表すメンバに値を格納しましょう.for文を用いて,全員分のデータを計算しましょう.
次に,データを出力する関数を作成しましょう.for文を用いて,全員分のデータを表示させましょう.
main関数では配列の宣言と,これらの関数の呼び出しを行いましょう.
- 6組
1.三次元ベクトルを表す事が可能な構造体 Vector3D を定義し, 2つのベクトルの内積を求める関数
float dot(Vector3D&, Vector3D&);
および 外積を求める関数
Vector3D cross(Vector3D&, Vector3D&);
をそれぞれ作成し,これらを用いてキーボードより入力した2つのベクトルの内外積を求めて表示するプログラムを作成しなさい.
double型の変数を3つ格納できる構造体を作成しましょう.
double x
double y
double z
が一般的かと思います。
格納さえできれば後は簡単な計算をする関数を作るだけです
2.ファイル【3d_points.csv】 には個数不明の3次元座標点が記述されている.但し最大個数は104以下とする. これらの重心座標を計算し表示するプログラムを作成せよ.また,座標点の個数も併せて表示すること.三次元点のハンドリングには,問題1で作成した Vector3D型を用いよ.
5組の問題1を参考に
重心の求め方はx,y,z各成分ごとに和を求めてから、座標点の個数で割ることで求められます
3.5人分の名前(char型),身長[cm](float型),体重[kg](float型)の順に記述されたファイル【health_data.csv】 を読み込み,以下の要件を満たすプログラムを作成しなさい.
- 名前,身長,体重,BMI指数を格納することのできる構造体HealthDataを作成する.
- 読み込んだ値を用いてそれぞれのBMI指数を計算した後作成した構造体に格納する.
- 「名前,身長,体重,BMI指数」の順番で各生徒のデータを書き出す.
5組の問題3を参考にしてください
BMIは体重[kg]/(身長[m]*身長[m])で求められます
4.上のプログラムに,任意の項目を任意に順(降順,昇順)でソートする関数 void sort(HealthData*, char* , int Order);
を加えよ.ただし,
HealthData: このデータを格納できる構造体.引数は配列へのポインタ
char* Koumoku: ソートする項目名(Name, Height, Weight, BMIのどれか)
int Order: 降順:0, 昇順:1
とする. すべての項目でソートした結果(8通り)を(ソートした項目と順がわかるようにタイトルをつけて)表示せよ.なお,名前はあいうえお順ではなく,ABC順でよい
5.複素数を表現する構造体 Complex を作り,2つの複素数同士の掛け算を行う関数 Complex mul(Complex&, Complex&);を作れ.
Complexとmul()を作ったら、ふたつの複素数の実部と虚部の値をそれぞれキーボードより入力して、計算結果を表示するようにしてください
ファイル入出力
今回はファイル入出力のおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.
まず、ファイルには主に2つの種類があります
ファイルの種類 | 内容 | 例 |
---|---|---|
テキストファイル | 文字として読めるデータ | .cファイル,.txtファイルなど |
バイナリファイル | 文字として読めないデータ | .pngファイル,wavファイルなど |
ファイルの種類によって使うべきモードが異なることに注意してください.
・テキストファイル
モード | 意味 | 備考 |
---|---|---|
r | 読み込み用にテキストファイルを開く | 既存ファイルがなければエラー |
w | 書き込み用にテキストファイルを開く | 既存ファイルがなければ先に消去 |
a | 既存のテキストファイルの最後に追加 | 元の内容は変更されない |
・バイナリファイル
モード | 意味 | 備考 |
---|---|---|
ra | 読み込み用にバイナリファイルを開く | テキストに準ずる |
wb | 書き込み用にバイナリファイルを開く | テキストに準ずる |
ab | 既存のバイナリファイルの最後に追加 | テキストに準ずる |
ファイルの開閉
通常ファイル処理を行うときは,対象となるファイルを「オープン」「クローズ」する処理が必要となります
関数 | 書式 | 意味 | 使用例 |
---|---|---|---|
fopen() | fopen(ファイル名,モード) | ファイルを指定したモードで開く.戻り値が,ファイルのポインタ | FILE* fp = open("sample.txt","w"); |
fclose() | fclose(ファイルポインタ) | 指定したファイルポインタのファイルを閉じる | fclose(fp); |
ファイル処理においてよく使う関数を紹介致します.
(ただし,int d,char buf[50],FILE *fpの表記をする)
・ファイルへの書き込み
関数 | 書式 | 使用例 |
---|---|---|
fprintf() | fprintf(ファイルポインタ,書き込み文字列,変数・・・) | fprintf(fp,"no=%d",i); |
・ファイルから値の読み込み
関数 | 書式 | 使用例 |
---|---|---|
fscanf() | fscanf(ファイルポインタ,読み込み文字列,変数・・・) | fscanf(fp,"no=%d",&i); |
・ファイルから文字列を一行取得してくれる関数
関数 | 書式 | 使用例 |
---|---|---|
fgets() | fgets(文字列配列のポインタ,一行の最大文字数,ファイルポインタ) | fgets(buf,30,fp); |
・ファイルに文字列を書き込む関数
関数 | 書式 | 使用例 |
---|---|---|
fputs() | fputs(書き込む文字列,ファイルポインタ) | fputs("test",fp); |
・一文字だけファイルから読み込むための関数
関数 | 書式 | 使用例 |
---|---|---|
fgetc() | fgetc(ファイルポインタ) | fgetc(fp); |
よく使う例として
ファイルの末端(EOF)になるまでwhileでループさせて処理する書き方をします
・一文字だけファイルに書き込むための関数
関数 | 書式 | 使用例 |
---|---|---|
fputc() | fputc(書き込む文字,ファイルポインタ) | fputc('a',fp); |
・指定バイト数のデータを指定した個数だけファイルに書き込みます
関数 | 書式 | 使用例 |
---|---|---|
fwrite() | fwrite(データが入ったバッファ,データ1個のバイト数,書き込むデータの個数,ファイルポインタ) | fputc(buf,1,6,fp);//1バイトのデータを6バイト分書き込む |
使用例
これらの関数を使ったサンプルコードを載せます
サンプルコード
#include<stdio.h> int main(void) { char ch[50] = "ABCDEFGH"; char buf[50]; FILE *fp; //ファイルの書き込み fp = fopen("test.txt","w"); //1バイトのデータを6個(6バイト分)書き込む. fwrite(ch,1,6,fp); fclose(fp); //書きこまれているか確認 fp = fopen("test.txt","r"); fgets(ch,50,fp); //画面に出力して確認 printf("書き込まれた文字は→ %s\n",ch); fclose(fp); //ファイルの読み込み if((fp = fopen("test.txt","r")) == NULL){ printf("ファイルを開けませんでした");//ファイルがないときのエラー処理 } else{ char ch; while((ch = fgetc(fp)) != EOF){ //1文字だけをファイルの終わりまで読み続ける printf("一文字だけ読みこみ%c\n",ch); } fclose(fp); } // ファイルのオープン fp = fopen("test.txt","w"); // ファイルに書き込み fputc('h', fp); fwrite(ch,1,6,fp); fputs("ijkl",fp); // ファイルのクローズ fclose(fp); //ファイルの読み込み if((fp = fopen("test.txt","r")) == NULL){ printf("ファイルを開けませんでした");//ファイルがないときのエラー処理 } else{ fgets(buf,50,fp); printf("ファイルに書き込まれた文字列は→ %s\n",buf); fclose(fp); } return 0 }
以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.
- 5組
1. このファイル document.txt に書かれた文章を,まずそのまま画面に表示し, その後に,同文章を構成するアルファベット,数字,記号,スペースなどの個数を表示せよ. なお,英文字は大文字と小文字は区別せずにカウントすること.
// 実行例 数値が正しいとは限らない 文章: Ubuntu is a Debian-based Linux operating system, with Unity as its default desktop environment. It is based on free software and named after the Southern African philosophy of ubuntu (literally, "human-ness"), which often is translated as "humanity towards others" or "the belief in a universal bond of sharing that connects all humanity". ........... 文字数カウント結果: a は 100 個ありました. b は 30 個ありました. c は 70 個ありました. d は 60 個ありました. e は 200 個ありました. ... " は 32個ありました. スペースは 100個ありました.
まずはfgetsを利用してEOFまで文字列を行ごとに読み込みましょう.そして読み込んだ行ごとの文字列を対象に,”文字と文字列”の実習課題2を参考にしながら,文字の出現頻度をカウントしましょう.そして最後に行毎にカウントしたそれぞれの文字の出現頻度を足しあわせましょう.
EOFまでファイル読み込みが達したかどうかを確認するには以下のコーディングを参考にしてください.
FILE* fp; . . . while(!feof(fp)){ . . . }
2. 以下のリンクからテキストファイルinputdata.txtをダウンロードせよ.
このファイルを読み込み,一行ごとの数値(5個のデータ)の平均値を行の最後に追加し,別のファイルに書き込むプログラムを作成せよ.
出力するファイル名は,outputdata.txt とする.
【ヒント】上記ファイルには,1行に5個の数値データが格納されている.今回は,データ個数が事前に分かっているとしてよい.
ファイル読み込みした数値をそれぞれの数値毎に格納する2次元配列を用意しましょう.ここまで課題をしっかりこなしてきた人であれば,行毎に平均値を算出することは簡単でしょう.
そして,fprintf()で順番にファイル読み込みした数値を出力するようにループを回し,その行の読み込んだ最後の一文字がファイル出力されたあとに平均値を出力し,最後に改行コードを出力する. . . . .というながれを繰り返せばOKです.
3. このファイルは test_result.csv 内には,各行に生徒の名前と 3科目のテストの点数がカンマ( , )区切りで記述されている.
このファイルの中身を読み取り,各生徒の合計点数を算出してその値が大きい順に並べてファイル test_rank.csv に出力するプログラムを作成せよ.
ヒント:fscanf関数を用いて1行分のデータを読み込むには,ファイル内のデータに合わせて書式指定文字列に,"%s,%d,%d,%d" を使用する. (test_rank.csvの各行に,名前と合計点をカンマ区切りで記し,最後の行に平均点を追加すること.)
// 出力ファイルの中身 Yuma,285 Alicia,276 Clarice,274 Jean,273 《中略》 Flora,214 Elena,206 Kate,165 average,247.13
基本的な流れは前問と同様です.ファイル読み込みとファイル出力の際には,しっかり名前と点数をカンマ区切りする点だけ注意しましょう.
4. 素数をファイル primes.txt に書き出せ.
(int 型の最大値である 2,000,000,000(20億)以下で良い.)
注意:出力ファイルのサイズは大きくなる可能性があるので,実行・確認が終わったら primes.txt は削除すること.
参考:下記のように,コンパイル時に -O2 オプションつけると実行速度の最適化が行われ,計算時間が短縮される場合がある.(短縮されない場合もある.)
c:\.windows2000> bcc32 -O2 152R??????-??-?.cpp
コンパイル時に-O2オプションをつけてみましょうということですね.ちなみに素数かどうか判定する単純なアルゴリズムをつかった関数は以下を参考にしてください.
bool PrimeJudge(int num) { if(num < 2){ return false; // 素数でない } for(int i = 2; i < num; i++){ if (num % i == 0){ return false; // 素数でない } } return true; // 素数である }
- 6組
1. 【as_document.txt】 に書かれた文章を読み込み,表示するプログラムを作成せよ. また,同文章を構成するアルファベット,数字,記号の個数も併せて表示せよ. なお,英文字は大文字と小文字は区別せずにカウントすること.
fgetsをつかって最後まで行を読み込みましょう.今度は読み込んだ文字列から1文字1文字カウントしていくプログラムをつくってください
2.【as_test_result.csv】 内には各行に生徒の名前と3科目のテストの点数がカンマ( , )区切りで記述されている(並びは名前順).このファイルの中身を読み取り,各生徒の合計点数を算出して、その値が大きい順に並べてファイルに出力するプログラムを作成せよ. なお,出力ファイル名はtest_rank.csvとし,各行に名前と合計点をカンマ区切りで記し, 最後の行に平均点を追加すること
カンマ区切りのファイルなので,fscanfを使って1つ1つ合っている型に読み込んでください.
あとは今までの課題同様に計算し大きい順にソートするだけです
3.以下の行列データ,
typedef double Matrix[4][4]; Matrix A = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
をメモリイメージそのままバイナリファイル 【as_matrix_image.bin】 に保存した. このデータを上記の行列変数に読み込み,画面に表示するプログラムを作れ.
バイナリファイルを開くときはfopen(filename,"rb")を使ってください
またバイナリファイルの読み込みにはfread(void* restrict ptr, size_t size,size_t nmemb, FILE * restrict stream)を使います
引数の意味としては
・ptr: 読み込む領域へのポインタ
・size: 要素 1 個あたりの大きさ
・nmemb: 要素の個数
・stream: ファイル
です
1. このファイル【as_lyrics.txt】 を読み込み,"*iller"が含まれる行だけを抜き出して 表示するプログラムを作成せよ(*は任意の文字)
fgets()を使って一行ずつ読み込み、"iller"の文字列が含まれるかを探索していきます。含まれていたらその行全体を表示させればokです。
1. このファイル【as_hex_words.txt】 には4桁区切りで16進数の数が格納されている. これを読み取って2桁ごとに区切り,ASCII文字として表示するプログラムを作成せよ.
読み込むときは16進の数値として読み込みましょう。読み込むことができれば、あとはL06データ型の分野の問題です。講義ページを見て復習しながら解いてみてください
ポインタ
今回はポインタのおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.
- アドレス
数値などを格納するための箱である変数(オブジェクト)は、コンピュータの記憶域(メモリ空間)の一部として存在します。記憶域には、たくさんの変数が配置されるため、それぞれの変数の場所をアドレスを用いて表します。つまり、変数のアドレスとは、それが格納されている記憶域上の番地のことです。
&演算子を用いることで、その変数のアドレスを知ることができます。
- ポインタ
変数名の前に*をつけることによって、ポインタとなります。
int m; int *n;
ここでmはint型の変数であり、nはint型の変数を指すためのポインタです。mには整数が入り、nには整数を格納する変数のアドレスが入ります。
*演算子を用いることで、それが指す変数の値を表すことができます。
- 関数の引数としてのポインタ
関数の中で仮引数を変更しても、呼び出し側の実引数には反映されません。しかし、関数にその変数へのポインタを渡すことで、変数の値を変更することができます。以下に具体例を示します。
#include <stdio.h> void function(int a){ a = 5; } void function2(int *a){ *a = 5; } int main(void){ int n = 3; printf("%d\n", n); function(n); printf("%d\n", n); function2(&n); printf("%d\n", n); }
function2のように変数へのポインタを仮引数に受け取れば、そのポインタに*演算子を用いることで、その変数そのものにアクセスができます。よって、function2でnの値を変更できます。
- ポインタと配列
配列名は、その配列の先頭要素へのポインタと解釈されます。つまり配列aにおいて、a=&a[0](a[0]のアドレス)です。
また、ポインタpが配列aを指すとき、p+iは、配列aの先頭要素のi 個後ろの要素を指すポインタとなり、*(p+i)は、i 個後ろの要素となります。
つまり、&a[i] , a+i , &p[i] , p+i は、すべて配列の各要素を指すポインタであり、a[i] , *(a+i) , p[i] , *(p+i) は、すべて配列の各要素を表します。
#include <stdio.h> int main(void){ int a[3] = {1, 2, 3}; int *p = a; for(int i=0;i<3;i++){ printf("a[%d]:%d, *(a+%d):%d, p[%d]:%d, *(p+%d):%d\n", i, a[i], i, *(a+i), i, p[i], i, *(p+i)); } for(int i=0;i<3;i++){ printf("&a[%d]:%p, a+%d:%p, &p[%d]:%p, p+%d:%p\n", i, &a[i], i, a+i, i, &p[i], i, p+i); } }
関数間の配列の受け渡しは、先頭要素へのポインタで行います。もちろん値を書き換えれば、呼び出し側の配列の要素も書き換わります。
void function(int *a){ .... }
- ポインタによる文字列
配列による文字列は
char s[] = "abc";
のように初期化しました。
ポインタによる文字列は
char *p = "abc";
のように初期化します。ここで、ポインタpは先頭文字 ' a ' を指します。また、ポインタによる文字列の配列は
char *p[] = {"abc", "de", "fghij"};
のように初期化します。ここで、ポインタp[0]は ' a '、p[1]は ' d '、p[2]は ' f ' を指します。
以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.
- 5組
1. 要素数 5 個のchar型配列,short型配列,int型配列,double型配列をそれぞれ用意し,配列の各要素のアドレスを順に表示するプログラムを作成せよ.
アドレスは,printf文の書式指定文字列%pで表示させることができる.(実際に表示されるアドレス値は環境依存のため,下の例の通りとはならない)
for文を用いて配列の各要素を表示させましょう.
&演算子を用いることで、その配列の要素のアドレスを知ることができます.
「配列とポインタの関係」の練習問題を参考にしましょう.
2. 入力した文字列の順序を反転して表示するプログラムを,ポインタを用いて実現せよ.
inverted_print関数を完成させよ.
main関数は変更しないこと.ヒント:swap関数を作成し,上手に使うと...
まず,文字列の文字数を数えましょう.
「関数の仮引数としてのポインタ」の練習問題を参考にしましょう.
int count = 0; while(???){ count++; ... } swap(???, count);
そして,swap関数に配列の先頭アドレスと文字数を渡して文字列を反転させましょう.
char tmp; for(int i=0;i<???;i++){ tmp = p[i]; p[i] = p[???]; p[???] = tmp; }
3. 以下のルールに沿ってある文字列(「TCznemhs!^lsngehJmAjr^UEbdadfkp 」)を解読しその答えを表示するプログラムを作成しなさい.
・main文は変更してはならない.
・関数 decode を作成する.
・関数 decode は受け取った文字列の偶数番目の文字の文字コードをインクリメント(+1)して表示.
void decode(const char *s) { /* ここを作成 */ } int main(void) { decode("TCznemhs!^lsngehJmAjr^UEbdadfkp "); return 0; } 実行結果: Dont_think_Feel!
この問題のポイントは,(i)受け取ったポインタのアドレスのインクリメント,(ii)ポインタが示すあるアドレスに格納されている文字コードのインクリメント. . . の2パターンのインクリメントをしっかり使い分けられるかが鍵となります.
上記の参考コードを例に取ると,ポインタのアドレスのインクリメントは
*s++;
ポインタが示すあるアドレスに格納されている文字コードのインクリメントは
*s+1;
となります.このことと,以下のさらなる参考コードをヒントに解いて下さい.
#include <stdio.h> void decode(const char *s) { int i = 1; while(*s){ if(. . .){ . . . } . . . } printf("\n"); } int main(void) { decode("TCznemhs!^lsngehJmAjr^UEbdadfkp "); return 0; }
3. 10個のint型配列を作成し,
1. create関数で,配列の各要素に,添字の値の2乗の値を格納し
2. print関数で,その結果を画面に表示する
プログラムである.
それぞれの関数の中ではポインタ変数を用いて配列アクセスせよ.
main()関数は変更しないこと.
変数 SIZE の値を 100 や 20 など任意の値に変更した場合でも正しく動作することを確認せよ.
void create(int *p, ...) { //...配列のi番目の値は *(p+i) または p[i] } void print(int *p, ...) { //... } int main(void) { const int SIZE = 10; int data[SIZE]; create(data, SIZE); print(data, SIZE); return 0; } 【実行例】 data[0] = 0 data[1] = 1 data[2] = 4 data[3] = 9 ....
この問題はポインタが示す先頭アドレスから数えてi番目(i = 0, 1, .. , SIZE)に格納されている値を参照できれば簡単です.
参照の仕方は上記の参考コード中にコメントアウトで書かれているとおりです.
- 6組
1. キーボードから一文字ずつ入力し,その文字をそれまで入力した文字列の 最後に追加するプログラムを作りなさい.文字列の最後を覚えておくポインタを 用いなさい.一文字ずつ入力する度に画面に文字列を表示し,10文字入力した時点で終わること.
1文字目を入力しEnterキーを押すと改行文字列を入力した文字として処理し、改行した空の1行を出力する処理が行われます。その後に2番目の文字を入力しても、3番目の文字列を入力した処理が行われます。
これを改善するためには、下記のように「%*c」句を「%c」変換子のあとに記述します。
「%*c」句で改行文字を除去します。
char moji;
scanf("%c%*c",&moji);
2. 文字列の中をスキャンして,キャラクタコード番号の 小さい順に1文字ずつ拾い,画面にプリントするプログラムを作りなさい. 表示するタイミングは,上記の10文字入力が終わった後(最後)とする. ソートを使わず,あくまでポインタを使ってスキャンして実現してください.
ここではポインタを使ってスキャンすることしか指定されていないのでやり方はいろいろあると思います。ポインタが理解できていればあまり難しくないので自分でやり方を考えて書いてみましょう。
3. 入力した文字列の中から英数字以外の文字(記号)のみを表示するプログラムを作成せよ. ただし,文字列のスキャニングにはポインタを用いること. また,配列の要素を示す括弧( [ ] ) は定義のとき以外で使用しないこと.
こちらもポインタを理解出来ていれば特に問題無いと思います。LECTURE6を振り返りながらやりましょう。
4.ポインタの2次元配列を用いて, 月の英語名称,和名のデータテーブルを作り, 月の番号(1,2,・・・,12)を入れると英名と和名が, 英名を入れると月番号と和名が表示されるようにせよ. また,存在しない月番号や英名を入力すると,誤りを指摘するようにエラー処理を行え.
ここでは入力する番号と データテーブルの先頭アドレスから数えた配列にアクセスできれば,画面表示するだけです.
また,2次元配列の場合,アドレスを保持しているのはmonth[0][0]のアドレスだけであり,month[1][0]にアクセスするときは
&month[0][0] + 1*(2次元の配列の要素数,今回であれば2)+0
となります
5. 文字列をキーボードから istr に入力し, 大文字を抜き出す関数 find_capital(), 小文字を抽出する関数 find_small() を作成せよ.
抽出した大文字の文字列は cstr に,小文字の文字列は sstr に それぞれ格納し,画面に出力せよ. 但し,配列へのアクセスはポインタを用いること. また,string.h に用意された関数を用いてはならない.
問題1、3ができれば問題なくできると思います。
文字列と配列
今回は文字列と配列のおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.
- 文字
文字は,char型を用いて表します.char型は符号なしの8ビット整数型です.つまり,文字は非負の整数値として扱われ,それぞれの文字に整数の文字コードが与えられています.
アスキーコード表にしたがって,文字' 0 ' ~ ' 9 'を16進数と10進数で表すと
文字 16進数 10進数 '0' 0x30 48 '1' 0x31 49 '2' 0x32 50 . . . '9' 0x39 57
のようになります.文字の' 0 'と数値の0は違うものなので注意しましょう.文字の' 0 'は16進数の0x30,10進数の48と同値です.
- 文字列
文字列は一連の文字の並びを表します.よって,文字列はchar型の配列に格納します.
文字列の終端を示す目印として,ヌル文字' \0 'を用います.
文字列の宣言は以下のように行います.
char str[100];
文字が何文字入力されるか事前には知ることができないため,配列の要素数は大きめにしておくと良いでしょう.
文字列の初期化は以下のように行います.
char str[4] = {'A', 'B', 'C', '\0'}; char str[4] = "ABC";
これはどちらも同じ処理です.配列の要素数はヌル文字を含めた数以上にする必要があります.
char str[4] = {'A', 'B', 'C', 'D'}; char str[4] = "ABCD";
は文字列ではなく,文字が4つ集まった配列となります.
printf関数で文字列を表示するときには,変換指定に%sを用います.文字を表示するときには,%cを用います.
char moji = 'A'; char mojiretsu[] = "ABC"; printf("%c\n", moji); printf("%s\n", mojiretsu); for(int i=0;mojiretsu[i];i++){ printf("%c", mojiretsu[i]); } printf("\n");
文字列をキーボードから読み込む場合は&演算子をつけません.詳しくはポインタの回で習います.また以下のようにscanf関数を用いて読み込むと,スペース以降の文字は保存されません.
char str[20]; printf("input a string : "); scanf("%s", str);
- 配列
配列は,同じ型の変数の集合を扱うときに利用します.複数の変数を単一の変数名で扱うことで,同じような処理を何度も繰り返し記述する必要がなくなり,非常に便利です.
- 配列の宣言
変数の宣言は
int n;
のように型と変数名で宣言したと思います.配列の宣言は,
int n[5];
のように要素型,変数名,要素数を宣言します.このとき,要素数は定数にしなければなりません.
- 配列の初期化
変数の初期化は
int n = 3;
のように行ったと思います.配列の初期化は,
int n[5] = {1, 2, 3, 4, 5};
のように各要素の値をコンマ区切りで並べ,{ }で囲みます.また,
int a[] = {1, 2, 3, 4, 5}; int b[5] = {1, 2};
のように書くこともできます.このときaの要素数は自動的に5に決定され,bの要素は,{ }内で与えられていない要素は0で初期化されるため,{1, 2, 0, 0, 0}になります.
- 配列の要素へのアクセス
配列の各要素には,
n[0] n[1] ...
のように添字演算子[ ]を用いてアクセスします.配列の先頭要素の添字は0であり,要素数-1までの整数値で各要素にアクセスできます.
- 配列のコピー
変数は,
int a = 1; int b = 0; b = a;
のように=で代入することができたと思います.しかし,配列は同じように代入することができません.
int a[3] = {1, 2, 3}; int b[3]; for(int i=0;i<3;i++){ b[i] = a[i]; }
コピーはfor文などの繰り返し文を用いて,各要素を逐一コピーします.
- 多次元配列
配列の要素を配列にすることで,2次元以上の配列を作ることもできます.例えば,
int n[2][3];
は2行3列の2次元配列であり,要素は,
n[0][0], n[0][1], n[0][2], n[1][0], n[1][1], n[1][2]
の6個です.
以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.
- 5組
1. キーボードから実数を10個入力し,全ての値をまず配列に格納し,そのなかの最大値,最小値を画面に表示せよ.
(配列の要素数を増減させても,正しく動作するプログラムとすること.以下同様.)
#include<stdio.h> int main(void) { const int N = 10; /* 配列の要素数 */ float data[N]; /* 実数10個分の配列 */ .../* データ入力 */ for(...) ... printf("最大値は %f \n", ... ); printf("最小値は %f \n", ... ); return 0; }
まず,最大値と最小値を格納するための変数を定義しましょう.今回入力されるのは実数です.用意する変数の型に注意しましょう.
次に,for文の中の処理を書いていきます.データ入力の方法は練習問題1を参考にしましょう.
最初のループで,用意した変数の両方に,とりあえず入力された値を代入し,それ以降のループで,最大値と最小値の探索を行うと良いと思います.
最大値の探索は練習問題2を参考にしましょう.最小値も似た処理を行えば良いはずです.
2. キーボードから正の整数を10個入力し配列に格納し,そのなかから奇数の数をカウントして画面に表示せよ.
正でない整数が入力されたらエラーを表示して,正しい値が入力されるまで繰り返し再入力させよ.
引数として配列とその要素数を受け取り,奇数の個数を返す関数count_oddを作ると良い.
まず,奇数の数を保存しておくための変数を定義しましょう.
次に,配列の各要素にデータを入力していきます.このとき,if文を用いて0以下の整数が入力された時,実行例のような文を出力するようにしましょう.
入力エラーの場合は奇数かどうかの判定を行う必要が無いため,それ以降の処理をスキップして,再入力させましょう.ループのスキップは第5回の授業で習いました.
このとき,添字に何か処理をしてからスキップしないと,入力エラーの要素に再入力のデータが上書きされません.
a[0]=10 a[1]=-3 入力エラー! 正の整数を入力 a[2] =0 入力エラー! 正の整数を入力 a[3]=5 . . .
再入力のデータを上書きできるように添字に処理を加えましょう.
あとは,if文を用いて,奇数をカウントし,最後にその値を表示させれば良いはずです.
3. キーボードから 2 以上の整数を10個入力し配列に格納し,次に,その中の素数の個数を数え表示するプログラムを作成せよ.
入力に際しては,以下のようなエラー処理を行うこと.
素数の数を数えて返す関数を作ること.
この問題では,最後に素数の数だけでなく,入力された素数の値も表示する必要があります.そのため,0などで初期化された同じ大きさの配列を1つ定義しておくと良いと思います.
データの入力やエラー処理は課題2と同様です.
素数の判定は第5回の課題4でやりました.同様の処理を行いましょう.もし素数であればカウントし,用意した配列にその値を代入しておきましょう.
最後に,用意した配列の代入された値とカウントした値を出力しましょう.
int prime_number[N] = ... int finel = -1; ... for( ... ){ ... if( ... ){ //もし素数ならば final = i; ... } } printf("素数は"); for( ... ){ if(i==final){ printf(" %d の", prime_number[i]); break; } else if(prime_number[i] ... ){ printf(" %d,", prime_number[i]); } } printf(" %d 個ありました.", count);
細かい見た目にこだわるならば,上のようにすれば最後に出力する素数の値の後ろには,コンマの代わりに’の’が出力され,きれいな表示になると思います.
4. キーボードから10個の整数を配列に入力し,(1). 配列の全要素を画面に表示, (2).次に大きい順に並べ替えて表示せよ.
ヒント(各自で調べてみよう):ソーティング,アルゴリズム,単純ソート,挿入ソート,バブルソート,ヒープソートなど
データ入力の方法は課題2と同様です.for文を用いて並べ替え前の各要素を表示させましょう.
大きい順に並べ替えるには,ある要素とそれより後ろの要素を比べて,後ろの要素の方が大きい場合にはその要素どうしを入れ替えるという処理を行います.この処理は2重ループを用いると良さそうです.また,2つの要素を入れ替えるためには,1つ変数を定義しておく必要があります.
int tmp; ... for(int i= ... ){ for(int j= ... ){ if(a[j]>a[i]){ tmp = ...
最後にfor文を用いて並べ替え後の各要素を表示させましょう.
5. 整数型配列の各要素を全て絶対値に変換して,変換前後の配列の中身をそれぞれ表示せよ.
for文を用いて変換前の各要素を表示しましょう.表示にこだわるならば最後の要素のときだけprint文を変えましょう.
if( ... ){ //最後の要素ならば printf(" %d", a[i]); } else{ printf(" %d,", a[i]); }
絶対値に変換するには,配列の要素の値が負の場合,その要素に-1をかければ良いはずです.
- 6組
1. 英数字から成る文字列を入力すると, それぞれの文字コードが1ずつ大きい文字列に変換するプログラムを作りなさい.
まず,char型配列に1文字ずつ格納していきましょう.そして,全配列内の中身を1回ずつインクリメントすればOKです.
2. 三次元ベクトル(x,y,z)を一次元配列を使って以下のようにつくる.
typedef float Vector[3];
このとき,
内積d = a・bを計算する関数 float d = dot(Vector a, Vector b);
外積c = a X b を計算する関数 void cross(Vector c, Vector a, Vector b);
をそれぞれ作成せよ.
Vector[0],Vector[1],Vector[2]に代入する中身をそれぞれx, y, zとして考えましょう.基本的に今まで習ってきたことを使いこなして,数学の定義にしたがって各配列の対象となる要素同士で計算させればいいだけです.注意すべきはcross関数で,求めるのはスカラ値ではないので,計算結果はベクトル値としてしっかり表示しましょう.
3. 一次元配列に文字列が格納されています(キーボードから入力してもよい). この文字列を,降順(asciiコード番号の反対順)に並べる(ソートする) プログラムを作ってください.
この問題の解き方の1例として,ある空のchar型配列に対して,文字列が格納されている配列の内,インデックスの一番最後から順番にコピーしていくことでソート出来ます.
//例えば文字列が格納されている配列aのサイズが10とすると char b[10]; //コピー先の配列を用意 for(int i=9; i>=0; i--){ for(int j ...){ b[j] = a[i]; } }
4. 複数の単語からなる文を入力すると,各単語の先頭文字を大文字に変えて表示するプログラムを作成せよ.
アスキーコードを見ると小文字と大文字の違いは 0x20 分違うことが見受けられます.ヒントとして以下に小文字から大文字にする関数を表記しますので参考にしてください.
void upper(char str){ if('a'<= str && str <= 'z'){ str = str -0x20; } printf("%c\n",str); }
これを用いて大文字から小文字を作ってください.
5. int num_list[] = {1, 2, 3, 4, 5, 6, 7} の数字の並びを, 任意の回数ローテーションさせるプログラムを作成せよ.
まず,ローテーションを1回するような関数を用意し,その関数をn回繰り返せるようにfor文でくくればOKです.
データ型
今回はデータ型のおさらいと課題の解法について解説したいと思います.
前回の授業”関数”では関数に渡す値のデータ型と関数の引数のデータ型,関数のデータ型とその戻り値のデータ型とその代入先の変数のデータ型を一致しなければコンパイルエラーとなっていたと思います.このコンパイルエラーはそのデータ型それぞれのbitサイズを合わせてほしいためです.今回の授業でそれが納得してくれれば幸いです.
あとは基本的には授業ページを参照してもらい,「こんなデータ型があるんだな」,「変数の宣言にはスコープを考慮しなくては」と各自復習してください.また,将来大規模なプログラムを書くときには今回の授業で習った構造体を用いることでコードの煩雑さが緩和され,可読性が上がるのでデバッグもしやすくなるので,構造体は是非マスターしてほしいですね.
では,問題の解説をしたいと思います.
- 5組
1. 摂氏温度を華氏温度に変換する関数c2fをつくりなさい. 引数として実数1個,戻り値も実数とする.
main関数を作成し,関数を呼び出して正しく動作するかチェックすること.
(摂氏=Celsius度,華氏=Fahrenheit度の意味や変換方法は,各自で調べること.)
#include... ??? c2f(...) { .... return ???; } int main(void) { float T; /* 温度を格納 */ printf(...); scanf(...); printf("摂氏 %f 度は,華氏 %f 度です.\n", T, c2f(T) ); return 0; }
この問題のポイントとなるのは関数自体のデータ型および引数のデータ型の定義です.このポイントさえ押さえられれば簡単な問題です.
2. 1 から n までの整数の和 ∑n を計算する関数 Sum を作成しなさい.
(この関数の引数は整数 n ,戻り値として計算した和を返す関数とすること.)
#include... ??? Sum(...) { .... return ???; } int main(void) { int n; printf(...); scanf(...); printf("1から %d までの和は %d です.\n", n, Sum(n) ); return 0; }
この問題のポイントとなるのはSum()内でおそらくfor()文等で計算した値をreturnできるかどうかにあります.おそらく上記の問題が解けたならば,この問題も楽勝でしょう.
3. キーボードから3次元の空間座標を入力すると,原点からの距離を返す関数pytagを作成せよ. キーボードからの入力および結果の画面への表示は全て main 関数内で行うこと.
ヒント:平方根の計算は,sqrt関数を使う.要#include
#include<math.h> ???? pytag???? { .... float root_two = sqrt(2.0); /* sqrt関数で,平方根の計算ができる */ } int main(...) { ... return 0; } /* 実行例: x座標を入力してください:3 y座標を入力してください:4 z座標を入力してください:5 距離:7.071068 x座標を入力してください:-5 y座標を入力してください:2 z座標を入力してください:-8 距離:9.643651 */
この問題のポイントとなるのはmath.hを利用することにあります.たとえば,距離を求めるためにデカルト座標系でいうところのx, y, z成分それぞれの2乗和が必要になりますが,単純に x*x + y*y + z*zで求めるのではなく,以下のように求めることも可能です.
float x = ...; float y = ...; float z = ...; int a = 2; x_jijou = pow(x, a); y_jijou = pow(y, a); z_jijou = pow(z, a);
このpow()関数はmath.hからincludeしています.
4. 整数を引数として,引数が素数かどうかを判定する関数 IsPrime() 関数を作成しなさい.
関数 IsPrime 内では素数の判定のみ行い,画面表示などは全て main 関数内で行うこと.
戻り値の意味などは各自で設定して良いが,コメントとしてソースコード中に記述しておくこと.
(例えば,素数の場合の関数の戻り値は1,それ以外は0とする,など.)
#include... ??? IsPrime(...) { .... return ???; } int main() { int n; /* 整数を格納 */ printf(...); scanf(...); .... }
この問題のポイントは関数内で判定した処理をどのように戻り値を与え,その戻り値をmain関数内でどのように処理をするかです.
多くの場合,c言語では二値判定の場合はbool型というものを使用します.今回の課題では例として以下のように使用します.
#include <stdio.h> bool Judgment(int n){ ........ if(nが素数){ return true; }else{ return false; } int main() { int num = ...; bool judge = Judgment(num); if(judge){ printf("numは素数\n"); }else{ printf("numは素数でない\n"); } return 0; }
このboolはtrueという文字列をソースコード中では1として扱い,falseは0として扱います.詳しいことは各自でぜひ調べてみて下さい.
5. 三つの実数を引数に取り,それらの中央値(メディアンという)を返す関数 median を作成せよ.
main 関数を用いて確認すること.
#include... ??? median(...) { .... return ???; } int main() { printf(...); scanf(...); .... } /* 実行例: a= 10.5 b= -3.9 c= 0.3 中央値は 0.3 です. */
この問題の攻略はメディアンというものを各自で調べ,その数学の定義をコードとして書き起こせるかにかかっています.それさえ出来ればあとはメディアンを戻り値として返すだけです.
- 6組
1.QUIZ1,2,3 を参考にして,入力した文字が大文字であれば小文字に変換し, 小文字であれば大文字に変換するプログラム作成しなさい. また入力は半角文字のみとする. キー入力と表示以外は組み込み型の関数は使わずに実現して下さい.
文字を入力して下さい: M m 文字を入力して下さい: e E 文字を入力して下さい: i I 文字を入力して下さい: j J 文字を入力して下さい: I i
アスキーコード表とquiz1,2,3を理解していれば難しくないと思います.一度の実行で一度入力、変換、表示したら終了して構いません。
.... += 'A' - 'a'
のようにするとアスキーコード表を覚えていなくても大文字小文字変換することができます.
2.QUIZ4を参考にして,一桁の自然数(数値)を引数にとり,文字(数字)を返す関数 toascii() を作れ. 但し変換にprintfなどを用いてはならない.
10進数を16進数に変換するときは10進数を16で割って余りを計算します.
char toascii(int number) { // write your code down here. } int main(void) { for(int i=0; i<10; i++){ char c = toascii(i); printf("%c",c); } printf("¥n"); }
char型の’0'にnumberを足していきましょう、そのままだとおそらくコンパイルで怒られるのでキャストしてから足しましょう。
3.unsigned short int型(16bit整数)の変数を引数として受け取り, 2進数を印字する関数
void print_short2binary(unsigned short);
を作成せよ.様々な値をテストするテスト用main()も作れ. テスト用を行うときの文字定義(コンパイルスイッチ)は"TEST"とせよ.
【ヒント:以下のプログラムの足らない部分を埋めなさい】 // 年組番号 氏名 // print_short2binary.cpp #include <stdio.h> void print_short2binary(unsigned short si) { for(int i=.... printf("%d", ....); if(!(i%4)) // 見やすくするために4桁毎にスペースを入れる printf(" "); } printf("\n"); } #if defined(TEST) int main(void) { print_short2binary(0x1234); print_short2binary(0x1248); print_short2binary(0x8421); print_short2binary(0xAAAA); print_short2binary(0x5555); } #endif
4.unsigned short int型の変数に256以上の値を入力したとき,下位8ビットはどんな数値になるかを 調べるため,次のようなプログラムを作りたい. 下位8ビットの部分を返す関数 unsigned char LSB8(unsigned short) をつくり, 2進数のビットパターン,16進,10進の3通りで表わす. この関数をチェックするテスト用main関数も同時に作れ.
【ヒント:以下のコードの穴埋めをせよ】 // 年組番号 氏名 // lsb8.cpp #include "print_short2binary.h" unsigned char lsb8(unsigned short us) { .... return uc; } #if defined(TEST) #include <stdio.h> int main(void) { for(int i=0; i<8; i++){ unsigned short x = 252 + i; printf("%d : %04X : ", x, x); print_char2binary( lsb8(x) ); } } #endif
5.上記のunsigned short型の変数のうち,上位8ビットはどんな数値になるかを調べたい. 上位8ビットの部分を返す関数 unsigned char MSB8(unsigned short) をつくり, 2進数のビットパターン,16進,10進の3通りで表わせ. テスト用main関数は上記と同様で良いが,関数のテストが正しく行える必要はある.
【ヒント:以下のコードの穴埋めをして正しいプログラムとせよ】 // 年組番号 氏名 // msb8.cpp #include "print_short2binary.h" unsigned char msb8(unsigned short us) { .... return uc; } #if defined(TEST) #include <stdio.h> int main(void) { for(int i=0; i<8; i++){ unsigned short x = 0x4321 + i*0x1111; printf("%5d : %04X : ", x, x); print_char2binary( msb8(x) ); } } #endif
関数
今回は関数のおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.
プログラムは関数が組み合わさって構成されています.今まで作成したプログラムには
int main(void){ ... }
という部分があったと思います.これもmain関数と呼ばれる関数の1つです.
関数は自分で定義することができ,ソースコードの中で何度も利用することができます.そのため,同じ処理を何度も記述する必要がなくなり,プログラムが簡潔になります.プログラム作成にかかる時間が短縮でき,バグの削減にもつながります.また,処理を関数として一度作成しておけば,他のソースコードで同様の処理を行いたい場合に,その関数を適用することもできるため,正しい関数をたくさん定義しておくことは非常に有益です.
- 関数の定義
関数の作り方を復習します.下に関数の書き方とその具体例を示します.
戻り値の型 関数名(仮引数の型 仮引数名1, 仮引数の型 仮引数名2)
{
処理 ...
return 戻り値;
}
int add_number(int number1, int number2) { int sum; //変数を定義 sum = number1 + number2; //number1とnumber2の和をsumに代入 return sum; }
まず,戻り値の型,関数名,仮引数の型,仮引数名を記述します.ここでは,戻り値sumの型はint型であるのでintとします.関数名はその関数がどのような処理を行っているのかわかる名前にすると良いでしょう.仮引数の型と個数は,関数の呼び出し元の実引数の型と個数に合わせなければなりません.
引数の個数はいくつでも良く,複数の場合はカンマで区切ります.戻り値の個数は0か1です.
void 関数名(void) { 処理... }
戻り値や引数がない関数は上のようにvoidと記述します.
- 関数の呼び出し
関数の使い方を復習します.下に具体例を示します.
#include <stdio.h> void print_sentence(void) { printf("りんごとみかんがあります.\n"); } void print_number(int number1, int number2) { printf("りんごは %d 個です.\n", number1); printf("みかんは %d 個です.\n", number2); } int add_number(int number3, int number4) { int sum; //変数を定義 sum = number3 + number4; //number3とnumber4の和をsumに代入 return sum; } int main(void) { int apple = 10; int orange = 25; int total; print_sentence(); print_number(apple, orange); total = add_number(apple, orange); printf("合計%d個\n", total); return 0; }
- プログラムが起動され,main関数が実行されます.
- 変数が定義されます.
- print_sentence( )で関数を呼び出します.引数がないので( )の中は空にします.プログラムの処理はprint_sentence関数へと移り,関数の中の処理が実行されます.末尾の}に到達すると,その関数を抜けてmain関数に戻ります.
- print_number(apple, orange)で関数を呼び出します.プログラムの処理はprint_number関数へと移り,関数の中の処理が実行されます.このときnumber1とnumber2に,それぞれappleとorangeの値の10と25が代入され,処理が実行されます.末尾の}に到達すると,その関数を抜けてmain関数に戻ります.
- add_number(apple, orange)で関数を呼び出します.プログラムの処理はadd_number関数へと移り,関数の中の処理が実行されます.このときnumber1とnumber2に,それぞれappleとorangeの値の10と25が代入されます.そして,関数の中の処理が実行され,return文に到達すると,その関数を抜けてmain関数に戻ります.このとき,戻り値sumがtotalに代入されます.
- totalが出力されます.
以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.
- 5組
1. 自然数を入力すると,1からその数値までの和を求め表示するプログラムを作成せよ.
5回の授業の内容が理解できていれば,簡単に解けると思います.for文やwhile文を用いて合計値を計算しましょう.また,0以下の数字が入力された場合はエラーと表示するようにプログラムを分岐させましょう.条件分岐は第4回の授業で習いました.
練習問題Q1を参考にすると良いと思います.
2. 縦横の長さを整数で入力すると,#で長方形を表示するプログラムを作れ.
2重ループを用いて#を表示させます.適切に改行して長方形を作成しましょう.また,負の値が入力された場合はエラーと表示するようにプログラムを分岐させましょう.
練習問題Q4を参考にすると良いと思います.
3. 月と月始めの曜日を入力すると,カレンダーを表示するプログラムを作成せよ.
第4回の練習問題Q3で何月が何日まであるかは分岐させることができるようになったと思います.この問題では1から28or30or31までをカレンダーのように配置するところがポイントです.
このプログラムを作成する方法の1つとして2重ループを用いることが考えられます.第5回の練習問題Q4を参考にすると良いと思います.日月火...土は最初にプリント文で表示させ,その下に6行7列の表を作りましょう.数字だけでなく適切に空白も表示することで何曜日始まりでも対応できるプログラムが作成できます.最後の日まで表示できたらbreak文でループから脱出する,もしくは空白を表示します.
... printf(" 日 月 火 水 木 金 土\n"); for(int i=0;i<=6;i++){ for(int j=0;j<=7;j++){ if(???? > day){ break; } else if( ... ){ ...
4. 入力した自然数以下の素数をすべて表示するプログラムを作成せよ.
この問題のポイントは素数の判定です.
素数とは1と自分自身以外に正の約数を持たない自然数で,1でない数のことです.そこでまず,2から入力された自然数までを表示するプログラムを作成しましょう.これは練習問題Q3を参考にすると良いと思います.それができたら,素数であるかどうかの判定をして,素数であれば表示するようにプログラムを追加しましょう.また1より小さい数字が入力された場合はエラーと表示するようにプログラムを分岐させましょう.
判定の一例として,下に示すようなフラグを用いた処理が考えられます.フラグの有無を条件にして表示するかしないかの分岐を行います.そして,ループの最後でフラグを戻し,次の数字の判定を同様に行います.このようにすれば素数だけを表示することができると思います.
他にもいろいろな方法があると思うので,試行錯誤してみましょう.プログラミングは書けば書くほど,考えれば考えるほど力がつきます.
繰り返しになりますが,素数とは1と自分自身に約数を持ちません.つまり,素数は自分より小さな数字で割られた時,剰余が0になることはありません.逆に,素数でない数字は剰余が0になることがあるということです.
int main(void){ int flag = 0; ... for( ... ){ //2から入力された自然数まですべての数を順番に調べていく ... if( ... ){ //素数の判定. もしその数が...なら flag = 1; //フラグをたてる } ... if(flag == ??){ //もしフラグが...なら printf("%d", ??);関数 } flag = 0; } ... }
5. 縦,横,斜めのいずれの方向においても,3数の和が全て等しくなるような値をプログラムにより求めよ.手計算でなく,必ずプログラムで解答を求めること.
数独とは若干異なる問題ですね.TAが考えた解法の1例を示します.下図のように左下のマス(”3行1列目の成分”と言います.詳しくは線形代数を勉強して下さい.)をxとおくと下図のように相対的関係から文字式が求まるマスがあります.
----------------------- | 3 | | | ----------------------- | 4 | 5 | x-2 | ----------------------- | x | | x-1 | -----------------------
ここまで求められたら実はxは一意に定まるので,この問題はただひとつの解にたどり着きます.この手法でワタクシTAがプログラムでこの問題を解くとすれば,和の組み合わせ方は8通りあるので,まずその8通りの組み合わせ方のうち,2つのマスに数字が含まれているものをfor文またはwhile文の中でif, else文を用いて探索します.見つけた組み合わせ方は何かしらの方法でリストとして記録して置きます.(今回は3通りと定まっていることは既知としても良いでしょう笑)そして,最初に見つけた組み合わせ方の未知数をxと宣言し,残り2つの組み合わせ方の未知数を差分から文字式として求めます.あとは,文字式としても求められていないマスの中が組み合わせとして重複しているマスと一致している場合を探索し,そこからxをfor文で-n~+nまで力技で方程式として成り立つxを求めます.これを残りの未知のマスを繰り返し探索して行います.この提案手法は手計算でやる手順を具現化しているつもりですが,かなり言葉足らずかと思われます.ご容赦下さい.(しかも皆さんの知らないコーディング技術も使わないと,かなり気合と根性が必要になってくるかと思われます.)
しかし,上記の3, 4, 5, x, x-1, x-2をプログラム上において既知としてしまえば,以下のようにもっとCPらしくゴリ押し計算で簡単に求めることもできます!
for(int n=-99999;n<99999;n++){ if(8通りすべての組み合わせ方の和が等しい){ ... } }
他にもっと良い手法もあるかもしれませんね!
- 6組
1.以下のサンプルコードのように,しきい値60以上であれば合格, それ未満であれば不合格となるように判定する関数 eval() を作って下さい.
まず、今回作成するeval()関数はmain文でどのように使われているでしょうか
if(eval(test_result)){ printf("テストの結果は「合格」です.\n"); }else{ printf("テストの結果は「不合格」です.\n"); }
if文の条件式で使われていますね、
if文はif(条件式)で条件式が正しければ真、間違っていれば偽を返す関数でした。
int a=0; if(a==0){ printf("aは0と等しい\n"); }else{ printf("aは0と等しくない\n"); }
このような使い方をすると条件式は成り立っているので「aは0と等しい」と表示されます。では、次の場合はどうでしょうか
int a=0; if(a=0){ printf("aは0と等しい\n"); }else{ printf("aは0と等しくない\n"); }
条件式がa==0からa=0に変わりました。この条件式でもコンパイルエラーにはならずにプログラムは作動します。この場合も「aは0と等しい」と表示されるような気がしますね、
ですが実はこの場合は条件式は偽の判定になり、「aは0と等しくない」と表示されます。これはC言語において
≠0 : 真
=0 : 偽
という決まりがあるためです。つまりif(0)は偽の判定、if(0以外)は真の判定になります。
以上のことを踏まえて問題に戻ると、eval()関数は引数の値がTHRESHOLD(=60)以上なら0以外の数(例えば1)、それ以下なら0を返す関数にすれば良いということになります。
2.直角三角形の短辺2つの長さ(a,b)を入力すると,長辺の長さ(c)を返す関数を作りましょう. a,bの値はそれぞれキーボードから入力します
三平方の定理を使いましょう.ルートの計算はmath.hをインクルードしてsqrt()関数を使います。
3.QUIZの5番目の問題は,1から3までの3個の2乗和を求めました. このプログラムを改良して,1からNまでの2乗和を求めるプログラムにしてください. このとき,Nの値は,scanf()関数でキーボードから与えられるようにしてください.
入力した数までの2乗和を足し続けるプログラムです.
int function(...){ int sum; ... for( ... ){ //2乗和の計算を行う } ... return sum; }
for文で足し続ける時に int sumという数を保存するようにして 戻り値にsum を取って表示するようにしてください.
4.速度0で静止しているロケットに点火してロケットを打ち上げます.ロケットの質量をm,推力が F で一定だとしたとき, 打ち上げから時間t秒後の速度v(t)を求める関数を作って下さい.引数はm,F,tで,戻り値がv(t)です.
空気抵抗を考慮する必要ないです.速度を求める公式 V = at + v0 を用いて関数を作ってください.また,加速度を求めるのは 運動方程式を利用して求めてください.
5.関数f(x)=sin(x^2)の数値積分を実行するプログラムを作成してください. なお,積分は最も単純な長方形近似で計算して良いです.
基本的な積分がわかっていれば特に困らないと思います。わからない時は積分の復習をしましょう。
問題3を参考にしてdx * function()をsumに足していきましょう
|
制御文2
授業のおさらいと課題を解くにあたっての考え方について簡単に解説したいと思います.
- if~else文
if(条件式){ 処理1 } else{ 処理2 }
elseは「~でなければ」という意味です.条件式が成り立つのであれば処理1が実行され,そうでなければ処理2が実行されます.つまり,elseを用いることで処理の選択を行うことができます.
ある条件が成り立つときのみ処理を行いたい場合はif文の基本形を用いて,条件成立の可否によって処理を違うものにしたい場合はif~else文を用いるようにしましょう.
- if文のネスト(入れ子)
if(条件式1){ if(条件式2){ 処理1 } else{ 処理2 } } else{ 処理3 }
上のようにif文の中にif文を記述する入れ子の構造をとることもできます.
条件式1が成立し条件式2も成立する場合は処理1,条件式1が成立し条件式2は成立しない場合は処理2,条件式1が成立しない場合は処理3が行われます.
- switch文
switch(条件){ case 定数1: 処理1 break; case 定数2: 処理2 case 定数3: 処理3 処理4 break; default: 処理5 break; }
if文は条件式成立の可否によってプログラムを2つに分岐することができました.switch文は条件の値によって,一度にたくさんの分岐をさせたい場合に用います.if~elseをたくさん記述すれば同様の処理が行えますが,switch文の方が簡潔な表現が行えます.
条件の値と一致するcase文の処理を行い,break文がない場合は,プログラムが次の文へと落ちていきます.上の場合,条件が定数1であれば処理1,条件が定数2であれば処理2,3,4,条件が定数3であれば処理3,4,条件がどの定数とも一致しない場合は処理5を行います.
- 多重ループ
for(初期化式1;条件式1;値変化1){ for(初期化式2;条件式2;値変化2){ 処理1 } }
for文による繰り返しは前回紹介しました.前回紹介した繰り返しは単純な構造でしたが,上のように繰り返しの中で繰り返しを行うことで2重の繰り返しが行えます.
以上が,授業のおさらいです.しっかりと復習をしておきましょう.
では,課題の考え方について解説したいと思います.
- 5組
1. キーボードから整数を入力し,その数が11の倍数または17の倍数,かつ,1000以上2000未満に該当する数かどうかを表示せよ.
キーボードから整数を入力する部分はもうできると思います.ポイントは問題文を満たす条件分岐の記述です.
”かつ”と”または”をしっかりと意識して条件文を記述しましょう.論理演算子"かつ"は"&&","または"は"||"でした.今回は1つの条件式でも,if文のネストを使っても,解けると思います.ここで
if(条件式1); 処理1
のようにすると処理1はif文とは関係のない文となり,必ず実行されてしまいます.if文の条件式の後ろにセミコロンは付けないように注意しましょう.
倍数の判定は剰余が0であるか否かです.剰余の演算は第3回の講義ページを復習しましょう.また等価性の演算子は"=="でした.代入"="と間違えないようにしましょう.
等価演算子や関係演算子は2項演算子です."a==b==c"や"5<=a<10"は正しく判定されません."a==b && b==c"や"5<=a && a<10"と記述しましょう.
if(11の倍数 または 17の倍数){ if(1000以上 かつ 2000未満){ 処理1 } else{ 処理2 } } else{ 処理3 }
if((11の倍数 または 17の倍数) かつ (1000以上 かつ 2000未満)){ 処理1 } else{ 処理2 }
2. キーボードから西暦を入力すると,その年が閏年かどうかを表示せよ.閏年については,ここ(国立天文台)を参照
ポイントは課題1と同じです.正しく条件文を記述しましょう."400で割り切れない年"は"400で割った剰余が0ではない年"です.
3. 前の課題を参考にして,「西暦」と「何月」かを入力すると,その月は何日あるかを表示せよ.月に1-12以外の数値が入力されたらエラーを返すこと. もちろん,閏年を考慮すること.
練習問題Q3と課題2を組み合わせることで解くことができます.2月の場合にその年が閏年であるか否かの判定をします.
if(month==2){ if(year%4==0){...
case 2: if(year%4==0){...
4.今日の最高気温を実数で入力すると,「夏日」「真夏日」「猛暑日」「それ以外」のいずれかを画面に表示するプログラムを書きなさい. 用語の定義については,各自で気象庁のWebページを調べること.ソースコード中にコメントとして情報元のWebサイトURLや書籍名をコメント/**/として記入すること.
if~else if~else文を用いることで簡潔に記述できると思います.
if(temperature < ??){ printf(" ... "); } else if(?? <= temperature && ... ){ ...
5.じゃんけんの勝敗判定を行いたい.グー=0,チョキ=1,パー=2として,A君とB君の2人の手を入力すると,勝敗またはあいこを判定して表示せよ.入力エラー処理も行うこと.
if~else if~else文やswitch文を用いて正しく条件分岐をしましょう.条件式は2人の手の組み合わせを9通り記述する方法や,数字の差をとる方法などがあると思います.
- 6組
1.縦と横の長さを入力し,"#"で埋め尽くされた長方形を作って下さい.
この問題のループのネスト(多重ループ)で頭が混乱してしまう人はまず次の問題を説いてみてください。
「横の長さ8、縦の長さがn(入力値)の長方形を作ってください。」
この問題は次のようになると思います。
for(int i=0; i<n; i++){ printf("########"); printf("\n"); }
このコードなら混乱せずに理解できると思います。このコードは次のようにも書けます。見た目は違いますが全く同じ結果になります。
for(int i=0; i<n; i++){ printf("#"); printf("#"); printf("#"); printf("#"); printf("#"); printf("#"); printf("#"); printf("#"); printf("\n"); }
とても頭の悪い書き方ですね。ではこの繰り返している部分をfor文で書き換えてみましょう。
2.縦と横の長さを入力し,"#"で埋め尽くされた逆三角形を作って下さい. ただし,縦の回数が終わったらそのままプログラムを終了させ, 横の#がなくなった場合も終了してください.
問題2は問題1と似ていますね、問題1では横に繰り返す回数が入力した定数でしたが、問題2では横の繰り返し回数が変わってきます。この場合は2つめのループで1つ目のループで使用している変数を使用することで横の繰り返し回数を変化させることができます。
for(int i=0; ...){ for(int j=0; /*ここでiをつかう*/ ; ... ) ...
3.for文を2回使用して以下に示すような九九の計算結果を表示するプログラムを作りなさい. このとき,桁をきちんと合わせて表示すること.
for文を2回使う参考として、以下のソースを例に作成して見てください.
for(int row=1; ...){ for(int col=1; ... int val = ...; printf(" %2d", val); ... } }
九九を表示する上で、まずかける数とかけられる数(row,col)を作ります.for文の中にfor文を入れることで 『row= 1 の時 col のfor文が始まり, col の for文が終わると,rowの値が1つ増え,またcol のfor文が始まる … 』となります.
また、今回の課題では桁をきちんと合わせて表示をしなければなりません.そこで用いられるのが
printf(" %2d", val);
という形です.今までは %d という形を使われていたと思うのですが,% と dの間に数字を入れることで,その数分のスペースを確保しての表示をすることが出来ます.
今回では2桁の数に揃えるべきなので %2d を用いることになります.
4.switch文を使って,1, 2, 3の入力に応じて,入力した数字が表示されるプログラムを書いて下さい. それ以外の数字の場合は,繰り返し1-3の数字の入力を求め,0が入力されたら終了するようにしてください.
この問題では,switch文の条件に当たる変数こそが入力値になります.また,入力値が0となるまでプログラムを実行し続けなければならないため,以下のようにコーディングする必要があります.
while(. . .){ switch(. . .){ case 1: . . . } }
ポイントは,どのようにwhile()ループを抜けるかという点ですね.
5.while文を用い (0<=x<=2π) の範囲で,f(x)=sin(x) の極大値fmax(a)とその位置 a を探索し求めよ. なお探索時のxの刻み幅 dx=0.0001 とする.
この問題では高校数学で予想はつくと思うのですが、極大値とはどのような値なのか今一度考えてみてください.
ここで登場する
#include<math.h>
とは,数値演算の関数が詰まったライブラリになってます.ここではsin(x)の関数を用いるために宣言をします.今後様々な関数を使うことがあるので覚えておきましょう.
while文を用いる上でどのタイミングでプログラムを終了させるか考えないと終わらないことにも注意してください.終わらせるやり方は授業ページを見直しましょう.