web-dev-qa-db-ja.com

構造体へのポインタ/参照を関数に渡す

これはばかげた質問のように聞こえますが、私はまだCを学んでいるので、我慢してください。 :)

私はK&R(構造体)の第6章に取り組んでおり、これまでのところ、この本を通して大きな成功を収めています。私は構造体をかなり重く扱うことに決めたので、章の早い段階でポイントと正しい例を使って多くの作業を行いました。私が試したかったことの1つは、ポインターを介してcanonrect関数(第2版、p 131)の動作を変更し、それによってvoidを返すことでした。

私はこれを機能させていますが、皆さんが私を助けてくれることを望んでいた問題に遭遇しました。 canonRectで一時的な長方形オブジェクトを作成し、その変更を実行してから、渡されたポインターを一時的な長方形に再割り当てして、コードを簡略化する必要がありました。

しかし、そうしても、rectは変わりません。代わりに、渡されたrectのフィールドを手動で再入力します。これは機能します。

コードは次のとおりです。

_#include <stdio.h>

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

struct point {
    int x;
    int y;
};

struct rect {
    struct point lowerLeft;
    struct point upperRight;
};

// canonicalize coordinates of rectangle
void canonRect(struct rect *r);

int main(void) {
    struct point p1, p2;
    struct rect r;

    p1.x = 10;
    p1.y = 10;
    p2.x = 20;
    p2.y = 40;
    r.lowerLeft = p2; // note that I'm inverting my points intentionally
    r.upperRight = p1;

    printf("Rectangle, lower left: %d, %d; upper right: %d %d\n\n", 
        r.lowerLeft.x, r.lowerLeft.y, r.upperRight.x, r.upperRight.y);

    // can't pass a pointer, only a reference. 
    // (Passing pointers results in illegal indirection compile time errors)
    canonRect(&r); 
    printf("Rectangle, lower left: %d, %d; upper right: %d %d\n\n", 
        r.lowerLeft.x, r.lowerLeft.y, r.upperRight.x, r.upperRight.y);    
}

void canonRect(struct rect *r) {
    struct rect temp;
    temp.lowerLeft.x = min(r->lowerLeft.x, r->upperRight.x);
    temp.lowerLeft.y = min(r->lowerLeft.y, r->upperRight.y);
    temp.upperRight.x = max(r->lowerLeft.x, r->upperRight.x);
    temp.upperRight.y = max(r->lowerLeft.y, r->upperRight.y);

    r = &temp; // doesn't work; my passed-in rect remains the same

    // I wind up doing the following instead, to reassign all 
    // the members of my passed-in rect
    //r->lowerLeft = temp.lowerLeft;
    //r->upperRight = temp.upperRight;
}
_

だからここに質問があります:

  1. _r = &temp;_が機能しないのはなぜですか? (I thinkこれは、ポインターの代わりに参照を渡すためです。参照は変更できないが、ポインターは変更可能であると考えるのは正しいですか?)
  2. canonRectへのポインタを渡そうとすると、なぜ不正な間接コンパイル時エラーが発生するのでしょうか。 (つまり、canonRect(*r);main()がある場合。)

#1の答えはすでに知っていると思いますが、#2は私を困惑させます-ポインタを渡すことは合法だと思いました。

とにかく... Cニュービーを許してください。

13
John Rudy

あなたがやりたいことはこれだと思います:

void canonRect(struct rect *r) {
    struct rect temp;
    temp.lowerLeft.x = min(r->lowerLeft.x, r->upperRight.x);
    temp.lowerLeft.y = min(r->lowerLeft.y, r->upperRight.y);
    temp.upperRight.x = max(r->lowerLeft.x, r->upperRight.x);
    temp.upperRight.y = max(r->lowerLeft.y, r->upperRight.y);

    *r = temp; 
}

上記のコードでは、rect型の* rをrect型のtempに設定しています。

Re 1:rが指しているものを変更したい場合は、ポインターへのポインターを使用する必要があります。それが本当に必要なものである場合(上記を参照、実際には必要なものではありません)、ヒープ上の何かを指すようにする必要があります。 'new'またはmallocで作成されていないものを指すと、スコープから外れ、その変数に使用されなくなったメモリを指すことになります。

コードがr =&tempで機能しないのはなぜですか?

Rはrect *タイプだからです。つまり、rは、メモリにrectが含まれているメモリアドレスを保持する変数です。 rが指しているものを変更しても問題ありませんが、渡された変数は変更されません。

Re 2:*型宣言で使用されていない場合は、間接参照単項演算子です。これは、ポインタのアドレス内にあるものを検索することを意味します。したがって、* rを渡すことにより、ポインタをまったく渡すことはありません。 rはポインタではないので、これは無効な構文です。

18
Brian R. Bondy

また、変数 'struct rec temp'は、そのメソッドcanonRectが終了するとすぐにスコープから外れ、無効なメモリを指すことになります。

12
Joel Cunningham

'逆参照'演算子(*)と 'アドレス'演算子(&)を混同しているようです。

&rと書くと、rのアドレスを取得し、rへのポインタを返します(ポインタは変数の単なるメモリアドレスです)。つまり、実際には関数にポインタを渡しています。

*rを書くとき、rを逆参照しようとしています。 rがポインタの場合、rが指している値を返します。ただし、rはポインタではなく、四角形であるため、エラーが発生します。

混乱を招くために、ポインタ変数を宣言するときに*文字も使用されます。この関数宣言では:

void canonRect(struct rect *r) {

rstruct rectへのポインタとして宣言されています。これは、次のように*を使用することとは完全に異なります。

canonRect(*r); 

どちらの場合も、*文字はまったく異なるものを意味します。

5
Jeremy Ruten

パラメータを(概念的に)関数に渡すことができるさまざまな方法を読みたいと思うかもしれません。 Cは call-by-value であるため、rectへのポインターを関数に渡すと、ポインターのコピーが渡されます。関数が値rに直接(間接的にではなく)行った変更は、呼び出し元には表示されません。

関数が呼び出し元に新しい構造体を提供するようにしたい場合は、2つの方法があります。1。rectを返すことができます。2。ポインタをrectへのポインタに渡すことができます。

最初の方法はより自然です:

struct rect* canonRect(struct rect* r)
{
  struct rect* cr = (struct rect*) malloc(sizeof(struct rect));
  ...
  return cr;
}

2番目の方法は次のとおりです。

void canonRect(struct rect** r)
{
  *r = (struct rect*) malloc(sizeof(struct rect));
}

そして、発信者は以下を使用します。

   canonRect(&r);

しかし、呼び出し元は元のポインターを失い、構造体をリークしないように注意する必要があります。

どちらの手法を使用する場合でも、関数は、mallocを使用してヒープ上の新しい構造体にメモリを割り当てる必要があります。関数が戻るとそのメモリが無効になるため、構造体を宣言するだけではスタックにスペースを割り当てることはできません。

2
Rob Walker

まず、K&R cには「参照」の概念がなく、ポインタだけがあります。 _&_演算子は、「のアドレスを取る」ことを意味します。


次に、cannonRect()rはローカル変数であり、notrではありません。 main()で。ローカルのrポイントを変更しても、呼び出し元のルーチンのrには影響しません。


最後に、すでに述べたように、ローカル_struct rect_はスタックに割り当てられ、中括弧で範囲外になります。