joho1-2017の日記

joho1-2017の日記

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

ポインタ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

データ型

第6回の6組の授業では,データ型について勉強しました.

我々人間と,コンピュータでは扱えるデータには違いがあり,これを理解せずプログラムすると,意図しないエラーが起こることもあります.
そして,これは経験上ですが,この手のエラーはコンパイルは通ってしまい,実行時に生じることが多々あり,デバッグが非常に厄介だったりします.今回の授業の内容は全て覚えるのはなかなか難しいですが,今後PCを使おうと思ってる人には是非知っていて欲しい内容です.

ビット(bit)とバイト(Byte)

コンピュータの記憶容量を数える際に使う単位で,ビット(bit)はその最小単位になります.そしてコンピュータは0と1の2値しか記憶しないので,1bitで表現できる数は,0か1になります.また,このbitを8つ集めたものをバイト(Byte)になります.

8bit = 1Byte
進数

我々が主に扱う0~9までの数字で数を表現するものを,「10進数」と呼びます.これに対し,コンピュータは0と1のみの「2進数」で記憶を行います.そして,あまり馴染みがないかもしれませんが,0~Fまでの「16進数」というものもあります.コンピュータの2進数を表記すると人間にはとても理解しにくいので,わかりやすい表現として16進数が扱われます.

変数

これまでの講義で扱ってきた変数も,割り当てられた要領だけメモリが確保されています.表は講義ページを参照してください.この事から,割り当てられた変数で表現できる以上の数を代入しても,正しい数は得られないため,エラーが起こります.しばしばエラーという形ではなく,正しくない数を返してくる事もあります.この場合は非常に厄介ですね...こんな事が起こらないようにするためにも,各変数に割り当てられたメモリのサイズ表は定期的に確認しておきましょう!

変数のスコープ,auto変数とstatic変数,グローバル変数は講義ページの通りです.長くプログラムを書く先輩としてアドバイスするならば,同一プログラム内に同名の変数があるのは,一部の例外(for文のカウンタ変数iなど)を除いて避けるべきです.例えそれがauto変数であっても,作った直後でない限り混乱しやすくなり,無駄にプログラムを読み返す時間を増やすことになるでしょう.

定数

変数と違って,値が変化しないものとして,定数がある.これらは講義ページにあるように,数値定数文字定数文字列定数と言うものがあります.ところで,この記事の最初の方で述べていますが,コンピュータは0か1しか記憶しません.なのに,文字を扱えるのはなぜでしょうか?
答えは,「文字を数で表現し,その数を記憶している」からです.そして,数と文字の対応を表にしているものがアスキーコード表なのです.この表は16進数で表現されていますが,これは1桁が4bit=

=16で表現されるため,16進数表記なのです.

型変換

(1)代入時の型変換
左辺と右辺の型が異なる場合,左辺の型に変換される.

int a;
double b = 3.14159265358;
a = b;         //これはint型に変換
実行結果:
3

(2)式中の変換
式中の型が異なる場合,制度の高い型に統一される.

精度高 double > float > long > int > char 精度低

(3)キャスト
型を一時的に強制変換できる方法もあります.

float x = 3.14159265358;
int y = (int)x;       //float型のxは一時的にint型に変換された!
独自の型作成

C言語では,ユーザ自身が独自に変数型を作成できる.これは後々プログラミングをとても楽かつ見やすくしてくれるものなので,是非覚えておいてもらいたい.書き方は講義ページを参照してください.

第6回の5組の授業では,配列について勉強しました.

学生の皆さんは,これまでのプログラミングで,何度も変数を宣言し,それらに値を代入するのは面倒だと思ったことはありませんか?
そして今後は,それではソースコードがとても長く,読み難くなる場合が出てきます.
こういった事を解決するものに,「配列」というものがあります.

1次元配列

配列とは変数のリストであり,1度で複数の変数を宣言できます(ただし,型が違う変数はそれぞれ宣言しなければならない).
1次元の配列は,

