Передача указателей / ссылок на структуры в функции - PullRequest
13 голосов
/ 02 ноября 2008

Это будет звучать как глупый вопрос, но я все еще изучаю C, поэтому, пожалуйста, потерпите меня. :)

Я работаю над 6-й главой K & R (структура), и до сих пор в книге были замечены большие успехи. Я решил довольно много работать со структурами, поэтому в начале главы проделал большую работу с точными и прямыми примерами. Одна из вещей, которую я хотел попробовать, - изменить работу функции canonrect (2nd Edition, p 131) с помощью указателей и, следовательно, вернуть void.

У меня это работает, но я столкнулся с ошибкой, я надеялся, что вы, ребята, сможете мне помочь. Я хотел canonRect создать объект временного прямоугольника, выполнить его изменения, а затем переназначить указатель, который передается во временный прямоугольник, упрощая таким образом код.

Однако, если я это сделаю, прямоугольник не изменится. Вместо этого я обнаруживаю, что заново заполняю поля прямоугольника, в который я прошел, и это работает.

Код следует:

#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; не работает? (Я думаю это потому, что я передаю ссылку вместо указателя; правильно ли я считаю, что ссылки не модифицируются, а указатели есть?)
  2. Почему я могу получить недопустимую ошибку компиляции косвенного обращения, если я пытаюсь передать указатель на canonRect? (То есть, если бы у меня было canonRect(*r); в main().)

Я подозреваю, что уже знаю ответ на # 1, но # 2 сбивает меня с толку - я думал, что это было законно, чтобы указатели вокруг.

В любом случае ... пожалуйста, прости новичка C.

Ответы [ 6 ]

18 голосов
/ 02 ноября 2008

Я думаю, что вы хотите сделать это:

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; 
}

В вышеприведенном коде вы устанавливаете * r, который имеет тип от rect до temp, который имеет тип rect.

Re 1: Если вы хотите изменить то, на что указывает r, вам нужно использовать указатель на указатель. Если это действительно то, что вы хотите (см. Выше, это не совсем то, что вы хотите), то вам нужно обязательно указать на что-то в куче. Если вы укажете на что-то, не созданное с помощью 'new' или malloc, это выпадет из области видимости, и вы будете указывать на память, которая больше не используется для этой переменной.

Почему ваш код не работает с r = & temp?

Потому что r имеет тип * rect. Это означает, что r - это переменная, которая содержит адрес памяти, память которого содержит прямоугольник. Если вы измените то, на что указывает r, это нормально, но это не изменит переданную переменную.

Re 2: * когда не используется в объявлении типа, это унарный оператор разыменования. Это означает, что он будет искать то, что находится внутри адреса указателя. Таким образом, передавая * r, вы вообще не передаете указатель. На лице, так как r не указатель, это неверный синтаксис.

12 голосов
/ 02 ноября 2008

Стоит также отметить, что ваша переменная struct rec temp выйдет из области действия, как только этот метод canonRect завершится, и вы укажете на недопустимую память.

4 голосов
/ 02 ноября 2008

Похоже, вы путаете оператор 'разыменования' (*) с оператором 'адреса' (&).

Когда вы пишете &r, он получает адрес r и возвращает указатель на r (указатель - это просто адрес памяти переменной). Итак, вы действительно передаете указатель на функцию.

Когда вы пишете *r, вы пытаетесь разыменовать r. Если r является указателем, он вернет значение, на которое указывает r. Но r не указатель, это прямоугольник, поэтому вы получите ошибку.

Чтобы сделать вещи более запутанными, символ * также используется при объявлении переменных указателя. В этом объявлении функции:

void canonRect(struct rect *r) {

r объявлен указателем на struct rect. Это полностью отличается от использования * следующим образом:

canonRect(*r); 

В обоих случаях символ * означает что-то совершенно другое.

2 голосов
/ 02 ноября 2008

Во-первых, в K & R c нет понятия «ссылки», только указатели. Оператор & означает «получить адрес».


Во-вторых, r в cannonRect() является локальной переменной, и не r в main(). Изменение места r не влияет на r в вызывающей подпрограмме.


Наконец, как уже отмечалось, локальный struct rect выделяется в стеке и выходит из области видимости при закрывающей скобке,

2 голосов
/ 02 ноября 2008

Возможно, вы захотите ознакомиться с различными способами, которыми параметры (концептуально) могут передаваться в функции. C равно call-by-value , поэтому, когда вы передаете указатель на прямоугольник в функцию, вы передаете копию указателя. Любые изменения, внесенные функцией в значение r напрямую (не косвенно), не будут видны вызывающей стороне.

Если вы хотите, чтобы функция предоставляла вызывающей стороне новую структуру, то есть два способа сделать это: 1. вы можете вернуть прямоугольник: 2. Вы можете передать указатель на указатель на прямоугольник в:

Первый путь был бы более естественным:

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

Второй способ будет:

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

и тогда вызывающий абонент будет использовать:

   canonRect(&r);

Но вызывающая сторона теряет свой оригинальный указатель, и вам следует быть осторожным, чтобы не пропустить структуры.

Какой бы метод вы ни использовали, функция должна будет выделять память для новой структуры в куче, используя malloc. Вы не можете выделить место в стеке, просто объявив структуру, потому что эта память становится недействительной, когда функция возвращает.

0 голосов
/ 04 ноября 2008

2.Почему я могу получить недопустимую ошибку во время компиляции косвенного обращения, если я попытаюсь передать указатель на canonRect? (IE, если бы у меня было canonRect (* r); в main ().)

Потому что это не цель указатель .

...