C言語勉強会1 C言語基礎編
C言語勉強会1 C言語基礎編
KFIP・KogCoder合同勉強会で作成した資料をこのまま載せておきます。
この資料は工学院大学情報学部の1年生が履修する"プログラミング及び演習1"の内容に対応しています。
Twitter
@kogakuin_kfip2
@kogcoder
目的
C言語の基本的な文法・構文を理解し、迷わずコーディングができるようにする。
テストでいい点が取れるようにする。😚
変数と型、配列
変数
// var_create.c
#include <stdio.h>
int main(void)
{
int a,b=10,c=b;
a = 0;
printf("a: %d\n", a); // => a: 0
printf("b: %d\n", b); // => b: 10
printf("c: %d\n", c); // => c: 10
return 0;
}
変数-解説
変数を使うためには 宣言 が必要です。
int a;みたいに
変数に値を代入したい時は 代入演算子 = を使います。
a = 10;
また、変数は宣言と代入を同時に行えます。
これを初期化と言います。
int b = 10;
型
// type.c
#include <stdio.h>
int main(void)
{
int a = 10;
float b = 1.5;
double c = 1.5;
printf("a: %d\n",a); // => a: 10
printf("b: %f\n",b); // => b: 1.500000
printf("c: %lf\n",c); // => c: 1.500000
return 0;
}
型-解説
型には 整数型 と 浮動小数点型 のパターンに分類されます。(void型もあるけど)
整数型 はint, unsigned int, char等があり、整数を表すことができます。整数型で指定された変数には整数値しか代入できません。もし小数が入った場合は切り捨てられます。
浮動小数点型 はfloat, double等があり、整数でない実数を表すことができます。floatとdoubleの違いは精度の違いです。floatは小数6位、doubleは小数15位まで再現できます。(コンパイラによる)もし整数が入った場合は実数に変換されます。
計算
// var_calc.c
#include <stdio.h>
int main(void)
{
int a=10,b=10;
a = a + 1;
b += 1;
printf("a: %d\n", a); // => a: 11
printf("b: %d\n", b); // => b: 11
printf("a++: %d, a: %d\n", a++, a); // => a++: 11, a: 12
printf("++b: %d, b: %d\n", ++b, b); // => ++b: 12, b: 12
return 0;
}
計算-解説
b += 1;の += はb = b + 1;と同じ意味です。
これは複合代入演算と言い、 算術演算子op(+, -, *, /, %) を組み合わせて x op= y; と表せます。
ちなみに、
b -= 1;はb = b - 1です。
a++やb--をインクリメント/デクリメント演算子と言います。a += 1、b -= 1と同じ意味です。++aを前置記法(すぐに加算)、a++を後置記法(あとで加算) と言って加算が行われるタイミングが違います。
キャスト
// type_cast.c
#include <stdio.h>
int main(void)
{
const int a = 2;
double b1, b2;
b1 = 1.0 / a; // aが自動的に(double)aとして処理される
b2 = 1.0 / (double)a;
printf("1.0 / a: %lf\n", b1); // => "1.0 / a: 0.500000
printf("1.0 / (double)a: %lf\n", b2); // => 1.0 / (double)a: 0.500000
b1 = 1 / a; // (1 / a)が自動的に(double)(1 / a)として処理される
b2 = 1 / (double)a; // (1)が自動的に(double)(1)として処理される
printf("1 / a: %lf\n", b1); // => 1 / a: 0.000000
printf("1 / (double)a: %lf\n", b2); // => 1 / (double)a: 0.500000
return 0;
}
キャスト-解説
キャストとは明示的な型変換のことを言います。
int a = 10; double b = a;の場合、コンパイラが自動的にb = aをaを一時的にdouble型に変換し、bに代入します。これを暗黙の型変換と言いますが、明示的に行わないと結果が異なる場合があります。
型変換したい変数の前にキャスト演算子(変更後の型) を付けます。
配列
// array.c
#include <stdio.h>
int main(void)
{
int array1[3]; // 初期値不明
int array2[] = { 1, 2, 3 }; // 配列の宣言時のみ初期値を指定できる
int array3[3] = { 1, 2 }; // 3つ目の要素には初期値が無いが自動で0が代入される
array1[0] = 1;
array1[1] = 2;
array1[2] = 3;
for(int i=0;i<sizeof(array1)/sizeof(int);++i){
printf("array1[%d]: %d\n",i,array1[i]); // array1と2は1,2,3の順番で出力される
printf("array2[%d]: %d\n",i,array2[i]);
printf("array3[%d]: %d\n\n",i,array3[i]); // array3は1,2,0の順番で出力される
}
return 0;
}
配列-解説
同じ型の複数のオブジェクトを扱いたい場合は配列を使います。
宣言の時のみまとめて数値を代入することができます。
sizeof演算子は、指定したオブジェクトがメモリで占めているバイト数を調べることができます。int型のオブジェクトは8バイトを占めるので、要素数が3つint型の配列は8*3=24バイトを占めます。配列の要素数を調べたい時は配列のバイト数を配列の要素1つ分のバイト数で割ればいいですね。
標準入出力
printf関数とscanf関数はstdio.hという外部ファイルに関数が定義されているので、使う時は
#include <stdio.h>
で読み込んでください。
printf関数
// printf.c
#include <stdio.h>
int main(void)
{
int a = 10;
float b = 1.5;
double c = 1.5;
printf("Hello\nWorld\n");
/*=> Hello
World */
printf(" 123456\n");
printf("a: %6d\n",a); // => a: 10
printf("b: %6.4f\n",b); // => b: 1.5000
printf("c: %06.2lf\n",c); // => c: 001.50
return 0;
}
printf関数-解説
printf関数は文字列を出力することができます。
\n で改行することができます。他にも \a で警告を出すことができます。(詳しく知りたい人はエスケープシーケンスと調べてください)
変数の値を出力したい時は、文字列の中にフォーマット指定子を入れます。型によって、使うフォーマット指定子が異なります。
int型なら %dか %i, float型なら %f, double型なら %lf (由来はlong float?)
他にも色々あるので、↑のサンプルプログラムを見て使い方を把握してください(丸投げ)
scanf関数
// scanf.c
#include <stdio.h>
int main(void)
{
int a,b;
scanf("%d %d", &a, &b); // <= 10 20 ,&はアドレス演算子 プロ演3で学ぶ
printf("a: %d\n", a); // => a: 10
printf("b: %d\n", b); // => b: 20
return 0;
}
scanf関数-解説
scanf関数はprintf関数の逆で入力を行うことができます。
使い方は上記のサンプルプログラムを見て把握してください。(丸投げ)
例えば、10 20と入力した場合、1つ目の変換指定子%dが10に2つ目の変換指定子%dが20に割り当てられます。小数は%lfを使えばいいですね。
変数に入力した数値を入れたい時は、変数の前にアドレス演算子を付けてください。scanf関数の内部でその変数に入力値を代入するので、その変数のメモリ上のアドレスをアドレス演算で求め、それをscanf関数に渡しています。(ポインタ渡しですね)
scanf関数を連続して使う
// contiguous_scanf.c
#include <stdio.h>
int main(void)
{
int array[3];
for(int i=0; i<sizeof(array)/sizeof(int);++i){
scanf("%d", &(array[i])); // <= 1 2 3
/* カンマ区切りなら
scanf("%d,", &(array[i]));
*/
}
for(int i=0; i<sizeof(array)/sizeof(int);++i) {
printf("array[%d]: %d\n", i, array[i]); // => 1 2 3
}
return 0;
}
scanf関数を連続して使う-解説
実はscanf関数はスペースやタブ、改行で分離されます。
scanf関数を何度も実行して複数の値を入力するプログラムの場合、1つ入力してEnterキーを押すのを繰り返すよりも、まとめて入力しそれぞれをスペースで区切る方が効率的に入力することができます。もし、カンマで区切りたい場合は"%d,"としてください。10,20,30,40と入力したなら10,を読み取り、次に20,を読み込み、scanf関数が実行されなくなるまで順番に読み込まれます。
条件分岐
if文
// if.c
#include <stdio.h>
int main(void)
{
int a, b, c;
scanf("%d", &a); // <= 10
b = a == 0;
c = a == 10;
printf("b: %d\n",b); // => b: 0
printf("c: %d\n",c); // => c: 1
if (a == 0) {
printf("aは0\n");
} else if (a <= 10) {
printf("aは10以下\n");
} else {
printf("aは11より大きい\n");
}
return 0;
}
if文-解説-1
==や<のことを 関係演算子 (==, !=, <, >, <=, >= ) と言います。
関係演算子を使うことで、比較を行うことができます。
x == yなら、xとyが同じ値なら1(真)を返し、違うなら0(偽)を返します。
例えば、result = 10 == 10;なら10と10は同じ値なので、resultに1が代入されます。
また、論理演算子 (&&, ||, !) を使うと、いくつかの比較をまとめることができます。
int result1 = 10 == 10 && 0 == 1; // => 0
int result2 = 0 != 0 || 10 > 5; // => 1
int result3 = !(40 <= 80 && 80 <= 160); // => 0
if文-解説-2
if文で条件分岐ができます。
if(条件式1){
文1;
} else if (条件式2) {
文2;
} else {
文3;
}
if文の中に()に条件式を入れ、条件式が0であればif文のブロック内で囲まれた文1は実行されません。0以外の数値であれば実行されます。else ifは上記のifの条件式が0の場合のみ実行され、if文と同じように処理されます。else ifはいくつでも作ることができます。else節では、上記のifやelse ifの条件式が全て0だった場合のみ実行され、ブロック内の文3が実行されます。
if文-解説-3
条件式の部分は(1)や(0)でも構いません。
さらに、
if(1)printf("動く\n");
if(2)
printf("これも動く\n");
文が1つだけの場合は{}を省略できます。
また、
if(0.00)printf("実行されない\n");
if(0.01)printf("実行される");
条件式の部分が小数の場合は、完全に0でない限り真なので注意してください。
switch文
// switch.c
#include <stdio.h>
int main(void)
{
int a;
scanf("%d", &a); // <= 0
switch(a) {
case 0: // breakが無いのでfall through(case 1に行く)
a = 10; // ここでaが10になってもcase 10には行かない
printf("aは0\n");
case 1: // fall through(case 2行く)
case 2:
printf("aは2以下\n");
break; // breakがあるのでここで終了
case 10:
printf("aは10\n");
break; // breakがあるのでここで終了
default:
printf("aはよくわからない");
break;
}
return 0;
}
switch文-解説1
switch文の構成はこの通りです。
switch(整数) {
case 整数1:
文;
break;
case 整数2:
文;
break;
default:
文;
break;
}
switchの()に入れた整数に対応したcaseラベルの文を実行します。整数がどのラベルにも当てはまらない場合は、defaultラベルが実行されます。break;を付け忘れると下のラベルの文も実行されるので気を付けましょう。
switch文-解説2
switch(x){
case 0:
break;
case 1:
break;
}
if(x == 0){}
else if (x == 1){}
のようにif文を使って再現することができます。
整数の一致しか判定できないswitch文は一見不便に見えますが、
C言語の拡張版「C++」に登場するenum型と相性がとても重宝されます。
ループ
while文とdo while文
// while.c
#include <stdio.h>
int main(void)
{
int a;
scanf("%d", &a); // <= 10 だったら?
while (a < 10) {
++a;
printf("a: %d\n",a);
}
printf("\n");
scanf("%d", &a); // <= 10 だったら?
do {
++a;
printf("a: %d\n",a);
} while (a < 10); // セミコロン忘れるな!!!
return 0;
}
while文とdo while文-解説
while (条件式) {
文;
}
do {
文;
} while(条件式);
while文は上部駆動のループで、条件式->ブロック内実行をくりかえします。
do while文は下部駆動のループで、ブロック内実行->条件式をくりかえします。
ブロック内でbreak;が実行された場合、ループが強制終了します。また、continue;が実行された場合、ブロックの終わりにジャンプし、条件式が実行され、元のループに戻ります。
for文
// for.c
#include <stdio.h>
int main(void)
{
int a;
for(a=0;a<3;a++){
printf("a: %d\n",a);
}
printf("for終了後のa: %d\n", a);
}
for文-解説
for (初期化処理部; 条件式; 更新処理部) {
文;
}
処理の流れは
①初期化処理部
②条件式
③文
④更新処理部
②~④くりかえし
です。条件式が偽になるまで繰り返されます。
ちょっと複雑なfor文
// for_exp.c
#include <stdio.h>
int main(void)
{
int score[100]={0};
int student_num = 0;
printf("生徒の数を入力してください: ");
scanf("%d", &student_num); // <= 6
if(student_num <= 0)return -1; // student_numが0以下だったら、プログラムを終了
for (int i = 0;;) { // 普通はこんなことしません
int input_score;
printf("点数を入力してください: ");
scanf("%d",&input_score); // <= 80 70 30 40 90 10
if (!(0 <= input_score && input_score <= 100)) {
printf("もう一度");
continue;
}
score[i++] = input_score;
if (i >= student_num)break;
}
for(int i = 0; i < student_num; ++i) {
printf("score[%d]: %d\n", i, score[i]);
}
return 0;
}
ちょっと複雑なfor文-解説
めちゃくちゃなプログラムですが、我慢してください。
for文は(;;)のように初期化処理部等を省略することができます。また、初期化処理部で変数を定義した場合、()内とブロック内のみで使用することができます。while文同様にbreak;やcontinue;等のジャンプにも対応しています。continue;が実行された場合、ブロックの終わりに移動し、更新処理->条件式が実行され、元のループに戻ります。
マクロ
#define
// define.c
#include <stdio.h>
#define MAX_SCORE_SIZE 8
#define SWAP(x, y) (y = x - y, x -= y, y += x)
int main(void)
{
int score[MAX_SCORE_SIZE]={40,60,10,0,50,70,20,30};
for (int i = 0; i < MAX_SCORE_SIZE - 1 ; ++i) {
for (int j = 0; j < MAX_SCORE_SIZE - 1; ++j) {
if (score[j] > score[j+1]) {
SWAP(score[j], score[j+1]);
}
}
}
for (int i = 0; i < MAX_SCORE_SIZE; ++i) {
printf("score[%d]: %d\n", i, score[i]);
}
return 0;
}
#define-解説1
#defineはプリプロセッサ指令と言って、コンパイルの前に実行される前処理の一種です。#defineはマクロを定義することができます。マクロはコンピュータの自動操作の事を指します。#define NAME 20と書き、ソースコード内にNAMEと書くと、プリプロセッサが自動で20に書き換えてくれます。
また、式だったら何でも書き換えてくれます。
例では、2つの変数の値を入れ替える動作をswapと言いますが、それをマクロでまとめています。これをSWAPマクロと呼んだりもします。(2Qでやる関数の定義に似ていますね。)
#define-解説2
#define以外のプリプロセッサ指令は
#include <math.h>
#if 0
printf("消える\n");
#end if
#if 1
printf("消えない\n");
#end if
などが、挙げられます。(もちろん他にもたくさんあります。)
#includeは外部ファイルの読み込みを行います。例ではmath.hという数値計算のライブラリを読み込もうとしています。math.hにはルートを計算するsqrt関数などがあります。(伏線)
#if #endifは条件付きコンパイルを行うことができます。#if の後ろの条件式が偽の場合は、#if と #endif で囲った部分がコメントアウトされます。便利なのでどんどん使っていきましょう!
最後に
プロ演1で学ぶプログラムの解説でした。いやー長かったですね。
参考文献
ピーター・プリンツ ウーラ・カークプリンツ . Cデスクトップリファレンス . オライリージャパン , 2003年
“苦しんで覚えるC言語” . MMGames .
https://9cguide.appspot.com , (参照 2023-05-01)
Discussion