ホーム
C/C++チュートリアル
この章ではCの関数、型、変数、ポインターを説明します。
-
この「C++入門」は、
GTKのコードを読むために必要な最小限な説明をしていこうと思っています。
-
他の言語などの予備知識は必要としていません。
関数
-
前章では、main( )とprintf( )という関数が出てきました。
-
Cでは、このように関数というものを組み合わせてプログラミングしていきます。
-
関数とは何かの処理をするものです。
-
何かの処理をするものを組み合わせてプログラミングする手法を
「手続き型プログラミング(Procedural programming)」と言います。
-
関数はプログラミング言語によっては、
プロシージャ、ルーチン、サブルーチン、メソッドなどとも呼ばれます。
-
main( )関数とprintf( )関数は C によって最初から用意されていた関数でした。
このように処理系に最初から用意されている関数のことを
標準関数や標準ライブラリ関数と言います。
-
関数は自分で作ることもできます。自分で作る関数のことを「ユーザー定義関数」
などと呼びます。
-
次のコードをお好きなエディタで記述して function.c
という名前で保存してください。
-
Windows で MSYS2 を使う場合は、
C:¥msys64¥home¥ユーザー名
に保存するのが便利です。
function.c
#include <stdio.h>
void func()
{
printf("I am a function.\n");
}
int main()
{
func();
return 0;
}
実行結果
I am function.
コード説明
-
関数は、次のように記述して定義します。
戻り値の型 関数名( 引数 )
{
処理;
}
-
戻り値(返り値)がない場合は、void と記述します。
-
関数名は任意に決められますが、
後述する「識別子の命名規則」に従わなければなりません
-
関数名に続く( と )の間に引数(ひきすう)を記述します。
引数とは関数に渡すデータです。引数がない場合は、( )と記述するか、( void )と記述します。
引数がなくても( )を省略することはできません。
-
関数が行う処理は、{ と }の間に記述しなければなりません。
-
関数を実行する場合は、関数名( 引数 ); と記述します。
func();
-
引数がない場合も、( )を省略することはできません。
-
何かの命令(式、処理)の最後にはセミコロン ; を付けなければなりません。
-
関数を実行することを「関数を呼び出す(コールする)」と言います。
-
関数は、呼び出される前に定義されていなければなりません
関数の前方宣言
-
関数は呼び出される前に定義されていなければなりませんが、
複数の関数が、お互いを呼び出すようなプログラムを記述する場合には、
難しくなります。そこで「関数の前方宣言」という方法を使います。
-
前方宣言を使うと function.c は次のようになります。
function.c
#include <stdio.h>
void func(); // 関数の前方宣言
int main()
{
func();
return 0;
}
void func() // 関数の実際の定義は、関数の呼び出しよりも後で記述しています
{
printf("I am a function.\n");
}
-
関数の前方宣言は関数の実装部分以外( { }以外の部分 )にセミコロン
; をつけたものです。前方宣言はプロトタイプ(Prototype)とも呼ばれます。
-
プロトタイプ(関数の前方宣言)は、実際のプログラミングでは良く使われます。
識別子の命名規則
ユーザー定義関数の名前のようにプログラマーが自由につけられる名前のことを
識別子(Identifier)と言います。識別子には次の命名規則があります。
-
使える文字は、アルファベットの大文字と小文字、アラビア数字、
アンダースコア( _ )のみ
-
アルファベットの大文字と小文字は区別されます。
num と Num と NUM は違う識別子です。
-
識別子を数字で始めることはできません。数値と誤認されると覚えると良いでしょう。
-
キーワードと同じ名前の識別子は使えません
引数
-
関数にはデータ(値)を渡すことができます。
この関数に渡すデータのことを引数(ひきすう)と言います。
引数を受けとった関数は、その引数を使っていろいろな処理ができます。
parameter.c
#include <stdio.h>
int sum(int a, int b);
int main()
{
printf("%d\n", sum(5, 5));
return 0;
}
int sum(int a, int b)
{
return a + b;
}
実行結果
10
コード説明
int sum(int a, int b);
-
引数は、型(データの種類)と引数名を指定します。
複数の引数がある場合は、カンマ , で区切ります。
-
引数名はその、その引数を受け取った関数の中で、その値を表すものとして使えます。
-
関数定義や関数前方宣言の中で記述されている引数を「仮引数(Parameter)」
と言います。
printf("%d\n", sum(5, 5));
-
%d は変換指定子と呼ばれるものです。そこに10進数の整数が入ることを
指定しています。十進数(Decimal)の頭文字と覚えれば良いでしょう。
-
変換指定子の場所に実際に入る値は、カンマ , の後に指定します。
ここでは sum( )関数の戻り値を指定しています。
-
関数を呼びだす側では、引数に実際の値を、仮引数の順序に合うように、カンマ ,
で区切って指定します。
-
関数を呼び出す側で、関数に渡たす引数のことを「実引数(Argument)」と言います。
return a + b;
-
ここで使われている + のような記号ことを「演算子(Operator)」と言います。
+ 演算子の意味は数学と同じで、足すという意味になります。
型
-
Cでは、データ(Data、値)の型(Type)が決まっています。
すでに整数と文字列が出てきました。整数は整数型、文字列は文字列型になります。
関数の戻り値にも、引数にも型を明記します。
-
Cは、コンパイルする時にソースコードにエラーがないかを調べます。
このチェックは色々な方面で行われますが、そのチェックの一つとして、
データ型を使って間違ったデータが使われていないかを調べています。
type.c
#include <stdio.h>
int main()
{
printf("%s\n", "I am a string.");
// "I am a string."は文字列型です
printf("%d\n", 100);
// 100は整数値型です
return 0;
}
実行結果
I am a string.
100
コード説明
-
%s も変換指定子です。そこに文字列が入ることを指定しています。文字列(String)の
頭文字と覚えれば良いでしょう。
-
"I am a string." と " "で囲むと文字列になります。
変数
-
C では、変数(へんすう)という、データを格納できる容れ物を作ることができます。
-
変数を定義する時には、どの型のデータの容れ物であるかを指定しなければいけません。
-
変数の中のデータは、
指定された型のデータであれば何度でも上書きすることができます。
しかし違う型のデータで上書きするようなコードを書くと、
コンパイルする時に、どこそこにエラーがあるとコンパイラから指摘され、
コンパイルは完了しません。
-
エラーとなったコードは、エラーを訂正しなければコンパイルを
完了することはできません。
variable.c
#include <stdio.h>
int main()
{
char * name;
name = "Jane";
printf("I am %s.\n", name);
name = "Mary";
printf("I am %s.\n", name);
int number;
number = 100;
printf("Number is %d.\n", number);
number = 200;
printf("Number is %d.\n", number);
return 0;
}
実行結果
I am Jane.
I am Mary.
Number is 100.
Number is 200.
コード説明
char * name;
-
文字列型の name という変数を定義しています。
-
変数は「
型 変数名;
」と記述して定義します。
C で文字列型は char *
です。
name = "Jane";
-
変数に値(データ)を入れる場合は、
=
(イコール)という記号を使います。
=
を使うと、左辺の変数に、
右辺の値を上書きするという意味になります。
このデータの上書きのことを C では、「代入(Assign、割り当て)」と呼びます。
-
なお、C で、イコール(同じ)という意味を表す記号は
==
となります。
-
また、文字列を表すデータはダブルクォーテーション
"
で囲む決まりになっています。
printf("I am %s.\n", name);
-
変数はコード中で上記のように使うことができます。変数の部分は
その変数に入れている値に置き換わります。
int number;
整数型の型指定には、int
を使います。
ポインター
-
C には、ポインター(Pointer)という変数もあります。
-
C では、変数を定義するとメモリーに値を入れる場所が確保されます。
-
ポインターは、そのメモリーの場所の先頭位置を 0 から 0xfffffff...
(コンピューターに備わっているメモリー次第)
という16進数の整数値として入れる変数です。
-
このメモリーの場所のことをアドレス(Address)とも言います。
pointer.c
#include <stdio.h>
int main()
{
int num = 100;
int *ptr = #
printf("%d\n", num);
printf("%p\n", ptr);
printf("%d\n", *ptr);
printf("%p\n", &num);
num = 200;
printf("%d\n", num);
printf("%d\n", *ptr);
printf("%p\n", ptr);
printf("%p\n", &num);
return 0;
}
実行結果
100
0x7ffee7682b58
100
0x7ffee7682b58
200
200
0x7ffee7682b58
0x7ffee7682b58
コード説明
int num = 100;
int *ptr = #
-
ポインターを定義するには、変数名の前に * (アスタリスク)をつけます。
-
普通の変数のアドレスを取得するには、その変数の前に & (アンバサンド)
をつけます。
-
ある型のポインターは、そのポインターと同じ型の変数のアドレスしか代入できません。
printf("%p\n", ptr);
printf("%d\n", *ptr);
-
ポインターの値(16進数のアドレス)を取得するには、
ポインター名をそのまま記述します。その場合の変換指定子は %p です。
ポインター(Pointer)略だと思えば覚えやすいでしょう。
-
ポインターが指し示すアドレスに入っている値を取得するには、
ポインター名の前に * をつけます。
num = 200;
・・・
printf("%p\n", ptr);
-
num 変数の値を変更しても、そのポインターが表すアドレスは変わりません。
値渡しと参照渡し
-
引数の渡し方には、「値渡し(by value)」と「参照渡し(by reference)」
があります。
byreference.cpp
#include <stdio.h>
int plus100(int a);
void plus200(int *a);
int main()
{
int a = 100;
a = plus100(a);
printf("%d\n", a);
plus200(&a);
printf("%d\n", a);
return 0;
}
int plus100(int a)
{
return a + 100;
}
void plus200(int *a)
{
*a = *a + 200;
}
実行結果
200
400
コード説明
-
plus100( )関数では、aの値のコピーが引数として渡されます。
aが直接渡されているわけではありません。
したがってplus100( )関数の中で、引数として渡されたデータを加工しても、
main 関数の中の a の値は変わりません。
このような引数の渡し方を値渡しと言います。
-
「引数」の項で、引数を受け取った関数は、引数の名前をその値として使える
と説明ましたが、正確には
受け取った引数を変数として使えるということになります。
しかし、引数を値渡しで受け取った場合は、引数を受け取った関数側で、
その値をいくら加工しても、関数を呼び出した側の値は変わりません。
-
一方、plus200( )関数では、aのアドレス(ポインター)が引数として渡されています。
plus200( )関数の中では、ポインターをたどって main( )関数の中の a
の値を直接加工することができます。このような引数の渡し方を参照渡しと言います。
ポインターが必要になる理由
-
ポインターが必要になる大きな理由の一つとして
上記の引数の渡し方の違いにあります。
値渡しで渡されるのは元のデータのコピーです。
元のデータが動画データのような大きなサイズだった場合は
それと同じのメモリー容量が新たに必要となります。
-
一方、ポインターを使った参照渡しでは、新たに必要となるメモリーサイズは
ポインター(アドレスを格納する変数)に必要な 8 バイトだけです。
また、直接データが加工ができることも大きな点です。
そして、関数がデータを戻り値として返す場合も、
その戻り値もメモリーに新たにコピーされ、
新たなメモリー領域が消費されます。
main( )関数の引数
-
main( )関数も引数を受け取ることができます。
-
main( )関数が受け取ることができる引数は、決まっています。
-
main( )関数が受け取ることができる引数は「コマンドライン引数」呼び、
コマンドラインからしか引数にデータを渡すことができません。
mainarg.c
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("%d\n", argc);
printf("%s\n", argv[0]);
return 0;
}
実行結果
1
./a.out
コード説明
-
int main(int argc, char *argv[])
- 第1引数に int 型の argc という仮引数を指定します
- 第2引数に char *型(文字列型)の argv[ ] という仮引数を指定します。
[ ]が付いた変数は「配列」と呼ばれるものです。
配列は同じ型のデータを複数入れられる変数です。
配列については後の章で詳しく説明します
-
printf("%d\n", argc);
プログラムをターミナルから起動した場合には、そのコマンドに使われた文字列の個数が
argc に渡されます。今回は、その結果が 1 と表示されます。
プログラムを起動する時に ./a.out abc abc と入力すると、
その結果は 3 と表示されます。
-
printf("%s\n", argv[0]);
argv[ ]には、複数の文字列が入っています。[0]は、
0番目の文字列を指定するという意味になります。配列の中での順番は、0、1、2、3
というふうに 0 から始まっています。
その結果としてターミナルに ./a.out と表示されます。ターミナルに入力したプログラム名が
argv[ ]配列の最初(0 番目)に渡されるからです。
-
なお argc は Argument countの略、argv は Argument vectorの略だと思えば
覚えやすかもしれません(Vectorが何を意味するか分かりませんが w)。
-
プログラムを起動する時に、./a.out aaa bbb ccc
などとした場合に、
argv から2番目、3番目の値を取り出すコードは次のようになります。
argvfor.c
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("%d\n", argc);
for (int i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
このコードで使われいる for については「条件文」の章で説明します。
実行と実行結果
// コンパイル
gcc argvfor.c
// 実行
./a.out aaa bbb ccc
// 実行結果
4
./a.out
aaa
bbb
ccc
Posted: Dec. 22, 2019
Update: Dec. 23, 2019