C言語/基礎知識

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C言語C言語 変数 から転送)
ナビゲーションに移動 検索に移動

main関数[編集]

main関数はプログラムの開始処理において、ランタイムライブラリーの初期化処理の後、最初に呼び出されるユーザープログラムのエントリーポイントです[1]。 main() は、引数を省略することはできますが、戻値は int とされ環境に実行状況を返し、0 が成功、0 以外が失敗を表します。

引数を伴わないmain関数
int main(void) {
  /* いくつかの文 */
}
「/* いくつかの文 */」の部分に実行したい文を記述します。
引数を伴なったmain関数
int main (int argc, char *argv[]) {
  /* いくつかの文 */
}
の形式は、コマンドライン引数をプログラムから参照するときに使います。

データー型と変数の基本[編集]

変数とは、数値などのデーターをひとつ入れるための領域のことです。

C言語では、基本的に、1つの変数には、1つのデーターしか入れられません[2]

変数の宣言[編集]

C言語では、変数を使用するまえに宣言する必要があります。

#include <stdio.h>

int main(void) {
  int a = 3;

  printf("%d", a);
}
実行結果
3

というプログラムで考えてみましょう。

int a = 3; は、a という名前で型として int をもつの変数を宣言し 3 で初期化しています。

異なる型の値での初期化[編集]

literal-conversion.c
#include <stdio.h>

int main(void) {
  int a = 3.45;

  printf("%d\n", a);
}
コンパイル&実行
% clang literal-conversion.c -o literal-conversion    literal-conversion.c:5:11: warning: implicit conversion from 'double' to 'int' changes value from 3.45 to 3 [-Wliteral-conversion]
  int a = 3.45;
      ~   ^~~~
1 warning generated.
% ./literal-conversion
3
%

のように、整数を宣言した変数を浮動小数点リテラルで初期化してもコンパイルエラーにはなりません[3]

代入[編集]

初期化を伴わず宣言された自動変数の値は不定( indeterminate )です[4]。このため、使用する前に値を決定する必要があります。

変数の宣言と初期化を別個に行った例
  int a;
  a = 3;

というように、2つの文に分けて記述することもできます[5]

変数の値を非可逆的に変更することを代入と呼び、典型的には代入演算子 = の左辺値式に変数・右辺に式の値を与えるケースを想起しますが、左辺値式にはポインタによる参照や配列要素の参照、構造体や共用体のメンバーの参照などがあります[6]

#include <stdio.h>

int main(void) {
  int a = 3;
  int next = 8;
  printf("%d\n", next);
}
実行結果
8
解説

変数名には、2文字以上の文字も使えます。ただし、先端を数字にすることはできません

日本語の単語をローマ字表記した変数名は使わない

外部に公開しない変数はあえて短めにし、一時的な変数であることを伝えようとする傾向がありますが、コーディング規約で定めていない限りリラックスした名付けが行われます。

ただし、日本語の単語をローマ字表記した変数名は単数複数の区別がないので int item = items[0]の様な名付けが行えなかったり、のつもりで tsuki と綴ったら tuki が正しかったなど、日本語のラテン語文字翻字のゆらぎでミスタイプとなる可能性を増やすので推奨しません(月、の場合 month なのか moon なのかの間にも曖昧さが生じます )。

複数の変数の表示[編集]

複数の変数の表示
#include <stdio.h>

int main(void) {
  int a = 3;
  int b = 7;
  printf("変数aは%dです。変数bは%dです。\n", a, b);
}
実行結果
変数aは3です。変数bは7です。
printfの出力順序欄にあるaとbの順序を入れ替え
#include <stdio.h>

int main(void) {
  int a = 3;
  int b = 7;
  printf("さいしょの変数は%dです。つぎの変数は%dです。\n", b, a);
}
実行結果
さいしょの変数は7です。つぎの変数は3です。

出力結果も入れ替わります。

代入式の右辺と左辺[編集]

下記の式の a = a + 1のように、右辺と左辺に同じ変数が含むことも可能です。 =はC言語では、(if文などの条件式の中であっても)等式では無く代入でことに注意してください。

#include <stdio.h>

int main(void) {
  int a;      // int型のiという名前の変数を「宣言」します。
  a = 0;      // 0をiに「代入」します。
  printf("いまの変数は%dです。\n", a);
  a = a + 1;  // iの値を「参照」してそれに1を加えたものをiに「代入」する
  printf("この時点の変数は%dです。\n", a);
}
実行結果
いまの変数は0です。
この時点の変数は1です。

