Как понять концепцию указателей (*) и операторов адреса (&)? - PullRequest
0 голосов
/ 27 февраля 2019

Я пытаюсь понять значение этих двух операторов, поэтому я написал этот код только для этой цели.

#include <stdio.h>
#include <string.h>

int main()
{
    char *mnemonic, *operands;

    mnemonic = "add";
    operands = "five to two";

    analyse_inst(mnemonic, operands);

}

void analyse_inst(char mnemonic, char operands)
{
    printf("%s", mnemonic);
    printf("%s", operands);
}

Однако я заметил, что он не будет работать, если я не изменю аргументыanalyse_inst() function to analyse_inst(char * mnemonic, char * operands), что означает, что я буду передавать указатели на функцию.Но почему это требуется?

Кроме того, я посмотрел вверх по поводу «передачи по ссылке».И согласно tutorialspoint.com, его определение:

Метод вызова по ссылке для передачи аргументов в функцию копирует адрес аргумента в формальный параметр.Внутри функции адрес используется для доступа к фактическому аргументу, используемому в вызове.Это означает, что изменения, внесенные в параметр, влияют на переданный аргумент.

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

Я где-нибудь ошибаюсь?

Как я могу изменить свой код так, чтобы я передавал две переменные по ссылке?

(PS Я прочитал другое переполнение стекатемы на ту же тему, но я был бы признателен, если бы кто-нибудь мог объяснить это в контексте кода, который я написал)

Ответы [ 4 ]

0 голосов
/ 27 февраля 2019

Я буду обсуждать вещи в контексте вашего кода, но сначала я хочу немного разобраться с основами.

В объявлении унарный оператор * указывает, что объявленная вещь имеет тип указателя:

T *p;       // for any type T, p has type "pointer to T"
T *p[N];    // for any type T, p has type "N-element array of pointer to T"
T (*p)[N];  // for any type T, p has type "pointer to N-element array of T"
T *f();     // for any type T, f has type "function returning pointer to T"
T (*f)();   // for any type T, f has type "pointer to function returning T"

Унарный оператор * имеет более низкий приоритетзатем операторы постфикса [] subscript и () function, так что если вы хотите указатель на массив или функцию, * должен быть явно сгруппирован с идентификатором.

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

int x;
int *p;
p = &x;  // assign the address of x to p
*p = 10; // assigns 10 to x via p - int = int

После выполнения вышеуказанного кода выполняются следующие условия:

 p == &x       // int * == int *
*p ==  x == 10 // int   == int   == int

Выражения p и &x имеют тип int * (указатель на int), а их значением является (виртуальный) адрес x.Выражения *p и x имеют тип int, а их значение равно 10.

Действительное 1 значение указателя объекта получается одним из трех способов (указатели на функции также важны, но мы не будем вдаваться в них здесь):

  • с использованием унарного оператора & в lvalue 2 (p = &x;);
  • выделение динамической памяти через malloc(), calloc() или realloc();
  • и, что важно для вашего кода, с использованием массива выражений без оператора & или sizeof.

За исключением случаев, когда это операнд оператора sizeof или унарный & или строковый литерал, используемый для инициализации массива символов в объявлении, выражение типа "массив N-элементов из T" преобразуется ("распадается") в выражение типа "указатель на T", а значением этого выражения является адрес первого элемента массива 3 .Таким образом, если вы создаете массив типа

int a[10];

и передаете выражение этого массива в качестве аргумента функции, подобной

foo( a );

, то перед вызовом функции выражение aпреобразуется из типа «10-элементный массив int» в «указатель на int», а значение a является адресом a[0].Таким образом, функция фактически получает значение указателя, а не массив:

void foo( int *a ) { ... }

Строковые литералы, такие как "add" и "five to two", являются выражениями массива - "add" имеет тип "4-элементный массив из char"five to two" имеет тип" массив из 12 элементов char "(для строки из N символов требуется не менее N + 1 элементов для хранения из-за ограничителя строки).

В операторах

mnemonic = "add";
operands = "five to two";

ни строковый литерал не является операндом операторов sizeof или унарных &, и они не используются для инициализации массива символов вобъявление, поэтому оба выражения преобразуются в тип char *, а их значения являются адресами первого элемента каждого массива.И mnemonic, и operands объявлены как char *, так что это нормально.

Поскольку типы mnemonic и operands оба char *, при вызове

analyse_inst( mnemonic, operands );

типы формальных аргументов функции также должны быть char *:

void analyse_inst( char *mnemonic, char *operands ) 
{
  ...
}

Что касается бита "передача по ссылке" ...

C передает все аргументы функции по значению .Это означает, что формальный аргумент в определении функции является объектом, отличным от реального аргумента в вызове функции, и любые изменения, внесенные в формальный аргумент, не отражаются в фактическом аргументе.Предположим, мы пишем функцию swap как:

int swap( int a, int b )
{
  int tmp = a;
  a = b;
  b = tmp;
}

int main( void )
{
  int x = 2;
  int y = 3;

  printf( "before swap: x = %d, y = %d\n", x, y );
  swap( x, y );
  printf( "after swap: x = %d, y = %d\n", x, y );
  ...
}

