Шаг 213.
Язык программирования C#. Начала.
Указатели. Указатели и текст

    На этом шаге мы рассмотрим связь указателей и текстовых значений.

    Как мы знаем, текстовые значения в языке C# реализуются в виде объектов класса String. Стандартная схема такая: имеется объектная переменная класса String, которая ссылается на объект этого же класса, а объект, в свою очередь, содержит текстовое значение. И, как мы помним, ранее утверждалось, что текстовые объекты неизменны: нельзя поменять текст в текстовом объекте, а можно только создать новый объект и ссылку на этот новый объект записать в объектную переменную. Создается иллюзия, что текст изменился.

    Реальность немного сложнее. Технически текст реализуется в виде символьного массива. Эта "традиция" уходит корнями в языки C и C++. В этих языках в конце такого символьного массива размещается нуль-символ '\0', который служит индикатором окончания текста. Данный символ (не следует его путать с символьным изображением цифры ноль - это не одно и то же) имеет нулевой код и добавляется в конец символьного массива автоматически. Но это, еще раз подчеркнем, в языках C и C++. В языке C# упомянутый выше символьный массив "спрятан" в текстовом объекте, на который ссылается переменная класса String. И хотя в целях совместимости обычно в конце символьного массива нуль-символ '\0' добавляется, никакой особой роли в C# он не играет. Размер текста запоминается отдельно, а нуль-символ обрабатывается, как и все прочие символы.


Резюмируем. Текстовая строка в языке 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);
этому элементу присваивается новое значение. В результате в текстовом объекте, на который ссылается переменная txt, символы текста меняются на цепочку символов из алфавита, начиная с буквы 'A'.


Выше мы исходили из того, что символьный массив, через который реализуется текст, завершается нуль-символом '\0'. Но это не самый надежный критерий проверки окончания текста. Более того, теоретически сам текст может содержать в себе нуль-символ. Поэтому в общем случае разумнее использовать свойство Length.

    На следующем шаге мы рассмотрим многоуровневую адресацию.




Предыдущий шаг Содержание Следующий шаг