Шаг 217.
Язык программирования C#. Начала.
Указатели. Примеры использования
На этом шаге мы рассмотрим несколько примеров решения задач, в которых используются указатели.
Здесь мы рассмотрим несколько программ, в которых используются рассмотренные на предыдущих шагах указатели.
Задание 1.
Напишите программу, в которой объявляются три переменные типа int. Первые две переменные получают случайные значения. Область памяти,
выделенная под третью переменную, заполняется следующим образом: первые два байта копируются из первой переменной, а следующие два байта копируются
из второй переменной. Предложите способ проверки корректности вычислений.
Раскрыть/скрыть решение и комментарии.
Приведем сначала текст программы:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr217_1
{
class Program
{
unsafe static void Main()
{
// Объект для генерирования случайных чисел:
Random rnd = new Random();
int A = rnd.Next(20), B = rnd.Next(20), C;
// Вывод начальных значений:
Console.WriteLine("A = " + A);
Console.WriteLine("B = " + B);
// Формируем указатели, которые будут
// работать с двухбайтовыми ячейками:
char* sA = (char*)&A;
char* sB = (char*)&B;
char* sC = (char*)&C;
// Формируем значение C:
*sC = *sA;
*(sC + 1) = *sB;
// Вывод сформированного значения:
Console.WriteLine("C = " + C);
}
}
}
Архив проекта можно взять
здесь.
Результат работы приложения приведен на рисунке 1.
Рис.1. Результат работы приложения
Прокомментируем приведенную программу и полученный результат.
Переменная целого типа занимает в памяти четыре байта, а переменная типа char - два байта. Поэтому для работы с двухбайтовыми областями памяти
командами:
char* sA = (char*)&A;
char* sB = (char*)&B;
char* sC = (char*)&C;
заводим три указателя
sA, sB и
sC, которые инициализируем адресами расположения в памяти переменных
A, B и
C.
Затем командой
в младшую часть результата помещаем два младших байта значения переменной
A, а командой
в старшую часть результата отправляем два младших байта значения переменной
B.
Разберемся, почему получается именно такой результат.
Давайте представим в двоичной системе счисления значение переменной A, равное 3, с учетом того, что это значение занимает четыре байта:
00000000 00000000 00000000 00000011
Мы видим, что старшие три байта имеют нулевые значения.Таким образом, младшая часть итогового результата будет выглядеть так:
Аналогичную опрацию проведем и со значением переменной B, которая равна 17:
00000000 00000000 00000000 00010001
Младшая часть (2 байта) этого значения "уйдет" в старшую часть результата. Таким образом, итоговый результат в двоичной системе счисления будет иметь следующий вид:
00000000 00010001 00000000 00000011
Осталось перевести это значение в десятичную систему. счисления. Заметим, что единицы в результате находятся в разрядах с номерами: 0, 1, 16 и 20.
Таким образом, итоговый результат будет следующим:
220 + 216 + 21 + 20 = 1048576 + 65536 + 2 + 1 = 1114115
что согласуется с результатом выполнения программы.
Задание 2.
Напишите программу, в которой есть класс с целочисленным полем. На основе класса создайте объект. С помощью указателей запишите в первые два байта
области памяти, выделенной под поле объекта, символ
'A', а в два следующие байта - символ
'B'. Проверьте значение поля и объясните результат.
Раскрыть/скрыть решение и комментарии.
Начнем с текста программы:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr217_2
{
// Пользовательский класс:
class MyClass
{
// Целочисленное поле:
public int n;
}
// Класс с главным методом:
class Program
{
// Главный метод:
unsafe static void Main()
{
// Использован fixed-блок:
fixed (int* p = &new MyClass().n)
{
// Создаем указатель для работы с
// двухбайтовыми ячейками памяти:
char* s = (char*)p;
// Записываем значения:
*s = 'A';
*(s + 1) = 'B';
// Вывод значения поля
// (доступ к полю через указатель):
Console.WriteLine("Значение поля n = " + (*p));
}
}
}
}
Архив проекта можно взять
здесь.
Результат работы приложения изображен на рисунке 2.
Рис.2. Результат работы приложения
Здесь мы используем fixed-блок, в котором создаем указатель на поле класса. Необходимость в его использовании диктуется тем, что нам нужно сохранить
созданный безымянный объект класса для того, чтобы можно было работать с указателем на поле класса. Все остальное аналогично программе из первого задания.
Прокомментируем результат выполнения приложения.
Код символа 'A' равен 65 (00000000 01000001 в двоичной системе счисления), а код символа 'B' равен 66 (00000000 01000010). Таким образом,
итоговый результат в двоичной системе счисления равен
00000000 01000010 00000000 01000001
Отметим, что единицы содержатся в следующих разрядах: 0, 6, 17 и 22. Переведем результат в десятичную систему счисления:
222 + 217 + 26 + 20 = 4194304 + 131072 + 64 + 1 = 4325441
Это совпадает с полученным результатом.
Задание 3.
Напишите программу, в которой объявляется переменная типа
int, а также указатель на указатель на значение типа
char. С помощью этого указателя
нужно записать в первые два байта в области памяти, выделенной под переменную типа
int, символ
'A', а в следующие два байта - символ
'B'. Проверьте значение целочисленной переменной и объясните результат.
Раскрыть/скрыть решение и комментарии.
Начнем с текста программы:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr217_3
{
class Program
{
unsafe static void Main()
{
// Переменная целого типа:
int n;
// Указатель на указатель на значение
// типа char:
char** s;
// Придадим ей значение:
char* t = (char*)(&n);
s = &t;
// Присвоим значение переменной n
// через указатель на указатель:
**s = 'A';
*(*s + 1) = 'B';
Console.WriteLine("Значение n = " + n);
}
}
}
Архив проекта можно взять
здесь.
Результат работы приложения аналогичен результату из задачи 2.
Рис.3. Результат работы приложения
Объяснение полученного результата полностью идентично приведенному для задачи 2.
Пркомментируем текст программы.
Командой
описываем указатель на указатель. Значение ему придаем в два этапа. Сначала командой
описываем указатель
t и придаем ему в качестве значения приведенный адрес переменной
n. Затем командой
помещаем в переменную
s адрес переменной
t. Таким образом, указатель
s проинициализирован.
Далее будем его использовать для задания значений переменной
n.
Сначала командой
дважды разименовывая указатель
s, придаем значение младшей части переменной
n.
Затем командой
задаем значение старшей части. Здесь конструкция
*s содержит адрес младшей части переменной
n. Увеличивая ее на 1, мы получаем
адрес старшей части
n. Конструкция
*(*s + 1) разименовывает старшую часть переменной
n, что позволяет задать ей значение.
Со следующего шага мы начнем знакомиться с обработкой исключений.
Предыдущий шаг
Содержание
Следующий шаг