Умный дом

Указатели дают больше гибкости

Наше решение по выводу на дисплей в предыдущей секции имеет два основных недостатка. Во-первых, оно ограничено выводом шести числовых последовательностей - если пользо­ватель угадает все шесть, программа сразу завершится. Во-вто­рых, она всегда выводит те же самые шесть пар элементов в той же последовательности. Как же увеличить гибкость про­граммы?

Одно из возможных решений - создать шесть векторов, по одному на каждую последовательность, рассчитанных на одинаковое количество элементов. На каждом проходе цик­ла мы выбираем пары элементов из разных векторов. При повторном использовании вектора мы возьмем пару элемен­тов из другой части вектора. Это приблизит нас к устране­нию обоих отмеченных недостатков.

Как и в предыдущем решении, хотелось бы иметь «про­зрачный» доступ к разным векторам. В предыдущем разделе мы достигали «прозрачности» за счет доступа по индексам, а не по имени. На каждом проходе цикла мы увеличивали зна­чение индекса на 3. Сам код оставался неизменным.

В этом разделе мы добьемся «прозрачности» тем, что бу­дем обращаться к вектору косвенно, через указатель, вместо обращения по имени. Указатель вносит определенный уро­вень косвенности в программу. Вместо того чтобы работать с объектом напрямую, мы будем работать с указателем, сохра­няющим адрес объекта. Мы определим указатель, который будет адресоваться вектору чисел целого типа. При каждой итерации цикла мы будем модифицировать указатель, адре­суясь к разным векторам. Фактический код для манипуляций с указателями не изменится.

Использование указателей дает два преимущества нашей программе: увеличивает гибкость программы и добавляет уровень гибкости, отсутствующий при прямой работе с объектом. Этот раздел убедит вас в правдивости обоих ут­верждений.

Мы уже знаем, как определять объект. Следующее выра­жение определяет ival как объект целого типа int, инициа­лизированного значением 102-1:

Int ival = 1024;

Указатель сохраняет адрес объекта данного типа. Для оп­ределения указателя мы добавляем к имени типа звездочку:

Int *pi; // pi указатель на объект типа int

Pi - указатель на объект типа int. Как мы должны иници­ализировать его для указания на ival? Определением имени объекта, примерно так:

Ival; // определяет значение ival

Мы определяем ассоциированное значение, в нашем слу­чае 1024. Для получения адреса объекта, а не его значения, мы добавляем оператор адресации (&):

&ival; // определяет адрес ival

Для инициализации pi как адреса ival запишем сле­дующее:

Int *pi = fcival;

Для доступа к объекту, адресуемому указателем, мы долж­ны разыменовать указатель, что даст содержимое объекта по адресу, содержащемуся в указателе. Для этого добавим звез­дочку к указателю, следующим образом:

// разыменовываем рі для доступа к объекту им адресуемому

If (*pi! = 1024) // читаем *pi = 1024; // пишем

Сложность инициализации с использованием указателя, как вы видите, исходит от запутывающего синтаксиса. Ви­ною тому двойственная природа указателя. Мы можем мани­пулировать адресом, содержащимся в указателе, а можем манипулировать объектом, на который он указывает. Когда мы пишем:

Pi; // определяет адрес сохраняемый в pi

То фактически управляем указателем объекта. А когда мы пишем:

*pi; // определяет значение объекта адресуемого pi

То управляем объектом, адресуемым pi.

Вторая сложность в понимании указателей - возможность отсутствия адресуемого объекта. Например, когда мы пишем *pi, это может быть, а может и не быть причиной краха про­граммы при выполнении. Если pi адресуется к объекту, разы - меновывание pi работает совершенно правильно. Если же pi не адресуется к объекту, попытка разыменовать pi влечет непредсказуемое поведение программы во время выполне­ния. Это означает, что когда мы используем указатель, то должны быть уверены, что он адресуется к объекту, прежде чем сделаем попытку разыменовать его.

Указатель, который не адресуется к объекту, имеет адре­суемое значение «0» (иногда его называют нулевым указате­лем). Любой тип указателя может быть инициализирован, или определен со значением «0»:

// инициализация каждого указателя без адресации к

Объекту

Int *pi = 0;

Double *pd = 0;

String *ps = 0;

Для защиты от разыменовывания нулевого указателя мы проверяем указатель, чтобы убедится, что его адресуемое значение не равно «О». Например,

If (pi ScSc *рі ! = 1024) *рі = 1024;

Выражение:

If (pi && ...)

Становится истинным, только если pi содержит иной адрес, чем «0». Если оно ложно, оператор И не выполняется во вто­ром выражении. Для проверки мы обычно пользуемся опе­ратором логического отрицания НЕ:

If (! pi) // истинно, если pi установлено в О

А вот наши шесть объектов векторов последовательностей:

Vector<int> fibonacci, lucas, pell, triangular, square, pentagonal;

На что похож указатель вектора объектов целого типа? Что ж, в общем, указатель имеет такую форму:

