Полное руководство. С# 4.0
Шрифт:
Что касается индексирования указателей, то необходимо иметь в виду следующее. Во-первых, при таком индексировании контроль границ массива не осуществляется. Поэтому указатель может обращаться к элементу вне границ массива. И во-вторых, для указателя не предусмотрено свойство Length, определяющее длину массива. Поэ тому, если используется указатель, длина массива заранее неизвестна. Указатели и строки
Символьные строки реализованы в C# в виде объектов. Тем не менее отдельные сим волы в строке могут быть доступны по указателю. Для этого указателю типа char* присваивается адрес начала символьной строки в следующем операторе с модифика тором fixed. fixed(char* р = str) { // ...
После выполнения оператора с модификатором fixed переменная
Ниже приведена программа, демонстрирующая доступ к символьной строке по указателю типа char*. // Использовать модификатор fixed для получения // указателя на начало строки. using System; class FixedString { unsafe static void Main { string str = "это тест"; // Получить указатель р на начало строки str. fixed(char* р = str) { // Вывести содержимое строки str по указателю р. for(int i=0; p[i] != 0; i++) Console.Write(p[i]); } Console.WriteLine; } }
Эта программа дает следующий результат. это тест Многоуровневая непрямая адресация
Один указатель может указывать на другой, а тот, свою очередь, — на целевое зна чение. Это так называемая многоуровневая непрямая адресация, или применение указа телей на указатели. Такое применение указателей может показаться, на первый взгляд, запутанным. Для прояснения принципа многоуровневой непрямой адресации обра тимся за помощью к рис. 20.1. Как видите, значением обычного указателя является адрес переменной, содержащей требуемое значение. Если же применяется указатель на указатель, то первый из них содержит адрес второго, указывающего на переменную, содержащую требуемое значение.
Рис. 20.1. Одно- и многоуровневая непрямая адресация
Многоуровневая непрямая адресация может быть продолжена до любого предела, но потребность более чем в двух уровнях адресации по указателям возникает крайне редко. На самом деле чрезмерная непрямая адресация очень трудно прослеживается и чревата ошибками.
Переменная, являющаяся указателем на указатель, должна быть объявлена как та ковая. Для этого достаточно указать дополнительный знак * после имени типа пере менной. Например, в следующем объявлении компилятор уведомляется о том, что переменная q является указателем на указатель и относится к типу int. int** q;
Следует, однако, иметь в виду, что переменная q является указателем не на целое значение, а на указатель типа int.
Для доступа к целевому значению, косвенно адресуемому по указателю на указа тель, следует дважды применить оператор *, как в приведенном ниже примере. using System; class MultipleIndirect { unsafe static void Main { int x; // содержит значение типа int int* p; // содержит указатель типа int int** q; // содержит указатель на указатель типа int х = 10; р = &х; // поместить адрес переменной х в переменной р q = &р; // поместить адрес переменной р в переменной q Console.WriteLine(**q); // вывести значение переменной х } }
Результатом выполнения этой программы будет выведенное на экран значение 10 переменной х. В данной программе переменная р объявляется как указатель на значе ние типа int, а переменная q — как указатель на указатель типа int.
И последнее замечание: не путайте многоуровневую непрямую адресацию со структурами данных высокого уровня, в том числе связными списками, так как это со вершенно разные понятия. Массивы указателей
Указатели могут быть организованы в массивы, как и любой другой тип данных. Ниже приведен пример объявления массива указателей типа int длиной в три эле мента. int * [] ptrs = new int * [3];
Для
того чтобы присвоить адрес переменной var типа int третьему элементу мас сива указателей, достаточно написать следующую строку кода. ptrs[2] = Svar;А для того чтобы обнаружить значение переменной var, достаточно написать при веденную ниже строку кода. *ptrs[2] Оператор sizeof
Во время работы с небезопасным кодом иногда полезно знать размер в байтах одного из встроенных в C# типов значений. Для получения этой информации служит оператор sizeof. Ниже приведена его общая форма: sizeof(тип)
где тип обозначает тот тип, размер которого требуется получить. Вообще говоря, опе ратор sizeof предназначен главным образом для особых случаев и, в частности, для работы со смешанным кодом: управляемым и неуправляемым. Оператор stackalloc
Для распределения памяти, выделенной под стек, служит оператор stackalloc. Им можно пользоваться лишь при инициализации локальных переменных. Ниже приведена общая форма этого оператора: тип *р = stackalloc тип[размер]
где р обозначает указатель, получающий адрес области памяти, достаточной для хра нения объектов, имеющих указанный тип, в количестве, которое обозначает размер. Если же в стеке недостаточно места для распределения памяти, то генерируется ис ключение System.StackOverflowException. И наконец, оператор stackalloc мож но использовать только в небезопасном коде.
Как правило, память для объектов выделяется из кучи — динамически распределя емой свободной области памяти. А выделение памяти из стека является исключением. Ведь переменные, располагаемые в стеке, не удаляются средствами "сборки мусора", а существуют только в течение времени выполнения метода, в котором они объявляют ся. После возврата из метода выделенная память освобождается. Преимущество при менения оператора stackalloc заключается, в частности, в том, что в этом случае не нужно беспокоиться об очистке памяти средствами "сборки мусора".
Ниже приведен пример применения оператора stackalloc. // Продемонстрировать применение оператора stackalloc. using System; class UseStackAlloc { unsafe static void Main { int* ptrs = stackalloc int[3]; ptrs[0] = 1; ptrs[1] = 2; ptrs[2] = 3; for(int i=0; i < 3; i++) Console.WriteLine(ptrs[i]); } }
Вот к какому результату приводит выполнение кода из данного примера. 1 2 3 Создание буферов фиксированного размера
Ключевое слово fixed находит еще одно применение при создании одномерных массивов фиксированного размера. В документации на C# такие массивы называются буферами фиксированного размера. Такие буферы всегда являются членами структуры. Они предназначены для создания структуры, в которой содержатся элементы массива, образующие буфер. Когда элемент массива включается в состав структуры, в ней, как правило, хранится лишь ссылка на этот массив. Используя буфер фиксированного раз мера, в структуре можно разместить весь массив. В итоге получается структура, при годная в тех случаях, когда важен ее размер, как, например, в многоязыковом програм мировании, при согласовании данных, созданных вне программы на С#, или же когда требуется неуправляемая структура, содержащая массив. Но буферы фиксированного размера можно использовать только в небезопасном коде.
Для создания буфера фиксированного размера служит следующая общая форма: fixed тип имя_буфера[размер];
где тип обозначает тип данных массива; имя_буфера — конкретное имя буфера фик сированного размера; размер — количество элементов, образующих буфер. Буферы фиксированного размера могут указываться только в структуре.
Для того чтобы стала очевиднее польза от буферов фиксированного размера, рас смотрим ситуацию, в которой программе ведения счетов, написанной на C++, тре буется передать информацию о банковском счете. Допустим также, что учетная запись каждого счета организована так, как показано ниже. Name Строка длиной 80 байтов, состоящая из 8-разрядных символов в коде ASCII Balance Числовое значение типа double длиной 8 байтов ID Числовое значение типа long длиной 8 байтов