C言語基礎
C言語は関数の集まり(順番は無い)
関数名(mainは必須),変数名のルール
半角アルファベット,数字
_
1文字目は数字以外
何もしないプログラム
int main(void)
{
return 0;
}
言語の歴史
マシン語(機械語):0101
アセンブラ:16進数を記号化
FORTRAN:世界初のプログラミング言語
機械語への翻訳
-
インタプリタ:同時翻訳
-
コンパイラ:一斉翻訳
プリプロセッサ:文字列の調整
コンパイラ:最適化→高速
リンカ:実行可能ファイル(.exe)にする
書き方
トークン:単語
大文字と小文字を区別する
/* */ :コメントアウト
printf
print format
int main(void)
{
printf("HelloWorld");
return 0;
}
mainもprintも関数。
C言語のコンパイラはprintf関数を知らない→#include疑似命令 <ファイル名>
#include
\n(文末にも必要)
\t
出力変換指定
%d:この部分を後述の数値に入れ替える
- 100円
printf("%d円\n", 100); - 計算
ptintf("%d\n",100 + 200);
割り算は切り捨て
何重の()であっても()
数値
10進数:数字
8進数:先頭に0をつけた数
16進数:先頭に0xをつけた数
- 整数
- 実数(小数も)
浮動小数のため、10進数のみ
%f
変数
関数内の最初に宣言
宣言した関数内のみで使用可能
+=
int型:整数
double型:実数
-
キャスト変換
強制的に型を変換する機能
(変換する型名)数値や変数名 -
桁数揃え
空欄:%桁数d
0:%0桁数d
実数値
%全体桁数.小数桁数f
*全体桁数は小数の桁数と小数点を含む
scanf関数
キーボードから入力する
scanf(" 入力変換指定子",&変数名);
入力変換指定子:入力された数字をどのような数値に変換するか
変数名:入力されたデータを記憶しておく変数名
実数:%lf
複数入力する場合はscanf(,)等で区切り文字も入力し、
入力も,で区切る
int min,max,sum
printf("最大値と最小値を「,」区切りで入力してください。")
scanf(%d,%d,&max,&min);
add=(min+max)(max-min+1)/2;
printf(%d\n,add);
int price,one,three,five,eight;
printf("定価を入力してください。");
scanf("%d",&price);
one=price*0.9;
three=price*0.7;
five=price*0.5;
eight=price*0.2;
printf("定価%d円の1は%d,3は、、、です。"price,one)
if文
真:0以外
偽:0
=:代入
-
等値演算子
==:等しい→真
!=:等しい→偽
if文の条件に使用される。 -
関係演算子
<,>,<=,>= -
論理演算子
&&:AND
||:OR
!:NOT
else-if
else{if(){} }
↓
else if(){}
switch-case文
複数の番号との一致を判断
値が整数の場合しか判断できない
*if文のように、変数同士を比べたり、大小関係の比較などは不可能
同じ値のcaseまで処理の順番をジャンプ
break文: {} から抜け出す
case:ジャンプする場所を示す
default:当てはまる数値が無かった場合のcase
switch (month) {
case 1:
printf("睦月\n");
break;
case 2:
printf("如月\n");
break;
default:
printf("そんな月はありません。\n");
break;}
プログラマーは単純な計算を複雑に組み合わせるパズルであり、
それはある側面では芸術
for
定数回のループに使用
for (初期化; 条件式; 更新) {
繰り返す文;
}
初期化:カウント変数の初期化
条件式:ループの終了条件
更新:カウント変数の更新
無限ループ
非常に広く使われるテクニック
一般的なアプリでは、キー入力やマウス入力があったら、それに対応した画面を表示するという処理を、
ユーザーがアプリを終了させるまでは永遠に繰り返す
- 強制脱出
無限ループをfor文内のif(条件)breakで脱する。
while
何回処理を繰り返せば良いのかわからない場合
do-while文
必ず1回は実行される
do {
繰り返す文;
} while (条件式)**;**
入力チェックの時に威力を発揮
関数の利用
プロトタイプ宣言
関数の形を宣言しておく。
関数の順番に関わらず関数を利用可能になる。(mainは必要ない)
int sum(void);
printf関数等は、#includeの中にプロトタイプ宣言をしている
関数の呼び出し
sun();
引数
仮引数:関数宣言に書かれた引数の型と名前
プロトタイプ宣言では型だけでよい)int sum(int);
実引数:関数に渡す引数のうち数値
すべての引数に数値を渡さないと呼び出せない
戻り値
関数から返ってくる数値
int** sum(int min)
{
printf();
**return 0;**
}
int型の数値0
#include <stdio.h>
int calc(int);
int main(void)
{
int year,hold;
printf("西暦年を入力してください。\n");
scanf("%d",&year);
hold = calc(yaer);
switch(hold){
case 1:
printf("夏季\n");
break;
case 2:
printf("冬季\n");
break;
case 3
printf("開催しない\n");
break;
};
return 0;
}
int calc(int year)
{
if(year % 2 == 0){
if(year % 4 == 0){
return 1;
}else {
return 2;
}
} else {
return 3;
}
}
変数
スコープ:変数の寿命を決定する仕組み
変数がブロック内で独立しているため、自由に関数を使える。
ブロックは関数内で自由に設定できるため、変数の用途にも合わせられる。
グローバル変数
1つのソースファイル内で共有される
初期化しないと0になる
グローバル変数と同名のローカル変数ではローカル変数が優先される
プログラムで関数を使う場合、関数内の変数がグローバル変数ではなくローカル変数であることを確定させるため。
静的ローカル変数(static)
関数が以前に呼び出された時の値を覚えておきたい場合に使用
値はプログラムが終了するまで残り、初期化せずカウント可能
自動的に0に初期化
= 0で初期化しても最初しか"= 0"は行われないため、カウント可能
配列
1度に同じ型の変数を複数宣言
番号は0番から始まる
- 初期化
数字が足りない場合、残りにはすべて0が代入
要素数は省略可能
宣言の時のみ
sizeof演算子
変数や配列のサイズを求める
sizeof(array) / sizeof(array[0])
memcpy関数
memcpy(コピー先配列名、コピー元配列名、配列全体のサイズ(sizeof(array)))
- バッファオーバーラン
「memcpy関数は配列の長さを考慮しないため、無関係の他の配列を上書きしてしまう」こと。
#include <stdio.h>
int main(void)
{
int data[] = {79,42,39,79,13,75,19};
int i,sum = 0,avg;
for(i = 0; i < sizeof(data) / sizeof(data[0]); i++){
sum += data[i];
}
avg = sum / ( sizeof(data) / sizeof(data[0]));
printf("%d\n",avg);
return 0;
}
#include <stdio.h>
int main(void)
{
int data[10];
int i;
for(1 = 0; i < 10; i++){
printf("%d 番目の数値を入力してください。"i);
scanf("%d",&data[i]);
}
for(1 = 9; i > 0; i--){
printf("%d",data[i]);
}
printf("\n");
return 0;
}
文字列:char型
1文字用の文字変数。
(文字列用の変数は無い。
文字列が何文字になるか分からずメモリサイズが不明のため。)
printf関数での表示は%c
255種類のみのため、日本語はNG
- しくみは整数型と同じ
文字コードの番号を代入しているため、変数には数字が代入されている。
「char c = 'A' + 9;」をすると10番目のアルファベットを取り出せる。
「char c = '8'; /* 数字 /
int suuti = c - '0'; / 数値に変換 */」
をすると数値に変換され、8が出力される。
変換する際に文字か数字か判断する
→文字の番号が、'0' 〜 '9' の間にあるか調べる
種類によって調べる関数がある
#include
isalnum 英数字(A〜Z a〜z 0〜9)
isdigit 10進数(0〜9)
isxdigit 16進数(A〜F a〜f 0〜9)
isalpha 英字(A〜Z a〜z)
isupper 英大文字
islower 英小文字
ispunct 記号
isspace スペース
文字配列
char str[6] = { 'M', 'A', 'R', 'I', 'O', '\0' };
文字列を表現
文字列の最後にEOS(\0)を記憶して文字数を判断
\0か要素数の指定が必要。ないと文字列が永遠続く。
printf関数での表示は%s
文字列リテラル:""
char str[] = "MARIO";
初期化の時にしか使えない→初期化時以外はstrcpy関数
atoi関数
変数に文字列を数値に変換した値を代入
数字ではない文字列の場合:0に変換
#include
atof関数:実数に変換
strcpy関数
文字列のコピー→文字列の代入に使われる
#include
strcpy(コピー先配列名, コピー元配列名);
strncpy関数
先頭から指定された文字数だけをコピー
strncpy(コピー先文字列配列名, コピー元文字列配列名, コピーする文字数);
EOS必要
strncpy(str2, str1, 3);
str2[3] = '\0'; /* EOSを付加 */
- 連結
1,並べるだけ
char str[] = "DRAGON""QUEST";
2.strcat関数
連結
#include
strcat(元の配列, 追加する配列);
元の配列は、元の文字列+追加する文字列だけの長さが必要
→長さが足りないとバッファオーバーランエラー
sprintf関数
printf関数と同じ機能だが、sprintf関数は結果を配列の中に記憶する
sprintf(結果を記憶する配列, 書式文字列, 各種変数・・・);
ex)sprintf(str, "%s%s%d\n", str1, str2, i);
もともとprintf関数は文字列を表示する関数のため、printf(str);で直接表示できる。
文字列のscanf関数
1.配列名の前に&を付けない
2.文字配列の要素数を指定する(文字数が超えるとバッファオーバーランが起こる)
scanf("%32s", str);
スペースは区切り記号のため入力不可
strlen関数
文字列の文字数を数える
i = strlen(str);
strcmp関数
文字列自体を比較→同じである場合には0を返す
(==は同じメモリを使うかを比較)
#include <string.h>
#include <stdio.h>
int main(void)
{
str last[256],first[256];
printf("苗字を入力してね。"\n);
scanf("%s",last);
printf("名前を入力してね。"\n);
scanf("%s",first);
strcat(last,first);
printf("%s\n",last);
return 0;
}
メモリ
メモリの中にある多数のロッカーは荷物があれば1,なければ0
=2進数で記憶している(記憶内容も2進数)
32ビットコンピュータ:32個をひとまとめにして取り扱い
実際は1Bづつ。
メモリには、1バイト毎に番号がつけられて区別されている。
変数はメモリに番号付きで記憶されている
コンパイルすると、変数名は番号に変換される
アドレス
- 変数のメモリ上の番号(アドレス)を調べる
%pを使用。変数には&を付ける
printf("%p\n", &i);→16進数で表示。
型ごとのサイズ
char:1バイト(文字コードが255のため)
int:4バイト
配列のアドレス
配列のアドレスは&不要
個々の要素は&必要
配列自体のアドレス=要素最初のアドレス
→要素番号の意味は、
「配列名のアドレス+要素番号」のメモリを参照するという意味
&
+や-等と同じく計算を行う演算子の1つで、
メモリ上の番号を知ることができる。
-
値渡し
関数に単なる数値を引数として渡すこと。
(関数に渡されるデータは全て数値) -
参照渡し(C言語には無い)
変数の中身を直接変更したい場合、値渡しされても元の変数の中身は変えられない。
→&演算子を使ってアドレスを求めて、そのアドレスの数値を渡す -
scanf
数値の入力:&を付けることでアドレスにデータを保存できる。
文字列の入力:配列名を渡せばアドレスが分かるため、&は不要。
ポインタ
関数に渡したアドレスの値を保存しておく変数。
ポインタ型
int型やdouble型と同じような型。
他の型から作り出される派生型(寄生虫)である。→他の型とポインタ型を合体させて作る。
ex.)int型とポインタ型を合体させると、intへのポインタ型という型ができる。
さらに、intへのポインタのポインタ型という、多重のポインタ型を作れる。
→型によってサイズが異なるため、何メモリ数分引き出せばよいかわかる必要がある。
ポインタ値
ポインタ型で扱える数値=要するにアドレスの値
整数や実数といった数値の区別と同様に、ポインタ値という区別が存在する。
※ポインタ値は整数のため整数値でも問題無いはずだが、ポインタは計算に使用しないため分けている。
ポインタ変数
ポインタ型で宣言されたポインタ値を記憶できる変数のこと。
ポインタ変数そのものを計算に使うのではなく、
それが指し示している変数(アドレス番号のメモリの値)を計算するのがポインタ変数の目的。
変身機能
普段はポインタ変数として振る舞っているが、
指し示している変数の計算が必要な時には、変数の前に*を付けて普通の変数に変身する。
通常変数モードで使用したいアドレスをポインタ変数モードで代入しておく。
直接メモリの書き換えを指定するのではなく、
書き換えたいメモリのアドレスを代入→モードを切り替えて書き換える2段構
-
ポインタ変数モード
アドレスさえ記憶していれよい。 -
通常変数モード
通常の変数と同様に、演算子を使って計算できる。 -
宣言
変数名の前に*を付ける
int *p; -
代入
OSが割り振ったアドレスの値を代入する。
→別に宣言した変数のアドレスを代入すればよい。
int *p;
int i;
p = &i; /ポインタ変数pに変数iのアドレスを代入/
*p = 10; /通常変数モードに切り替え、pが記憶したアドレスに10を代入/
↓
結果として、変数iの値は10に書き換えられている
この2つはそもそも同じメモリ位置を示している -
ヌルポインタ
この代入を行う前のアドレスを使用するとバグになる。
→int *p = NULL;でヌルで初期化しておく。
間接参照演算子のと、ポインタ変数を宣言する時に使用する記号(ポインタ変数を通常変数モードにする。)のはまったく別の記号
ポインタの本当の使い方は、ショートカットとして使用すること
ポインタ変数に、実際に存在する変数のアドレスを記憶しておけば、
そのポインタ変数が使える場所であれば、元の変数が使えない場所であっても、
ポインタ変数を通常変数モードに切り替えれば、元の変数と同じく使うことができる。
→Javaでは参照
C言語のポインタは、完全に手動
使いこなすとポインタだけで、ほぼあらゆる制御構造、データ構造を実現可能
ポインタ型の引数
戻り値では1つしか情報を返せない。
→引数をポインタ型の値渡し(値のコピー)で渡す
void sum(int* pvalue)
{
*pvalue = 100; /*通常変数モードに切り替えて返す値を代入*/
return ;
}
→呼び出し時に指定した変数に情報が記憶されている(pvalue = 100)
#include <stdio.h>
void func(int* pvalue);
int main(void)
{
int value = 10;
printf("&value = %p\n", &value);
func(&value)/* アドレスを渡す */
printf("value = %d\n", value);
return 0;
}
void func(int* pvalue)
{
printf("pvalue = #p\n", pvalue);
*pvalue = 100; /*通常変数モードに切り替えて返す値を代入*/
return;
}
- 実行結果
&value = 0F68
pvalue = 0F68
value = 100
ポインタ変数にアドレス値が代入されているため、
通常変数モードに切り替えて、ポインタ変数に代入されているアドレス上の値に返す値を代入できる
→呼び出された関数から、呼び出し元の変数の中身を書き換えられる(C言語でのもっともポピュラーなポインタの使い方)
配列の引数
配列自体を渡すのではなく、配列の先頭のアドレスを渡している
- 要素数は無視される。
int main(void)
{
int array[5] = { 15, 98, 98, 17, 42 }; /* 要素数が5 */
average = getaverage(array);
}
int getaverage(int data[10]) /* 要素数が10 */
{
}
- 実行結果
エラー
- 関数内で配列の値を変えると呼び出し側まで変化する
※通常は呼び出された関数で変更しても値は変わらない
int main(void)
{
int array[10] = { 15, 78, 98, 15, 98, 85, 17, 35, 42, 15 };
printf("array[3] = %d\n", array[3]);
**average = getaverage(array);**
printf("array[3] = %d\n", array[3]);
}
int getaverage(int data[10])
{
data[3] = 111; /* 引数の配列の値を変更 */
}
- 実行結果
array[3] = 15
array[3] = 111
↓
3つは同じ意味の仮引数宣言
int getaverage(int data[10]); /* 幼稚 /
int getaverage(int data[]); / おすすめ /
int getaverage(int data); /* 普通のポインタ型と紛らわしい */
[]
[]は配列の要素数を差していなくても、アドレス値なら何でもよい。
(配列でも。先頭アドレスに要素分を足しているだけのため)
配列名[]
[]内の記号の有無に関わらず、配列名は配列の先頭要素のアドレス(ポインタ値)となる。
[]の違い
宣言時[]:要素数指定
要素使用時[]:アドレスに足し算する演算子
- ポインタ変数を配列のように使える
ポインタ変数も配列名も同じメモリアドレスを差しているだけのため。
int main(void)
{
int *data:
int i;
int array[10] = {15, 78, 98, 15, 98, 85, 17, 35, 42, 15};
data = array; /* ポインタ変数に配列のアドレスを代入 */
for(i = 0; i < 10; i++)
{
average += data[i]; /* 配列みたいに使える */
}
}
ポインタ演算
ポインタ専用の書き方
(ポインタ変数 + 要素番号)
(先頭は、ポインタ変数を通常変数モードに切り替えるための演算子)
- 値の流れ
ポインタ変数 + 要素番号
↓
加算後のアドレスを通常変数モードに切り替える
↓
先頭アドレスから指定数だけ進んだ先のメモリにアクセス
int main(void)
{
int *data;
int i, average = 0;
array[10] = {15, 78, 98, 15, 98, 85, 17, 35, 42, 15};
data = array; /* ポインタ変数に配列のアドレスを代入 */
for (i = 0; i < 10; )
{
average += *(data + i); /* ポインタ演算 */
}
return 0;
}
ポインタ変数自体を増加
int main(void)
{
int *data;
int average = 0;
array[10] = {15, 78, 98, 15, 98, 85, 17, 35, 42, 15};
for (data = array; data != &array[10]; data++)
{
average += *data;
}
return 0;
}
※for文の条件
data = array:初期値に配列arrayのアドレスを代入
data++:data内アドレスを1(ポインタ変数が差す型のサイズ分)増やす
data != &array[10]:ポインタ変数が(0から数えて)10番目の要素と同じ値になる
ポインタ変数の値そのものを増加させてアクセスすることで、
配列の要素1つ1つに順番にアクセスしていく
上記のいくつかのやり方は今の高性能CPUでは動作は関係ない。
しかし、組み込みコンピュータでは最適化されない場合もある。
C言語の本質はショートカット(&変数→*で通常変数モード)
よってアドレスとは関係ないが、バグの時はアドレスを踏まえ修正する必要がある
ポインタプログラム例
0〜100の範囲で入力された複数の数値の中から、
最大値と最小値を求めて表示するプログラムを作成せよ。
-1が入力された場合は入力の終わりと判定する。
ただし、最大値と最小値はmain関数以外の一つの関数の中で求める。
また、入力された数値を記憶する配列の要素数は10とし、
それ以上入力された場合はエラーが起きても仕方ないこととする。
ヒント:配列の中に -1 があればデータの終わりだと判断できる。
ヒント:最小値を探すには、最大値を記憶した変数との比較を繰り返せば良い。
#include <stdio.h>
void maxmin(int array[], int *max, int *min);
int main(void)
{
int i = 0, array[10], max, min;
do
{
printf("%d 番目の数:", i + 1);
scanf("%d", &array[i]);
i++;
}while (array[i - 1] != -1);
maxmin(array, &max, &min);
printf("最大値 %d : 最小値 %d\n", max, min);
return 0;
}
void maxmin(int array[], int *max, int *min)
{
int i = 0;
*max = 0;
*min = 100;
while (array[i] != -1)
{
if (array[i] > *max) *max = array[i];
if (array[i] < *min) *min = array[i];
i++;
}
}
構造体
複数の異なる型をまとめて作られた新しい型
型を宣言
struct **student(構造体タグ名)**
{
int year; /* 学年 */
int clas; /* クラス */
int number; /* 出席番号 */
char name[64]; /* 名前 */
double stature; /* 身長 */
double weight; /* 体重 */
};
構造体タグ名:構造体それ自体の名前
構造体の型の変数を宣言
struct student data;
student:構造体タグ
data:構造体変数
関数よりも先に宣言し、後に登場するすべての関数でこの構造体が使えるようにする。
- 1つの要素にアクセス
構造体変数名.要素名
全要素を一括して代入できる
struct student data1, data2;
data2 = data1;
配列は1つ1つ代入しなければならないが、structはまとめて代入できる。
typedef
新しい型(構造体型)を宣言する。
typedef 型の形 新しい型名
↓一番簡略化した宣言
typedef struct
{
int year; /* 学年 */
int clas; /* クラス */
} student;
-
構造体の引数
typedefで宣言:struct不要
受け取る側の関数にすべての値がコピーされる→中身を変更しても、元の構造体変数には影響しない。 -
構造体のポインタ
&演算子で求められるアドレスは、構造体で初めに宣言する要素のアドレス。
表記方法
(*pdata).year = 10;
pdata->year = 10; /* おすすめ */
- 構造体をポインタ変数として渡す
関数内で値を変更できる。
中身をコピーせずアドレスを渡すだけため、呼び出しが高速化する。
typedef struct {} /*プロトタイプ宣言の前に構造体作成*/
void student_print(student *data);
int main(void)
{
student_print(&data); /* アドレスで呼び出す */
}
void student_print(student *data)
{
printf("[学年]:%d\n", data->year); /* ->記号でアクセス */
}
- 構造体の配列
通常の配列と同じ使い方。
下記3つは同じ意味
(*data).year
data->year
data [0] .year
↓指定された数だけstudent型の中身を表示
void student_print(student data[], int count)
{
int i;
for (i = 0; i < count; i++)
{
printf("[学年]:%d\n", data[i].year);
printf("[クラス]:%d\n", data[i].clas);
printf("[出席番号]:%d\n", data[i].number);
}
}
↓3人分の、名前、年齢、性別、を入力して表示するプログラム
#include <stdio.h>
#include <string.h>
typedef struct
{
char name [64];
int age;
int sex;
} People;
void InputPeople(People *data); /*情報入力用関数*/
void ShowPeople(People data); /*情報表示用関数*/
int main(void)
{
People data[3];
int i;
for (i = 0; i < 3; i++) /*data[1]が1人目、data[2]が2人目、data[3]が3人目*/
{
InputPeople(&data[i]);
}
for (i = 0; i < 3; i++)
{
ShowPeople(data[i]);
}
return 0;
}
void InputPeople(People *data)
{
printf("名前:");
scanf("%s", data->name);
printf("年齢:");
scanf("%d", &data->age);
printf("性別(1-男性、2-女性):");
scanf("%d", &data->sex);
printf("\n");
}
void ShowPeople(People data)
{
char sex[16]; /*単なる文字型配列*/
printf("名前:%s\n", data.name);
printf("年齢:%d\n", data.age);
if (data.sex == 1)
{
strcpy(sex, "男性");
}
else
{
strcpy(sex, "女性");
}
printf("性別:%s\n", sex); /*if文で文字型sexに代入した性別の文字列を表示するため、「%s」*/
printf("\n");
}
&dataかdataかは
呼び出し元関数で値を使用するかしないか
(データはアドレスに保存したいか、呼び出し先関数での使用用か。)
##ファイルへの出力
- 手順
FILE型のポインタ作成
ファイルを開く:fopen関数
ファイルに読み書き
ファイルを閉じる:fclose関数
fopenでファイルを開くと、他ソフトで編集されないようにロックがかかる。
fcloseはそのロックを解除し、メモリ上に浮かせることが出来る。
fopen
関数を実行すると、FILE型へのポインタ(ファイルポインタ)が返される。
ポインタは以降ファイルの識別子として使用。(ポインタの用途では使わない。)
FILE型のポインタ変数 = fopen(ファイル名, モード);
-
モード
r:読み込み
r+:読み書き
w:書き込み+(空ファイル作成)
w+:読み書き+(空ファイル作成)
a:追加書き込み+(ファイル作成)
a+:追加書き込み+(ファイル作成) -
バイナリファイル
FILE型のポインタ変数 = fopen("ファイル名", "モードb");
fclose
fclose(FILE型のポインタ変数);
↓ファイルを開閉する
#include <stdio.h>
int main(void)
{
FILE *file;
file = fopen("test.txt", "w");
fclose(file);
return 0;
}
ファイルへの書き込み
fprintf(ファイルポインタ, %d, &year);
ファイルに直接書き込まれる。
↓変数の値をファイルに書き込む
#include <stdio.h>
int main(void)
{
int i = 100;
FILE *file;
file = fopen("test.txt", "w");
fprintf(file, "%d", i);
fclose(file);
return 0;
}
↓結果
100
ファイルからの読み込み
fscanf(ファイルポインタ, "%d", &i); /&iにデータが読み込まれる。/
関数を実行するとファイルテキストの先頭から読み込まれる。
指定%
%d:数字以外のテキストは無視される。
%s:テキスト全部読み取る。
- 複数変数の読み込み
CSVファイルを読み取れる。
fscanf(file, "%d,%d", &i, &j);
バイナリとテキスト
テキストも数値で記載されているため、テキストファイルも本質的にはバイナリファイル。
テキストファイルはテキストエディタで修正可能。
バイナリ:高速
テキスト:扱いやすい
ファイルへの書き込み
fwrite(書き込む変数アドレス, sizeof(), 項目数, ファイルポインタ);
書き込む数値を変数に代入しておき、書き込む変数アドレスで指定する。
fprintfとの違い
%d等ではなく、&buf等の変数で文字をそのまま書き込む。
また、バイナリファイルも書き込める。
↓数値100を書き込む
#include <stdio.h>
int main(void)
{
int buf = 100; /*書き込む数値を変数に代入*/
FILE *file;
file = fopen("test.dat", "wb");
fwrite(&but, sizeof(but), 1, file); /*書き込む変数アドレスで指定*/
fclose(file);
return 0;
}
バイナリファイルを見るにはバイナリエディタ(hgBed等)が必要。
リトルエンディアン:逆 //主流。先頭に数値が来るため読み取りやすい。
ビッグエンディアン:真
- 配列の書き込み
#include <stdio.h>
int main(void)
{
int buf[] = {10, 100, 1000, 1000};
FILE *file;
file = fopen("test.dat", "wb");
fwrite(buf, sizeof(buf), 1, file);
fclose(file);
return 0;
}s
ファイルからの読み込み
fread関数
fread(読み込む変数のポインタ, 1項目のサイズ, 項目数, ファイルポインタ);
#include <stdio.h>
int main(void)
{
int buf;
FILE *file;
file = fopen("test.dat", "rb");
fread(&buf, sizeof(buf), 1, file); /*ファイル内容を&bufに保存*/
fclose(file);
printf("%d\n", buf);
return 0;
}
ドラッグ
コマンドライン引数
アプリケーション起動時に渡される文字列
int main(int argc, char* argv[]);
(コマンドラインの数, 文字配列のポインタ変数)
argv[0]:アプリケーション自体のファイル名
↓コマンドライン引数の表示
printf("%s", argv[0]);
↓ドラッグされたファイル名を表示
#include <stdio.h>
int main(int argc, char* argv[])
{
if (argc > 1) {
printf("%s\n", argv[1]);
}
fflush(stdin); /* 画面の表示
getchar(); を止める*/
return 0;
}
↓オプションの有無を判断
#include <stdio.h>
int main(int argc, char* argv[])
{
while (argc > 0) {
argc--;
if (argv[argc][0] == '-') {
if (argv[argc][1] == 'a')
printf("-a オプション\n");
if (argv[argc][1] == 's')
printf("-s オプション\n");
}
}
return 0;
}
不変の値(定数)
変数と違い変わらない値。
数値や文字列に名前を付ける
#define
#define 名前 数値
↓本体価格から税込み価格に変更する
#include <stdio.h>
#define EXCISETAX 0.03 /*消費税*/
int main(void)
{
int price;
printf("本体価格:");
scanf("%d", &price);
price = (int)(1 + EXCISETAX) * price);
printf("税込価格;%d\n",price);
return 0;
}
const定数
変数を定数で定義する→初期値を固定する
-
#defineとの違い
特定の関数の中だけで使用する定数を宣言したい時 -
使い道
関数の引数の型(配列の1つの要素)として使い、値を変更させないようにする。
enum定数
名前の宣言のみで数値が自動で割り振られる。
(指定することもできる)
整数値のみ。
- 数値の割り振られ方
先頭が0で、以降は1づつ増加。
ある地点で数値を指定すると、以降1づつ増加。
enum {
STATE_NORMAL, /*0が振られる */
STATE_POISON, /*1が振られる*/
STATE_NUMBLY, /*2が振られる*/
STATE_CURSE /*3が振られる*/
};
↓数字割り振り
enum {
ENUM_0,
ENUM_1,
ENUM_5 = 5,
ENUM_6,
ENUM_7,
ENUM_9 = 9,
};
↓結果
0,1,5,6,7,9
#defineの発展
defineで関数を定義すると、定数名で関数処理が行える。
強力な機能だが、使いすぎるとプログラムが解読不能になる。
#include <stdio.h>
#define PRINT_TEMP printf("temp = %d\\n", temp)
int main(void)
{
int temp = 100;
PRINT_TEMP;
return 0;
}
マクロ
#difineを使って簡易的な関数を作成できる。
大きいプログラムはメモリが極端に大きくなるため良くない。
#include <stdio.h>
#define PRINTM(X) printf("%d\n", X)
int main(void)
{
int a1 = 100, a2 = 50;
PRINTM(a1);
PRINTM(a2);
return 0:
}
↓台形の面積を求める
#include <stdio.h>
#define GET_TRAPEZOID_AREA(A,B,H) (A + B) * H / 2
int main(void)
{
int up, down, h, s;
printf("上底、下底, 高さ:");
scanf("%d,%d,d", &up, &down, &h);
s = GET_TRAPEZOID_AREA(&up, &down, &h);
printf("面積:%d\n",s);
return 0:
}
#include <stdio.h>
int olympic(int year);
enum
{
OLYMPIC_NON,
OLYMPIC_SUM,
OLYMPIC_WIN,
};
int main(void)
{
int year, hold;
scanf("%d", &year);
hold = olympic(year);
switch (hold) {
case OLYMPIC_SUM:
printf("かきおりん\n");
break;
case OLYMPIC_WIN:
printf("とうきおりん\n");
break;
case OLYMPIC_NON:
printf("ないよ!\n");
break;
};
return 0;
}
int olympic(int year)
{
if(year % 2 == 0)
{ if(year % 4 == 0)
{ return OLYMPIC_SUM;
}else { return OLYMPIC_WIN;
} else { return OLYMPIC_NON; }
}
動的配列
C言語は通常、配列の要素数を変更できない
→malloc関数
malloc関数
ポインタ変数 = malloc(必要メモリサイズ);
- ポインタ変数:配列の先頭アドレス
→[]を使用して配列として扱う - メモリサイズ(ヒープ):バイト単位のため、sizeofを使用
(ヒープに確保された配列:動的配列)
ヒープ:長期使用される大きいサイズのメモリを格納する領域
動的配列:プログラム実行中に用意された任意のサイズの配列
→プログラム終了までの寿命
→free関数で解放する
free(ポインタ変数)
↓{1...10}の動的配列を作成
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
int* heap;
/* sizeofで変数1つ分のサイズを求める * 10 */
heap = (int*)malloc(sizeof(int) * 10);
/* exit:プログラム終了関数 */
if (heap == NULL) exit(0);
for (i = 0; i < 10; i++) {
heap[i] = i;
}
printf("%d¥n", heap[5]);
/* 解放 */
free(heap);
return 0;
}
mallocはメモリにマークを付けているだけで扱いが難しいため、あまり使わない
realloc関数
中身を維持したまま、新しいサイズのメモリを確保
mallocのポインタ変数 = realloc(前のポインタ変数, 必要メモリ数)
↓動的配列要素数を変更(10個から100個)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* heap;
heap = (int*)malloc(sozeof(int) * 10);
heap = (int*)realloc(heap,sizeof(int) * 100);
free(heap);
return 0;
}
reallocしすぎるとフラグメンテーションが起きるため、
大きめに確保しておく等工夫する。
名前、年齢、性別、を入力して表示するプログラム
(何人分でも入力できる, 年齢に-1が入力されれば入力終了)
↓
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
char name [256];
int age;
int sex;
} People;
void InputPeople(People* data); /*情報入力用関数*/
void ShowPeople(People data); /*情報表示用関数*/
int main(void)
{
int i, count, datasize;
people* data;
datasize = 10;
data = (people*)malloc(sizeof(people) * datasize);
count = 0;
while (1) {
InputPeople(&data[count]);
if (data[count].age == -1 )
break;
count++;
if (count >= datasize) {
datasize +- 10;
data = (People*)realloc(data, sizeof(people) * datasize)
}
}
for (i = 0; i < count; i++ ) {
ShowPeople(data[i]);
}
free(data);
return 0;
}
void InputPeople(People *data)
{
printf("名前:");
scanf("%s", data->name);
printf("年齢:");
scanf("%d", &data->age);
printf("性別(1-男性、2-女性):");
scanf("%d", &data->sex);
printf("\n");
}
void ShowPeople(People data)
{
char sex[16]; /*単なる文字型配列*/
printf("名前:%s\n", data.name);
printf("年齢:%d\n", data.age);
if (data.sex == 1)
{
strcpy(sex, "男性");
}
else
{
strcpy(sex, "女性");
}
printf("性別:%s\n", sex); /*if文で文字型sexに代入した性別の文字列を表示するため、「%s」*/
printf("\n");
}
ファイル分割
- ヘッダーファイル(.h)
関数や変数の宣言のみが書かれたファイル。 - ソースファイル(.c)
プログラムが書かれたファイル。
ex.)sum
↓sum.c
int main(int min, int max)
{
int num;
num = ( min + max ) * ( max - min + 1 ) / 2;
return num;
}
↓sum.h
int sum(int min, int max);
自作ヘッダーファイルを""でインクルードする
↓main.cでheaderファイルを宣言する
#include "sum.h"
#include <stdio.h>
int main(void)
{
int value;
value = sum(50, 100);
printf("%d¥n",value);
return 0;
ファイル分割により、多人数開発も実現可能
(Gitがあるけどね)
変数の分割
宣言:コンパイラが名前と形を記憶する
定義:作成する
同じ関数を何度も定義をすると、区別が付かずエラーになる
(プロトタイプ宣言は宣言のみのため、何度行っても良い)
extern宣言
宣言のみを複数回行い、定義は1回のみ行う
→複数ファイルが宣言のみ行う
→複数ファイルで変数を共有できる
↓sum.h
extern int sum (int min, int max);
extern int Public;
/* sum.hをインクルードする全てのファイルでPublicを使用できる */
↓sum.cで実体を定義する
int Public; /* 実体の定義 */
int main(int min, int max)
{
int num;
num = ( min + max ) * ( max - min + 1 ) / 2;
Public = 100;
return num;
}
↓main.c
#include "sum.h"
#include <stdio.h>
int main(void)
{
int value;
value = sum(50, 100);
printf("%d\n", Public);
return 0;
}
↓実行結果 変数Publicを複数ファイルで使用できている。
100
本来変数は独立しているため、使用はし過ぎない。
#ifndef~#endif
ヘッダーファイルの重複を防ぐ
↓sum.h
#ifndef _INCLUDE_SUM_
#define _INCLUDE_SUM_
int sum(int min, int max);
#endif
#ifndefで以前に INCLUDE_SUM が定義されているか調べ、定義されていない場合のみコンパイルする。
sum.hの一回目の呼び出し時は、
#ifndefの後に#defineが呼ばれているため、コンパイルされる。
二回目の呼び出し時には、既に INCLUDE_SUM が定義されているため、コンパイルされない。
↓sum.hにexternを加えた一般的なヘッダーファイル
#ifndef _INCLUDE_SUM_
#define _INCLUDE_SUM_
/* min~max間の合計値を計算する関数
int min 最小値
int max 最大値
戻り値 int 合計値
*/
extern int sum(int min, int max);
#endif
ヘッダーファイルはプログラムの設計書。
いろんな人が読むため、コメントを記載する。
ソースファイルには余分なことは書かない。
計算
abs関数
絶対値を計算する
abs(数値)
累乗
pow関数
累乗 = pow(数値, 指数)
#include <math.h>
#include <stdio.h>
void main(void)
{ /* 累乗のため%f */
printf("%dの%d乗 = %f¥n", 5, 2, pow(5, 2));
return;
}
↓結果
5の2乗 = 25.000000
√
sqrt関数
(平方根:2乗するとその数になる数値)
平方根 = sqrt(数値)
#include <math.h>
#include <stdio.h>
void main(void)
{
printf("√%d = %f : %f * %f = %f¥n", 100, sqrt(100), sqrt(100), sqrt(100), sqrt(100) * sqrt(100));
三角関数
- 通常
角度→辺の長さ
sin
cos
tan
- アーク(三角関数の逆計算)
辺の長さ→角度
asin
acos
atan
ex.)tan
tan = tan(ラジアン角度);
↓普通の角度→ラジアン角度
ラジアン = (度 * 3.14159 / 180)
↓マクロver.
#define RADIAN(ARC) ((ARC)*3.14159 / 180)
↓身長160cmの人が5m離れた位置から木を見上げたときに、
その角度が40度だった場合の木の高さを求める例
#include <math.h>
#include <stdio.h>
#define RADIAN(ARC) ((ARC)*3.14159 / 180)
void main(void)
{
double stature = 160;
double distance = 500;
double arc = 40;
double tree;
tree = distance * tan(RADIAN(arc)) + stature;
printf("%fm¥n", tree / 100);
return;
}
↓結果
5.795493m
乱数
ランダムな数
疑似乱数:計算で乱数を作る
- 線形合同法
X = 適当な数 * X
を繰り返し、毎回違う数を得る
rand関数
変数 = rand();
↓乱数を10個作る
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", rand());
}
return 0;
}
- 最大値を決める
RNAD_MAX
範囲乱数公式(意味は分からなくて良い)
↓最小値から最大値の範囲の乱数を計算する
最小値 + (int)( rand() * (最大値 - 最小値 + 1.0) / (1.0 + RAND_MAX) )
1~6の乱数を10個作る
#include <stdio.h>
#include <stdlib.h>
int GetRandom(int min, int max);
int main(void)
{
int i;
for (i = 0; i < 10; i++) {
/* (min, max) */
printf("%d¥n", GetRamdom)(1,6));
}
return 0;
}
/* 公式 */
int GetRamdom(int min, imt max)
{
return min (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}
- stand関数
同じ値で何度も疑似乱数を作ると、同じ値が何度も生まれる
→使用する元の数を変える
stand(元の数)
現在時刻を使えば良い→time関数
↓毎回違う乱数を作成(意味は分からなくて良い)
/* 符号なしの整数値 */
stand((unsigned int)time(NULL));
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int GetRandom(int min, int max);
int main(void)
{
int i;
for(i = 0; i < 10; i++) {
printf("%d¥n", GetRandom(1,6));
}
return 0;
}
int GetRandom(int min, int max9
{
static int flag;
/* flagにより始めの一回のみstandを使用 */
if(flag == 0) {
stand((unsigned int)time(NULL));
flag = 1;
}
return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}
入力
- gets関数
scanfで入力ミスを知る方法
gets(文字配列);
- puts関数
画面に1行の文字列を表示する
必ず改行される
puts(文字列);
#include <stdio.h>
int main(void)
{
char str[32]
gets(str);
puts(str);
return 0;
}
getは文字配列の先頭アドレスしか分からないため、バッファオーバーランを起こす↓
- fgets関数
fgets(文字列, 配列の要素数, ファイルポインタ);
ファイルから文字列を読み込む
全ての周辺機器はstdinというファイル
→キーボードにもstdinのファイルポインタが有る
→stdinの指定で、ファイルから読み込んだ関数がキーボード用になる
fgets(文字列, sizeof(文字列), studin);
長い文字列を入力しても、配列宣言の要素数で打ち切る
- 数値にする
atoi関数
↓入力数字を2倍にする
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[32];
int val;
fgets(str sizeof(str), stdin);
val = atoi(str);
printf("%d¥n", val * val);
return 0;
}
↓
5 /* 入力 */
25
-
実数
atof関数 -
strtok関数
文字の切り出し
/* 初めの単語を取り出す */
単語のアドレス値 = strtok(文字配列, 区切り文字);
/* 次の単語を取り出す */
単語のアドレス値 = strtok(NULL, 区切り文字);
暗号
暗号化と複合化
- シーザー
文字をアルファベット順に数文字ずらす