型 変数名[要素の個数];
 例: int days_of_month[12];

と宣言し,これにより,宣言した型の変数が要素の個数分生成されます(例だと,int型のday_of_monthという変数が12個生成された).
そして,これらの変数に値を代入したり,判定処理をするためにアクセスする時は,

month[0] = 31;

のように,変数名と何番目の要素にアクセスするのかを指定しなければいけません.注意すべき事として,

1.配列の宣言時に要素の個数がNならば,アクセスする時は0~(N-1)まで
//例  
int month[12];  //宣言が12個なら
month[0] = 1;
.
.
.
month[11]=12;  //アクセスは0~11まで
2.for文やwhile文などでまとめて値を代入する際,要素数以上の繰り返し処理をしてもコンパイルエラーは起きない
//例
int month[12];               //宣言は12個
for(int i=0; i<100; i++){  //要素数より多いループ回数!でもコンパイルは通ってしまう!
    month[i] = i+1;
}
3.まとめてのコピーは不可
int num1[12], num2[12];
num1 = num2;                 //これはできない!エラーになる.

1は宣言時とアクセス時で要素の扱う範囲が異なるように見えるので混乱しがちだが,アクセス時の[ ]内の数字は,最初の要素からのオフセットを表すので,1番目はオフセット0だし,今回の例の最期はオフセット11になるので,0~(N-1)となります.
2は実行して初めてミスに気付くとういう,かなり厄介なバグになるので,注意しましょう.
3は配列の特徴であり,C言語では対応していません.1つ1つに代入する事を意識しましょう.

配列とポインタの互換性は,ポインタを習っていない今解説しても混乱するだけなので,ここではしません.

多次元配列

実は1次元配列だけでなく,多次元配列も存在します.例えば2次元配列(3行5列)を作りたい場合,

int matrix[3][5];

と宣言する事で作成できます.
アクセスの場合,例えば1行目の3列目にアクセスしたい場合は,

matrix[0][2] = 12;

のように書きます.
三次元の時も,

int a[2][3][4];

のように,[ ]の数を増やす事で表せる.

 

以上で今回の講義のおさらいは終わりです.以下,各クラスの課題の考え方です. 

 

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

この課題は練習問題2練習問題3を参考にすれば,簡単に解くことができると思います.
 ①実数型を用いること
 ②配列へのアクセス時は要素数番号は0~N-1であること
に注意しましょう.

2.キーボードから正の整数を10個入力し配列に格納し,そのなかから奇数の数をカウントして画面に表示せよ.
正でない整数が入力されたらエラーを表示して,正しい値が入力されるまで繰り返し再入力させよ.

実行例:
a[0]= 10
a[1]= -3
入力エラー! 正の整数を入力
a[1]= 0
入力エラー! 正の整数を入力
a[1]= 5
a[2]= 100
a[3]= 2
  .
  .
  .
a[9]= 10

奇数は 4 個ありました.

この課題のポイントは,
 ①入力された数が正の整数か判定
 ②奇数の判定
です.①はdo-while文で入力されるたびに判定すれば良いでしょう.もちろん他の方法でも構いません.②は2で割り切れるか判定すれば良いだけですね.

3.キーボードから 2 以上の整数を10個入力し,その中の素数の個数を数え表示するプログラムを作成せよ.

実行例:
a[0]= 10
a[1]= 1
入力エラー! 2以上の整数を入力
a[1]= 5
a[2]= 11
a[3]= 2
  .
  .
  .
a[9]= 10;

素数は 2, 5, 11 の 3 個ありました.

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

<4.キーボードから10個の整数を配列に入力し,1.まず配列の全要素を画面に表示,2.次に大きい順に並べ替えて表示せよ.
ヒント(各自で調査せよ):ソーティング,アルゴリズム,単純ソート,挿入ソート,バブルソートヒープソートなど

a[0]= -2
a[1]= 1
a[2]= 11
a[3]= 2
  .
  .
  .
a[9]= 10;

並べ替え前:-2 1 11 2 ... 10
並べ替え後:11 10 ... 2 1 -2

