joho1-2017の日記

joho1-2017の日記

情報処理実習1の解説ブログです.皆さんが課題を解く時の助けになれば幸いです.

模擬テストと解説

6組

模擬テストの結果はどうだったでしょうか.
時間内に提出できていなかった人が何人かいましたが,来週の本番で提出できなかった場合には,当然0点となってしまいます.何度でも再提出可能なので,1題解くごとに(解けていなくても),こまめに提出するようにしてください.テスト開始前からログインして,提出ページを開いておくといいかと思います.
授業中の説明にもあった通り,課題と中間テストの結果を集計した結果,現時点では全員が単位取得可能であることがわかりました.
一夜漬けでどうにかなるようなものではありませんが,皆さんが一週間学習しても理解できないような難しい内容ではないので,いまついていけなくなってしまっている人も,今週一週間頑張ってみてください.


構造体と共用体

今週は構造体と共用体について学習しました.
構造体を利用することで,コードが読みやすくなるといったメリットがあります.
構造体と共用体の違いは,授業中の説明にあった通り,メモリを各メンバで共有するかどうかにあります.例えば各メンバがそれぞれdouble型の配列であるような場合に,環境によっては構造体のメモリを確保できずに,実行できないかもしれません.
共用体については,授業課題を解く上で,しばらく使う機会はないかと思いますが,コードを読んだ時に,何をしているのかがわかる程度には覚えておいてください.
以下,課題の解説です.

1. ファイル【3d_points.csv】には個数不明の3次元座標点が記述されている.但し最大個数は 10^4以下とする.これらの重心座標を計算し表示するプログラムを作成せよ.また,座標点の個数も併せて表示すること.main関数は下記の通りとする.

int main(int argc, char* argv[])
{
    Point p[N]; 

    int size = load(p, argv[1]);
    if(!size){ 
        printf("データがありません\n"); 
        return -1; 
    }
    printf("座標点の数は %d です.\n",size); 

    Point cent_p = calc(p,size); 
    printf("重心点の座標は\n(%f %f %f)\nです.\n",cent_p.x, cent_p.y, cent_p.z); 
    return 0; 
} 

まず構造体の定義は,三次元点群なので,メンバ変数にはdouble型のx, y, zを用意すればいいですね.csvファイルからの読み込みは前回の課題2が参考になるかと思います.double型なのでprintfだと%fでscanfでは%lfとすればよかったですね.読み込みの際に個数をカウントする必要がありますが,これはループの回った回数(breakした時点でのcounterの値)をみるといいでしょう.重心の計算は授業中のQ12-5と同様です.


問題は同じですが,5組はデータの個数が50個と決まっていますね.つまり,個数のカウントはしなくても解くことができます.関数は,先生が授業ページに載せているような形で作ってもらえれば問題ないと思います.
ファイル 【3d_points.csv】には,50個の3次元座標値が,カンマ区切りで記述されている.ダウンロードして,エディタで開いてみよ.ダブルクリックするとExcelが起動する.

まず,
(1)float または double 型 3 つの変数で 3 次元座標を表す構造体 Point を定義し
(2)ファイルから50個のデータを読み込む関数 load を作成
(3)これらの重心座標を返す calc 関数を用い
重心位置を画面に表示するプログラムを作成せよ.

ヒント:ファイル内には1行あたり,カンマ区切りで実数が3つ並んでいるので, fscanf(fp, "%f,%f,%f", &..., &..., &...);とすれば,1行分の値3つの読み込みができる.
double 型の場合は,%lf を使用すること.


2. ファイル【rugby_matchup.csv】には各行に明治大学のチーム名(A or B),対戦相手,得点,失点が記されている. このファイルを読み込み,これら各要素に加え勝敗(Win,Lose,Draw)を格納する構造体を作成し,チーム名を入力するとそのチームの結果を全て表示するプログラムを作成せよ.

//実行例 
Which team? 
team=A 
   Tsukuba :  41 vs  21 => Win 
    Aoyama :  80 vs   0 => Win 
   Meigaku : 106 vs   0 => Win 
      Keio :  40 vs  17 => Win 
    Teikyo :   6 vs  31 => Lose 
    Waseda :  24 vs  37 => Lose 
      Joho :  31 vs  31 => Draw 

メンバを,チーム名,対戦相手,得点,失点,勝敗の5つとして,構造体を定義します.読み込み部分は[課題1]とほとんど同じですが,勝敗の部分は得点と失点の大小関係から与える必要があります.実行時にキーボードから入力されたチーム名を格納するのは,scanfを使えばいいですね.もう何度も登場しているので大丈夫でしょう.あとは読み取ったチーム名と同じチーム名のデータだけを表示してあげれば完成です.for文で構造体の配列を順に見ていけば実現できそうです.
最後に表示の際のbreakの判定ですが,例えば,csvファイルから読み取り,構造体の配列にデータを格納していくときに,どれかメンバの最後の値を-1に設定しておけば,for(int i=0; data.some[i]+1; i++)として終了の判定ができます.


