Зачем использовать указатели?

301

Я знаю, что это действительно основной вопрос, но я только что начал с базового программирования на С++ после кодирования нескольких проектов с языками высокого уровня.

В принципе у меня есть три вопроса:

  • Зачем использовать указатели над нормальными переменными?
  • Когда и где я должен использовать указатели?
  • Как вы используете указатели с массивами?
  • 4
    Обсуждали раньше на этот вопрос Надеюсь, что поможет!
  • 0
    Другое обсуждение указателя здесь .
Показать ещё 4 комментария
Теги:
pointers

18 ответов

154
  • Зачем использовать указатели над нормальными переменными?

Короткий ответ: Не надо.;-) Указатели должны использоваться там, где вы не можете использовать что-либо еще. Это либо из-за отсутствия соответствующей функциональности, отсутствующих типов данных, либо для чистого исполнения. Еще ниже...

  • Когда и где я должен использовать указатели?

Короткий ответ: Где вы не можете использовать что-либо еще. В 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 вместо указателя char с форматом% s?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

Это будет печатать все, что находится по адресу 120 памяти, и продолжать печать до тех пор, пока не будет найден нулевой символ. Ошибочно и незаконно выполнять этот вывод printf, но он, вероятно, будет работать в любом случае, поскольку указатель фактически имеет тип int во многих средах. Представьте, какие проблемы вы могли бы решить, если бы вы использовали sprintf() вместо этого и слишком долго присваивали этот массив "char" другой переменной, которая выделяла только ограниченное пространство. Скорее всего, вам удастся написать что-то еще в памяти и привести к сбою вашей программы (если вам повезет).

О, и если вы не присвойте строковое значение массиву/указателю 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 после выполнения свободной() выделенной памяти, но вы не знаете, что там находится. Также обратите внимание, что два printf() могут предоставить вам разные адреса, поскольку нет гарантии, что второе распределение памяти выполняется в том же пространстве, что и первое.

  • 8
    Ваш пример с 120 в нем неверен. Он использует% c, а не% s, так что ошибки нет; он просто печатает строчную букву х. Более того, ваше последующее утверждение, что указатель имеет тип int, неверно и очень плохой совет для программиста на C, не имеющего опыта работы с указателями.
  • 0
    @R ..% c был изменен на% s
Показать ещё 18 комментариев
47

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

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

Рассмотрим следующие примеры:

Использование ссылок:

public void 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
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

Использование указателей:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}
  • 11
    Вероятно, стоит упомянуть, что ссылки безопаснее, поскольку вы не можете передать пустую ссылку.
  • 20
    Да, это, вероятно, самое большое преимущество использования ссылок. Спасибо за указание на это. Не каламбур предназначен :)
Показать ещё 3 комментария
32
  • Указатели позволяют ссылаться на одно и то же пространство в памяти из нескольких мест. Это означает, что вы можете обновлять память в одном месте, и это изменение можно увидеть из другого места в вашей программе. Кроме того, вы сможете сэкономить место, имея возможность обмениваться компонентами в ваших структурах данных.
  • Вы должны использовать указатели в любом месте, где вам нужно получить и передать адрес в определенное место в памяти. Вы также можете использовать указатели для перемещения массивов:
  • Массив - это блок смежной памяти, который был выделен определенным типом. Имя массива содержит значение начальной точки массива. Когда вы добавите 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

потому что он принимает значение для каждого символа и увеличивает его на единицу.

25

Указатели - это один из способов получения косвенной ссылки на другую переменную. Вместо того, чтобы удерживать значение переменной, они сообщают вам свой адрес. Это особенно полезно при работе с массивами, поскольку с помощью указателя на первый элемент в массиве (его адрес) вы можете быстро найти следующий элемент, увеличив указатель (до следующего адреса).

