のむログ

技術メモ / 車 / 音楽 / 雑記 / etc...

C言語 - Part.1:簡単なプログラムを組む

Part.0に引き続き、Part.1では簡単なプログラムを実装していきます。

Part.0はこちら

www.nomunomu0504.work

環境構築

C言語の実行環境をセットアップできる方は、自前で構築してくれたものを使用してください。
さすがに環境構築まではって人は、Webの実行環境を使いましょう。

オススメはいくつかありますが、ここでは『Paiza.io』を紹介しておきます。

paiza.io

基本的なプログラム

一般的に基本的なプログラムはHello, Worldと呼ばれる文字列を出力するプログラムであると(IT業界では?)言われてます。

サンプルプログラム

以下のプログラムを入力してみましょう。行の先頭にある数字(1: など)は行番号を表しているだけなので、入力しないでください。プログラムが実行できなくなります。

1: #include <stdio.h>
2: 
3: int main (void)
4: {
5:   printf("Hello, World\n");
6:   return 0;
7: }

入力し終えたら、実行してみます。すると....

Hello, World

と表示されたはずです。そうです、ただHello, Worldという文字列を表示させただけです。どの入門書にもよく書かれていると思います。

実行できたところでプログラムを解説していきます。

解説

1行目

#include <stdio.h>

これはプリプロセッサ命令(ディレクティブ)と呼ばれ、プリプロセッサに対しての命令を示すものです。

プリプロセッサとは、ソースをコンパイルする前に、ソースに対して行われる前処理をプリプロセスといいますが、そのプリプロセスを行うプログラムのことをいいます。基本的にはコンパイラがその役割を担っていることがほとんどです。

ディレクティブにはいくつかの種類が存在します

ディレクティブ 概要
#define マクロを定義する
#ifdef シンボル定義がされていれば実行する
#if 条件式が真であれば実行する
#include ヘッダファイルをインクルードする
#error コンパイラにエラーを発生させる
#warning コンパイラに警告を発生させる
#pragma 実行するマシンやOS固有の機能をサポートする

3行目

プログラムには実行時に必ず呼び出される関数が存在します。C言語ではmain関数にあたります。

このmain関数の書き方には、サイト・本によっては

void main()
{
  // ...
}

という風に書いているものもあるかと思いますが、これははっきりいうと間違いです。何がどう間違っているのか、説明していきます。

そもそも関数とはどのように定義するのか。C言語では以下のように関数を定義します。

返り値の型 関数名(引数...)
{
  // 処理;
  return 返り値;
} 

C言語の仕様では、必ずmainという名前を持つ関数を定義しておかなければならない, main関数は必ずOSに対してプログラムの終了コードを返さなければならないとなっているので、int mainになることは間違いありません。

しかし、main関数の引数に関しては、仕様上厳密に定義はされていないので、様々な書き方がありますが、少なくともmain関数はOSに対して終了コードを返す必要があるので

int main (void)
{
  return 0;
}

とする必要があります(ちなみに、return 0; とは正常終了を表しています。0以外は全て異常終了として扱われます)

各コンパイラのバージョンに準じた処理系(例えばVCとかは違います)で、コンパイラのマニュアルにvoid mainでいいと書いてある場合以外は、void mainは不正です!

様々な書き方はこちら

書き方 概要
main() 戻り値省略は確かにintですが、これ自体が非推奨です
void main() 前述の理由で、おそらく不正です
void main( void ) 同様に、おそらく不正です
int main() C++ (ISO/IEC14882)では、規格書にも載っている正しい形式です。
※Cと違い、C++ では引数の void を省略したと看做されます
int main( void ) C言語として(C++としても)正しい形式です
int main(int argc, char *argv[]) 上記よりさらに正しい形式です

5行目

printf( ... );stdio.hに定義されている標準出力系の関数になります。最後に記述されているセミコロン(;)は処理の最後に記述します。この記号は処理の終わりを示しているものであり、複数の処理を行う際にはセミコロンで区切って記述する必要があります。

#include <stdio.h>

int main (void)
{
  int a = 2;
  printf("test\n");
  return 0;
}

また、printfで表示する文字列の中に\n(windowsOSでは¥n)というのがあります。これは改行を表しています。メモ帳などと異なり、普通に改行しただけでは、実行時に改行してくれないのです。

