はじめに
この記事について
「C言語の基礎を学ぼう」をテーマに、自身の知識 + α をアドベントカレンダーにまとめます。
25日間でC言語をマスターしよう - Qiita Advent Calendar 2025 - Qiita
こんな方を対象としています
-
コンピュータがプログラムをどのように動かしているか知りたい/知らない方
-
プログラミングをしてみたい方
-
C言語初心者の方
キーワード
-
関数
-
プロトタイプ宣言
-
値渡し/参照渡し
-
破壊的操作
説明
関数
一連の処理の流れをまとめたものを 関数 といいます。
printf も関数の1つです。
関数はだれでも自由に作ることができます。
プロトタイプ宣言
関数を作成する場合、プロトタイプ宣言と実装の2つが必要です。
プロトタイプ宣言は下記形式で行います。
戻り値や引数が無い場合は void と記載します。
戻り値の型 関数名(引数);
実際の例を見てみましょう。
#include <stdio.h>
// プロトタイプ宣言
int add(int num1, int num2);
// main
int main(void) {
printf("%d", add(1,2));
return 0;
}
// 実装
int add(int num1, int num2) {
return num1 + num2;
}
3
プロトタイプ宣言では変数名は省略可能です(上記例では記載しています)。
さて、プロトタイプ宣言が必要な理由を考えてみます。
C言語のプログラムをコンパイラがコンパイルするとき、先頭の行から順番に解析します。
プロトタイプ宣言が無い場合、関数使用箇所が関数実装箇所より前にあると その関数が存在するかを判断することができません。
プログラムの先頭でプロトタイプ宣言をすることで、関数が存在することをコンパイラに教えることができます。
プロトタイプ宣言をせずに、使用箇所より前に実装を書いても正常にコンパイルは通りますが、プロトタイプ宣言をしたほうが読みやすいプログラムになります。
#include <stdio.h>
// 実装
int add(int num1, int num2) {
return num1 + num2;
}
// main
int main(void) {
printf("%d", add(1,2));
return 0;
}
3
値渡し/参照渡し
関数の引数の渡し方には2種類の渡し方があります。
- 値渡し:値を渡します。関数内の変更は元変数に影響しません。
- 参照渡し:アドレスを渡します。関数内の変更が元変数に影響します。
プログラムで見てみます。
#include <stdio.h>
void change1(int num);
void change2(int num[3]);
int main(void) {
int a = 1;
int b[3] = {1, 2, 3};
printf("a: %d ", a);
printf("b: %d %d %d\n", b[0], b[1], b[2]);
change1(a);
change2(b); // アドレスを渡している
printf("a: %d ", a);
printf("b: %d %d %d\n", b[0], b[1], b[2]);
return 0;
}
void change1(int num) {
num = 5;
return;
}
void change2(int num[3]) {
num[0] = 5;
return;
}
a: 1 b: 1 2 3
a: 1 b: 5 2 3
int型など値を直接渡す場合は、関数内で書き換えても元の変数の値は変わりません。元変数と関数の引数の変数でメモリの場所が異なるためです。
配列の場合は渡す値はアドレスです。関数内で書き換えた場合、元の変数の値も書き換わります。元変数と関数の引数の変数のメモリの場所が同一なためです。
練習
1. 関数を作ろう
文字列を大文字にする関数 to_upper を2種類作ってみよう。
// strを大文字にした結果をresultに格納する
void to_upper_1(char str[100], char result[100]);
// strを直接書き換える
void to_upper_2(char str[100]);
ポイント
どちらも参照渡しの特性を利用しています。関数内の変更が呼び出し元に影響します。
厳密に考えると難しくなるため、引数はchar配列で定義したアルファベット小文字の文字列としましょう。
解答例
#include <stdio.h>
void to_upper_1(char str[100], char result[100]);
void to_upper_2(char str[100]);
int main(void) {
char str1[100] = "apple";
char str2[100] = "pineapple";
char result[100];
// to_upper_1
printf("str1 : %s\n", str1);
to_upper_1(str1, result);
printf("result1: %s\n", result);
printf("str1 : %s\n\n", str1);
// to_upper_2
printf("str2 : %s\n", str2);
to_upper_2(str2);
printf("result2: %s\n", str2);
printf("str2 : %s", str2);
return 0;
}
void to_upper_1(char str[100], char result[100]) {
int i;
for (i = 0; str[i] != '\0'; i++) {
result[i] = str[i] - 'a' + 'A';
}
return;
}
void to_upper_2(char str[100]) {
int i;
for (i = 0; str[i] != '\0'; i++) {
str[i] = str[i] - 'a' + 'A';
}
return;
}
str1 : apple
result1: APPLE
str1 : apple
str2 : pineapple
result2: PINEAPPLE
str2 : PINEAPPLE
to_upper_1 は str1 がそのままになっていますが、 to_upper_2 は str2 が変更されています。
to_upper_2 のような関数を 破壊的操作 をする関数といいます。
破壊的操作 をすることは悪いことではないです。時と場合に応じて使い分けすることが重要です。
おわりに
関数は同じような操作をまとめる、といったことにも使われます。
関数化はいくつかの処理から共通点を見つけるよい練習になりますね。
参考文献
↓↓↓ はじめてプログラミングを学んだときに読んだ本です ↓↓↓
詳細(プログラミング入門 C言語)|プログラミング|情報|実教出版