Зачем нужны указатели в c
Перейти к содержимому

Зачем нужны указатели в c

  • автор:

Указатели (C++)

Указатель — это переменная, в которой хранится адрес памяти объекта. Указатели широко используются как в C, так и в C++ для трех основных целей:

  • для выделения новых объектов в куче,
  • передача функций другим функциям
  • для итерации элементов в массивах или других структурах данных.

В программировании в стиле C необработанные указатели используются для всех этих сценариев. Однако необработанные указатели являются источником многих серьезных ошибок программирования. Поэтому их использование настоятельно не рекомендуется, за исключением случаев, когда они обеспечивают значительное преимущество производительности, и нет неоднозначности в отношении того, какой указатель является владельцем указателя, который отвечает за удаление объекта. Современный C++ предоставляет интеллектуальные указатели для выделения объектов, итераторов для обхода структур данных и лямбда-выражений для передачи функций. Используя эти средства языка и библиотеки вместо необработанных указателей, вы сделаете программу более безопасной, проще отладить и упростить понимание и обслуживание. Дополнительные сведения см . в смарт-указателях, итераторах и лямбда-выражениях .

В этом разделе

  • Необработанные указатели
  • Константные и переменные указатели
  • новые и удаленные операторы
  • Интеллектуальные указатели
  • Практическое руководство. Создание и использование экземпляров unique_ptr
  • Практическое руководство. Создание и использование экземпляров shared_ptr
  • Практическое руководство. Создание и использование экземпляров weak_ptr
  • Практическое руководство. Создание и использование экземпляров CComPtr и CComQIPtr

См. также

Обратная связь

Были ли сведения на этой странице полезными?

Обратная связь

Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделе https://aka.ms/ContentUserFeedback.

Отправить и просмотреть отзыв по

Зачем нужны указатели?

Oбъяcнитe, зaчeм нyжны yкaзaтeли? Heт, я пoнял пpинцип paбoты, нo нe пoнял oднoгo — ЗAЧEM? Зaчeм paбoтaть c фyнкциями-члeнaми, пepeмeнными пo ccылкe, ecли я мoгy paбoтaть c ними нaпpямyю?

Отслеживать
51.6k 204 204 золотых знака 67 67 серебряных знаков 251 251 бронзовый знак
задан 4 янв 2012 в 13:25
3,288 4 4 золотых знака 37 37 серебряных знаков 49 49 бронзовых знаков
Если Вы не поняли «зачем», значит Вы не поняли указатели!
4 янв 2012 в 13:28

Посмотрите, например, man qsort (это очень полезная функция сортировки), подумайте как это можно запрограммировать и многое прояснится.

4 янв 2012 в 13:59

qsort реализуется и без указателей. Например в таких языках как lisp/haskell, где указателей (по крайней мере в явном виде) нет. На плюсах можно без указателей тоже сделать, но только памяти будет больше потребляться.

4 янв 2012 в 14:20
Какой смысл биться за лишние сто байт памяти?
4 янв 2012 в 15:20

На самом деле все намного проще, указатели нужны для того чтобы писать «волшебные» коды, быстрые и непанятные. + для того чтобы те кто не знает С++ думали что С++ это что-то неимоверно сложное 😀 Использовать х надо потому, что в они есть и твой код становится более непонятным. **** PS:Как мне показалось — объяснил на вашем нынешнем уровне **** PPS:не трольте коммент ибо коммент сам по себе троллинг 😀

4 янв 2012 в 15:35

4 ответа 4

Сортировка: Сброс на вариант по умолчанию

«Если кто-то зажигает звезды, значит, это кому-нибудь нужно»

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

void func (int a) < a += 10; >. int i = 2; func(i); 

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

void func (int* a) < *a += 10; >. int i = 2; func(&i); 

Если нужно передать в функцию структуру или объект, то передавать его целиком можно, но это может отнять кучу памяти и времени, ведь они могут быть очень большими. В простых примерах это незаметно, но в реальных программах, где это может повторяться тысячи и миллионы раз, тормоза будут огромные. Вместо этого передаете указатель или ссылку и получаете из функции доступ к объекту.

