TPY ENGINEERING VLOG

TPY ENGINEERING VLOG(C,JAVA)

*C言語* 〜41時限目〜 ビットフィールドを使う・・・

〜目次〜

 

 

 

ビットフィールドを使う

構造体のサイズに影響を与える、

ビットフィールド(bit field)

というメンバについて学んでいきます。

構造体のメンバの中には、

取りうる値の範囲が狭くて済むものがあります。

以下のような構造体の宣言、

小さなサイズのメンバを宣言することができます。

typedef struct Car1{
  int num;

  double gas;

  unsigned int tire : 3;   (3ビットのメンバ)

  unsigned int roof : 1;   (1ビットのメンバ)

  unsigned int color : 4;   (4ビットのメンバ)

}Car1;

3つのメンバの最後には「:O」という記述があります。

これは、

「指定したメンバをOビットにする」

という意味を持っています。

このように、

ビットを指定したメンバのことを、

「ビットフィールド」と呼びます。

例えばtireは3ビットなので、8種類の値を格納できます。

このように取りうる値が少ないメンバは、

ビットフィールドとしておくと便利です。

ビット数を指定しておくと、

構造体全体のサイズを小さくし、

メモリを節約できる可能性があるからです。

実際にビットフィールドを使ってコードを入力します。

sample94.c

f:id:YuyaTerayama:20180914141202p:plainーsample94の実行画面ー

ビットフィールドを使った構造体のサイズは24バイトです。

ビットフィールドを使わない構造体のサイズは32バイトです。

sample94では、2種類の構造体を宣言しています。

Car1はビットフィールドを使った構造体型です。

Car2はビットフィールドを使わない構造体型です。

sizeof演算子と使って、2つの型のサイズを調べています。

実行画面をみてわかる通り、

メンバをビットフィールドとしておくことで、

構造体全体のサイズを小さくすることができる可能性があります。

構造体のサイズは、各メンバのサイズを足し合わせたものになるとは限りません。

なので、

ビットフィールドを使っても、

常に全体のサイズを小さくできる訳ではないのです。

使っている実行環境によって結果は異なります。

 

引数として構造体を使う

構造体を関数の中で使う方法について見ていきます。

構造体は、関数の引数として利用することができます。

sample95.c

f:id:YuyaTerayama:20180914142321p:plainーsample95の実行画面ー

ナンバーを入力してください。

7777

ガソリン量を入力してください。

12.3

車のナンバーは7777:ガソリン量は12.300000

引数は原則として値渡しで関数内に渡されます。

構造体を引数とした場合も、「値」が渡されます。

これは、

実引数の構造体のメンバの値がそれぞれコピーされて、

関数の本体に渡される

ということを意味しています。

つまり、

ここでは構造体のメンバであるnumとgasの値が、

コピーされて関数に渡されるようになっているのです。

 

構造体へのポインタを引数に使う

メンバがたくさんある構造体を引数として使う場合には、

注意が必要になります。

関数を呼び出すたびに、

たくさんのメンバがコピーされるため、

関数の呼び出しに時間がかかってしまうことがあります。

このため、

大きな構造体を関数の引数として利用する場合、

構造体ではなく、

構造体へのポインタを引数として使う

ことがあります。

つまり、

構造体型の変数のアドレスを使うようにするのです。

構造体へのポインタを関数の引数としておくと、

アドレスを渡すだけで関数が呼び出されます。

構造体へのポインタの方が構造体よりもサイズが小さいということは、

すでに学びました。

なので、

構造体へのポインタを使った方が、

処理速度が向上する場合があります。

尚、ポインタで渡した場合は引数を実質的に参照渡しとすることになるので、

渡した構造体のメンバの値を関数内で変更することができます。

ただし、このような関数内では、

構造体へのポインタを使って各メンバにアクセスする、

処理を記述しなくてはいけません。

ポインタからメンバにアクセスする場合には、

アロー演算子(ー>)

を用いると便利です。

実際に引数として構造体へのポインタを渡す、

コードを入力して見ます。

sample96.c

f:id:YuyaTerayama:20180914143555p:plainーsample96の実行画面ー

ナンバーを入力してください。

7777

ガソリン量を入力してください。

12.3

車のナンバーは7777:ガソリン量は12.300000

この関数内ではポインタが渡されるので、

メンバにアクセスする場合には、

ドット演算子ではなく、アロー演算子を使います。

 

〜END〜

*C言語* 〜40時限目〜 構造体を初期化する・・・

〜目次〜

 

 

 

構造体を初期化する

構造体を宣言した後に、

ドット演算子を使ってメンバに値を代入する方法について、

学びました。

この宣言と値の代入を構造体でもまとめることができます。

そのことを、

構造体の初期化

と呼びます。

変数を宣言する際に、{ }で囲んで値を記述すると、

カンマで区切った順番にメンバに値が格納されます。

構造体にあらかじめ初期値を与える際に便利なので、

覚えてください。

39時限目のコードを初期化を使って書き換えてみます。

sample91.c

f:id:YuyaTerayama:20180913214311p:plainーsample91の実行画面ー

車のナンバーは7777:ガソリン量は12.300000

 

構造体に代入する

これまでは各メンバに対して、

代入演算子を使って値を格納してきました。

構造体変数そのものに対して代入演算子を使うとどうなるのか、

以下のコードを入力して確認してみます。

sample92.c

f:id:YuyaTerayama:20180913215259p:plainーsample92の実行画面ー

car1の車のナンバーは7777:ガソリン量は12.300000

car2の車のナンバーは6666:ガソリン量は32.100000

car1をcar2に代入しました。

car2の車のナンバーは7777:ガソリン量は12.300000

sample92では、

car1とcar2という構造体を宣言しています。

そして、代入を行なっています。

この代入は、

car2のメンバにcar1のメンバを1つずつコピーして値を格納する

ということを意味しています。

構造体で同士でこのような代入をすることができるので、

覚えておいてください。

 

構造体型のサイズを知る

構造体も新しい型です。

そこで、構造体型のサイズについて調べてみます。

構造体型のサイズを調べるには、以前使っていた

sizeof演算子を使います。

以下のコードを入力してみてください。

sample93.c

f:id:YuyaTerayama:20180914023218p:plainーsample93の実行画面ー

int型サイズは4バイトです。

double型サイズは8バイトです。

構造体struct Car型のサイズは16バイトです。

構造体struct Car型へのポインタのサイズは8バイトです。

構造体struct Car型は、int型とdouble型のメンバを持っている。

構造体型のサイズは、各メンバのサイズを足し合わせたものと同じか、

