На этом шаге мы рассмотрим связь указателей и текстовых значений.
Как мы знаем, текстовые значения в языке C# реализуются в виде объектов класса String. Стандартная схема такая: имеется объектная переменная класса String, которая ссылается на объект этого же класса, а объект, в свою очередь, содержит текстовое значение. И, как мы помним, ранее утверждалось, что текстовые объекты неизменны: нельзя поменять текст в текстовом объекте, а можно только создать новый объект и ссылку на этот новый объект записать в объектную переменную. Создается иллюзия, что текст изменился.
Реальность немного сложнее. Технически текст реализуется в виде символьного массива. Эта "традиция" уходит корнями в языки C и C++. В этих языках в конце такого символьного массива размещается нуль-символ '\0', который служит индикатором окончания текста. Данный символ (не следует его путать с символьным изображением цифры ноль - это не одно и то же) имеет нулевой код и добавляется в конец символьного массива автоматически. Но это, еще раз подчеркнем, в языках C и C++. В языке C# упомянутый выше символьный массив "спрятан" в текстовом объекте, на который ссылается переменная класса String. И хотя в целях совместимости обычно в конце символьного массива нуль-символ '\0' добавляется, никакой особой роли в C# он не играет. Размер текста запоминается отдельно, а нуль-символ обрабатывается, как и все прочие символы.
Используя указатели, мы можем "проникнуть" в текстовый объект и получить прямой доступ к элементам символьного массива, который фактически содержит текст. С помощью указателей мы можем не только прочитать отдельные символы в массиве, но также и изменить их непосредственно в текстовом объекте. Пример, в котором иллюстрируется данный подход, представлен в примере ниже.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr213_1 { // Класс с главным методом: class Program { // Главный метод: unsafe static void Main() { // Текстовая строка: String txt = "Программируем на C#"; // Отображение текста: Console.WriteLine(txt); // Указатель на начальный элемент строки: fixed(char* p = txt) { // Перебор символов строки: for(int k = 0; p[k] != '\0'; k++){ // Отображение символа: Console.Write("|" + p[k]); // Изменение значения символа в объекте: p[k] = (char)('A' + k); } Console.WriteLine("|"); } // Отображение текста: Console.WriteLine(txt); } } }
Результат выполнения программы будет следующим:
Рис.1. Результат выполнения программы
Мы объявляем текстовую переменную txt со значением "Программируем на C#". В fixed-блоке объявляется символьный указатель p, которому в качестве значения присваивается переменная txt. В результате в указатель p записывается адрес начального элемента символьного массива, содержащего текст. Получается так: текстовая переменная txt содержит ссылку на объект, который содержит символьный массив с текстом (символы из текста являются элементами массива). Указатель p ссылается на начальный элемент в этом символьном массиве. Индексируя указатель в цикле, последовательно получаем доступ к каждому элементу в символьном массиве. Оператор конструкция цикла, пока истинно условие p[k]!='\0', то есть пока не будет прочитан нуль-символ. За каждую итерацию цикла сначала отображается текущее символьное значение p[k] элемента массива, а затем командой
p[k] = (char)('A' + k);
На следующем шаге мы рассмотрим многоуровневую адресацию.