まとめ[編集]

変数(へんすう)とは名前をもったデーターを格納しておく領域のことです。 変数は、一般にメモリー上に確保され、値を代入したり参照したりすることができます。 変数は、型を持ちます。

さらに詳しい説明[編集]

変数の宣言[編集]

変数を使用するには前もって宣言をする必要があります。

int a;	// int型のaという名前の変数を「宣言」します。

int は変数のデーター型、a は変数名です。

変数のデーター型とは、メモリー上に確保する領域の大きさや、確保した領域の扱い方などを決定するものである(型指定子)。データー型は扱いたいデーターの種類や値の範囲によって決定します。 「変数名」とはその変数を他の変数と区別するために付ける名前のことで、 変数名に使えるのは、半角英数の小文字と大文字、および_(下線、アンダーライン) です。 また、変数名に int は使えません。intreturn などキーワードは変数名や関数名など利用できません。

キーワードの一覧[7]

なお、printfは標準ライブラリーの関数でキーワードではありませんが、もしグローバルな変数名に用いてしまうと重複定義となりリンクに失敗します。

代表的な型
データー型の種類 データー型 データー型の名称 大きさ[bit] 扱える値の範囲
整数型 int 整数型 32 -2147483648 ~ 2147483647
浮動小数点型 float 単精度浮動小数点型 32

最小の正の数1.175494351e-38、 最大値3.402823466e+38

double 倍精度浮動小数点型 64 最小の正の数 2.2250738585072014e-308、 最大値 1.7976931348623158e+308
文字型 char 文字型 8

※ 大きさと扱える値の範囲は実装依存なので、上記の数値は一例です。

C言語では、文字を入れるための文字型 char があります。文字列は文字型の配列の '\0' で終端された特殊なケースです。

コメント[編集]

#include <stdio.h>

int main(void) {
  int i;      // int型のiという名前の変数を「宣言」します。
  i = 0;      // iに0を「代入」します。
  printf("いまの変数は%dです。\n", a);
  i = i + 1;  // iの値を「参照」してそれに1を加えたものをiに「代入」する
  printf("この時点の変数は%dです。\n", a);
}
// コメント

のように、// 以降にその行の説明を追記できます。このような追記を注釈(ちゅうしゃく)またはコメントといいます。

上のコードでは逐語的にプログラムの動作をコメントで説明していますが、自明なので冗長です。 また、プログラムを変更した時にコメントも合わせて変更しないと、プログラムの実体とコメントが乖離するという望ましくない状況が発生します。 プログラムでは直喩的に表現できなかったり、プログラムを作るにあたって参考にした文献へのURLなど情報を補完するコメントを心がけましょう。

/* 変数の宣言と代入 */
#include <stdio.h>

int main(void) {
  int a = 0; // 変数の宣言と同時に初期値を与えると、未初期化による不具合を未然に防ぐことができます。
  printf("いまの変数は%dです。\n", a);
  a = a + 1;
  /* 代数学的にありえない表現ですが、変数aの値を1つの増すことになります。
   * このパターンは頻出するので a++ あるいは ++a と書けます。
   * 2つは少し意味が違いますが後に学びましょう。
   */
  printf("この時点の変数は%dです。\n", a);
}
他のコメント記号

コメントの書き方には、「//」の他にも

/* 変数の宣言と代入 */

のように、「/*」と「*/」によって、プログラムの説明を追記する方法があります。 「/*」と「*/」は入れ子に出来ません。

たとえば

入れ子のコメントを含んだ間違った例
/*
    printf(... );
    /*
        コメントの例
    */
     printf(... );
*/

は誤ったコードで、1行目から5行目までがコメントとなり、6行目は生きたコード、7行目は場所をわきまえないコメントの終了トークンとなります。

算術演算子と代入演算子[編集]

+(プラス)や-(マイナス)などを、演算子といいます。演算子(えんざんし)とは、演算の内容を指示する記号です。

C言語には非常に多くの演算子が存在しますが、ここでは算術演算子と代入演算子についてだけ説明します。

算術演算子を使ったプログラムの例
#include <stdio.h>

int main(void) {
  int product = 2 * 3,
      quotient = 6 / 2,
      remainder = 5 % 3,
      sum = 3 + 6,
      difference = 8 - 2;

  printf("積は%d\n", product);
  printf("商は%d\n", quotient);
}

;実行結果:

積は6
商は3

上記のプログラム中で、算術演算子になるものは、「-」「*」「/」「%」「+」「-」です。