それよりも大きくなります。

従って、たくさんのメンバを持つ構造体や大きなサイズのメンバを持つ

構造体は大きなサイズになります。

ただし、

実行画面を見てわかる通り、

構造体のサイズは2つの型をぴったり足し合わせたサイズに、

なるとは限りません。

実行環境によってサイズは少し変わってきます。

一般的に、

多くのメンバを持つ構造体へのポインタは、

構造体そのもののサイズよりも小さくなっています。

 

次回はビットフィールドについて学んでいきます。

 

〜END〜

*C言語* 〜39時限目〜 構造体の基本・・・

〜目次〜

 

 

構造体型の仕組みについて

新しく決めることができる型として、

構造体型(structure type)

というものがあります。

この型は、

異なる型の値をまとめて新しい型にする

という機能を持っています。

車で例えると、

ナンバー(int型)、ガソリン量(double型)などの、

異なる型の値をまとめて、

「車」をあらわす型にすることができるのです。

構造体型がどのような型であるか取り決めることを、

構造体型の宣言

と呼びます。

構造体型を宣言するには、

struct

というキーワードを使います。

構造体型はブロックの中に、

変数などをまとめて記述したものです。

例で出した車の場合は以下のように宣言できます。

struct Car{    (車をあらわす構造体型Car)

  int num;      (ナンバーを格納する)

  double gas;    (ガソリン量を格納する)

}

 

構造体変数を宣言する

構造体型を宣言することで、

新しい型としてコードの中で使うことができます。

構造体変数の宣言は以下の通りです。

構造体型名 構造体変数名;

struct Car型の変数も、通常の変数と同じように宣言します。

以下のように宣言することで、

struct Car型の変数となります。

struct Car car1;

 

メンバにアクセスする

構造体型の変数(構造体)を宣言すると、

その中のnumやgasに、

実際のナンバーやガソリン量を格納することができるようになります。

このnumやgasといった変数のことを、

メンバ(member)

と呼びます。

構造体のメンバを利用するためには、

ドット演算子(.)

というものを使います。

そして、メンバを利用することを、

メンバにアクセスする

と呼びます。

構造体のメンバへのアクセスは以下の通りです。

構造体変数名.メンバ

実際にコードを入力して、

どのようにして使うのか確認してみます。

sample88.c

f:id:YuyaTerayama:20180913211355p:plainーsample88の実行画面ー

車のナンバーは7777: ガソリン量は12.300000

このようにすることで、

車を扱うコードを書くことができました。

尚、メンバの値をキーボードから入力できると、

より柔軟なプログラムにすることができます。

実際にコードを入力してみます。

sample89.c

f:id:YuyaTerayama:20180913211754p:plainーsample89の実行画面ー

ナンバーを入力してください。

7777

ガソリン量を入力してください。

12.3

車のナンバーは7777:ガソリン量は12.300000

これまでの変数と同じで、

&をつけるということに注意をしてください。

尚、&は構造体名の前につけます。

 

typedefで名前を割り当てる

構造体型の型名を短縮するために、

typedef

というキーワードを使う方法があります。

//構造体型 struct Carの宣言

typedef struct Car{

  int num;

  double gas;

} Car;

こうすることで、「struct Car」という型名に対して、

「Car」という新しい型名をつけることができます。

このため、あとで構造体型の変数を宣言する時には、

以下のように短い名前を使って宣言することができます。

Car car1;

typedefを使うことは便利になるので、

実際にコードの中で使ってみます。

sample90.c

f:id:YuyaTerayama:20180913212556p:plainーsample90の実行画面ー

ナンバーを入力してください。

7777

ガソリン量を入力してください。

12.3

車のナンバーは7777:ガソリン量は12.300000

尚、typedefでつける名前は、

Carでなく別の名前でも問題はありません。

 

次回は構造体の初期化から学んでいきます。

 

〜END〜

*C言語* 〜38時限目〜 関数ポインタの仕組みについて・・・

〜目次〜

  

 

 

関数ポインタの仕組みについて

さらに高度なポインタの使い方について学んでいきます。

関数の処理内容は、

C言語のプログラムをコンパイルした時に、

機械語のコードに翻訳されます。

プログラムが実行される時には、

この機械語のコードがメモリに格納されることになっています。

コンピュータはメモリ上にある、

機械語のコードを1つずつ取り出して、

処理をするようになっているのです。

このため、

関数はメモリ上のアドレスというものを持っています。

関数のアドレスとは、

関数の処理の先頭部分のメモリの位置をあらわしたものです。

この「関数アドレス」を、ポインタに格納して使うことができます。

このようなポインタのことを、

関数ポインタ(function pointer)

と呼びます。

 

関数ポインタを宣言する

関数ポインタと言っても、

これまでのポインタと使い方は同じです。

関数ポインタは以下のようにして宣言します。

戻り値の型(*関数ポインタ)(引数リスト);

例で、

int (*pM) (int x, int y);

のような関数ポインタを宣言したとします。

「pM」が、指定した形式の関数のアドレスを、

格納することができるポインタです。

変わった形式での宣言に見えるが、

いままでのポインタの宣言と同じ方式です。

関数のポインタは「*ポインタ」の部分を( )で囲むので、

注意をしてください。

囲まないと、

演算子の優先順位の関係で、

pM(int x, int y)という関数を宣言したことになります。

 

関数ポインタにアドレスを格納する

ポインタにアドレスを代入することで、

はじめて、メモリ上の変数の位置をさし示すことができるようになりました。

意味のないアドレスが格納されている場合には、

ポインタを使うことはできません。

関数ポインタも関数のアドレスを格納する必要があるのです。

以下のようにして、関数のアドレスを格納します。

関数のアドレスは、「関数名」であらわします。

&はつける必要がありません。

関数ポインタ = 関数名;

このようにして、

関数の処理内容が格納されている、

メモリの先頭位置をさし示すことになります。

変数をさし示すポインタに、

間接参照演算子*を使うことで、

その変数の内容を間接的に扱うことができました。

関数の場合も同じです。

関数ポインタに間接参照演算子を使うことで、

関数を間接的に扱うことができます。

すなわち、関数を呼び出すことができるようになります。

 

関数ポインタを利用する。

実際に関数ポインタを使った、

プログラムを入力してみます。

sample86.c

f:id:YuyaTerayama:20180913131137p:plainーsample86の実行画面ー

1番目の数値を入力してください。

7

2番目の数値を入力してください。

10

最大値は10です。

sample86は、

関数ポインタを宣言し、関数のアドレスを代入しています。

さらにポインタを使って関数を呼び出しています。

