初級C言語Q&A(online) 「質問と考察」ページ

[質問一覧] [記事一覧] [ホームページ]


解説

このページは、CQAに来た質問の回答を考える仮定を、 ほとんどそのままメモ的な感じで書きとめてみたものです。 プログラミングの途中の思考がどうなっているか知りたい方へのヒントになるかもしれないと思いまして、 あえて公開を試みています。 基本的に独り言ということで、 表現がぞんざいになりがちですが、その点はご了承ください。


2003-05-30

Q10 【構造体のメンバの値をセットする】

(ペンネーム jun さん)

下記のプログラミングに不足している部分を 加えて、struct gazou型の変数Imageの struct color型の変数akarusaのred,green,blue の平均をkidoに代入するプログラムを作成したい。

というご質問。 元のコード。

#include<stdio.h>

struct color{
int red,green,blue,kido;
};
struct zahyou{
int x,y;
};
struct gazou{
struct color akarusa;
struct zahyou zahyou1;
};
void print_color(struct color c)
{
printf("color=(R:%d,G:%d,B:%d),%d\n",
c.red,c.green,c.blue,c.kido);
}
void print_zahyou(struct zahyou z)
{
printf("zahyou=(%d,%d)\n",z.x,z.y);
}
void print_gazou(struct gazou g)
{
print_color(g.akarusa);
print_zahyou(g.zahyou1);
}

int main(void)
{
struct gazou Image={{20,40,90},{240,180}};


return 0;
}

プログラミングじゃなくてプログラムだと思うのだが、 それはどうでもいいとして、インデントの方が気になる。 インデントがないのはTABの扱いとかの問題かと勝手に想像してみたりするが、 とりあえずインデントしないと分からないのでやってみる。 自動的にインデントするツールはいくらでもあるが、 手でインデント付けると、だいたい全部まじめに読んだことになるので、 手作業でインデントすると、こんな感じに。

#include <stdio.h>

struct color {
    int red, green, blue, kido;
};

struct zahyou {
    int x,y;
};

struct gazou {
    struct color akarusa;
    struct zahyou zahyou1;
};

void print_color(struct color c)
{
    printf("color=(R:%d,G:%d,B:%d),%d\n",
        c.red, c.green, c.blue, c.kido);
}

void print_zahyou(struct zahyou z)
{
    printf("zahyou=(%d,%d)\n",z.x,z.y);
}

void print_gazou(struct gazou g)
{
    print_color(g.akarusa);
    print_zahyou(g.zahyou1);
}

int main(void)
{
    struct gazou Image = {{20,40,90},{240,180}};

    return 0;
}

余談だが、 本当にインデントができない人がたまにいるようだが、 処理の深さとか、ネストレベルとか、そのあたりの感覚が身に付いていないか、 あるいは、全く理解していないのだと思う。 このソースの中にはループは出てこないのだが、

    int n;
    for (n = 0; n < 100; n++) {
        foo(n);
    }

こんな処理があったとして、 int n; とか、for とかいう処理の「中に」foo(n); がある、 というような感覚があるかどうかの話なのだが、 まあそれは別の機会に。

で、皆さんはこれを見てまず何を感じるだろうか? 私の場合、 即座にひっかかったのは、kido に平均値を入れるという話なのだが。 kido というのは、多分輝度のことだと思うのだが、 だったらRGBの平均値なんてことは聞いたことがない。 RGBとYUVの変換でよく出てくるのは、 Y = 0.299*R + 0.587*G + 0.114B とか、 Y = 0.3*R + 0.59*G + 0.11*B といった感じの変換式のはずなのだが、 今回は質問した方が明確に「平均値をkidoに入れろ」と言っているのだから、 こだわらない。

余談だが、 輝度を英語にすると何なのだろう? brightness だろうか? Eijiro で調べたら、 輝度調節は「intensity control」で、 輝度調節ツマミは「brightness control」だった。 訳が分からない。 しかも、このコードには「akarusa」なんてものまで出てくるのだが、 「明るさ」は英語で何というのだろうか、 というか、 輝度と明るさの違いは? とか考え出すとまさに混乱するので、 これもいい加減な所でやめておこう。