3. 5人分の名前(char型),身長[cm](float型),体重[kg](float型)の順に記述された ファイル【health_data.csv】を読み込み,以下の要件を満たすプログラムを作成しなさい.
・名前,身長,体重,BMI指数を格納することのできる構造体を作成する.
・health_data.csvから読み込んだ値を用いてそれぞれのBMI指数を計算した後作成した構造体に格納する.
・「名前,身長,体重,BMI指数」の順番で各生徒のデータを書き出す.

//実行例
Name    Height Weight BMI
Aida    170.6  70.7   24.3 
Iijima  158.3  90.9   36.3 
Uehara  167.8  64.0   22.7 
Eguchi  193.1  50.2   13.5 
Ono     173.4  70.6   23.5

この問題は[課題2]と同様にcsvファイルから読み込み,BMI = (Weight) / (Height) / (Height) として格納してあげれば良さそうです.表示も[課題2]と同様にできそうです.
いずれの課題も表示がしっかりそろうように,printfのフォーマットを工夫してください.

課題の解説は以上です.

来週の授業はいままでのおさらい(模擬テスト)となります.それまでに,今までの授業でやった内容を各自でしっかり復習しておくようにしてください.シラバスにある通り,期末テストの点数が成績評価の大部分を占めています.来週のおさらいを効率的に活用するためにも,わからない部分がある人は,ブログでの質問やオフィスアワー等を利用して早めに解決するようにしてください.
それでは,課題提出頑張ってください.



ポインタ2

今週は先週に引き続き,ポインタについて学習しました.
そして今回新たに扱うのがファイルポインタです.
今まで,画面の入出力で扱ってきた,printfやscanfなどは,ファイル名が省略されていたもので,ファイル名を指定して使うのがfprintf, fscanfなどと考えると,使い方(引数や返り値の型)を覚えやすいと思います.
以下課題の解説です.

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 は 153 個ありました.
b は   0 個ありました.
c は  76 個ありました.
d は  44 個ありました.
e は 217 個ありました.
...

1 は 217 個ありました.
2 は 217 個ありました.
...
" は   6 個ありました.
! は   1 個ありました.
? は   2 個ありました.
...

この課題は,問題文を読んだだけでも,

ファイルの中身を画面に表示
文章を構成するアルファベット,数字,記号の個数を表示

の2つがやるべき事としてありますね.こういった場合,まずはファイルを読み込んで表示するところまでコードを書き終えたら,コンパイルをして実行してみます.ここまでうまく実行できている事が確認できれば,もし後半の個数を表示するところまで書き終えてコンパイル・実行した時にうまくいかなくても,前半部分がうまく機能している事は確認できているので,ダメな部分は後半書いたコードのどこかに絞れるわけです.このようにこまめにコンパイル・実行をする事で,一見無駄に時間をかけているように思えても,結果的には効率的にプログラムコーディングが出来るのです.騙されたと思って,ぜひ一度試してみてください!

 では,課題のヒントに入ります.

課題1の問題文は上で記述しているので,省略します.この課題のポイントは,
 ①ファイルの中身を読み込んだ際の処理
 ②アルファベット,数字,記号のカウント方法
です.①に関しては,もし表示するだけならfgetc( )やfgets( ),fscanf( )をそのままprintf関数で表示するだけでいいですが,今回は文章中の文字についてカウントする処理が②にあります.なので,文字列(char型の配列)に代入しておくのが良いでしょう.
②に関しては,①で文章を格納した文字列に対して,繰り返し処理でアルファベット,数字,記号のそれぞれどれに属するのかを1文字ずつ条件判定させていき,個数をカウントする変数をprintf関数で表示させると,この問題は解決です.

2.以下のリンクからテキストファイルinputdata.txtをダウンロードせよ.
このファイルを読み込み,一行ごとの数値(5個のデータ)の平均値を行の最後に追加し,別のファイルに書き込むプログラムを作成せよ.
出力するファイル名は,outputdata.txt とする.

【ヒント】上記ファイルには,1行に5個の数値データが格納されている.今回は,データ個数が事前に分かっているとしてよい.

【ヒント】最初に画面に表示するようにプログラムをつくり,うまく出来るようになったら同じ内容をファイルに書くようにすれば比較的短時間でできる.

