Арифметика указателей

У кого-нибудь есть хорошие статьи или объяснения (блоги, примеры) для арифметики указателя? Рисунок аудитории – куча Java-программистов, изучающих C и C ++.

    7 Solutions collect form web for “Арифметика указателей”

    Во-первых, может показаться модное видео. Это хорошее видео о указателях. Для арифметики, вот пример:

    int * pa = NULL; int * pb = NULL; pa += 1; // pa++. behind the scenes, add sizeof(int) bytes assert((pa - pb) == 1); print_out(pa); // possibly outputs 0x4 print_out(pb); // possibly outputs 0x0 (if NULL is actually bit-wise 0x0) 

    (Обратите внимание, что приращение указателя, содержащего нулевое значение указателя, строго неопределенное поведение. Мы использовали NULL, потому что нас интересовало только значение указателя. Обычно используйте приращение / уменьшение при указании на элементы массива).

    Ниже показано два важных понятия

    • сложение / вычитание целого числа на указатель означает перемещение указателя вперед / назад на N элементов. Поэтому, если int имеет 4 байта большой, pa может содержать 0x4 на нашей платформе после увеличения на 1.
    • вычитание указателя другим указателем означает получение их расстояния, измеряемое элементами. Таким образом, вычитание pb из pa даст 1, так как они имеют одно расстояние элемента.

    На практическом примере. Предположим, вы пишете функцию, и люди предоставляют вам указатель начала и конца (очень распространенная вещь на C ++):

     void mutate_them(int *begin, int *end) { // get the amount of elements ptrdiff_t n = end - begin; // allocate space for n elements to do something... // then iterate. increment begin until it hits end while(begin != end) { // do something begin++; } } 

    ptrdiff_t – это тип (конец – начало). Это может быть синоним «int» для некоторого компилятора, но может быть другим типом для другого. Нельзя знать, поэтому выбирается общий typedef ptrdiff_t .

    Здесь я узнал указатели: http://www.cplusplus.com/doc/tutorial/pointers.html

    Как только вы поймете указатели, арифметика указателей проста. Единственное различие между ним и обычной арифметикой заключается в том, что число, добавляемое к указателю, будет умножаться на размер типа, на который указывает указатель. Например, если у вас есть указатель на int а размер int равен 4 байтам, (pointer_to_int + 4) будет оцениваться по адресу памяти 16 байт (4 интервала) вперед.

    Поэтому, когда вы пишете

     (a_pointer + a_number) 

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

     (a_pointer + (a_number * sizeof(*a_pointer))) 

    в обычной арифметике.

    применяя НЛП, назовите его арифметикой адреса. «указатели» опасаются и неправильно понимаются в основном потому, что их учат неправильные люди и / или на неправильной стадии с неправильными примерами неправильным образом. Неудивительно, что никто «не получает» его.

    при преподавании указателей, преподаватель продолжает: «p – указатель на a, значение p – адрес a» и т. д. это просто не работает. вот исходный материал для вас. тренируйтесь с ним, и ваши ученики получат его.

    ‘int a’, a – целое число, оно хранит значения целочисленного типа. ‘int * p’, p является «int star», он сохраняет значения типа «звезда».

    «a» – это то, как вы получаете «что» целое число, хранящееся в (попробуйте не использовать «значение a»). «& a» – это то, как вы получаете «где» сам хранится (попробуйте сказать «адрес»)

    ‘b = a’ для этого, обе стороны должны быть одного типа. если a является int, b должен быть способен хранить int. (так ______ b, пробел заполняется «int»)

    ‘p = & a’, чтобы это работало, обе стороны должны быть одного типа. если a – целое число, & a – адрес, p должен быть способен хранить адреса целых чисел. (так ______ p, пробел заполняется «int *»)

    теперь напишите int * p по-разному, чтобы узнать информацию о типе:

    int * | п

    что такое «р»? ans: это ‘int *’. поэтому ‘p’ – это адрес целого числа.

    int | *п

    что такое ‘* p’? ans: это «int». поэтому ‘* p’ является целым числом.

    теперь по адресной арифметике:

    int a; а = 1; а = а + 1;

    что мы делаем в «a = a + 1»? подумайте об этом как о «следующем». Потому что a – это число, это как сказать «следующее число». Так как a hold 1, говорящий «next», это будет 2.

    // ошибочный пример. вы были предупреждены!!! int * p int a; p = & a; р = р + 1;

    что мы делаем в p = p + 1? он все еще говорит «следующий». На этот раз p не является числом, а адресом. Итак, что мы говорим, это «следующий адрес». Следующий адрес зависит от типа данных, а точнее от размера типа данных.

    printf (“% d% d% d”, sizeof (char), sizeof (int), sizeof (float));

    поэтому «next» для адреса будет перемещаться вперед sizeof (тип данных).

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

    Я считаю хорошим примером арифметики указателя следующую функцию длины строки:

     int length(char *s) { char *str = s; while(*str++); return str - s; } в int length(char *s) { char *str = s; while(*str++); return str - s; } 

    Итак, главное помнить, что указатель – это просто переменная размера слова, которая набирается для разыменования. Это означает, что это void *, int *, long long **, это все еще только переменная размера слова. Разница между этими типами заключается в том, что компилятор рассматривает разыменованный тип. Чтобы уточнить, размер слова означает ширину виртуального адреса. Если вы не знаете, что это значит, просто запомните на 64-битной машине, указатели – 8 байт, а на 32-разрядной машине указатели – 4 байта. Концепция адреса СУПЕР важна в понимании указателей. Адрес – это номер, способный однозначно идентифицировать определенное место в памяти. Все в памяти имеет адрес. Для наших целей можно сказать, что каждая переменная имеет адрес. Это не всегда всегда так, но компилятор позволяет нам это принять. Сам адрес является байт гранулированным, то есть 0x0000000 указывает начало памяти, а 0x00000001 – один байт в память. Это означает, что, добавляя один к указателю, мы перемещаем один байт вперед в память. Теперь давайте возьмем массивы. Если вы создадите массив типа quux, который содержит 32 элемента, он будет охватывать от начала его выделения до начала его выделения плюс 32 * sizeof (quux), поскольку каждая ячейка массива имеет размер (quux) большой. Итак, действительно, когда мы указываем элемент массива с массивом [n], это просто синтаксический сахар (стенография) для * (array + sizeof (quux) * n). Арифметика указателя на самом деле просто меняет адрес, на который вы ссылаетесь, поэтому мы можем реализовать strlen с

     while(*n++ != '\0'){ len++; } 

    так как мы просто просматриваем, байт байтом, пока мы не достигнем нуля. Надеюсь, это поможет!

    Есть несколько способов решить эту проблему.

    Интуитивный подход, о котором думают большинство программистов C / C ++, заключается в том, что указатели – это адреса памяти. Этот пример использует этот пример. Если у вас есть нулевой указатель (который на большинстве машин соответствует адресу 0), и вы добавляете размер int, вы получаете адрес 4. Это означает, что указатели в основном представляют собой просто фантастические целые числа.

    К сожалению, с этим связано несколько проблем. Во-первых, это может не сработать. Нулевой указатель не гарантирует фактического использования адреса 0. (Хотя присвоение константы 0 указателю дает нулевой указатель).

    Кроме того, вам не разрешается увеличивать нулевой указатель, или, как правило, указатель должен всегда указывать выделенную память (или один элемент прошлого) или специальную константу нулевого указателя 0.

    Таким образом, более правильный способ думать о том, что указатели – это просто iteratorы, позволяющие перебирать выделенную память. Это действительно одна из ключевых идей, стоящих за iteratorами STL. Они моделируются так, чтобы вести себя как указатели и предоставлять специализации, которые исправляют исходные указатели для работы в качестве правильных iteratorов.

    Более подробное объяснение этого дается здесь , например.

    Но это последнее мнение означает, что вы действительно должны объяснять iteratorы STL, а затем просто говорите, что указатели – это особый случай. Вы можете увеличить указатель, указав на следующий элемент в буфере, точно так же, как вы можете использовать std::vector::iterator . Он может указывать один элемент за конец массива, точно так же как конечный iterator в любом другом контейнере. Вы можете вычесть два указателя , указывающих на один и тот же буфер, чтобы получить количество элементов между ними, так же, как вы можете с помощью iteratorов, и точно так же, как с помощью iteratorов, если указатели указывают на отдельные буферы, вы не можете их осмысленно сравнивать. (Для практического примера того, почему нет, рассмотрите, что происходит в сегментированном пространстве памяти. Какое расстояние между двумя указателями указывает на отдельные сегменты?)

    Конечно, на практике существует очень тесная корреляция между адресами ЦП и указателями на C / C ++. Но они не совсем то же самое. Указатели имеют несколько ограничений, которые могут быть не обязательно необходимы для вашего процессора.

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

    Но для кого-то, приходящего с Java, и просто узнавая о указателях с нуля, последнее объяснение может быть так же легко понято, и в дальнейшем у них будет меньше сюрпризов.

    Это очень хорошо подходит для ссылки здесь на «Арифметика указателей»

    Например:

    Указатель и массив

    Формула для вычисления адреса ptr + i, где ptr имеет тип T *. то формула для адреса:

    addr (ptr + i) = addr (ptr) + [sizeof (T) * i]

    Для типа int на 32-битной платформе addr (ptr + i) = addr (ptr) + 4 * i;

    вычитание

    Мы также можем вычислить ptr – i. Например, предположим, что у нас есть массив int, называемый arr. int arr [10]; int * p1, * p2;

     p1 = arr + 3 ; // p1 == & arr[ 3 ] p2 = p1 - 2 ; // p1 == & arr[ 1 ] 
    Давайте будем гением компьютера.