Я знаю, что это действительно простой вопрос, но я только начал с некоторого базового программирования на C ++ после написания нескольких проектов на языках высокого уровня.
В основном у меня есть три вопроса:
Зачем использовать указатели на нормальные переменные?
Мы используем указатели, потому что проще дать кому-то адрес вашего дома, чем разослать копию вашего дома всем.
Риши Дуа
14
@RishiDua Это единственное лучшее объяснение указателя, с которым я когда-либо сталкивался. Спасибо за это, это расширило мое понимание :)
Джош
2
Указатели также можно использовать, когда вы хотите вернуть более одного значения / объекта.
Гаурав
Ответы:
173
Зачем использовать указатели на нормальные переменные?
Короткий ответ: не надо. ;-) Указатели должны использоваться там, где вы больше ничего не можете использовать. Это либо из-за отсутствия соответствующей функциональности, из-за отсутствия типов данных, либо из-за чистой производительности. Подробнее ниже ...
Когда и где я должен использовать указатели?
Короткий ответ здесь: где вы не можете использовать что-либо еще. В C у вас нет поддержки сложных типов данных, таких как строка. Также нет способа передачи переменной «по ссылке» в функцию. Вот где вы должны использовать указатели. Также вы можете указывать на них практически все, связанные списки, членов структур и так далее. Но давайте не будем вдаваться в подробности.
Как вы используете указатели с массивами?
Без особых усилий и большого замешательства. ;-) Если говорить о простых типах данных, таких как int и char, разница между массивом и указателем будет небольшой. Эти объявления очень похожи (но не одинаковы - например, sizeofбудут возвращать разные значения):
char* a ="Hello";char a[]="Hello";
Вы можете добраться до любого элемента в массиве, как это
printf("Second char is: %c", a[1]);
Индекс 1, так как массив начинается с элемента 0. :-)
Или вы могли бы сделать это в равной степени
printf("Second char is: %c",*(a+1));
Оператор указателя (*) необходим, поскольку мы сообщаем printf, что хотим напечатать символ. Без *, символьное представление самого адреса памяти будет напечатано. Теперь вместо этого мы используем самого персонажа. Если бы мы использовали% s вместо% c, мы бы попросили printf напечатать содержимое адреса памяти, на который указывает «a» плюс один (в этом примере выше), и нам не пришлось бы ставить * спереди:
printf("Second char is: %s",(a+1));/* WRONG */
Но это не просто напечатало бы второй символ, но вместо этого все символы в следующих адресах памяти, пока не был найден нулевой символ (\ 0). И здесь вещи начинают становиться опасными. Что если вы случайно попытаетесь напечатать переменную типа integer вместо указателя на символ с помощью% s форматера?
char* a ="Hello";int b =120;
printf("Second char is: %s", b);
Это напечатало бы все, что найдено на адресе памяти 120, и продолжит печать, пока не будет найден нулевой символ. Это неправильно и незаконно выполнять этот оператор printf, но он, вероятно, будет работать в любом случае, так как указатель на самом деле имеет тип int во многих средах. Представьте себе проблемы, которые вы могли бы вызвать, если бы вместо этого вы использовали sprintf () и присвоили слишком длинный «массив символов» другой переменной, для которой было выделено только определенное ограниченное пространство. Скорее всего, вы закончите тем, что переписываете что-то еще в памяти и вызовете сбой вашей программы (если вам повезет).
Да, и если вы не присваиваете строковое значение массиву / указателю char при его объявлении, вы ДОЛЖНЫ выделить ему достаточный объем памяти, прежде чем присваивать ему значение. Использование malloc, calloc или аналогичного. Это потому, что вы объявили только один элемент в вашем массиве / один адрес памяти, на который нужно указать. Итак, вот несколько примеров:
char* x;/* Allocate 6 bytes of memory for me and point x to the first of them. */
x =(char*) malloc(6);
x[0]='H';
x[1]='e';
x[2]='l';
x[3]='l';
x[4]='o';
x[5]='\0';
printf("String \"%s\" at address: %d\n", x, x);/* Delete the allocation (reservation) of the memory. *//* The char pointer x is still pointing to this address in memory though! */
free(x);/* Same as malloc but here the allocated space is filled with null characters!*/
x =(char*) calloc(6,sizeof(x));
x[0]='H';
x[1]='e';
x[2]='l';
x[3]='l';
x[4]='o';
x[5]='\0';
printf("String \"%s\" at address: %d\n", x, x);/* And delete the allocation again... */
free(x);/* We can set the size at declaration time as well */char xx[6];
xx[0]='H';
xx[1]='e';
xx[2]='l';
xx[3]='l';
xx[4]='o';
xx[5]='\0';
printf("String \"%s\" at address: %d\n", xx, xx);
Обратите внимание, что вы все еще можете использовать переменную x после выполнения free () выделенной памяти, но вы не знаете, что там находится. Также обратите внимание, что два printf () могут дать вам разные адреса, поскольку нет гарантии, что второе выделение памяти будет выполнено в том же пространстве, что и первое.
Ваш пример со 120 в нем неверен. Он использует% c, а не% s, так что ошибки нет; он просто печатает строчную букву х. Более того, ваше последующее утверждение, что указатель имеет тип int, неверно и очень плохой совет для программиста на C, не имеющего опыта работы с указателями.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
13
-1 Вы хорошо начали, но первый пример неверен. Нет, это не то же самое. В первом случае aуказатель, а во втором aмассиве. Я уже упоминал об этом? Это не тоже самое! Проверьте сами: сравните sizeof (a), попробуйте назначить новый адрес массиву. Это не сработает.
Sellibitze
42
char* a = "Hello";и char a[] = "Hello";не одинаковы, они совершенно разные. Один объявляет указатель, другой массив. Попробуйте, sizeofи вы увидите разницу.
Патрик Шлютер
12
Msgstr "Это не неправильно или незаконно выполнять это утверждение printf". Это совершенно неправильно. Это утверждение printf имеет неопределенное поведение. На многих реализациях это приведет к нарушению доступа. Указатели на самом деле не относятся к типу int, они фактически являются указателями (и ничем иным).
Mankarse
19
-1, Вы ошиблись в этом так много фактов, и я просто скажу, что вы не заслуживаете количества голосов или статуса принятого ответа.
asveikau
50
Одной из причин использования указателей является то, что переменная или объект могут быть изменены в вызываемой функции.
В C ++ лучше использовать ссылки, чем указатели. Хотя ссылки по сути являются указателями, C ++ в некоторой степени скрывает этот факт и создает впечатление, будто вы передаете по значению. Это позволяет легко изменить способ, которым вызывающая функция получает значение, без необходимости изменять семантику его передачи.
Рассмотрим следующие примеры:
Используя ссылки:
publicvoid doSomething(){int i =10;
doSomethingElse(i);// passes i by references since doSomethingElse() receives it// by reference, but the syntax makes it appear as if i is passed// by value}publicvoid doSomethingElse(int& i)// receives i as a reference{
cout << i << endl;}
Вероятно, стоит упомянуть, что ссылки безопаснее, поскольку вы не можете передать пустую ссылку.
SpoonMeiser
26
Да, это, вероятно, самое большое преимущество использования ссылок. Спасибо за указание на это. Никакой каламбур не предназначен :)
trshiv
2
Вы, безусловно, можете передать нулевую ссылку. Это не так просто, как передать нулевой указатель.
августа
1
согласен с @ n0rd. Если вы думаете, что не можете передать нулевую ссылку, вы ошибаетесь. Проще передать висячую ссылку, чем нулевую, но в конечном итоге вы можете сделать и достаточно простую. Ссылки не являются серебряной пулей, защищающей инженера от выстрела себе в ногу. Посмотри вживую .
WhozCraig
2
@ n0rd: это явно неопределенное поведение.
Даниэль Камил Козар
42
Указатели позволяют ссылаться на одно и то же пространство в памяти из нескольких мест. Это означает, что вы можете обновить память в одном месте, а изменения можно увидеть из другого места в вашей программе. Вы также сэкономите место, имея возможность совместно использовать компоненты в своих структурах данных.
Вы должны использовать указатели в любом месте, где вам нужно получить и передать адрес в определенное место в памяти. Вы также можете использовать указатели для навигации по массивам:
Массив - это блок непрерывной памяти, выделенный для определенного типа. Имя массива содержит значение начальной точки массива. Когда вы добавляете 1, вы переходите на второе место. Это позволяет вам записывать циклы, которые увеличивают указатель, который скользит по массиву, не имея явного счетчика для использования при доступе к массиву.
Вот пример на C:
char hello[]="hello";char*p = hello;while(*p){*p +=1;// increase the character by one
p +=1;// move to the next spot}
printf(hello);
печать
ifmmp
потому что он принимает значение для каждого символа и увеличивает его на единицу.
because it takes the value for each character and increments it by one, Находится в представлении ascii или как?
Эдуардо С.
28
Указатели являются одним из способов получения косвенной ссылки на другую переменную. Вместо того, чтобы держать значение переменной, они сообщают вам ее адрес . Это особенно полезно при работе с массивами, поскольку, используя указатель на первый элемент в массиве (его адрес), вы можете быстро найти следующий элемент, увеличив указатель (до следующего местоположения адреса).
Лучшее объяснение указателей и арифметики указателей , которые я читал в K & R в Язык программирования C . Хорошая книга для начала изучения C ++ - C ++ Primer .
благодарю вас! наконец, практическое объяснение преимуществ использования стека! Указатель на позицию в массиве также улучшает производительность доступа к значениям @ и относительно указателя?
Это, наверное, лучшее объяснение, которое я читал. у людей есть способ "чрезмерно усложнять" вещи :)
Детилиум
23
Позвольте мне попробовать и ответить на это тоже.
Указатели похожи на ссылки. Другими словами, это не копии, а способ ссылки на исходное значение.
Прежде всего, вам обычно приходится часто использовать указатели , когда вы работаете со встроенным оборудованием . Может быть, вам нужно переключить состояние цифрового вывода ввода-вывода. Возможно, вы обрабатываете прерывание и вам нужно сохранить значение в определенном месте. Вы получаете картину. Однако, если вы не имеете дело с аппаратным обеспечением напрямую и просто задаетесь вопросом, какие типы использовать, читайте дальше.
Зачем использовать указатели, а не обычные переменные? Ответ становится понятнее, когда вы имеете дело со сложными типами, такими как классы, структуры и массивы. Если бы вы использовали обычную переменную, вы могли бы в итоге сделать копию (компиляторы достаточно умны, чтобы предотвратить это в некоторых ситуациях, и C ++ 11 тоже помогает, но пока мы будем держаться подальше от этого обсуждения).
Что произойдет, если вы захотите изменить исходное значение? Вы можете использовать что-то вроде этого:
MyType a;//let's ignore what MyType actually is right now.
a = modify(a);
Это будет хорошо работать, и если вы не знаете точно, почему вы используете указатели, вы не должны их использовать. Остерегайтесь причины «они, вероятно, быстрее». Запустите свои собственные тесты и, если они действительно быстрее, используйте их.
Однако предположим, что вы решаете проблему, в которой вам нужно выделить память. Когда вы выделяете память, вам нужно освободить ее. Выделение памяти может или не может быть успешным. Здесь полезны указатели - они позволяют вам проверять существование выделенного вами объекта и позволяют получить доступ к объекту, для которого была выделена память, путем разыменования указателя.
MyType*p = NULL;//empty pointerif(p){//we never reach here, because the pointer points to nothing}//now, let's allocate some memory
p =newMyType[50000];if(p)//if the memory was allocated, this test will pass{//we can do something with our allocated arrayfor(size_t i=0; i!=50000; i++){MyType&v =*(p+i);//get a reference to the ith object//do something with it//...}delete[] p;//we're done. de-allocate the memory}
Это ключ к тому, почему вы используете указатели - ссылки предполагают, что элемент, на который вы ссылаетесь, уже существует . Указатель не имеет.
Другая причина, по которой вы будете использовать указатели (или, по крайней мере, в конечном итоге придется иметь дело с ними), заключается в том, что они являются типом данных, существовавшим до ссылок. Поэтому, если вы в конечном итоге будете использовать библиотеки для выполнения задач, которые, как вы знаете, лучше всего, вы обнаружите, что многие из этих библиотек используют указатели повсеместно, просто из-за того, как долго они были (много из них были написаны до C ++).
Если вы не использовали никаких библиотек, вы могли бы разработать свой код таким образом, чтобы вы могли держаться подальше от указателей, но, учитывая, что указатели являются одним из основных типов языка, чем быстрее вы освоитесь с ними, тем больше Переносим ваши навыки C ++.
С точки зрения удобства обслуживания, я должен также упомянуть, что когда вы используете указатели, вы должны либо проверить их действительность, либо обработать случай, когда они недействительны, либо просто предположить, что они действительны, и принять тот факт, что ваш программа потерпит крах или когда это предположение будет нарушено. Иными словами, ваш выбор с указателями - либо усложнять код, либо увеличивать объем работ по обслуживанию, когда что-то ломается, и вы пытаетесь отследить ошибку, относящуюся к целому классу ошибок, которые вызывают указатели, например, повреждение памяти.
Поэтому, если вы контролируете весь свой код, держитесь подальше от указателей и вместо этого используйте ссылки, сохраняя их постоянными, когда можете. Это заставит вас задуматься о времени жизни ваших объектов и в конечном итоге сделает ваш код более понятным.
Просто запомните эту разницу: ссылка, по сути, является действительным указателем. Указатель не всегда действителен.
Так я говорю, что невозможно создать недействительную ссылку? Нет, это вполне возможно, потому что C ++ позволяет вам делать практически все. Это просто сложнее сделать непреднамеренно, и вы будете поражены тем, сколько ошибок непреднамеренно :)
Вы можете написать хорошие классы-обертки для отображения ввода-вывода в память, с помощью которых вы можете практически избежать использования указателей.
По сути, стандартная архитектура ЦП является архитектурой фон Неймана, и чрезвычайно полезно иметь возможность ссылаться на местоположение элемента данных в памяти и выполнять арифметические действия с ним на такой машине. Если вы знаете какой-либо вариант языка ассемблера, вы быстро увидите, насколько это важно на низком уровне.
C ++ делает указатели немного запутанными, так как он иногда управляет ими и скрывает их эффект в виде «ссылок». Если вы используете прямую C, потребность в указателях становится намного более очевидной: нет другого способа сделать вызов по ссылке, это лучший способ сохранить строку, это лучший способ перебрать массив и т. Д.
Одно из применений указателей (я не буду упоминать то, что уже освещалось в постах других людей) - доступ к памяти, которую вы не выделили. Это не очень полезно для программирования на ПК, но используется во встроенном программировании для доступа к аппаратным устройствам с отображенной памятью.
В старые времена DOS вы имели возможность напрямую обращаться к видеопамяти видеокарты, объявляя указатель на:
Нет смысла использовать указатель - структура, похожая на промежуток, которая также содержит длину - гораздо более уместна. В эти дни это есть gsl::span, и скоро это будет std::span.
einpoklum
10
По большей части указатели являются массивами (в C / C ++) - они являются адресами в памяти и при желании могут быть доступны как массив (в «нормальных» случаях).
Так как они являются адресом элемента, они маленькие: они занимают только пространство адреса. Поскольку они маленькие, отправка их в функцию обходится дешево. И затем они позволяют этой функции работать с реальным элементом, а не с копией.
Если вы хотите динамически распределять память (например, для связанного списка), вы должны использовать указатели, потому что они являются единственным способом извлечения памяти из кучи.
Я думаю, что вводить в заблуждение указатели - это массивы. Действительно, имена массивов являются константными указателями на первый элемент массива. То, что вы можете получить доступ к произвольной точке, как к массиву, не означает, что это так ... вы можете получить нарушение прав доступа :)
rmeador
2
Указатели не являются массивами. Читайте раздел 6 comp.lang.c FAQ .
Кит Томпсон
Указатели действительно не являются массивами. Кроме того, в необработанных массивах также нет особой пользы - конечно, не после C ++ 11 и std::array.
einpoklum
@einpoklum - указатели достаточно близко к массивам эквивалентным в справочных операциях и переборе массивов :) .... также - C ++ 11 был 3 -х лет от выпуска , когда этот ответ был написан в 2008 году
заповедный
@warren: указатели не являются массивами и не близки к массивам, они просто распадаются на массивы. С педагогической точки зрения неуместно говорить людям, что они похожи. Кроме того, вы, безусловно, можете иметь ссылки на массивы, которые не относятся к указателям и содержат информацию о размере; смотрите здесь
einpoklum
9
Указатели важны во многих структурах данных, дизайн которых требует умения эффективно связывать или связывать один «узел» с другим. Вы бы не «выбрали» указатель над обычным типом данных, таким как float, у них просто разные цели.
Указатели полезны, когда вам требуется высокая производительность и / или компактный объем памяти.
Адрес первого элемента в вашем массиве может быть назначен указателю. Это тогда позволяет вам получить доступ к основным выделенным байтам напрямую. Весь смысл массива в том, чтобы избежать необходимости делать это, хотя.
Один из способов использовать указатели на переменные - это исключить дублирование памяти. Например, если у вас есть какой-то большой сложный объект, вы можете использовать указатель для указания на эту переменную для каждой ссылки, которую вы делаете. С переменной необходимо дублировать память для каждой копии.
Действительно, когда вы думаете об этом, это имеет смысл. Когда вы используете полиморфизм подтипов, в конечном итоге вы заранее не знаете, какая реализация метода класса или подкласса будет вызвана, потому что вы не знаете, что такое настоящий класс.
Эта идея иметь переменную, которая содержит объект неизвестного класса, несовместима с режимом C ++ по умолчанию (без указателя) для хранения объектов в стеке, где объем выделенного пространства напрямую соответствует классу. Примечание: если в классе 5 полей экземпляра против 3, потребуется выделить больше места.
Обратите внимание, что если вы используете «&» для передачи аргументов по ссылке, косвенное обращение (т. Е. Указатели) все еще остается за кадром. '&' - это просто синтаксический сахар, который (1) избавляет вас от необходимости использовать синтаксис указателя и (2) позволяет компилятору быть более строгим (например, запрещать нулевые указатели).
Нет, вам не нужно использовать указатели - вы можете использовать ссылки. А когда пишешь "указатели задействованы за кулисами" - это бессмысленно. gotoинструкции также используются за кулисами - в инструкциях целевой машины. Мы до сих пор не утверждаем, что использовали их.
einpoklum
@ einpoklum-reinstateMonica Если у вас есть набор объектов, с которыми вы хотите работать, присваивая каждому элементу по очереди временную переменную и вызывая полиморфный метод для этой переменной, тогда да, вам НУЖЕН указатель, потому что вы не можете привязать ссылку.
Сильдорет
Если у вас есть ссылка базового класса на производный класс и вы вызываете виртуальный метод для этой ссылки, то будет вызвано переопределение производного класса. Я ошибаюсь?
einpoklum
@ einpoklum-reinstateMonica Это правильно. Но вы не можете изменить объект, на который ссылается. Так что, если вы просматриваете список / набор / массив этих объектов, ссылочная переменная не будет работать.
Сильдорет
5
Потому что копирование больших объектов повсюду тратит время и память.
И как указатели помогают с этим? Я думаю, что человек, приходящий из Java или .Net, не знает разницы между стеком и кучей, поэтому этот ответ довольно бесполезен ..
Матс Фредрикссон,
Вы можете пройти по ссылке. Это предотвратит копирование.
Мартин Йорк,
1
@MatsFredriksson - Вместо того, чтобы передавать (копировать) большую структуру данных и копировать результат обратно, вы просто указываете, где он находится в ОЗУ, а затем изменяете его напрямую.
Джон У
Так что не копируйте большие объекты. Никто не говорил, что для этого нужны указатели.
айнпоклум
5
Вот мой ответчик, и я не буду обещать быть экспертом, но я нашел, что указатели великолепны в одной из моих библиотек, которые я пытаюсь написать. В этой библиотеке (это графический API с OpenGL :-)) вы можете создать треугольник с переданными в него объектами вершин. Метод draw берет эти объекты треугольника, и хорошо .. рисует их на основе созданных мною объектов вершин. Ну, все в порядке.
Но что, если я изменю координату вершины? Переместить это или что-то с помощью MoveX () в классе вершин? Ну, ладно, теперь мне нужно обновить треугольник, добавляя больше методов и производительность теряется, потому что я должен обновлять треугольник каждый раз, когда вершина движется. Все еще не большое дело, но это не так здорово.
Теперь, что если у меня есть сетка с тоннами вершин и тоннами треугольников, и сетка вращается, движется и тому подобное. Мне придется обновить каждый треугольник, который использует эти вершины, и, вероятно, каждый треугольник в сцене, потому что я не знаю, какие из них используют какие вершины. Это очень требовательно к компьютеру, и если у меня есть несколько сеток поверх ландшафта, о боже! У меня проблемы, потому что я обновляю каждый треугольник почти каждый кадр, потому что эти вершины меняются все время!
С помощью указателей вам не нужно обновлять треугольники.
Если бы у меня было три * объекта Vertex на класс треугольника, я не только экономлю место, потому что у zillion треугольников нет трех больших объектов вершины, но и эти указатели всегда будут указывать на вершины, для которых они предназначены, независимо от того, как часто меняются вершины. Поскольку указатели по-прежнему указывают на одну и ту же вершину, треугольники не меняются, и процесс обновления легче обрабатывать. Если бы я вас запутал, я бы не усомнился в этом, я не претендую на звание эксперта, просто бросая свои два цента в дискуссию.
Необходимость указателей на языке Си описана здесь
Основная идея заключается в том, что многие ограничения в языке (например, использование массивов, строк и изменение нескольких переменных в функциях) могут быть устранены путем манипуляций с областями памяти данных. Чтобы преодолеть эти ограничения, указатели были введены в C.
Кроме того, также видно, что с помощью указателей вы можете выполнять свой код быстрее и экономить память в тех случаях, когда вы передаете большие типы данных (например, структуру со многими полями) в функцию. Создание копии таких типов данных перед передачей займет много времени и потребует память. Это еще одна причина, по которой программисты предпочитают указатели для больших типов данных.
PS: Пожалуйста, обратитесь по ссылке для подробного объяснения с примером кода.
Нет, вопрос помечен c и c ++. Может быть, тег c не имеет значения, но он здесь с самого начала.
Жан-Франсуа Фабр
3
В java и C # все ссылки на объекты являются указателями, в случае c ++ вы можете лучше контролировать, куда указывает указатель. Помните, С великой силой приходит великая ответственность.
Что касается вашего второго вопроса, как правило, вам не нужно использовать указатели при программировании, однако есть одно исключение из этого, когда вы делаете публичный API.
Проблема с конструкциями C ++, которые люди обычно используют для замены указателей, очень сильно зависит от используемого вами набора инструментов, что хорошо, когда у вас есть все необходимое управление исходным кодом, однако, если вы компилируете статическую библиотеку с Visual Studio 2008, например и попробуйте использовать его в Visual Studio 2010, вы получите массу ошибок компоновщика, потому что новый проект связан с более новой версией STL, которая не имеет обратной совместимости. Все становится еще хуже, если вы компилируете DLL и предоставляете библиотеку импорта, которую люди используют в другом наборе инструментов, потому что в этом случае ваша программа рано или поздно вылетит без видимой причины.
Таким образом, с целью перемещения больших наборов данных из одной библиотеки в другую вы можете рассмотреть возможность указания указателя на массив для функции, которая должна копировать данные, если вы не хотите, чтобы другие использовали те же инструменты, которые вы используете. , Хорошей частью этого является то, что он даже не должен быть массивом в стиле C, вы можете использовать std :: vector и дать указатель, указав, например, адрес первого элемента & vector [0], и использовать std :: vector для внутреннего управления массивом.
Еще одна веская причина для использования указателей в C ++ опять-таки связана с библиотеками, подумайте о том, чтобы иметь dll, который не может быть загружен при запуске вашей программы, поэтому, если вы используете библиотеку импорта, зависимость не будет удовлетворена, и программа аварийно завершит работу. Это тот случай, например, когда вы предоставляете публичный API в DLL рядом с вашим приложением и хотите получить к нему доступ из других приложений. В этом случае, чтобы использовать API, вам нужно загрузить dll из его местоположения (обычно это находится в разделе реестра), а затем вам нужно использовать указатель на функцию, чтобы иметь возможность вызывать функции внутри DLL. Иногда люди, которые создают API, достаточно хороши, чтобы предоставить вам файл .h, который содержит вспомогательные функции для автоматизации этого процесса и все необходимые указатели функций,
В некоторых случаях указатели функций должны использовать функции, которые находятся в общей библиотеке (.DLL или .so). Это включает в себя выполнение вещи на разных языках, где часто предоставляется интерфейс DLL.
Создание компиляторов
Делаете научные калькуляторы, где у вас есть массив или векторная или строковая карта указателей функций?
Попытка напрямую изменить видеопамять - создание собственного графического пакета
Создание API!
Структуры данных - указатели узловых связей для специальных деревьев, которые вы создаете
Есть много причин для указателей. Если вы хотите сохранить межязыковую совместимость, особенно важно иметь имя C, искажение имен в DLL.
Ответы:
Короткий ответ: не надо. ;-) Указатели должны использоваться там, где вы больше ничего не можете использовать. Это либо из-за отсутствия соответствующей функциональности, из-за отсутствия типов данных, либо из-за чистой производительности. Подробнее ниже ...
Короткий ответ здесь: где вы не можете использовать что-либо еще. В C у вас нет поддержки сложных типов данных, таких как строка. Также нет способа передачи переменной «по ссылке» в функцию. Вот где вы должны использовать указатели. Также вы можете указывать на них практически все, связанные списки, членов структур и так далее. Но давайте не будем вдаваться в подробности.
Без особых усилий и большого замешательства. ;-) Если говорить о простых типах данных, таких как int и char, разница между массивом и указателем будет небольшой. Эти объявления очень похожи (но не одинаковы - например,
sizeof
будут возвращать разные значения):Вы можете добраться до любого элемента в массиве, как это
Индекс 1, так как массив начинается с элемента 0. :-)
Или вы могли бы сделать это в равной степени
Оператор указателя (*) необходим, поскольку мы сообщаем printf, что хотим напечатать символ. Без *, символьное представление самого адреса памяти будет напечатано. Теперь вместо этого мы используем самого персонажа. Если бы мы использовали% s вместо% c, мы бы попросили printf напечатать содержимое адреса памяти, на который указывает «a» плюс один (в этом примере выше), и нам не пришлось бы ставить * спереди:
Но это не просто напечатало бы второй символ, но вместо этого все символы в следующих адресах памяти, пока не был найден нулевой символ (\ 0). И здесь вещи начинают становиться опасными. Что если вы случайно попытаетесь напечатать переменную типа integer вместо указателя на символ с помощью% s форматера?
Это напечатало бы все, что найдено на адресе памяти 120, и продолжит печать, пока не будет найден нулевой символ. Это неправильно и незаконно выполнять этот оператор printf, но он, вероятно, будет работать в любом случае, так как указатель на самом деле имеет тип int во многих средах. Представьте себе проблемы, которые вы могли бы вызвать, если бы вместо этого вы использовали sprintf () и присвоили слишком длинный «массив символов» другой переменной, для которой было выделено только определенное ограниченное пространство. Скорее всего, вы закончите тем, что переписываете что-то еще в памяти и вызовете сбой вашей программы (если вам повезет).
Да, и если вы не присваиваете строковое значение массиву / указателю char при его объявлении, вы ДОЛЖНЫ выделить ему достаточный объем памяти, прежде чем присваивать ему значение. Использование malloc, calloc или аналогичного. Это потому, что вы объявили только один элемент в вашем массиве / один адрес памяти, на который нужно указать. Итак, вот несколько примеров:
Обратите внимание, что вы все еще можете использовать переменную x после выполнения free () выделенной памяти, но вы не знаете, что там находится. Также обратите внимание, что два printf () могут дать вам разные адреса, поскольку нет гарантии, что второе выделение памяти будет выполнено в том же пространстве, что и первое.
источник
a
указатель, а во второмa
массиве. Я уже упоминал об этом? Это не тоже самое! Проверьте сами: сравните sizeof (a), попробуйте назначить новый адрес массиву. Это не сработает.char* a = "Hello";
иchar a[] = "Hello";
не одинаковы, они совершенно разные. Один объявляет указатель, другой массив. Попробуйте,sizeof
и вы увидите разницу.Одной из причин использования указателей является то, что переменная или объект могут быть изменены в вызываемой функции.
В C ++ лучше использовать ссылки, чем указатели. Хотя ссылки по сути являются указателями, C ++ в некоторой степени скрывает этот факт и создает впечатление, будто вы передаете по значению. Это позволяет легко изменить способ, которым вызывающая функция получает значение, без необходимости изменять семантику его передачи.
Рассмотрим следующие примеры:
Используя ссылки:
Использование указателей:
источник
Вот пример на C:
печать
потому что он принимает значение для каждого символа и увеличивает его на единицу.
источник
because it takes the value for each character and increments it by one
, Находится в представлении ascii или как?Указатели являются одним из способов получения косвенной ссылки на другую переменную. Вместо того, чтобы держать значение переменной, они сообщают вам ее адрес . Это особенно полезно при работе с массивами, поскольку, используя указатель на первый элемент в массиве (его адрес), вы можете быстро найти следующий элемент, увеличив указатель (до следующего местоположения адреса).
Лучшее объяснение указателей и арифметики указателей , которые я читал в K & R в Язык программирования C . Хорошая книга для начала изучения C ++ - C ++ Primer .
источник
Позвольте мне попробовать и ответить на это тоже.
Указатели похожи на ссылки. Другими словами, это не копии, а способ ссылки на исходное значение.
Прежде всего, вам обычно приходится часто использовать указатели , когда вы работаете со встроенным оборудованием . Может быть, вам нужно переключить состояние цифрового вывода ввода-вывода. Возможно, вы обрабатываете прерывание и вам нужно сохранить значение в определенном месте. Вы получаете картину. Однако, если вы не имеете дело с аппаратным обеспечением напрямую и просто задаетесь вопросом, какие типы использовать, читайте дальше.
Зачем использовать указатели, а не обычные переменные? Ответ становится понятнее, когда вы имеете дело со сложными типами, такими как классы, структуры и массивы. Если бы вы использовали обычную переменную, вы могли бы в итоге сделать копию (компиляторы достаточно умны, чтобы предотвратить это в некоторых ситуациях, и C ++ 11 тоже помогает, но пока мы будем держаться подальше от этого обсуждения).
Что произойдет, если вы захотите изменить исходное значение? Вы можете использовать что-то вроде этого:
Это будет хорошо работать, и если вы не знаете точно, почему вы используете указатели, вы не должны их использовать. Остерегайтесь причины «они, вероятно, быстрее». Запустите свои собственные тесты и, если они действительно быстрее, используйте их.
Однако предположим, что вы решаете проблему, в которой вам нужно выделить память. Когда вы выделяете память, вам нужно освободить ее. Выделение памяти может или не может быть успешным. Здесь полезны указатели - они позволяют вам проверять существование выделенного вами объекта и позволяют получить доступ к объекту, для которого была выделена память, путем разыменования указателя.
Это ключ к тому, почему вы используете указатели - ссылки предполагают, что элемент, на который вы ссылаетесь, уже существует . Указатель не имеет.
Другая причина, по которой вы будете использовать указатели (или, по крайней мере, в конечном итоге придется иметь дело с ними), заключается в том, что они являются типом данных, существовавшим до ссылок. Поэтому, если вы в конечном итоге будете использовать библиотеки для выполнения задач, которые, как вы знаете, лучше всего, вы обнаружите, что многие из этих библиотек используют указатели повсеместно, просто из-за того, как долго они были (много из них были написаны до C ++).
Если вы не использовали никаких библиотек, вы могли бы разработать свой код таким образом, чтобы вы могли держаться подальше от указателей, но, учитывая, что указатели являются одним из основных типов языка, чем быстрее вы освоитесь с ними, тем больше Переносим ваши навыки C ++.
С точки зрения удобства обслуживания, я должен также упомянуть, что когда вы используете указатели, вы должны либо проверить их действительность, либо обработать случай, когда они недействительны, либо просто предположить, что они действительны, и принять тот факт, что ваш программа потерпит крах или когда это предположение будет нарушено. Иными словами, ваш выбор с указателями - либо усложнять код, либо увеличивать объем работ по обслуживанию, когда что-то ломается, и вы пытаетесь отследить ошибку, относящуюся к целому классу ошибок, которые вызывают указатели, например, повреждение памяти.
Поэтому, если вы контролируете весь свой код, держитесь подальше от указателей и вместо этого используйте ссылки, сохраняя их постоянными, когда можете. Это заставит вас задуматься о времени жизни ваших объектов и в конечном итоге сделает ваш код более понятным.
Просто запомните эту разницу: ссылка, по сути, является действительным указателем. Указатель не всегда действителен.
Так я говорю, что невозможно создать недействительную ссылку? Нет, это вполне возможно, потому что C ++ позволяет вам делать практически все. Это просто сложнее сделать непреднамеренно, и вы будете поражены тем, сколько ошибок непреднамеренно :)
источник
Вот несколько иное, но глубокое понимание того, почему многие функции C имеют смысл: http://steve.yegge.googlepages.com/tour-de-babel#C
По сути, стандартная архитектура ЦП является архитектурой фон Неймана, и чрезвычайно полезно иметь возможность ссылаться на местоположение элемента данных в памяти и выполнять арифметические действия с ним на такой машине. Если вы знаете какой-либо вариант языка ассемблера, вы быстро увидите, насколько это важно на низком уровне.
C ++ делает указатели немного запутанными, так как он иногда управляет ими и скрывает их эффект в виде «ссылок». Если вы используете прямую C, потребность в указателях становится намного более очевидной: нет другого способа сделать вызов по ссылке, это лучший способ сохранить строку, это лучший способ перебрать массив и т. Д.
источник
Одно из применений указателей (я не буду упоминать то, что уже освещалось в постах других людей) - доступ к памяти, которую вы не выделили. Это не очень полезно для программирования на ПК, но используется во встроенном программировании для доступа к аппаратным устройствам с отображенной памятью.
В старые времена DOS вы имели возможность напрямую обращаться к видеопамяти видеокарты, объявляя указатель на:
Многие встроенные устройства все еще используют эту технику.
источник
gsl::span
, и скоро это будетstd::span
.По большей части указатели являются массивами (в C / C ++) - они являются адресами в памяти и при желании могут быть доступны как массив (в «нормальных» случаях).
Так как они являются адресом элемента, они маленькие: они занимают только пространство адреса. Поскольку они маленькие, отправка их в функцию обходится дешево. И затем они позволяют этой функции работать с реальным элементом, а не с копией.
Если вы хотите динамически распределять память (например, для связанного списка), вы должны использовать указатели, потому что они являются единственным способом извлечения памяти из кучи.
источник
std::array
.Указатели важны во многих структурах данных, дизайн которых требует умения эффективно связывать или связывать один «узел» с другим. Вы бы не «выбрали» указатель над обычным типом данных, таким как float, у них просто разные цели.
Указатели полезны, когда вам требуется высокая производительность и / или компактный объем памяти.
Адрес первого элемента в вашем массиве может быть назначен указателю. Это тогда позволяет вам получить доступ к основным выделенным байтам напрямую. Весь смысл массива в том, чтобы избежать необходимости делать это, хотя.
источник
Один из способов использовать указатели на переменные - это исключить дублирование памяти. Например, если у вас есть какой-то большой сложный объект, вы можете использовать указатель для указания на эту переменную для каждой ссылки, которую вы делаете. С переменной необходимо дублировать память для каждой копии.
источник
В C ++, если вы хотите использовать полиморфизм подтипов , вы должны использовать указатели. Смотрите этот пост: C ++ Полиморфизм без указателей .
Действительно, когда вы думаете об этом, это имеет смысл. Когда вы используете полиморфизм подтипов, в конечном итоге вы заранее не знаете, какая реализация метода класса или подкласса будет вызвана, потому что вы не знаете, что такое настоящий класс.
Эта идея иметь переменную, которая содержит объект неизвестного класса, несовместима с режимом C ++ по умолчанию (без указателя) для хранения объектов в стеке, где объем выделенного пространства напрямую соответствует классу. Примечание: если в классе 5 полей экземпляра против 3, потребуется выделить больше места.
Обратите внимание, что если вы используете «&» для передачи аргументов по ссылке, косвенное обращение (т. Е. Указатели) все еще остается за кадром. '&' - это просто синтаксический сахар, который (1) избавляет вас от необходимости использовать синтаксис указателя и (2) позволяет компилятору быть более строгим (например, запрещать нулевые указатели).
источник
goto
инструкции также используются за кулисами - в инструкциях целевой машины. Мы до сих пор не утверждаем, что использовали их.Потому что копирование больших объектов повсюду тратит время и память.
источник
Вот мой ответчик, и я не буду обещать быть экспертом, но я нашел, что указатели великолепны в одной из моих библиотек, которые я пытаюсь написать. В этой библиотеке (это графический API с OpenGL :-)) вы можете создать треугольник с переданными в него объектами вершин. Метод draw берет эти объекты треугольника, и хорошо .. рисует их на основе созданных мною объектов вершин. Ну, все в порядке.
Но что, если я изменю координату вершины? Переместить это или что-то с помощью MoveX () в классе вершин? Ну, ладно, теперь мне нужно обновить треугольник, добавляя больше методов и производительность теряется, потому что я должен обновлять треугольник каждый раз, когда вершина движется. Все еще не большое дело, но это не так здорово.
Теперь, что если у меня есть сетка с тоннами вершин и тоннами треугольников, и сетка вращается, движется и тому подобное. Мне придется обновить каждый треугольник, который использует эти вершины, и, вероятно, каждый треугольник в сцене, потому что я не знаю, какие из них используют какие вершины. Это очень требовательно к компьютеру, и если у меня есть несколько сеток поверх ландшафта, о боже! У меня проблемы, потому что я обновляю каждый треугольник почти каждый кадр, потому что эти вершины меняются все время!
С помощью указателей вам не нужно обновлять треугольники.
Если бы у меня было три * объекта Vertex на класс треугольника, я не только экономлю место, потому что у zillion треугольников нет трех больших объектов вершины, но и эти указатели всегда будут указывать на вершины, для которых они предназначены, независимо от того, как часто меняются вершины. Поскольку указатели по-прежнему указывают на одну и ту же вершину, треугольники не меняются, и процесс обновления легче обрабатывать. Если бы я вас запутал, я бы не усомнился в этом, я не претендую на звание эксперта, просто бросая свои два цента в дискуссию.
источник
Необходимость указателей на языке Си описана здесь
Основная идея заключается в том, что многие ограничения в языке (например, использование массивов, строк и изменение нескольких переменных в функциях) могут быть устранены путем манипуляций с областями памяти данных. Чтобы преодолеть эти ограничения, указатели были введены в C.
Кроме того, также видно, что с помощью указателей вы можете выполнять свой код быстрее и экономить память в тех случаях, когда вы передаете большие типы данных (например, структуру со многими полями) в функцию. Создание копии таких типов данных перед передачей займет много времени и потребует память. Это еще одна причина, по которой программисты предпочитают указатели для больших типов данных.
PS: Пожалуйста, обратитесь по ссылке для подробного объяснения с примером кода.
источник
В java и C # все ссылки на объекты являются указателями, в случае c ++ вы можете лучше контролировать, куда указывает указатель. Помните, С великой силой приходит великая ответственность.
источник
Что касается вашего второго вопроса, как правило, вам не нужно использовать указатели при программировании, однако есть одно исключение из этого, когда вы делаете публичный API.
Проблема с конструкциями C ++, которые люди обычно используют для замены указателей, очень сильно зависит от используемого вами набора инструментов, что хорошо, когда у вас есть все необходимое управление исходным кодом, однако, если вы компилируете статическую библиотеку с Visual Studio 2008, например и попробуйте использовать его в Visual Studio 2010, вы получите массу ошибок компоновщика, потому что новый проект связан с более новой версией STL, которая не имеет обратной совместимости. Все становится еще хуже, если вы компилируете DLL и предоставляете библиотеку импорта, которую люди используют в другом наборе инструментов, потому что в этом случае ваша программа рано или поздно вылетит без видимой причины.
Таким образом, с целью перемещения больших наборов данных из одной библиотеки в другую вы можете рассмотреть возможность указания указателя на массив для функции, которая должна копировать данные, если вы не хотите, чтобы другие использовали те же инструменты, которые вы используете. , Хорошей частью этого является то, что он даже не должен быть массивом в стиле C, вы можете использовать std :: vector и дать указатель, указав, например, адрес первого элемента & vector [0], и использовать std :: vector для внутреннего управления массивом.
Еще одна веская причина для использования указателей в C ++ опять-таки связана с библиотеками, подумайте о том, чтобы иметь dll, который не может быть загружен при запуске вашей программы, поэтому, если вы используете библиотеку импорта, зависимость не будет удовлетворена, и программа аварийно завершит работу. Это тот случай, например, когда вы предоставляете публичный API в DLL рядом с вашим приложением и хотите получить к нему доступ из других приложений. В этом случае, чтобы использовать API, вам нужно загрузить dll из его местоположения (обычно это находится в разделе реестра), а затем вам нужно использовать указатель на функцию, чтобы иметь возможность вызывать функции внутри DLL. Иногда люди, которые создают API, достаточно хороши, чтобы предоставить вам файл .h, который содержит вспомогательные функции для автоматизации этого процесса и все необходимые указатели функций,
источник
Есть много причин для указателей. Если вы хотите сохранить межязыковую совместимость, особенно важно иметь имя C, искажение имен в DLL.
источник