実行画面をみてわかる通り、

関数はちゃんと呼び出されています。

関数ポインタを使うことで、

関数を間接的に呼び出すことができるのです。

 

関数ポインタを応用する

単純に関数を呼び出すのであれば、

関数ポインタを使わないで、関数を直接呼び出す方が簡単です。

関数ポインタを使って便利な時は、

その配列を作成したときに、

便利なプログラムが書けるようになります。

実際に以下のコードを入力してみて見てください。

sample87.c

f:id:YuyaTerayama:20180913202112p:plainーsample87の実行画面ー

どこに行きますか?(0:アメリカ 1:フランス 2:イギリス)

1

フランスです。

sample87では要素数が3の配列を宣言しています。

各関数のアドレスを代入することで、

配列の各要素が3種類の関数をさし示すようにしています。

ユーザーが入力した数値によって、

どの関数を呼び出すか決めています。

関数ポインタを使わずに、

同じような処理をすることもできるが、

コードが複雑になります。

同種類の関数を多く宣言する際には、

関数ポインタを使うことで、便利に書くことができます。

 

〜END〜

*C言語* 〜37時限目〜 文字列を比較する・・・

〜目次〜

 

 

 

文字列を比較する

文字列を比較することができる、

strcmp( )関数

という関数を使ったコードを入力してみます。

この関数は、引数として2つの文字列が渡されると、

文字列1と文字列2を比較して、

一致していた場合は0を戻り値として返します。

実際に以下のコードで確認してみます。

sample84.c

f:id:YuyaTerayama:20180912021725p:plainーsample84の実行画面その1ー

1番目の文字列を入力してください。

Hello

2番目の文字列を入力してください。

Hello

2つの文字列は同じです。

ーsample84の実行画面その2ー

1番目の文字列を入力してください。

Hello

2番目の文字列を入力してください。

hello

2つの文字列は異なります。

一致しているか、していないかを調べることができています。

if文と組み合わせることで、

比較をして結果を出力するプログラムを作成することができました。

 

文字列の長さを実行時に決める

いままでのプログラムでは、

文字列を格納する配列の大きさは、

プログラムを実行する前に決まっていました。

しかし、

いつでも配列を大きく取っておくと、

文字列が短い場合などでは、

メモリの無駄使いになってしまいます。

このようなときに、

プログラムの実行時に必要なだけ、

メモリを確保できると便利です。

このとき使うのが、

malloc( )関数

free( )関数

という標準ライブラリ関数です。

malloc( )関数は、

プログラムを実行したときに必要なサイズのメモリを確保し、

その場所のアドレスを返します。

確保したメモリは、

使い終わったらfree( )関数で解放します。

この2つの関数は、ヘッダファイルの

stdlib.h

をインクルードする必要があります。

実際に2つの関数を使ってコードを入力してみます。

sample85.c

f:id:YuyaTerayama:20180912023147p:plain

ーsample85の実行画面ー

何文字のaを用意しますか?

10

aaaaaaaaaaを用意しました。

12行目で確保されたメモリの場所をあらわすポインタが得られます。

(num+1)で指定された数+1個分の、

char型のサイズ分のメモリを確保します。

このコードでは、文字列を扱うために、

「char型サイズ×(実行時に指定された文字列の長さ+1)」だけの、

メモリをmalloc( )関数によって確保しています。

最後に1を足しているのは、¥0を格納するためです。

また、実行時にメモリが確保できなかった場合に備えて、

エラー処理を行なっています。

メモリが確保できた場合には、

その場所へのポインタを使って「aaaa....」という、

文字列を格納し、出力するようにしています。

このようにすることで、

文字列に必要なだけのメモリを使うようにすることができます。

ただし、

このメモリは使い終わったら必ず、

free( )関数を使ってメモリの解放をする必要があります。

解放するコードを忘れてしまうと、

プログラムを実行するたびに、

使えるメモリが減ってしまいます。

 

動的なメモリの確保

変数や配列の寿命について思い出してください。

関数の開始・終了時に確保・解放される静的変数を学びました。

今回出てきた、

malloc( )関数、free( )関数は、

このようなタイミングをプログラムの処理が行われる際に、

行うものです。

この方法では、上記のようにポインタを使って値を扱う必要があります。

このメモリの確保の仕方は

動的なメモリ確保

と呼びます。

このメモリは、ヒープ領域と呼ばれる場所に確保されます。

 

次回は関数ポインタについて学んでいきます。

 

〜END〜

*C言語* 〜36時限目〜 文字列配列の配列・・・・

〜目次〜

 

 

文字列配列の配列

文字列をさらに高度に使っていきます。

プログラムの中では、

複数の文字列をまとめて配列で扱えると、

便利になる場合があります。

文字列自体がchar型の配列であることから、

二次元の配列として扱うことができます。

実際に以下のコードを入力してみてみます。

sample79.c

f:id:YuyaTerayama:20180911205907p:plainーsample79の実行画面ー

文字列はHelloです。

文字列はThankyouです。

文字列はSorryです。

3つの文字列を配列としたものです。

C言語の多次元配列は、

内部的に配列の各要素が配列となった仕組みです。

つまり、

str[0]、str[1]、str[2]のそれぞれが、

素数20の配列となっているという、

イメージをすることができます。

 

文字列ポインタの配列

文字列はポインタでも扱えるということは、

すでに学びました。

ポインタを配列としたコードを作成することもできます。

実際に以下のコードを入力してみてください。

sample80.c

f:id:YuyaTerayama:20180911210339p:plainーsample80の実行画面ー

文字列はHelloです。

文字列はThankyouです。

文字列はSorryです。

実行画面をわかる通り、

ポインタで扱った文字列を配列にしたものですが、

結果は全く同じになります。

 

標準ライブラリ関数を使う

C言語の開発環境には、

標準ライブラリ関数(standard library)

が組み込まれているのはすでに学びました。

標準ライブラリ関数には、

文字列を扱う関数が多く含まれています。

これらの関数をコードの中で利用することで、

文字列の操作を簡単に行うことができます。

標準ライブラリの主な文字列操作関数について、

以下にまとめます。

f:id:YuyaTerayama:20180911211747p:plain

文字列操作関数を使うときには、

string.h

というファイルをインクルードします。

 

文字列の長さを調べる

strlen( )関数

を使って、文字列の長さを調べるコードを入力してみます。

sample81.c

f:id:YuyaTerayama:20180911212148p:plainーsample81の実行画面ー

文字列を入力してください。

Hello

文字列の長さは5です。

strlen( )関数は文字列の長さを調べる関数です。

