Г. Шилдт - Полный справочник по C# (1160789), страница 90
Текст из файла (страница 90)
Следовательно, нельзя объявлять указатель на объект класса. Обратитевнимание на расположение оператора "звездочка" (*). Он стоит после имени типа.Элемент имя_переменной представляет собой имя переменной-указателя.Рассмотрим пример. Чтобы объявить переменную i p указателем на int-значение,используйте следующую инструкцию:1 int* ip;Для объявления указателя на float-значение используйте такую инструкцию:| f l o a t * fp;В общем случае использование символа "звездочка" (*) в инструкции объявленияпосле имени типа создает тип указателя.Тип данных, на которые будет указывать указатель, определяется его базовым типом.
Следовательно, в предыдущих примерах переменную i p можно использовать дляуказания на int-значение, а переменную fp — на float-значение. Однако помните:не существует реального средства, которое могло бы помешать указателю указывать на"бог-знает-что". Вот потому-то указатели потенциально опасны.Если к С# вы пришли от C/C++, то должны понять важное различие между способами объявления указателей в С# и C/C++. При объявлении типа указателя вC/C++ оператор " * " не распространяется на весь список переменных, участвующих вобъявлении. Поэтому в C/C++ при выполнении инструкции| i n t * p, q;объявляется указатель р на int-значение и int-переменная с именем q.
Эта инструкция эквивалентна следующим двум объявлениям:i n t * Print q;IОднако в С# оператор " * " распространяется на все объявление, и поэтому привыполнении инструкции| i n t * p, q;создаются два указателя р и q на int-значения. Таким образом, в С# предыдущая инструкция эквивалентна таким двум объявлениям:i n t * р;i n t * q;IЭто важное различие обязательно следует иметь в виду при переводе C/C++-кодана "рельсы" С#.486Часть I. Язык С#Операторы "*" и "&"С указателями используются два оператора: " * " и "&".
Оператор "&" — унарный.Он возвращает адрес памяти, по которому расположен его операнд. (Вспомните:унарный оператор требует только одного операнда.) Например, при выполнении следующего фрагмента кодаint* ip;i n t num = 10;i p = #в переменную i p помещается адрес переменной num. Этот адрес соответствует области во внутренней памяти компьютера, которая принадлежит переменной num. Выполнение последней инструкции никак не повлияло на значение переменной num. Итак,переменная i p содержит не значение 10 (начальное значение переменной num), а адрес, по которому оно хранится. Назначение оператора "&" можно "перевести" на русский язык как "адрес переменной", перед которой он стоит.
Следовательно, последнюю инструкцию присваивания можно выразить так: "переменная i p получает адреспеременной num".Второй оператор работы с указателями (*) служит дополнением к первому (&). Этотакже унарный оператор, но он обращается к значению переменной, расположеннойпо адресу, заданному его операндом.
Другими словами, он указывает на значение переменной, адресуемой заданным указателем. Если (продолжая работу с предыдущимфрагментом кода) переменная i p содержит адрес переменной num, то при выполнении инструкцииI i n t val = *ip;Iпеременной v a l будет присвоено значение 10, являющееся значением переменнойnum, на которую указывает переменная i p . Назначение оператора " * " можно выразить словосочетанием "по адресу". В данном случае предыдущую инструкцию можнопрочитать так: "переменная v a l получает значение (расположенное) по адресу i p " .Оператор " * " также можно использовать с левой стороны от оператора присваивания. В этом случае он устанавливает значение, адресуемое заданным указателем. Например, при выполнении инструкции| i p = 100;число 100 присваивается переменной, адресуемой указателем i p (в данном случаеимеется в виду переменная num).
Таким образом, эту инструкцию можно прочитатьтак: "по адресу i p помещаем значение 100".Использование ключевого слова unsafeКод, в котором используются указатели, должен быть отмечен как "опасный" спомощью ключевого слова unsafe. Так можно отмечать отдельные инструкции и методы целиком. Например, рассмотрим'программу, в методе Main() которой используются указатели, и поэтому весь метод отмечен словом unsafe.// Демонстрация использования указателей и// ключевого слова unsafe.usingSystem;class UnsafeCode {// Отмечаем метод Main() как "опасный",unsafe public s t a t i c void Main() {int count = 99;Глава 18. Опасный код, указатели и другие темы487int* p; // Создаем указатель на int-значение.р = &count; // Помещаем адрес переменной count/ / в указатель р.Console.WriteLine("Начальное значение переменной count разно " +*Р);*р = 10; // Присваиваем значение 10 переменной count// через указатель р.Console.WriteLine("Новое значение переменной count равно " + * р ) ;Вот результаты выполнения этой программы:Начальное значение переменной count равно 99Новое значение переменной count равно 10Использование модификатора f i x e dПри работе с указателями зачастую используется модификатор fixed.
Он предотвращает удаление управляемых переменных системой сбора мусора. Это необходимо втом случае, если, например, указатель ссылается на какое-нибудь поле в объекте класса. Поскольку указатель "ничего не знает" о действиях "сборщика мусора", то в случае удаления такого объекта этот указатель будет указывать на неверный объект.Формат применения модификатора fixed таков:fixed (type* p = &var) {/I Использование зафиксированного объекта.Здесь элемент р — указатель, которому присваивается адрес переменной. Объектбудет оставаться в текущей области памяти до тех пор, пока не выполнится соответствующий блок кода.
Инструкция fixed может включать вместо блока кода единственную инструкцию. Ключевое слово fixed можно использовать только в контексте"опасного кода". Используя список элементов, разделенных запятыми, можно объявить сразу несколько фиксированных указателей.Рассмотрим пример использования модификатора fixed://Демонстрация использования модификатораfixed.using System;c l a s s Test {public i n t num;public T e s t ( i n t i){ num = i ;}c l a s s FixedCode {/ / Отмечаем метод Main() как опасный,unsafe public s t a t i c void Main() {Test о = new Test(19);fixed488( i n t * p = So.num){ / / Используем модификатор/ / fixed, чтобы поместить/ / адрес поля о.num в р .Часть I. Язык С#Console.WriteLine("Начальное значение поля o.num равно " + * р ) ;*р = 10; // Присваиваем число 10 переменной count// через указатель р.Console.WriteLine("Новое значение поля o.num равно " + * р ) ;«При выполнении этой программы получены такие результаты:Начальное значение поля o.num равно 19Новое значение поля o.num равно 10Здесь модификатор fixed защищает объект о от удаления.
Поскольку р указываетна поле о . num, то в случае удаления объекта о указатель р некорректно ссылался бына область памяти.Доступ к членам структур с помощью указателейУказатель может ссылаться на объект структурного типа, если он не содержит ссылочных типов. При доступе к члену структуры посредством указателя необходимо использовать оператор "стрелка" (->), а не оператор "точка" (.). Рассмотрим, например, следующую структуру:struct MyStruct {public int x;public int y;public int sum() { return x + y; }}Теперь покажем, как получить доступ к ее членам с помощью указателя:MyStruct о = new MyStruct ( ) ;MyStruct* p ; // Объявляем указатель.р = & о;р->х = 10;р->у = 20;Console.WriteLine("Сумма равна " + p->sum());Арифметические операции над указателямиС указателями можно использовать только четыре арифметических оператора: ++, —, + и -.
Чтобы лучше понять, что происходит при выполнении арифметических действий с указателями, начнем с примера. Пусть p i — указатель на int-переменную с текущим значением 2 000 (т.е. p i содержит адрес 2 000). После выполнения выражения1содержимое указателя p i станет равным 2 004, а не 2 001! Дело в том, что при каждоминкрементировании указатель p i будет указывать на следующее int-значение. Поскольку в С# int-значения занимают четыре байта, то при инкрементировании p iего значение увеличивается на 4.
Для операции декрементирования справедливо обратное утверждение, т.е. при каждом декрементировании значение p i будет уменьшаться на 4. Например, после выполнения инструкцииГлава 18. Опасный код, указатели и другие темы489указатель p i будет иметь значение 1 996, если до этого оно было равно 2 000.Итак, каждый раз, когда указатель инкрементируется, он будет указывать на область памяти, содержащую следующий элемент базового типа этого указателя. А прикаждом декрементировании он будет указывать на область памяти, содержащую предыдущий элемент базового типа этого указателя.Арифметические операции над указателями не ограничиваются использованиемоператоров инкремента и декремента. Со значениями указателей можно выполнятьоперации сложения и вычитания, используя в качестве второго операнда целочисленные значения. Выражение| p i = p i + 9;заставляет p i указывать на девятый элемент базового типа указателя p i относительноэлемента, на который p i указывал до выполнения этой инструкции.Несмотря на то что складывать указатели нельзя, один указатель можно вычесть издругого (в предположении, что они оба имеют один и тот же базовый тип).
Разностьпокажет количество элементов базового типа, которые разделяют эти два указателя.Помимо сложения (и вычитания) указателя и (из) целочисленного значения, атакже вычитания двух указателей, над указателями никакие другие арифметическиеоперации не выполняются. Например, с указателями нельзя складывать f l o a t - или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("intfor(x=0; x < 10; x++) {Console.WriteLine((uint)(uint)double\n");(ip) + " " +(fp));fp++;Ниже показаны возможные результаты выполнения этой программы.
Ваши результаты могут отличаться от приведенных, но интервалы между значениями должныбыть такими же.490Часть I. Язык С#intdouble12433241243328124333212433361243340124334412433481243352124335612433601243328124333612433441243352124336012433681243376124338412433921243400Как подтверждают результаты выполнения этой программы, арифметические операции над указателями выполняются в зависимости от базового типа каждого указателя. Поскольку любое int-значение занимает четыре байта, а double-значение — восемь, то и сами адреса изменяются с учетом этих значений.Сравнение указателейУказатели можно сравнивать, используя операторы отношения ==, < и >.