2010年4月アーカイブ

C言語:構造体

| コメント(1) | トラックバック(0)
続いては構造体について。

異なったデータ型をひとまとめにして扱いたい場合に構造体を利用する。

構造体の宣言は↓のような感じで行う。

struct 構造体タグ {
    メンバ;
    ・・・;
};

↑だけではまだ構造体のテンプレートを宣言しただけで使えない。
実際に使うには構造体タグをを使って構造体変数を宣言する必要がある。

struct data man;
     ↑   ↑
     タグ 変数

構造体テンプレートを宣言するのと同時に、構造体変数を宣言するやり方もある↓。
struct 構造体タグ{
    メンバ;
    ・・・;
} 構造体変数1, 構造体変数2, ・・・;

↑のように同時に宣言する場合は構造体タグを省略することもできる。

構造体のメンバへアクセスするには構造体メンバ演算子「.」を利用する。

構造体変数の配列を作ることも可能。

struct Man student;
と構造体変数を宣言していたところを
struct Man student[5];
とかすればいい。

student[2]のnameメンバにアクセスする場合は
student[2].name でOK。

構造体へのポインタ

構造体変数を

struct Man *student;

のように宣言すると、studentはManへの構造体へのポインタとなる。
このときメンバにアクセスするのは

(*student).name とする。

でも一般的にはアロー演算子を使って
student->name
とする。

新しい型を定義する

typedefキーワードを使えばデータ型の名前を自由に定義することができる。

typedef 既存の型名 新しい型名;

typedef int SEISU; こんな感じで。

上の例では何がうれしいのかあまりわからない・・・。

でも構造体にこのtypedefを適用するとあたかも新しい型ができたかのように見える。

たとえば

typedef struct _tagData {
    int age;
    double bl;
    double bw;
} MYDATA, *LPMYDATA;

とすると_tagData型の構造体変数を定義するとき、

MYDATA mydata;

と書ける。

構造体へのポインタを宣言する場合も
 
LPMYDATA lpmydata;

と宣言できる。



以上のことを踏まえてのサンプルプログラム↓。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct DATA {
        char name[32];
        char tel[32];
} MYDATA, *LPMYDATA;

int menu();
int meibo_input( LPMYDATA );
int meibo_output( LPMYDATA );

int main() {
        int selectno, loopend = 0;

        MYDATA mydata[10];
        LPMYDATA lpMydata = mydata;
        
        memset( mydata, '\0', sizeof(MYDATA[10]) );

        while(1) {
                selectno = menu();

                switch (selectno) {
                        case 1:
                                meibo_input( lpMydata );
                                break;
                        case 2:
                                meibo_output( lpMydata );
                                break;
                        default:
                                loopend = 1;
                                break;
                }
                if( loopend )
                        break;
        }
        return 0;
}

int menu() {
        char ret[8];
        
        printf( "****** MENU ******\n" );
        printf( "1:データ入力\n" );
        printf( "2:データ表示\n" );
        printf( "0:終了\n" );
        printf( "********************\n" );
        printf( "---->" );
        gets(ret);
        return atoi(ret);
}

int meibo_input( LPMYDATA lpData ) {
        char strno[8];
        int no;

        printf( "何番のデータを入力しますか(0-9)--" );
        gets(strno);
        if( strno[0] < '0' || strno[0] > '9' ) {
                printf( "入力が不正です\n" );
                return -1;
        }
        strno[1] = '\0';
        no = atoi(strno);

        printf("氏名--");
        gets( (lpData + no)->name );
        printf( "電話番号--" );
        gets( (lpData + no)->tel );

        return 0;
}

int meibo_output( LPMYDATA lpData ) {
        char strno[8];
        int no;
        
        printf( "何番のデータを読み出しますか(0-9)--" );
        gets( strno );
        
        if(strno[0] < '0' || strno[0] > '9') {
                printf( "入力が不正です\n" );
                return -1;
        }
        strno[1] = '\0';
        no = atoi( strno );

        if(!strcmp( (lpData + no )->name, "" )) {
                printf( "データがありません。\n" );
                return -1;
        }
        printf( "氏名-- %s\n", ( lpData + no )->name );
        printf( "電話番号-- %s\n", (lpData + no)->tel );

        return 0;
}