このように\¥で始まる文字をエスケープシーケンスと呼びます。もちろん改行以外にもあります。

記号 意味
\a 警告音
\b バックスペース
\n 改行
\t タブ
\ 文字としての\
\? 文字としての?マーク
\" ダブルクォーテーション
\' シングルクォーテーション
\0 ヌル文字

さて、これらを用いたprintfを考えてみましょう。

#include <stdio.h>

int main (void)
{
  printf("123\n456\789\n");
  printf("シングルクォーテーション: \'\nダブルクォーテーション: \"\n");
  printf("\t, バックスラッシュマーク: \\\n");
  return 0;
}
123
456
789
シングルクォーテーション: '
ダブルクォーテーション: "
        , バックスラッシュマーク: \

さらには、printf関数では表示する方法を細かく指定(書式設定)して表示することが可能です。

#include <stdio.h>
 
int main (void)
{
    printf("私の名前は%sです。\n年齢は%d歳です。\n", "のむのむ", 22);
    printf("イニシャルは、%c . %cです。\n", 'H', 'N');
    printf("%f + %f = %f\n", 1.2, 2.7, 1.2 + 2.7);
}
私の名前はのむのむです。
年齢は22歳です。
イニシャルは、H . Nです。
1.200000 + 2.700000 = 3.900000
  • 書式設定の話 最初に仕組みを説明すると、こういうことです。 f:id:nomunomu0504:20190523011103p:plain

このように、%d%sといった記号で記述した部分が、後半の値で置き換えられているのがわかります。

書式には以下のような物を指定することができて、置き換える値によって書き換える必要があります。

書式 対応する型 意味 使い方
%c char 1文字を出力する "%c"
%s char * 文字列を出力する "%8s", "%-10s"
%d int, short 整数を10進で出力する "%-2d","%03d"
%u unsigned int, unsigned short 符号なし整数を10進で出力する "%2u","%02u"
%o int, short,
unsigned int, unsigned short
整数を8進で出力する "%06o","%03o"
%x int, short,
unsigned int, unsigned short
整数を16進で出力する "%04x"
%f float 実数を出力する "%5.2f"
%e float 実数を指数表示で出力する "%5.3e"
%g float 実数を最適な形式で出力する "%g"
%ld long 倍精度整数を10進で出力する "%-10ld"
%lu unsigned long 符号なし倍精度整数を10進で出力する "%10lu"
%lo long, unsigned long 倍精度整数を8進で出力する "%12lo"
%lx long, unsigned long 倍精度整数を16進で出力する "%08lx"
%lf double 倍精度実数を出力する "%8.3lf"

というような感じで、とりあえず基本的な部分の説明は終わりです。

演習問題を置いておくので、解いてみましょう。理解できてれば問題なく解けると思います。

Q1: 簡単なプログラム

Q1-1

自分の名前を表示するプログラムを作成しましょう。ただし、%sなどの書式は使わずに作成してください。また、最後は改行してください。

Q1-2

以下のプログラムがありますが、printfを1回だけ使って同じ出力結果になるように書き換えてください。

#include <stdio.h>

int main (void)
{
  printf("古池や\n");
  printf("蛙飛び込む\n");
  printf("水の音\n");
  return 0;
}
古池や
蛙飛び込む
水の音

Q1-3

以下の出力結果となるようなプログラムを%dを4つ使って書いてください。

1 + 2 + 3 = 6
#include <stdio.h>

int main (void)
{
  // ここに処理を書いてください
  
  return 0;
}

各回答は、このページの一番下に記載してあります。 解答を見る

語句解説

#define

#defineとは、マクロ定義を行います。C言語では数値、文字列、数式などに名前をつけて定義することができます。

  • 数値、文字列を定義する
#include <stdio.h>
#include <math.h>

#define M_PI 3.14159265
#define M_E 2.718281828

int main (void)
{
  double theta = M_PI / 4;
  double result = log(M_E);
  return 0;
}
  • 数式を定義する
#include <stdio.h>

#define REVERSE(X) X * (-1)

int main (void)
{
  int a = 3;
  int b = REVERSE(a);
  return 0;
}

数式を定義したときのように、マクロには引数を指定することもできます。

