Какова точка указателей на С++, когда я могу просто объявить переменные? Когда это целесообразно использовать?
В чем смысл указателей?
Ответ 1
Указатели лучше всего понимаются в различиях C и С++ при переходе переменной в функции.
Да, вы можете передать либо целую переменную, либо просто указатель на нее (жаргон по значению или ссылке, соответственно).
Но что, если переменная составляет 20 мегабайт байтов, например, вы решили прочитать весь файл в одном массиве? Передача его по значению была бы глупо: зачем вы копируете 20 мегабайт для этой операции, и если вы в конечном итоге ее модифицируете (т.е. Это параметр out), вам нужно скопировать эти 20 мегабайт BACK?
Лучше всего просто "указать" на него. Вы говорите: "Здесь указатель на большой блок памяти". И это небольшое косвенное использование экономит массу времени.
Как только вы это понимаете, все остальное в основном одно и то же. Переупорядочение элементов в списке становится просто заменой указателей, а не копированием каждого элемента вокруг, вам не нужно знать, насколько велики вещи, когда вы начинаете, и т.д.
Ответ 2
Указатели наиболее полезны при работе с структурами данных, размер и форма которых неизвестны во время компиляции (списки мысли, деревья, графики, массивы, строки,...).
Edit
Эти связанные ответы также могут помочь (лучший ответ во второй ссылке определенно стоит посмотреть):
В С++ я не могу ухватить указатели и классы
Каковы препятствия для понимания указателей и что можно сделать для их преодоления?
Ответ 3
Указатели также отлично подходят для передачи измененного аргумента функции, чтобы вызывающий мог "видеть изменение". Вы можете задаться вопросом: "Но почему бы не использовать ссылку?". Мне нравится аргумент Google:
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Reference_Arguments
Ответ 4
У меня был тот же самый вопрос, когда я изучал указатели, они просто не казались важными, но по мере того, как вы прогрессируете, вы обнаруживаете, что они несколько раз очень полезны.
Указатели часто используются в ситуациях программирования. Например, когда вы ссылаетесь на массив по имени, например
array[i] = 3;
Компилятор делает какую-то причудливую математику, которая заканчивает превращение этого кода в
(адрес массива) + (sizeof (элементы массива) * i) = 3;
Они также создают деревья, связанные списки и другие структуры данных, которые вы узнаете, когда узнаете больше.
Ответ 5
На самом базовом уровне указатели позволяют связывать непересекающиеся блоки памяти. Простой (надуманный, по общему признанию) пример того, где указатели могут помочь вам, будет в алгоритме, который требует массив из 1000000000000 целых чисел. Такой массив будет слишком большим, чтобы вставляться в ОЗУ компьютера, на котором я печатаю прямо сейчас, если бы попытался определить такое определение, как:
int bigMatrix[1000000000000]; // I can't allocate this much contiguous memory
Однако, если я создаю один массив указателей, я могу сохранить подмассивы на массиве среднего размера.
int *bigMatrix[1000000]; // Each pointer refers to a sub-array
// of 1000000 elements on disk
По общему признанию, мне придется писать код на страницу в этих суб-массивах, если/когда пользователь требует их, включая скрытие нотации массива за методом доступа. Тем не менее, указатели позволяют мне создавать специальные ассоциации, которые мне нужны, когда они мне понадобятся.
Ответ 6
Иногда у вас есть функция, которая должна возвращать некоторый объем памяти, который не является установленной величиной, например, чтение из файла.
bool ReadDataFromFile(char** contents);
Вы объявляете содержимое char * и передаете адрес этого указателя на функцию. Эта функция затем выделит память, и ваш указатель укажет на содержимое после возврата.
Ответ 7
Помимо эффективности и гибкости. Главным моментом указателей на C/С++ является то, как работает оборудование, вы не могли зачать драйвер устройства, диспетчер памяти или эффективный кеш, не используя указатели где-то вдоль линии.
Одна из основных целей проектирования для первого компилятора C заключалась в том, чтобы быть "переносимым языком ассемблера" и иметь возможность делать на языке более высокого уровня все, что вы могли бы сделать с традиционным сборочным/машинным кодом. Это означает возможность манипулирования адресами напрямую - это точка указателей.
Однако, следуя принципу KISS, не используйте указатели, если они действительно упрощают работу.
Ответ 8
С точки зрения архитектуры указатели являются экономичным способом моделирования отношения 0.. n:
struct A {
vector<const B *> *pBees;
A() : pBees(nullptr) {}
void notice_bee(const B *pB) {
if (!pBees)
pBees = new vector<const B *>;
pBees.push_back(pB)
}
~A() {
delete pBees; // no need to test, delete nullptr is safe
}
size_t bees_noticed() { return pBees ? pBees->size : 0 }
};
Если подавляющему большинству объектов A никогда не потребуется обращать внимание на любые объекты B, нет причин, по которым каждый объект A должен иметь вектор нулевой длины. В Gnu С++ 4.0.1 sizeof (vector) равен 12; sizeof (vector *) равен 4.
Ответ 9
Предположим, вы пишете текстовый редактор. В этом случае вы заранее не знаете размер документа. У вас может возникнуть соблазн объявить что-то вроде
char Document[10000];
но потом, конечно, когда-нибудь кто-то захочет использовать ваш редактор на гораздо большем документе. Так что эта попытка бесполезна; и вам нужен способ запросить новую память во время выполнения (вместо времени компиляции).
С++ способ сделать это - использовать оператор new, который возвращает указатель на только что выделенную память:
char* pDocument = new char[getSizeOfDocument()];
(Обратите внимание, что этот пример упрощен. В реальной жизни вы, конечно, не сделали бы этого так, а вместо этого использовали бы что-то вроде std::string и std::vector, которые внутренне делают это выделение для вас.)
Ответ 10
Мне кажется, что вы еще не узнали о распределении динамической памяти. Существует два способа выделения памяти на С++: статически и динамически. Вы уже знакомы со статическим распределением (т.е. Объявлением переменных). Это здорово, если вы на момент написания своей программы знаете, сколько переменных (а точнее, памяти) вам нужно.
Но что, если вы этого не сделаете? Например, скажем, вы читаете, что содержит кучу цифр, которые вам нужно отслеживать. Зачем? Я не знаю, но это не главное.
Ну, вы можете начать с массива:
int array[100];
Но что, если в файле содержится более 100 номеров? В конце концов вам понадобится более гибкое решение:
int *array = new int[size];
// do stuff
delete [] array;
Это дает вам большую гибкость и позволяет создавать более динамические структуры данных.
Ответ 11
Указатели сохраняют адрес памяти, поэтому вы будете использовать их, когда вам понадобится адрес памяти. Это может быть полезно для таких вещей, как управление памятью, зная, где хранятся ваши данные. Мой профессор сказал нам, что это прекрасная вещь для продвинутых программ с точки зрения обеспечения того, чтобы ваши данные смежно располагались в памяти (например, строки c-стиля).
Надеюсь, что это какая-то форма помощи для вас!
-Zen, новичок в С++
Ответ 12
От Wikipedia:
Ссылки на С++ отличаются от указателей в несколько основных способов:
- Невозможно напрямую обратиться к ссылочному объекту после он определен; любое его имя ссылается непосредственно на объект, который он ссылки.
- Как только ссылка создана, она не может быть позже сделана ссылка другой объект; мы говорим, что это не может быть желание пересесть. Это часто делается с указатели.
- Ссылки не могут быть нулевыми, тогда как указатели могут; каждая ссылка ссылается к некоторому объекту, хотя он может или может недействительны.
- Ссылки не могут быть неинициализированы. Потому что невозможно повторно инициализировать ссылку, они должны быть инициализируются, как только они создано. В частности, локальные и глобальные переменные должны быть инициализированы где они определены, и ссылки которые являются членами данных класса экземпляры должны быть инициализированы в список инициализаторов класса конструктор.
С вышеуказанными ограничениями указатели - это все, что вы получили как легкий контейнер для ссылки на объект, особенно если вы делаете что-то с полиморфным или динамическим. Примером может служить динамический массив чистых абстрактных указателей классов для представления окон.
Поскольку я слишком ленив, чтобы думать о примере полиморфизма, я вытащу один из cplusplus.com:
// pointers to base class
#include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
};
class CRectangle: public CPolygon {
public:
int area ()
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area ()
{ return (width * height / 2); }
};
int main () {
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = ▭
CPolygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << endl;
cout << trgl.area() << endl;
return 0;
}
В приведенном выше коде указатель CPolygon *
используется для ссылки на объект CRectangle
и CTriangle
.