Смекни!
smekni.com

Создание консольных приложений с помощью мастера в Visual C++ 6 - 2 (стр. 3 из 3)

Ниже расположено место, куда мы подключаем заголовочные файлы, которые редко изменяются и будут использоваться как перекомпилированные (я говорил об этом выше, в начале статьи). Zabot’ливая среда Visual C++ 6.0 уже подключила там <iostream>.

А вот теперь давайте смотреть самое интересное - кончено же Listing4.cpp. Тут у нас вот чего уже готово:

Разбираем по порядку. Как обычно, подключаем stdafx.h, а вместе с ним и Listing4.h . Он подключается здесь, а не в файле stdafx.h, потому что ожидается, что мы этот файл (Listing4.h) будем достаточно часто ковырять. Следующий блок из пяти строчек может оказаться малопонятным. Пожалею я вас и не буду сейчас мозги пудрить, подробно разъясняя эту запись, просто скажу, что она, можно сказать, каноническая, и менять её, как правило, не стоит. Применяется в основном в отладочных целях и во избежание некоторых трудностей с ключевыми словами и именами. Дальше как раз интересное и начинается!

Итак, дальше у нас идёт объявление объекта класса нашего приложения. А именно объявление объекта theApp класса CWinApp. Класс CWinApp является производным от CWinThread. Поэтому класс CWinApp представляет и основной поток выполнения программы, и само приложение. Таким образом, в любом MFC-приложении существует только один объект класса CWinApp. Нас об этом в комментарии уже предупредили.

Строчка

using namespace std;

говорит нам о том, что в данном блоке программы будет использоваться стандартное пространство имён std. Поясняю. В программе может подключаться очень большое число разных файлов заголовков. В каждом файле заголовка используется большое количество различных имен: переменные, функции, классы, структуры и т.д. Разные библиотеки разрабатывают разные люди, и, конечно же, получается так, что в разных библиотеках могут использоваться одинаковые имена. Если мы будем пользоваться сразу несколькими библиотеками, то может сложиться такая ситуация, когда при попытке использовать имя, которое дублируется и в другом подключаемом файле, компоновщик выдаст сообщение об ошибке: «Identifier multiply defined» (Идентификатор определён несколько раз», где идентификатор - это имя какого-либо элемента). Такая ситуация называется конфликтом имён. Для того, чтобы избежать таких проблем и придумали пространства имён. Пространства имён используются для разделения глобального пространства имён, что позволяет пусть и не всегда устранить, но, по крайней мере, уменьшить количество конфликтов имён.

Пространство имен (namespace) по сути представляет собой логическую группу имен, в пределах которой имена не дублируются. Например, пространство имен стандартной библиотеки std. При обращении к идентификатору (имени), входящему в какое-либо пространство имен надо явно указывать это пространство. Поэтому, обращаясь к cout пространства имен std, мы и пишем std::cout. То быть, пишется название пространства имён (std) и через два двоеточия само имя (cout). Вот почему, пользуясь именами из заголовочных файлов стандартной библиотеки (<iostream>, <string>, <list> и др.) мы должны указывать, что всё это относится пространству имён std. Вы можете создавать и свои пространства имён (ну если вам, например, потребуется назвать свою функцию именем cout), но это разговор отдельной статьи.

Запись using namespace std; говорит о том, что в пределах текущей области действия (обычно в пределах фигурных скобок) будет по умолчанию использоваться пространство имён std (не надо будет перед каждым именем писать std:: , если оно принадлежит к этому пространству). Однако этой штукой надо пользоваться очень осторожно - только если вы уверены, что будете использовать только имена указанного пространства, в противном случае могут возникнуть трудности. Если не уверены - лучше не ленитесь и явно указывайте, к какому пространству имен принадлежит данное имя.

Дальше у нас идет функция _tmain. А почему _tmain? И почему там тип TCHAR* , а не char* ? И что ещё за параметр TCHAR* envp[] ?

Ну что же, для интересующихся растолкую. Функция называется _tmain и тип параметров TCHAR* потому что среда генерирует нам такой код, для того чтобы обеспечить совместимость с различными кодировками текста, разных типов char - одно и многобайтных и т.д. и т.п. Не буду вдаваться во все подробности, да и что нам с того: main или _tmain, char или TCHAR? Для нас сейчас, в общем-то, смысл не меняется: _tmain - главная функция в программе, TCHAR* - строка текста. Обо всех проблемах со стандартами уже поZabot’илась среда. А вот касательно envp ?

Как видите, TCHAR* envp[] - тоже массив строк. Он необходим для работы с переменными окружения. Кто не в курсе про переменные окружения - читайте что-нибудь про MS-DOS (напр., Фигурнов «IBM PC для пользователя»). Поскольку работа с переменными окружения и процессами в консоли (а оно часто для того и надо) не так проста, и не слишком часто используется, то более подробно в этой статье я разъяснять про них не буду. Хватит с нас пока аргументов командной строки.

Ниже идет объявление переменной nRetCode - она будет возвращаться функцией _tmain как код ошибки. Напоминаю, если 0 - выполнение программы завершилось успешно, если не 0 - то неуспешно :)

