プログラミング 10 - Soft Forum P.C. Club

構造体, ポインター演算

構造体

さて, 座標計算をしたいとしましょう. x, yの値を10増やす関数を作るとします. doubleのxとyを値にとって, x, yの値を変化させなければならないのですから, 当然ポインタで渡さなければいけませんね.

void add_10(double* x, double* y) {
  *x+=10;
  *y+=10;
}

これはどうですか? まず, x,yのポインターを取らなければならない時点で厄介です. 2つの値を変更する必要があるからですね. かつ, もうひとつ問題があります. それは, 私たちが「add_10関数は1つ目の引数がx, 2つ目の引数がy」というのを覚えておかなければいけないということです. これをうっかりひっくり返してもコンパイル自体はエラーにならず(どちらもdouble*), 実行結果が変になるという非常に面倒なbugを生みます.

また, x,yを別々に管理しなければいけません. x/yはセットで管理したいところです.


これを解決するかもしれないのが構造体です. 構造体は, 複数の値をセットにしてまとめたものです.

struct point {
  double x;
  double y;
};

構造体(struct)のpointを定義しています. これはdouble xとdouble yをまとめてひとつの型にしたもので, 以下のように使います.

#include <stdio.h>

struct point {
  double x;
  double y;
};

int main(int argc, const char *argv[]) {
  struct point a;
  a.x = 20;
  a.y = 20;
  return 0;
}

struct pointという型を定義しています. そして, それをつかって変数aを定義. それぞれxとyに20という値を代入しています.

struct のそれぞれの値(double xやdouble yのこと)をメンバといいます. また, dot(.)をメンバアクセス演算子といい, これを使ってメンバにアクセスします.

また,

#include <stdio.h>

struct point {
  double x;
  double y;
};

int main(int argc, const char *argv[]) {
  struct point a;
  struct point* p;
  p = &a;
  p->x = 20;
  p->y = 20;
  return 0;
}

のようにstructのpointerもとることができます. また->というものがありますが, これは間接メンバアクセス演算子(もしくはアロー演算子)といい, structのpointerからメンバにアクセスするためのものです.

つまり,

struct point a;
struct point* p
p = &a;

a.x;
(&a)->x;
p->x;
(*p).x;

の下4つは全部同じですね.

ではためしに2つのvectorの内積をとるprogramを書いてみましょう.

#include <stdio.h>

struct vector {
  double x;
  double y;
};

struct vector inner_product(struct vector, struct vector);

int main(int argc, const char *argv[]) {
  struct vector a = {20, 20},
                b = {0,  40},
                res;
  res = inner_product(a, b);
  printf("(20, 20) * (0, 40) = (%.0f, %.0f)\n", res.x, res.y);
  return 0;
}

struct vector inner_product(struct vector a, struct vector b) {
  struct vector result;
  result.x = a.x * b.x;
  result.y = a.y * b.y;
  return result;
}

structを使っているので値をひとつにまとめる, よってポインタを使わなくても返り値でとることができるのに注意してください.

また, これが全部doubleなら,

void inner_product(double, double, double, double, double*, double*);

とかになりますね, どれがaのxでどれがa.yか, どう考えても間違えそうです.

配列の初期化のように{ }でくくって値を与えていますね.

この場合,

struct vector {
  double x;
  double y;
};

の順なので, 1つめはx, 2つめはyの初期化に使われます.


ではここで, 少し試してみましょう,

#include <stdio.h>

struct vector {
  double x;
  double y;
};

int main(int argc, const char *argv[]) {
  struct vector a = {20, 20};
  double b = 20;
  int i = 10;
  printf("struct vector : %d\n"
         "double : %d\n"
         "int : %d\n", sizeof(a), sizeof(b), sizeof(i));
  return 0;
}

sizeof演算子は, 渡された引数のメモリ上の大きさを返します.

int i = 20;
sizeof(i);
sizeof(int);

変数を入れたり, 型を入れたりすることができます.

sizeを見ると, おそらく

struct vector : 16
double : 8
int : 4

となっているのではないでしょうか?

この場合, ちょうどstruct vectorはdouble2つ分の大きさになっていますね.

ちょっと考えてください. intの大きさ, 4が最適だとして, 16の大きさを2つもcopyしているのは少し問題があるのではないでしょうか?

なので, 先ほどの内積のprogram, 戻り値はstruct vectorでいいので, 引数のstruct vectorをpointerに変えてみましょう.

解答


ポインター演算

恐怖のポインターその2

配列のイメージを覚えていますか?

int 配列 int[5]

+-------------------+
|int|int|int|int|int|
+-------------------+

配列は値がpointerに格下げされます.

int* ptr;
int test[10] = {0};
ptr = test;

このとき, ptrはtestの先頭のポインタをさしています.

+-------------------+
|int|int|int|int|int|
+-------------------+
  |
 ptr

ポインタは++/--などすることができます.

例えば, 今ptrが,

+-------------------+
|int|int|int|int|int|
+-------------------+
  |
 ptr

のとき, ++ptrは,

+-------------------+
|int|int|int|int|int|
+-------------------+
      |
     ptr

をさします. intひとつ分ずれています.

よって, 配列を回るなどのとき, 次のようにすることもできます.

#include <stdio.h>

int main(int argc, const char *argv[]) {
  int test[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  int i;
  int* ptr = test;
  for (i = 0; i < 10; ++i) {
    printf("%d\n", *(ptr++));
  }

  return 0;
}
test[0];

は,

*(ptr+0)

と同じ意味だったのです.

Soft Forum P.C. Club