長さをあらわす戻り値によって、

文字列の長さを出力するコードが作成できます。

尚、strlen( )関数で返される文字列の長さには、

¥0を含まない、文字だけの個数になります。

 

文字列を配列にコピーする

文字列を配列にコピーする関数を使ってみます。

文字列を配列で扱う場合には" "で初期化をすることができます。

しかし、

初期化以外の場所では、

文字を1文字ずつ配列に格納しなくてはいけないため、

面倒な作業になります。

そんなときに、

文字列を配列に一度にコピーできる、

標準ライブラリの、

strcpy( )関数

を使うことで便利になります。

実際にstrcpy( )関数を使ってみます。

sample82.c

f:id:YuyaTerayama:20180911212954p:plainーsample82の実行画面ー

配列str1はHelloです。

配列str2はThankyouです。

この関数を使うことで、

配列に文字列を簡単に格納できるので便利です。

 

文字列を連結する

文字列の末尾に文字列を追加するという処理を行う、

strcat( )関数

を使ってみます。

sample83.c

f:id:YuyaTerayama:20180911213645p:plain

ーsample83の実行画面ー

配列str1はHelloです。

配列str2はThankyouです。

連結するとHelloThankyouです。

strcat( )関数を使うことで、

末尾に文字列を追加することができました。

 

配列の大きさに注意をする

strcpy( )関数やstrcat( )関数を使う際には、

配列の大きさに十分注意してください。

関数の引数がさす配列の大きさが十分でないと、

これらの関数は配列の要素を超えて文字列をコピーしてしまいます。

配列の要素を超えて要素を扱うような、

操作を行なってはいけません。

コンパイルは問題なくできるのですが、

実行時に予期せぬエラーになります。

特にstrcap( )関数では末尾に追加するので、

超えてしまう場合が多いので注意してください。

 

次回は残っている、文字列の比較であるstrcmp( )関数から

学んでいきます。

 

〜END〜

*C言語* 〜35時限目〜 ポインタに[ ]演算子を使う・・・

〜目次〜

 

 

 

ポインタに[ ]演算子を使う

配列名を先頭要素へのポインタとして使う

ということができることを学びました。

これとは逆に、

ポインタに[ ]を使って配列のように表記する

ということもできます。

34時限目のavg( )関数の中のポインタpTを、

[ ]を使うことで、以下のようにあらわせます。

pT[2]

この[ ]のことを 、

添字演算子(subscript operator)

と呼びます。

このようにポインタに[ ]を使った表記は、

ポインタpTがさしている要素から数えて、2つ先の要素

をあらわすことになっています。

ポインタが配列をさしている時は、

ポインタに[ ]をつけて、それがさし示す要素をあらわすことが、

できるようになっているのです。

つまり、配列と同じ表記になるのです。

ただし、[ ]が正しく使えるには、

ポインタと配列が密接な関係を持つ時のみ

となっているので注意してください。

この関数では実引数として配列の先頭要素のアドレスが

渡されてくるので、

[ ]を使った表記ができるのです。

ほかのポインタに[ ]を使ってしまうと、

予期せぬエラーが起こる場合があります。

実際に以下のコードは[ ]を使って入力したものです。

sample77.c

f:id:YuyaTerayama:20180911202630p:plainーsample77の実行画面ー

5人分のテストの点数を入力してください。

99

70

77

13

56

5人分のテストの平均点は63.000000点です。

実行結果に変化はありません。

pT[i]という表記を使うことで、

ポインタpTがさす配列の要素を順番に出力しています。

つまり、この関数は前のavg( )関数と全く同じ処理を行うことができます。

このように、

ポインタと配列に関係がある場合は、

ポインタと配列を同じように扱うことができる

ということが確認できます。

 

文字列をポインタで使う

文字列を扱うコードは、

実際によく作成するコードになります。

なので、

配列やポインタを使って文字列を、

自由に使えるように勉強していきます。

文字列はchar型の配列であるということを、

思い出してください。

文字列はchar型へのポインタを使って扱うことができます。

以下のようにして扱えるのです。

char *str = "Hello";

char型へのポインタに代入する場合には、

文字列を配列で扱う場合とは異なり、

ポインタで扱う場合には、" "を使うことで、

メモリ上のどこか別の場所に文字列が格納され、

その場所をさすようにするということになります。

実際に以下のコードで確認します。

sample78.c

f:id:YuyaTerayama:20180911203435p:plainーsample78の実行画面ー

文字列はHelloです。

文字列を配列で扱う場合も、ポインタで扱う場合も、

同じように出力することができます。

 

配列とポインタの違いについて

文字列を配列で扱う場合とポインタで扱う場合には、

いくつか注意をすることがあります。

配列を使って文字列を格納した場合には、

以下のように" "を使って初期化したあと、

あらためて文字列全体を配列に代入することはできません。

char str[ ] = "Hello";

//str = "Goodbye";   (配列に代入することはできない)

これに対して、

文字列を" "を使ってポインタで扱うように格納した場合には、

さらに" " を使って扱う文字列を変更することができます。

char *str = "Hello";

str = "Goodbye";     (別の場所にある文字列をさすように変更できる)

ポインタの場合は、

どこか別の場所に格納されている文字列をさすので、

ポインタのさし示す場所を変更するだけで、

扱う文字列を変更することができます。

まとめると、

  • 配列・・・配列名に代入して文字列を変更することはできない
  • ポインタ・・・ポインタに代入してさし示す文字列を変更できる

また、文字列を入力する場合には、

配列を宣言して、文字列を格納する領域を確保しなくてはいけない。

ポインタは" "などを使いどこか別の場所に格納されている、

文字列をさし示すためのものなので、

ポインタによって入力する方法は使わないです。

文字列は配列とポインタの2通りで扱うことができるが、

2つの違いに気をつけてコードを作成する必要がある。

 

次回は文字列配列の配列から学んでいきます。

 

〜END〜

*C言語* 〜34時限目〜 ポインタ演算の仕組みについて・・・

〜目次〜

 

 

ポインタ演算の仕組みについて

C言語では、

ポインタが配列に密接な関係を持つとき、

ポインタに対して以下のような演算を行うことができます。

f:id:YuyaTerayama:20180911154526p:plain

ポインタの演算は、

四則演算などの計算とは異なります。

例えば+演算子では、

pがさしている「1つ次の要素」のアドレスを得る

という演算が行われます。

「アドレスの値に1を足す」という演算は行いません。

実際にポインタ演算を使ったコードを入力してみます。

sample74.c

f:id:YuyaTerayama:20180911154939p:plainーsample74の実行画面ー

