自作関数

自作関数

今回の内容は自作関数と変数のスコープについてです。今までに登場した関数と言うのはprintf関数とかscanf関数の2つですね(main関数を合わせて3つですね)。この2つのような関数は標準関数と呼ばれています。これらはもともと用意されている関数なので自作関数ではありません。

関数についてはmain関数の時の記事でも説明していますがかなり時間がたっているので、関数について学び直しつつ自作関数と標準関数のお話をしていきます。

前回の復習

前回はプリプロセッサ指令(ディレクティブ)について勉強しました。これらはコンパイルをする前に行うプロプロセスを指定するために用いられる命令文のことでしたね。C言語におけるディレクティブは『#』から始まる文法になっています。

#include

#includeはヘッダーファイルを読み込むためのディレクティブです。

#include <ヘッダーファイル名>
#include "ヘッダーファイル名"

#define

#includeはマクロ置換を行うことができるディレクティブです。定数を作るときは#defineを使い、定数名はすべて大文字にします(定数名に2単語使いたい場合はERROR_CHECKのようにすると良いです)。

#define PI 3.14

標準関数と自作関数

最初に関数というものについて復習しておきましょう。関数というのはある程度の処理をまとめたもののことを指します。例えば2つの値を渡すと足し合わせた結果を返してくれるものとか、渡した値が互いに素であるかを確かめるものなどがわかりやすいです。

上の例で挙げた2つの値を足し合わせた結果を返す関数について具体的な処理を考えてみます。最初に2つの値を受けとる必要があります。その後に足し合わせた結果を格納しておく変数を作っておく必要がありますね。その後に2つの値の和を計算して宣言しておいた変数に代入し、最後に値を戻すという処理がなければなりません。

このような一連の処理を実行してくれるように処理をまとめて書いておいたものが関数になります。

関数とはある程度の処理をまとめて、呼び出すだけで使えるようにしたもののこと。

関数について勉強し直したので、次は自作関数と標準関数のお話に入っていきましょう。

自作関数というのは文字通り自分で定義する(作成する)関数のことで、反対に自分で作らずに元々C言語の方で定義されている関数のこと標準関数と言います。

標準関数はprintf関数やscanf関数のようなものが例として挙げられます。他の標準関数については本講座の最終回に良く使う物だけをピックアップしてまとめたものを別記事として紹介する予定なので、今はprintf関数とscanf関数のみおさえておけば良いでしょう。

今回は標準関数については一旦置いておいて、自作関数の方に翔tンを当てて勉強していきます。

例えば自作関数というのは以下のようなものになります。

#include <stdio.h>

void sayHello() {
  printf("Hello\n");
  return;
}

int main() {
  sayHello(); //呼び出すだけでHelloが表示される。
  return 0;
}

実行結果

Hello

上の例(tc1201.c)ではsayHelloという名前の関数を作りました。この関数がどんな処理をするかを定めることを一般に定義すると言います。上記のコードの場合は3行目から6行目までの部分が関数の定義をしている箇所に該当します。

このように定義を書く必要がある関数が自作関数で、元々用意されている関数が標準関数と考えるとしっくりくるでしょう。

以下では自作関数についてもう少し勉強していきます。まずは自作関数における文法について解説をしていきます。

以下が自作関数の基本形になります。初出の用語もあるのでひとつひとつ説明しておきます。

戻り値の型 関数名(引数1の型 引数1, 引数2の型 引数2) {
  処理1;
  処理2;
  return 戻り値;
}

引数(ひきすう)というのは関数を呼び出す時に一緒に渡す値のことを指します。例えば2つの数値を比較して大きい方を調べる関数が必要なとき、関数の呼び出しと同時に2つの値も渡してあげる必要があります。(引数が必要になる理由についてはこの記事の後半で変数のスコープについて勉強するとスッキリすると思うので先にそちらを読んでも構いません)
引数が必要ない関数の場合は丸括弧の中を空っぽにすれば大丈夫です。