(тип_объекта_указывающего_на * имя_указателя__объекта) type__of_object_pointecLto * name__of_pointer_object

Наш указатель адресует тип vector<int>. Назовем его pv и инициализируем нулем:

Vector<int> *pv = 0;

Pv может адресоваться каждому вектору последовательно­сти по очереди. Конечно, мы можем определить pv адреса­цией к каждой последовательности:

Pv = fcfibonacci;// ... pv = &lucas;

Но этим приносится в жертву «прозрачность» кода. Альтер­нативное решение - запомнить адреса каждой последова­тельности в векторе. Этот прием позволяет нам добраться до них «прозрачно», через индекс:

Const int seq_cnt = 6; // массив seq_cnt указателей на // объекты типа vector<int>

Vector<int> *seq_addrs[seq_cnt] = {fcfibonacci, fclucas, &pell, ^triangular, fcsquare, ^pentagonal};

Seq_addrs - это встроенный массив элементов типа vector<int>*. seq_addrs[0] содержит адрес fibonacci век­тора, seq_addrs [ 1 ] - адрес lucas вектора и т. д. Мы исполь­зуем это для доступа к различным векторам через индекс, а не по имени:

^ector<int> *current_vec = 0; // ...

For (int ix = 0; ix < seq_cnt; ++ix) { current_vec = seq_addrs[ix];

// вывод на дисплей всех элементов осуществляется

// косвенно через current_vec }

Оставшейся проблемой с задуманной реализацией является полная предсказуемость. Последовательности всегда Fibonacci, Lucas, Pell... Мы бы хотели сделать вывод на дисплей последо­вательностей случайным. Это возможно с использованием стандартной библиотеки языка С функциями rand() или srand():

#include <cstdlib> srand(sequent);

Seq_index = rand() % seq_cnt; current_vec = seq__addrs [seq_index];

RandO и srand () - функции стандартной библиотеки, которые поддерживают псевдослучайную генерацию, srand () активизирует генератор с его параметрами. Каждый вызов rand () возвращает целое значение в диапазоне от 0 до максимального целого значения, представленного int. Мы должны это ограничить числами от 0 до 5, чтобы они были правильными индексами seq_addrs. Оператор остатка (%) гарантирует нам индексацию между 0 и 5. Файл заголовка cstdlib содержит объявление обеих функций.

Мы сохраняем указатель на класс объекта несколько ина­че, чем делали это с указателем на объект встроенного типа, потому что класс объекта имеет связанное с ним множество операций, которые мы можем вызвать. Например, для про­верки, является ли первый элемент вектора f ibonacci еди­ницей, можно написать:

If (! fibonacci. empty () && (fibonacci [1] == 1))

Как мы могли бы осуществить ту же проверку через pv? Объединение f ibonacci и empty О через точку называется оператором выбора члена. Оно используется для выбора опера­ций класса через объект класса. Для выбора операции клас­са через указатель используем оператор-стрелку (->) выбора члена:

! pv->empty()

Поскольку указатель может адресоваться к отсутствующе­му объекту, перед тем как мы используем empty () через pv, необходимо проверить, что адресация не нулевая:

Pv && ! pv->empty()

Окончательно для вызова оператора индексов мы долж­ны разыменовать pv (понадобятся дополнительные скобки вокруг разыменованного pv из-за более высокого приорите­та индексного оператора):

If (pv ScSc I pv->empty () ScSc ((*pv) [ 1 ] == 1)) Запись и чтение файлов

Если пользователю случится запустить нашу программу по­вторно, было бы здорово, если бы счет сохранялся для обе­их сессий. Чтобы это стало возможным, мы должны:

• записать имя пользователя и данные сессии в файл в конце сессии;

• прочитать данные предыдущей сессии в программу при ее повторном запуске.

Посмотрим, как мы можем это сделать. Для чтения и записи в файл мы должны включить файл заголовка f stream:

#include <fstream>

Чтобы открыть файл для вывода, определим объект клас­са of stream (an output file stream - поток вывода файла), пе­редавая его имя в открываемый файл:

// seq_data. txt открыт на вывод ofstream outfile("seq_data. txt");

Что происходит, когда мы объявляем out file? Если его не существует, он создается и открывается на вывод. Если же он существует, то открывается на вывод, а все данные, которые в нем содержатся, игнорируются.

Если мы хотим добавить, а не замещать данные в существу­ющем файле, необходимо открыть файл в режиме добавления.

Мы делаем это, передавая второе значение ios_base:: арр объекту of stream:

// seq_data. txt открывается в append mode (режим добавления)

// новые данные добавляются в конец файла ofstream outfіle("seq_data. txt", ios_base::арр) ;

Файл может не открыться. Прежде чем записывать в него, нужно убедиться, что он открылся успешно. Простейший путь проверки - убедиться в истинности объекта класса:

// если outfile определяется, как false, // файл не может быть открыт if (! outfile)

Если файл не может быть открыт, объект класса ofstream становится ложным. В этом примере мы предупредим пользователя, выводом сообщения cerr. сегг представляет стандартную ошибку, сегг, подобно cout, выводится на тер минал пользователя. Разница в том, что вывод сегг не буфе­ризуется, оно выводится сразу на терминал:

If (! outfile)

// по какой-то причине не открывается...

Сегг « "Бах! Не могу сохранить данные сессии!п";

Else

// ok: outfile открыт, давайте писать данные outfile « usr_name « " " « num_tries « " " « num_right « endl;

Если файл открывается успешно, мы непосредственно вы­водим в него данные, как делаем это для объектов класса ostream cout и cerr. В этом примере мы пишем три значения в out file, последние два отделены пробелами. Endl - предопре­деленный манипулятор, поставляемый библиотекой iostream.

Манипулятор выполняет несколько операций с iostream, отличных от записи и чтения данных, endl вставляет сим­вол перевода на новую строку, а затем сбрасывает на диск выходной буфер. Другие предопределенные манипуляторы включают hex, отображающий на дисплее целое число в ше - стнадцатеричном виде, oct, который отображает целое в восьмеричном виде, и setprecision(n), устанавливающий точность отображения чисел с плавающей точкой в п.

Чтобы открыть файл на ввод, мы определяем объект клас­са if stream (an input file stream - поток ввода в файл), пере­давая ему имя файла. Если файл не может быть открыт, объект класса if stream определяется как ложный. Иначе, файл позиционируется в начало данных, записанных в него:

// in file открыт в output mode ifstream infіle("seq_data. txt") ; int num_tries = 0; int num_cor = 0;

If (! infile){

// по какой-то причине файл не открывается...

// мы будем предполагать, что это новый пользователь...}

Else {

// ok: читаем каждую линию входного файла

// смотрим, играл ли пользователь раньше —

// формат каждой линиии:

// name num_tries num_correct

// nt: количество попыток

// пс: количество отгадываний

String name;

Int nt;

Int nc;

While (infile » name){ infile » nt » nc; if (name == usr_name) {

11 нашлиI

Cout « "С возвращением, " « usr_name« "пВаш текущий счет " « nc « " out of " « nt « "ХпУдачи! n" ; nurrutries = nt; num_cor = nc;}}}

Каждый проход цикла while прочитывает новую линию файла, пока не будет достигнут конец файла. Когда мы пи­шем:

Infile » name

Возвращаемое значение входного выражения - объект клас­са, из которого мы читаем— infile в данном случае. Когда конец файла достигнут, условие true объекта класса сменя­ется на false. Это причина, по которой условное выражение цикла while прерывается, когда достигается конец файла:

While (infile » name)

Каждая линия файла содержит строку, за которой следу­ют два целых в форме:

Anna 24 19 danny 16 12 ...

Выражение:

Infile » nt » nc;

Читает по очереди количество попыток пользователя в nt и количество угадываний в пс.

Если мы хотим и читать, и писать в тот же самый файл, мы определяем объект класса fstream. Для открывания его в режиме дополнения мы должны передать второе значение в форме:

Ios_base:: in I ios_base:: app : fstream iofile("seq_data. txt",ios_base::in Iios_base::app) ; if (! iofile)

// файл не открывается по какой-то причине ______ гаді

{ // переходим к началу файла для начала чтения iofile. seekg(O);

// ok: все остальное без изменений...}

Когда мы открываем файл в режиме добавления, текущая позиция - конец файла. Если мы пытаемся читать файл без перепозиционирования, то просто получаем конец файла. Оператор seekgO возвращает iofile к началу файла. По­скольку он открыт в режиме дополнения, любая операция записи добавляет данные в конец файла.

♦в ncunst

Умный дом

Вторая версия основной программы на языке С++

/****************^ * TOC o "1-3" h z Copyright (С) 2006 by Vladimir Gololobov * * vgololobov@yandex. ru * * * * This program is free software; you can redistribute it …

Циклы

Циклы выполняют выражения или блоки выражений до тех пор, пока выражение условия не становится истинным. Наша программа требует двух циклов (один вложен в дру­гой). Пока пользователь желает угадывать последовательно­сти: { …

Две полезные схемы

Первая схема относится к настенному выключателю, работа­ющему по протоколу XI0. Что полезного можно почерпнуть из этой схемы? Например, организацию сканирования сети и управления триаком. Схему я привожу, как она сохранилась …

Как с нами связаться:

Украина:
г.Александрия
тел./факс +38 05235  77193 Бухгалтерия
+38 050 512 11 94 — гл. инженер-менеджер (продажи всего оборудования)

+38 050 457 13 30 — Рашид - продажи новинок
e-mail: msd@msd.com.ua
Схема проезда к производственному офису:
Схема проезда к МСД

Оперативная связь

Укажите свой телефон или адрес эл. почты — наш менеджер перезвонит Вам в удобное для Вас время.