Листинг 1.2. Использование static_cast
Листинг 1.1. Helloword.
/* Данная программа выводит на экран строку «Hello, World!» ,
* после чего ожидает нажатия любого символа на клавиатуре
* для своего завершения.
*/
#include <iostream>
using std::cout;
int main (void)
{
cout << "Hello, World!";
system("pause");
return 0;
}
Первая строка кода программы является комментарием – тем текстом, который предназначен только для программиста, и который не будет рассматриваться компилятором как исполняемый код. Комментарий может оформляться двумя способами. В первом случае комментарием считается любой текст, следующий после пары символов «//» и до символа конца строки. Во втором случае, унаследованном от языка С, комментарий представляет собой текст, расположенный между символами «/*» и «*/». Он может быть много строчным.
Использование комментариев является более чем просто полезным, так как на них возлагают функции описания тех или иных частей программы. Это важно потому, что со временем функциональность отдельных участков кода забывается, а если код был написан кем-то еще, то его разбор может оказаться весьма трудоемким процессом. Также, комментарии могут передавать некоторую мета информацию, как например имя автора, версию или дату создания. Таким образом, сопровождение кода, содержащего комментарии, осуществляется гораздо легче, а его читабельность заметно повышается. Однако, пространные комментарии малоприятны и утомительны. Если есть ощущение того, что необходимо закомментировать каждую строчку кода, возможно стоить переписать сам код более удобочитаемым образом, ведь не стоит забывать, что компилятору безразлична семантика, которая может быть направлена на повышение читабельности кода.
1.2. Функция «main»
Обязательным для каждой программы является наличие функции «main», потому что именно с ее вызова начинается выполнение программы. Функция с именем «main» должна быть в точности одна, иначе неизвестно с какой именно функции должно начаться исполнение, что вызовет ошибку при компиляции.
Имя любой функции стоит перед скобками «()», в которых могут определяться параметры функции, а слово перед именем функции, в примере это «int»,определяет тип возвращаемого значения. Следом за круглыми скобками должны находиться фигурные «{}», они определяют те границы, в пределах которых расположены выполняемые функцией действия. Этот блок операций называется телом функции. Так выглядят объявления функций. Для выполнения производимых функцией действий необходимо вызвать эту функцию, где ей должны в к круглых скобках передаться значения параметров, если таковые есть. Так в примере вызывается функция «system()» с параметром «"pause"». Данная функция выполняет указанную в качестве параметра команду оболочки, после чего выполнение кода программы продолжится. Подробно функции и их параметры будут рассмотрены позже.
1.3. Идентификаторы и пространства имен
Идентификатором называют имя класса, переменной, функции и других программных сущностей. Он может состоять из букв латинского алфавита в обоих регистрах, цифр и символа «_» и не должен начинаться с цифры. Язык С++ – регистрозависимый, т.е. идентификаторы «variable» и «VARIABLE» различны. Идентификатор должен быть уникальным, т.е. не должен совпадать с другими идентификаторами или зарезервированным словом. Имена выбираются, отталкиваясь от назначения переменной или функции. Типичным примером является префикс «tmp», говорящий о том, что данная переменная является временной.
Таблица 1.1. Зарезервированные слова.
and | char | dynamic_cast | goto | not_eq | short | try |
and_eq | class | else | if | operator | signed | typedef |
asm | compl | enum | inline | or | sizeof | typeid |
auto | const | explicit | int | or_eq | static | typename |
bitand | const_cast | export | long | private | static_cast | union |
bitor | continue | extern | main | protected | struct | unsigned |
bool | default | false | mutable | public | switch | using |
break | delete | float | namespace | register | template | virtual |
case | do | for | new | reinterpret_cast | this | void |
catch | double | friend | not | return | true | volatile |
wchar_t | while | xor | xor_eq |
Длинные идентификаторы очень неудобны, но в случае повторного использования одного имени, возникнет ошибка – конфликт имен. Подобные проблемы решает контекст идентификатора – пространство имен – «namespace». Все имена группируются в соответствии со своим назначением, и каждой группе присваивается свой идентификатор. Несколько групп могут объединяться в одну. Отдельная группа и есть пространство имен, т.о. уникальность должна соблюдаться лишь в рамках одного (данного) пространства имен.
Обращение к классу или вызов функции происходит посредством оператора разрешения контекста «::». Если, к примеру, в пространстве имен «Math» существует еще одно – «Complex», где находиться искомая функция «Pow()», то ее вызов выглядит так: «Math::Complex::Pow()». Такие обращения утомительно и писать, и читать. Избавиться от этого помогает директива «using». Она позволяет сделать доступным пространство имен без явного к нему обращения: «using namespace Math::Complex». Возможно и включение отдельных идентификаторов «using Math::Complex::Pow». В обоих случаях обращаться к функции можно так, как если бы она являлась членном данного пространства имен. Различие состоит в том, что при включении всего пространства имен «Complex» будут доступны и другие неиспользуемые функции, которые могут вызвать конфликты имен.
В примере строка «using namespace std;» делает доступными все содержимое пространства имен «std». Оно определено в библиотечном файле «iostream», для включения которого необходимо использовать директиву препроцессора «#include» Для использования своих собственных файлов надо вместо символов «<>» использовать «""».
1.4. Типы
Большинство распространенных языков программирования поддерживают различные типы данных, но их реализация зависит от конкретной аппаратной архитектуры. В языке С++ существует поддержка следующих встроенных типов, размеры которых приведены для 64-разрядной машины семейства x86.
В языке С++ существует оператор «sizeof», который поваляет определить размер типа на конкретной аппаратной архитектуре. Так размер, например, типа «float» может быть определен посредством оператора «sizeof(float)».
Каждый из приведенных типов может быть знаковым, что задается по умолчанию или при помощи спецификатора «signed», или беззнаковым, если используется спецификатор «unsigned». В случае, если переменная задана, как беззнаковая, ее старший бит, отвечающий за знак, освободится для значения. Так диапазон значений «short» лежит в пределах от -32768 до 32767, а «unsigned short» – от 0 до 65535.
Таблица 1.2. Встроенные типы.
Тип | Размер | Значение | Приоритет |
long double | Дробное | Старший | |
double | Дробное | ||
float | Дробное | ||
long long | Целое | ||
long | Целое | ||
int | Целое | ||
short | Целое | ||
wchar_t | Символьное | ||
char | Символьное | ||
bool | Логическое | Младший |
Типы «short», «int», «long», а также тип «long» увеличенной разрядности представляют числовые типы, типы «float», «double» и «double» увеличенной разрядности представляют дробные числа, тип «char» и «whar_t» – символьные, интерпретируемые как код некоторого символа, а «bool» – логический, принимающей одно из двух значений «true» или «false». Тип «wchar_t» был унаследован из языка С, где не являлся базовым.
В языке С++ существует еще один тип – «void». Он является базовым, однако объектов этого типане существует, в связи с чем используется он только для определения функции, как не возвращающей какого-либо значения или непринимающей каких-либо параметров, или для задания типа указателя на объекты неизвестного типа.
1.5. Переменные
Переменные являются фундаментальной частью любого языка. Каждая из них имеет свои имя и значение. Имя переменной адресует закрепленный за ней участок памяти, и когда переменной присваивается новое значение, оно записывается в этот участок. Связывание идентификатора с выделяемой областью памяти называется объявлением, а если при этом в память заносится первоначальное значение – определением или инициализацией, при этом, если попытаться получить значение переменной до того, как ей будет впервые присвоено значение, результатом будет то, что находилось в закрепленным за переменной участке памяти до ее объявления. Если выделение памяти для переменной происходит во время выполнения программы, то оно называется динамическим, если же выделение происходит на этапе компиляции, – статическим.
Переменная может быть объявлена в каком-либо классе, в функции или пространстве имен. Если переменная объявлена в пространстве имен, но вне класса, то она является глобальной для всех классов и функций, объявленных в томже пространстве имен. Если же переменная объявлена, например, внутри функции, то она называется локальной, и внешнее к ней обращение невозможно. Таким образом, область видимости переменной может быть глобальной или локальной.
Важным параметром переменной является ее время жизни. Оно начинается с момента объявления переменной и длиться до конца блока операций, заключенных в фигурные скобки, в которых объявлена и сама переменная, или до конца выполнения программы, в зависимости от спецификатора, выделяющего память одного из четырех классов. Спецификатор «auto» используется по умолчанию, без необходимости его явного указания, и задает время жизни до конца блока операций. Спецификатор «register» позваляет сохранять переменную не в оперативной памяти, а в свободном регистре. В случае, если свободного регистра нет, переменная станет «auto». Спецификатор «extern» сообщает компилятору о том, что переменная будет объявлена (уже без спецификатора «extern») в другом файле. Спецификатор «static» увеличивает время жизни до конца выполнения программы. Так для переменной, объявленной как «static» в функции, память выделиться только один раз – при первом вызове функции, а сама переменная будет сохранять свое значение между вызовами. Еще одним эффектом применения данного спецификатора является инициализация переменной нулевым значением и ограничение ее видимости текущим файлом.
1.6. Преобразование типов
При работе с переменными часто возникают ситуации, когда переменной одного типа необходимо присвоить значение переменной другого типа. В большинстве случаев компилятор корректно выполняет преобразование типов, и нет необходимости во вмешательстве программиста. Такие случаи называются неявным преобразованием и выполняются в соответствии с приоритетами типов.
Особым случаем при неявном преобразовании можно считать преобразование с участием типа «bool». Переменные этого типа, имеющие значение «true», преобразуются к «1», а «false» – к значению «0», при этом любое значение, отличное от «0», будет интерпретироваться как «true», а нулевое как «false». Еще одним особым случаем является преобразование с участием типа «char». При преобразовании его к любому числовому типу, переменная получит в качестве результата – ASCII – код символа, хранящегося в переменной типа «char», а при обратном преобразовании число будет интерпретироваться как ASCII – код.
При использовании макроопределений проверка соответствия типов не выполняется вовсе, по этому и рекомендуется пользоваться «const» – переменными.
Явное преобразование типов используется, в основном, при работе с пользовательскими типами данных, когда компилятор не в состоянии выполнить преобразование автоматически. При таком преобразовании используются операторы «const_cast», «static_cast», «reinterpret_cast» и «dynamic_cast». Конструкция преобразования типов языка С, где тот тип, к которому требуется преобразовать, указывался в круглых скобках перед выражением, также допустима, но не рекомендуется к применению в С++. Оператор «const_cast» позволяет отменить признак постоянства. Оператор «static_cast» используется, если преобразование поддерживается языковыми средствами напрямую. Так в случае деления целочисленных переменных результат также является целочисленным, чего и помогает избежать «static_cast». Оператор «reinterpret_cast» является более мощным по сравнению со «static_cast», но и менее безопасным, и применяется в случаях, когда формально преобразование неразрешено, но имеет определенный смысл, как при преобразовании типов указателей и ссылок. Оператор «dynamic_cast» обеспечивает динамический контроль типов при работе с классами, но применим и к обычным ссылкам и указателям.
Листинг 1.2. Использование static_cast.
#include <iostream>
using std::cout;
int main (void)
{
float z;
z = (10 - 2) / 3;
cout << z << "\n";
z = static_cast<float>(10 - 2) / 3;
cout << z << "\n";
return 0;
}
1.7. Константы
При инициализации переменных и в ряде других случаев могут использоваться константы. Целочисленные константы могут задаваться в различных системах счисления. Десятичные числа записываются цифрами обычным образом. Перед шестнадцатеричными ставится символы «0х», а восьмеричные — нулем. Дробные константы состоят из целой части, разделяющей точки, дробной части, символа «е» или «Е» и экспоненты в виде целой константы. Так запись «3.5e2» равносильна записи «350.0». Строковые константы записываются в двойных кавычках «""», а символьные — в одинарных «''». Символьными константами называются также и управляющие последовательности, специальные символы, регулирующие вывод информации на экран. В примере – это символ перевода строки «\n». Так как символы «"», «'» и «\» используются в служебных целях, то их непосредственный вывод осуществляется при помощи экранирования символом «\». Также, с помощью «\» можно выводить символы, используя их ASCII – код: «\xHH», где «HH» – это шестнадцатеричный код требуемого символа.
Таблица 1.3. Специальные символы.
Символ | Описание | Символ | Описание |
\a | Сигнал | \'' | Двойные кавычки |
\b | Возврат на шаг | \' | Одинарные кавычки |
\n | Перевод строки | \\ | Обратная косая черта |
\r | Возврат в начало строки | \xHH | Символ с ASCII – кодом HH |
\t | Табуляция |
При задании констант можно явным образом определять их тип, указывая после значения определенный символ. Для литерала «long» – это «L» или «l», для «unsigned» – это – «U» или «u», для типа «float» – «F» или «f».
В случае, если в программе не должно происходить изменения значения какой-либо переменной, ее объявляют как «const». В таком случае попытка изменить ее вызовет ошибку компиляции. В языке С для этого использовались макроопределения директивой «#define», что сохранилось и в С++, однако такой подход несет в себе потенциальные проблемы, связанные с преобразованиями типов, и затрудняет последующее сопровождение кода.
Унаследованным из языка С является особый тип констант «enum» – перечисление. Он представляет из себя список целочисленных констант. Если значения констант не заданы, то первая приравнивается к нулю, следующая – к единице и т.д.