この課題のポイントは,
 ①ファイルの中身を読み込んだ際の処理
 ②代入する配列のサイズ
です.課題1と同様,読み込んだファイルの中身をただ表示するわけではないので,①に関して,配列に代入した方が良いでしょう.
②に関して,用意する配列の要素数はどうするべきでしょう?ヒントの通り,1行5個のデータ個数だから,要素数も5個で良いでしょうか?問題文を見ると,「一行ごとの数値の平均値を行の最後に追加」とありますね.それなら,初めから6個分(読み込むデータ個数+1個)の要素数の配列を宣言するのが良いかと思います.

3.このファイルは test_result.csv 内には,各行に生徒の名前と3科目のテストの点数がカンマ( , )区切りで記述されている.
このファイルの中身を読み取り,各生徒の合計点数を算出してその値が大きい順に並べてファイル test_rank.csv に出力するプログラムを作成せよ.
ヒント:1 行分のデータを読み込むには,ファイル内のデータ形式に合わせて書式指定文字列に,fscanf(fp, "%s, %d, %d, %d", ...); を使用すればよい. (test_rank.csvの各行に,名前と合計点をカンマ区切りで記し,最後の行に平均点を追加すること.)

//出力ファイルの中身
Yuma,285
Alicia,276
Clarice,274
Jean,273
《中略》
Flora,214
Elena,206
Kate,165
average,247.13

この課題のポイントは,
 ①fscanf関数を使う際の代入する配列
 ②合計点数の大きい順に並び替え
です.課題1,2でも処理したように,この問題でもやはりファイルの中身を読み込む時に代入する配列を用意するのが適当です.しかし,今回は1列目が文字列,2列目以降は整数と,型が違うため,配列1つでは足りません.それぞれに対応した配列を用意しましょう.②に関して,これまでも並び替えを行うswap関数を自作したと思います.そのため,詳しい処理は省きますが,並び変えるのは名前の配列と合計点の配列の2つがあるので注意しましょう.

4.ファイル lyrics.txt を読み込み,"*iller"が含まれる行全体だけを抜き出して表示するプログラムを作成せよ(*は任意の文字).

//出力の例
'Cause this is thriller, thriller night
You know it's thriller, thriller night
You're fighting for your life inside a killer, thriller tonight
・・・

この課題のポイントは,
 ①"*iller"を含む行の判定
です.この課題は行単位で判断していけばいいので,読み込みにはfgets( )を使えば良いと思います.そして①に関して,1文字ずつ判定するのも悪くはないのですが,どうせなら文字列処理関数の中の検索できる関数を使いましょう!どんな関数があるのかは,「C言語 文字列処理関数」で検索してみよう.

6組

1. Quiz 6を参考にして,ポインタの配列を用いて, 月の英語名称のデータテーブルを作り, 月の番号(1,2,・・・,12)を入れると英名が, または英名を入れると月番号が表示されるようにせよ. また,存在しない月番号や英名を入力すると,誤りを指摘するようにエラー処理を行え.
まず,月の番号を引数として,月名を返す関数については簡単に作れると思います.受け取った数値をそのまま月名の配列に渡してあげればいいですね.月名から番号を返す方は,strcmp関数を使うのが簡単かと思います.月名の配列をループで見ていき,受け取った月名と一致したところでbreakする.といった形で実装できるかと思います.ループが最後まで回れば,一致する文字列がなかった,すなわち入力が誤りだったということですね.
あとは入力が数値なのか,文字列なのかを判断する必要がありますが,今回atoi関数を使うのがシンプルです.数値に変換できなかった場合は0を返すので,atoiの返り値が1~12の範囲であれば,数値として扱い,それ以外であれば文字列として扱えばよいかと思います.ただしatoiは先頭が数値であれば変換できてしまうので,余裕がある人はそこの部分のエラー処理についても考えてみてください.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* month_db[] = {
	"January", ...};

int name2num(char* name)
{
	//...
}

const char* num2name(int num)
{
	//...
}

void print(char strin[])
{
	//...
	if(...){
		printf("%s\n", num2name(num));
	}else{
		num = name2num(strin);
		if(...){
			printf("%d\n", num);
		}else{
			puts("入力が誤っています.");
		}
	}
}

int main()
{
	char strin[128];

	printf("input: ");
	scanf("%s", strin);
	print(strin);

	return 0;
}