Лучшее объяснение указателей и арифметики указателей, которые я прочитал, находится в K и R Язык программирования C. Хорошая книга для начала обучения С++ - С++ Primer.

  • 1
    благодарю вас! наконец, практическое объяснение преимуществ использования стека! Указатель на позицию в массиве также улучшает производительность доступа к значениям @ и относительно указателя?
  • 0
    Это, наверное, лучшее объяснение, которое я читал. у людей есть способ "чрезмерно усложнять" вещи :)
20

Позвольте мне попытаться ответить и на это.

Указатели похожи на ссылки. Другими словами, они не являются копиями, а скорее способом ссылки на исходное значение.

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

Зачем использовать указатели вместо обычных переменных? Ответ становится яснее, когда вы имеете дело со сложными типами, такими как классы, структуры и массивы. Если бы вы использовали обычную переменную, вы могли бы сделать копию (компиляторы достаточно умны, чтобы предотвратить это в некоторых ситуациях, и С++ 11 тоже помогает, но пока мы не будем обсуждать эту дискуссию).

Теперь, что произойдет, если вы хотите изменить исходное значение? Вы можете использовать что-то вроде этого:

MyType a; //let ignore what MyType actually is right now.
a = modify(a); 

Это будет работать нормально, и если вы точно не знаете, почему используете указатели, вы не должны их использовать. Остерегайтесь причины "они, вероятно, быстрее". Запустите свои собственные тесты, и если они на самом деле быстрее, используйте их.

Однако, скажем, вы решаете проблему, когда вам нужно выделить память. Когда вы выделяете память, вам необходимо ее освободить. Распределение памяти может быть или не быть успешным. Вот где полезны указатели - они позволяют вам проверять наличие выделенного объекта, и они позволяют вам получить доступ к объекту, на который была выделена память путем удаления ссылки на указатель.

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(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
}

Это ключ к тому, почему вы использовали указатели - ссылки предполагают, что существующий элемент уже существует. Указатель этого не делает.

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

Если вы не использовали какие-либо библиотеки, вы могли бы создать свой код таким образом, чтобы вы могли держаться подальше от указателей, но учитывая, что указатели являются одним из основных типов языка, тем быстрее вам удобнее пользоваться ими, тем более переносимыми будут ваши навыки на С++.

С точки зрения ремонтопригодности я должен также упомянуть, что когда вы используете указатели, вам нужно либо проверить их достоверность, либо обработать случай, когда они недействительны, либо просто предположите, что они действительны и принимают факт, что ваша программа потерпит крах или ухудшится КОГДА это предположение нарушено. Другими словами, ваш выбор с помощью указателей заключается в том, чтобы либо ввести сложность кода, либо больше усилий по обслуживанию, когда что-то ломается, и вы пытаетесь найти ошибку, относящуюся ко всему классу ошибок, которые указывают указатели, как повреждение памяти.

Итак, если вы контролируете весь свой код, держитесь подальше от указателей и вместо этого используйте ссылки, сохраняя их const, когда сможете. Это заставит вас задуматься о сроках жизни ваших объектов и в конечном итоге сохранить код более понятным.

Просто помните эту разницу: Ссылка, по существу, является действительным указателем. Указатель не всегда действителен.

Так я говорю, что невозможно создать недопустимую ссылку? Нет. Это вполне возможно, потому что С++ позволяет делать практически все. Это просто сложнее сделать непреднамеренно, и вы будете поражены тем, сколько ошибок непреднамеренно:)

13

Здесь немного другое, но проницательное понимание того, почему многие особенности C имеют смысл: http://steve.yegge.googlepages.com/tour-de-babel#C

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

С++ делает указатели несколько запутанными, поскольку иногда он управляет ими для вас и скрывает их эффект в виде "ссылок". Если вы используете прямой C, потребность в указателях гораздо более очевидна: нет другого способа сделать call-by-reference, это лучший способ сохранить строку, это лучший способ перебора массива и т.д.

12