戻り値というのは関数が実行された時に戻ってくる値のことを指します。例えば2つの数値の和を調べる関数を作るとします。引数で2つの値を持ってきて関数の処理で足し算を定義するところまではイメージできると思います。実はこのままだと呼び出し元に足し算の結果が戻らないので、戻り値という仕組みを使って呼び出し元に値が返るようになっています。
ちなみに戻り値が要らない関数の場合は戻り値の型をvoidにして、return 戻り値;のところをreturn;だけにすれば大丈夫です。

関数名は予約語(すでに特別な意味を持つ単語)にかぶらなければどんな名前でも大丈夫ですが、ぱっと見てどんな役割を持っているのかがわかるような名前が望ましいとされています。関数名の名前の付け方に関しては結構人によりけりなので、詳しくはUpper Camel Case とか Lower Camel Case とかその辺の話題を漁ってみるといいと思います。

長々と説明してきたので頭の中の整理を兼ねて自作関数の具体例を挙げておきます。

#include <stdio.h>

/*戻り値がない, 引数がない場合の例*/
void errorMessage() {
  printf("error!!\n");
  return;
}

int main() {
  int age;
  printf("年齢を入力してください: ");
  scanf("%d", &age);
  if(age >= 0) printf("%d才です\n", age);
  else errorMessage();
  return 0;
}

実行結果

年齢を入力してください: 22 //入力値
22才です
年齢を入力してください: -1 //入力値
error!!
#include <stdio.h>

/*戻り値がない, 引数が2つの場合の例*/
void max(int a, int b) {
  int c;
  if(a > b) c = a;
  else c = b;
  printf("%dと%dのうち大きいのは%dです\n", a, b, c);
  return;
}

int main() {
  int x, y;
  printf("数字を2つ入力してください: ");
  scanf("%d %d", &x, &y);
  max(x, y); //関数で定義されている引数と違う名前の変数を呼び出しに用いても良い
  max(x, 12); //引数の型に一致していれば定数を渡すこともできる。
  return 0;
}

実行結果

数字を2つ入力してください: 2 33 //入力値
2と33のうち大きいのは33です
2と12のうち大きいのは12です
数字を2つ入力してください: 23 4 //入力値
23と4のうち大きいのは23です
23と12のうち大きいのは23です

自作関数として定義を済ませておけばプログラム中で何回でも呼び出せますね。

#include <stdio.h>