算術演算子とは、加減乗除などの算術を指示する演算子です。 算術演算子には、加法を指示する+、減法を指示する-、乗法を指示する*、除法を指示する/、剰余を指示する%、などがあり、 また、-は符号の反転を指示するためにも用いられます。

数学と同様に、加減よりも乗除のほうが優先度が高く、()で囲むことにより、カッコ内の演算を優先させることができます。

算術演算子と代入演算子
演算子の種類 演算子 演算子の名称 意味
単項演算子 - 単項-演算子 右オペランドの符号を反転した値
乗除演算子 * 2項*演算子 左右オペランドの積
/ 2項/演算子 左オペランドを右オペランドで除した商
右オペランドの値は0以外でなければならません。
左右オペランドが整数型の場合、商の小数部は切り捨てられます。
% 2項%演算子 左オペランドを右オペランドで除した剰余
右オペランドの値は0以外でなければならません。
左右オペランドは整数型でなければならません。
加減演算子 + 2項+演算子 左右オペランドの和
- 2項-演算子 左オペランドから右オペランドを引いた差
代入演算子 = 単純代入演算子 左オペランドが指す変数に右オペランドの値を格納する

※ この表の演算子の種類は、演算子の優先順位が高い順番に並んでいます。

整数どうしの割り算[編集]

整数どうしの割り算
#include <stdio.h>

int main(void) {
  int a = 8, b = 5;
  double c = a / b;

  printf("計算結果は%f \n", c);
}
実行結果
計算結果は1.000000

と表示され、「1.600000」とは表示されません。

これは、整数どうしの割り算の結果は整数型なるので、端数は切り捨てられたからです(なお、プログラム中の「計算結果は」の後ろは「%f」(浮動小数点)にすることに注意してください)。

除算の片方の項をキャストで double に昇格
#include <stdio.h>

int main(void) {
  int a = 8, b = 5;
  double c = (double)a / b;

  printf("計算結果は%f \n", c);
}
実行結果
計算結果は1.600000

除算にあたって、「(double)」のように明示的に型変換する(キャストする)ことにより、他方の項も暗黙的に double に昇格させ、整数演算ではなく浮動小数点数演算を行わせています[8]

 double c = a / (double)b;

としても、同じように浮動小数点数として除算が行われます(型昇格の規則)。

参照[編集]

変数に格納されたの値を使用することを変数の値の参照と呼びます。

a = a + 1;	// aの値を「参照」してそれに1を加えたものをaに「代入」する

参照する際、変数があらかじめ初期化や代入されているよう注意してください。未初期化の変数の値は不定です。

識別子の長さとスコープ
for (int i = 0; i < 10; i++{...}

の i のように極めて短いスコープを持つ変数に short_loop_counter の様な冗長な名前を付けるのは滑稽です。

この様に、識別子の長さはスコープと関連付けて考えるべきです。 また、C言語には namespace が無いので名前の衝突を避ける必要があります。 この事から、一貫した命名規約が1つのプログラムの中で貫かれているべきで、長さよりも一貫性が大事になります。

定数[編集]

(ここで言う定数はキーワード const によってもたらされたReadOnly属性のことではなく、リテラルのことです。一般にリテラルは定数には含まれます。むしろ無引数マクロを定数というケースがC言語では主流でした。)

定数とは、プログラム実行時に一定の値しかもたない数です。 変数の値がプログラム実行中に変更される場合もあるのに対して、定数の値はプログラム実行中を通して一定です。 ソースコード中で直接に記述された定数を特にリテラルとも呼ぶ。 ここではそのリテラルについて説明します。

整数定数を使ったプログラムの例
int main(void) {
  int a;
  a = 0;      // 整数定数0をiに代入します。
  a = a + 1;  // aの値に整数定数1を加える。
}

なお、これは画面に何も表示することなく、ただちに終了するプログラムです。 このソースコード中の「0」や「1」が定数です。

定数には整数定数、浮動小数点定数、文字定数、文字列定数、などがあります。 整数定数とは整数を記述するための定数で、主に10進数表記が使われます。 浮動小数点定数とは浮動小数点数を記述するための定数で、主に10進数の小数点数表記で記述します。 文字定数とは1バイト文字を記述するための定数で、文字を「'(一重引用符)」で囲む。 文字列定数とは1バイト文字または多バイト文字の文字列を記述するための定数で、文字列を「"(二重引用符)」で囲む。

リテラル表現
定数の種類 進数 記法
整数定数 10進数 10進数 1234
浮動小数点定数 10進数 10進整数部と「.」と10進小数部 3.14
文字定数 - 「'(一重引用符)」で囲まれた文字 'a'
文字列定数 - 「"(二重引用符)」で囲まれた文字列 "Hello, World!"

標準ライブラリー[編集]

標準ライブラリーとはプログラミングでよく使われる処理がまとめられたもので、代表的なものには入出力(stdio.h)、文字列操作(string.h)、数学(math.h)などがあります。

「stdio」は standard input-output の略で、日本語では標準入出力となります。

入力と出力とを行うことはプログラミングの初期においても必須なものであるため、入出力(stdio.h)の内、printf関数(プリントエフ)とscanf関数(スキャンエフ)とについては、ここでその使い方を簡単に説明します。

前処理指令[編集]

前処理とは、翻訳単位の翻訳の前に行う処理で、前処理指令とは、#前処理字句で始まる1行やPragma演算子を指します。前処理指令には、ソースファイルの1部分を条件によって読み飛ばす・他のソースファイルを取り込む・マクロを置き換える、などの機能があります。

ヘッダーは、型定義や関数宣言やマクロ定義などをひとまとめにし、 ソースファイルやほかのヘッダーでインクルードして使用します。

標準ライブラリーを使用するためには、使用する標準ライブラリーの機能に応じた標準ヘッダーをインクルードする必要があります。 ヘッダーを組み込むには前処理指令の内の1つ #include指令を用います。 printf関数及びscanf関数を使用するためには、stdio.hというヘッダーを組み込む必要があります。

stdio.hをインクルード
#include <stdio.h>

ヘッダーをインクルードする1つの形式

#include "my_header.h"

は、ユーザー定義のヘッダーの include に使うものです。

標準ヘッダーは必ず < ... > でヘッダー名を囲む形式にします。

標準ヘッダーを " ... " で取り込むとユーザヘッダーの検索パスに標準ヘッダーと同じ名前(例: stdio.h)の偽装されたヘッダーを置かれた場合に偽装されたヘッダーを使ってしまうというセキュリティホールになります。標準ヘッダーは通常はユーザが書き換えできないパーミッションが付与されていますが、ユーザヘッダーの検索パスへのに書き込みパーミッションは付与されている可能性があります。

printf関数[編集]

printf関数は、書式付で標準出力へ書き込む関数です。 標準出力はデフォルトでコンソール画面です。

printf関数の宣言部分
int printf(const char * restrict format, ...);

format には、それに続く任意個数の実引数と同じ数だけ(%d のような)変換指定子( conversion specifier )[9]が含まれなれなければなりません[10]。 書式化文字列に含まれる変換指定子の部分が、それに対応する後の実引数の値によって置換されます。

変換指定子は、実引数のデーター型に応じて、主に以下のようになる。

*printf関数の変換指定子
実引数のデーター型 変換指定子
int
(整数)
%d
double
(浮動小数点数)
%f
char
(文字)
%c
char*
(文字列)
%s

エスケープシーケンス[編集]

書式付きの文字列定数には、エスケープシーケンス(Escape sequence; 逆斜線表記)を含めることもできます[11]。 逆斜線表記とは、\に文字が続くことで特別な意味を表すものです。

エスケープシーケンスとその意味
エスケープシーケンス 意味
\n 改行(New line)
現在の印字位置を次の行の先頭位置に移動する
\t 水平タブ(horizontal Tab)
次の水平タブ位置に移動する
\' シングルクォーテーション(single quotation mark)
一重引用符
\" ダブルクォーテーション(double quotation mark)
二重引用符
\\ 円記号(\)

printf関数の使用例[編集]

printf関数を用いて変数を出力に書き込む
#include <stdio.h>

int main(void) {
  int i = 1234;
  printf("iの値は%d\n", i);  //「iの値は1234(改行)」と出力します。
  double d = 3.14;
  printf("dの値は%f\n", d);  //「dの値は3.14(改行)」と出力します。
  char c = 'a';
  printf("cの値は%c\n", c);  //「cの値はa(改行)」と出力します。
  char str[] = "Hello, World!";
  printf("strの値は%s\n", str);  //「strの値はHello, World!(改行)」と出力します。
}

scanf関数[編集]

scanf関数を使うことにより、標準入力(既定値はキーボード)から入力した値を読み取らせることができます。

#include <stdio.h>

int main(void) {
  int input = -1; // scanf() が失敗した場合の対策
  printf("整数を入力してください。\n");
  if (scanf("%d", &input) == EOF) {
      printf("End of File に達しました。\n");
      return 1;
  }
  printf("入力された整数は%dです。 \n", input);
}

これを実行すると、まず

整数を入力してください。

と表示されます。

そして、カーソルが点滅するので、そこに整数を入れて、エンターキー(リターンキー)を押す。 たとえば整数 73 を入れると、

整数を入力してください。
73
入力された整数は73です。

と表示されます。

scanfの使用では、「&input」のように、変数名の前に「&」をつける必要があります。なお、この「&」記号は、記憶領域のアドレスという意味を表す。

アドレスについては、高度な説明になるので、ほかのページで後述します。

「なんで変数へのキーボードからの入力のさい、アドレスというものを使うのか?」という疑問には、

左辺値式を関数の引数に渡す方法が他にない。

という答えになります。

左辺値式は代入の左辺になりうる式の事で、a = 1aが左辺値式です。 他方、関数の引数は値渡しされるので、変数の値を渡すことはできますが、仮引数を変更して元々の変数の値は変わりません。 このため、アドレスを引数として渡し関数の中で間接参照演算子を使い値を書き変えています。

#include <stdio.h>

void by_value(int i) { i = 0; }
void by_address(int *i) { *i = 100; }

int main(void) {
  int i = 10;
  printf("i = %d\n", i);
  by_value(i);
  printf("by_value(i); i = %d\n", i);
  by_address(&i);
  printf("by_address(&i); i = %d\n", i);
}

結果

i = 10
by_value(i); i = 10
by_address(&i); i = 100

scanf関数の使用例[編集]

//例 scanf関数を用いて入力を変数に読み込む。
#include <stdio.h>

int main(void)
{
  int i;
  if (scanf("%d", &i) == EOF) {  //整数入力をiに格納します。
      printf("End of File に達しました。\n");
      return 1;
  }
  double d;
  if (scanf("%f", &d) == EOF) {  //浮動小数点数入力をdに格納します。
      printf("End of File に達しました。\n");
      return 1;
  }
  char c;
  if (scanf(" %c", &c) == EOF) {  //文字入力をcに格納します。
      printf("End of File に達しました。\n");
      return 1; 
  }
  char str[32];
  if (scanf("%31s", str) == EOF) {  //文字列入力をstrに格納します。
      printf("End of File に達しました。\n");
      return 1; 
  }
}

scanf関数の実用には様々な問題を解決する必要があるが、ここではこれ以上説明しません。

scanfのくわしい説明[編集]

scanf関数の宣言部分
int scanf(const char * restrict format, ...);

format には、それに続く任意個数の実引数と同じ数だけの変換指定子が含まれなければなりません。 標準入力からの入力が、書式化文字列に含まれる変換指定子に従って、それに対応する後の実引数が指す変数に代入されます。

*scanf関数の変換指定子
実引数のデーター型 変換指定子
int 整数 %d
double 浮動小数点数 %f
char 文字 %c
char* 文字列 %s

変換指定子に対応する実引数は、単項&演算子(アドレス参照演算子)を前置します。

&オブジェクト

これはscanf関数にアドレスを渡すことでオブジェクトへの代入を可能にするためです。

脚註[編集]

  1. ^ ただし、フリースタンディング環境(例えば組み込み用途)では、main() を用意しない場合もあります。
  2. ^ 配列や構造体は除きます。
  3. ^ 過去の編集で「コンパイルでエラーとなる」とされていましたが、実際は規格的にも実装もエラーとはなりません。上記のように、clang は警告しましたが、gcc は警告しませんでした。
  4. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 30, §6.2.4 Storage durations of objects. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. "If an initialization is specified for the object, it is performed each time the declaration or compound literal is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.(仮訳:オブジェクトに初期化が指定されている場合、ブロックの実行中に宣言または複合リテラルに到達するたびに初期化が実行され、そうでない場合は宣言に到達するたびに値が不定になります。)" 
  5. ^ 宣言と同時に初期化することで「未初期化変数の使用」という厄介な問題が避けられるので、宣言と同時に初期化することを強く推奨します。
  6. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 40, §6.3.2.1 Lvalues, arrays, and function designators. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  7. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 42, §6.4.1 Keywords. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  8. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 39, §6.3.1.8 Usual arithmetic conversions. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  9. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 225, §7.21.6.1 The fprintf function. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  10. ^ 以前の編集で、書式化文字列を書式付の文字列定数としていましたが、定数(リテラル)である必要はありません。
  11. ^ 逆斜線表記という名前ですが、皆様の画面では \ ではなく \ と表示されていると思います。それで正常です。

参考文献[編集]