実行結果
$ ./type03.exe 
****** MENU ******
1:データ入力
2:データ表示
0:終了
********************
---->1
何番のデータを入力しますか(0-9)--3
氏名--test
電話番号--1234567
****** MENU ******
1:データ入力
2:データ表示
0:終了
********************
---->2
何番のデータを読み出しますか(0-9)--3
氏名-- test
電話番号-- 1234567
****** MENU ******
1:データ入力
2:データ表示
0:終了
********************
---->0


もうちょっと色々やってみないとしっくりこないな。


猫でもわかるC言語プログラミング 第2版 猫でもわかるプログラミングシリーズ (NEKO Series)
粂井 康孝
ソフトバンククリエイティブ
売り上げランキング: 30287
おすすめ度の平均: 3.0
3 後半の説明に難あり?
5 猫でもわかるC言語プログラミング
1 猫どころか人間でもわからない
5 実際にコードを書いて覚える本
1 ポインタや配列の説明が内容的に薄いかな。
ちょっと色々思うところがあり、GoogleのApp Engineをいまさらながら勉強してみようかと。。

ついでにPythonの勉強にもなればいいな。

というわけでGAE-Pythonの開発環境を設定してみる。

まずはPythonのインストールから。

Python公式サイトからGAE-Pythonでサポートされている2.5.xをダウンロード&インストールします。

Download  >  Releases

今回は参考本と同じようにpython-2.5.4.msiをダウンロードしてインストールします。

何も考えずに全部デフォルトでインストールしてみました。

次にGAEのPython SDKをダウンロードします。

GoogleAppEngine_1.3.3.msiをダウンロードしてインストール。

インストールしたフォルダの内容は以下のようになりました。

p_1.jpg
dev_appserver.py
これは開発用のWebサーバのようです。Googleのクラウド環境をエミュレートして動作確認ができるらしい。

appcfg.py
これでGAEのクラウド環境へのアップロード/デプロイを行うみたい。

ではデモ用のプログラムが用意されているようなので実行してみます。

コマンドプロンプトを起動してdev_appserver.pyのところまで移動します。
そして

dev_appserver.py demos\guestbook

と入力します。

p_2.jpg


画面のように最後にローカルのURLが表示されれば成功のようです。

実際にブラウザから以下にアクセスすると
http://localhost:8080/

p_3.jpg
こんな画面が表示されます。
適当に入力して「Sign Guestbook」をクリックすると、



An anonymous person wrote:

    テスト入力です。

とか表示されます。
これで一応動くところまでこぎつけました。

終了の方法は
「Ctrl+C」または「Ctrl+Break」みたいです。

今日はとりあえずここまで。





Google App Engine 実践リファレンス
清野 克行
技術評論社
売り上げランキング: 26659
おすすめ度の平均: 4.0
4 これから始めたい方に・・・JavaでもPythonでも

C言語:文字と文字列

| トラックバック(0)
※C言語初心者の為、内容に不備がある可能性があります。

C言語での文字の取り扱いについて。

文字を変換指定子「%c」と「%d」で出力してみる。


#include <stdio.h>

int main() {
  char a1, a2, a3;

  a1 = 'a';
  a2 = 'b';
  a3 = 'c';
  printf( "%c%c%c\n", a1, a2, a3 );
  printf( "a1 = %d, a2 = %d, a3 = %d\n", a1, a2, a3 );
  return 0;
}

実行結果
abc
a1 = 97, a2 = 98, a3 = 99

%dで出力してもコンパイルエラーも無くコンパイルが終わり、実行できた。
結果を見てみると「%d」での出力は数字になっている。
この数字はどうもASCIIコードを表しているみたい。
文字は単なる数字ということらしい。

文字は数字ということを利用してこんなこと↓もできるらしい。
#include <stdio.h>
int main() {
  char i;

  for( i = '!'; i <= '~'; i++ ) {
    printf( "%3d(0x%2X) -- %c ", i, i, i );
    if( ( i - '!' + 1 ) % 4 == 0 )
      printf( "\n" );
  }
  printf( "\n" );
  return 0;
}


