C言語基礎 関数・ポインタ 関数ってなによ? 関数という言葉を聞いて、最初に思うのは、やはり数学の関数でしょう。 数学でいう定義の関数とは f ( x, y) x 2 2 xy y 2 みたいな形だったよね。関数にxとyを渡すと結果は f (2,3) 25 とかけたでしょ^^。じゃぁこれを試しにC言語で書いてみると num = f(2,3); printf(“%d”,num); と書いて、25と表示させることができそうだ。 別の例を挙げてみよう。 関数の概念 あまり「関数ってなにか」を考えたことはないでしょう^^。 「ばかにするな」っていわれるかも知れませんが、説明しましょう。 出力 入力 120円 コーラ 自動販売機 商品1 まずは図を描いてみました。関数とは入力に見合った出力をだす「機械」です ね^^ 関数とは2 前の図を少し形式的に書いてみると 自動販売機(120,商品1)= コーラ 自動販売機(120,商品2)= ドクターペッパー 自動販売機(100,商品1)= エラー というように、入力と出力を表せるでしょ? これが数字に限らない「関数」です。 じゃぁ関数「自動販売機(x,y)」の中身はどのようになってるのか? 自動販売機(金、商品のボタン) 自動販売機(金、商品ボタン)の内容 If(金<120){ 結果(エラー); } if(商品ボタン==“商品1”){ 結果(コーラ排出); }else if(商品ボタン==“商品2”){ 結果(ドクターペッパー排出); } C言語で関数を定義 では、実際にC言語で関数を作ってみよう。 まず、関数には、引数と戻り値というものがあります。 関数名(引数1、引数2、引数3・・・)=戻り値 先ほどの例でいえば、引数1が「金額」、引数2が「商品番号」であり、戻り 値が、「商品」ということになります。 もちろん、関数を使うまえに引数達が「数字をあらわすのか、文字をあらわ すのか」をキチンと定義する必要があります。もちろん戻り値もですが^^ C言語では、これを以下のように宣言します。 戻り値型 関数名(引数型 変数名、引数型 変数名、・・・) 例) int fuction01(int a,int b) 数字のa,bを加工して、数字を返す。 実際の関数 #include <stdio.h> int wa(int x,int y); main(void) { int a=5,b=3; int res; res=wa(a,b); printf(“RES:%d”,res); } int wa(int x,int y) { return( x + y); } /* 関数を使うよ、という宣言 */ 解析1 #include <stdio.h> int wa(int x,int y); main(void) { int a=5,b=3; int res; res=wa(a,b); printf(“RES:%d”,res); } /* 関数を使うよ、という宣言 */ /* 関数の呼び出し */ int wa(int x,int y) { return( x + y); } 黄緑色の部分が、main関数、黄色部分が新しくつくった「和を求める関数、wa関数」、 水色部分が、宣言部分です。Main関数では、wa関数を呼び出して、その戻り値(結果)を RESに入れる、という形です。Wa関数の中のreturnは、その値を戻り値として返す命令です。 解析2 #include <stdio.h> int wa(int x,int y); /* 関数を使うよ、という宣言 */ 先ほどの宣言文、これは、このような形の関数を使います、という宣言文で、これを 記述しないと、エラーが発生することがあります。そのため、しっかりと忘れないよう にしましょう。この宣言の仕方は、関数の中身を記述する、 int wa(int x,int y) { return( x + y); } の部分の先頭にセミコロンをつけるだけです。 解析3 int wa(int x,int y) { return( x + y); } 関数の中身の定義をするとき、気をつけなれければいけないのは、変数の 扱いです。もし、関数の中でループをしたい場合、変数iなどを使いたいです よね?その場合は、関数の中で変数を宣言すればいいんです。 int wa(int x,int y) { int i=0; int res=a; for(i=0;i<10;i++){ res = res +y; } return res; } 解析4 ひとつ前の例をみて、少し不安になったでしょうか?? 「main関数でも変数resを使っている」ということです。しかし、関数の中では、 変数は独立して作ってくれますので、いくら他の関数と変数名が重複しても問 題はないのです。 また、int wa(int x,int y){で部分で、xとyは宣言されたことになっているので、 宣言をする必要はありません。 プログラム例 引数とした文字が大文字なら0、小文字なら1を返す関数uord()を使って、 入力した文字列の大文字だけ表示プログラム int uord(char ch); main(void) { char str[500]; printf(“INPUT>”); scanf(“%s”,str); for(i=0;str[i]!=‘\0’;i++){ if(uord(str[i])==0){ printf(“%c”,str[i]); } } } int uord(char ch) { if(‘A’ <= ch && ch <= ‘Z’){ return 0: }else{ return 1; } } 関数なんていらない? 関数を勉強してみて、いかがでしょうか(まだ終わったわけではないですが^^) 「別に関数なんて面倒なことしなくても、mainに書いちゃえばいいじゃん」と思うか もしれません。実際学校でで出る課題だったら、関数は必要ないかもしれません。 しかし、自分でプログラムを組もうとすると、関数は必須です。 例を挙げると、私は、文字列をバイナリ形式で表示する関数を必ずつくります。 データをこのように表示させる関数をあらかじめ作っておくのです。 こうすれば、データが数字とし て、もしくは文字として現在どの ような数値が入っているのかを 見ることができます。文字列を 渡すと、これを表示してくれる 関数を、つくっておけば、 binary(str);とするだけで表示し てくれるのです。これは便利で しょう??(NULL文字に関係な く指定バイト分表示してくれる よ) ポインタという言葉がちらほら では、戻り値が二つ、三つある場合、どうすればいいか? ポインタですよ! さぁ、そろそろポインタでもはじめっか さぁ、最後の難関、ポインタです。 がんばってついてきてね^^ まず、変数とは何かをもう一度確認してみよう。いままで、変数とは、データを持っ ているものであったが、では、内部ではどのような動きをしているのか? ここで、アセンブリの知識が少し必要となります。まずアセンブリでの値の 参照方法は、 mov ax,[0x012345] で、メモリの番地0x012345にある数値をaxに入れるという動作をします。 mov ax,0x012345 の場合は、axに0x012345(数値)を入れるという動作をします。 つまり、プログラムにおいて、数値を「アドレス」とみなすか、「数値」とみ なすかの二通りがあるということです。 まずはじめに プログラムにおいて、数値を「アドレス」とみなすか、「数値」とみなすかの二通りがある いままでプログラミングしてきて、そんなことを考えたこと無かったでしょう? 実は、いままでの「変数」は、アドレスとしての変数だったわけです。変数の 宣言でアドレス(0x012345)が割り当てられたら、たしかにプログラムは 0x012345と変数を対応付けているが、結局 mov ax,[0x012345] と置き換えてしまうので、プログラマは「どこのアドレスに在るのかよくわから ないけど、とりあえず、宣言されたら、ひとつのメモリ領域が確保されるから、 その値だけ考えればいいや」と割り切れるわけです。 別の表現。 変数と、メモリ番地は対応表のようなもので、関連付けられている。よって、 変数に割り当てられた番地はプログラムが終了するまで変わらない。すべて の命令が mov ax,[番地]などと置き換えられるのならば、プログラマは、アド レスを意識しなくても、変数名だけ気にしてれば良い。 図 変数と対応表で結ばれているのはメモリ番地である。 mov ax,[test] add ax,0x50 mov [a],ax 対応表(コンパイル時に自動生成) ----------------------------------------変数名 アドレス Test a I 0x12345 0x33BA0 0x12FFA mov ax,[0x12345] add ax,0x50 mov [0x33BA0],ax ほら、コンパイル時に勝手に対応表が作られるのだったら、アドレスを意識しな くてもOKだよね^^ 変数とアドレスの関係を調べよう では理解を深めるために、このようなプログラムを書いてみよう printf(“DATA:%d\n”,test) printf(“ADDRESS:%p\n”,&test); これを実行すると、testのメモリ番地と、その番地を参照して得ることができ るデータを読む(つまり通常の動作)ことができる。 どうだろうか。変数の最初に「&」をつけることにより、変数そのものの値、 つまりアドレスを参照できるわけ。 ほら、気づいたかな?この「&」、scanfで使ったよね^^。Scanfに&aとかを 渡して、実行すると、aになにか数字が入っているという動作は、「アドレス そのものを関数に教えて、そこに直接書き込んでもらうという」ということを やっているわけです。 アドレスに直接数値を入れる 変数に&をつけることで、アドレスが参照できることが分かった。これを関数で 応用すると、scanfのように「&a」と指定して、その値を変更できるような、関数 が設計可能であるl。でもいまの状態ではなにもできない。 なぜなら、あるアドレスに直接書き込むということができないからだ。いままで やった変数を思い出してみても、書き込めるのは「変数が保持しているアドレ ス番地先」でしかないのだから。やっとここでポインタが登場する(わーい) まずは・・ ポインタの宣言 ポインタ型 *ポインタ名 Char *chp; 初期化 Char *chp = &ch; 値の参照 Printf(“%p”,chp); printf(“%c”,*chp); *chp=‘a’; わからなければ、ここは読み飛ばしてもらってもOKです。慣れてきてから読む と「はいはい」と思うはず! とりあえず、知っておくこと まぁまぁ、突っ込んでいくと教えることが多すぎるので、とりあえず、知っておく こと。 ★ 内容の参照は「*」をつけて、アドレスの参照はそのまま。 例) char *cp; char ch; cp = &ch; *cp = ‘K’; ★ ポインタ型は、代入しようとしている変数と同じものにしておく。 例) char ch; int num; char *cp; cp = &ch; // ○ cp = &num // × 変数とポインタは一緒? 変数 変数 :アドレスを参照した値(可変) &変数:そのアドレス自身(0x012345)(不変) ポインタ ポインタ:アドレス自身(0x012345)(可変) *ポインタ:アドレスを参照した値(可変) この(可変)とか(不変)に注意されたい。変数は宣言されたときにアドレスが 固定されてしまう。だから、特定のアドレスに書き込みができない。しかし、 ポインタは宣言時にその存在のみが宣言されるため、すべてが可変なので す。では、小さな例を。 例 Int *chp; int test=100; chp = &test; //変数testのアドレスをポインタに代入 *chp = 500; //変数testのアドレスに直接、数値500をぶち込む printf(“%d\n”,*chp); //ポインタが保持するアドレスを参照した値 printf(“%d\n”,test); これは意味がないやり方ですが、このような使い方もできる これを関数で応用してみよう! 例 void swap(int *a,int *b); //二つの変数を取り替える関数 void main(void) { int data1,data2; scanf(“%d”,&data1); scanf(“%d”,&data2); printf(“[%d %d]\n”,data1,data2); swap(&data1,&data2); printf(“[%d %d]\n”,data1,data2); } void swap(int *a,int *b) { int tmp; tmp = *a; *a = *b; *b = tmp; } じゃぁ自分で作ってみよう。 なんでもいいからポインタを使ったプログラムを作ろう。 先ほどの例を改造して、文字の交換をする関数に変えてみるとか。 とりあえず、ここまで。 文字列を関数に渡してみよう #include <stdio.h> int _strlen(char data[]); void main(void) { char str[512]; scanf("%s",str); printf("length:%d\n",_strlen(str)); } int _strlen(char data[]) { int i; for(i=0;data[i]!='\0';i++); return i; } //これで空ループ これは、文字列を渡すと、その長さを返してくれる関数。これは、相当便利 だと思います。C言語にこの関数(strlen())があることが、それを物語ってい ますね^^ 簡単そうに見えるけど 別に、引数のところをstr[]とするだけやん!と思うでしょう。 しかし、この構造はそこまで簡単ではありません。 まず、配列についての復習をします。これを抑えないとヤバいですよ^^ ‘k’ ‘e’ ‘I’ ‘s’ ‘a’ ‘n’ ‘\0’ Str[0] Str[1] Str[2] Str[3] Str[4] Str[5] Str[6] ‘k’ ‘e’ ‘I’ ‘s’ ‘a’ ‘n’ ‘\0’ アドレス 012000番地 012001番地 012002番地 012003番地 012004番地 012005番地 012006番地 まず、配列はメモリ上でこのように表現されていることに注意してください。
© Copyright 2024 ExpyDoc