Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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)

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

Ниже показаны две важные концепции

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

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

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 - это тип (end-begin). Это может быть синоним "int" для некоторого компилятора, но может быть другим типом для другого. Нельзя знать, поэтому выбирается общий typedef ptrdiff_t.

Ответ 2

Здесь я узнал указатели: 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)))

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

Ответ 3

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

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

'int a', a - целое число, оно хранит значения целочисленного типа. 'int * p', p является "int star", он сохраняет значения типа "int star".

'a' - это то, как вы получаете "что" целое число, хранящееся в (попробуйте не использовать "значение a" ) '& a' - это то, как вы получаете "where" сам хранится (попробуйте сказать "адрес" )

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

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

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

int * | р

что такое p? 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 (тип данных).

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

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

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

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

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

Ответ 7

Это один из хороших ссылок на ссылку здесь об арифметике указателей

Например:

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

Формула для вычисления адреса 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 ]