実行結果
$ ./char03.exe 
 33(0x21) -- !  34(0x22) -- "  35(0x23) -- #  36(0x24) -- $ 
 37(0x25) -- %  38(0x26) -- &  39(0x27) -- '  40(0x28) -- ( 
 41(0x29) -- )  42(0x2A) -- *  43(0x2B) -- +  44(0x2C) -- , 
 45(0x2D) -- -  46(0x2E) -- .  47(0x2F) -- /  48(0x30) -- 0 
 49(0x31) -- 1  50(0x32) -- 2  51(0x33) -- 3  52(0x34) -- 4 
 53(0x35) -- 5  54(0x36) -- 6  55(0x37) -- 7  56(0x38) -- 8 
 57(0x39) -- 9  58(0x3A) -- :  59(0x3B) -- ;  60(0x3C) -- < 
 61(0x3D) -- =  62(0x3E) -- >  63(0x3F) -- ?  64(0x40) -- @ 
 65(0x41) -- A  66(0x42) -- B  67(0x43) -- C  68(0x44) -- D 
 69(0x45) -- E  70(0x46) -- F  71(0x47) -- G  72(0x48) -- H 
 73(0x49) -- I  74(0x4A) -- J  75(0x4B) -- K  76(0x4C) -- L 
 77(0x4D) -- M  78(0x4E) -- N  79(0x4F) -- O  80(0x50) -- P 
 81(0x51) -- Q  82(0x52) -- R  83(0x53) -- S  84(0x54) -- T 
 85(0x55) -- U  86(0x56) -- V  87(0x57) -- W  88(0x58) -- X 
 89(0x59) -- Y  90(0x5A) -- Z  91(0x5B) -- [  92(0x5C) -- \ 
 93(0x5D) -- ]  94(0x5E) -- ^  95(0x5F) -- _  96(0x60) -- ` 
 97(0x61) -- a  98(0x62) -- b  99(0x63) -- c 100(0x64) -- d 
101(0x65) -- e 102(0x66) -- f 103(0x67) -- g 104(0x68) -- h 
105(0x69) -- i 106(0x6A) -- j 107(0x6B) -- k 108(0x6C) -- l 
109(0x6D) -- m 110(0x6E) -- n 111(0x6F) -- o 112(0x70) -- p 
113(0x71) -- q 114(0x72) -- r 115(0x73) -- s 116(0x74) -- t 
117(0x75) -- u 118(0x76) -- v 119(0x77) -- w 120(0x78) -- x 
121(0x79) -- y 122(0x7A) -- z 123(0x7B) -- { 124(0x7C) -- | 
125(0x7D) -- } 126(0x7E) -- ~ 

上記は"!"?"~"までのASCIIコードを表示するプログラム。
forの部分は
for( i = 33; i <= 126; i++ ) {
と書いても同じこと。
次に文字をint型で扱ってみる。
#include <stdio.h>

int main() {
  int c;

  for( c = 'A'; c <= 'z'; c++ ) {
    if( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') )
      printf( "%c = %3d(0x%X)\n", c, c, c );
  }
  return 0;
}

実行結果
$ ./char04.exe 
A =  65(0x41)
B =  66(0x42)
C =  67(0x43)
D =  68(0x44)
E =  69(0x45)
F =  70(0x46)
G =  71(0x47)
H =  72(0x48)
I =  73(0x49)
J =  74(0x4A)
K =  75(0x4B)
L =  76(0x4C)
M =  77(0x4D)
N =  78(0x4E)
O =  79(0x4F)
P =  80(0x50)
Q =  81(0x51)
R =  82(0x52)
S =  83(0x53)
T =  84(0x54)
U =  85(0x55)
V =  86(0x56)
W =  87(0x57)
X =  88(0x58)
Y =  89(0x59)
Z =  90(0x5A)
a =  97(0x61)
b =  98(0x62)
c =  99(0x63)
d = 100(0x64)
e = 101(0x65)
f = 102(0x66)
g = 103(0x67)
h = 104(0x68)
i = 105(0x69)
j = 106(0x6A)
k = 107(0x6B)
l = 108(0x6C)
m = 109(0x6D)
n = 110(0x6E)
o = 111(0x6F)
p = 112(0x70)
q = 113(0x71)
r = 114(0x72)
s = 115(0x73)
t = 116(0x74)
u = 117(0x75)
v = 118(0x76)
w = 119(0x77)
x = 120(0x78)
y = 121(0x79)
z = 122(0x7A)


次に文字列。
ABCのような文字の並びを文字列といい、ダブルクォーテーションで囲む。
"ABC"を文字列リテラルと言う。
"ABC"がメモリに置かれる場合、'A'、 'B'、 'C'の順にメモリ上に置かれる。
そして'C'の次には'\0'(ヌル文字)が置かれる。
ヌル文字は文字列がここで終わりという合図を示す。

以下イメージ図
________________
|     |      |       |      |
|A   | B  | C    | \0  |
|___|___|____|___|
300  301 302 303  <- アドレス

んで、"ABC"の式は配列のときと同様に'A'の置かれたアドレスを指す。
「文字列の式の値は、先頭文字の置かれたアドレス」


 

char型のポインタへ代入してみる。
#include <stdio.h>
int main() {
  char *str;
  str = "ABC";
  printf(
         "*(str + 0) = '%c', "
         "*(str + 1) = '%c', "
         "*(str + 2) = '%c'\n",
         *(str + 0), *(str + 1), *(str + 2)
         );
  printf( "str[0] = '%c', str[1] = '%c', str[2] = '%c'\n",
          str[0], str[1], str[2]);
  return 0;
}

実行結果
*(str + 0) = 'A', *(str + 1) = 'B', *(str + 2) = 'C'
str[0] = 'A', str[1] = 'B', str[2] = 'C'


文字列の内容を比較する(strcmp関数
#include <stdio.h>
#include <string.h> /* strcmp関数を使うため */

int main() {
  char str1[32], str2[32];
  int cmp;
  printf( "str1を入力してください---" );
  scanf( "%s", str1 );
  printf( "str2を入力してください---" );
  scanf( "%s", str2 );

  cmp = strcmp( str1, str2 );
  if( cmp < 0 )
    printf( "%sは、%sより前にあります\n", str1, str2 );
  else if( cmp > 0 )
    printf( "%sは、%sより後ろにあります\n", str1, str2 );
  else
    printf( "%sと%sは、同じ文字列です\n", str1, str2 );

  return 0;
}

実行結果
$ ./string03.exe 
str1を入力してください---asd
str2を入力してください---asd
asdとasdは、同じ文字列です
$ ./string03.exe 
str1を入力してください---asd
str2を入力してください---asf
asdは、asfより前にあります
$ ./string03.exe 
str1を入力してください---asf
str2を入力してください---asd
asfは、asdより後ろにあります

strcmp関数は辞書式に並べたときに前にあればマイナス、後ろにあればプラスを、
まったく同じであれば0を返すらしい。

そのほかに文字列の長さを調べる「strlen」関数もある。

猫でもわかるC言語プログラミング 第2版 猫でもわかるプログラミングシリーズ (NEKO Series)
粂井 康孝
ソフトバンククリエイティブ
売り上げランキング: 4362
おすすめ度の平均: 3.0
3 後半の説明に難あり?
5 猫でもわかるC言語プログラミング
1 猫どころか人間でもわからない
5 実際にコードを書いて覚える本
1 ポインタや配列の説明が内容的に薄いかな。

C言語:配列

| トラックバック(0)
※C言語初心者の為、内容に不備がある可能性があります。

C言語で配列を定義する。

#include <stdio.h>

int main() {
        int a[4];

        a[0] = 1;
        a[1] = 2;
        a[2] = 100;
        a[3] = 50;

        printf( "a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n", 
a[0], a[1], a[2], a[3] ); return 0; }

実行結果
a[0] = 1, a[1] = 2, a[2] = 100, a[3] = 50

もちろん以下のようにも書ける。
int a[4] = { 1, 2, 100, 50 };

int a[] = { 1, 2, 100, 50 };

int a[4] = { 1, 2 };
↑こうやって書くとa[2], a[3]は「0」が勝手に代入される!

int b[2] = { 1, 2, 3 }; と書くと以下のように怒られる。
array01.c:13: 警告: 配列初期化子内の要素が多すぎます
array01.c:13: 警告: (near initialization for 'b')
array01.c:13: 警告: unused variable 'b'

int c[] = { 1, 2,  };
↑この書き方はなぜか怒られない。

配列の要素は原則として、順番にメモリ上に並んでいるはずなので実際に各
要素のアドレスを出してみる。


#include <stdio.h>

int main() {
        int a[4] = { 1,2,3,4 }, i;

        for( i = 0; i < 4; i++ ) {
                printf( "&a[%d] = %p\n", i, &a[i] );
        }
        printf( "\na = %p\n", a );
        return 0;
}


実行結果
&a[0] = 0xbfa83570
&a[1] = 0xbfa83574
&a[2] = 0xbfa83578
&a[3] = 0xbfa8357c

a = 0xbfa83570

確かに4バイトずつつながってる。
最後のaの出力結果からわかるとおり、配列の名前はその配列の先頭の要素の
アドレスをあらわしている!

ということは、int型の配列であればaで示されている先頭のアドレスより4バイト進めると
a[1]のアドレスになるということ?以下で試してみる。


#include <stdio.h>
int main() {
  int a[] = {10, 20, 30, 40}, i, *p;
  p = a; /* pにa[0]のアドレスを代入 */ 
  for ( i = 0; i < 4; i++ ) {
    printf( "&a[%d] = %p, a[%d] = %d, *(p + %d) = %d\n",
            i, &a[i], i, a[i], i, *(p+i) );
  }
  return 0;
}

結果
&a[0] = 0xbff2ade8, a[0] = 10, *(p + 0) = 10
&a[1] = 0xbff2adec, a[1] = 20, *(p + 1) = 20
&a[2] = 0xbff2adf0, a[2] = 30, *(p + 2) = 30
&a[3] = 0xbff2adf4, a[3] = 40, *(p + 3) = 40


結果から、「int型へのポインタに1を加える」ということは「int型の大きさだけアドレスを1つ進める」
ということがわかる。これはどうも他の型についても同様みたい。

配列の名前aはポインタのように(a + 1)するとaより4番地進んだアドレスをあらわすらしい。
#include <stdio.h>
int main() {
  int *p, i, a[] = {20, 40, 80, 100};
  p = a;
  for( i = 0; i < 4; i++ ) {
    printf( "&a[%d] = %p, (p + %d) = %p, (a + %d) = %p\n",
            i, &a[i], i, p + i, i, a + i);
  }
  return 0;
}

実行結果
&a[0] = 0xbff0a8e8, (p + 0) = 0xbff0a8e8, (a + 0) = 0xbff0a8e8
&a[1] = 0xbff0a8ec, (p + 1) = 0xbff0a8ec, (a + 1) = 0xbff0a8ec
&a[2] = 0xbff0a8f0, (p + 2) = 0xbff0a8f0, (a + 2) = 0xbff0a8f0
&a[3] = 0xbff0a8f4, (p + 3) = 0xbff0a8f4, (a + 3) = 0xbff0a8f4


a[n] は *(a+n) と同じ!
配列とポインタは似てる。でもポインタは変数だから書き換えができた。
でも配列名は先頭要素のアドレスを示すもので、変数ではない。だから書き換えは
できない。
ポインタpに対してはp++可能だけど配列名aに対してa++はできない。
さらに・・・。
a[n]は*(a + n)と同じで、
*(a + n) と*(n + a)は実は同じ。。
だから、、、
「a +n」と[n + a]も実は同じ。。
*(a + n)がa[n]と同じなので、*(n + a)はn[a]と同じ。
つまり、、
a[n]はn[a]と書いても同じ!!!
a[5]を5[a]と書いても同じ意味(気持ち悪い。)
配列とポインタはすっごい密接。

う?ん。なんとなくという感じ。次は多次元配列。

int a[5][2] の配列はメモリ上では以下のように並んでいる。

a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[3][0]
a[3][1]
a[4][0]
a[4][1]

1次元の配列と同様に「a」はa[0][0]のアドレスを表している。

配列の初期化方法
int a[5][2] = {0,1,2,3,4,5,6,7,8,9};
もしくは
int a[][2] = {0,1,2,3,4,5,6,7,8,9};
最初の要素数のみ省略が可能。

メモリに並ぶ順番⇒「多次元配列は、一番外側(右)から回る

a[2][0]
は式の中に2つの[]が存在している。
演算子の優先順位は等しいので結合規則 通りに→で評価される。
そのため(a[2])[0]と書いても同じ。
a[2]を仮に「A」と置き換えた場合A[0]となり、一次元の配列のように見える。
するとAが配列名で、A[0]のアドレスを表していることになる。
またA[0]は*(A + 0)とも書けた。
これを元に戻すと、、
*(a[2] + 0)
a[2]自体は*(a + 2)とかけたのでa[2][0]は・・・
*( *(a + 2) + 0 )
と書ける。


以上のことを確かめるプログラムを書いてみる。
#include <stdio.h>

int main() {
  int a[3][2] = {10, 20, 30, 40, 50, 60};
  int i, j;

  /* 各要素の値を確かめる */
  for( i = 0; i < 3; i++ ) {
    for( j = 0; j < 2; j++ ) {
      printf( "a[%d][%d] = %d\n", i, j, a[i][j] );
    }
  }
  printf( "\n" );

  /* 各要素のアドレスを確かめる */
  for( i = 0; i < 3; i++ ) {
    for( j = 0; j < 2; j++ ) {
      printf( "&a[%d][%d] = %p   (a[%d] + %d) = %p\n",
              i, j, &a[i][j], i, j, ( a[i] + j ) );
    }
  }
  printf( "\n" );

  /* a[m][n]を*(*(a + m) + n)と書けるのか確かめる */
  for( i = 0; i < 3; i++ ) {
    for( j = 0; j < 2; j++ ) {
      printf( "*(*(a + %d) + %d) = %d\n",
              i, j, *(*(a + i) + j));
    }
  }

  return 0;
}


実行結果
a[0][0] = 10
a[0][1] = 20
a[1][0] = 30
a[1][1] = 40
a[2][0] = 50
a[2][1] = 60

&a[0][0] = 0xbf97b3e0   (a[0] + 0) = 0xbf97b3e0
&a[0][1] = 0xbf97b3e4   (a[0] + 1) = 0xbf97b3e4
&a[1][0] = 0xbf97b3e8   (a[1] + 0) = 0xbf97b3e8
&a[1][1] = 0xbf97b3ec   (a[1] + 1) = 0xbf97b3ec
&a[2][0] = 0xbf97b3f0   (a[2] + 0) = 0xbf97b3f0
&a[2][1] = 0xbf97b3f4   (a[2] + 1) = 0xbf97b3f4

*(*(a + 0) + 0) = 10
*(*(a + 0) + 1) = 20
*(*(a + 1) + 0) = 30
*(*(a + 1) + 1) = 40
*(*(a + 2) + 0) = 50
*(*(a + 2) + 1) = 60


おんなじだ?。

少し実用的に使ってみる。

#define NO 3 /* NOを3と定義 */

#include <stdio.h>

int main() {
  int point[][2] = {
    80, 80,          /* 一人目の点数 英語、数学 */
    100, 98,         /* 二人目の点数 英語、数学 */
    60, 80,          /* 三人目の点数 英語、数学 */
  };

  int i, j, sum = 0, p_sum[NO];
  double ave;

  /* 英語の平均点を求める */
  for( i = 0; i < NO; i++ ) {
    sum += point[i][0];
  }
  ave = (double)sum / NO;
  printf( "英語の平均点は%5.1f点です\n", ave );

  /* 数学の平均点を求める */
  for( i = 0; i < NO; i++ ) {
    sum += point[i][1];
  }
  ave = (double)sum / NO;
  printf( "数学の平均点は%5.1f点です\n", ave );

  /* 個人別の合計点数を求める */
  for( i = 0; i < NO; i++ ) {
    p_sum[i] = 0;
  }

  for( i = 0; i < NO; i++ ) {
    for( j = 0; j < 2; j++ ) {
      p_sum[i] += point[i][j];
    }
    printf( "出席番号%dの総得点 %d\n", i + 1, p_sum[i] );
  }
  return 0;
}

実行結果
英語の平均点は 80.0点です
数学の平均点は166.0点です
出席番号1の総得点 160
出席番号2の総得点 198
出席番号3の総得点 140

配列はこんなところで。
猫でもわかるC言語プログラミング (猫でもわかるプログラミングシリーズ)
粂井 康孝
ソフトバンククリエイティブ
売り上げランキング: 141668
おすすめ度の平均: 4.0
3 悪いわけではありませんが
5 猫の好きな私に向いた入門書
5 困ったもんだ
3 手前のレビュアー達の書く通り
4 猫でもわかるかな?

C言語:ポインタ突入

| コメント(4) | トラックバック(0)
※C言語初心者の為、内容に不備がある可能性があります。

ポインタ突入。

変数の実際のアドレスを調べるには変数に「&」をつければいいみたい。

「&hoge」は変数hogeのアドレスを表す。

「&」はアドレス演算子って言う。 「アンパサンドのアはアドレスのア!」

printf関数でのアドレスを表す変換指定子は「%p」


/* address01.c */

#include <stdio.h>

int main() {
  char c;
  int i;
  double d, e;
  printf( "変数cのアドレスは%pです\n", &c );
  printf( "変数iのアドレスは%pです\n", &i );
  printf( "変数dのアドレスは%pです\n", &d );
  printf( "変数eのアドレスは%pです\n", &e );
  return 0;
}


実行結果
$ ./address01.exe 
変数cのアドレスは0xbf9ec85fです
変数iのアドレスは0xbf9ec858です
変数dのアドレスは0xbf9ec850です
変数eのアドレスは0xbf9ec848です
この変数のアドレスを格納するための変数がポインタ(pointer)

ポインタを使うためには一般の変数と同じようにブロックの最初に宣言する必要がある。
int 型の変数アドレスを格納するポインタを「pi」とすると

int *pi;

というように宣言する。

「*」これをポイント宣言子(pointer declarator)と言う。


int a, b;
int *pa;

a = 5;
pa = &a;
b = *pa; 


最後の行の「*pa」は変数aの値を表す。
この場合の「*」のことを間接参照演算子
(indirection refarence operator)と言う。
変数の型によってポインタの宣言が違うのは間接参照で値を代入する
ことがあるからでいいのかな?

サンプル。


/* pointer01.c */

#include <stdio.h>

int main() {
  int a;
  int *lpa;
  lpa = &a;

  printf( "適当な数字を入力-----" );
  scanf( "%d", &a );
  printf( "変数aに%dが代入されました。\n", a );
  printf( "変数aのアドレスは%pです。\n", &a );
  printf( "変数aを指しているポインタはlpaです。\n" );
  printf( "*lpaの値は%dです。\n", *lpa );
  return 0;
}

実行結果
$ ./pointer01.exe 
適当な数字を入力-----10
変数aに10が代入されました。
変数aのアドレスは0xbfc5423cです。
変数aを指しているポインタはlpaです。
*lpaの値は10です。
※初期化されていないポインタには何が入っているかわからないよ。

次に参照呼出しの例。
C言語には参照呼出しが無い!
でも引数にアドレスを渡せば同じ効果が得られる。



/* swap.c */

#include <stdio.h>

void swap(int *, int *);

int main() {
  int a, b;

  a = 10;
  b = 20;

  swap( &a, &b );

  printf( "a = %d, b = %d\n", a, b );

  return 0;
}

void swap( int *x, int *y ) {
  int z;

  z = *y;
  *y = *x;
  *x = z;
  return;
}


$ ./swap.exe 
a = 20, b = 10

変数の値を関数に変更してもらうには、変数のアドレスを渡す!

ポインタのポインタ
#include <stdio.h>

int main() {
        int a;
        int *p;
        int **pp;

        p = &a;
        pp = &p;

        **pp = 10;

        printf( "a = %d, *p = %d, **pp = %d\n", a, *p, **pp );

        return 0;
}

実行結果
a = 10, *p = 10, **pp = 10


&とか*とかまだなれないけど意味はわかる。

C言語:関数

| トラックバック(0)
※C言語初心者の為、内容に不備がある可能性があります。

C言語の勉強の続き

今日は関数を書いてみる。

/* function01.c */

#include <stdio.h>

int func( int, int );

int main() {
  int a,b,c;
  a = 10;
  b = 20;
  c = func( a, b );
  printf( "%d * %d = %d", a, b, c );
  return 0;
}

int func( int a, int b ) {
  int c;
  c = a * b;
  return c;
}


実行結果
10 * 20 = 200


最初のほうに書いた「int func(int int);」はプロトタイプ宣言というやつ。
標準関数であるprintfとかもstdio.hにプロトタイプ宣言が書いてあるらしい。
ボトムアップ式(自作関数の定義を先に書いて最後にmain関数を書く)の場合はプロトタイプ宣言は不要。
何も返さない関数の場合は「void」を利用する。
void 関数名(引数の並び) {
    ・・・・
    return; /* return のみで何も返さない */
}



引数を持たない関数の場合は以下。
戻り値の型 関数名( void ) {
    ・・・・
    return 戻り値;
}



次はスコープ関係。
/* span01.c */

#include <stdio.h>

int add(int);

int main() {
  int sum;

  sum = add(10);
  printf( "sum = %d\n", sum );

  sum = add(20);
  printf( "sum = %d\n", sum );

  sum = add(100);
  printf( "sum =%d\n", sum );

  return 0;
}

int add( int x ) {
  static int gokei = 0;
  gokei += x;
  return gokei;
}

実行結果
$ ./span01.exe 
sum = 10
sum = 30
sum =130


「static」をつけないとaddに渡した引数がそのまま出ちゃいます。
「static」は記憶クラス指定子(storage class specifier)というものの一つ。
他には「extern」、「auto」、「register」がある。普通に変数を宣言するとauto扱い?
関数外で変数を宣言すると「グローバル変数」になる。

関数の再帰呼び出し。
階乗を求めるプログラムを書いてみる。
/* recursive02.c */

#include <stdio.h>

int kaijyo( int );

int main() {
  int i;
  for( i = 0; i < 11; i++ ) {
    printf( "%d! = %d\n", i, kaijyo(i) );
  }
  return 0;
}

/*
int kaijyo( int n ) {
  if( n == 0 ) {
    return 1;
  } else {
    return n * kaijyo( n - 1 );
  }
}
*/

/* 条件演算子を使ってみる */
int kaijyo( int n ) {
  return ( n == 0 ) ? 1 : n * kaijyo( n - 1 );
}


実行結果。
$ ./recursive02.exe 
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

「kaijyo(4)」の場合の戻り値の例。
4 * kaijyo(3)
4 * ( 3 * kaijyo(2) )
4 * ( 3 * ( 2 * kaijyo(1) ) )
4 * ( 3 * ( 2 * ( 1 * kaijyo(0) ) ) )
4 * ( 3 * ( 2 * ( 1 * 1 ) ) )

再帰呼び出しはスタック領域のメモリを大量に消費するから多用は避けたほうがいいとのこと。
Lispとかだと話しは違うと思うけど。。

猫でもわかるC言語プログラミング (猫でもわかるプログラミングシリーズ)
粂井 康孝
ソフトバンククリエイティブ
売り上げランキング: 46045
おすすめ度の平均: 4.0
3 悪いわけではありませんが
5 猫の好きな私に向いた入門書
5 困ったもんだ
3 手前のレビュアー達の書く通り
4 猫でもわかるかな?

2012年1月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

ウェブページ

このアーカイブについて

このページには、2010年4月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2010年3月です。

次のアーカイブは2010年5月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 5.01