Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
в переменной val сохраняется значение 10 переменной num, на которую указываетпеременная ip. Операцию * можно рассматривать как получение значения по адресу.Поэтому приведенный выше оператор присваивания описывается словами следующим образом: "Переменная val получает значение по адресу, хранящемуся в переменной ip."
Оператор * можно использовать также в левой части оператора присваивания.В этом случае он задает значение, на которое указывает соответствующий указатель,как в приведенном ниже примере.*ip = 100;
В данном примере значение 100 присваивается переменной, на которую указываетпеременная ip, т.е. переменной num. Поэтому приведенный выше оператор присваивания описывается словами следующим образом: "Разместить значение 100 по адресу,хранящемуся в переменной ip."Применение ключевого слова unsafe
Любой код, в котором используются указатели, должен быть помечен как небезопасный с помощью специального ключевого слова unsafe. Подобным образом можно пометить конкретные типы данных (например, классы и структуры), члены класса(в том числе методы и операторы) или отдельные кодовые блоки как небезопасные.В качестве примера ниже приведена программа, где указатели используются в методеMain(), помеченном как небезопасный.// Продемонстрировать применение указателей и ключевого слова unsafe.using System;class UnsafeCode { // Пометить метод Main() как небезопасный. unsafe static void Main() { int count = 99; int* p; // создать указатель типа int p = &count; // поместить адрес переменной count в переменной р Console.WriteLine("Исходное значение переменной count: " + *р); *р = 10; // присвоить значение 10 переменной count, // на которую указывает переменная р Console.WriteLine("Новое значение переменной count: " + *р); }}
Эта программа дает следующий результат.Исходное значение переменной count: 99Новое значение переменной count: 10Применение модификатора fixed
В работе с указателями нередко используется модификатор fixed, который препятствует удалению управляемой переменной средствами "сборки мусора". Потребностьв этом возникает, например, в том случае, если указатель обращается к полю в объектеопределенного класса. А поскольку указателю ничего не известно о действиях системы"сборки мусора", то он будет указывать не на тот объект, если удалить нужный объект.Ниже приведена общая форма модификатора fixed:fixed (тип* р = &фиксированный_объект) ( // использовать фиксированный объект}
где р обозначает указатель, которому присваивается адрес объекта. Этот объект будетоставаться на своем текущем месте в памяти до конца выполнения кодового блока.В качестве адресата оператора fixed может быть также указано единственное выражение, а не целый кодовый блок. Модификатор fixed допускается использовать тольков коде, помеченном как небезопасный. Кроме того, несколько указателей с модификатором fixed могут быть объявлены списком через запятую.
Ниже приведен пример применения модификатора fixed.// Продемонстрировать применение оператора fixed.using System;class Test { public int num; public Test(int i) { num = i; }}class FixedCode { // Пометить метод Main() как небезопасный. unsafe static void Main() { Test о = new Test(19); fixed (int* p = &o.num) { // использовать модификатор fixed для размещения // адреса переменной экземпляр о.num в переменной р Console.WriteLine("Исходное значение переменной о.num: " + *р); *р = 10; // присвоить значение 10 переменной count, // на которую указывает переменная р Console.WriteLine("Новое значение переменной о.num: " + *р); } }}
Вот к какому результату приводит выполнение этой программы.Исходное значение переменной о.num: 19Новое значение переменной о.num: 10
В данном примере модификатор fixed препятствует удалению объекта о. А поскольку переменная р указывает на переменную экземпляра о.num, то она будет указывать на недостоверную область памяти, если удалить объект о.Доступ к членам структуры с помощью указателя
Указатель может указывать на объект типа структуры при условии, что структуране содержит ссылочные типы данных. Для доступа к члену структуры с помощью указателя следует использовать оператор-стрелку (->), а не оператор-точку (.). Например, доступ к членам структурыstruct MyStruct { public int a; public int b; public int Sum() { return a + b; }}
осуществляется следующим образом.MyStruct о = new MyStruct();MyStruct* p; // объявить указательp = &o;p->a = 10; // использовать оператор ->p->b = 20; // использовать оператор ->Console.WriteLine("Сумма равна " + p->Sum());Арифметические операции над указателями
Над указателями можно выполнять только четыре арифметические операции: ++,--, + и -. Для того чтобы стало понятнее, что именно происходит в арифметическихоперациях над указателями, рассмотрим сначала простой пример. Допустим, что переменная p1 является указателем с текущим значением 2000, т.е. она содержит адрес
После выполнения выраженияp1++;переменная p1 будет содержать значение 2004, а не 2001! Дело в том, что после каждогоинкрементирования переменная p1 указывает на следующее значение типа int. А поскольку тип int представлен в C# 4 байтами, то в результате инкрементирования значение переменной p1 увеличивается на 4. Справедливо и обратное: при каждом декрементировании переменной p1 ее значение уменьшается на 4. Например выражениеp1--;приводит к тому, что значение переменной p1 становится равным 1996, если раньшеоно было равно 2000!
Все сказанное выше можно обобщить: после каждого инкрементирования указатель будет указывать на область памяти, где хранится следующий элемент его соотносимого типа, а после каждого декрементирования указатель будет указывать на область памяти, где хранится предыдущий элемент его соотносимого типа.Арифметические операции над указателями не ограничиваются только инкрементированием и декрементированием. К указателям можно добавлять и вычитать из нихцелые значения. Так, после вычисления следующего выражения:p1 = p1 + 9;
переменная p1 будет указывать на девятый элемент ее соотносимого типа по отношению к элементу, на который она указывает в настоящий момент.
Если складывать указатели нельзя, то разрешается вычитать один указатель из другого, при условии, что оба указателя имеют один и тот же соотносимый тип. Результатом такой операции окажется количество элементов соотносимого типа, которыеразделяют оба указателя.
Кроме сложения и вычитания целого числа из указателя, а также вычитания двухуказателей, другие арифметические операции над указателями не разрешаются. В частности, к указателям нельзя добавлять или вычитать из них значения типа float илиdouble. Не допускаются также арифметические операции над указателями типа void*.
Для того чтобы проверить на практике результаты арифметических операций надуказателями, выполните приведенную ниже короткую программу, где выводятся физические адреса, на которые указывает целочисленный указатель (ip) и указатель с плавающей точкой одинарной точности (fp). Понаблюдайте за изменениями каждого изэтих указателей по отношению к их соотносимым типам на каждом шаге цикла.// Продемонстрировать результаты арифметических операций над указателями.using System;class PtrArithDemo { unsafe static void Main() { int x; int i; double d; int* ip = &i; double* fp = &d; Console.WriteLine("int doublen"); for(x=0; x < 10; x++) { Console.WriteLine((uint) (ip) + " " + (uint) (fp)); ip++; fp++; } }}
Ниже приведен примерный результат выполнения данной программы. У вас онможет оказаться иным, хотя промежутки между выводимыми значения должны бытьтакими же самыми.int double1243464 12434681243468 12434761243472 12434841243476 12434921243480 12435001243484 12435081243488 12435161243492 12435241243496 12435321243500 1243540
Как следует из приведенного выше результата, арифметические операции выполняются над указателями относительно их соотносимого типа. Так, значения типа intзанимают в памяти 4 байта, а значения типа double — 8 байтов, и поэтому их адресаизменяются с приращением именно на эти величины.Сравнение указателей