この課題のポイントは,
 ①配列の並べ替え
です.様々なやり方があると思いますが,配列の要素番号の隣同士を比較して,要素番号が小さい方の中身の数が小さければ大きい方と入れ替える,という作業を要素数だけ繰り返せば,最終的にできた配列は大きい順に並び替わります.これには繰り返し処理のネストが必要になりますね.

5.整数型配列の各要素を全て絶対値に変換して,変換前後の配列の中身をそれぞれ表示せよ.

#include <stdio.h>

int main(void)
{
    const int N = 10;
    int a[N] = {3, -4, -9, 10, -5, 6, 2, 0, -9, 5};   /* 数値は例.値を書き換えても正しく動作すること. */

    ...

    return 0;
}
実行例: 変換前:3, -4, -9, 10, -5, 6, 2, 0, -9, 5
     変換後:3, 4, 9, 10, 5, 6, 2, 0, 9, 5

この課題のポイントは,
 ①整数を絶対値にする
です.C言語には絶対値を計算する関数というものがあります.詳しくは「C言語 絶対値」などでググるとでてきます.この時,いつものstdio.h以外にも他のヘッダーファイルをインクルードしないといけないことにも注意しましょう.
ただ,簡単な条件判定で解くことができるので,わざわざ使う必要はありません.

 

 

6組

1.QUIZ1,2,3 を参考にして,入力した文字が大文字であれば小文字に変換し, 小文字であれば大文字に変換するプログラム作成しなさい. また入力は半角文字のみとする. キー入力と表示以外は組み込み型の関数は使わずに実現して下さい.

実行例:
文字を入力して下さい: M
m
文字を入力して下さい: e
E
文字を入力して下さい: i
I
文字を入力して下さい: j
J
文字を入力して下さい: I
i

この課題のポイントは,
 ①大文字と小文字の認識
です.QUIZ1~3を参考にすればいいので,アスキーコード表を使うことは自明ですね.そしてアスキーコード表を見ると,同じアルファベットの大文字と小文字は下位4bitは同じですね.つまり,上位4bitに対して何かしらの条件処理をすれば,小文字->大文字,大文字->小文字の変換はできますね.

2.QUIZ4を参考にして,一桁の自然数(数値)を引数にとり,文字(数字)を返す関数 toascii() を作れ. 但し変換にprintfなどを用いてはならない.

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

この課題のポイントは,
 ①数字から文字への変換
です.QUIZ4において,文字としての数字(0x30~0x39)を数値(0~9)に変換はできましたね.そして,その時に,関数atoi( )では,引数に対してどんな処理がされていますか?今回の問題はその逆をやればいいだけですね.

3.QUIZ5 を参考にして,2点の三次元座標A(x,y,z),B(p,q,r)を入力すると,内積を計算する関数 dot_product()と, 外積を計算する関数 cross_product() を作成せよ.

この課題のポイントは,
 ①独自の変数型作成
 ②内積外積計算

です.①はQUIZ5と全く同じで大丈夫なので,省略します.②は,QUIZ5の関数add( )の中身を内積外積の式に則って計算するよう書き換えれば解けますね.

以上で課題の考え方について終わりです.
もう授業では何も言いませんが,命名規則守ってください! 何人か未だに守れていない人がいます.

 

Remove all ads

関数

第5回の授業では,関数について勉強しました.
関数のイメージは,一言で言うと「関連する復数の処理を1つにまとめたプログラム」だと思ってください.

現在までの授業では,100行にも満たないとても短いプログラムしか書いていませんが,今後の課題では数百行や数千行というプログラムを書かなければいけないことが出てきます.このようなプログラムでは,どの部分でどのような処理が行われているのか,ということが把握しにくくなり,結果としてバグ(プログラムの間違い)だらけのプログラムになりやすくなってしまいます.

そこで,関数を利用して,関連した処理(例えば,累乗を計算する処理や総和を計算する処理など)を1つのまとまりとしてまとめることで,プログラムをコンパクトにし,どの部分でどの処理が行われているかを分かりやすくします.