test[0]の値は99です。

test[0]のアドレスは0x7ffeed9b07a0です

testの値は0x7ffeed9b07a0です。

test+1の値は0x7ffeed9b07a4です。

*(test+1)の値は70です。

sample74では、+演算子を使ってポインタの足し算を行なっています。

testに1を足した「test+1」を出力しています。

すると、

1つ次の要素である、配列の2番目の要素のアドレス

が出力されています。

*演算子を使うことで、

配列の2番目の要素の値も知ることができました。

 

配列名を使うときの注意

配列名は配列の先頭要素のアドレスを、

格納しているポインタと同じであると考えることができます。

ただし、

このポインタは通常のポインタと異なる部分があります。

それは、

配列名であらわされるポインタには、

ほかの変数のアドレスを代入することができない

ということです。

通常のポインタではほかの変数のアドレスを、

代入することができました。

しかし、

配列名は、その配列の先頭要素以外のアドレスを、

あらわすことはできません。

 

配列を引数として使う

まず配列を関数の引数として使うプログラムを作成します。

以下がコードになります。

sample75.c

f:id:YuyaTerayama:20180911160304p:plainーsample75の実行画面ー

5人分のテストの点数を入力してください。

99

70

77

13

56

5人分のテストの平均点は63.000000点です。

avg( )関数では、配列を引数として使っています。

仮引数にはt[ ]と記述し、

実引数として配列名testを記述して渡している、

ことに注目してください。

このように、

引数として配列を使うときには、

実引数として配列名を渡します。

配列を関数に渡す場合には、

先頭要素のアドレスを渡す方法を使います。

このため、

配列は参照渡しで渡されることになります。

関数内で配列に変更を加える処理をした場合には、

実引数の配列の値が直接変更されることになります。

 

ポインタを引数として使う

同じ関数をポインタのように記述することができます。

実際にコードを入力してみてみます。

sample76.c

f:id:YuyaTerayama:20180911161106p:plainーsample76の実行画面ー

5人分のテストの点数を入力してください。

99

70

77

13

56

5人分のテストの平均点は63.000000点です。

実行画面はsample75と全く同じになっています。

avg( )関数を利用している部分のコードについても、

sample75と全く同じです。

配列を受け取る関数は、

このようにポインタで表記した仮引数を

使うことがよくあります。

実引数として配列の先頭要素のアドレスを渡す際に、

仮引数としてポインタを記述できる。

 

〜END〜

*C言語* 〜33時限目〜 配列要素のアドレスを知る・・・

〜目次〜

 

 

 

配列要素のアドレスをし知る

C言語では、

ポインタと配列の密接な関係を利用した、

コードを作成する場合があります。

アドレス演算子&を使って、

配列の各要素のアドレスを調べていくことにします。

配列とはメモリの中に値を格納するための仕組みでした。

変数と同じように、

配列の各要素に、&演算子を使うことができます。

つまり、配列の要素に&演算子を使うことで、

その要素の値が格納されているアドレスを知ることができます。

例として以下のようにします。

&test[0]    (配列の先頭要素のアドレス)

&test[1]    (配列の2番目の要素のアドレス)

実際に以下のコードを入力してみてみます。

sample71.c

f:id:YuyaTerayama:20180911151831p:plainーsample71の実行画面ー

test[0]の値は99です。

test[0]のアドレスは0x7ffeeae937a0です

test[1]の値は70です。

test[1]のアドレスは0x7ffeeae937a4です。

変数と同じように、

それぞれの配列要素に&演算子をつけることで、

要素のアドレスが出力できています。

配列はメモリ上に連続して、

値を格納する箱が並んでいるイメージです。

 

配列名の仕組みについて

配列は、特別な書き方で配列要素のアドレスをあらわすことができます。

「配列名」を記述しただけで、

配列の先頭要素のアドレス

をあらわすことができるのです。

[ ]や添字をつけてはいけません。

&演算子も不必要です。

実際にコードを入力して確かめてみます。

sample72.c

f:id:YuyaTerayama:20180911152318p:plainーsample72の実行画面ー

test[0]の値は99です。

test[0]のアドレスは0x7ffee258e7a0です

testの値は0x7ffee258e7a0です。   (配列の先頭要素のアドレスになっている)

実行画面をみてわかる通りに、

「test」の値が先頭要素である、「test[0]」の値と同じになっています。

配列名だけで、配列の先頭要素のアドレスを知ることができる

ということを確認できました。

 

配列名から先頭要素の値を知る

配列名が先頭要素のアドレスをあらわしていることから、

配列名は、配列の先頭要素のアドレスを格納している

ポインタと同じはたらきをもつ

ということが言えます。

配列名は、配列の先頭要素をさすポインタのように、

使うことができるのです。

なので、

ポインタに間接参照演算子*を使うことで、

それがさし示している変数の値を知ることができました。

配列名testに*をつけた場合にも、

同じように配列の先頭要素であるtest[0]の値を、

あらわすことができます。

実際に以下のコードを入力してみてみます。

sample73.c

f:id:YuyaTerayama:20180911152849p:plainーsample73の実行画面ー

test[0]の値は99です。

test[0]のアドレスは0x7ffee97997a0です

testの値は0x7ffee97997a0です。

つまり*testの値は99です。

このように、

配列名は先頭要素のアドレスを格納したポインタと、

同じ扱いができるようになっています。

 

次回はポインタ演算の仕組みについてから、

学んでいきます。

 

〜END〜

*C言語* 〜32時限目〜 実引数を変更したくない場合・・・

〜目次〜

 

 

 

実引数を変更したくない場合

ポインタを使っても、

必ずしも実引数を変更する必要はありません。

関数の内容によっては、

ポインタを使っていても、

実引数を変更したくない場合もあります。

このようなとき、

関数内で実引数の値を変更しないことをはっきりと示すために、

仮引数に

const

という指定をつけることができます。

以下のようにしてconstをつけます。

void func(const int *pX)

constを使った場合、

関数内で引数の変更をする処理を記述すると、

エラーになります。

f:id:YuyaTerayama:20180911132514p:plain

上のfunc( )関数内では、

引数を変更する処理を行うことはできません。

constは、指定しなくてもかまいません。

しかし、指定をすることで実引数を変更しないことも、

はっきりと示すことができ、

誤りのおきにくいコードを記述することができます。

 

