Г. Шилдт - Полный справочник по C# (1160789), страница 91
Текст из файла (страница 91)
Однакодля того чтобы результат сравнения указателей поддавался интерпретации, сравниваемые указатели должны быть каким-то образом связаны. Например, если p i и р2 —указатели, которые указывают на две отдельные и никак не связанные переменные, толюбое сравнение p i и р2 в общем случае не имеет смысла. Но если p i и р2 указывают на переменные, между которыми существует некоторая связь (как, например, между элементами одного и того же массива), то результат сравнения указателей p i и р2может иметь определенный смысл.Рассмотрим пример, в котором сравнение указателей используется для отысканиясреднего элемента массива.//Демонстрация возможности сравнения указателей.u s i n g System;class PtrCompDemo {unsafe public s t a t i c void Main() {int[] nums = new i n t [ 1 1 ] ;int x;// Находим средний элемент массива,fixed (int* s t a r t = &nums[0]) {fixed(int* end = Snums[nums.Length-1]) {for(x=0; start+x <= end-x; x++) ;}}Console.WriteLine("Средний элемент массива имеет номер " + х ) ;Вот как выглядят результаты выполнения этой программы:1 Средний элемент массива имеет номер бЭта программа находит средний элемент, первоначально установив указательs t a r t равным адресу первого элемента, а указатель end — адресу последнего элемента массива.
Затем, используя возможности выполнения арифметических операций надуказателями, мы увеличиваем указатель s t a r t на целочисленное значение х, а указаГлава 18. Опасный код, указатели и другие темы491тель end — уменьшаем на то же значение х до тех пор, пока результат суммированияs t a r t и х не станет меньше или равным результату вычитания end и х.Одно уточнение: указатели s t a r t и end должны быть созданы внутри fixedинструкции, поскольку они указывают на элементы массива, который представляетсобой ссылочный тип данных. Не забывайте, что в С# массивы реализованы как объекты и могут быть удалены сборщиком мусора.Указатели и массивыВ С# указатели и массивы связаны между собой.
Например, имя массива без индекса образует указатель на начало этого массива. Рассмотрим следующую программу:/* Имя массива без индекса образует указатель на началоэтого массива. */using System;class PtrArray {unsafe public static void Main() {int[] nums = new int[10];fixed(int* p = &nums[0]f p2 = nums) {if(p == p2)Console.WriteLine("Указатели р и р2 содержат один и тот же адрес.");Вот какие результаты получены при выполнении этой программы:I Указатели р и р2 содержат один и тот же адрес.Как подтверждают результаты выполнения этой программы, выражениеI &nums[0]эквивалентно1 numsПоскольку вторая форма короче, большинство программистов используют именноее в случае, когда нужен указатель на начало массива.Индексация указателяУказатель, который ссылается на массив, можно индексировать так, как если быэто было имя массива.
Этот синтаксис обеспечивает альтернативу арифметическимоперациям над указателями, поскольку он более удобен в некоторых ситуациях. Рассмотрим пример.// Индексирование указателя подобно массиву.using System;class PtrlndexDemo {unsafe public static void Main() {int[] nums = new int[10];// Индексируем указатель.Console.WriteLine("Индексируем указатель подобно массиву.");492Часть I. Язык С#fixed (int* p = nums) {for(int i=0; i < 10;p[i] = i; // Индексируем указатель подобно массиву.for(int i=0; i < 10;Console.WriteLine("p[{0}]: {1}// Используем арифметические операции над указателями.Console.WriteLine("ХпИспользуем арифметические операции над указателями.")fixed (int* p = nums) {for(int i=0; i < 10; i++)*(p+i) = i; // Используем арифметические// операции над указателями.for(int i=0; i < 10;)Console.WriteLine("*(p+{0};{1}}Вот результаты выполнения этой программы:Индексируем указатель подобно массиву.р[0]рр[2]р[3]р[4]р[5]р[б]р[7]р[8]р[9]Используем арифметические операции над указателями.Мр+0)*(р+2)Мр+3)*(р+4)*(р+5)*(р+6)*(р+7)Мр+8)*(р+9)Как видно по результатам выполнения этой программы, выражение (в которомучаствует указатель) в формате* ( p t r + i)можно переписать с использованием синтаксиса, применяемого при индексированиимассивов:ptr[i]При индексировании указателя необходимо помнить следующее.
Во-первых, приэтом нарушение границ массива никак не контролируется. Следовательно, существуетвозможность получить доступ к "элементу" за концом массива, если на него ссылается указатель. Во-вторых, указатель не имеет свойства Length. Поэтому при использовании указателя невозможно узнать длину массива.Глава 18. Опасный код, указатели и другие темы493Указатели и строкиНесмотря^ на то что в С# строки реализованы как объекты, к отдельным их символам можно получить доступ с помощью указателя. Для этого достаточно присвоитьchar*-указателю адрес начала этой строки, используя fixed-инструкцию:f i x e d ( c h a r * p = s t r ) { II . .
.После выполнения такой f ixed-инструкции р будет указывать на начало символьного массива, который составляет эту строку. Этот символьный массив заканчиваетсясимволом конца строки, т.е. нулевым символом. Этот факт можно использовать дляпроверки достижения конца массива. В C/C++ символьные строки реализованы в виде символьных массивов, завершающихся нулевым символом. Таким образом, получив char*-указатель на строку, можно обрабатывать строки практически так же, какэто делается в C/C++.Рассмотрим программу, которая демонстрирует доступ к строке с помощью char*указателя:// Использование fixed-инструкций для получения// указателя на начало строки.using System;class FixedString {unsafe public static void Main() {string str = "Это простой тест.";// Направляем указатель р на начало строки str.fixed(char* p = str) {// Отображаем содержимое строки str/ / с помощью указателя р.for(int i=0; p[i] != 0;Console.Write(p[i]);}Console.WriteLine();Вот результаты выполнения этой программы:I Это простой тест.Использование многоуровневой непрямой адресацииМожно создать указатель, который будет ссылаться на другой указатель, а тот — наконечное значение.
Эту ситуацию называют многоуровневой непрямой адресацией(multiple indirection), или использованием указателя на указатель. Идея многоуровневой непрямой адресации схематично проиллюстрирована на рис. 18.1. Как видите,значение обычного указателя (при одноуровневой непрямой адресации) представляетсобой адрес переменной, которая содержит некоторое значение. В случае примененияуказателя на указатель первый содержит адрес второго, а тот указывает на переменную, содержащую определенное значение.При использовании непрямой адресации можно организовать любое желаемое количество уровней, но, как правило, ограничиваются лишь двумя, поскольку увеличение числа уровней часто ведет к возникновению концептуальных ошибок.494Часть I.
Язык С#Переменную, которая является указателем на указатель, нужно объявить соответствующим образом. Для этого достаточно поставить дополнительный символ"звездочка"(*) после имени типа. Например, следующее объявление сообщает компилятору о том, что q — это указатель на указатель на значение типа i n t :| i n t * * q;Необходимо помнить, что переменная q здесь — не указатель на целочисленноезначение, а указатель на указатель на int-значение.Чтобы получить доступ к значению, адресуемому указателем на указатель, необходимо дважды применить оператор " * " , как показано в следующем примере:using System;class Multiplelndirect {unsafe public static void Main() {int x;// Переменная содержит значение,int* p; // Переменная содержит указатель//на int-значение.int** q; // Переменная содержит указатель на указатель//на int-значение.х = 10;р = &х; // Помещаем адрес х в р.q = &р; // Помещаем адрес р в q.Console.WriteLine(**q); // Отображаем значение х.При выполнении этой программы мы получили бы значение переменной х, т.е.число 10.
Здесь переменная р объявлена как указатель на int-значение, а переменнаяq — как указатель на указатель на int-значение.И еще: не следует путать многоуровневую непрямую адресацию с такими высокоуровневыми структурами данных, как связные списки, которые используют указатели.Это — две принципиально различные концепции.УказательПеременнаяОдноуровневая непрямая адресацияМногоуровневая непрямая адресацияРис. 18.1. Одноуровневая и многоуровневая непрямая адресацияМассивы указателейУказатели, подобно данным других типов, могут храниться в массивах.
Вот, например, как выглядит объявление трехэлементного массива указателей на i n t значения:i n t * [] p t r s = new i n t * [ 3 ] ;Глава 18. Опасный код, указатели и другие темы495Чтобы присвоить адрес int-переменной с именем v a r третьему элементу этогомассива указателей, запишите следующее:I p t r s [ 2 ] = &var;Чтобы получить значение переменной var, используйте такой синтаксис:*ptrs[2jКлючевые слова смешанного типаВ заключение части I рассмотрим определенные в С# ключевые слова, которыееще не были здесь описаны.sizeofНе исключено, что вам понадобится узнать размер (в байтах) одного из С#-типовзначений.
Для получения этой информации используйте оператор s i z e o f . Форматего применения таков:sizeof{тип)Здесь элемент тип — это тип, размер которого мы хотим получить. Операторs i z e o f можно использовать только в контексте опасного (unsafe) кода. Таким образом, он предназначен в основном для специальных ситуаций, особенно при работе сосмешанным (управляемым и неуправляемым) кодом.lockКлючевое слово lock используется при работе с несколькими потоками. В С#программа может содержать два или больше потоков выполнения. В этом случаепрограммы работают в многозадачном режиме. Другими словами, отдельные фрагменты программ выполняются не только независимо один от другого, или, можносказать, одновременно. Это приводит к возникновению определенной проблемы: ачто если два потока попытаются использовать ресурс, который одновременно можетиспользовать только один поток? Чтобы решить эту проблему, можно создать критический раздел кода, который в данный момент может выполняться только одним потоком.
Такой подход реализуется с помощью ключевого слова lock. Его формат таков:lock{obj){// Критический раздел.Здесь элемент obj представляет объект, который стремится получить блокировку.Если один поток уже вошел в критический раздел, то второй должен ожидать до техпор, пока не выполнится первый. Критический раздел может быть выполнен при получении разрешения на установку блокировки. (Подробнее см. главу 21.)readonlyВ классе можно создать поле, предназначенное только для чтения, если объявитьего с помощью ключевого слова readonly, причем readonly-поле можно инициализировать, но после этого изменить его содержимое уже нельзя. Следовательно, использование readonly-полей — удобный способ создать константы (например, обозначающие размерность массивов), которые применяются на протяжении всей про-496Часть I. Язык С#граммы.
Предназначенные только для чтения поля могут быть как статическими, таки нестатическими.Рассмотрим пример создания readonly-поля://Демонстрация использования readonly-поля.using System;class MyClass {public static readonly int SIZE = 10;}class DemoReadOnly {public static void Main() {int[]nums = new int[MyClass.SIZE];for (int i^=0; i<MyClass . SIZE; i++)nums[i] = i;foreach(int i in nums)Console.Write(i + " ") ;// MyClass.SIZE = 100; // Ошибка!!! readonly-поле// изменять нельзя!Здесь поле MyClass.