Одно использование указателей (я не буду упоминать о вещах, которые уже были учтены в сообщениях других людей) - это доступ к памяти, которую вы не выделили. Это не очень полезно для программирования на ПК, но оно используется во встроенном программировании для доступа к аппаратным устройствам с отображением памяти.

В старые времена DOS вы имели обыкновение иметь доступ к видеопанели видеокарты непосредственно, объявив указатель на:

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

Многие встроенные устройства по-прежнему используют эту технику.

11

В основном, указатели являются массивами (в C/С++) - они являются адресами в памяти и могут быть доступны как массив, если это необходимо (в "нормальных" случаях).

Поскольку они являются адресом элемента, они небольшие: они занимают только пространство адреса. Поскольку они маленькие, отправка их на функцию дешевая. И затем они позволяют этой функции работать с фактическим элементом, а не с копией.

Если вы хотите выполнить динамическое распределение хранилища (например, для связанного списка), вы должны использовать указатели, потому что они - единственный способ захватить память из кучи.

  • 8
    Я думаю, что вводить в заблуждение указатели - это массивы. Действительно, имена массивов являются константными указателями на первый элемент массива. То, что вы можете получить доступ к произвольной точке, как будто это массив, не означает, что это так ... вы можете получить нарушение прав доступа :)
  • 2
    Указатели не являются массивами. Прочтите раздел 6 FAQ по comp.lang.c
9

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

9

Указатели важны во многих структурах данных, для дизайна которых требуется эффективная связь или цепочка "node" с другим. Вы бы не "выбрали" указатель над выражением обычного типа данных, например float, они просто имеют разные цели.

Указатели полезны там, где требуется высокая производительность и/или компактность памяти.

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

6

В C++, если вы хотите использовать полиморфизм подтипа, вы должны использовать указатели. Смотрите этот пост: C++ Полиморфизм без указателей.

Действительно, когда вы думаете об этом, это имеет смысл. Когда вы используете полиморфизм подтипов, в конечном итоге вы не знаете заранее, какой класс или подкласс реализации метода будет вызван, потому что вы не знаете, что это за класс.

Эта идея иметь переменную, которая содержит объект неизвестного класса, несовместима с режимом C++ по умолчанию (без указателя) для хранения объектов в стеке, где объем выделенного пространства напрямую соответствует классу. Примечание: если у класса есть 5 полей экземпляра против 3, потребуется выделить больше места.


Обратите внимание, что если вы используете "&" для передачи аргументов по ссылке, косвенное обращение (т.е. Указатели) все еще остается за кадром. '&' - это просто синтаксический сахар, который (1) избавляет вас от необходимости использовать синтаксис указателя и (2) позволяет компилятору быть более строгим (например, запрещать нулевые указатели).
5

Поскольку копирование больших объектов по всем местам тратит время и память.

  • 2
    И как указатели помогают с этим? Я думаю, что человек из Java или .Net не знает разницы между стеком и кучей, поэтому этот ответ довольно бесполезен ..
  • 0
    Вы можете пройти по ссылке. Это предотвратит копирование.
Показать ещё 1 комментарий
4

Вот мой anwser, и я не буду пробовать быть экспертом, но я нашел указатели, чтобы быть замечательным в одной из моих библиотек, которые я пытаюсь написать. В этой библиотеке (это графический API с OpenGL:-)) вы можете создать треугольник с переданными в него объектами вершин. Метод draw берет эти треугольные объекты и хорошо... рисует их на основе созданных объектов вершин. Хорошо, все в порядке.

Но что, если я изменяю вершинную координату? Переместить его или что-то с moveX() в классе вершин? Ну, хорошо, теперь мне нужно обновить треугольник, добавив больше методов и производительность теряется, потому что я должен обновлять треугольник каждый раз, когда вершина перемещается. Все еще неважно, но это не так уж здорово.

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

С указателями вам не нужно обновлять треугольники.

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

3

Необходимость указателей на языке C описана здесь

