配列とポインタ

配列は、データがメモリ上に順に並んでいるので、ポインタと相性がいい。前のエントリでさらっと書いたけど、配列名を添字を付けずに使うと、先頭を指すポインタを返す。

int a[10];
int *p;

p = a;

配列の要素にポインタでアクセス

配列の各要素には、添字を使ってアクセスするほかにポインタを使ってもアクセスできる。

#include

int main(void)
{
    int a[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
    int *p;

    p = a;

    printf("%d %d %d\n", a[0], a[1], a[2]); /* access with index */
    printf("%d %d %d\n", *p, *(p + 1), *(p + 2)); /* access with pointer */

    return 0;
}
takatoh@nightschool $ ./sample_6_3a
10 20 30
10 20 30

2次元配列

2次元配列の場合は、ポインタを型キャストしてやる必要がある。これはよくわからないな。次のような float 型の2次元配列の要素にアクセスするには、float 型のポインタでいいんじゃなかろうか。

#include

int main(void)
{
    float balance[5][5] = {
        { 1.0, 2.0, 3.0, 4.0, 5.0},
        { 6.0, 7.0, 8.0, 9.0, 10.0},
        {11.0, 12.0, 13.0, 14.0, 15.0},
        {16.0, 17.0, 18.0, 19.0, 20.0},
        {21.0, 22.0, 23.0, 24.0, 25.0}
    };
    float *p;

    p = balance;

    printf("%f\n", *p);
    printf("%f\n", *(p + 1));

    return 0;
}

これを試してみると:

takatoh@nightschool $ gcc sample_6_3b.c -o sample_6_3b
sample_6_3b.c: In function ‘main’:
sample_6_3b.c:15:3: warning: assignment from incompatible pointer type [enabled by default]
 p = balance;
   ^
takatoh@nightschool $ ./sample_6_3b
1.000000
2.000000

出力結果は予想どおりだけど、コンパイル時に warning が出ている。互換性のないポインタを代入している、ということらしい。でもちゃんと動いてるんだけど…。

ポインタへの代入を次のように直すと warning は出なくなった。

#include

int main(void)
{
    float balance[5][5] = {
        { 1.0, 2.0, 3.0, 4.0, 5.0},
        { 6.0, 7.0, 8.0, 9.0, 10.0},
        {11.0, 12.0, 13.0, 14.0, 15.0},
        {16.0, 17.0, 18.0, 19.0, 20.0},
        {21.0, 22.0, 23.0, 24.0, 25.0}
    };
    float *p;

    p = (float *) balance;

    printf("%f\n", *p);
    printf("%f\n", *(p + 1));

    return 0;
}
takatoh@nightschool $ gcc sample_6_3c.c -o sample_6_3c
takatoh@nightschool $ ./sample_6_3c
1.000000
2.000000

ポインタの添字

ポインタが配列を指しているときに限って、ポインタに添字をつけることができる。

#include

int main(void)
{
    char str[] = "Pointer is interesting.";
    char *p;
    int i;

    p = str;

    for (i = 0; p[i]; i++) {
        printf("%c", p[i]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./sample_6_3d
Pointer is interesting.

配列のアドレスを使う

最初に書いたように、配列名を添字を付けずに書くとその配列の先頭を指すポインタを返す。なのでこれを普通のポインタの代わりに使うこともできる。

#include

int main(void)
{
    char str[80];

    *(str + 3) = 'c';

    printf("%c\n", *(str + 3));

    return 0;
}
takatoh@nightschool $ ./sample_6_3e
c

[追記]

コメントをもらった。配列名を添字なしで使ったときにポインタのように振る舞うのは、暗黙の型変換が行われるからだそう。配列名の型自体はあくまで配列だと。
ふーむ、なるほど。でも、参照するときに限って言えば、暗黙の型変換のおかげで実質的にポインタとして(というかポインタの代わりに)使えるってことでいいのかな。

ポインタの演算

ポインタは、整数の加算と減算だけができる。

int *p;

...

p = p + 1;

ポインタの加算・減算は整数のそれと意味が異なる。ポインタはデータのアドレスを格納しているので、ポインタに 1 加算すると、次のデータを指すようになる。減算の場合は逆だ。

#include

int main(void)
{
    int ary[] = {1, 2, 3, 4, 5};
    int *p;

    p = ary;

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

    p = p + 1;
    printf("%d\n", *p);

    return 0;
}
takatoh@nightschool $ ./sample_6_2a
1
2

上の例では、9行目で p に配列 ary のアドレスを代入している。この時点で p は ary の先頭の要素を指しているので、11行目の出力では、1 が出力されている。
その後、p に 1 加算すると、今度は ary の2番目の要素を指すようになる。なので、14行目の出力では 2 が出力されている。

ポインタの演算には、インクリメント演算子・デクリメント演算子も使える。

p++;
p--;

整数のバイト同じく 1 加算したり減算したりするんだけど、上に書いたように制すのものとは意味が違う。

インクリメント演算子・デクリメント演算子を間接参照と一緒に使うときには注意が必要だ。次のように書くと、ポインタ自体がインクリメントされたあと間接参照が行われる。

*p++;

参照しているデータをインクリメントしたいときにはカッコをつける。

(*p)++;

次のプログラムでは、最初の出力ではポインタ自体がインクリメントされて配列の2番めの要素を指すようなった結果 20 と出力され、2番めの出力ではポインタの指している配列の要素がインクリメントされて 21 と出力される。

#include

int main(void)
{
    int narray[] = {1, 20, 300, 4000};
    int *p = narray;

    *p++;
    printf("%d\n", *p);

    (*p)++;
    printf("%d\n", *p);

    return 0;
}
takatoh@nightschool $ ./sample_6_2b
20
21

ポインタ

ポインタは別の変数(データ)のアドレスを保持する変数。p という変数(ポインタ)が別の変数 q のアドレスを保持しているとき、「p は q を指している」という。

ポインタの宣言

ポインタを宣言する、一般的な形式は次の通り。

型 *変数名;

ポインタは変数名の前に * (アスタリスク)をつけて宣言する。

ポインタ演算子

* と & の2つのポインタ演算子がある。両方とも変数名の前につけて使う。* はポインタの指しているデータの値を返す。& は変数のアドレスを返す。
次のプログラムは、int 型のポインタ p と変数 q を使っている。

#include

int main(void)
{
    int *p, q;

    q = 199;
    p = &q;

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

    return 0;
}

9行目で q のアドレスを p に代入し、11行目で p を使って q の値を表示している。ポインタを通じて変数の値を参照することを「間接参照(indirection)」という。結果として、199 と表示される。

takatoh@nightschool $ ./sample_6_1a
199

間接参照は変数に値を代入するのにも使える。

int *p, q;

p = &q;
*p = 199;

これで q に 199 が代入された。

ポインタとデータ型

型は重要だ。これによって、コンパイラはポインタの指しているデータがどれくらいのメモリ領域を使うかを知ることができる。
次のプログラムは、int 型のポインタを使って double のデータを代入しているため、正しく出力しない。

#include

int main(void)
{
    int *p;
    double q, temp;

    temp = 1234.56;

    p = &temp;
    q = *p;

    printf("%f\n", q);

    return 0;
}
takatoh@nightschool $ gcc sample_6_1b.c -o sample_6_1b
sample_6_1b.c: In function ‘main’:
sample_6_1b.c:11:7: warning: assignment from incompatible pointer type [enabled by default]
     p = &temp;
       ^
takatoh@nightschool $ ./sample_6_1b
1889785610.000000

urllibでユーザーエージェントを変更する

Python の urllib.urlopen や urllib.urlretrieve で URL にアクセすると、FancyURLopener オブジェクトが生成されて、これが目的の URL にアクセスする。ユーザーエージェントの値もこの FancyURLopener オブジェクトが持っている。なので、これを変えてやればいい。次のように FancyURLopener のサブクラスを作ってユーザーエージェントの値を設定し、urllib._urlopenr に代入してやればいい。

class Myopener(urllib.FancyURLopener):
    version = "Mozilla"

urllib._urlopener = Myopener()

これで、urllib のデフォルトでは跳ね返されてしまう URL にもアクセスできるようになる。

参考ページ:

 cf. http://docs.python.jp/2/library/urllib.html#urllib._urlopener

文字列の配列

文字列の配列は、しばしば文字テーブルと呼ばれる。次の例は、(ヌル文字を含めて)40文字の文字列を10個格納できる。

char names[10][40];

各文字列にアクセスするには、左の添字だけを書いてやる。例えばキーボードから読み取った文字列を3番目に格納するには、

gets(names[2]);

とする。同様に、5番目の文字列を出力するには、

printf(names[4]);

とする。

次のプログラムは、文字テーブルに 0 から 9 を意味する単語を格納しておき、ユーザーが入力した数値に対応する単語を出力する。

#include

int main(void)
{
    char digits[][10] = {
        "zero",
        "one",
        "two",
        "three",
        "four",
        "five",
        "six",
        "seven",
        "eight",
        "nine"
    };
    char num[2];

    printf("Input a number: ");
    gets(num);
    printf("%s\n", digits[num[0] - '0']);

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_5_5
Input a number: 3
three

配列の初期化

配列も初期化することができる。一般的な形式は次のとおり。

型 変数名[サイズ] = {値のリスト};

「値のリスト」は、定数をカンマで区切ったもの。当然だけどサイズよりも少なくなければいけない。例えば次のように。

int ary[5] = {1, 4, 9, 16, 25};

2次元の場合にも同様に初期化できる。

int sqr[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

文字列の初期化には2通りのやり方がある。1つは、1文字ずつカンマで区切って初期化する方法。この方法だとヌル文字を自分で追加する必要がある。次の例ではヌル文字を追加していない。

char str[3] = {'a', 'b', 'c'};

もう1つは、二重引用符で囲んだ文字列で初期化する方法。この方法だとコンパイラが自動的にヌル文字を追加するので、配列の大きさを初期化しようとする文字列よりも大きくしておく必要がある。

char str[5] = "Herb";

1次元の配列を初期化する場合は、配列のサイズを指定しなくても構わない。次のようにすると、コンパイラが要素数を数えて配列のサイズを決めてくれる。

int pwr[] = {1, 2, 4, 8, 16, 32, 64, 128};

サイズを明示しない配列を、「サイズ指定のない配列(unsized array)」と呼ぶ。
何かの都合で、初期化する要素数が変わっても、数えなおさなくていいので便利。

サイズ指定のない配列は2次元以上の場合には、最初の次元だけサイズを省略することができる。逆に言うと、2次元以上のサイズは省略が出来ない。

int sqr[][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

多次元配列

2次元以上の配列を作ることもできる。単に、サイズの指定を増やせばいいだけだ。

int ary2[4][5];
int ary3[3][3][3];

これらの配列は、メモリ上にはより右に書いてあるの次元の配列が連続して割り当てられる。言い換えると、最も低いアドレスから高いアドレスに順にアクセスしていくと、より右の添字のほうが早く変化する。
4次元以上についても同様。もっとも、そんな次元の配列を使う機会はそうなないだろう。

次のプログラムは、4×5の2次元配列を作って各添字の積を格納してから、行列形式で表示する。

#include

int main(void)
{
    int twod[4][5];
    int i, j;

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 5; j++) {
            twod[i][j] = i * j;
        }
    }
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 5; j++) {
            printf("%d ", twod[i][j]);
        }
        printf("\n");
    }
}
takatoh@nightschool $ ./sample_5_3
0 0 0 0 0 
0 1 2 3 4 
0 2 4 6 8 
0 3 6 9 12

文字列

C では文字列は、文字の配列だ。最もよく使われる1次元配列は文字列だそうだ。文字列は1文字ずつ配列の要素に格納され、最後にヌル文字(0)がつく。
文字列に関する関数をいくつか見てみよう。

gets()

gets() はキーボードから文字列を読み取る。ユーザーが文字列に続いて Enter キーを押すと、gats() は改行文字をヌル文字に置き換えて返す。

#include

int main(void)
{
    char str[80];
    int i;

    printf("Input string (less than 80).: ");
    gets(str);
    for (i = 0; str[i]; i++) {
        printf("%c", str[i]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ gcc sample_5_2a.c -o sample_5_2a
sample_5_2a.c: In function ‘main’:
sample_5_2a.c:10:5: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
     gets(str);
     ^
/tmp/ccMGfyBh.o: 関数 `main' 内:
sample_5_2a.c:(.text+0x2e): 警告: the `gets' function is dangerous and should not be used.

おっと、何やら警告が出た。gets は危険だから使うべきじゃない、と。まあ、そのとおりらしい。本には gets は文字の長さをチェックしないので受け入れる配列におさまるかどうかプログラマが注意しなければいけない、と書いてある。
コンパイル自体はできてるようだし、今回はサンプルなのでこのまま使うことにしよう。

takatoh@nightschool $ ./sample_5_2a
Input string (less than 80).: Hello, this is Takatoh!
Hello, this is Takatoh!

このプログラムでは、文字列終端のヌル文字が 0 (偽)であることを利用している。

strcpy()

文字列ソースをターゲットにコピーする。string.h が必要。

strcpy(ターゲット, ソース);

使い方はこんな感じ。

char str[80];

strcpy(str, "Hello, world.");
printf("%s\n", str);

strcat()

文字列ターゲットにソースを連結する。string.h が必要。

strcar(ターゲット, ソース);

次のようにすると、「hello, threr」と表示する。

char str[80];

strcat(str, "hello,");
strcat(str, " there");

printf(str);

strcmp()

2つの文字列を比較する。string.h が必要。

strcmp(文字列1, 文字列2);

文字列はコード順に大きさを比較(コードが小さい方の文字列が小さいと判定される)され、文字列1と文字列2が同じ大きさなら 0 を返す。文字列1のほうが小さければ 0 より小さい値を、大きければ 0 より大きい値を返す。

strlen()

文字列の長さを返す。ヌル文字は含まれない。string.h が必要。

strlen(文字列);

練習問題

次のプログラムは、文字列を読み取り、それを逆の順序で表示する。

#include
#include

int main(void)
{
    char str[80];
    int i;

    printf("Input string (less than 80): ");
    gets(str);

    i = strlen(str);

    for ( ; i; i--) {
        printf("%c", str[i - 1]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./practice_5_2
Input string (less than 80): hello, world.
.dlrow ,olleh

配列

(1次元の)配列を宣言する一般的な形式は次の通り。

型 変数名[サイズ];
  • 配列の要素はすべて同じ型
  • 要素にアクセスするには変数名に大カッコ([])で囲んだ添字をつけてアクセスする
  • 添字は 0 から始まる
  • 配列は連続したメモリ領域に格納される
  • 配列をまるごと代入(コピー)することは出来ない。要素を1つずつ代入(コピー)する

要素を1つずつコピーしなきゃいけないのな面倒だな。

次のプログラムは、1 から 10 までの平方値を、配列 sqrs に格納してから表示する。

#include

int main(void)
{
    int sqrs[10];
    int i;

    for (i = 1; i < 11; i++) {
        sqrs[i - 1] = i * i;
    }
    for (i = 0; i < 10; i++) {
        printf("%d ", sqrs[i]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./sample_5_1
1 4 9 16 25 36 49 64 81 100

もうひとつ、大事なこと。
C では配列の添字の範囲をチェックしない。例えばサイズ 10(添字は 0〜9)の配列の11番目の要素(添字10)にもアクセスできてしまう。もちろん正しい動作にはならないので、場合によってはプログラムがクラッシュするかもしれない。
ちょっと試してみよう。

#include

int main(void)
{
    int ary[10];
    int i;

    for (i = 1; i < 11; i++) {
        ary[i - 1] = i;
    }

    printf("%d\n", ary[10]); /* out of range */

    return 0;
}
takatoh@nightschool $ gcc sample_5_1a.c -o sample_5_1a
takatoh@nightschool $ ./sample_5_1a
0

何事もないかのようにコンパイルされたけど、実行すると、代入したはずのない ary[10] の出力として、0 と表示された。
何度か試してみたけど、毎回 0 と表示される。これはたまたまかもしれないし、gcc がそうしているのかもしれない。どちらにせよ、配列の要素にアクセスするときには添字の範囲に注意が必要だ。

型キャスト

値の型を明示的に変換する方法を型キャスト(type cast)という。型キャストの一般亭な形式は次の通り。

(型) 値

次の例では、浮動小数点数を整数として表示するために int に型キャストしている。

#include

int main(void)
{
    float f;

    f = 100.5;

    printf("%d\n", (int) f);

    return 0;
}
takatoh@nightschool $ ./sample_4_7
100