Ликбез по программированию

Честно сказать, просмотрев несколько учебников по информатике для школьников, а также ознакомившись с задачами олимпиад по программированию (или информатике, не помню, как точно называется), прочитав несколько методических пособий для ВУЗов, и зная, как функционирует коммерческое программирование у нас, у меня сформировалось пессимистическое мнение. Также по итогам моего общения/работы в последние два года, могу отметить, что самый худший код, который я мог увидеть — у белорусов. Он хуже индийского и китайского. С белорусами сравним разве что российский код, но не тот, который был раньше, а сейчас. Видите, как все плохо получается:)? И это проблема наших студентов? Они бездари? Разве? Возьмите методическое пособие БГУИР, например, по программированию сетей. Нормальный человек там что-то поймет? Навряд ли. Почему? Из-за местного научного косноязычия. Оно проявляется и в тех же постановках задач для олимпиад по информатике, что я прочел. Неужели русский язык так сложен, что трудно написать понятные задания или описать какой-либо процесс? Такое ощущение, что есть некий «бюрократический русский язык», в рамках которого чем непонятнее текст, тем умнее выглядит автор.

Поскольку в компьютерной прессе довольно мало такого «мужского тяжелого», а именно, практического программирования, я решил сделать активной(!) серию «Ликбез по программированию», присылайте вопросы. Только введу ограничения и опишу ситуацию. Я могу грамотно ответить по языкам и средам, которые знаю, среди основных языков это: C, C++, C#, Java, J#, Object Pascal, Lua, ActionScript, JavaScript, PHP, все остальное — просто, если в мире программирования не произошло революции. Не удивляйтесь, пожалуйста, такому списку, просто я вырос в семье программиста…

Впрочем, ситуация несколько усугублена тем, что фактически у каждой среды свои стандарты. Даже более того, нами так любима Visual Studio, но она значительно меняется от версии к версии. Поэтому первые вопросы как раз таки будут по ней. Причем обсуждать мы все будем на простых примерах.

Первый запрос — файл-менеджер (VC++, MCF)

Первый вопрос-запрос пришел по поводу программирования менеджера файлов. Вроде бы тут ничего сложного, но студент остановился в самом начале, а именно, ему нужно отобразить файлы и папки, расположенные по определенной ссылке. Создать навигацию по папкам. Причем нигде вразумительного описания, как он сказал, не нашел. Язык… гх-м… С++ (MSVS 2008), технология… гхм-м (два раза)… MFC. ОК. Если уж вы не нашли ответы на такие очевидные вещи, хотя… это возможно, я покажу самый что ни на есть простой пример на уровне отображения файлов и папок в элементе List Box. В принципе, для схожих компонентов действия будут практически идентичными.

Запускаем Visual Studio. Создаем новый проект Visual C++ —>>> MFC Application, назовем его FileList. Для простоты выбираем вариант Dialog Based без окна About. В результате появляется форма ListBox.rc и т.п. В общем, загружается все, что нужно. Удаляем с центра формы элемент Static Text с надписью. В ToolBox находим компоненту List Box и помещаем ее на форму, подгоняем нужный размер. В его свойствах отключаем сортировку (окно Properties, Sort устанавливаем в False).

Далее нам нужно назначить DDX переменную объекту List Box. Для этого нажимаем над ним правую кнопку мыши и в контекстном меню выбираем пункт Add Variable…, в результате откроется соответствующее окно. В нем мы смотрим, что тип переменной у нас будет CListBox, и даем ей имя, например m_listbox. Нажимаем ОК. Теперь все для предварительной работы готово.

Далее нам нужно представить, как все будет происходить. То есть, изначально, например, будет выводиться список файлов и папок, находящихся на диске С:\. Потом, после двойного клика на папке, она открывается, и соответственно показывается ее содержимое. Пойдем классическим путем, используя класс CFileFind. При этом, вы понимаете, что обращаться к отображению содержимого мы будем постоянно, поэтому сам процесс лучше вынести в отдельную функцию.

Поэтому, у нас пока только отображена форма, делаем двойной клик на кнопке ОК. В результате открывается файл FileListDlg.cpp на месте отображения содержимого функции void CFileListDlg::OnBnClickedOk(). Пока не вписываем в нее ничего.

Над ней создаем новую функцию, которую назовем, например, FileView. То есть, помещаем строки:
void CFileListDlg::FileView(CString link)
{}

