1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 89
Текст из файла (страница 89)
Рассмотрим, например,следующую структуру:struct MyStruct {public int x;public int y;}public int sum() {return x + y;}Теперь покажем, как получить доступ к ее членам с помощью указателя:MyStruct о = new MyStruct();MyStruct* p; // Объявляем указатель.p = &о;p->x = 10;p->y = 20;Console.WriteLine("Сумма равна " + p->sum());Арифметические операции над указателямиС указателями можно использовать только четыре арифметических оператора: ++, -, + и -. Чтобы лучше понять, что происходит при выполнении арифметических действий суказателями, начнем с примера.
Пусть p1 — указатель на int-переменную с текущимзначением 2 000 (т.е. p1 содержит адрес 2 000). После выполнения выраженияp1++;содержимое указателя p1 станет равным 2 004, а не 2 001! Дело в том, что прикаждом инкрементировании указатель p1 будет указывать на следующее int-значение.Поскольку в C# int-значения занимают четыре байта, то при инкрементировании p1 егозначение увеличивается на 4. Для операции декрементирования справедливо обратноеутверждение, т.е. при каждом декрементировании значение p1 будет уменьшаться на 4.Например, после выполнения инструкцииГлава 18.
Опасный код, указатели и другие темы489p1--;указатель p будет иметь значение 1 996, если до этого оно было равно 2 000.Итак, каждый раз, когда указатель инкрементируется, он будет указывать на областьпамяти, содержащую следующий элемент базового типа этого указателя.
А при каждомдекрементировании он будет указывать на область памяти, содержащую предыдущийэлемент базового типа этого указателя.Арифметические операции над указателями не ограничиваются использованиемоператоров инкремента и декремента. Со значениями указателей можно выполнятьоперации сложения и вычитания, используя в качестве второго операнда целочисленныезначения. Выражениеp1 = p1 + 9;заставляет p1 указывать на девятый элемент базового типа указателя p1 относительноэлемента, на который p1 указывал до выполнения этой инструкции.Несмотря на то что складывать указатели нельзя, один указатель можно вычесть издругого (в предположении, что они оба имеют один и тот же базовый тип). Разностьпокажет количество элементов базового типа, которые разделяют эти два указателя.Помимо сложения (и вычитания) указателя и (из) целочисленного значения, а такжевычитания двух указателей, над указателями никакие другие арифметические операции невыполняются.
Например, с указателями нельзя складывать float- или double-значения.Чтобы понять, как формируется результат выполнения арифметических операций надуказателями, выполним следующую короткую программу. Она выводит реальныефизические адреса, которые содержат указатель на int-значение (ip) и указатель наfloat-значение (fp). Обратите внимание на каждое изменение адреса (зависящее отбазового типа указателя), которое происходит при повторении цикла.// Демонстрируем результаты выполнения арифметических// операций над указателями.using System;class PtrArithDemo {unsafe public static void Main() {int x;int i;double d;int* ip = &i;double* fp = &d;Console.WriteLine("int double\n");}}for(x=0; x < 10; x++) {Console.WriteLine((uint) (ip) + " " +(uint) (fp));ip++;fp++;}Ниже показаны возможные результаты выполнения этой программы.
Ваширезультаты могут отличаться от приведенных, но интервалы между значениями должныбыть такими же.490Часть I. Язык C#int double1243324 12433281243328 12433361243332 12433441243336 12433521243340 12433601243344 12433681243348 12433761243352 12433841243356 12433921243360 1243400Как подтверждают результаты выполнения этой программы, арифметическиеоперации над указателями выполняются в зависимости от базового типа каждого указателя.Поскольку любое int-значение занимает четыре байта, а double-значение — восемь, то исами адреса изменяются с учетом этих значений.Сравнение указателейУказатели можно сравнивать, используя операторы отношения ==, < и >. Однако длятого чтобы результат сравнения указателей поддавался интерпретации, сравниваемыеуказатели должны быть каким-то образом связаны.
Например, если p1 и p2 — указатели,которые указывают на две отдельные и никак не связанные переменные, то любоесравнение p1 и p2 в общем случае не имеет смысла. Но если p1 и p2 указывают напеременные, между которыми существует некоторая связь (как, например, междуэлементами одного и того же массива), то результат сравнения указателей p1 и p2 можетиметь определенный смысл.Рассмотрим пример, в котором сравнение указателей используется для отысканиясреднего элемента массива.// Демонстрация возможности сравнения указателей.using System;class PtrCompDemo {unsafe public static void Main() {int[] nums = new int[11];int x;}}// Находим средний элемент массива.fixed(int* start = &nums[0]) {fixed(int* end = &nums[nums.Length-1]) {for(x=0; start+x <= end-x; x++);}}Console.WriteLine("Средний элемент массива имеет номер " + x);Вот как выглядят результаты выполнения этой программы:Средний элемент массива имеет номер 6Эта программа находит средний элемент, первоначально установив указатель startравным адресу первого элемента, а указатель end — адресу последнего элемента массива.Затем, используя возможности выполнения арифметических операций над указателями, мыувеличиваем указатель start на целочисленное значение x, а указательГлава 18.
Опасный код, указатели и другие темы491end — уменьшаем на то же значение x до тех пор, пока результат суммирования start иx не станет меньше или равным результату вычитания end и x.Одно уточнение: указатели start и end должны быть созданы внутри fixedинструкции, поскольку они указывают на элементы массива, который представляет собойссылочный тип данных. Не забывайте, что в C# массивы реализованы как объекты и могутбыть удалены сборщиком мусора.Указатели и массивыВ C# указатели и массивы связаны между собой. Например, имя массива без индексаобразует указатель на начало этого массива. Рассмотрим следующую программу:/* Имя массива без индекса образует указатель на началоэтого массива. */using System;class PtrArray {unsafe public static void Main() {int[] nums = new int[10];fixed(int* p = &nums[0], p2 = nums) {if(p == p2)Console.WriteLine("Указатели p и p2 содержат один и тот же адрес.");}}}Вот какие результаты получены при выполнении этой программы:Указатели p и p2 содержат один и тот же адрес.Как подтверждают результаты выполнения этой программы, выражение&nums[0]эквивалентноnumsПоскольку вторая форма короче, большинство программистов используют именно еев случае, когда нужен указатель на начало массива.Индексация указателяУказатель, который ссылается на массив, можно индексировать так, как если бы этобыло имя массива.
Этот синтаксис обеспечивает альтернативу арифметическим операциямнад указателями, поскольку он более удобен в некоторых ситуациях. Рассмотрим пример,// Индексирование указателя подобно массиву.using System;class PtrIndexDemo {unsafe public static void Main() {int[] nums = new int[10];// Индексируем указатель.Console.WriteLine("Индексируем указатель подобно массиву.");492Часть I. Язык C#fixed(int* p = nums) {for(int i=0; i < 10; i++)p[i] = i; // Индексируем указатель подобно массиву.for(int i=0; i < 10; i++)Console.WriteLine("p[{0}]: {1} ", i, p[i]);}}}// Используем арифметические операции над указателями.Console.WriteLine("\nИспользуем арифметические операции над указателями.");fixed(int* p = nums) {for(int i=0; i < 10; i++)*(p+i) = i; // Используем арифметические// операции над указателями.for(int i=0; i < 10; i++)Console.WriteLine("*(p+{0}|): {1} ", i, *(p+i));}Вот результаты выполнения этой программы:Индексируем указатель подобно массиву.p[0]: 0p[1]: 1p[2]: 2p[3]: 3p[4]; 4p[5]: 5p[6]: 6p[7]: 7p[3]: 8p[9]: 9Используем арифметические операции над указателями.*(p+0): 0*(p+1): 1*(p+2): 2*(p+3): 3*(p+4): 4*(p+5): 5*(p+6): 6*(p+7): 7*(p+8): 8*(p+9): 9Как видно по результатам выполнения этой программы, выражение (в которомучаствует указатель) в формате*(ptr + i)можно переписать с использованием синтаксиса, применяемого при индексированиимассивов:ptr[i]При индексировании указателя необходимо помнить следующее.
Во-первых, приэтом нарушение границ массива никак не контролируется. Следовательно, существуетвозможность получить доступ к “элементу” за концом массива, если на него ссылаетсяуказатель. Во-вторых, указатель не имеет свойства Length. Поэтому при использованииуказателя невозможно узнать длину массива.Глава 18. Опасный код, указатели и другие темы493Указатели и строкиНесмотря, на то что в C# строки реализованы как объекты, к отдельным их символамможно получить доступ с помощью указателя.