2. このファイルは【as_test_result.csv】 内には各行に生徒の名前と3科目のテストの点数がカンマ( , )区切りで記述されている(並びは名前順). このファイルの中身を読み取り,各生徒の合計点数を算出してその値が大きい順に並べて ファイルに出力するプログラムを作成せよ. なお,出力ファイル名はtest_rank.csvとし,各行に名前と合計点をカンマ区切りで記し, 最後の行に平均点を追加すること.
今週の3つの課題中で,この課題2が一番むずかしいのではないでしょうか.csvファイルから読み込む部分は,授業中の演習課題を参考にすれば書けるかと思います.もしもfscanfを使いたい場合は"%s"としてしまうとカンマも読み込んでしまうので,気をつけてください.カンマまでを読み込みたい場合はスキャン集合指定子(%[^,])を使えば良さそうですね.
この課題はcsvファイルから読み込んだ値を変数に格納する部分が少し難しいかもしれませんが,ここができればあとは,今まで一度は書いたことがあるような処理ばかりかと思います.

#include <stdio.h>
#include <stdlib.h>

void load(char name[][128], ...)
{
	//...
	for(; fscanf(fp, "%[^,],%d,%d,%d\n", ...
	//...
}

void calc_sum(int score[][3], ...)
{
	//...
}

void write_csv(char name[][128], ...)
{
	//...
	for(...){
	    //...
	}
	fprintf(fp, "average,%.2f\n", ...);
}

void sort(char name[][128], ...)
{
	//...
}

int main()
{
	char name[512][128];
	int score[512][3];
        //...

	load(name, score);
	calc_sum(score, sum);
	sort(name, sum);
	write_csv(name, sum);

	return 0;
}

3. 行列データをメモリイメージそのままバイナリファイル 【as_matrix_image.bin】 に保存した. このデータを上記の行列変数に読み込み,画面に表示するプログラムを作れ.

この課題はバイナリファイルを扱うことに,少し抵抗を感じるかもしれませんが,おそらくプログラム自体は今回の課題の中で最も簡単に書けるかと思います.読み込み部分はfread関数を使えばいいですね.授業ページを参考にしてください.このとき何バイトのデータをいくつ読み込むのかの部分は,サイズがdoubleで,個数は4x4の行列なので16となります.バッファ変数はMatrix型のアドレスを渡せばいいですね.

#include <stdio.h>
#include <stdlib.h>

typedef double Matrix[4][4];

void load(...)
{
	// ...
}

int main()
{
	Matrix m;
	load(m);
	for(int i=0; i<4; i++){
		for(int j=0; j<4; j++){
			printf("%.1f, ", m[i][j]);
		}
		puts("");
	}
	return 0;
}




ポインタの習得は難しいかと思いますが,とにかく手を動かしてみることが最も近道だと思います.コンパイル -> 実行 -> うまくいかなければprintfで途中の値を出力してみて...といういつもの手順を繰り返してみてください.
ポインタをしっかり扱えるようになると,C言語初級者から中級者にランクアップしたと言えるかと思います.難しい場所ではありますが時間をかけて頑張ってください.

Remove all ads

ポインタ1

今回の授業では,ポインタを扱いました.

ポインタとは,簡単にまとめると「変数等のメモリアドレスのこと」を言います.
イメージは,ホテルや旅館等の部屋番号がポインタ(アドレス)でその部屋の中身(お客さんなど)が変数の数字だと思ってください.

例えば,今の例を下のようなソースコードに当てはめて考えると,

int a = 0;    //int型の変数aを定義
int *p = &a;    //ポインタ変数を定義

と宣言した場合は,ある旅館で「0」さんが「int」というグレードの「&a(仲居さん達には「*p」の部屋と呼ばれている)」号室の部屋を「a」という名前で予約した.というイメージです.かなり乱暴ですがこのようなイメージを持っているとポインタについて理解しやすいと思います.
その他,授業で取り扱った内容は細かいテクニック的な部分が多いので,おいおい覚えて行くようにしてください.

では,課題の解説を行います.

1. 以下のルールに沿ってある文字列(「TCznemhs!^lsngehJmAjr^UEbdadfkp 」)を解読しその答えを表示するプログラムを作成しなさい.

  • main文は変更してはならない.
  • 関数 decode を作成する.
  • 関数 decode は受け取った文字列の偶数番目の文字のみ文字コードをインクリメントして表示する.

void decode(const char *s)
{
    /* ここを作成 */
}

int main(void)
{

    decode("TCznemhs!^lsngehJmAjr^UEbdadfkp ");

    return 0;

}
//実行結果 
Dont_think_Feel!

この問題を解くポイントは,

  1. 偶数番目をどのように判定するか?
  2. 文字コードのインクリメントはどのようにやるか?

の2点です.

まず,偶数番目の判定は「2で割ったあまりに注目して判定すると良いでしょう」.
文字コードのインクリメントに関しては,例えば

char str[] = "meiji";
char *p = str;

のようにある文字列strがポインタ変数pを用いて表現できるとき,

*p++;
printf("%c", *p);

(*p)++;
printf("%c", *p);

の違いについて理解していれば解けるかと思います.

2. 入力した文字列の中から英数字以外の文字(記号)のみを表示する関数search_symbolsを追加し,プログラムを完成せよ.
配列の要素を示す括弧( [ ] ) は,下記の配列定義のとき以外で使用しないこと.

void search_symbols(char* p)
{
...
}

void input(char* p)
{
    printf("文字列を入力: ");
    scanf("%[^\n]", p);
}

int main(void)
{
    char str[100];

    input(str);
    search_symbols(str);

    return 0;
}
//実行例
文字列を入力: C++ & 'Oh-o!' "Meiji!" No.1;
記号: ++ & '-!' "!" .;

この問題のポイントは,

  1. 文字列を配列として扱わないこと
  2. 文字とそれ以外の記号をどのように区別するか

という2点です.

まず,文字列を配列として扱わないことに関してですが,これは「ポインタ変数」を利用することで解決できます.
次に,文字列とそれ以外の記号の区別の仕方ですが,これはアスキーコードを利用しましょう.

アスキーコードでは,大文字が16進数において「0x41から0x5a」に該当し,小文字が「0x61から0x7a」に該当します.この範囲外の文字を表示すれば問題は解けるはずです.


 

3. 整数値を3つキーボードより入力して配列に収め,それぞれを3乗して返す関数 cubic を作りなさい.
ただし関数 cubic の戻り値の型は void 型とし,N = 3 以外でも正しく動作すること.
すなわち,下記でNの宣言時に値を3以外に変更してもそのまま動作するようにすること.

void cubic(....)
{
    //ここを作成
}
                
int main(void)
{
    const int N = 3;
    int data[N];
                
    printf("整数値を %d 個入力して下さい\n", N);

    for(int i=0; i<N; i++){
        printf("data[%d]:", i);
        scanf("%d", &data[i]);
    }

    cubic(data, N);    /* 3乗の計算 */

    printf("計算結果\n");

    for(int i=0; i<N; i++) {
        printf("data[%d]:%d\n", i, data[i]);
    }

    return 0;
}
実行例:

整数値を 3 個入力して下さい
data[0]:2
data[1]:3
data[2]:4
計算結果
data[0]:8
data[1]:27
data[2]:64

 この課題のポイントは,
 ①関数への引数
です.この課題は,実はポインタを使わずにプログラムできます.
ここでは,配列をそのまま引数として渡し,cubic関数内では配列を計算すればいいわけです.

 4.入力された文字列を全て小文字に変換するmy_tolower関数を作成せよ.
ローマ字以外の文字は何もしないこと.
ライブラリ関数(string.h内にある)tolower関数を使用しないこと.

void my_tolower(char* p)
{
    /* ここを作成 */
}

int main(void)
{
    char str[100];

    printf("文字列を入力:");
    scanf("%[^\n]", str);

    printf("変換前:%s\n", str);

    my_tolower(str);

    printf("変換後:%s\n", str);

    return 0;
}
実行例
文字列を入力:ABCdef12345!
変換前:ABCdef12345!
変換後:abcdef12345!

この課題のポイントは,
 ①大文字の認識
 ②次の文字のアドレスへのアクセス方法
です.①は「第9回 中間テスト」の記事で同じような問題の考え方のヒントを書いています.それを参考にしましょう.
②は,本記事の2.の②で考えたのと同じ方法でアクセスできるので,ここでは省略します.

5.2つの文字列をキーボードからそれぞれ str1 と str2 に入力し,

  • str1 からは大文字のみを抜き出し(find_capital関数を作成)
  • str2 からは小文字のみを抜き出し(find_small関数を作成)
  • それぞれを結合したものを文字列変数 str に順に結合し,

最後に str を表示するプログラムを作成せよ.
但し,配列へのアクセスは必ずポインタを用いること.
また,string.h に用意されたライブラリ関数を使用しないこと.

... find_capital(...)
{
    ...
}

... find_small(...)
{
...
}

void input(char* str)
{
    printf("文字列入力:");
    scanf("%[^\n]%*c",str);
}
                    
int main(void)
{
    const int N = 100;
    char str1[N], str2[N], str[2*N];    /* 空の文字列を用意.*/

    input(str1);
    input(str2);
                    
    /* ... ここで自作の関数を呼び出す.*/
                    
    printf("===> %s\n", str);
}
//実行例
str1入力:kaMjdEIsjfuaJusfhIjauxx
str2入力:KmeJACBAiVOADAKjACYBiAQ
===> MEIJImeiji

 この課題のポイントは,
 ①大文字,小文字の認識
 ②各関数の型および引数
です.①おなじみアスキーコード表の大文字,小文字に対応する値の時にのみ処理をおこなえば良いだけです.
②は,まず①の処理を行うために,input( )で代入した文字列を格納した配列が引数で必要ですね.そして,①で認識した文字を格納する配列も必要です.つまり,各関数には最低2つの引数が必要で す.そして,各関数の目的は配列への文字の格納です.つまり,返り値は必要ありません.ということは,関数の型はもう何を選択すればいいかわかりますね.

 

解説は以上です.課題提出をがんばってください.

Remove all ads

中間テスト

解答例は必ずしも満点解答とは限らないことに注意してください.

 

Remove all ads

おさらい

5組

今週は文字列について勉強しました.

以下,課題の解説です.

1.キーボードから1文字入力すると, その文字の文字コードを16進数で表示し, その文字が大文字か,小文字か,数値か,それ以外かを表示するプログラム作成しなさい.
入力は半角文字1文字のみとする.
ヒント:大文字,小文字,数値の区別は,文字コードの範囲によって判定可能.
1文字入力は,scanf()では%cを使用する.getchar()関数というのも有る.

char c;     /* 1文字 */
scanf("%c", &c);    /* 「文字」は文字列ではないので,&が必要! */
実行例:

文字を入力して下さい: M
M の文字コードは 0x4d です.
M は大文字です.
			
文字を入力して下さい: 5
5 の文字コードは 0x35 です.
5 は数値です.
			
文字を入力して下さい: !
! の文字コードは 0x21 です.
! はそれ以外です.

この課題のポイントは,
 ①文字の16進数表示
 ②文字の分類
です.講義中に解説した通り,PC側は文字を文字として認識していません.アスキーコード表にあるように,対応する数を記憶しているだけです.つまり,①はその数を16進数表示すればいいだけであり,何か特別な関数や処理が必要なわけではありません.今まで表示するときには,printf( )を使ったと思いますが,その際,整数型なら%d,実数型なら%f,文字なら%cなど,それぞれの変換指定文字を使いますよね.つまり,文字の16進数表示も何か変換指定文字を使うだけで可能になるのです.
②はヒントにある通り,大文字,小文字,数字はアスキーコード表のある範囲内にありますね.つまり,その範囲内にあるかどうかを条件判定する事で解決しますね.

2.char型(1バイト)の引数をとり,その2進数表記(ビットパターンと言う)を表示する関数 void bitpattern1(char c) を作れ.
次に,この関数を用いて,キーボードより入力した任意の文字の16進表記および2進数表記を表示せよ.
(表示の後ろにかっこ付きで進数を表示する)

#include <stdio.h>
			
void bitpattern1(char c)
{
			
    /* ここを作成 */
			
}
			
			
int main(void)
{
    char c;       /* 1 文字分の変数 */
			
    printf("Input a char: ");
    scanf("%c", &c);      /* 1文字 入力 */
			
    bitpattern1(c);
			
    return 0;
}
実行例
Input a char : C
C (10) = 0x43 (16) = 0100 0011 (2)

この課題のポイントは,
 ①16進数と2進数の対応
です.char型は問題文の通り,1バイト(=8ビット)です.そのため,16進数なら2桁,2進数なら8桁で表現できます.2進数は0と1のみで数値を表現するため,2進数のn桁で表現出来る数のパターンは,2^n個です.具体的には,1桁ならば0 or 1の2個,2桁ならば00,01,10,11の4個といったものになります.そして4桁で表現出来るのは,16個であり,16進数の0~Fまでと同じ数ですね!
8ビットは8桁なので,16進数を2つで表現するのです.そしてこれを2進数に変換するには,上記の説明の逆順を辿ればできますね.

3.以下のように「単語」を順に入力し,大文字で END と入力したら,それまで入力した単語をハイフン(-)区切りで連結し,すべて画面に表示するプログラムを作成しなさい.
「END」は連結しないとする.

実行例:
単語を入力: ABC
単語を入力: zzz
単語を入力: 123
単語を入力: END
全ての単語:ABC-zzz-123

この課題のポイントは,
 ①文字列の結合
です.今回の講義で文字列を連結する関数strcatを習いましたね.つまり,これを使って入力された文字列がENDではなかった場合はハイフン(-)区切りで連結していけば,課題は終了ですね.

4.入力された「単語」に含まれるアルファベットの大文字・小文字の数をカウントして表示するプログラムを作成せよ.

実行例:
単語を入力: AAAbbbCdeFG123!
大文字は 6 個
小文字は 5 個
それ以外は 4 個

この課題のポイントは,
 ①文字の種類判定
です.実は①は課題1の②を解いた方法と同じ方法で判別し,それをカウンタ変数で数えていくといいだけです.

5.入力された「単語」の大文字は小文字に,小文字は大文字に変換して表示せよ.

実行例:
単語を入力: AAAbbbCdeFG
変換後:aaaBBBcDEfg

この課題のポイントは,
 ①文字の種類判定
 ②大文字,小文字の変換
です.①は課題4の①と同じ方法で判定します.②に関しては,アスキーコード表を見ると,同じアルファベットの大文字と小文字は,16進数の1桁目は同じで,2桁目が2違うだけです.つまり,大文字→小文字なら+0x20,小文字→大文字なら-0x20をもとの変数に計算するだけで変換できます.

以上で5組の課題の考え方を終了します.

6組

今週は模擬テストだったので,授業の解説はありません.

提出課題もないので課題の解説もありません.

来週は中間テストです.今までの範囲で理解できていないところがないように,しっかり復習をしておいてください.

復習するなかで疑問点等あればブログでの質問やオフィスアワーを利用して,解決するようにしてください.

Remove all ads

配列と文字列

第7回の授業では,5組は関数について勉強しました.
関数のまとめについては,本ブログの関数のページを確認してください.

6組の授業では,配列と文字列について勉強しました.
配列のまとめについては本ブログの第6回 データ型 配列のページを確認してください.

文字列
文字列も配列の1種です.char型で宣言する事で文字が並び,文字列となります.
文字列に関しては,文字数+ヌル文字が要素数になることに注意しましょう.
文字列操作関数については,講義ページやインターネットで調べてみましょう.

以上で講義の解説は終了です.以下,課題の考え方です.


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;
}

この課題のポイントは
 ①関数の型と引数
です.関数内にreturn文があるということは,返り値があるということです.また,printf()を見ると,関数c2f()は%fに対して代入していて,引数にはTを代入していますね.ということは,型と引数はこれに合わせると良いわけです.今回の課題において,①は全てに共通したポイントになるので,課題2以降ではポイントには書きません.

2.1からnまでの整数の和 ∑n を計算する関数 Sum を作成しなさい.

#include...
	
??? Sum(...)
{
    ....
    return ???;
}
	
int main(void)
{
    int n;
    printf(...);
    scanf(...);
	
    printf("1から %d までの和は %d です.\n", n, Sum(n) );
    return 0;
}

この課題のポイントは,
 ①関数内での引数の使用方法
です.①については,今回の課題は和の計算なので,おそらく繰り返し計算をすると思います.そして,その繰り返しの終点を関数Sum()の引数で指定しているわけです.つまり,繰り返し計算の終了判定に使えば良いわけですね.

3.キーボードから和暦(平成??年)の整数の数値を渡すと,西暦に変換して返す関数wa2seiを作りなさい.
キーボードからの入力および結果の画面への表示は全て main 関数内で行うこと.

実行例:
和暦を入力してください(平成):28
平成 28 年は,西暦2016年です.

この課題のポイントは,
 ①和暦から西暦への変換
です.このぐらいの問題であれば,ググッた方が早いと思います.そして,returnには西暦を返せば良いわけです.

4.整数を引数として,引数が素数かどうかを判定する関数 IsPrime() 関数を作成しなさい.
関数 IsPrime 内では,素数の判定のみ行い,画面表示などは全て main 関数内で行うこと.
戻り値の意味などは各自で設定して良いが,コメントとしてソースコード中に記述しておくこと.
(例えば,素数の場合の関数の戻り値は0,それ以外は1とする,など.)

#include...
	
??? IsPrime(...)
{
    ....
    return ???;
}
	
int main()
{
    int n;       /* 整数を格納 */
    printf(...);
    scanf(...);
	
    ....
}

この課題のポイントは,
 ①素数の判定
です.素数とは,条件「1とその数自身以外の数では割り切れない数」を満たす数のことですね.と言うことは,入力された数nを1からnまで割っていき,余りが0になる数をカウントしていき,上述の条件を満たす時,素数であることを示す返り値を与えれば良いわけです.

5.三つの実数を引数に取り,それらの中央値(メディアンという)を返す関数 median を作成せよ.
main関数を用いて確認すること.

#include...
	
??? median(...)
{
	....
	return ???;
}
	
int main()
{
    printf(...); scanf(...);	
    ....
}||<
>||
実行例:
a= 10.5
b= -3.9
c= 0.3
中央値は 0.3 です.

この課題のポイントは,
 ①中央値の判定
です.今までの条件処理を思い出せばさほど難しい判定ではないと思います.今回のように実数が3つと決まっているのならば,全ての大小関係を比較して,中央値を見つけるのが1番楽かと思います.

以上で5組の課題の考え方については終了です.


6組
1. 1次元配列を作り,その中にキーボードから入力した英文を格納します. 英文は複数の単語がスペースで区切られているものとします. この文から,単語を一つずつ抜き出して,別の1次元文字配列にコピーし,画面に表示しなさい. プログラム名を,学籍番号10桁-07-1.cppとする.
英文の格納はscanfを使います.ここではスペースも読み取りたいので,授業中に説明したスキャン集合指定子を使えばいいですね.格納された文字列を1文字ずつfor文で見ていき,それがスペースであるところまで別の文字配列に格納していきます.スペースかどうかの判断については,前回のデータ型の回を参考にしてください.

for(入力された文字列の先頭から終わりまで){
    if(スペースでなかったら){
        格納;
    }else if(...){
        ...;
        ...;
    }
}

2. 複数の単語からなる文を入力すると,各単語の先頭文字を大文字に変えて表示するプログラムを作成せよ.

//実行例
> here is meiji university.
Here Is Meiji University.

この問題を解く上でのポイントは,

1.どのようにスペースありで文字列を入力するか?
2.どのように単語の先頭の文字を判別するか?
3.どのように小文字を大文字に変換するか?

の3点かと思います.

まず,1.に関して,皆さんはキーボードからの何かを入力して処理を行う時には,「scanf」関数を利用していると思います.実際に試してみた人はお気づきかと思いますが,今まで通り,

char str[1024];
scanf(%s, str);

のように書くと,スペースありの文字列を入力するとその手前までで切れてしまうと思います.
これを回避するためには,スキャン集合指定子を利用しましょう.


次に,2.ですが,これに関しては,私は,「スペースの次の文字が単語の先頭である(文字列全体に一番最初の文字は必ず単語の先頭である).」という条件でどの文字が単語の先頭かを判別しました.

これをプログラムにすると,入力された文字列はstrという変数に格納されているので,

for(int i =0; str[i]!=0; i++){
    
}

この処理でstr内の文字1つ1つについて考えることができ,

for(int i =0; str[i]!=0; i++){
    if(i==0){
        文字列の最初だから,単語の最初
    }
    else{
        if(str[i-1]==' '){
            str[i]はスペースの次の文字だから単語の先頭である
        }
    }
}

というような処理で,単語の先頭の文字を見つけることが出来ます.

最後に,3.の小文字を大文字に変換することについてですが,これはアスキーコードをもとに考えることですぐに解決することが出来ます.
アスキーコードでは,大文字と小文字の間には「32」の数の違いがあるので,これを考慮すると,

char komoji_to_omoji(char c){
    if(小文字なら){
        return (c - 32);
    }else{
        return c;
    }
}

という関数で変換することが可能です.
これらを組み合わせれば,課題2は上手く動くプログラムが作成できると思います.

3. 1次元配列をつくり,その中にいろいろな文字列をキーボードから入力してください. 入力が終わったら入力された文字列が画面に表示されるようにして下さい. プログラム名を学籍番号10桁-07-3.cppとする.

今回の課題はこの3番から解き始めるといいと思います.全体像は以下のようになるかと思います.

#include<stdio.h>
#include<string.h>

void input(...)
{
	printf("Type a string: ");
	scanf(スペースも読み取れるように);
}

void disp(...)
{
	...;
}

int main()
{
	char str[512];

	input(str);
	disp(str);

	return 0;
}

4. 前のプログラムに機能を追加して,入力した文字をABC順に並べ替えてみよう. プログラム名を学籍番号10桁-07-4.cppとする.
ABC順は前回登場したASCIIコードに並び替えてください.ASCIIコード表においてA-zは連続した数値となっているので,数値の並びかえと同様に並び替えることができます.

//数字が格納された配列を小さい順に並び替える
int src[10] = { 3, 5, 2, 9, 1, 8, 4, 7, 6 };
int sorted[10];
for(...){
    sorted[i] = src[i];
}
for(...){
    for(...){
        if(sorted[i] < sorted[j]){
            ...;
            ...;
            ...;
        }
    }
}

以上で課題の解説は終わりです.
少しずつ難しい内容になってきているので,ついていけなくなってしまう前に,ブログでの質問やオフィスアワーを有効に活用しましょう.
次回は今までの内容のおさらいになります.わからない部分がある人はここでしっかり理解するようにしてください.
それでは課題提出頑張ってください.

Remove all ads