まとめ

  • アドレスは、メモリ上の位置を直接あらわします。
  • ポインタは、特定のアドレスを格納する変数です。
  • アドレス演算子&を使うと、変数などのアドレスを知ることができます。
  • ポインタ間接参照演算子*を使うと、ポインタがさしている変数の値を得ることができます。
  • 引数は原則として値渡しで関数に渡されます。
  • 関数の仮引数にポインタを使うと、呼び出し元の実引数を変更することができます。
  • 仮引数にconstを指定すると、実引数を変更することができなくなります。

ポインタを使うと、メモリ上の位置を直接あらわすことができます。

ポインタを使って、関数の引数を参照渡しとする方法を学びました。

ポインタの概念はややこしいが、

焦らずにじっくりとひとつずつまとめで、

書いたものについて復習してください。

 

今回は短いですが、

ここまでになります。

 

〜END〜

*C言語* 〜31時限目〜 動作しない関数・・・

〜目次〜

 

 

動作しない関数

以下のswap( )という名前の関数を定義してから、

今回の学習をはじめていきます。

f:id:YuyaTerayama:20180911123205p:plainswap( )関数は、

引数xとyを交換する処理を行う関数として作成しました。

この関数内では、次のような手順で変数xとyの値を交換していきます。

f:id:YuyaTerayama:20180911123539p:plain

変数tmpを介してxとyを交換しています。

これでxの値とyの値が交換されるはずです。

ところが、

この関数を呼び出してみると、

実際には思った通りの処理が行われません。

swap( )関数を実際に使ってみます。

以下のコードを入力してみてください。

sample69.c

f:id:YuyaTerayama:20180911124210p:plainーsample69の実行画面ー

変数num1の値は7です。

変数num2の値は10です。

変数num1とnum2の値を交換します。

変数num1の値は7です。

変数num2の値は10です。

変数num1とnum2を交換するため、

swap( )関数を呼び出してnum1とnum2を、

実引数として渡してみました。

しかし、実行結果をみてわかる通り、

変数num1とnum2の値は交換されていません。

何故なのか見ていきます。

 

関数に引数を渡す方法

関数に実引数を渡す際には、

実引数の「値」だけが関数内に渡される

ということになっています。

このような引数の渡し方を、

