初めに
今回はedXのコースに参加してC言語基礎の一部をまとめていきたいと思います。
week 1
ライブラリ:
Basic
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
// return 0;
}
// hello, world
-
#include
:プログラムに必要なヘッダーファイルを読み込むのを、コンパイラに明示する。 -
<>
:ファイルのパスをsystem
に指定する。 -
stdio.h
:standard input(stdin)
/standard output(stdout)
を合わせてstdio
、標準入力と出力を扱うヘッダーファイル。 -
stdint.h
:standard integer
、整数型の集合。 -
math.h
:数学関数。 -
stdlib.h
:standard library
、標準ライブラリ関数。
(...ヘッダーファイルがありすぎてここのは一部だけ) -
int
:整数型。(整数値を返す) -
main
:関数名。 -
void
:引数がないことを明示する。(デフォルト設定として) -
return
:関数の返り値。(例えばint
型は整数値で返す。main()
にvoid
を指定しているため返り値を書かなくてもいい。)
-
void
型以外ならreturn
を省略できない。
→void function(void)
(返り値なし、引数無し)void
型なのでreturn
は省略できる。
→int function(void)
(返り値は整数、引数無し)int
型の返り値が求められる。 -
int
型に指定しておいて、return
が省略できるのはmain
だけの特別ルールです。
(実際の手順ではCLIを通してコンパイルしてからhello, world\n
出力される。)
Compiling in detail
preprocessing
:前処理。
#...
:前処理指令の始まり。
#include
:組み込み指令。(後ろに組み込みファイルを指定)
#define
:定義指令。
...
↓
compiling
:アセンブル言語へ解析する。
↓
assembling
:CPUが分かる機械語(0
と1
)へ。
↓
linking
:ソースコードと、ソースコードで用いられたヘッダーファイルの関数へリンクし、ミックスする。
C言語の書き方では、初めのところからコンパイラに与える指令を書いてから、ほかのファイルに(保存された関数やプログラムの)協力を求めて、機械語に翻訳していくように感じています。
書き方としてはJavaScriptと違って、C言語は固定の手続きが必要だと感じて、事前に決められたデータ型しかインプットできないため、柔軟性が下がるけどとても安定していると勝手に思っています。
Practices
下は映像の例の練習:
#include <stdio.h>
#include <cs50.h>
int main(void)
{
// get_int() is cs50 library function
// protorype: int get_int(string prompt, ...);
// string prompt can't be nothing
int x = get_int("x is ");
int y = get_int("y is ");
// %i => check argument's data type if "int" or not
printf("sum of x and y is %i\n", x + y);
}
// x is 1
// y is 2
// sum of x and y is 3
int main(void)
{
int x = get_int("x is ");
int y = get_int("y is ");
printf("%i plus %i is %i\n", x, y, x + y);
printf("%i minus %i is %i\n", x, y, x - y);
printf("%i times %i is %i\n", x, y, x * y);
printf("%i divided by %i is %i\n", x, y, x / y);
printf("remainder of %i divided by %i is %i\n", x, y, x % y);
}
// x is 1
// y is 10
// 1 plus 10 is 11
// 1 minus 10 is -9
// 1 times 10 is 10
// 1 divided by 10 is 0 // incorrect
// remainder of 1 divided by 10 is 1
int main(void)
{
float x = get_float("x is ");
float y = get_float("y is ");
printf("%f plus %f is %f\n", x, y, x + y);
printf("%f minus %f is %f\n", x, y, x - y);
printf("%f times %f is %f\n", x, y, x * y);
printf("%f divided by %f is %f\n", x, y, x / y);
// error: invalid operands to binary expression ('float' and 'float')
// printf("remainder of %f divided by %f is %f\n", x, y, x % y);
}
// x is 1
// y is 10
// 1.000000 plus 10.000000 is 11.000000
// 1.000000 minus 10.000000 is -9.000000
// 1.000000 times 10.000000 is 10.000000
// 1.000000 divided by 10.000000 is 0.100000
(整数に対する%
で剰余演算はできるが、浮動小数点数にはできません。浮動小数点数の剰余は<math.h>
のfmod
が使える。)
sizeof
演算子:
#include <stdio.h>
#include <cs50.h>
int main(void)
{
printf("bool is %lu byte\n", sizeof(bool));
printf("int is %lu byte\n", sizeof(int));
printf("char is %lu byte\n", sizeof(char));
printf("string is %lu byte\n", sizeof(string));
printf("double is %lu byte\n", sizeof(double));
printf("float is %lu byte\n", sizeof(float));
printf("long long is %lu byte\n", sizeof(long long));
}
// bool is 1 byte
// int is 4 byte
// char is 1 byte
// string is 8 byte
// double is 8 byte
// float is 4 byte
// long long is 8 byte
#include <stdio.h>
int main(void)
{
printf("%f\n", 1 / (float)10);
printf("%f\n", 1.0 / 10.0);
}
// 0.100000
// 0.100000
float
なら6桁まで精度が保証されているが。
int main(void)
{
printf("%.10f\n", 1 / (float)10);
printf("%.10f\n", 1.0 / 10.0);
}
// 0.1000000015
// 0.1000000000
7桁から精度が失われる。けれどもっと気になるのは、下のような書き方すると計算ではまた違った結果ができました。
int main(void)
{
printf("%.20f\n", 1 / (float)10);
printf("%.20f\n", 1.0 / 10.0);
}
// 0.10000000149011611938
// 0.10000000000000000555
int main(void)
{
printf("%.20f\n", 1 / (float)10);
printf("%.20f\n", 1.0 / (float)10);
}
// 0.10000000149011611938
// 0.10000000000000000555
int main(void)
{
printf("%.20f\n", 1 / (float)10);
printf("%.20f\n", 1.000000 / 10.000000);
}
// 0.10000000149011611938
// 0.10000000000000000555
(色々と試してみたけどキーワードが分からず検索しても原因がどこにあったかわからなかった。一応メモしておきます。)
四則演算子は計算前に左右の型を揃える、また結果の型もその揃えられた型と同じになるという規則があり
-
計算結果が計算時の型によって型変換する。
-
関数に渡すときにも変換が起こる。
データ型(Data Types):
型によって固定のバイト長を有するもの。
@SaitoAtsushi さんのコメントを見て考えさせられました。JavaScriptしか本格的に勉強していないからかな、ネット通信のこと考えるとプログラムをより小さくする傾向があって、自然と型の大きさに気になってしまいました。問題意識というより応用する場面が完全に違っていて、この行が不適切なので削除します。
-
int(singed int)
:符号付き整数型。一つの整数型データは常に4バイト(32bits)のメモリーを占めている。符号付きで負数を表現するため1bitを削って、残りの31bits(2^31通表現)で正数と負数を正しく表現できる範囲は-2^31 ~ 2^31-1
になる。(-1
は0
も一つの表現として表されているからです。)-
unsigned int
:符号なし整数型。負数の表現が不要になって正数の使える範囲が2倍(2^31→2^32通表現)になり、0 ~ 2^32-1
の正数を正しく表現できる。
-
-
char
:文字型。一文字が1バイト(8bits)のメモリーを取っているので表現範囲は-128 ~ 127
。ASCIIコードは0 ~ 127
範囲で表現されている。 -
float
:単精度浮動小数点型。4バイト(32bits)。符号に1bit、仮数部23bits、指数部8bits。-
FLT_MIN
:float型表現可能な最小正数。1.175494e-38。 -
FLT_MAX
:float型表現可能な最大値。3.402823e+38。 -
- FLT_MAX
:float型表現可能な最小値(負の最大値)。 -
FLT_DIG
:float型の有効桁(精度)。6。
-
-
double
:倍精度浮動小数点型。8バイト(64bits)。符号に1bit、仮数部52bits、指数部11bits。-
DBL_MIN
:double型表現可能な最小正数。2.225074e-308。 -
DBL_MAX
:double型表現可能な最大値。1.797693e+308。 -
- DBL_MAX
:double型表現可能な最小値。 -
DBL_DIG
:double型の有効桁。15。
-
-
struct
:構造体型。データ型を格納する型?(格納する対象がオブジェクトに見えるけど、ブーリアンなど単純の値が格納できるかどうかまだわかりません。主にint
とchar
の集成。) -
union
:共用体型。構造体と同じくほかのデータ型を格納することができる。ただし格納する要素が同じメモリアドレスに置かれているため同時に使えません。
-
enum
:列挙型。int
型整数定数の参照先(変数名)の集合です。(ちょっとマップに似ている気がして)
型なし:
-
void
:データがない型、空洞を示す型です。(データ/引数を入れる必要がない、それを明示するための型)
値としての型:
-
_Bool
:理論型。真を1、偽を0で表す。 -
bool
:理論型。<stdbool.h>
によって使える値です。真をtrue
、偽をfalse
で表す。
型修飾子(Type qualifier):
-
short
:メモリーのバイト長が2バイト(16bits)。 -
long
:メモリーのバイト長が4バイト(32bits)。 -
signed
:符号付き。 -
unsigned
:符号なし。
変換指定子(Conversion Specification):
-
文字の入出力
-
%c
(character):char
。一文字。 -
%s
(string):char
。文字列。
-
-
10進数の入出力
-
%d
(decimal):int
(32bit)・short int
(16bit)。整数を10進数で。 -
%ld
(long decimal):long int
(64bit)。倍精度整数。 -
%u
(unsigned):unsigned int
(32bit)・unsigned short int
(16bit)。符号なし整数。 -
%lu
(long unsigned):unsigned long int
(64bit)。符号なし倍精度整数。
-
-
8進数の入出力
-
%o
(octal):int
(32bit)・short
(16bit)・unsigned int
(32bit)・unsigned short
(16bit)。整数を8進数で。 -
%lo
(long octal):long int
(64bit)・unsigned long int
(64bit)。倍精度整数を。
-
-
16進数の入出力
-
%x
(hexadecimal):int
(32bit)・short int
(16bit)・unsigned int
(32bit)・unsigned short int
(16bit)。整数を16進数で。 -
%lx
(long hexadecimal):long int
(64bit)・unsigned long int
(64bit)。倍精度整数を。
-
-
浮動小数点の入出力
-
%f
(float):float
(32bit)。実数を入出力。 -
%lf
(long float):double
(64bit)。倍精度実数を入出力。(すでに頭文字のd
が使われてるのでl(long)
で2倍というかもしれません。) -
%e
(exponentiation):float
(32bit)。指数表示で実数を出力。(出力だけ) -
%g
:float
(32bit)。最適な形式で実数を出力。(出力だけ)
-
#define
マジックナンバーなどを定数にする。指定した文字列(大文字)に値や式として置き換える。
// #define NAME value(integer/string/expression...)
#define CARDSIZE 52 // integer
#define COURSE "CS50" // string
#define PI 3.14f // float
Memo
int num; // declaration => uninitialized
num = 17; // assignment => initialized
char letter; // declaration => uninitialized
letter = 'abc'; // assignment => initialized
//
int num = 17; // initialization
char letter = 'abc'; // initialization
運算子(Operators):
算術演算子(Arithmetic operators):
-
+
(add):加算演算子。 -
-
(subtract):減算演算子。 -
*
(multiply):乗算演算子。 -
/
(divide):除算演算子。 -
%
(get remainder):剰余演算子(モジュロ演算子)。除算で余った数を返す。
Shorthand operators
-
x += 1
:x = x + 1
-
x -= 1
:x = x - 1
-
x *= 2
:x = x * 2
-
x /= 2
:x = x / 2
-
x %= 2
:x = x % 2
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int a = 2;
int b = 3;
a += 1; // a = a + 1 => a = 2 + 1
b -= 2; // b = b - 2 => b = 3 - 2
printf("a is %i\n", a);
printf("b is %i\n", b);
}
// a is 3
// b is 1
int main(void)
{
int x = 1;
int y = get_int("Input: "); // cs50 library
int z = 15;
x = x * 5; // 1 * 5
x *= 5; // 5 * 5
y = y / 3; // 12 / 3
y /= 2; // 4 / 2
printf("x is %i\n", x);
printf("y is %i\n", y);
printf("z is %i\n", z %= 2);
}
// input 12
// x is 25
// y is 2
// z is 1
int main(void)
{
int a = 2;
int b = 3;
a++; // a = a + 1
b--; // b = b - 1
printf("a is %i\n", a);
printf("b is %i\n", b);
}
// a is 3
// b is 2
ブール式(Boolean Expressions):
-
true
/non-zero
:true
または0
ではない値を真値(true value)として扱われる。 -
false
/0
:false
または0
は偽値。
SaitoAtsushiさんのコメントより補足しました:
「ブール式(Boolean Expressions)」という言葉は映像の中から取り上げたもので英語そのまま訳語として使いました。言葉としてあまり使われていなく、「**制御式 (controlling expression)」という名前が広く使われていると指摘をいただきました。本文では映像からの勉強メモなので一貫性を保つため「ブール式」そのまま使いますが、正式の用語ではありません。
論理演算子(logical operators):
-
!x
(NOT):x
ではない。x
がtrue
なら!x
はfalse
になる。x
がfalse
なら!x
はtrue
になる。論理否定。 -
x && y
(AND):x
とy
両方がtrue
であればtrue
。論理積。 -
x || y
(OR):x
かy
どちらがtrue
であればtrue
。論理和。
比較演算子(relational operators):
-
x < y
(less than):x
はy
より小さい。 -
x <= y
(less than or equal to):x
はy
(を含めて)以下。 -
x > y
(greater than):x
はy
より大きい。 -
x >= y
(greater than or equal to):x
はy
(を含めて)以上。
等値演算子(Equality & InEquality)
-
x == y
:x
はy
と同じならtrue
、でなければfalse
。 -
x != y
:x
はy
と同じではないならtrue
、同じならfalse
。
優先順位と結合規則
行の長さ(Line length)、字下げ(Indentation)
行の長さ:一行に80字。
字下げ:4文字幅。
(JavaScriptの書き方になれていて、どうしても4スペースが読みづらくてスキップします。)
Conditions
条件分岐(if
, Conditional branch with Boolean expressions)
排他的条件分岐(mutually exclusive branchs):
if
条件式は上から下へブール式がtrue
であるかを判定する、一番早くtrue
が出てくるブール式での波括弧のコードを行う(一つの結果だけに導く)。
// two branch
if (boolean-expression)
{
// if boolean expression is true, execute all lines of code in curly bracket
}
else
{
// if boolean expression is false, execute here
}
// two or more branch
if (boolean-expr1)
{
// first branch
}
else if (boolean-expr2)
{
// second branch
}
else if (boolean-expr3)
{
// third branch
}
else
{
// fourth branch
}
#include <stdio.h>
#include <cs50.h>
int main(void)
{
int x = get_int("Give me a number: "); // cs50 library
if (x > 0)
{
printf("x is positive\n");
}
else if (x < 0)
{
printf("x is negative\n");
}
else
{
printf("x is zero\n");
}
}
独立的条件分岐(non-mutually exclusive branchs):
それぞれの条件式が自分のブール式の判定をし、複数の結果が出る可能性があります。
#include <stdio.h>
#include <cs50.h>
int main(void)
{
int x = get_int("Let's check the number: ");
if (x < 0)
{
printf("OK, x is negative.\n");
}
if (x > 0)
{
printf("OK, x is positive.\n");
}
if (x > 5)
{
printf("Excellent, x is bigger than five!\n");
}
else
{
printf("Well, x is smaller than five.\n");
}
}
// "Let's check the number: 2
// OK, x is positive.
// Well, x is smaller than five.
// "Let's check the number: 10
// OK, x is positive.
// Excellent, x is bigger than five!
ケース対応分岐(switch
, Conditional statement with discrete cases)
if
と違ってswitch
では波括弧に想定しているケースを書いておき、break
を付ける/付けないことによって一つのケースとして出力するか、数値を通して残りのケースを順番通り行う。(fall throughを利用する)
#include <stdio.h>
#include <cs50.h>
int main(void)
{
// int x = get_int("Let's check where to go: ");
int x = get_char("Input the first letter of city name: ");
switch (x)
{
case 1:
case 'T':
case 't':
printf("The first stop is Tokyo.\n");
case 2:
case 'N':
case 'n':
printf("The second stop is Nagoya.\n");
case 3:
case 'O':
case 'o':
printf("The third stop is Osaka.\n");
case 4:
case 'K':
case 'k':
printf("The fourth stop is Kyoto.\n");
default:
printf("Have a nice trip!\n");
}
}
// Input the first letter of city name: n
// The second stop is Nagoya.
// The third stop is Osaka.
// The fourth stop is Kyoto.
// Have a nice trip!
int main(void)
{
int x = get_int("Only one fruit you can choose: ");
switch (x)
{
case 1:
printf("Number 1 is Apple\n");
break;
case 2:
printf("Number 2 is Banana\n");
break;
case 3:
printf("Number 3 is Kiwi\n");
default:
printf("I'm still hungry...\n");
break;
}
}
// Only one fruit you can choose: 2
// Number 2 is Banana
// Only one fruit you can choose: 3
// Number 3 is Kiwi
// I'm still hungry...
三項演算子(ternary operator)
SaitoAtsushiさんのコメントより補足しました:
「三項演算子(ternary operator)」も映像の中から取り上げた用語です。C言語では「条件演算子」のほうが適切だと指摘を頂きました。適切な用語ではありませんが、これも一貫性のためそのままにしておきたいと思います。
-
expression ? A : B
:はてな前の式がtrue
であればA
を返す。false
であればB
を返す。
#include <stdio.h>
int main(void)
{
int x;
printf("How old are you: ");
// specify type(%i=>int), if it's true, assign input to x
scanf("%i", &x);
x >= 18 ? printf("%i is Adult.\n", x) : printf("%i is under age\n", x);
}
// How old are you: 17
// 17 is under age
// How old are you: 20
// 20 is Adult.
Loops
for
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; i++)
{
printf("i is %i\n", i);
}
}
// i is 0
// i is 1
// i is 2
int main(void)
{
int j;
printf("Please input the length: ");
scanf("%i", &j);
for (int i = 0; i < j; i++)
{
printf("i is %i\n", i);
}
}
// Please input the length: 3
// i is 0
// i is 1
// i is 2
for
ループの実行順序:
for (start; boolean-expression; increment/decrement...)
{
// do something here
}
start
↓
boolean-expression
↓ if true
do something in {}
↓
increment/decrement...
↓
boolean-expression
↓ if true
do something in {}
↓
increment/decrement...
↓
boolean-expression
↓ if false
end the loop
while
int main(void)
{
int i = 1;
int sum = 0;
while (sum <= 5)
{
sum = sum + i;
printf("(i, sum) = (%d, %d)\n", i, sum);
}
}
// (i, sum) = (1, 1)
// (i, sum) = (1, 2)
// (i, sum) = (1, 3)
// (i, sum) = (1, 4)
// (i, sum) = (1, 5)
// (i, sum) = (1, 6)
while(boolean-expr)
はブール式の真偽判断してからコードを実行する。
do...while
int main(void)
{
int i = 1;
int sum = 0;
do
{
sum = sum + i;
printf("(i, sum) = (%d, %d)\n", i, sum);
} while (sum <= 5);
}
// (i, sum) = (1, 1)
// (i, sum) = (1, 2)
// (i, sum) = (1, 3)
// (i, sum) = (1, 4)
// (i, sum) = (1, 5)
// (i, sum) = (1, 6)
do...while(boolean-expr)
は先にコード実行してからブール式の判断する。
while vs. do...while
int main(void)
{
int i = 1;
int sum = 10;
do
{
sum = sum + i;
printf("(i, sum) = (%d, %d)\n", i, sum);
} while (sum <= 5);
}
// (i, sum) = (1, 11)
// note: run at least one time
do...while
のようにブーリアン判断が後ですると、せめて一回の実行が確保できる。
int main(void)
{
int i = 1;
int sum = 10;
while (sum <= 5)
{
sum = sum + i;
printf("(i, sum) = (%d, %d)\n", i, sum);
}
}
// *nothing happend
while
は先に判断を行うので何も表示されなかった。