После переходим в файл FileListDlg.h, и в месте записи указания функции «нажатие на кнопку» вводим дополнительную запись:
afx_msg void FileView(CString link);

Проверяем Ctrl+Shift+B (или в главном меню Build —>>> Build Solution), если ошибок нет, идем дальше. Теперь объявляем новые переменные, которые будут использоваться из различных функций, то есть прописываем их в заголовке cpp-файла либо же по классике — вносим в h-файл.
//текущая ссылка
CString temp_link;
//массив папок и файлов
CString dir_n_files[1024];
//массив указателей на то,
//это директория или файл
BOOL dir_metka[1024];
//счетчик количества
//файлов и папок
int NUM;

Как вы поняли, мы по умолчанию указываем, что файлов и папок в сумме не будет больше 1024.
Теперь в тело функции void CFileListDlg::FileView(CString link) вносим следующий код:
void CFileListDlg::FileView(CString link)
{
//объявляем экземпляр класса
//CFileFinder
CFileFind finder;
// присваиваем строку поиска
// временной переменной функции
CString str_link(link);
//присваиваем строку поиска
//временной переменной temp_link
temp_link = link;
//формируем строку
//поиска
str_link += _T("/*.*");
//активизируем ListBox
m_listbox.ResetContent();
UpdateData(TRUE);
// начинаем работу с файлами
BOOL bWorking = finder.FindFile(str_link);
//запускаем цикл поиска
//с использованием функции-метода
//FindNextFile()
if (bWorking)
{
NUM=0;
while (bWorking = finder.FindNextFile()) {
dir_n_files[NUM] = finder.GetFileName();
//если это директория?
//выводим в квадратных
//скобках
if (finder.IsDirectory())
{
m_listbox.InsertString(NUM, _T("[")+dir_n_files[NUM]+_T("]"));
dir_metka[NUM]=true;
} else {
m_listbox.AddString(dir_n_files[NUM]);
dir_metka[NUM]=false;
}//end if-else
NUM++;
}//end while
}//end if(bWorking)
finder.Close();
}
Все, теперь нам осталось ввести в тело функции нажатия кнопки OK, вместо того что там есть, строку:
void CFileListDlg::OnBnClickedOk()
{
FileView(_T("C:"));
}
Теперь компилируем проект и запускаем. По нажатии кнопки ОК в List Box выведется все содержимое диска C.

Теперь нам нужно реализовать навигацию по папкам. Как вы понимаете, делая двойной клик левой кнопкой мыши на той или иной директории, вы просто меняете ссылку для функции FileView(). При этом нужно учитывать, что открываться должны только папки, для этого нам и нужен был массив указателей на то, директория это или нет.

Теперь входим в свойства объекта ListBox, выбираем закладку событий (Control Events, кнопка с изображением желтой молнии), и первой в списке идет то, которое нам и нужно, а именно, LBN_DBLCLICK. В соседнем с этой надписью окошке открываем ComboBox, где отобразится надпись <<>> OnLbnDblclkList1. Выбираем этот вариант, в результате чего произойдет автоматический переход к файлу FileListDlg.cpp на место новообразованной функции void CFileListDlg::OnLbnDblclkList1().
Для начала попробуем просто открыть папку, не полностью реализуя навигацию, для этого в данную функцию помещаем следующие строки:
void CFileListDlg::OnLbnDblclkList1()
{
//если выбранное
//является директорией, то...
if (dir_metka[m_listbox.GetCurSel()])
{
//к темповой ссылке
//добавляется имя каталога
temp_link+=_T("/")+dir_n_files[m_listbox.GetCurSel()];;
//вызывается функция
//FileView
FileView(temp_link);
}//end if
}

На самом деле, здесь уже можно и закончить. При входе в папку в ListBox автоматически будут отображаться сверху точка и две точки для перехода к родительским каталогам (точка не работает). В других компонентах подобного нет, поэтому, если хотите, такое нужно вводить дополнительно. Хотя, если вы выведите на просмотр содержимое темповой ссылки, то заметите, что с каждым этапом навигации она вырастает. То есть тут нужно управлять процессом. Поэтому необходима обработка для двойного нажатия на «..» и «.», надо парсить строку, удалять из нее название директории при выходе на родительский уровень и так далее.

Пример с List Box самый простой, но не намного сложнее делать List Control и т. п. Отметим, что у объекта Finder есть множество свойств. Например, набрав finder.GetLength(); вы получите длину файла и так далее.

