1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 90
Текст из файла (страница 90)
Для этого достаточно присвоить char*указателю адрес начала этой строки, используя fixed-инструкцию:fixed(char* p = str) { // ...После выполнения такой fixed-инструкции p будет указывать на началосимвольного массива, который составляет эту строку. Этот символьный массивзаканчивается символом конца строки, т.е. нулевым символом.
Этот факт можноиспользовать для проверки достижения конца массива. В C/C++ символьные строкиреализованы в виде символьных массивов, завершающихся нулевым символом. Такимобразом, получив char*-указатель на строку, можно обрабатывать строки практически также, как это делается в C/C++.Рассмотрим программу, которая демонстрирует доступ к строке с помощью char*указателя:// Использование fixed-инструкций для получения// указателя на начало строки.using System;class Fixedstring {}unsafe public static void Main() {string str = "Это простой тест.";// Направляем указатель p на начало строки str.fixed(char* p = str) {// Отображаем содержимое строки str// с помощью указателя р.for(int i=0; p[i] != 0; i++)Console.Write(p[i]);}Console.WriteLine();}Вот результаты выполнения этой программы:Это простой тест.Использование многоуровневой непрямой адресацииМожно создать указатель, который будет ссылаться на другой указатель, а тот — наконечное значение.
Эту ситуацию называют многоуровневой непрямой адресацией (multipleindirection), или использованием указателя на указатель. Идея многоуровневой непрямойадресации схематично проиллюстрирована на рис. 18.1. Как видите, значение обычногоуказателя (при одноуровневой непрямой адресации) представляет собой адрес переменной,которая содержит некоторое значение. В случае применения указателя на указатель первыйсодержит адрес второго, а тот указывает на переменную, содержащую определенноезначение.При использовании непрямой адресации можно организовать любое желаемоеколичество уровней, но, как правило, ограничиваются лишь двумя, поскольку увеличениечисла уровней часто ведет к возникновению концептуальных ошибок.494Часть I.
Язык C#Переменную, которая является указателем на указатель, нужно объявитьсоответствующим образом. Для этого достаточно поставить дополнительный символ“звездочка” (*) после имени типа. Например, следующее объявление сообщаеткомпилятору о том, что q — это указатель на указатель на значение типаint: int** q;Необходимо помнить, что переменная q здесь — не указатель на целочисленноезначение, а указатель на указатель на int-значение.Чтобы получить доступ к значению, адресуемому указателем на указатель,необходимо дважды применить оператор “*”, как показано в следующем примере:using System;class Multiplelndirect {unsafe public static void Main() {int x; // Переменная содержит значение.int* p; // Переменная содержит указатель//на int-значение.int** q; // Переменная содержит указатель на указатель//на int-значение.x = 10;p = &x; // Помещаем адрес x в р.q = &p; // Помещаем адрес p в q.Console.WriteLine(**q); // Отображаем значение x.}}При выполнении этой программы мы получили бы значение переменной x, т.е.
число10. Здесь переменная p объявлена как указатель на int-значение, а переменная q — какуказатель на указатель на int-значение.И еще: не следует путать многоуровневую непрямую адресацию с такимивысокоуровневыми структурами данных, как связные списки, которые используютуказатели. Это — две принципиально различные концепции.УказательПеременнаяАдресЗначениеОдноуровневая непрямая адресацияУказательУказательПеременнаяАдресАдресЗначениеМногоуровневая непрямая адресацияРис. 18.1. Одноуровневая и многоуровневая непрямая адресацияМассивы указателейУказатели, подобно данным других типов, могут храниться в массивах.
Вот,например, как выглядит объявление трехэлементного массива указателей на int-значения:int*[] ptrs = new int*[3];Глава 18. Опасный код, указатели и другие темы495Чтобы присвоить адрес int-переменной с именем var третьему элементу этогомассива указателей, запишите следующее:ptrs[2] = &var;Чтобы получить значение переменной var, используйте такой синтаксис:*ptrs[2]Ключевые слова смешанного типаВ заключение части I рассмотрим определенные в C# ключевые слова, которые ещене были здесь описаны.sizeofНе исключено, что вам понадобится узнать размер (в байтах) одного из C# -типовзначений.
Для получения этой информации используйте оператор sizeof. Формат егоприменения таков:sizeof(тип)Здесь элемент тип — это тип, размер которого мы хотим получить. Операторsizeof можно использовать только в контексте опасного (unsafe) кода. Таким образом,он предназначен в основном для специальных ситуаций, особенно при работе сосмешанным (управляемым и неуправляемым) кодом.lockКлючевое слово lock используется при работе с несколькими потоками. В C#программа может содержать два или больше потоков выполнения. В этом случаепрограммы работают в многозадачном режиме.
Другими словами, отдельные фрагментыпрограмм выполняются не только независимо один от другого, или, можно сказать,одновременно. Это приводит к возникновению определенной проблемы: а что если двапотока попытаются использовать ресурс, который одновременно может использоватьтолько один поток? Чтобы решить эту проблему, можно создать критический раздел кода,который в данный момент может выполняться только одним потоком.
Такой подходреализуется с помощью ключевого слова lock. Его формат таков:lock(obj) {// Критический раздел.}Здесь элемент obj представляет объект, который стремится получить блокировку.Если один поток уже вошел в критический раздел, то второй должен ожидать до тех пор,пока не выполнится первый. Критический раздел может быть выполнен при полученииразрешения на установку блокировки.
(Подробнее см. главу 21.)readonlyВ классе можно создать поле, предназначенное только для чтения, если объявить егос помощью ключевого слова readonly, причем readonly-поле можноинициализировать, но после этого изменить его содержимое уже нельзя. Следовательно,использование readonly-полей — удобный способ создать константы (например,обозначающие размерность массивов), которые применяются на протяжении всей про-496Часть I. Язык C#граммы. Предназначенные только для чтения поля могут быть как статическими, так инестатическими.Рассмотрим пример создания 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.SIZE инициализируется значением 10.
После этого его можноиспользовать, но не изменять. Чтобы убедиться в этом, попытайтесь удалить символкомментария, стоящий в начале последней строки, и скомпилировать программу. Выполучите сообщение об ошибке.stackallocС помощью ключевого слова stackalloc можно использовать память стека.
Этоможно делать только при инициализации локальных переменных. Распределение памятистека имеет следующий формат:тип *p - stackalloc тип[размер]Здесь p — указатель, который получает адрес области памяти достаточно большогоразмера, чтобы хранить размер объектов типа тип. Ключевое слово stackalloc можноиспользовать только в контексте опасного (unsafe) кода.Обычно память для объектов выделяется из кучи, которая представляет собойобласть свободной памяти. Выделение памяти из области стека — своего рода исключение.Переменные, размещенные в области стека, не обрабатываются сборщиком мусора. Но онисуществуют только при выполнении блока, где были объявлены. По завершениивыполнения блока эта память освобождается. Единственное достоинство использованияключевого слова stackalloc — можно не беспокоиться о том, что соответствующаяобласть памяти попадет под “метлу” сборщика мусора.Рассмотрим пример использования ключевого слова stackalloc:// Демонстрация использования ключевого слова stackalloc.using System;class UseStackAlloc {unsafe public static void Main() {int* ptrs = stackalloc int[3];Глава 18.