Как понять концепцию указателей (*) и операторов адреса (&)?

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

#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() функция к analyse_inst(char * mnemonic, char * operands), что означает, что я буду передавать указатели на функцию. Но почему это требуется?

Кроме того, я посмотрел "прохождение по ссылке". И согласно tutorialspoint.com, его определение:

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

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

Я что-то не так делаю?

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

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

4 ответа

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

Потому что у вас есть в основном указатели, и что 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");
}

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

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

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

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

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

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

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

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"

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

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

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 msgstr " (для хранения 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 **,


  1. Значение указателя является действительным, если оно указывает на объект в течение времени жизни этого объекта.
  2. Lvalue - это выражение, которое ссылается на объект, так что значение объекта может быть прочитано или изменено.
  3. Хотите верьте, хотите нет, но для этого правила есть веская причина, но это означает, что выражения массива теряют свою "массивность" в большинстве случаев, что приводит к путанице среди людей, впервые изучающих язык.

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

Другие вопросы по тегам