***

Отдельно стоит отметить (кстати, с этим может столкнуться и наш студент, используя другие компоненты), что в Visual С++ используется достаточно большое количество строковых типов данных, и зачастую между ними нужно осуществлять совместимость. То есть в этом языке практически отсутствует динамическое приведение типов, причем отсутствует оно и для родственных типов, например, строк. Да, в нашем коде, например, мы переходили от массивов char к CString с помощью преобразования _T(), но нередки случаи, когда оно не срабатывает. Во многих компонентах используются другие строковые типы, не совместимые напрямую с CString. Мало того, когда вы начнете парсить в рамках MSVS 2008, то некоторые привычные методы работать не будут, например, будет выдаваться ошибка, что эти операции могут производиться только со строковым типом CStringT и так далее. В общем, для конвертации я всегда рекомендую использовать:
USES_CONVERSION;
после чего A2T() — преобразование символьного массива в строковые данные, T2A() — наоборот. Причем под T в данных аббревиатурах подразумеваются практически все строковые типы. Например, работая с тем же List Control вы не можете напрямую совместить СString и LPWSTR, хотя оба строковые (кстати, по этому поводу были вопросы от читателей). Например, в варианте:
CString str1=_T("ПРИВЕТ");
LPWSTR str2=_T("ПОКА");
str2=str1;

Естественно выдастся ошибка в третьей строке, а именно, Cannot convert ‘CString’ to ‘LPWSTR’. Но с правой стороны мы можем заметить идентичную картину. Как сделать так, чтобы в str2 было «ПРИВЕТ»? На самом деле просто.
CString str1=_T("ПРИВЕТ");
LPWSTR str2=_T("ПОКА");
USES_CONVERSION;
str2=A2T(T2A(str1));

Что касается парсинга и поиска элементов в строке, то я всегда рекомендую опять же классику, а именно, использование функции strtok(). Это пришло из ранних С/С++ и работает всегда.

Другие вопросы

В принципе, пришло несколько интересных вопросов, но описывать их подробно у меня пока нет времени. Первый… В целом, общие принципы работы- взаимодействия ActionScript с серверными скриптами достаточно понятно описаны в книге Дмитрия Гурского «ActionScript 2.0». В принципе, я писал ранее сетевые игры, но в материалах «Ликбез по программированию» в будущем хотел описать другой вариант, а именно, связанный с Java и PHP. Благодаря книге Гурского вы можете также научиться очень интересной вещи — не рисовать баннеры, используя множество слоев, а писать их в коде в нулевом кадре. И, конечно, получается более интерактивный вариант, то есть после этого вы легко можете писать и игры.

Второй вопрос… Вариант написания асинхронного сервера (на С++), а также листинги для обмена большим количеством данных между клиентом и сервером есть в книге Михаила Фленова «С++ глазами хакера». Там все доступно объяснено, правда, в том издании, что я встречал, код писался под старые версии Visual Studio, и в MSVS 2008-м он не совсем работоспособен. Хотя книга отличная. В прошлой статье «Ликбез по программированию» я также рассказал ключевые понятия, которые необходимы начинающим специалистам для программирования сетей.

Лично я в последнее время активно использую Lua, об этом также имеет смысл рассказать. Сервер получает какой-нибудь запрос от клиента (все это взаимодействие с клиентами реализуется на С++), парсит его, а всю обработку (даже формирование какого-нибудь документа, который нужно выслать по запросу) выполняет встроенный Lua-код, то есть сценарий. Чем это выгодно? В принципе, на Lua работать гораздо проще (как и хранить в нем базу данных), во-вторых — это сценарии, которые можно редактировать отдельно от компилируемой программы на С++. То есть, не нужно все перепахивать в основной программе, достаточно поработать с файлом сценария.

Кстати, я отметил, что далеко не единственный так делаю, есть множество примеров от крупных разработчиков. Причем Lua-сценарии, как мы говорили ранее (серия материалов «Lua для игр и не только»), встраиваются во внешний API, а последний может работать и на других платформах. То есть достаточно один раз написать правильный сценарий, и потом использовать его для различных платформ без изменений.

Кристофер christopher@tut.by


Компьютерная газета. Статья была опубликована в номере 43 за 2009 год в рубрике программирование

©1997-2024 Компьютерная газета