Как понять концепцию указателей (*) и операторов адреса (&)?
Я пытаюсь понять значение этих двух операторов, поэтому я написал этот код именно для этой цели.
#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 **
,
- Значение указателя является действительным, если оно указывает на объект в течение времени жизни этого объекта.
- Lvalue - это выражение, которое ссылается на объект, так что значение объекта может быть прочитано или изменено.
- Хотите верьте, хотите нет, но для этого правила есть веская причина, но это означает, что выражения массива теряют свою "массивность" в большинстве случаев, что приводит к путанице среди людей, впервые изучающих язык.
Строки в 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
, которая является другой переменной, содержащей копию того же значения, увеличивается внутри функции, не имеет значения. Увеличенное значение является локальным для функции и выбрасывается, как только выходит из области видимости.