値渡し(pass by value

と呼びます。

関数内で仮引数xとyの値を交換する処理を行っても、

これは変数num1とnum2の値を「コピー」した7と10を、

交換しているにすぎません。

つまり、swap( )関数内で値を交換しても、

呼び出し元の変数である、

num1とnum2に影響を与えることができません。

ところがポインタをうまく使うことで、

関数の呼び出し時に指定した、

引数の値を変更することができます。

このためには、swap( )関数の

仮引数をポインタとして定義する

ということが必要です。

そこで以下のようにswap( )関数を書き換えます。

f:id:YuyaTerayama:20180911125312p:plain

今回は仮引数にポインタを使いました。

ただし、関数内では*を使ってポインタが、

さし示しているものどうしを交換しているので、

先ほどのswap( )関数と内容として同じものとなっています。

この関数を呼び出すには、

仮引数がポインタなので、

変数のアドレスを引数として渡します。

関数の引数にポインタを使ったコードを、

入力してみます。

sample70.c

f:id:YuyaTerayama:20180911125808p:plainーsample70の実行画面ー

変数num1の値は7です。

変数num2の値は10です。

変数num1とnum2の値を交換します。

変数num1の値は10です。

変数num2の値は7です。

sample70の実行画面をみてわかる通り、

正しく交換されています。

この関数内では、

仮引数側の*pX、*pYを変更する

→実引数側のnum1、num2を変更する

ポインタを関数の仮引数として使うと、

実引数の値を変更することができるようになります。

尚、関数が呼び出されるとき、

実引数のアドレスが関数に渡されることを、

参照渡し(pass by reference)

と呼びます。

今回のポインタを使った関数は、

アドレスを指定して関数に渡すため、

実質的に参照渡しの関数となっています。

 

ポインタの使い道

C言語ではポインタを使うことで、

渡した実引数に変更を加える関数を作成することができます。

引数にポインタを使う方法は、

特に呼び出し元の「複数個」の変数を、

関数内で直接変更したい場合に便利です。

複数個の変数を一度に変更したい場合には、

戻りの仕組みを使うことができません。

関数の戻り値は1個に限定されているからです。

このため、複数個の変数を変更する場合には、

ポインタを使うと良いです。

ポインタにはこれ以上にも様々な使い方があります。

 

それについては次回また学んでいきます。

 

〜END〜

*C言語* 〜30時限目〜 ポインタから変数の値を知る・・・

〜目次〜

 

 

ポインタから変数の値を知る

ポインタに変数のアドレスが格納されると、

そのポインタから逆にたどって、元の変数の値を知る

ということができるようになります。

ポインタから変数の値をたどるには、

ポインタに対して、*という演算子を使います。

*演算子は、

間接参照演算子(indirection operator)

と呼ばれています。

この演算子を使うと、

そのポインタに格納されているアドレスに、

対応する変数の値を知ることができます。

変数の値を「間接的」に知ることができるのです。

実際に以下のコードを入力してみてみます。

sample66.c

f:id:YuyaTerayama:20180911033403p:plainーsample66の実行画面ー

変数aの値は7です。

変数aのアドレスは0x7ffee21ee7b8です。

ポインタpAの値は0x7ffee21ee7b8です。

pAの値は7です。

実行画面から確認できるように、

*演算子を使うことで、変数aの値を知ることができます。

 

ポインタについて整理

複雑になってきたので、

一度ここでポインタについて整理していきます。

変数aとそのアドレス&aは以下のようになっています。

a・・・変数a

&a・・・変数aのアドレス

ここで「pA = &a;」と代入します。

つまり、

ポインタpAが変数aをさすようにします。

pA・・・変数aのアドレスを格納したポインタ

*pA・・・変数aのアドレスを格納するポインタがさしている変数→変数a

代入しなければ上記の2つは成り立ちません。

 

ポインタに別のアドレスを代入する

別の変数のアドレスを格納してみます。

実際に以下のコードをみてください。

sample67.c

f:id:YuyaTerayama:20180911034140p:plain

ーsample67の実行画面ー

変数aの値は7です。

ポインタpAの値は0x7ffee97807b8です。

pAの値は7です。

変数bの値は10です。

ポインタpAの値は0x7ffee97807b4です。

pAの値は10です。

sample67の実行画面をみてわかる通りに、

ポインタの値を変更し、

別の変数をさすようにすることもできます。

尚、ポインタは原則として、

指定した型以外の値を格納することはできません。

 

ポインタに代入しない場合

ポインタに何も代入しないで、

使った場合にはどうなるのかについてみてみます。

ポインタが何もさし示していない状態で、

使ってしまうと、

コンピュータが予期せぬエラーを起こす場合があります。

なので、

できる限りポインタは以下のように、

初期化を行います。

int a = 7;

int *pA = &a;

宣言時に初期化を行うことで、

ポインタに値を代入し忘れるなどの、

ミスをなくすことができます。

 

ポインタを使って変数を変更する

ポインタを使うことで、

ポインタがさしている変数そのものの値を変更することができます。

実際に以下のコードを入力してください。

sample68.c

f:id:YuyaTerayama:20180911035024p:plain

ーsample68の実行画面ー

変数aの値は7です。

*pAに10を代入しました。

変数aの値は10です。

sample68の実行画面をみてわかる通り、

「a = 10;」という代入をせずに、

「*pA = 10;」という代入の仕方で、

変数aに代入されている値が変更しています。

 

実際「a = 10;」でいいのではないかと思われると思います。

しかし、この仕組みを利用してポインタを使いこなす方法があるのです。

それについて次回学んでいきます。

 

〜END〜

*C言語* 〜29時限目〜 アドレスの仕組みについて・・・

〜目次〜

 

 

アドレスの仕組みについて

変数の値がコンピュータのメモリに記憶されることは、

すでに学びました。

C言語には、メモリの位置を直接あらわす

「ポインタ」という機能が用意されています。

ポインタはなかなかわかりにくい仕組みなので、

頑張って覚えていきます。

まずはじめに、メモリの位置を直接あらわす

アドレス(address:番地)

について学んでいきます。

C言語での「アドレス」とは、

メモリの場所を直接あらわすために使われる、

メモリ上の「住所」のことです。

 

変数のアドレスについて

しかし、「メモリ」や「アドレス」といっても、

イメージできないと思います。

変数の値が格納されているメモリのアドレスを知るには、

アドレス演算子(address operator)

の&を使います。

以下のようにして使います。

&変数名   (変数のアドレスをあらわします)

実際にアドレスを出力してみます。

以下のコードを入力してみてください。

sample64.c

f:id:YuyaTerayama:20180911022257p:plainーsample64の実行画面ー

変数aの値は7です。

変数aのアドレスは0x7ffeee3877b8です。

「&a」によって、

変数aの値がメモリ上の「どの位置」に記憶されているのか

を知ることができます。

アドレスを出力するには、

変換仕様として%pを使います。

0x7ffeee3877b8」という値が出力されていますが、

これが「アドレス」になります。

0x7ffeee3877b8というメモリの位置に格納されているということです。

これは実行状況により変化するので、

違った値が出力されると思います。

ここで大事なのは、

アドレスを使って、メモリ上の位置をあらわすことができる

ということなのです。

 

ポインタの仕組みについて

アドレスを使うコードを記述するために、

アドレスを格納する特殊な変数

を学んでいきます。

この変数のことを、

ポインタ(pointer)

と呼びます。

ポインタの使い方は原則として、

これまでの変数と同じです。

ポインタも使う前に「pA」などという名前を決めて宣言します。

ただし、ポインタをあらわす変数には、

「*」という記号をつけて宣言することになっています。

ポインタの宣言は以下の通りです。

型名 *ポインタ名;

実際にポインタに何かアドレスを格納する、

プログラムを入力してみます。

sample65.c

f:id:YuyaTerayama:20180911023505p:plainーsample65の実行画面ー

変数aの値は7です。

変数aのアドレスは0x7ffeed4eb7b8です。

ポインタpAの値は0x7ffeed4eb7b8です。

8行目でポインタpAに変数aのアドレスを、

格納するという作業ができています。

ポインタpAの値として出力される値は、

変数aのアドレスである、&aと同じになっています。

変数aとポインタpAの間には何かしらの「関係」ができた

と見ることもできます。

この「関係」のことを、

pAは変数aをさす

などといったりします。

変わった言い方ですが、

pAの値が、変数aの場所をさし示すようになった

と考えるとイメージしやすくなります。

 

今回は少し短いですが、

ここまでになります。

次回からポインタについてさらに詳しくやっていきます。

 

〜END〜

*C言語* 〜28時限目〜 関数を宣言する・・・

〜目次〜 

 

 

関数を宣言する

関数を使って大きなプログラムを作成していきます。

大きなプログラムを作成する前に、

新しい仕組みについて紹介します。

C言語では、

関数の名前と引数の数をコンパイラに知らせておく

という仕組みがあります。

これを、

関数プロトタイプ宣言(関数宣言:function declaration)

と呼びます。

関数プロトタイプ宣言は、

関数を呼び出す前に、

呼び出す関数の名前・戻り値の型・引数について、

以下のように記述します。

戻り値の型 関数名(引数リスト);

この宣言をしておくと、

関数の本体を、コードの中のどこの場所で定義していても、

間違いなく呼び出すことができます。

これまでは呼び出す前の部分で関数を定義していたが、

今度はあらかじめ関数を宣言しておき、

関数本体はmain( )関数の後に、

定義するコードを入力してみます。

sample62.c

f:id:YuyaTerayama:20180911012428p:plainーsample62の実行画面ー

1番目の数を入力してください。

7

2番目の数を入力してください。

10

最大値は10です。

sample62では、最初にmax( )関数が2つの引数をもち、

int型の戻り値を返すことを、

関数プロトタイプ宣言で示しました。

main( )関数の呼び出し方が、

戻り値や引数がプロトタイプ宣言と異なる場合には、

コンパイル時にエラーが表示される仕組みになっています。

 

ファイルを分割する

実際に関数プロトタイプ宣言が役立つのは、

大きなプログラムを作成する場合です。

大きなプログラムを作成するということをイメージしてください。

一度作成した便利な関数については、

色々なプログラムで再利用することができれば、

便利になります。

この時、何度も別のプログラムから利用するような関数は、

main( )関数と同じファイルに記述するのではなく、

別のファイルに分割して記述するのが普通です。

ファイルを分割することで、

色々なプログラムから利用しやすくなります。

実際に3つのファイルに分割してみます。

myfunc1.h

f:id:YuyaTerayama:20180911013924p:plain

myfunc1.c

f:id:YuyaTerayama:20180911013946p:plain

sample63.c

f:id:YuyaTerayama:20180911013957p:plain

ーsample63の実行画面ー

1番目の数を入力してください。

7

2番目の数を入力してください。

10

最大値は10です。

上記のコードでは、ファイルを以下の3つに分割しています。

  1. myfunc1.h・・・関数プロトタイプ宣言
  2. myfunc1.c・・・作成したmax( )関数の定義
  3. sample63.c・・・main( )関数の定義(プログラム本体)

これはsample63.cとmyfunc1.cを別々にコンパイルして、

オブジェクトファイルを作成します。

さらにオブジェクトファイル同士をリンクすることで、

1つのプログラムを作成します。

myfunc1.hファイルには、

関数の仕様をあらわす関数プロトタイプ宣言だけを記述しています。

このように関数プロトタイプ宣言をまとめたファイルのことを、

ヘッダファイル(header file)

と呼びます。

複数のファイルのコンパイル・リンクの手順については、

C言語の開発環境により異なります。

なので、対応する説明書をお読みください。

1つのプロジェクトに、

分割した複数のソースコードを追加して、

コンパイル・リンクを行ってください。

 

標準ライブラリ関数の仕組みについて

C言語の開発環境には、

どのプログラムでも使える標準的な、

処理について、

すでに定義された関数が添付されています。

これを、

標準ライブラリ関数(standard library)

と呼びます。

これまで使っていた、

printfやscanfなども、

標準ライブラリ関数に含まれる関数なのです。

例えば、入出力を行う

標準ライブラリ関数の関数プロトタイプ宣言は、

開発環境に添付されている、

stdio.hというヘッダファイルに記述されています。

これまでのコードでは、

関数プロトタイプ宣言が記述されている、

stdio.hをインクルードしていたのです。

尚、標準のヘッダファイルをインクルードするには、

< >で囲む必要があります。

一方、自分で作成したヘッダファイルをインクルードするには、

" "で囲みます。

 

ファイルを分割する際の注意

ヘッダファイルには以下の事項を宣言します。

  • 他のファイルに公開する関数プロトタイプの宣言
  • 他のファイルに公開するグローバル変数の宣言
  • 他のファイルに公開する型の宣言

上記の中の、

グローバル変数の宣言については注意があります。

ヘッダファイルでグローバル変数を宣言すると、

コード本体ファイルとの間でグローバル変数が重複するため、

エラーになります。

ファイルを分割する場合には、

変数や関数がどのファイルで適用するのかを、

考えなくてはいけません。

グローバル変数と関数の名前は全てのファイルで適用します。

ただし、staticをつけると、

ファイル内だけに限定することができます。

ヘッダファイルで公開するグローバル変数には、

externという指定をつけて宣言します。

externは変数の名前だけを他のファイルに知らせるための指定です。

 

〜END〜

*C言語* 〜27時限目〜 変数について細かく・・・

〜目次〜

 

 

 

ローカル変数の名前を重ねた場合

変数の名前と宣言位置には注意してください。

同じ関数内のローカル変数には同じ名前をつけることはできません。

ただし、

異なる関数内で宣言した、

ローカル変数には同じ名前をつけることができます。

同じ名前がついていても、

異なる関数内のローカル変数は、

別の変数になります。

 

グローバル変数の名前を重ねた場合

グローバル変数とローカル変数は、

同じ名前をつけることができます。

名前が重複した場合には以下のようになります。

f:id:YuyaTerayama:20180910233439p:plain

ローカル変数を宣言したfunc( )関数内での、

「a++;」などという記述をすると、

それはローカル変数aのことをさします。

つまり、func( )関数内でインクリメントされるのは、

ローカル変数aということになります。

一方で、main( )関数内でインクリメントされるのは、

グローバル変数aになります。

func( )関数内ではローカル変数により、

グローバル変数の名前は隠されるようになっています。

 

ローカル変数のバリエーション

ローカル変数は、

関数の先頭だけでなく、

for文やif文などのブロックの先頭で宣言することもできます。

関数の仮引数もローカル変数の一種です。

ローカル変数は、

それを宣言したブロックだけで通用する変数になります。

ブロックの内側と外側で変数名が重複した場合には、

内側の変数の名前が優先されます。

 

変数の記憶寿命について

変数や配列は、

プログラムを開始してから終了するまで、

ずっと値を記憶してる訳ではありません。

変数を宣言すると、

まず値を記憶するための箱がメモリ内に準備されます。

このことを、

メモリが確保される

と呼ぶことがあります。

そのあと、変数に値を格納したり出力したりして、

利用するわけだが、

最後に箱が廃棄されることにより、

メモリがまた別の用途に使われるようになります。

このことを、

メモリを解放する

と呼ぶことがあります。

変数の箱が存在し、値を記憶していられる期間のことを、

記憶寿命

と呼びます。

変数がどのような記憶寿命を持つかは、

変数の宣言位置にも関係しています。

通常のローカルの変数の場合、

  1. 関数内で宣言された時に、変数の箱がメモリに準備され
  2. 関数が終了すると、箱が廃棄されメモリが他の用途に使われるようになる

という流れをたどります。

つまり、通常のローカル変数は、

宣言されてから関数が終了するまでの間だけ、

値を格納しておくことができます。

グローバル変数の場合、

  1. プログラム全体の処理が始まる前に、一度だけメモリが確保され
  2. プログラム終了時にメモリが解放される

という流れをたどります。

つまり、グローバル変数は、

プログラムの開始から終了までの間、

ずっと値を格納しておくことができます。

変数の一生を確かめるために、以下のコードを入力してください。

sample61.c

f:id:YuyaTerayama:20180911001329p:plainーsample61の実行画面ー

変数aは0、変数bは0、変数cは0です。

変数aは1、変数bは0、変数cは1です。

変数aは2、変数bは0、変数cは2です。

変数aは3、変数bは0、変数cは3です。

変数aは4、変数bは0、変数cは4です。

func( )関数は変数a,b,cの値を出力し、

1つずつインクリメントしている関数です。

グローバル変数aはプログラムの開始から終了まで値を

記憶しているので、1つずつ値が増えていく。

一方ローカル変数bは、

関数が呼び出されるたびに最初に0が格納され、

関数の終了ごとに箱が廃棄されます。

このため、インクリメントをしても

いつも0のままになってしまいます。

 

staticをつけると・・・

通常のローカル変数は関数が終了するまでの、

記憶寿命しかもちません。

ただし、ローカル変数にstaticというキーワードを指定することで、

グローバル変数と同じ記憶寿命を持つようになります。

このようなローカル変数のことを、

静的寿命を持つローカル変数

と呼びます。

staticは、

記憶クラス指定子(storage class identifier)

と呼ばれています。

尚、変数の初期化コードを記述しなかった場合には、

グローバル変数とstaticなローカル変数は、

自動的に0で初期化されるようになっています。

通常のローカル変数を初期化しない場合の初期値は、

特に決まっていません。

 

〜END〜