関数を書いてプログラムをまとめる利点は,プログラムがコンパクトになり処理が把握しやすくなるだけでなく,

  • 同じような処理を複数回記述する手間を省ける
  • 各機能毎に関数をまとめておくことでプログラムの修正が容易になる(どの部分を直せば良いのか分かりやすい)

と言った点があげられます.

ですので,皆さんにはぜひmain文が非常に長いものにならないように,関数をうまく活用して行くことをお勧めします.

では,「具体的にはどのように関数を書くのか」と言う点について説明して行きたいと思います.関数の書き方についての詳細は授業ページを見ていただいて,ここでは基本的な書き方について簡単に説明したいと思います.

戻り値の型 関数名(引数){
    ...
    関数内での処理;
    ...
    return文;
}

この書き方が基本です.
例として,x+yの値を計算するプログラムを考えます(xyは整数とする).

#include <stdio.h>

int func(int x, int y){
	return x + y;
}

int main(void){

	int x_input = 1;
	int y_input = 2;

	int sum = wa(x_input, y_input);

	printf("sum = %d\n", sum);
	return 0;
}

この関数の処理の流れは,

  1. x_input,y_inputのそれぞれの変数に値 1,2を代入
  2. 変数sumに関数waで書いた処理(x + yのこと,具体的に何の値を足せば良いのかを関数に教えるために引数にx_input, y_inputをとる)を実行した時の戻り値を返す
  3. 最後にsumの値を画面に出力

という順番で行われます.

授業では,このときの関数とメイン文で同じ変数を使っていいのか?などの質問を多く受けましたが,とりあえずは,「{ }」の中で同じ変数が使われていなければ大丈夫である.と覚えておいてください.

以上で,今回の授業のおさらいは終わりです.
以下,課題の考え方についてです.

5組
1.キーボードから整数 n を入力し,1 から n までの和,∑n および二乗和 ∑n2 を計算し,画面に表示せよ.
この課題のポイントは,
 ①1からnまでの繰り返し計算
 ②繰り返しの終点が計算開始初期からわかっている
です.和の計算に関しては,授業内練習問題のQ2にてプログラム済みですので,もうできると思います.
また,繰り返し計算をどのループ(for, while)を使っても可能なので,各々試してみてください.強いて言うなら,②があるので,forループの方がやりやすいかも...?

2.フィボナッチの数列 ak = ak-1 + ak-2 ,a1 = 1, a2 = 1 の第n項までを画面に表示せよ.nはキーボードから入力する.

実行例:
n= 10
a1 = 1
a2 = 1
a3 = 2
a4 = 3
a5 = 5
a6 = 8
a7 = 13
a8 = 21
a9 = 34
a10 = 55

この課題のポイントは,
 ①各変数ak,ak-1,ak-2の計算順番です.例えば,3つの変数をそれぞれ用意しておきak = ak-1 + ak-2を計算し,その後ak-1とak-2の値を更新していくのと,更新を先に行ってから計算処理をするのでは,全く違う値が得られます.果たしてどの順番が正しいのか,混乱するとは思いますが,よく考えながらプログラムしましょう.

3.for文のネストを利用して,以下のように - と | が交互に出現する表を画面に表示しなさい

|-|-|-|-|-|-
-|-|-|-|-|-|
|-|-|-|-|-|-
-|-|-|-|-|-|
|-|-|-|-|-|-
-|-|-|-|-|-|

 この課題のポイントは,
 ①6×12の行列の形
 ②|と-が交互
です.①の形は,授業内練習課題のQ4でもやりましたね.②は,考え方はいくつかあると思います.for文のカウンタ変数の奇偶による条件分岐,四則演算による条件分岐etc...
ぜひ,TAも感心する処理方法を考えてプログラムしてみてください!

4. while 文を用い, 0 < x < 2π の範囲で,f( x ) = cos( x ) の最初の極小値と,その時のxの値を求め,画面に表示せよ. (もちろん答えは x = π のとき,-1 である)

