unsafe static void Main() {
Test o = new Test(19);
fixed (int* p = &o.num) {
// использовать модификатор fixed для размещения
// адреса переменной экземпляр о.num в переменной р
Console.WriteLine("Исходное значение переменной о.num: " + *p);
*p = 10; // присвоить значение 10 переменной count,
// на которую указывает переменная р
Console.WriteLine("Новое значение переменной о.num: " + *p);
}
}
}
Вот к какому результату приводит выполнение этой программы.
Исходное значение переменной о.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, т.е. она содержит адрес 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