/*戻り値あり, 引数が1つの場合の例*/
int isAlphabet(char ch) {
  int flag = 0;
  if(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')) flag = 1;
  return flag;
}

int main() {
  char ch;
  scanf("%c", &ch);
  if(isAlphabet(ch)) printf("isAlphabet関数から1という値が返ってきた\n");
  else printf("isAlphabet関数から0という値が返ってきた\n");
  return 0;
}

実行結果

a //入力値
isAlphabet関数から1という値が返ってきた
+ //入力値
isAlphabet関数から0という値が返ってきた

このように煩雑になりやすい条件式を自作関数にまとめる使い方もできます。

#include <stdio.h>

/*戻り値あり, 引数がない場合の例*/
int input() {
  int a;
  scanf("%d", &a);
  return a;
}

int main() {
  int sum = 0, n = 3;
  int number[n];
  printf("半角空白か改行で%d個整数を入力してください: ", n);
  for(int i = 0; i < n; i++) {
    number[i] = input();
  }
  for(int i = 0; i < n; i++) {
    sum += number[i];
  }
  printf("%d個の値を合計した結果は%dです\n", n, sum);
  return 0;
}

実行結果

半角空白か改行で3個整数を入力してください: 12 23 34 //入力値
3個の値を合計した結果は69です

自作関数の作り方をすぐに理解できる人は少ないと思うので、練習問題を解きながらゆっくり理解してもらえれば大丈夫です。

自作関数のまとめをする前にひとつ重要なルールを言いそびれていたのでそれについてお話ししておきます。今まで自作関数をmain関数より上で書いていましたが、実はmain関数より下に書くとコンパイルエラーが発生します。どうしてエラーになるのかというと、main関数の中で使われている関数の定義がそれより上で見つからないためです。言い換えれば見つけられさえすればエラーが回避できるわけですね。

この自作関数の存在をコンパイラに教えるための文法として自作関数の宣言(プロトタイプ宣言)があります。基本形は以下のようになります。これをプリプロセス指令の次に書いておくことで自作関数の定義をmain関数より後ろに自作関数の定義を書いてもエラーがでなくなります。

戻り値の型 関数名(引数1の型 引数1, 引数2の型 引数2);

以下のように自作関数がめちゃくちゃ長くなる場合やたくさん自作関数を作る場合にはmain関数の場所がわかりにくくなってしまうので、自作関数のプロトタイプ宣言だけをmain関数より上で済ませて、関数の定義自体をmain関数より下に持っていきましょう!
(main関数がすぐに見つけられればその分早くコードを読み始められ、コードの可読性をあげることに繋がります。読まなくて大丈夫です^^まだ教えてないことが盛り沢山なコードなので( ̄▽ ̄;)) 関数電卓のコードになります。使って見たい人はこちらのGitHubのページからよろしくお願いします。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "token.h"

#define LINE_BUF_SIZE 1024

static int st_look_ahead_token_exists;
static Token st_look_ahead_token;

static int factorial_calc(int);
static void my_get_token(Token *);
static void unget_token(Token *);
static double parse_primary_expression();
static double parse_pow_term();
static double parse_term();
static double parse_expression();


static int factorial_calc(int number) {
  if(number == 1 || number == 0) {
    return 1;
  } else {
    return number * factorial_calc(number - 1);
  }
}

static void my_get_token(Token *token) {
  if(st_look_ahead_token_exists) {
    *token = st_look_ahead_token;
    st_look_ahead_token_exists = 0;
  } else {
    get_token(token);
  }
}

static void unget_token(Token *token) {
  st_look_ahead_token = *token;
  st_look_ahead_token_exists = 1;
}

static double parse_primary_expression() {
  int minus_flag = 0, function_flag = 0;
  Token token;
  double value;
  double base;
  my_get_token(&token);
  if(token.kind == SUB_OPERATOR_TOKEN) {
    minus_flag = 1;
    my_get_token(&token);
  }
  if(token.kind == LOG_NATURAL_FUNCTION_TOKEN) {
    function_flag = 1;
    my_get_token(&token);
  } else if(token.kind == SIN_FUNCTION_TOKEN) {
    function_flag = 2;
    my_get_token(&token);
  } else if(token.kind == COS_FUNCTION_TOKEN) {
    function_flag = 3;
    my_get_token(&token);
  } else if(token.kind == TAN_FUNCTION_TOKEN) {
    function_flag = 4;
    my_get_token(&token);
  } else if(token.kind == EXP_FUNCTION_TOKEN) {
    function_flag = 5;
    my_get_token(&token);
  } else if(token.kind == SQRT_FUNCTION_TOKEN) {
    function_flag = 6;
    my_get_token(&token);
  } else if(token.kind == ARC_SIN_FUNCTION_TOKEN) {
    function_flag = 7;
    my_get_token(&token);
  } else if(token.kind == ARC_COS_FUNCTION_TOKEN) {
    function_flag = 8;
    my_get_token(&token);
  } else if(token.kind == ARC_TAN_FUNCTION_TOKEN) {
    function_flag = 9;
    my_get_token(&token);
  } else if(token.kind == LOG_FUNCTION_TOKEN) {
    function_flag = 10;
    my_get_token(&token);
    if(token.kind == NUMBER_TOKEN) {
      base = token.value;
      my_get_token(&token);
    } else if(token.kind == LEFT_BRACKET_TOKEN) {
      base = 10;
    }
  }
  if(function_flag != 0) {
    if(token.kind == LEFT_BRACKET_TOKEN) {
      value = parse_expression();
      my_get_token(&token);
      if(token.kind != RIGHT_BRACKET_TOKEN) {
        fprintf(stderr, "cannot find brackets.\n");
        exit(1);
      }
      switch(function_flag) {
        case 1:
          value = log(value);
          break;
        case 2:
          value = sin(value);
          break;
        case 3:
          value = cos(value);
          break;
        case 4:
          value = tan(value);
          break;
        case 5:
          value = exp(value);
          break;
        case 6:
          value = sqrt(value);
          break;
        case 7:
          value = asin(value);
          break;
        case 8:
          value = acos(value);
          break;
        case 9:
          value = atan(value);
          break;
        case 10:
          value = log(value) / log(base);
      }
    } else {
      exit(1);
      return 0.0;
    }
  } else if(token.kind == NUMBER_TOKEN) {
    value = token.value;
  } else if(token.kind == LEFT_BRACKET_TOKEN) {
    value = parse_expression();
    my_get_token(&token);
    if(token.kind != RIGHT_BRACKET_TOKEN) {
      fprintf(stderr, "cannot find brackets.\n");
      exit(1);
    }
  } else {
    exit(1);
    return 0.0;
  }
  if(minus_flag) {
    value = -1.0 * value;
  }
  return value;
}

static double parse_factorial_term() {
  double r1;
  Token token;
  r1 = parse_primary_expression();
  my_get_token(&token);
  if(token.kind == FACTORIAL_FUNCTION_TOKEN) {
    r1 = factorial_calc(r1);
  } else {
    unget_token(&token);
  }
  return r1;
}

static double parse_pow_term() {
  double r1, r2;
  Token token;
  r1 = parse_factorial_term();
  my_get_token(&token);
  if(token.kind == POWER_FUNCTION_TOKEN) {
    r2 = parse_factorial_term();
    r1 = pow(r1, r2);
  } else {
    unget_token(&token);
  }
  return r1;
}

static double parse_term() {
  double r1, r2;
  Token token;
  r1 = parse_pow_term();
  while(1) {
    my_get_token(&token);
    if(token.kind != MUL_OPERATOR_TOKEN && token.kind != DIV_OPERATOR_TOKEN && token.kind != FACTORIAL_FUNCTION_TOKEN) {
      unget_token(&token);
      break;
    } else if(token.kind != FACTORIAL_FUNCTION_TOKEN) {
      r2 = parse_pow_term();
    }
    if(token.kind == MUL_OPERATOR_TOKEN) {
      r1 *= r2;
    } else if(token.kind == DIV_OPERATOR_TOKEN) {
      r1 /= r2;
    } else if(token.kind == FACTORIAL_FUNCTION_TOKEN) {
      r1 = factorial_calc((int)r1);
    }
  }
  return r1;
}

static double parse_expression() {
  double r1, r2;
  Token token;
  r1 = parse_term();
  while(1) {
    my_get_token(&token);
    if(token.kind != ADD_OPERATOR_TOKEN && token.kind != SUB_OPERATOR_TOKEN) {
      unget_token(&token);
      break;
    }
    r2 = parse_term();
    if(token.kind == ADD_OPERATOR_TOKEN) {
      r1 += r2;
    } else if(token.kind == SUB_OPERATOR_TOKEN) {
      r1 -= r2;
    }
  }
  return r1;
}

double parse_line() {
  double value;
  st_look_ahead_token_exists = 0;
  value = parse_expression();
  return value;
}

int main(int argc, char **argv) {
  char line[LINE_BUF_SIZE];
  double value;
  while(fgets(line, LINE_BUF_SIZE, stdin) != NULL) {
    set_line(line);
    value = parse_line();
    printf(">> %f\n", value);
  }
  return 0;
}
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "token.h"

static char *st_line;
static int st_line_pos;
static int st_func_flag;

typedef enum {
  INITIAL,
  INTEGER_PART,
  FRACTIONAL_PART,
  DOT
} LexerStatus;

void get_token(Token *token) {
  char current_char;
  int out_pos = 0;
  LexerStatus status = INITIAL;
  token->kind = BAD_TOKEN;

  while(st_line[st_line_pos] != '\0') {
    current_char = st_line[st_line_pos];
    /*(ひとつ前に入れた文字が整数部分又は少数部分) かつ 今見ている一文字が数字ではない かつ 今見ている一文字が小数点でもない場合*/
    if((status == INTEGER_PART || status == FRACTIONAL_PART) && !isdigit(current_char) && current_char != '.') {
      token->kind = NUMBER_TOKEN;
      sscanf(token->str, "%lf", &token->value);
      return;
    }
    /*改行又はスペース又はタブの場合*/
    if(isspace(current_char)) {
      /*改行の場合*/
      if(current_char == '\n') {
        token->kind = END_OF_LINE_TOKEN;
        return;
      }
      st_line_pos++;
      continue;
    }
    /*トークンのサイズがオーバーしていないかチェックしている*/
    if(out_pos >= MAX_TOKEN_SIZE - 1) {
      fprintf(stderr, "token size is over. token is too long!\n");
      exit(1);
    }

    token->str[out_pos] = st_line[st_line_pos];
    st_line_pos++;
    out_pos++;
    token->str[out_pos] = '\0';

    if(current_char == '(') {
      token->kind = LEFT_BRACKET_TOKEN;
      return;
    } else if(current_char == ')') {
      token->kind = RIGHT_BRACKET_TOKEN;
      return;
    } else if(current_char == '+') {
      token->kind = ADD_OPERATOR_TOKEN;
      return;
    } else if(current_char == '-') {
      token->kind = SUB_OPERATOR_TOKEN;
      return;
    } else if(current_char == '*') {
      token->kind = MUL_OPERATOR_TOKEN;
      return;
    } else if(current_char == '/') {
      token->kind = DIV_OPERATOR_TOKEN;
      return;
    } else if(current_char == '^') {
      token->kind = POWER_FUNCTION_TOKEN;
      return;
    } else if(current_char == '!') {
      token->kind = FACTORIAL_FUNCTION_TOKEN;
      return;
    } else if(('a' <= current_char && current_char <= 'z') || ('A' <= current_char && current_char <= 'Z')) {
      st_func_flag++;
      if(strcmp(token->str, "log") == 0 || strcmp(token->str, "LOG") == 0) {
        token->kind = LOG_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "ln") == 0 || strcmp(token->str, "LN") == 0) {
        token->kind = LOG_NATURAL_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "sin") == 0 || strcmp(token->str, "SIN") == 0) {
        token->kind = SIN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "cos") == 0 || strcmp(token->str, "COS") == 0) {
        token->kind = COS_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "tan") == 0 || strcmp(token->str, "TAN") == 0) {
        token->kind = TAN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "arcsin") == 0 || strcmp(token->str, "ARCSIN") == 0) {
        token->kind = ARC_SIN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "arccos") == 0 || strcmp(token->str, "ARCCOS") == 0) {
        token->kind = ARC_COS_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "arctan") == 0 || strcmp(token->str, "ARCTAN") == 0) {
        token->kind = ARC_TAN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "exp") == 0 || strcmp(token->str, "EXP") == 0) {
        token->kind = EXP_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "sqrt") == 0 || strcmp(token->str, "SQRT") == 0) {
        token->kind = SQRT_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "pi") == 0 || strcmp(token->str, "PI") == 0) {
        token->kind = NUMBER_TOKEN;
        token->value = M_PI;
        st_func_flag = 0;
        return;
      }
      continue;
    } else if(isdigit(current_char)) {
      if(status == INITIAL) {
        status = INTEGER_PART;
      } else if(status == DOT) {
        status = FRACTIONAL_PART;
      }
    } else if(current_char == '.') {
      if(status == INTEGER_PART) {
        status = DOT;
      } else {
        fprintf(stderr, "syntax error.\n");
        exit(1);
      }
    } else {
      fprintf(stderr, "bad character: %c\n", current_char);
      exit(1);
    }
    if(st_func_flag != 0) {
      fprintf(stderr, "syntax error.\n");
      exit(1);
    }
  }
}