#include <stdio.h>
#include <math.h>    /* cos 関数の使用に必要 */

int main(void)
{
    float x;
    float dx = ????;

    while( ??? ) {

        if(???)
            break;

        x += dx;
    }

    printf(...);       /* 結果の表示 */
    return 0;
}

この課題は,本ブログ「第4回 制御文2」内の課題の考え方6組2問目での考え方と一緒です.そちらと授業ページのヒントを参考にしましょう.

5.1から1000までの自然数において,(a)入力した列数で改行して表示し,(b)その際に任意の自然数の倍数を伏せ字にするプログラムを作成せよ.

実行例 :

改行する列数 = 7
伏字にするのは = 4 
    1|    2|    3|-----|    5|    6|    7| 
-----|    9|   10|   11|-----|   13|   14| 
   15|-----|   17|   18|   19|-----|   21| 
(中略) 
  967|-----|  969|  970|  971|-----|  973| 
  974|  975|-----|  977|  978|  979|-----| 
  981|  982|  983|-----|  985|  986|  987| 
-----|  989|  990|  991|-----|  993|  994| 
  995|-----|  997|  998|  999|-----| 

この課題のポイントは,
 ①表示が行列の形
 ②列数は入力した値で変化
 ③入力にて指定した自然数の倍数に対する処理
です.①は課題3と同じなのですが,列数の条件は入力された値によって変わります(②).ネストさせたループの列数を指定する条件判定部に気をつけてプロ グラムしてみましょう.③はこれまでもやってきた条件分岐の処理です.任意の数nの倍数ということは,言い換えるとnで割った余りが0というのは,授業中 に先生が何度も仰っていましたね.この課題は①,②ができたことを確認できたら,③を追加しましょう.1つの問題の中に複数の独立した問題がある場合 (今回でいうと,任意の列数の行列表示任意の数字の倍数の伏字化),片方だけでうまく実行できることを確認できた後に全部を1つにすることで,エラーが出た際にバグ部分を見つけやすくなります.

6組

1. 直角三角形の短辺2つの長さ(a,b)を入力すると,長辺の長さ(c)を返す関数を作りましょう. a,bの値はそれぞれキーボードから入力します.

この問題では,計算処理は関数で行い,main関数では,関数を呼び出す前処理や結果の表示だけ行います.
具体的に関数内で行う計算処理は三平方の定理を利用することです.
またこの問題では,math.hというヘッダファイルをインクルードすることで平方根の計算を非常に簡単に行うことが可能です.
プログラムの例としては,

#include <stdio.h>
#include <math.h>

float calculate_triangle(float a, float b){
	return sqrt(平方根の中身の計算);
}

となります.このプログラムの中身の計算は,上記で説明した三平方の定理を利用した計算式です.

2. QUIZの5番目の問題は,1から3までの3個の2乗和を求めました. このプログラムを改良して,1からNまでの2乗和を求めるプログラムにしてください. このとき,Nの値は,scanf()関数で与えられるようにしてください.

この問題は,QUIZ5で作成したjijo関数をそのまま流用し,wa関数を少しだけ修正することで簡単にプログラムを解くことが出来ます.
それぞれQUIZでは

//二乗を計算する関数
int jijo(int x){
	return x*x;
}

//3つの値の和を計算する関数
int wa(int a, int b, int c){
        return a + b + c;
}

となっていました.

QUIZと今回の問題の変更点は,「1から3までの3個の2乗和を求める」と「1からNまでの2乗和を求める」と言う点です.ここで,注目してもらいたい点は,「1からNまで」というフレーズです.この部分から制御文の授業で学んだ,「繰り返し」が使えるということに気づいてほしいですね.

このことに築くことが出来たならば,後は引数を繰り返し回数の上限であるNとするような関数を作成します.
解答例のヒントとしては

int wa2(int N){

	for(int i=0;i<N;i++){
		2乗和を計算する処理(QUIZで作成したjijo関数を利用しましょう);
	}
	return 1からNまでの2乗和;
}