Уже говорили о динамическом выделении памяти. Если еще про это не читали, то прочитаете. Там без указателей В ПРИНЦИПЕ не обойтись.

Это лишь несколько примеров. Чем глубже будете вникать в С++, тем больше об этом узнаете.

И еще. Не думайте, что программисты до Вас были дураками.

Отслеживать
user207618
ответ дан 4 янв 2012 в 18:56
23.9k 2 2 золотых знака 38 38 серебряных знаков 69 69 бронзовых знаков

  1. Для возврата нескольких значений из функции. В качестве аргумента передаётся указатель на переменную, функция записывает туда значение. Такой подход очень распространён в DirectX, OpenGL, Windows API и других библиотеках в стиле C. Для этого можно использовать и ссылки, но не рекомендуется, так как синтаксис передачи и возврата неотличим.
  2. Для хранения адреса динамически выделенной памяти. Она отличается от обычной тем, что программист сам регулирует время жизни объектов, и её больше (а размер стека всего порядка мегабайта). Если адрес будет потерян, то память нельзя будет ни использовать, ни освободить. Возникнет утечка памяти.
  3. C-строка представляет собой указатель на её первый символ.
  4. Для создания различных структур данных: связанных списков, деревья и т. д.
  5. Для передачи аргумента в функцию без копирования (и вызова конструктора для объектов), которое может оказаться долгим для сложных объектов. Правда, здесь лучше использовать константные ссылки.

Таким образом, примений указателей очень много.

Отслеживать

@Xyanight, вспомните, что массивы в Си представляются указателями (даже не в случае параметров). Например: int a[10], *b = a+5; . printf («%d\n»,b[0]); // распечатает значение a[5] Вообще указатели в Си позволяют писать более обобщенный код.

4 янв 2012 в 16:46
В качестве аргумента передаётся указатель на переменную, функция записывает туда значение.
27 июл 2015 в 14:05

А зачем нужны почтовые адреса? Можно же общаться сразу с людьми. Но иногда приходится поручать доставку письма почтальону, которому надо сначала сказать, куда конкретно он должен доставить письмо, так как один и тот же почтальон должен уметь единообразно для адресанта доставлять письма всем адресатам. Как? Лезть в его днк нельзя, нельзя его даже переучивать. Указатель – это такая величина, в которой можно хранить адрес. А ссылка – это нечто, ведущее себя как указатель, но имеющее при этом синтаксис сразу целевой величины, которую мы по этому адресу ищем. То есть поле на конверте с адресом Пети, притворяющееся самим Петей. На это поле на конверте можно наорать вместо Пети, а обидится Петя. По указателю же можно только нанять хулигана, поручив ему наорать на Петю. Совсем же по-другому выглядит, а результат тот же. В одном случае мы голос посрвали, в другом – явно послали посредника. Но по указателю можно послать: почтальона, шпиона, хулигана и киллера, а по ссылке – только наорать на адрес на конверте, спокойно ему что-то сказать или поглазеть прямо на Петин стол, но киллеры даже не поймут, чего от них хотят, а сами мы убивать не умеем.

Отслеживать
ответ дан 8 ноя 2022 в 13:12
user329063 user329063

Чтобы выделять большое количество памяти. Советую прочитать про стек и кучу (heap). На стеке при большом объеме данных будет происходить переполнение.

Отслеживать
ответ дан 4 янв 2012 в 14:03
221 1 1 серебряный знак 10 10 бронзовых знаков

Вот именно! Автору вопроса нужно смотреть в сторону размерных и ссылочных типов данных, как они устроены, какие отличия, где хранятся, зачем их разделили и тд. К чему тут тролинг.

4 янв 2012 в 15:56
Нeт, я пoнял пpинцип paбoты, нo нe пoнял oднoгo — ЗAЧEM Малеха противоречивый бред? Не находите?
4 янв 2012 в 16:00
Согласен — автор не разобрался в данной теме!
4 янв 2012 в 16:13
@AlexWindHope ну значит плохо объясняли
4 янв 2012 в 16:14