Если вы скомпилируете и запустите этот код, вы увидите, что значения x и y не изменяются после вызоваswap - изменения в a и b не повлияли на x и y, потому что это разные объекты в памяти.

Чтобы функция swap работала, мы должны передать указатели на x и y:

void swap( int *a, int *b )
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

int main( void )
{
  ...
  swap( &x, &y );
  ...
}

В этом случае выражения *a и *b в swap относятся к тем же объектам, что и выражения x и y в main, поэтому изменения в*a и *b отражаются в x и y:

 a == &x,  b == &y
*a ==  x, *b ==  y

Итак, в целом:

void foo( T *ptr ) // for any non-array type T
{
  *ptr = new_value(); // write a new value to the object `ptr` points to
}

void bar( void )
{
  T var;
  foo( &var ); // write a new value to var
}

Это также верно для типов указателей - заменитеT с указателем типа P *, и мы получаем следующее:

void foo( P **ptr ) // for any non-array type T
{
  *ptr = new_value(); // write a new value to the object `ptr` points to
}

void bar( void )
{
  P *var;
  foo( &var ); // write a new value to var
}

В этом случае var сохраняет значение указателя.Если мы хотим записать новое значение указателя в var - foo, то мы все равно должны передать указатель в var в качестве аргумента.Поскольку var имеет тип P *, то выражение &var имеет тип P **.


Значение указателя допустимо, если оно указывает на объект в течение времени жизни этого объекта. lvalue - это выражение, которое ссылается на объект, так что значение объекта может быть прочитано или изменено. Верьте или нет, есть веская причина для этого правила, но это означает, что выражения массива теряют свою "массивность" в большинстве случаев, что приводит к большой путанице среди людей, впервые изучающих язык.
0 голосов
/ 27 февраля 2019

, что означает, что я буду передавать указатели на функцию.Но почему это требуется?

Потому что в основном у вас есть указатели, а printf("%s" ожидает char*.

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

Таким образом, хотя можно сказать, что сами строкибыть «переданным по ссылке», строго говоря, C фактически позволяет передавать параметры только по значению. Сами указатели передаются по значению.Параметры вашей функции будут копиями указателей, которые вы выделили в main ().Но они указывают на те же строки, что и указатели в main ().

Из этого я получил, что передача переменной по ссылке и последующее изменение этого значения будет означать, что та же самая переменная вне функциитакже будет изменен;

Действительно, вы можете изменить строку внутри функции через указатель, и тогда это повлияет на строку в main ().Но в этом случае вы не выделяете памяти для изменения - вы пытаетесь изменить строковый литерал "...", что было бы ошибкой.Если бы вы изменили строки, вы должны были объявить их как массивы в main (): char mnemonic[] = "add";

Теперь, как выясняется, всякий раз, когда вы используете массив, такой как в моем примере, внутри выражения,он «разлагается» на указатель на первый элемент.Таким образом, мы не смогли бы передать массив по значению в функцию, поскольку язык Си изменил бы его между строками на указатель на первый элемент.

Вы можете поиграться с этим кодом:

#include <stdio.h>
#include <string.h>

void analyse_inst(char* mnemonic, char* operands);

int main()
{
    char mnemonic[] = "add";
    char operands[] = "five to two";

    analyse_inst(mnemonic, operands);
    printf("%s\n", mnemonic);
}

void analyse_inst(char* mnemonic, char* operands)
{
    printf("%s ", mnemonic);
    printf("%s\n", operands);

    strcpy(mnemonic, "hi");
}
0 голосов
/ 27 февраля 2019

Когда вы пишете что-то вроде char *mnemonic, это означает, что вы создаете переменную-указатель (переменную, которая будет содержать адрес другой переменной), но, поскольку тип данных mnemonic равен char, он будет содержать адреспеременная только с char типом данных.

Теперь внутри вашего кода вы написали mnemonic = "add", поэтому здесь "add" - это строка, представляющая собой массив символов, а мнемоника указывает на базовый адрес этого массива.

и при вызове функции вы передаете ссылки этих char arrays, поэтому вам нужно изменить void analyse_inst(char mnemonic, char operands) на void analyse_inst(char *mnemonic, char *operands), чтобы получить ссылки в этих соответствующих переменных-указателях.Причина та же Нам нужны переменные-указатели для хранения ссылок .

И & возвращает адрес переменной, что означает ссылку на область памяти, в которой хранится переменная.

Надеюсь, это поможет.

0 голосов
/ 27 февраля 2019

Строки в C хранятся как массивы символов, оканчивающиеся символом со значением '\0' («NIL»).Вы не можете напрямую передавать массивы, поэтому вместо этого используется указатель на первый символ, поэтому вы должны передать char * s функции, чтобы получить доступ к строкам.

Символ обычно намного меньше, чемуказатель (например, 8 против 32/64 бит), поэтому вы не можете сжать значение указателя в один символ.

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

Учтите это:

static void put_next(const char *s)
{
  putchar(*s++);
}

int main(void)
{
  const char *string = "hello";
  put_next(string);
  put_next(string);
}

Это напечатает hh, так как ему каждый раз передается одно и то же значение string, тот факт, что s, представляющая собой другую переменную, содержащую копию того же значения, увеличивается внутри функциине имеет значенияПриращенное значение является локальным для функции и выбрасывается после выхода из области видимости.

...