Затем следует собственно инициализация MFC. Обычно (в GUI-приложениях) это происходит так: при инициализации объекта класса CWinApp (или производного от него) функцией WinMain, являющейся частью библиотеки MFC, вызывается функция AfxWinInit и проверяется возвращаемое ею значение. Но, поскольку консольные приложения не используют функцию WinMain, нам приходится вызывать функцию AfxWinInit непосредственно. А она у нас в таком случае просит четыре параметра:

HINSTANCE hInstance

- дескриптортекущегомодуля;

HINSTANCE hPrevInstance

- дескриптор предыдущей копии приложения; для Win32-приложений этот параметр всегда NULL;

LPTSTR lpCmdLine

- указатель на командную строку текущего процесса;

int nCmdShow

- определяет, как должно выглядеть основное окно GUI-приложения (поскольку у нас не GUI-приложение, то этот параметр тоже 0);

Что, типы данных странные? Ну да, странные… Но не буду я вам сейчас о них рассказывать - слишком много будет. Пожалею вас - не буду сейчас мозги вам этим пудрить. Всё что на данный момент вам необходимо знать, я пояснил.

Значит так, второй и четвёртый параметр у нас нуль, я уже сказал, а вот откуда мы берём первый и третий. В качестве первого параметра мы используем функцию GetModuleHandle. Она как раз и возвращает нам дескриптор модуля (файла .dll или .exe), имя которого указывается в качестве параметра. Когда этот параметр равен нулю (как у нас), возвращается дескриптор файла, использованного для создания текущего процесса. Что крутовато звучит? Если не понятно, почитайте что-нибудь по архитектуре и основным концепциям Windows… Почитайте про процессы, дескрипторы и т.д. Хотя, быть может, и без того разберётесь? Ладно. В качестве третьего параметра мы используем функцию GetCommandLine, возвращающую указатель на командную строку (формата Unicode) текущего процесса. Кто не в курсе, два двоеточия перед именем функции ставится, так как это - глобальная функция Windows.

Что у нас там дальше идёт? Ах да, после проверки условия, если функция AfxWinInit вернёт не 0 (помните, у нас ведь записано «если не» if (!AfxWinInit … ), то будет передано сообщение об ошибке с помощью стандартного небуферизованного потока диагностики ошибок cerr. У него синтаксис такой же, как и у cout. Кому интересно, макрос _T используется для решения всё тех же трудностей с кодировками (Unicode, ANSI …) Напоминаю, о потоках читайте в скором времени в разделе «Язык программирования С++». Ну и конечно же, нашему «сторожу» nRetCode присваивается значение 1. В противном случае (если инициализация MFC прошла успешно) будет выполняться код нашей программы. Именно сюда мы и будем писать свой программный код. В случае нашей заготовки это просто вывод на экран строкового ресурса. Объявляем strHello типа CString. Дальше строчкой

strHello.LoadString(IDS_HELLO);

мы, используя функцию LoadString, которая является членом класса CString, загружаем строковый ресурс Windows, имеющий идентификатор IDS_HELLO в существующий объект strHello. Затем этот объект выводится на экран с использованием стандартного потока вывода cout. Кому интересно, (LPCTSTR) - опять же для решения трудностей с кодировками.

Ну и, наконец, возвращается наш «сторож» nRetCode.

Вот, собственно, и всё! Ну что, загрузил по полной? Ничего, разбирайтесь, книжки читайте, MSDN читайте, «медитируйте» :)

Хотелось бы ещё сказать, что же всё-таки такое MSDN. MSDN - это здоровенный справочник для программирования в Visual Studio .NET. Там есть всё: и по языку С++, и по программированию в Microsoft Visual C++ и ещё очень много чего полезного. Вся эта прелесть размещается на трёх CD, и при установке на жёсткий диск весит порядка 2Гб. Да, она на английском. Но разобраться можно. Одним словом, рекомендую.

Что же вы теперь знаете и умеете? Вы теперь знаете, как сделать и использовать все 4 типа заготовок консольных приложений, знаете почти во всех деталях, что в этих заготовках и зачем нужно, а значит, можете и сами подобные вещи творить. Более того, разобравшись в этих заготовках, вы теперь знаете, как наилучшим образом на их основе начать создавать своё рабочее приложение. Но само рабочее приложение мы ещё не создавали. Я вас только «натаскал», натренировал маненько для сражений с Матрицей и её отродьями, боя же ещё не было. Но он будет! Впереди нас ждёт первый, пусть и небольшой, но реальный бой с Матрицей. А именно, мы с вами в следующей статье сделаем наше первое реально ПОЛЕЗНОЕ консольное приложение. Сегодня мы с вами подготовились. Битва впереди!

Список литературы

Н.Секунов «Самоучитель Visual C++ 6»;

Бьерн Страуструп «Язык программирования С++»;

Джесс Либерти «Освой С++ самостоятельно за 21 день»;

MSDN for Visual Studio .NET;

Содержимое заголовочных файлов (.h) среды Visual C++;

Юджин Олафсен, Кенн Скрайбер, Дэвид Уайт «MFC и Visual C++ 6»;

В.Э. Фигурнов «IBM PC для пользователя»

Zabot 2004