void set_line(char *line) {
  st_line = line;
  st_line_pos = 0;
}

比較しやすいようにさっきの自作関数のサンプルコード(tc1205.c)を例に自作関数の宣言を使った場合の書き方を見てみましょう。

#include <stdio.h>

int input(); //自作関数の宣言

int main() {
  int sum = 0, n = 3;
  int number[n];
  printf("半角空白か改行で%d個整数を入力してください: ", n);
  for(int i = 0; i < n; i++) {
    number[i] = input();
  }
  for(int i = 0; i < n; i++) {
    sum += number[i];
  }
  printf("%d個の値を合計した結果は%dです\n", n, sum);
  return 0;
}

int input() {
  int a;
  scanf("%d", &a);
  return a;
}
標準関数とは元から定義されている関数のことを指す。

自作関数とは自分で定義をした関数のことを指す。自作関数を作ることで複雑な処理を切り離してコードを読みやすくしたり繰り返し使ったりすることができる。自作関数を使う際には関数の呼び出しもとより先に関数が存在していることを示してあげる必要がある。このために用意された文法が関数の宣言である。

column
ポインタ渡しと値渡しについて。
自作関数をある程度練習していくと一つの疑問が湧いてきます。『main関数などで作った配列を自作関数で使いたい時はどうすればいいの?』という問題です。これについては次回の記事(ポインタとアドレス)の話を読んだ後で紹介した方が良い内容ですが、自作関数パート2をやるのもなんか違うので、コラム扱いとしました。
さて、変数と同じ要領で配列を渡そうとするとコンパイルエラーになります。これはどうしてかというと変数のデータ型はプリミティブなのに対し配列のデータ型はアドレスを保持する型であるために起こります。そのため自作関数の引数の部分を参照型で受け取れるように変更してあげないといけないわけです。
そこで自作関数の引数部分に少し細工をしてあげます。具体的にどうするのかはサンプルコードを見てもらうのが早いと思うので以下のコードを眺めてみてください。