public и private предполагает что код может быть использовн кем-то еще, кто может залезть своими руками куда не надо и все поломать.

Для чего же нужны указатели?

Всем доброго времени суток. Никак не могу понять для чего нужны указатели? Объясните простым, человеческим языком, пожалуйста.
Сам я сначала думал чтобы через указатели изменять значения переменных, но зачем это делать через указатели, если можно напрямую данной переменной присвоить новое значение.

  • Вопрос задан более трёх лет назад
  • 9357 просмотров

2 комментария

Оценить 2 комментария

Vitaly @vt4a2h Куратор тега C++

Напишите сначала, чтовы сделали для того, чтобы разобраться с этим вопросом и что конкретно не поняли. Например, читали ли вы книги по C++, статьи, пробовали ли писать программы? Если нет, то почитатйте. Просто так объяснять не имеет смысла, вы поймёте не больше, чем сейчас.

AtomKrieg

«Используют ли он указатели? Это хороший признак. Многие программисты на Си просто не знают, как заставить работать указатели. Я, как правило, не отказываюсь от кандидата из-за отсутствия у него какого-то навыка. Однако я обнаружил, что понимание указателей в Си — это не навык, а способность. В начале первого курса на факультете кибернетики набирается человек 200 вундеркиндов, писавших в четырехлетнем возрасте игрушки для Atari 800 на BASIC’е. Они хорошо проводят время, изучая Паскаль, но в один прекрасный день профессор заводит речь об указателях, и они внезапно перестают понимать. То есть абсолютно. 90% потока переходит на отделение политологии, объясняя это тем, что на кибернетике мало симпатичных студенток. На самом же деле, по неизвестной причине часть человечества просто рождается без того отдела мозга, который понимает указатели. Указатели — это не навык, а способность, требующая особого мышления, и некоторые люди им просто не обладают.»
Джоэль Спольски предлагает вам изучать политологию вместо программирования.

Решения вопроса 1

Nipheris

Станислав Макаров @Nipheris Куратор тега C++

Ну и я тогда тоже попробую.

Посмотрим на вопрос с другой стороны.
Переменная — это контейнер для значения. Что нам вообще нужно для работы с переменной? Что нам нужно, чтобы считать/записать значение из/в нее? Нам нужны тип данных и адрес в памяти.
Тип данных — отдельная история, оставим пока его в стороне. Скажем только, что он определяет допустимые операции со значением и количество памяти, необходимое для хранения значения.
Поговорим об адресе. В языках, где доступна прямая работа с памятью, любая переменная имеет свой адрес в памяти. Также, в чуть более узком смысле, переменная находится в такой области памяти, в которую разрешена запись. Не имеет особого значения, какой структурой данных управляется эта память — стеком или кучей — важно, чтобы на момент использования эта память была доступна.
Корректный адрес в памяти — это и уникальный «ключ» переменной, ее отличительная черта. Работая с переменными, программист на низкоуровневом языке неизбежно работает с адресами.
Другой вопрос — это способ работы с адресом переменной. Когда вы создаете обычную локальную переменную, в работе с ней принимает участие компилятор. Когда вы пишете int a, компилятор (если не вдаваться в детали) размещает у себя в таблице идентификаторов пару: (имя_переменной, адрес_в_памяти). Обычная локальная переменная «а» (еще ее называют «автоматическая переменная») — способ создания переменной средствами компилятора. Компилятор освободит память, занимаемую этой переменной, когда она уйдет из области видимости. Однако, пока вам точно известно, что эта переменная «живет», вы можете совершенно спокойно получить ее адрес с помощью операции & — компилятор отдаст вам его из своей таблицы идентификаторов.

Но «переменные» в широком смысле можно создавать не только средствами компилятора, но и вручную с помощью malloc (Си) или new (С++). Эти динамические переменные живут столько, сколько вам нужно — вы их создаете, вам их и уничтожать. Об этих переменных компилятор ничего не знает, т.к. вы создаете их динамически во время выполнения программы. Для доступа к этим переменным вам также нужен адрес, но у компилятора его не попросишь: поэтому необходимо самому сохранять те адреса, что вернули вам функция malloc или оператор new. Этот адрес вы можете сохранить в ДРУГОЙ переменной, и такая переменная, хранящая адреса — и есть указатель (кроме того, если указатель не бестиповый (void*) то его тип (float*) еще и подсказывает нам тип переменной, на которую он указывает (float)).