C言語にはすでに定義されているマクロが存在します。

マクロ 概要
__FILE__ ファイル名取得
__LINE__ 行番号取得
__DATE__ コンパイル日付取得
__TIME__ コンパイル時間取得
__STDC__ ANSI規格対応なら1、それ以外は0を取得

#ifdef

#ifdefは、シンボル定義がされているときのみ実行されるディレクティブです。シンボルは#defineで定義するか、コンパイル時のオプションで定義します。

#ifdef M_PI
// M_PIが定義されていたら実行する
#endif

逆に、定義されていなければ実行する#ifndefというディレクティブも存在します。

#ifndef M_PI
// M_PIが定義されていないときに実行する
#endif

条件分岐をしたい時は以下のように書きます

#ifdef M_PI
// M_PIが定義されているときに実行する
#else
// M_PIが定義されていないときに実行する
#endif

#if

#ifでは#ifdefと異なり、シンボルではなく条件式を評価した結果で実行するかどうかを決めます。

#if a > b
// aがbより大きければ実行する
#endif
#if !(a > b)
// aがbより大きくない時に実行する
#endif

これをまとめて書くと

#if a > b
// aがbより大きい時に実行する
#else
// aがbより大きくない時に実行する
#endif

さらに複数の条件が存在する時は#elifを利用します

#if 条件式1
// 条件式1に当てはまる時実行する
#elif 条件式2
// 条件式1に当てはまらず、条件式2に当てはまる時に実行する
#elif 条件式3
// 条件式1, 2に当てはまらず、条件式3に当てはまる時に実行する
#else
// すべての条件式に当てはまらない時に実行する
#endif

#include

C言語に限らず、様々な言語において既に必要なマクロや関数などを定義したヘッダファイルが用意されていることがほとんどです。そのヘッダファイルに記述されているマクロや関数を利用するためには、#includeを使ってソースに読み込まなければなりません。

#include <ファイル名>

C言語の代表的なヘッダファイルを示しておきます。基本的にはここらへんさえ分かっていれば問題ないような気がします...。

ファイル名 概要
limits.h 実装に依存する値に関するファイル
stdio.h 標準入出力に関するファイル
stdlib.h 標準ライブラリに関するファイル
string.h 文字列操作に関するファイル
math.h 数学関数に関するファイル
time.h 時間操作に関するファイル

また、自分で作成したファイルを読み込む際には#include <...>ではなく#include "..."とします。

#include "myFunctions.h"

また、ヘッダファイルにはインクルードガードという工夫をしておくことが望ましいです。

インクルードガードとは、同じヘッダファイルを2重でインクルードしないための処理です。

#ifndef _MYFUNCTIONS_H

#define _MYFUNCTIONS_H

#ifdef __cplusplus
extern "C" {
#endif

// ヘッダファイルの内容を記述する

#ifdef __cplusplus
}
#endif

#endif // __MYFUNCTIONS_H

なぜC++だとextern "C" {}という表記が必要なのか!!ということに関しては、過去記事を参照してください。

www.nomunomu0504.work

#error

名の通り、コンパイル時に記述してある行でエラーを発生させます

#error ERROR_MESSAGE

#warning

#error同様、警告を発生させます

#error WARNING_MESSAGE

#pragma

#pragmaはプログラムを実行させるマシンや、動作させるOSにある固有の機能をサポートします。そのため、コンパイラによって使用できる機能が制限されます。

例えば、割り込みが設定できるマシンで設定を行う時には、以下のような記述を行います。

#pragma interrupt

これにより、割り込みが許可されることになります。ただ、割り込みが発生した際に呼び出される関数が指定されている場合と、設定しないと行けない場合があるので、要注意です。

Q1の解答

A1-1

#include <stdio.h>

int main (void)
{
  printf("のむのむ\n");
  return 0;
}

A1-2

#include <stdio.h>

int main (void)
{
  printf("古池や\n蛙飛び込む\n水の音\n");
  return 0;
}

A1-3

#include <stdio.h>

int main (void)
{
  printf("%d + %d + %d = %d\n", 1, 2, 3, 1 + 2 + 3);
  return 0;
}

Part.2はこちら www.nomunomu0504.work

f:id:nomunomu0504:20190411151221p:plain:w0