#include <stdio.h>

void say(char *message);

int main() {
  char str[100];
  scanf("%s", str);
  say(str);
  return 0;
}

void say(char *message) {
  printf("%s\n", message);
}

変数と配列の違いを完全に理解するためにはポインタとアドレスの理解が必要なので、現時点でわからなくても何にも問題ありません!!

変数のスコープ

変数のスコープとは簡単にいうと変数の有効範囲のことです。今まで語ってきませんでしたが、変数には寿命があります。(今まで黙っていてすみません~_~;)

後半では変数の有効範囲について学んで行きます。まずは有効範囲がどこからどこまでなのかという話ですがものすごく単純で、その変数が定義されたブロックが終わるまでが有効範囲になります。すなわち波括弧『{』(開き)がある行から波括弧『}』(閉じ)までですね。
わかりやすいように可視化してみました。

ブロックの構造を図解したもの

それぞれの四角で囲まれた部分がブロックになります。例えば変数aは宣言された4行目から11行目の波括弧までの間は普通に使うことが出来ますが、それ以外のところでは使えないことになります。

変数iと変数bは10行目の波括弧までが有効範囲なので、11行目以降で変数bを使うことはできません。

このように変数には使える範囲があります。この規則により自作関数には引数と戻り値という概念が必要になってくるわけですね。

ただし強制的に変数の有効範囲を全体にすることが出来ます。例えば以下のようなコードを見てください。今まで必ず関数の内側で宣言されていた変数がmain関数の外側の3行目で宣言されています。このように波括弧に囲まれる前に宣言することで変数の寿命を気にしないで使うことが出来ます。

#include <stdio.h>

int pi = 3.14;

int main() {
  int r;
  printf("半径を入力してください: ");
  scanf("%d", &r);
  printf("円の面積は%fです\n", r * r * pi);
  return 0;
}

これらのお話から分かるように変数にはスコープ(有効範囲)持つものと持たないものの2種類が存在しています。それぞれの変数をローカル変数グローバル変数と呼びます。(特定の範囲内でしか使えないためローカル変数、どこでも使えるからグローバル変数ということですね)

そもそも何故、変数にはスコープ(有効範囲)があるのでしょうか。『すべてグローバル変数にしてしまえばいいのにな』と考えた人も多いでしょう。しかし現在の開発の主流はグローバル変数は『悪』とされています。そこでローカル変数を用いたときのメリットとグローバル変数を使った際の問題点について説明しておきます。

まずはローカル変数を使った場合のメリットですが自作関数の使い回しができるようになることです。どういうことかというと自作関数内で作られた変数は自作関数のブロックの外ではスコープ外になるためmain関数で使われている変数と衝突しないためです。たとえmain関数内に同じ名前の変数があってもそれぞれの変数はスコープが被らないので別々の変数としてしっかりと機能するわけですね。

