Подтвердить что ты не робот

Передача указателей/ссылок на структуры в функции

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

Я работаю над 6-й главой K & R (structs), и до сих пор через книгу был достигнут большой успех. Я решил работать с структурами довольно сильно и поэтому много работал в начале главы с примерами и примерами. Одна из вещей, которую я хотел попробовать, заключалась в изменении функции canonrect (2nd Edition, 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;
}

Итак, вот вопросы:

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

Я подозреваю, что я уже знаю ответ на # 1, но №2 меня озадачивает - я думал, что законно передавать указатели.

В любом случае... прошу простить C newb.

4b9b3361

Ответ 1

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

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 не является указателем, это недопустимый синтаксис.

Ответ 2

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

Ответ 3

Похоже, вы сбиваете с толку оператора "разыменования" (*) с помощью оператора "address of" (&).

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

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

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

void canonRect(struct rect *r) {

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

canonRect(*r); 

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

Ответ 4

Возможно, вам захочется прочитать различные способы передачи (концептуально) параметров функций. 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. Вы не можете выделить пространство в стеке, просто объявив структуру, потому что эта память становится недействительной, когда функция возвращается.

Ответ 5

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


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


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

Ответ 6

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

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