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

    На этом шаге мы рассмотрим создание и использование таких массивов.

    Указатели можно использовать практически так же, как и другие переменные. Нужно только помнить, что при использовании указателей мы имеем дело с адресами. С другой стороны, использование указателей иногда позволяет привнести в программу некоторую пикантность. В примере ниже представлена программа, в которой задействованы массивы указателей.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr215_1
{
    class Program
    {
        unsafe static void Main()
        {
            // Переменные:
            int x = 100, y = 200, z = 300;
            // Массив указателей на целочисленные значения: 
            int*[] nums = new int* [3];
            // Значение первого элемента массива: 
            nums[0] = &x;
            // Значение второго элемента массива: 
            nums[1] = &y;
            // Значение третьего элемента массива: 
            nums[2] = &z;
            // Отображение значений переменных:
            Console.WriteLine("Числа: {0}, {1} и {2}", 
                nums[0][0], *nums[1], nums[2][0]);
            // Массив указателей на символьные значения: 
            char*[] symbs = new char* [3];
            // Значение первого элемента массива: 
            symbs[0] = (char*)&x;
            // Значение второго элемента массива: 
            symbs[1] = (char*)&y;
            // Значение третьего элемента массива: 
            symbs[2] = (char*)&z;
            // Символьная переменная: 
            char s = 'A';
            // Заполнение значениями области памяти,
            // выделенной под переменные:
            for(int i = 0; i < symbs.Length; i++) {
                for(int j = 0; j < sizeof(int)/sizeof(char); j++) {
                    // В блок памяти записывается значение: 
                    symbs[i][j] = s;
                    // Новое значение символьной переменной: 
                    s++;
                    // Отображение значения из блока памяти:
                    Console.Write(symbs[i][j] + " ");
                }
                Console.WriteLine();
            }
            // Проверка результата:
            Console.WriteLine("Числа: {0}, {1} и {2}", x, y, z);
            Console.WriteLine("Проверка: {0}, {1} и {2}", 
                nums[0][0], *nums[1], nums[2][0]); 
            Console.WriteLine("Еще раз: {0}, {1} и {2}",
                *(int*)symbs[0], *(int*)symbs[1],*(int*)symbs[2]);
            Console.WriteLine("Для сравнения: {0}, {1} и {2}",
                *symbs[0], *symbs[1], *symbs[2]);
        }
    }
}
Архив проекта можно взять здесь.

    В результате выполнения программы в консольном окне отображаются следующие сообщения:


Рис.1. Результат выполнения программы

    Проанализируем программный код и результат его выполнения. Мы объявляем три целочисленные переменные x, у и z (со значениями 10 0, 200 и 300 соответственно). Массив из трех указателей на целочисленные значения создается командой

  int*[] nums = new int* [3];      .

    Элементы массива nums являются указателями, и в качестве значений им можно присваивать адреса целочисленных переменных, что мы и делаем с помощью команд

  nums[0] = &x;                 , 
  nums[1] = &y; 
и 
  nums[2] = &z;                 .

    Таким образом, nums[0] - это адрес переменной x, nums[1] - это адрес переменной у, а nums[2] - это адрес переменной z. С учетом правил адресной арифметики получаем, что nums[0][0] - это значение переменной x, *nums[1] - это значение переменной у, а nums[2][0] - это значение переменной z.

    Командой

  char*[] symbs = new char* [3];
создается массив из трех указателей на символьные значения. Значения элементам массива присваиваем командами
  symbs[0] = (char*)&x;         , 
  symbs[1] = (char*)&y;
и 
  symbs[2] = (char*)&z;         .
Поскольку переменные x, y и z целочисленные, а элементы массива symbs являются указателями на символьные значения, то нам пришлось выполнить явное приведение типа. Что получается? Например, значением элемента symbs[0] является адрес переменной x. Но переменная записана в 4 байтах, а указатель symbs[0] получает доступ к блокам размером в 2 байта. Поэтому symbs[0][0] - это значение (интерпретируемое как символ) в первых двух однобайтовых блоках области памяти, выделенной под переменную x. А выражение symbs[0][1] - это символьное значение в следующих двух однобайтовых блоках области памяти, выделенной под переменную x. Это же касается других элементов массива symbs и переменных y и z. В итоге получается, что на основе трех блоков памяти (каждый по 4 байта), выделенных под переменные x, y и z, мы организовали видимость символьного массива из трех строк и двух столбцов. Этот импровизированный двумерный символьный массив заполняется с помощью вложенных конструкций цикла, а соответствующие символьные значения отображаются в консольном окне.


Значение выражения symbs.Length - это количество элементов в массиве symbs (то есть 3). Выражение sizeof(int)/sizeof (char) определяет, сколько char-блоков помещается внутри int-блока (результат равен 2).

    Область памяти, выделенная под импровизированный двумерный символьный массив, используется также при работе с массивом nums, и в нее же записаны значения переменных x, у и z. Например, значение переменной x можно узнать по имени этой переменной с помощью инструкции nums[0][0] или с помощью выражения *(int*)symbs[0]. В последнем случае следует учесть, что symbs[0] есть указатель на первые два байта из области памяти, выделенной под переменную х. Выражение (int*)symbs[0] с явным приведением типа есть указатель на все четыре байта области, выделенной под переменную х. А выражение *(int*)symbs[0] - это значение в области памяти, выделенной под переменную х. Что касается значения переменной х, то в первые два байта этой переменной записывался код символа 'A' (это число 65), а в два других байта записан код символа 'B' (это число 66). Если бинарный код в четырех байтах интерпретировать как значение типа int, то с учетом того, что смещение на два однобайтовых блока означает умножение на 216 = 65 536, то получаем 65 + 66 * 65 536 = 4 325 441. Ну а значение выражения *symbs[0] - это символ 'A', записанный в первые два байта области памяти, выделенной под переменную х.


Вам предлагается самостоятельно объяснить результат вычисления прочих подобных выражений, использованных в программе.

    На следующем шаге мы резюмируем все ранее сказанное по данной теме на предыдущих шагах.




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