3. 速度0で静止しているロケットに点火してロケットを打ち上げます.ロケットの質量を m,推力が  F で一定だとしたとき, 打ち上げから時間t秒後の速度 v(t)を求める関数を作って下さい.引数は m,F,tで,戻り値が v(t)です.

この問題は,

 V(t) = at + V_0

の数式を利用できるので,この数式をそのまま関数とします.

また引数がm, F, tということから加速度aを求めるために

 F = ma

を利用してください.

このことをふまえて関数を作成すると,解答の一例として以下のような関数が作成できると思います.

float velocity(float F, float m, int t){
	加速度aを求める計算処理;

	return 上記の加速度を利用した速度を計算する処理;
}

この解答例では,推力Fと質量mは少数でも計算可能なようにfloat型の変数で定義し,時間tは整数しか入力されないと考えint型としました.さらに,計算される速度も小数になり得ると考え,float型です.
ここのそれぞれの変数型は,きちんと速度の計算を行うことが出来れば,個人の自由に変更していただいて結構です.

 

授業ページの問題文”~する関数を作ってください”とありますが,もちろんmain関数も必要です.

すべてのプログラムにおいて,きちんとコンパイル(リンクまで)が通る形で提出してください.

 

 

繰り返しでくどいようですが,まだ,ファイルの命名規則が守られてない人がいます. ~.zip.zipになっていないかなど,きちんと確認をしてから提出するようにお願いします.

では,皆さん課題をがんばってください.

Remove all ads

制御文2

第4回 制御文2のおさらいと課題を解くにあたっての考え方について解説したいと思います.
第4回では,前回学んだif文,for文,while文の応用とその他の条件処理,ループ処理の方法を学びました.

if文

if文は,ある条件を満たす(あるいは満たさない)時にのみ何か処理をさせたい場合に使用するものです.

if(条件式){
            処理文
}

前回はここまで学びましたね.
では,ある条件の中にさらに条件がある,例えば「変数xが4と5の公倍数であり,変数y未満の場合」のような場合には,if文の処理文の中でさらにif文を入れ子にする(プログラミングにおいて,ネストにすると表現する)ことができます.上記の例をもとにすると,

if(x%4==0 || x%5==0){
             if(x < y){
                     処理文
}

のような書き方になります.
また,if文とelse文を組み合わせることで,さらに複雑な条件分岐もすっきりと書くことが可能になります.

if(条件式1){
           処理文1
}else if(条件式2){
           処理文2
}...
}else{
           処理文n
}

 

for文

for文はループ(繰り返し計算)処理を行う際によく用いられるものです.基本的な構文は,

for(初期設定部; 条件判定部; インクリメント部)  処理文

であるが,各部を"( )"の外で定義することも可能です.

int i = 0;  //初期設定部
for(  ;  ;  ){
           if(i > 10){  //条件判定部
                     処理式
           }
           i++;  //インクリメント部
}

ただし,このような書き方は書いていて混乱しやすいため,特別な場合を除き基本的な書き方に則って書くべきです.
for文もif文と同様,ネストにすることが可能です.

for(int i=0; i<10; i++){
    for(int j=0; j<10; j++){
         printf("%d × %d = %d\n", i, j, i*j);
    }
}

 

while文

while(条件式){ 処理文 }

while文は条件式が真である間,処理が繰り返されます.ということは,条件式が最初から偽である場合,処理は一度も実行されません.
しかし,まず最初に一度処理をさせてから条件判定をしたい場合というのも出てくると思います.その時には,do-while文を使います.

do{
                 処理文  //最初から条件式が偽でも一度は実行される
}while(条件式)

すなわち,
・まず条件判定をさせたい → while文
・まず処理判定をさせたい → do-while文
といった使い分けとなります.

 

break文, continue文, switch文

ループの途中で終了判定を待たずにそのループから抜け出せるbreak文,ある条件の時は処理をさせないcontinue文というのもある.
そして,if-else文を多く用いらなければならなく,条件の評価対象が整数の場合,switch文というものを使うと,すっきり見やすく書けます.また,どちらもネストさせることが可能です.

switch(変数){  //変数は整数(int, char)のみ
    case 定数1:  //変数が定数1なら,処理文1が実行される
         処理文1;
         break;
    case 定数2:
         処理文2;
         break;
    default;         //どの条件にも当てはまらない場合(if-else文のelseに相当)
         処理文3;
         break;
}

どうでしょうか?if-else文より見やすくなりましたか?正直,これは条件分岐が多い場合を実際にプログラムした時に初めて恩恵を感じる気がします.このswitch文も先述のfor文,while文同様ネストさせる事ができます.

以上,かなりざっくりとしてますが第4回のおさらいになります.

 

では,課題の考え方について解説したいと思います.


1.for文を2回使用して以下に示すような九九の計算結果を表示するプログラムを作りなさい. このとき,桁をきちんと合わせて表示すること.

【実行例】
1   2   3   4   5   6   7   8   9 
2   4   6   8  10  12  14  16  18 
3   6   9  12  15  18  21  24  27 
4   8  12  16  20  24  28  32  36 
5  10  15  20  25  30  35  40  45 
6  12  18  24  30  36  42  48  54 
7  14  21  28  35  42  49  56  63 
8  16  24  32  40  48  56  64  72 
9  18  27  36  45  54  63  72  81 

この課題のポイントは,
 ①for文を2回使うこと
 ②桁を合わせること
です.①については...このページにヒントが載っていますね!
②については,画面に出力するprintfの中の変換指定(%dや%fなど)で桁数の設定ができますよね.
こういう分からない時こそ,Google先生に聞きましょう!「C言語 桁数揃え」などと検索すると...?

 

2.while文を用い (0<=x<=2π) の範囲で,f(x)=sin(x) の極大値fmax(a)とその位置 a を探索し求めよ. なお探索時のxの刻み幅 dx=0.0001 とする.
この課題のポイントは,
 ①while文の条件式
 ②三角関数を使う
 ③極大値を調べる
です.①は,何と何を比較して真偽を判定するのでしょうか?この問題文の中で,範囲が指定されている変数がありますよね!
②は,実は皆さんは習っていない部分があります.現段階ではまだ"おまじない"と教わったかもしれませんが,プログラムのコードの最初に"#include "というのを書いていますよね?この1行をコメントアウト(/*と*/で挟む)して保存・コンパイルしてみてください.printfやscanfに関するエラーが出てくると思います.今まで使っていたprintfやscanfは「標準入出力関数」と呼ばれるもので,これらを使用するためには,stdio.hというヘッダファイルをインクルードしなければいけないのです.難しい説明になってしまいましたが,"#include を最初に書いておくことでprintfやscanfが使えるようになる!"とだけ理解しておけば今は大丈夫です.同じように,sinやcos(これらを数学関数と言う)を使えるようにするためには,math.hをインクルードしなければなりません.具体的には,"#include "の下の行に"#include "と書けば,三角関数を使えるようになります.
③は,始めに極大値を代入する変数fmaxを用意しておき,インクリメントする前の変数のf'(x)と後の変数のf(x)の値を大小比較して,もしインクリメント後のf(x)の方が大きければ,極大値fmaxにf(x)を代入していく,というのを繰り返せば,最終的なfmaxが極大値となります.

[解答例]

 

3.何月かを入力するとその月の英語名(January等)を表示するプログラムをswitch文を用いて作れ.

【実行例】
今は何月ですか?:5 
ああ,今は May なんですね! 

この課題のポイントは,
 ①何がswitch文の変数になるのか
です.出力させたい月の英語名は,何から判断されますか?それを考えれば,何を変数にしたらいいのか分かると思います.

以上が,第4回の課題を解くにあたっての考え方になります.
授業中にもお話しましたが,命名規則を守って提出をしましょう

 

Remove all ads