多分オブジェクト指向的な発想だと、 gazou オブジェクトのメソッドに kido を設定するようなものがある、 というイメージなのだと思うが、 Cだとそうもいかない。 多分、struct gazou へのポインタを渡して中を変更する、 というのが自然なのだろう。 ポインタが p だとすれば、 処理の内容は、

    p->akarusa.kido = (p->akarusa.red + p->akarusa.green + p->akarusa.blue) / 3;

という感じで実に単純な話。

void set_kido(struct gazou *p)
{
    p->akarusa.kido = (p->akarusa.red + p->akarusa.green + p->akarusa.blue) / 3;
}

このあたりまで机上の空論で、 実は「p->akarusa、に括弧をつけなくてもいいのだっけ?」とかいうことを考えていたりする。 考える位なら、最初から付けておけばよさそうなものだが。 このレベルの話でもう一つ悩んだのは、次のような書き方がいいかという所だ。

void set_kido(struct gazou *p)
{
    struct color *pcolor;
    pcolor = &(p->akarusa);
    pcolor->kido = (pcolor->red + pcolor->green + pcolor->blue) / 3;
}

ちょっとくどいか。 最初の方が見た感じはいいかも。

もう一つ悩んでいたのが、 RGBの平均値がkidoに入るのなら、 RGBを指定した瞬間に輝度は決まってしまうわけで、 だったら、別に kido なんて変数持たなくてもいいのでは、 という話。 get_kido みたいな関数があって、

int get_kido(struct gazou p)
{
    return p.akarusa.red + p.akarusa.green + p.akarusa.blue) / 3;
}

というのを呼び出すようにすれば済む。 但し、この種のデータは当然画像処理に使うものとして、 かなりの回数の処理を想定しているのかもしれない。 所謂8:2ルールでいけば、よく使われる20%の処理というか、 全体の負荷の80%、というのに該当するとか。 そこで、効率を重視しようという話はあるかもしれない。

だったら、RGBを設定する所で計算するという手はあるのでは。 つまり、こうなる。

void set_kido(struct gazou *p, int red, int green, int blue)
{
    p->akarusa.red = red;
    p->akarusa.green = green;
    p->akarusa.blue = blue;

    p->akarusa.kido = (red + green + blue) / 3;
}

のような setter を作っておいて、 RGBをセットすればいいと思う。 ただ、元のコードだと、 Image の初期値は直接セットしている。 RGBを指定せずに座標値をセットすることってできたっけ?

何かC忘れてるのか、私…?。仕様書どこにやった?

まあそれはいいとして(よくないが)、 もう一つ根本的な考え方としては、 kidoに代入する処理をもっと上に持ってくる方法。

int main(void)
{
    struct gazou Image = {{20,40,90},{240,180}};

    Image.akarusa.kido =
        (Image.akarusa.red + Image.akarusa.green + Image.akarusa.blue) / 3;

    return 0;
}

あるいは、こう。

int main(void)
{
    struct gazou Image = {{20,40,90},{240,180}};

    Image.akarusa.kido = get_kido(Image);

    return 0;
}

何かどっちもヤだな。 どこがイヤなのかというと、 Imageの値を定義の時に指定しているのに、 その後でさらに指定しないと中途半端になってしまうというあたりが。 kido を設定し忘れたらどうなるのか、ということである。 だったら、前案の中では、 get_kido のような関数を用意しておく、というのが最も安全かもしれない。 しかし、このようなコードを本当に実用に使うのであれば、 Image のメンバの値を固定というのはあり得そうな話ではなく、 多分DBとかファイルから読み込んでセットするのだろうから、 だとすれば、やはり setter を使ってそこでセットするというのが定番だろうか。

このあたりで力尽きたので、今回はここまで。


【回答】

kido 以外の値をセットした状態から、 kido もセットした状態にしたいという趣旨でしょう。 例えば、

    struct gazou Image = {{20,40,90},{240,180}};

という状態から Image.akarusa.kido をセットするのであれば、

    Image.akarusa.kido = (Image.akarusa.red + Image.akarusa.green + Image.akarusa.blue) / 3;

とすればよいのですが、 変数が出てくる毎にこのような計算式を書くのは非現実的なので、 このような場合は構造体へのポインタを渡して関数内で処理するとよいでしょう。

void set_kido(struct gazou *p)
{
    p->akarusa.kido = (p->akarusa.red + p->akarusa.green + p->akarusa.blue) / 3;
}

このような関数を作っておけば、 Image.akarusa.kido のセットは、次のような関数呼び出しで済みます。

    set_kido(&Image);