Как подходить к указателям в C?
Я очень новичок в C, и у меня есть некоторые проблемы с изучением указателей. Я экспериментировал с заменой и что все, что я могу с ними сделать:) Я знаю, что каждая переменная имеет свой собственный адрес в ячейках памяти (это мне сказал мой лектор), и каждое значение переменной можно получить, перейдя на связанный с ней адрес, а затем выбор значения, хранящегося в нем. Я видел некоторые заголовки функций, такие как:
int doSomething(char **hihi);
моя голова настолько смущена. Я знаю, что указатель также является переменной, и он хранит только адресную информацию в своей ячейке памяти. Я читал, что они тесно связаны с массивами
arr = &arr[0];
Это все, что я знаю о указателях, и мне интересно, как я могу углубить свое видение по указателям. Я обыскал сеть, и я не мог найти полезных подсказок для подсказок. И я также хочу знать, почему они так важны и есть ли способ понять, что происходит, не используя printf()
для печати своих адресов (p
) и значений (\*p
)??
Ответы
Ответ 1
Печать адресов и значений - разумный способ взглянуть на них. Но если вы можете запустить отладчик и работать, это намного лучше, потому что вы можете быстрее следовать указателям, наблюдать за их изменением при шаге и т.д.
Если вы знакомы с "ярлыками" в Windows или софт-ссылками в файловых системах linux, тогда он может помочь, как вы начинаете, думать о указателе как ярлыке (softlink) для другого объекта ( является ли этот объект структурой, встроенным типом, другим указателем и т.д.).
Ярлык по-прежнему является файлом. Он занимает свое место на диске, он относится к другому файлу, и его можно изменить, чтобы ссылаться на другой файл с того, к чему он привык. Точно так же указатель на C является объектом, который занимает память, содержит адрес другой ячейки памяти и может быть изменен, чтобы содержать другой адрес, просто назначив ему.
Одно из отличий заключается в том, что дважды щелкните ярлык, он ведет себя так, как если бы вы дважды щелкнули то, на что указывают. Это не так с указателями - вы всегда должны явно разыменовывать указатель с помощью "*" или "- > ", чтобы получить доступ к тому, на что он указывает. Другое различие заключается в том, что довольно распространено иметь указатели на указатели на что-то в C.
Что касается жаргона, вам просто нужно усвоить его, к сожалению. "int doSomething (char ** hihi)" означает "функция, называемая doSomething, которая возвращает целое число и принимает в качестве параметра указатель на указатель a char". Решающим моментом является то, что "char ** hihi
" означает "указатель на указатель на char. Мы будем называть указатель на указатель на char hihi". Вы говорите, что "тип" hihi char **, и что "тип" * hihi (что вы получаете, когда вы разыскиваете указатель) составляет char *, а тип ** hihi - это char.
Часто в C указатель на char означает строку (другими словами, это указатель на первый char в NUL-концевом массиве). Так часто "char *" означает "строка", но это не обязательно. Это может означать только указатель на один char. Немного похоже на ярлык для 1-байтового файла в Windows (ну, в любом случае, с FAT32) указатель на char в C на самом деле больше, чем тот, на который он указывает: -)
Аналогично, char ** часто означает не просто указатель на один указатель на строку, а на массив указателей строк. Возможно, это не так, но если это произойдет, то следующая небольшая картинка может помочь:
hihi
____ ____ ________ _________ _______
|____| -----> |____| *hihi ---> |___A____| |___B_____| |___C___|
|____| *(hihi+1) ------------------^ ^
|____| *(hihi+2) ---------------------------------|
| ...| etc.
hihi указывает на усилие блока башни, что является моим способом представления массива указателей. Как вы уже отметили, я мог бы написать hihi [0] вместо * hihi, hihi [1] вместо * (hihi + 1) и т.д.
Это непрерывный блок памяти, и каждый его кусок указателя содержит адрес (то есть он "указывает на" ) еще один блок памяти, с сайта goodness-know-where, содержащий один или несколько символов, Итак, hihi [0] является адресом первой char строки A, hihi [1] является адресом первой char строки B.
Если hihi не указывает на массив, только один указатель, то блок башни - это бунгало. Аналогично, если * hihi не указывает на строку, только один char, то длинный тонкий блок является квадратом. Вы могли бы спросить: "Откуда я знаю, сколько этажей имеет блок башни?". Это большое дело в программировании на языке С - обычно либо документация по функциям скажет вам (это может сказать "1", либо "12", или "достаточно для того, что вы говорите мне делать", либо вы передадите количество этажей в качестве дополнительного параметра, иначе документация сообщит вам, что массив "NULL terminated", что означает, что он будет продолжать чтение, пока не увидит адрес/значение NULL, а затем остановится. Основная функция фактически выполняет как второе и третье - argc содержит количество аргументов, и просто чтобы быть в безопасности, argv завершает NULL.
Итак, всякий раз, когда вы видите параметр указателя, вам нужно посмотреть документацию для функции, чтобы увидеть, ожидает ли она указателя на массив, и если да, то насколько велик массив. Если вы не будете осторожны в этом, вы создадите какую-то ошибку, называемую "переполнение буфера", где функция ожидает указатель на большой массив, вы даете ему указатель на небольшой массив, и он отскакивает от конца того, что вы дали ему, и начинает разлагать память.
Ответ 2
Я думаю, что именно здесь классические книги более полезны, чем большинство онлайн-ресурсов. Если вы можете получить копию, очень внимательно прочитайте язык программирования C (a.k.a K & R). Если вы хотите узнать больше, перейдите на Expert C Programming: Deep Secrets (просто Google).
Ответ 3
Указатель - это место.
Массив является последовательной группой мест.
Всегда есть значение в месте. (Это может быть оставшийся барахло).
Каждая переменная имеет место.
Для переменных указателя значение на его месте является местом.
Это как охота за сокровищами. "Посмотрите в почтовый ящик 13 для заметки, в которой указывается, какой почтовый ящик содержит вашу поздравительную открытку".
И если почтовый ящик 13 содержит заметку, которая читает "13", ваша поздравительная открытка будет долгое время! (Это ошибка, вызванная ссылкой на круговой указатель.; -)
Ответ 4
При чтении K & R может быть лучшим выбором здесь, я попытаюсь сделать это немного яснее:
Указатель сам по себе является переменной. Но вместо сохранения значения он сохраняет только адрес. Подумайте об этом как о индексе: как в вашей адресной книге указатель на то, что вы ищете (например, номер телефона на какое-то имя), он указывает, где хранится информация. В вашей адресной книге он может сказать: "Посмотрите на стр. 23, чтобы найти номер телефона Джо". В случае указателя он просто говорит: "Посмотрите на адрес памяти 1234, чтобы получить информацию, на которую я указываю". Поскольку значение указателя само по себе является только адресом памяти, вы можете сделать с ним арифметику - например, добавление значений (это будет то же самое, что доступ к элементам массива: если указатель указывает на массив, адрес, следующий за указателем указывает на доступ к следующему элементу массива).
Ваш пример функции int doSomething(char *hihi)
будет содержать hihi, указывающий на адрес памяти, который вы передали ему при вызове. Это полезно, если вы хотите передавать большие объемы данных - вместо копирования данных (что происходит в функции типа void blah(int a)
со значением a) вы копируете только ее местоположение.
В приведенном выше описании я не упомянул некоторые детали, но я надеюсь, что это даст вам хотя бы некоторое базовое понимание. Я настоятельно рекомендую прочитать K & R или аналогичную книгу по этой теме.
Ответ 5
Похоже, вы поняли основы. Я бы не стал прыгать на обману, пока ты не перешел к какой-то литературе.
Причиной быть осторожным при работе с указателями является то, что они позволяют вам напрямую обращаться к определенным адресам памяти, и если ваш код совершит неправильные действия, это сломается. Массив будет указывать на первое местоположение и в зависимости от типа массива, вы можете получить доступ к дальнейшим местоположениям в массиве, работая с указателем того же типа хранимых значений массива.
Сначала я понял переменные, lvalue, rvalue и присваивания, затем указывал как переменные типы, затем разыменовывал указатель и другие операции указателя. Потребовалось бы немного, чтобы подробно остановиться на этом, и есть уже много хороших ссылок.
Здесь вы можете найти учебное пособие (действительно подробное объяснение).
Прямая ссылка PDF.
Ответ 6
/* Given a string of characters like this one: */
char *string = "Hello!\n";
/* Memory will contain something like:
0x00100 'H'
0x00101 'e'
0x00102 'l'
0x00103 'l'
0x00104 'o'
0x00105 '!'
0x00106 '\n'
0x00107 '\0'
*/
/* And the program code will say: */
string=0x00100;
/* C doesn't really have arrays */
char c=string[3];
/* is just syntactic sugar for: */
char c=*((char*)((void*)string + 3 * sizeof(char)));
/* ie. 0x00100 + 3 * 1 */
/* ie. 0x00103 */
/* and * dereferences 0x00103, this means char_in(0x00103) */
/* When you pass a pointer you are actually passing the value
of the memory position */
int a; /* allocates space for a random four byte value in
0x00108 */
scanf("%d",&a); /* &a = 0x00108 scanf now knows that it has to store
the value in 0x0108 */
/* Even if you declare: */
int b[23];
/* b will be just a pointer to the beginning of 23 allocated ints.
ie. 0x0010C */
/* pointer arithmetic is different from normal types in that: */
b++;
/* is actually: */
b+=4; /* b+=1*sizeof(int); */
/* So pointers in C work more like arrays */
Ответ 7
Давос, ты в колледже? Вы главный специалист по информационным технологиям? Одна вещь, которую вы можете рассмотреть, - это взять класс ассемблерного языка (MIPS, x86). Я был главным инженером в области электротехники, и мне нужно было брать такие классы низкого уровня. Одна вещь, которую я наблюдал, заключалась в том, что ясное понимание языка ассемблера действительно помогло мне, когда я начал изучать C++. В частности, это дало мне гораздо более четкое понимание указателей.
Указатели и разыменование являются фундаментальными понятиями на уровне языка ассемблера. Если вы впервые изучаете указатели, я нахожу, что в некотором смысле "С" скрывает его немного, и на самом деле он становится яснее на уровне ассемблера. Затем вы можете получить это знание более низкого уровня и посмотреть, как язык "C" просто накладывает на него некоторый синтаксис.
Просто мысль. Кроме того, K & R - отличная книга, о которой говорили несколько человек, а также использование некоторых абстрактных типов данных, таких как связанные списки, может быть полезно, особенно если вы рисуете диаграммы, показывающие макет памяти, это может помочь прояснить идею.
Ответ 8
IMHO лучший способ "получить" указатели - это сделать некоторое программирование на ассемблере. Когда вы привыкли думать о содержимом необработанного регистра (без реального различия между данными и адресами, отличными от того, как вы их используете) и инструкциями по загрузке, типы указателей C будут иметь гораздо больше смысла (и ваша оценка то, что C делает для вас, будет значительно улучшено).
Ответ 9
Если вы действительно хотите понять указатели,
Вам нужна хорошая лекция в колледже.
Лучший из тех, что я когда-либо видел, - этот.
Ничего другого не сравнивается.
Также ознакомьтесь с лекцией по Тьюрингу. Это очень хорошо сделано.
Ответ 10
Многие хорошие ответы, приведенные выше. Некоторые другие соображения заключаются в том, что указатель всегда представляет собой целочисленный тип без знака. Объект или переменная, которые он указывает в памяти, может быть любого типа.
В 32-разрядной операционной системе целое число является 4-байтным числом и должно находиться в диапазоне
0 < значение указателя < (2 ^^ 32) -1
В 64-битной операционной системе целое число является 8-байтовым числом и должно находиться в диапазоне
0 < значение указателя < (2 ^^ 64) -1
Значение указателя = 0 интерпретируется как специальное значение флага с именем NULL, которое указывает, что этот указатель не указывает на полезную переменную.
Указатели используются для косвенности. Некоторые регистры в ЦП действуют как указатели, например. счетчик программ (ПК) и регистры адресов.
Ответ 11
Вы читали K & R? Если вы этого не сделали, я бы сказал, что вам следует начать.
Ответ 12
Вот некоторое базовое объяснение, объясняющее, как работают C-указатели.
Ответ 13
Несколько нетрадиционное предложение: http://www.youtube.com/watch?v=Rxvv9krECNw
Это лекция из Высших вычислительных 1 в моем университете с прошлого года. Первые несколько минут будут немного бесполезны (тема типа администратора), но в остальном это действительно хорошее объяснение.