Очень важно, что в указатель можно сохранить адрес ЛЮБОЙ переменной — как автоматической, которую вам создал компилятор, так и «ручной» — которую создали ВЫ с помощью malloc/new. И передать, например, этот адрес в функцию. Фактически, в языке Си указатели это и есть способ передачи САМОЙ ПЕРЕМЕННОЙ в функцию, а не ее ЗНАЧЕНИЯ на момент вызова. В C++ есть еще ссылки, но это отдельная история (ссылки — это указатель, «обернутый» в обычный идентификатор), по ним задайте отдельный вопрос.

если можно напрямую данной переменной присвоить новое значение.

Как раз таки «напрямую» вы не присвоите, т.к. у вас не может быть в одной области видимости ВСЕХ переменных, имеющихся в программе. Хотя бы потому, что у вас могут быть динамические переменные, и единственный способ работы с ними — работать через указатель.

Если вы никогда не работали с динамической памятью, вы можете спросить, какой смысл в «ручных» переменных, если для хранения их адреса нужна парная переменная-указатель? А вся фишка в том, что адрес динамически созданной переменной также можно хранить в динамически созданном указателе. Тут-то и открывается вся бесконечная свобода построения динамических структур данных. Если вы не слышали про связный список, то самое время почитать хорошую книгу с примерами. В одном ответе этого не расскажешь, это основы программирования.

Зачем нужны указатели в C++?

Как правило, многие программисты понимают разницу между объектами и указателями на объекты. Тем не менее не всегда ясно, в пользу какого из вышеперечисленных способов обращения делать выбор. Попробуем ответить на этот вопрос.

Однажды на StackOverflow прозвучал вопрос следующего содержания: «Why should I use a pointer rather than the object itself?» — «Почему я должен использовать указатель, а не сам объект?»

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

 
Object *myObject=new Object;
 
ObjectmyObject;

Также и с методами, почему вместо этого:

 
myObject.testFunc();

следует писать это:

 
myObject->testFunc();

Разработчик, который задал вопрос, предположил, что это даёт выигрыш в скорости, ведь мы обращаемся напрямую к памяти. К слову, он перешёл в C++-программирование с Java. Что же, посмотрим, что ему ответили.

Ответ

В языке программирования Java указатели в явном виде не используются, то есть разработчик не может обратиться в коде к объекту через указатель на него. Но на деле все типы в Java, за исключением базовых, являются ссылочными, ведь обращение к ним осуществляется по ссылке, хотя явно передать по ссылке параметр нельзя. Также следует заметить, что new в C++, в Java и C# — разные вещи.

Чтобы понять, что же такое указатели в C++, рассмотрим два похожих фрагмента кода. Java:

 
Object object1 =newObject();// Новый объект Object object2 =newObject();// Очередной новый объект object1 = object2;// Обе переменные ссылаются на объект, на который ранее ссылалась object2 // При изменении объекта, на который ссылается object1, произойдет изменение и // object2, ведь это один и тот же объект 

Эквивалентный код на C++:

 
Object* object1 =newObject();// Память выделена под новый объект // На эту память ссылается object1 Object* object2 =newObject();// Аналогично со вторым объектом delete object1; // В C++ нет системы сборки мусора, поэтому если этого не cделать, // к этой памяти программа уже не сможет получить доступ, // как минимум до перезапуска // Это не что иное, как утечка памяти object1 = object2;// Как и в Java, object1 указывает туда же, куда и object2

Но это – совсем другая вещь (C++):

 
Object object1;// Новый объект Object object2;// Еще один новый объект object1 = object2;// Полное копирование объекта object2 в object1, // а не переопределение указателя является очень дорогой операцией

Но будет ли выигрыш в скорости, если мы будем обращаться к памяти напрямую?

