履修システム使い方
(塚本先生担当)
連絡事項
花やしきについて、学生にお知らせ頂きたい事項を3点連絡
第一章、第二章のまとめ
過去のページを参照
演習
演習2-6(P37)
身長を整数値として読み込み、標準体重を実数で表示するプログラムを作成せよ。
標準体重 = (身長 – 100) * 0.9
実行例
身長を入力してください: 175
標準体重は 67.5 です
ユビキタスコンピューティング研究ポータル
(塚本先生担当)
花やしきについて、学生にお知らせ頂きたい事項を3点連絡
過去のページを参照
演習2-6(P37)
身長を整数値として読み込み、標準体重を実数で表示するプログラムを作成せよ。
標準体重 = (身長 – 100) * 0.9
実行例
身長を入力してください: 175
標準体重は 67.5 です
ポインタの仕組み:ポインタで変数を指す
ポインタ (pointer)とは、あるオブジェクトがなんらかの論理的位置情報でアクセスできるとき、それを参照するものである。有名な例としてはC/C++でのメモリアドレスを表すポインタが挙げられる。(ja.wikipedia.org)
ポインタ=メモリアドレス
間接演算子 * と アドレス演算子 &
間接演算子を使って、ポインタが指すメモリの値を取得することを間接参照するといいます。
間接演算子を用いれば、アドレスを間接参照するだけではなく、ポインタが表すアドレスに値を間接代入することもできます。
#include <stdio.h> int main() { int iVar = 0; int *iPo = &iVar; // 宣言&設定 printf("*iPo = %d\n" , *iPo); // 使用 *iPo = 100; // 間接代入 printf("iVar = %d\n" , iVar); return 0; }
配列とポインタは全く別物
配列とは、多数の変数を順番つけでまとめて扱う方法であり、
ポインタとは、変数のショートカットを作る方法です。
文字列を1文字ずつ表示するプログラム
/* 文字列を1文字ずつ表示するプログラム */ #include <stdio.h> int main(void) { char str[] = "sun"; char *p; // 宣言 p = str; // 設定 while (*p != '\0') { printf("%c ", *p); p++; } return 0; }
str1 に、’ABCDEFGHIJKLMNOPQRSTUVWXYZ’と初期化
str2 に文字列を逆順に格納
コード: a7-2-3.c
実行結果例: a7-2-3.exe
str1 = ABCDEFGHIJKLMNOPQRSTUVWXYZ
str2 = ZYXWVUTSRQPONMLKJIHGFEDCBA
型と型変換
平均点を求めるプログラム
#include <stdio.h> int main(int argc, const char * argv[]) { // insert code here... int score[5]; // 5人の点数を入れる配列 int sum; // 合計点 // 各点数を入れる score[0] = 77; score[1] = 80; score[2] = 65; score[3] = 60; score[4] = 70; // 各点数を全て足して合計点を求める sum = score[0] + score[1] + score[2] + score[3] + score[4]; // 平均点を表示する printf("平均点は%d点です。", sum / 5); return 0; }
正しい結果は70.4
しかしプログラムは平均点は70点と表示され、一部情報を失ってしまう。
sum / 5 = 70 (余り 2)
オペランドは、式の中でよりサイズの大きい型に合わせて変換されるというお約束があります。(暗黙的な型変換)
もし、サイズの大きい型を小さい型に変換した場合、上位ビットを切り捨てることになるため、情報を失ってしまう可能性があります。しかし、サイズを拡張する場合は情報を失うことはありません。
sum / 5 = 70
sum / 5.0 = 70.4
#include <stdio.h> int main(int argc, const char * argv[]) { // insert code here... int score[5]; // 5人の点数を入れる配列 int sum; // 合計点 // 各点数を入れる score[0] = 77; score[1] = 80; score[2] = 65; score[3] = 60; score[4] = 70; // 各点数を全て足して合計点を求める sum = score[0] + score[1] + score[2] + score[3] + score[4]; // 平均点を表示する printf("平均点は%f点です。", sum / 5.0); return 0; }
代入の場合も、型が異なる場合は暗黙的に変換して代入されます。これを代入変換と呼びます。
#include <stdio.h> int main(int argc, const char * argv[]) { // insert code here... int score[5]; // 5人の点数を入れる配列 double sum; // 合計点 // 各点数を入れる score[0] = 77; score[1] = 80; score[2] = 65; score[3] = 60; score[4] = 70; // 各点数を全て足して合計点を求める sum = score[0] + score[1] + score[2] + score[3] + score[4]; // 平均点を表示する printf("平均点は%f点です。", sum / 5); return 0; }
指定した型に値を変換するようにプログラマが伝える明示的な変換方法も存在します。これを型キャスト変換と呼びます。
(変換型名)式
sum / 5 = 70
(double) sum / 5 = 70.4
#include <stdio.h> int main(int argc, const char * argv[]) { // insert code here... int score[5]; // 5人の点数を入れる配列 int sum; // 合計点 // 各点数を入れる score[0] = 77; score[1] = 80; score[2] = 65; score[3] = 60; score[4] = 70; // 各点数を全て足して合計点を求める sum = score[0] + score[1] + score[2] + score[3] + score[4]; // 平均点を表示する printf("平均点は%f点です。", (double)sum / 5); return 0; }
型キャストを利用して浮動小数点型変数 から実数や小数を取り出す
出力例:
浮動小数点数 = 12.34 実数 = 12 小数 = 0.34
解答例:
#include <stdio.h> int main() { float fVar = 12.34f; printf("全体 = %g\n" , fVar); printf("実数 = %d\n" , (int)fVar); printf("小数 = %g\n" , fVar - (int)fVar); return 0; }
ポインタの仕組み:アドレスとは
ポインタはC言語(および拡張言語)に特有の概念で、C言語を学び始めた初心者が必ずといっていいほどつまづく概念でもあります。ポインタがどうしても理解できないためにC言語に挫折してしまう方もいます。
コンピュータの記憶装置(メモリ)には、アドレスが付けられている。
変数のアドレスを取得するには変数名の前にアンパサンド “&”をつけます。
int a = 123;
printf(“aのアドレス : %p\n”, &a);
配列の先頭をアドレスは、配列名だけで示します。要素のアドレスは&配列名[添字]で示します。
#include <stdio.h> int main() { char str[3] = "AB"; printf("str[0]の要素のアドレス: %p\n", &str[0]); printf("strのアドレス: %p\n", str); getch(); return 0; }
二次元配列の先頭アドレスは、配列名だけで示します。要素のアドレスは&配列名[行][列]で示します。
変数、1次元配列、2次元配列の整理
値 | (要素)アドレス | 先頭アドレス | |
変数 | 変数名 | &変数名 | |
1次元配列 | 配列名[添字] | &配列名[添字] | 配列名 |
2次元配列 | 配列名[行][列] | &配列名[行][列] | 配列名 |
「値渡し」
int a = 10; int str[] = "DEF"; printf("%d", a); // 値渡し printf("%s", str); // 参照渡し
「参照渡し」
int a; int str[100]; scanf("%d", &a); scanf("%s", str);
一次元配列とそれぞれの要素のアドレスを表示するプログラムを作成
str[0]の要素の値 : 'A' str[1]の要素の値 : 'B' str[2]の要素の値 : 0x0 // 16進数で出力する str[2]の要素の値 : ' ' str[0]の要素のアドレス : 0019FF49 str[1]の要素のアドレス : 0019FF4A str[2]の要素のアドレス : 0019FF4B strの先頭要素のアドレス : 0019FF49
MacBookで、C言語開発環境の構築
Macに最初から入っている「テキストエディット」とMacに最初から入っている「ターミナル」から入門できますが、無料で多機能Atom、VSCodeを使えこなせるとより本格的にC言語開発できます。
Xcode またはXcode Command Line Toolsを入れる。
MacBookでは、Xcodeの開発環境を入れるは普通だか、サイズが大きい。iPhoneのアプリ開発環境も一気に揃う利点があります。
Xcode Command Line Toolsだけ入れるとする。サイズの節約になります。
# xcode-select –install
上記コマンド打って「インストール」を選択するだけでいい!
chen-no-air:bin chen$ gcc --version Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Apple LLVM version 7.3.0 (clang-703.0.29) Target: x86_64-apple-darwin15.4.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin chen-no-air:bin chen$ chen-no-air:bin chen$ git --version git version 2.6.4 (Apple Git-63) chen-no-air:bin chen$
Macに最初から入っている「テキストエディット」, 「ターミナル」で使えるVimや無料で多機能Atom、VSCodeなどのテキストエディタを使って、以下のプログラムをhello.cというファイル名で作成します。作成したファイルはデスクトップに保存します。
#include<stdio.h> int main() { printf("Hello, World\n"); return 0; }
次に「Finder」を起動し、「アプリケーション」→「ユーティリティ」から「ターミナル」を起動します。
cdコマンドで作業ディレクトリをデスクトップに移動します。
$ cd ~/Desktop/
gccコマンドで、実行ファイルを指定して、hello.cをコンパイルします。
$ gcc hello.c -o hello
そしたら hello
が作られています。
指定した実行ファイルを入力し、プログラムを実行します。
$ ./hello Hello, World
無事に実行できました。
Reverse Polish Notation Calculator
ツールボックスの中で、
Formにドラッグ&ドロップする
Form1を選択し、Form1のイベントの表示に切り替えて、「KeyPress」イベントに、FormKeyPressを入力
六つボタンを選択した状態で、コントロール類の、「KeyPress」イベントに、FormKeyPressを選択。
private void FormKeyPress(object sender, KeyPressEventArgs e) { string key = e.KeyChar.ToString(); int n = 0; try { n = int.Parse(key); } catch { return; } string num = typeText.Text + n; try { typeText.Text = "" + int.Parse(num); } catch { return; } }
private void plusButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum + typenum); typeText.Text = "0"; } catch { return; } }
private void subButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum - typenum); typeText.Text = "0"; } catch { return; } }
private void multiButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum * typenum); typeText.Text = "0"; } catch { return; } }
private void divButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum / typenum); typeText.Text = "0"; } catch { return; } }
private void clrButtonClick(object sender, EventArgs e) { if (typeText.Text == "0") { lastText.Text = "0"; } typeText.Text = "0"; }
private void enterButtonClick(object sender, EventArgs e) { string s = typeText.Text; lastText.Text = s; typeText.Text = "0"; }
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Calculator { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } private void FormKeyPress(object sender, KeyPressEventArgs e) { string key = e.KeyChar.ToString(); int n = 0; try { n = int.Parse(key); } catch { return; } string num = typeText.Text + n; try { typeText.Text = "" + int.Parse(num); } catch { return; } } private void plusButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum + typenum); typeText.Text = "0"; } catch { return; } } private void subButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum - typenum); typeText.Text = "0"; } catch { return; } } private void multiButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum * typenum); typeText.Text = "0"; } catch { return; } } private void divButtonClick(object sender, EventArgs e) { try { int lastnum = int.Parse(lastText.Text); int typenum = int.Parse(typeText.Text); lastText.Text = "" + (lastnum / typenum); typeText.Text = "0"; } catch { return; } } private void clrButtonClick(object sender, EventArgs e) { if (typeText.Text == "0") { lastText.Text = "0"; } typeText.Text = "0"; } private void enterButtonClick(object sender, EventArgs e) { string s = typeText.Text; lastText.Text = s; typeText.Text = "0"; } } }
計算例: 123+456=
まずキーボードから数字123入力
「ER」キーで数字を設定
次の数字456を入力
演算キーで計算
2つの数値の加減乗除をする電卓のようなプログラムを作ります。
簡単なようですが、多くの話題(データ型の選択、入力、判断や繰り返しなど)を含んでおり、学習に役立つ例題です。
今回は、プログラムをつくる流れも分かるように、どんなプログラムにするか検討するところから始めます。 それを処理手順に書き、C言語のコードに直します。
まずは、どんなプログラムにするかを考え、前提とすることや制約についても検討します。
上の検討結果を踏まえて、処理内容を具体的かつ端的に書きます。 判断や繰り返し、データの入出力も分かるようにします。
下記では、チャートを使わずに書きます。
【 電卓プログラムの処理手順 】
指定の書式を示すメッセージを表示する
<繰り返し>
| 「数値1 演算記号 数値2」の指定を受け取る –> 変数 a, op, b へ
| 指定の書式でなかったとき) 繰り返しを抜ける
| 演算記号 op に応じた計算をする –> ans へ結果を入れる
| + のとき) a + b を求める
| - のとき) a – b を求める
| * のとき) a * b を求める
| / のとき) b がゼロでないかチェックする
| ゼロのとき) エラー・メッセージを表示して繰り返しの先頭に戻る
| a / b を求める
| それ以外) エラー・メッセージを表示して繰り返しの先頭に戻る
- 答え ans を表示する
電源OFF のメッセージを表示する
上記の手順を素直にC言語で書きます。
【 電卓プログラムの書き方例 】
#include <stdio.h> main() { double a, b, ans; char op; printf( "加減乗除(+,-,*,/)ができます。指定例:2+5、終了時はq\n" ); while( 1 ) { printf( "ready : " ); if( scanf( "%lf %c %lf", &a, &op, &b ) != 3 ) break; switch( op ) { case '+': ans = a + b; break; case '-': ans = a - b; break; case '*': ans = a * b; break; case '/': if( b == 0.0 ) { printf( "Error!(ゼロでの割算はできません)\n" ); continue; } ans = a / b; break; default: printf( "Error!(演算記号の指定が誤りです)\n" ); continue; } printf( "--> %g\n", ans ); } printf( ".... Power OFF\n" ); }
加減乗除(+,-,*,/)ができます。指定例:2+5、終了時はq ready : 6.5 * 3 --> 19.5 ready : 7 % 4 Error!(演算記号の指定が誤りです) ready : 123 / 2 --> 61.5 ready : 7 + 16 --> 23 ready : q .... Power OFF
逆ポーランド記法を使えば、式の計算をする(評価)には、先頭からひとつずつ順番に記号を読み込み、その記号が演算子以外であればスタックに値を積み、演算子であればスタックから値を取り出して演算し結果をスタックに積む、という簡単な操作の繰り返しだけでよい。そのため、プログラミング初心者の練習課題として、逆ポーランド記法の電卓を作ることがよく行われる。
2+3を計算するとき,逆ポーランド記法では,次のように表す.数値や演算子(+, -, *, /)の間にはスペースを設ける.
これはいくつかのメモリー(記憶場所)が準備されているとき,
という手順で計算することを表している.
【中置記法】
3 * ( 1 + 2 ) / ( ( 4 – 5 ) / ( 6 + 7 ) ) = キータッチ数は22回
【逆ポーランド記法】
3 1 2 + * 4 5 – 6 7 + / / キータッチ数は13回
次のような機能を持つ電卓プログラムを実現せよ。
文字列解析、配列によるスタック実装など、総合的な演習問題としてよく使われます。K&Rにも同様の演習があります。
#define STACK_DEPTH 100 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> #include<math.h> double stack[STACK_DEPTH]; int sp=STACK_DEPTH; double pop(void){ return (sp<STACK_DEPTH)?stack[sp++]:0.0; } void push(double f){ if(sp>0)stack[--sp]=f; } int getword(char* dst, const char* str, int limit) { int i, j, len = strlen(str); for(i=0; i<len && isspace(str[i]); i++); for(j=0; j<limit && (j+i)<len && !isspace(str[i+j]); j++) dst[j]=str[i+j]; dst[j]='\0'; return i+j; } int main(void) { char line[100], tmp[100]; int i, c; while(1){ i = 0; fgets(line, 100, stdin); while((c=getword(tmp, &line[i], 100)) && strlen(tmp)){ if(strcmp(tmp, "sin")==0) push(sin(pop())); else if(strcmp(tmp, "cos")==0) push(cos(pop())); else if(strcmp(tmp, "sqr")==0) push(pow(pop(),2)); else if(strcmp(tmp, "+")==0) push(pop()+pop()); else if(strcmp(tmp, "-")==0) push(pop()-pop()); else if(strcmp(tmp, "*")==0) push(pop()*pop()); else if(strcmp(tmp, "/")==0) push(pop()/pop()); else if(strcmp(tmp, "=")==0) printf("%f\n", pop()); else push(atof(tmp)); i+=c; } if (line[0] == '\n') break; } return 0; }
関数型言語
コンピュータに実行してもらう命令はすべて関数の中に記述されている。関数がプログラムの実行単位。いくつかの関数を組み合わせ、コンピュータへ命令をする。最初にコンピュータが実行する関数はmain()に決まっている。
戻り値の型 関数名(引数リスト) { 命令文; }
式(Expression)
オペランド 演算子 オペランド...
コンパイラが認識する最小単位のテキスト、トークン(token)
#include <stdio.h> int main() { printf("Hello World\n"); return 0; }
トークン | トークンの種類 |
---|---|
int | キーワード |
main | 識別子 |
( | 区切り子 |
) | 区切り子 |
{ | 区切り子 |
printf | 識別子 |
( | 演算子 |
“Hello World\n” | リテラル文字列 |
) | 演算子 |
; | 区切り子 |
return | キーワード |
0 | 定数 |
; | 区切り子 |
} | 区切り子 |
トークンの間に演算子や区切り子、または空白文字で区切りする
空白文字とは、次のいずれかの文字のことを表します。
/* 一行コメント1 */ // 一行コメント2 /* main.c 山田 太郎 2012年10月25日 作成 */ /***********************/ /* main.c */ /* 山田 太郎 */ /* 2012年10月25日 作成 */ /***********************/ // main.c // 山田 太郎 // 2012年10月25日 作成
演算子 | 意味 |
---|---|
+ | 加算 |
– | 減算 |
* | 乗算 |
/ | 除算 |
% | 剰余(余り) |
= 代入(Assignment)
演算子 | 意味 |
---|---|
+= | 加算代入 |
-= | 減算代入 |
*= | 乗算代入 |
/= | 除算代入 |
%= | 剰余代入 |
x = x + 5;
x += 5;
#include <stdio.h> int main(void) { int DH, ON, wa, sa, se, sh, am; printf("1つ目の数字入力:"); scanf("%d", &DH); printf("2つ目の数字入力:"); scanf("%d", &ON); wa = DH + ON; sa = DH - ON; se = DH * ON; sh = DH / ON; am = DH % ON; printf("%d+%d=%d\n",DH, ON, wa); printf("%d-%d=%d\n",DH, ON, sa); printf("%d*%d=%d\n",DH, ON, se); printf("%d/%d=%d...%d\n", DH, ON, sh, am); getch(); return 0; }
出力結果:
1つ目の数字入力:60 2つ目の数字入力:20 60+20=80 60-20=40 60*20=1200 60/20=3...0
西暦年→和暦変換するプログラムを作る
実行例:
西暦: 2016
和暦: 平成28年です
数学的な計算が必要な場合に使用する命令セットです。引数と返りは全てdouble型になります。
主なもの:
サンプル:
#include <stdio.h> #include <math.h> //math.hのインクルードを忘れずに int main(int argc, const char * argv[]) { // insert code here... double value = 0.5; printf("sin : %f\n", sin(value)); printf("sqrt : %f\n", sqrt(value)); printf("ceil : %d\n", (int)ceil(value)); printf("floor : %d\n", (int)floor(value)); return 0; }
文字列を数値型にするときに使われる関数がatoi関数(整数型に変換する場合)とatof関数(小数型に変換する場合)があります。
サンプル:
#include <stdio.h> #include <stdlib.h> //stdlib.hのインクルードを忘れずに int main(int argc, const char * argv[]) { // insert code here... char a[] = "20"; char b[] = "30"; printf("(cast) a + b = %d\n", (int)a + (int)b); printf("(atoi) a + b = %d\n", atoi(a) + atoi(b)); return 0; }
サンプル:
サイコロの目1..6 をランダムに生成
#include <stdio.h> #include <stdlib.h> #include <time.h> //time関数を使うので int main(int argc, const char * argv[]) { // insert code here... int i; printf("///乱数の最大値 = %d///\n" , RAND_MAX); // 現在時刻を種に設定する srand((unsigned)time(NULL)); for(i = 0; i < 5; i++) { // 乱数を取得する int randValue; double randValue1; randValue = rand(); randValue1 = randValue / (double)(unsigned)(RAND_MAX + 1); printf("サイコロの目 : %d\n", (randValue % 6 + 1)); } return 0; }