プログラムの実行を中止する
任意の場所でプログラムの実行を中止させる場合は、exit()関数を使います。
stdlib.hが必要です。
#include <"stdio.h">
#include <"strlib.h">
int main(void)
{
int i;
if (i) {
exit(); //ここで突然終わります。
}
}
任意の場所でプログラムの実行を中止させる場合は、exit()関数を使います。
stdlib.hが必要です。
#include <"stdio.h">
#include <"strlib.h">
int main(void)
{
int i;
if (i) {
exit(); //ここで突然終わります。
}
}
配列内の数値や文字列を並びかえるにはqsort()関数を使います。
参考例を実行してみましたが、動作せず・・・。
#include <stdio.h>
#include <stdlib.h>
int compare(const void *a, const void*b)
{
if (*a > *b) {
return 1;
} else if(*a < *b) {
return -1;
} else if(*a == *b) {
return 0;
}
}
int main(void)
{
int num[] = {4, 6, 5};
qsort(num, sizeof(int), compare);
printf("%d\n", num);
return 0;
}
エラーメッセージは次のように出ました。
関数呼び出しに指定されているパラメータ数が少ないです。 呼び出している関数の引数の数を再確認してください。
「qsort(num, sizeof(int), compare);」この行でエラーが出ていることがわかっていますので、C言語ライブラリのマニュアルを検索してみると、次のように書かれています。
#include <stdlib.h>
void qsort( void * data , size_t data_cnt
, size_t data_size
, int( * func )( const void * , const void * );
■戻り値:
なし
指数や平方根などの数学レベルでの計算が必要な場合に使います。
実際に使う場合は、math.hで定義された数学用の関数を読み込みます。
■乱数を作る
ランダムな文字列を作成するには、stdlib.hを読み込み、rand()関数を使います。
実際に書いてみます。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int n;
srand(time(NULL));
n = rand();
printf("%d\n", n);
return 0;
}
結果は、実行するたびにランダムな数字が表示されます。
日時を取得する為には、time()関数とlocaltime()関数を組み合わせて使います。
これらの関数の定義はtime.hの中で定義されています。
■現在時刻を得る
time_t ct; //time_t型という型になります ct = time(NULL); struct tm *now; now = localtime(&ct); //「&ct」はtime関数で得た値が入った変数のアドレスを指定します。
time.hを利用したtm構造体のメンバはtm_secやtm_min等があります。
その他のメンバや、使い方等はC言語の標準ライブラリについて調べる必要があります。
シフト演算子とは、ビット列を左右に指定した分だけずらす動きをします。
シフト演算子は2種類あり、「右シフト演算子」と「左シフト演算子」があります。
■右シフト演算子(ビット列を右に2ビットシフトする)
↓最上位ビット ↓最下位ビット
元のビット列 1 0 1 1 0 0 1 0
~~~ここが
シフトしたビット列 1 1 0 0 1 0 1 0
~~~ここにシフトされる
■左シフト演算子(ビット列を左に2ビットシフトする)
↓最上位ビット ↓最下位ビット
元のビット列 1 0 1 1 0 0 1 0
~~~ここが
シフトしたビット列 1 0 1 0 1 1 0 0
~~~ここにシフトされる
データをビット単位で、比較、操作する際に使うのがビット演算子です。
■論理積(and) &
各ビットを比べて、「両方とも1なら1、そうでなければ0」を返す演算
参考例
a = 170, b = 245
変数名 10進数 2進数 a 170 1 0 1 0 1 0 1 0 b 245 1 1 1 1 0 1 0 1 a&b 160 1 0 1 0 0 0 0 0
■論理和(or) |
「片方が1なら1、そうでなければ0」を返す演算(これはどちらも1の場合は1となる)
変数名 10進数 2進数 a 170 1 0 1 0 1 0 1 0 b 245 1 1 1 1 0 1 0 1 a|b 160 1 1 1 1 1 1 1 1
■排他的論理和(xor) ˆ
「片方が1で、もう片方が0なら1、そうでなければ0」を返す演算
変数名 10進数 2進数 a 170 1 0 1 0 1 0 1 0 b 245 1 1 1 1 0 1 0 1 aˆb 160 1 1 1 1 1 1 1 1
■1の補数表現(not) ~
「各ビットを反転させたもの」
変数名 10進数 2進数 a 170 1 0 1 0 1 0 1 0 a~ 160 0 1 0 1 0 1 0 1
列挙型とは、整数値に特定の名前を与えることです。
列挙型を使うと、int型の整数値に名前をつけることができます。
列挙型の宣言はenumという記述ではじめます。
サンプルを書いて動かしてみます
#include <stdio.h>
int main(void)
{
//列挙型の宣言
enum _mon{
Jan,
Feb,
Mar,
Apr,
Jun
} mon;
printf("%d\n", mon); //結果「4198543」と表示される
//明示的な値を代入する場合
//列挙型の宣言
enum _mon{
Jan = 1,
Feb,
Mar = 5, //ここはわざと5にする
Apr,
Jun
} mon;
//↑の例では、1,2,5,6,7,という値が自動的に割り振られることになる
//列挙型の値を使う時の例。列挙指定子のいずれかの名前を使った代入や参照ができる
mon = Feb;
printf("%d\n", mon); //結果「2」と表示される
return 0;
}
共用体とは、1つのアドレスに異なるデータを割りあてることです。
共用体は、1つのメモリ領域で異なる型の変数のどれかひとつを選んで使うことができます。
共用体の宣言は以下のようになります。
//「uniondata」は共用体名、「unilist1」は共用体変数
union uniondata{
int no;
char name[10];
float weight;
};
union uniondata unilist1;
共用体を使ったサンプルを試してみます。
#include <stdio.h>
int main(void)
{
//「uniondata」は共用体名、「unilist1」は共用体変数
union uniondata{
int no;
char name[10];
float weight;
};
union uniondata unilist1;
unilist1.no = 1;
printf("%d\n", unilist1.no);
strcpy(unilist1.name, "test!!");
printf("%s\n", unilist1.name);
unilist1.weight = 123.4;
printf("%f\n", unilist1.weight);
return 0;
}
unilist1がメモリに占める領域は一番大きな型にあわせた大きさになる。
共用体はまだまだ深いので、改めて追記して理解を深めることにします。
出力結果は以下のなります。
1 test!! 123.400002
#defineを使うと、引数をもち、関数のように動作するマクロを定義することができます。
例
#define HIKU(x, y) ((x) - (y))
上記の書き方は有効範囲は1行です。
複数行の場合は次のように定義します。
#define hiku2(a, b, c) ((a) * (b) * (c) )\ + a+b+c)
実際に動かしてみます
#include <stdio.h>
#define HIKU(x, y) ((x) - (y))
int main(void)
{
int i;
printf("マクロ!%d\n", HIKU(10, 8));
return 0;
}
//結果は「マクロ!2」と表示されます
引数つきマクロは、実行する処理全体とその中の引数はカッコでくくります。
//正しい例 #define HIKU(x, y) ((x) - (y)) //間違った例 #define HIKU(x, y) (x - y)
マクロ名と()の間にスペースを入れてしまうと、区切りが正しく認識されません。
//正しい例 #define HIKU(x, y) ((x) - (y)) //間違った例 #define HIKU (x, y) ((x) - (y))
条件に応じて、必要な部分だけ抜き出してコンパイルしたい場合、次のように書きます。
#if 条件
指定範囲
#endif
#ifdef 識別子
指定範囲
#endif
#ifndef 識別子
指定範囲
#endif
また、複数の条件を判断することもできます。
#ifdef 識別子
範囲指定A //指定範囲Aをコンパイルする
#elsif 条件B
範囲指定B //指定範囲Bをコンパイルする
#else
範囲指定C //指定範囲Cをコンパイルする
#endif
複数のプログラムファイル内でヘッダファイルを使うと、同じヘッダファイルを重複インクルードしてしまう場合があります。
これを防ぐためには次のように書きます。
#ifndef _MYHEADER_
#define _MYHEADER_
//2回目以降は既に呼ばれているので、この内容は読み込まれない
void MyFunc();
extern int x;
#endif
#からはじまる1行分のことをマクロと呼びます。
マクロはプログラムのソースコードをコンパイルする前にプリプロセッサが処理します。
プログラムの最初に記述する「#include」もマクロの一種です。
マクロは次のような書式で書きます。
//マクロの中にはスペースやタブは入れてはダメ #define マクロ名 パターン
■置換
#defineは、文字列を置換するマクロです。
例
#define VALNUM 3;
次のように書くと、DEBUG_MODEが定義されているということを表します。
#define DEBUG_MODE;
簡単なサンプルを書いてみましたが、うまく動作せず。
#include <stdio.h>
#define VALNUM 3;
int main(void)
{
printf("%d \n", VALNUM);
return 0;
}
//実行結果
「7行目」で記述エラーを発見しました。
「,」を付け忘れています。
もうひとつ、サンプルを書きましたが、これもうまく動作しませんでした。原因について調査中、、、
#include <stdio.h>
#define VALNUM 3;
int main(void)
{
int i;
for (i=0; i<VALNUM; i++) {
printf("%d \n", i);
}
return 0;
}
//実行結果
「9行目」で記述エラーを発見しました。
「identifier」を付け忘れています。
エラーの原因から地道に紐解いていこうかと思います。
解決したら、ブログに追記します。
2014.09.25追記
コメントからご指摘いただいた点を元に、下記のようなプログラムを作って動かしてみました。
#include <stdio.h>
#define VALNUM 3
int main(void)
{
int i;
for (i=0; i<VALNUM; i++) {
printf("%d \n", i);
}
return 0;
}
エラーが出ずに実行され、コンソール上には「0 1 2」という表示が出ました。
ケアレスミスでした。
グローバル変数をstaticをつけて宣言すると、変数の有効範囲はその宣言をしたファイル内に限られます。
外部変数を宣言して、他のファイルから参照することはできません。
例として次のような場合
//ソースファイル1で記述 static int gvar; //ソースファイル2で記述 extern int gvar; //ソースファイル3で記述 extern int gvar;
上記のようにソースファイル2やソースファイル3からgvarを呼び出そうとしてもエラーになります。
■static宣言したローカル変数
static変数はプログラムの開始から終了まで、値が削除されません。
ローカル変数は関数の中で宣言され、関数の処理が終わると同時に破棄されます。
■const宣言
const宣言は変数の値を書き換えられないようにする宣言です。
定数として変数を使用する場合は、この宣言を使います。
const宣言を関数の引数で指定している場合、その引数は関数の中で値が保持されることを保障する動きになります。
ソースファイルの分割
ひとつのソースファイルに大量のプログラムコードを書くと良くないので、分割をしてソースファイルを管理します。
通常、プログラムを構成する「機能」ごとにソースファイルを分割します。
ソースファイルを分割すると、どのプログラムファイルからも参照できる変数が必要になってきます。
その変数を「外部変数」とよび、変数を宣言することを「外部変数宣言」といいます。
ソースファイルの分割例
本体ファイル①
int global_val;
分割ファイル①
extern int global_val;
分割ファイル②
extern int global_val;
上記のように記述します。
■ヘッダファイルを利用する
ヘッダファイルを利用することにより、さらに効率的にプロジェクトを管理できるようになります。
実行ファイルを作るには、C言語のソースを書いて、「コンパイル」、「リンク」することで完成します。
コンパイルとリンクを合わせて「ビルド」または「メイク」と呼びます。
拡張子が「h」のファイルのことをヘッダファイルと呼びます。
ヘッダファイルは、プロトタイプ宣言、構造体や定数の定義などのテキストファイルを指し示す。
ソースファイルの中に組み込むと、それらの宣言や定義を利用できるようになります。
#include <stdio.h>
インクルードの書き方は、C言語に標準搭載されているヘッダファイルは次のように書きます。
#include <stdio.h>
また、自分で作ったヘッダファイルは次のように書きます。
#include "xxxxxxx.h"
C言語が用意しているヘッダファイルは次のようなものがあります。
ヘッダファイル 処理の種類 stdio.h 入出力 string.h 文字列処理 time.h 時間処理 math.h 数学処理
自分でヘッダファイルを作るには、次のように書きます。
//呼び出し元の拡張子cのファイル
#include "test.h"
main ()
{
}
void testfunc()
{
}
自作ヘッダファイル側
void testfunc();
長い型名を簡潔に名前をつけなおすことができます。(型の再定義)
//「unsigned char」を「u_char」という名前に定義している typedef unsigned char u_char; u_char c;
ポインタ型の再定義もあります。
//ポインタ型の場合 typedef unsigned int * pt_int; pt_int a;
構造体名を再定義する方法
typedef struct data {
int no;
char name;
int age;
} DATA;
DATA list1;
//↑の例は下記のように記述したものと同じになる
typedef struct data {
int no;
char name;
int age;
};
struct data list1;
構造体変数を配列として考えることを構造体配列と呼びます。
#include <stdio.h>
int main(void)
{
//構造体配列の宣言(構造体テンプレートと、構造体配列を別々に書く場合)
struct data{
int no;
char name[10];
int age;
};
struct data list1[10];
//構造体配列の宣言(構造体テンプレートと、構造体配列を同時に書く場合)
struct data2{
int no;
char name[10];
int age;
} list1[10];
//構造体配列を初期化する場合(各々の配列内に値をセットしている)
struct data2 list1[10] = {
{1, "testA", 10},
{2, "testB", 20},
{3, "testC", 32},
{4, "testD", 41}
};
//↑の例では、list1[10]と定義しているので、10個分の構造体配列を持たせることができる
return 0;
}
上記プログラムを実行しただけでは、画面上にはなにもおこりません。
そこで、構造体配列の中身を参照する記述を書いてみます。
#include <stdio.h>
int main(void)
{
//構造体配列の宣言(構造体テンプレートと、構造体配列を別々に書く場合)
struct data{
int no;
char name[10];
int age;
};
struct data list1[10]; //構造体配列の宣言
//構造体配列の宣言(構造体テンプレートと、構造体配列を同時に書く場合)
struct data2{
int no;
char name[10];
int age;
} list1[10]; //構造体配列の宣言(同時に)
//構造体配列を初期化する場合(各々の配列内に値をセットしている)
struct data2 list1[10] = {
{1, "testA", 10},
{2, "testB", 20},
{3, "testC", 32},
{4, "testD", 41}
};
//↑の例では、list1[10]と定義しているので、10個分の構造体配列を持たせることができる
//構造体配列の中身を参照する記述
//方法1
int i;
for (i = 0; i <10; i++ ) {
printf("%d %s %d \n", list1[i].no, list1[i].name, list1[i].age);
}
printf("-----------\n");
//方法2
int j;
struct data2 *sp = list1;
for (j = 0; j < 10; j++ ) {
printf("%d %s %d \n", list1[*(sp+j)].no, list1[*(sp+j)].name, list1[*(sp+j)].age);
}
printf("-----------\n");
//方法3
struct data2 *sp;
for (sp = list1; sp != list1 + 10; sp++ ) {
printf("%d %s %d \n", sp->no, sp->name, sp->age);
}
return 0;
}
上記を実行した結果、方法2の結果がなぜか下記のようになる。
1 testA 10 2 testB 20 3 testC 32 4 testD 41 0 0 0 0 0 0 0 0 0 0 0 0 ----------- 2 testB 20 //出力の開始がおかしい(2つめからになっている) 3 testC 32 4 testD 41 0 0 //←ここの値がおかしい 1 testA 10 1 testA 10 1 testA 10 1 testA 10 1 testA 10 1 testA 10 ----------- 1 testA 10 2 testB 20 3 testC 32 4 testD 41 0 0 0 0 0 0 0 0 0 0 0 0
なぜなのか、、調査中
構造体の使用例について、実際に書いてみます。
#include <stdio.h>
int main(void)
{
//構造体のテンプレート宣言
struct data{
int no;
char name[10];
int age;
};
//初期化(「1」や「test」というひとつひとつの要素はメンバ)
struct data list1 = {1, "test !!!", 39};
//メンバが多い場合は折り返して書く
struct data list1 = {
1,
"test !!!",
39
};
//構造体メンバへのアクセス
printf("%d %s %d\n", list1.no, list1.name, list1.age);
//構造体変数へ値を代入する
list1.no = 3;
strcpy(list1.name, "cotytext!");
list1.age = 40;
return 0;
}
構造体に対するポインタもあります。この場合は、構造体をポインタで指し示すことになります。
#include <stdio.h>
int main(void)
{
//構造体のテンプレート宣言
struct data{
int no;
char name[10];
int age;
} list1;
//構造体のポインタを宣言
struct data *sp;
//ポインタへのアドレスの代入方法
//struct data list1;
sp = &list1;
//ポインタを使った構造体の参照方法(phpのクラスのメンバ変数へのアクセスに似ている)
printf("%d %c %d\n", sp->no, sp->name, sp->age);
return 0;
}
構造体配列について
構造体について
構造体とは、異なる型の変数をひとまとめにして、取り扱える形にしたもの。
どのような型の変数をまとめるのかを指定することを、構造体のテンプレートと呼びます。
テンプレートを定義しただけではデータを入れることができないので、構造体の型を持つ変数を用意します。この変数のことを構造体変数と呼びます。
構造体を配列に格納すると、それは構造体配列と呼ばれるものになります。これはまだ良くわかっていないので後ほど勉強します。
構造体は異なる型の変数をひとつにまとめ、まとめられた要素ひとつひとつをメンバと呼びます。
実際に書いてみます。
#include <stdio.h>
int main(void)
{
//構造体のテンプレート宣言
struct data{
int no;
char name[10];
int age;
};
//構造体変数の宣言(dataはテンプレート名、list1は構造体変数名)
struct data list1;
//構造体テンプレートと構造体変数を同時に宣言
struct data2{
int no;
char name[10];
int age;
} list2;
struct data2 list3; //宣言をした後から構造体変数を追加した場合
return 0;
}
キーボードからの入力を取り扱う方法を勉強します。
scanf()関数
キーボードから入力したデータを指定の書式に変換したり配列に格納したりします。
int a;
scanf("%d", &a); //&をつけてアドレスとしている
//文字列の場合
char s[30];
//配列の場合は、先頭要素のポインタとなるので、&はいらない
scanf("%s", s);
//複数のデータを一度に入力する場合
int a;
char s[30];
scanf("%d %s", &a, s);
gets()関数
キーボードから入力した1行分の文字列を文字列配列に格納する
char s[30]; gets(s); //1行分
getchar()関数
キーボードから入力された1文字だけ変数に格納する
//実行するとプログラムはキーボードからの入力待ちになる int c; c = getchar();
C言語はファイルポインタを通してディスク上のファイルを読み書きを行います。
また、ファイルポインタは必ずしもファイルを取り扱うのではなく、キーボードやディスプレイなどの入出力装置とのやりとりについても使用します。
■標準入出力ファイルの種類
基本的な入出力のため、stdin,stdout,stderrの3つのファイルポインタがあります。
これらはプログラム実行開始時に自動的に開いています。
stdinは標準入力と呼ばれ、キーボードなどからの入力を受け取る stdoutはディスプレイなどへの出力を行います stderrは基本的なエラー出力を(ディスプレイ等に)行います
ファイル用関数で標準入出力を指定するとキーボードやディスプレイからの入出力になります。
printf関数でディスプレイに出力する場合は、裏側でfprintf(stdout, “%d”, a);という命令が動き、ディスプレイに結果が出ています。
サンプル用プログラム
#include <stdio.h>
int main(void)
{
//入力された文字列を格納する変数を宣言しておく
char s[30];
//キーボードからの入力を変数sへ格納する
fgets(s, 29, stdin);
//変数sの値をそのままディスプレイ出力する
fputs(s, stdout);
//オマケ
fputs("error\n", stdout);
return 0;
}
バイナリファイルを書き込む場合は、ファイルのオープンモードを「wb」にします。
#include <stdio.h>
int main(void)
{
short buf[] = {
0x10, 0x20, 0x30
};
FILE *fp;
fp = fopen("d.data", "wb");
fwrite(buf, sizeof(short), 3, fp);
//ファイルを閉じる
fclose(fp);
return 0;
}
バイナリファイルを読み込む場合は、改行等の制御文字はなく、ひとつながりのデータとして読み込みます。
バイナリファイルを開くときはファイルモードに「b」(バイナリ)を追加します。
FILE *fp;
fp = fopen("file3.data", "rb");
オープンモードは以下のようになる
rb 読み込み専用 wb 書き込み専用 ab 追加書き込み用
バイナリファイルの読み込みは次のようになります。
#include <stdio.h>
int main(void)
{
//ファイルを開く
short buf[3];
FILE *fp;
fp = fopen("c.data", "rb");
//データを読み込む
//「3」は読み込み回数。fpが示す位置から2バイトのデータを3回読み込む
fread(buf, sizeof(short), 3, fp);
//ファイルを閉じる
fclose(fp);
return 0;
}
ファイルへの書き込みは以下のように書きます。
#include <stdio.h>
int main(void)
{
FILE *fp;
fp = fopen("b.txt", "w");
fputs("Hello \n", fp);
//指定形式での書き出し
int a = 5;
fprintf(fp, "%02d\n", a);
fclose(fp);
return 0;
}
別パターンを試してみる。
#include <stdio.h>
int main(void)
{
FILE *fp;
fp = fopen("b.txt", "w");
fputs("Hello \n", fp);
fputs("Hello1 \n", fp);
fputs("Hello2 \n", fp);
//指定形式での書き出し
int a = 5;
fprintf(fp, "%02d\n", a);
fclose(fp);
return 0;
}
上記の場合は
Hello Hello1 Hello2 05
という内容のb.txtファイルが新規作成されます。
fputsを繰り返し記述するのは良いのか悪いのか考えてみますが、動作上は特に問題なくファイルへの書き込みは完了しました。
書き込む内容を一旦、変数などにまとめて、最終的に一回のfputsで書き込んだほうがプログラムとしてはすっきり明確になります。
ファイルを読み込むプログラムを作成します。
ファイルの読み込みの判定を行わないと、a.txtがない場合に無限にループしてしまいます。
また、fgets関数は読み込んだファイル内容について、自動的に改行コードまでをひとつの文字列として認識します。
#include <stdio.h>
int main(void)
{
char s[10];
FILE *fp;
fp = fopen("a.txt", "r");
//ファイルオープンの判定
if (fp == NULL) {
printf("file null \n");
return;
}
//ファイルを最後まで読み込む
while(1) {
//「10」は読み込み最大文字数
fgets(s, 10, fp);
printf("%s \n", s);
if (feof(fp)) {
printf("break!! \n");
break;
}
}
//ファイルを閉じる
fclose(fp);
return 0;
}
ファイルの種類は大きく、テキストファイルとバイナリファイルがある。
テキストファイルは、主に文字データを取り扱う。
バイナリファイルは、主に音声や画像データを取り扱う。
ファイル処理の基本は、ファイルポインタを宣言して、ファイルの読み書きの場所を決定してから処理を行う。
FILE *fp; //ポインタとして宣言する
ファイルを扱うときは次の順序で行う。
①ファイルを開く
②読み書きを行う
③ファイルを閉じる
ファイルを開く場合は次のように書く。
FILE *fp;
fp = fopen("a.txt", "r");
三つめの引数はオープンモードで、C言語では次のようになります。
| モード | 動作 | ファイルがあるとき | ファイルがないとき |
|---|---|---|---|
| "r" | 読み出し専用 | 正常 | エラー(NULL返却) |
| "w" | 書き込み専用 | サイズを 0 にする(上書き) | 新規作成 |
| "a" | 追加書き込み専用 | 最後に追加する | 新規作成 |
| "r+" | 読み込みと書き込み | 正常 | エラー(NULL返却) |
| "w+" | 書き込みと読み込み | サイズを 0 にする(上書き) | 新規作成 |
| "a+" | 読み込みと追加書き込み | 最後に追加する | 新規作成 |
ファイルのオープンに成功すると、ファイルポインタを返す。
どのモードで開くかにより、前のファイルの扱いや、ファイルポインタが最初に示す位置は異なる。
オープンに失敗すると、fopen関数はNULLを返すので、NULLかどうかを判定して次の処理を決定すると良い。
ファイルを閉じるにはfclose()関数を使う。
fclose(fp);
ファイルにはテキストファイルとバイナリファイルという種類がある。
テキストファイルは人間が読めるもので、バイナリファイルは(一般的には)解読ができない。
C言語はファイル名を直接読み込むわけではなくファイルポインタと呼ばれるものでファイルを置き換えてアクセスする。
ファイルポインタはファイルのどの部分を読み書きするか。という情報も含んでいる。
■ファイルポインタを使ったファイルの読み書きの順番
以下のような順番でファイルを操作する
①ファイルポインタの宣言
②対象のファイルを開く
③ファイルポインタを得る
④ファイルポインタを通して読み書きする
⑤操作が終わり、ファイルを閉じる
テキストファイルとバイナリファイルでは、ファイル操作時の関数の種類や引数が違うので、注意が必要。
C言語では、キーボードから入力されたデータを扱うことがある。この時に入力される値を読み込む部分を「入力部分」と呼ぶ。
キーボードから入力される値と、ファイルから読み込む値も同様に考えられる。
キーボード入力やファイルから読み込むために使うファイルを標準入出力ファイルという。
プログラムの実行開始と同時に開かれていて、いつでも使うことができる。
main関数はプログラム開始点(エントリポイント)となる特殊な関数。
以下にmain関数の書き方の例を書く。
//引数と返り値を省略
main ()
{
}
//引数を省略、返り値はvoid
void main()
{
}
//引数を省略、返り値はint
int main()
{
return 0;
}
//引数と戻り値(int)を指定(これが基本パターンになる)
int main(int argc, char *argv[])
{
return 0;
}
コマンドライン引数の取得
コマンドラインから引数をつけてプログラムを実行する。
その際、main関数の引数にはプログラム自身のファイル名とコマンドライン引数の情報が入る
引数 格納する情報 argc 配列argvの大きさ(コマンドライン引数の数+1) argv[0] プログラムファイルのパスの文字列へのポインタ argv[1] 1番目のコマンドライン引数の文字列へのポインタ argv[2] 2番目のコマンドライン引数の文字列へのポインタ
使用例は次のような形になる
aaaa.exe val1 val2 val3 引数argv[0]には「aaaa.exe」が入る。 argv[1]には「val1\n」、argv[2]には「val2\n」、argv[3]には「val3\n」、がそれぞれ入る
関数を使う場合、呼び出し側と定義側の両方で引数を指定する。
呼び出し側を「実引数」といい、定義側を「仮引数」と呼ぶ。
■値渡しと参照渡し
引数を書く場合に、実引数と仮引数をまったく別もの(違うアドレス)として扱う場合は値渡しを行う。
値渡しの例
#include <stdio.h>
void addnum(int, int); //プロトタイプ宣言
void main()
{
int a = 2, b = 3;
int n;
n = addnum(a, b); //関数の呼び出し(値渡し)
printf("%d\n", n); //結果「2」となる
}
void addnum(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
対して、実引数と仮引数を同じもの(同一アドレス)として扱う場合は参照渡しを行う。
参照渡しの例
#include <stdio.h>
void addnum(int *, int *); //プロトタイプ宣言
void main()
{
int a = 2, b = 3;
int n;
addnum(&a, &b); //関数の呼び出し
printf("%d\n", n);
}
void addnum(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp; //全て参照として処理している
//結果「1970678749」になる
}
■関数のプロトタイプを宣言する
これまでは「関数の定義」→「関数の呼び出し」の順番でコーディングしていた。
これを逆に行うとコンパイルエラーになる。
コンパイルエラーにならないように関数を使いたい場合は、プロトタイプという関数のひな型を呼び出し前に宣言しておく。
プロトタイプ宣言は関数の仕様にあたる部分だけを抜き出したもの。
#include <stdio.h>
int addnum(int, int); //プロトタイプ宣言
void main()
{
int n;
n = addnum(2, 3); //関数の呼び出し
printf("%d\n", n);
}
int addnum(int a, int b)
{
int x;
x = a + b;
return x;
}
ブログ内の「関数の定義」の例では、関数を書く順番でエラーになった書き方が、プロトタイプを宣言することによってエラーではなくなった。