一方で、グローバル変数を使った場合、変数がずっと残り続けることになります。『例えば1000行のコードを書くとします。このときにグローバル変数と同じ名前の変数を作らないように気をつけながら作業をしなければなりません。それに前に作った自作関数が再利用できると気付いて脳死で貼り付けたら、たまたまそこにグローバル変数と同じ名前の変数があってバグが発生したり、そのせいでバグ取りに時間を割かなければならなくなったりすることも……』こんな状況にならないようにグローバル変数は極力使わないようにしましょう。

ローカル変数とはスコープ(有効範囲)を持つ変数のことを指す。基本的にはローカル変数を使うことが推奨されている。

グローバル変数とはスコープを持たない変数のことを指す。現在の開発ではあまり使われない傾向にある。

まとめ

今回の記事は関数について説明してきました。少し高度なお話しはコラムにしておきましたが結構重要なお話しなのでこの講座記事を一周し終えたら読んでみてくださいね。

今回の内容をざっとまとめておきます。

  • 関数とはある程度の処理のまとまりのことを指す。
  • 自作関数と標準関数の違いは自分で定義を書くか書かないかにある。
  • 関数の内側で宣言された変数にはスコープ(有効範囲)が存在する。一方で関数の外側で宣言された変数にはスコープが存在しない。これらはそれぞれローカル変数とグローバル変数と呼ばれている。

次回はC言語習得の最大の壁と言われているポインタとアドレスについて勉強していきましょう!

練習問題

A問題

2つの整数型の値a, bを受け取って、a×ba+bを計算し大きい方の値を返す自作関数calcを作ってください。

入力

a b

abは正の整数であり、1以上10000以下であることが保証されています。
出力

t

tにはa×ba+bのうち大きい方の値を出力してください。


入力例1

6000 20

出力例1

120000

6000 + 20 = 6020, 6000 × 20 = 120000 なので120000が答えになります。


入力例2

1 4000

出力例2

4001

B問題

ある数字aが与えられます。この数字の各位の和を計算する関数digitSumを作成してください。

入力

a

aは正の整数であり、1以上1000000000以下であることが保証されています。
出力

sum

sumにはaの各位の和を出力してください。


入力例1

731

出力例1

11

7 + 3 + 1 = 11。


入力例2

1023

出力例2

6

プリプロセッサ指令【C言語講座#11】

ポインタとアドレス【C言語講座#13】


最後まで記事を見ていただきありがとうございます。また別の記事でお会いできることを祈っております。

Print Friendly, PDF & Email

C言語カテゴリの最新記事