Основная идея заключается в том, что многие ограничения в языке (например, использование массивов, строк и изменение нескольких переменных в функциях) можно было бы устранить, манипулируя местоположениями памяти данных. Чтобы преодолеть эти ограничения, указатели были введены в C.

Кроме того, также видно, что с помощью указателей время от времени вы можете быстрее запускать свой код и сохранять память (в тех случаях, когда вы передаете большие функции данных, например структуры, в функцию. Создание копии таких типов данных перед передачей потребуется время и будет потреблять память). Это еще одна причина, по которой программисты предпочитают указатели на большие типы данных.

PS: Пожалуйста, обратитесь за ссылкой для подробного объяснения с образцом кода.

3

В java и С# все ссылки на объекты являются указателями, вещь с С++ заключается в том, что у вас больше контроля над точками указателя. Помните: с великой силой приходит великая ответственность.

1

Что касается вашего второго вопроса, обычно вам не нужно использовать указатели во время программирования, однако есть одно исключение из этого, и это когда вы создаете публичный API.

Проблема с конструкциями С++, которые люди обычно используют для замены указателей, очень зависит от используемого набора инструментов, который подходит, когда у вас есть весь элемент управления, который вам нужен над исходным кодом, однако, если вы скомпилируете статическую библиотеку с визуальной студией 2008, и попытайтесь использовать его в visual studio 2010, вы получите тонну ошибок компоновщика, потому что новый проект связан с более новой версией STL, которая не поддерживает обратную совместимость. Когда вы скомпилируете DLL и получите библиотеку импорта, которую люди используют в другом наборе инструментов, ситуация становится еще более неприятной, потому что в этом случае ваша программа рано или поздно будет разбиваться по какой-либо причине.

Итак, для перемещения больших наборов данных из одной библиотеки в другую вы можете рассмотреть возможность указания указателя на массив функции, которая должна копировать данные, если вы не хотите принуждать других использовать одни и те же инструменты что вы используете. Хорошая часть этого заключается в том, что он даже не должен быть массивом C-стиля, вы можете использовать std::vector и указать указатель, указав адрес первого элемента & vector [0], например, и используйте std::vector для управления массивом внутри.

Еще одна веская причина использовать указатели на С++ снова относится к библиотекам, подумайте о наличии DLL, которая не может быть загружена при запуске вашей программы, поэтому, если вы используете библиотеку импорта, зависимость не выполняется, и программа вылетает из строя. Например, когда вы предоставляете публичный api в dll вместе с вашим приложением, и вы хотите получить к нему доступ из других приложений. В этом случае для использования API вам необходимо загрузить DLL из своего "местоположения" (обычно это в раздел реестра), а затем вам нужно использовать указатель на функцию, чтобы иметь возможность вызывать функции внутри DLL. Иногда люди, которые делают API, достаточно хороши, чтобы предоставить вам файл .h, содержащий вспомогательные функции для автоматизации этого процесса и предоставления вам всех указателей функций, которые вам нужны, но если вы не можете использовать LoadLibrary и GetProcAddress для окон и dlopen и dlsym на unix, чтобы получить их (учитывая, что вы знаете всю подпись функции).

1
  • В некоторых случаях указатели на функции должны использовать функции, которые находятся в общей библиотеке (.DLL или .so). Это включает в себя выполнение материалов на разных языках, где часто предоставляется интерфейс DLL.
  • Создание компиляторов
  • Создание научных калькуляторов, где у вас есть массив или векторная или строковая карта указателей функций?
  • Попытка изменить видеопамять напрямую - создание собственного графического пакета
  • Создание API!
  • Структуры данных - указатели ссылок node для специальных деревьев, которые вы создаете.

Есть много причин для указателей. Особенно важно, чтобы использование имени C было особенно важным в DLL, если вы хотите поддерживать совместимость на разных языках.

-5

используются, поскольку производительность увеличивается при распределении и возврате данных из области динамической кучи.

Сообщество Overcoder
Наверх
Меню