Оказывается, нет. Дело в том, что работа с указателями оформлена в виде кучи, а работа с объектами представляет собой стек — более простую и быструю структуру. Таким образом, в одном вопросе мы получили два: 1. Когда лучше использовать динамическое распределение памяти? 2. Когда лучше использовать указатели?

Конечно, всегда желательно выбирать для работы наиболее подходящий инструмент. Но почти всегда существует реализация лучше, чем с применением ручного динамического распределения (dynamicallocation) и/или «сырых» указателей.

Динамическое распределение

Формулировка вопроса подразумевает 2 способа создания объекта. Главное различие — срок их жизни (storageduration) в памяти программы.

Применяя ObjectmyObject; , разработчик полагается на автоопределение срока жизни, то есть объект уничтожится сразу же после выхода из его области видимости. При этом Object *myObject = newObject; сохраняет жизнь объекту до того самого момента, пока разработчик вручную не удалит его из памяти с помощью команды delete. Применяйте последний вариант лишь тогда, когда это на самом деле надо. А значит, всегда выбирайте автоматическое определение срока хранения объекта, если, конечно, это возможно.

Что касается принудительного установления срока жизни, то оно используется в следующих случаях: • надо, чтобы объект существовал и после выхода из области его видимости. Мы говорим об именно этом объекте и именно в этой области памяти, а не про его копию. Если же для вас это не является принципиальным (а чаще всего это так), следует положиться на автоопределение срока жизни; • надо применять много памяти, которая может переполнить стек. В принципе, с этой проблемой сталкиваются редко, но, бывает, приходится решать и такую задачу; • точно не известен размер массива, который придётся использовать. Вы должны знать, что в C++ массивы имеют фиксированный размер при определении. Это иногда вызывает проблемы, к примеру, во время считывания пользовательского ввода. Указатель же определяет лишь тот участок в памяти, куда записывается начало массива.

Когда применение динамического распределения необходимо, стоит инкапсулировать его посредством умного указателя (о таких указателях мы уже писали) или иного типа, поддерживающего концепцию “Получение ресурса есть инициализация”. Умными указателями, например, являются std::unique_ptr и std::shared_ptr .

Указатели

Иногда применение указателей оправдано не только из-за динамического распределения памяти. Однако помните, что практически всегда существует альтернативный путь, не предполагающий использование указателей — его и следует выбрать. То есть, если особая необходимость использовать указатели отсутствует, всегда выбирайте альтернативу.

Однако рассмотрим случаи, когда применение указателей оправдано: • ссылочная семантика. Иногда нужно обратиться к объекту вне зависимости от того, каким образом под него распределена память, если вы желаете обратиться в функции именно к этому объекту, а не к его копии. То есть речь идёт о случае, когда надо реализовать передачу по ссылке. Но, опять же, чаще всего тут достаточно использовать не указатель, а именно ссылку, ведь как раз для этого и созданы ссылки. Но если есть возможность обратиться к копии объекта, то и ссылку, соответственно, использовать нет необходимости (однако учтите, что копирование объекта является дорогой операцией); • полиморфизм. С помощью ссылки либо указателя возможен вызов функций в рамках полиморфизма. Но и тут использование ссылок предпочтительнее; • необязательный объект. Тут можно применять nullptr, дабы указать, что объект опущен. Но если это аргумент функции, сделайте реализацию с аргументами по умолчанию либо перегрузкой. С другой стороны, вы можете использовать тип, инкапсулирующий такое поведение, допустим, boost::optional (измененный в C++14 std::optional); • повышение скорости компиляции. Иногда нужно разделить единицы компиляции (compilationunits). Один из эффективных вариантов использования указателей — предварительная декларация. В результате вы получите возможность разнести единицы компиляции, что обычно положительно сказывается на ускорении времени компиляции. • взаимодействие с С-подобной библиотекой. Здесь придётся задействовать сырые указатели, освобождение памяти из-под которых вы выполняете в самый последний момент. Сырой указатель можно получить из умного, используя операцию get. Если библиотека использует память, которая потом должна освобождаться вручную, можно оформить в умном указателе деструктор.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *