Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Данные в классах исключений и присвоение имен объектам исключений
Часто для того, чтобы программа могла отреагировать должным образом на ошибку, полезно знать несколько больше, чем просто тип возникшего исключения. Классы исключений — это такие же классы, как и любые другие. Вы абсолютно свободно можете добавлять любые данные в эти классы, инициализировать их с помощью конструктора и считывать их значения в любое время, как показано в листинге 20.4. Листинг 20.4. возвращение данных из объекта исключения 1: #include < iostream.h> 2: 3: const int DefaultSize = 10; 4: 5: class Array 6: { 7: public: 8: // конструкторы 9: Array(int itsSize = DefaultSize); 10: Array(const Array & rhs); 11: ~Array() { delete [] pType; } 12: 13: // операторы 14: Array& operator=(const Array& ); 15: int& operator[](int offSet); 16: const int& operator[](int offSet) const; 17: 18: // методы доступа 19: int GetitsSize() const { return itsSize; } 20: 21: // функция-друг 22: friend ostream& operator< < (ostream&, const Array& ); 23: 24: // определение классов исключений 25: class xBoundary { }; 26: class xSize 27: { 28: public: 29: xSize(int size): itsSize(size) { } 30: ~xSize(){ } 31: int GetSize() { return itsSize; } 32: private: 33: int itsSize; 34: }; 35: 36: class xTooBig: public xSize 37: { 38: public: 39: xTooBig(int size): xSize(size){ } 40: }; 41: 42: class xTooSmall: public xSize 43: { 44: public: 45: xTooSmall(int size): xSize(size){ } 46: }; 47: 48: class xZero: public xTooSmall 49: { 50: public: 51: xZero(int size): xTooSmall(size){ } 52: }; 53: 54: class xNegative: public xSize 55: { 56: public: 57: xNegative(int size): xSize(size){ } 58: }; 59: 60: private: 61: int *pType; 62: int itsSize; 63: }; 64: 65: 66: Array:: Array(int size): 67: itsSize(size) 68: { 69: if (size == 0) 70: throw xZero(size); 71: if (size > 30000) 72: throw xTooBig(size); 73: if (size < 1) 74: throw xNegative(size); 75: if (size < 10) 76: throw xTooSnall(size); 77: 78: pType = new int[size]; 79: for (int i = 0; i< size; i++) 80: pType[i] = 0; 81: } 82: 83: 84: int& Array:: operator[] (int offSet) 85: { 86: int size = GetitsSize(); 87: if (offSet > = 0 & & offSet < GetitsSize()) 88: return pType[offSet]; 89: throw xBoundary(); 90: return pType[0]; 91: } 92: 93: const int& Array:: operator[] (int offSet) const 94: { 95: int size = GetitsSize(); 96: if (offSet > = 0 & & offSet < GetitsSize()) 97: return pType[offSet]; 98: throw xBoundary(); 99: return pType[0]; 100: } 101: 102: int main() 103: { 104: 105: try 106: { 107: Array intArray(9); 108: for (int j = 0; j< 100; j++) 109: { 110: intArray[j] = j; 111: cout < < " intArray[" < < j < < " ] okay..." < < endl; 112: } 113: } 114: catch (Array:: xBoundary) 115: { 116: cout < < " Unable to process your input! \n"; 117: } 118: catch(Array:: xZero theException) 119: { 120: cout < < " You asked for an Array of zero objectsl " < < endl; 121: cout < < " Received " < < theExesptiQn, GatSize() < < endl; 122: } 123: catch (Array:; xTooBig theException) 124: { 125: cout < < " This Array is too big,,, " < < endl; 126: cout < < " Received " < < theException, GetSize() < < endl; 127: } 128: catch (Array;: xTooSmall theException) 129: { 130: cout < < " This Array is too small... " < < endl; 131: cout < < " Received " < < theException.GetSize() < < endl; 132: } 133: catch (...) 134: { 135: cout < < " Something went wrong, but I've no idea what! \n"; 136: } 137: cout < < " Done.\n"; 138: return 0; 139: }
Результат: This array is too small... Received 9 Done.
Анализ: Объявление класса xSize было изменено таким образом, чтобы включить в него переменную-член itsSize (строкаЗЗ) и функцию-член GetSize() (строка 31). Кроме того, был добавлен конструктор, который принимает целое число и инициализирует переменную-член, как показано в строке 29. Производные классы объявляют конструктор, который лишь инициализирует базовый класс. При этом никакие другие функции объявлены не были (частично из экономии места в листинге). Операторы catch в строках 114-136 изменены таким образом, чтобы создавать именованный объект исключения (thoException), который используется в теле блока catch для доступа к данным, сохраняемым в переменной-члене itsSize.
Примечание: При работе с исключениями следует помнить об их сути: если уж оно возникло, значит, что-то не в порядке с распределением ресурсов, и обработку этого исключения нужно записать таким образом, чтобы вновь не создать ту же проблему. Следовательно, если вы создаете исключение OutOfMemory, то не стоит а конструкторе этого класса пытаться выделить память для какого-либо объекта.
Весьма утомительно писать вручную все эти конструкции с операторами oatch, каждый из которых должен выводить свое сообщение. Тем более, что при увеличении объема программы стремительно возрастает вероятность возникновения в ней ошибок. Лучше переложить эту работу на объект исключения, который сам должен определять тип исключения и выбирать соответствующее сообщение. В листинге 20.5 для решения этой проблемы использован подход, который в большей степени отвечает принципам объектно-ориентированного программирования. В классах исключений применяются виртуальные функции, обеспечивающие полиморфизм объекта исключения. Листинг 20.5. Передача аргументов как ссылок u использование виртуальных функций в классах исключений 1: #include < iostream.h> 2: 3: const int DefaultSize = 10; 4: 5: class Array 6: { 7: public: 8: // конструкторы 9: Array(int itsSize = DefaultSize); 10: Array(const Array & rhs); 11: ~Array() { delete [] pType; } 12: 13: // операторы 14: Array& operator=(const Array& ); 15: int& operator[](int offSet); 16: const int& operator[](int offSet) const; 17: 18: // методы доступа 19: int GetitsSize() const { return itsSize; } 20: 21: // функция-друг 22: friend ostream& operator< < 23: (ostream&, const Array& ); 24: 25: // определение классов исключений 26: class xBoundary { }; 27: class xSize 28: { 29: public: 30: xSize(int size): itsSize(size) { } 31: ~xSize(){ } 32: virtual int GetSize() { return itsSize; } 33: virtual void PrintError() 34: { 35: cout < < " Size error. Received: "; 36: cout < < itsSize < < endl; 37: } 38: protected: 39: int itsSize; 40: }; 41: 42: class xTooBig: public xSize 43: { 44: public: 45: xTooBig(int size): xSize(size){ } 46: virtual void PrintError() 47: { 48: cout < < " Too big. Received: "; 49: cout < < xSize:: itsSize < < endl; 50: } 51: }; 52: 53: class xTooSmall: public xSize 54: { 55: public: 56: xTooSmall(int size): xSize(size){ } 57: virtual void PrintError() 58: { 59: cout < < " Too small. Received: "; 60: cout < < xSize:: itsSize < < endl; 61: } 62: }; 63: 64: class xZero: public xTooSmall 65: { 66: public: 67: xZero(int size): xTooSmall(size){ } 68: virtual void PrintError() 69: { 70: cout < < " Zero!. Received: " ; 71: cout < < xSize:: itsSize < < endl; 72: } 73: }; 74: 75: class xNegative: public xSize 76: { 77: public: 78: xNegative(int size): xSize(size){ } 79: virtual void PrintError() 80: { 81: cout < < " Negative! Received: "; 82: cout < < xSize:: itsSize < < endl; 83: } 84: }; 85: 86: private: 87: int *pType; 88: int itsSize; 89: }; 90: 91: Array:: Array(int size): 92: itsSize(size) 93: { 94: if (size == 0) 95: throw xZero(size); 96: if (size > 30000) 97: throw xTooBig(size); 98: if (size < 1) 99: throw xNegative(size); 100: if (size < 10) 101: throw xTooSmall(size); 102: 103: pType = new int[size]; 104: for (int i = 0: i< size; i++) 105: pType[i] = 0; 106: } 107: 108: int& Array:: operator[] (int offSet) 109: { 110: int size = GetitsSize(); 111: if (offSet > = 0 & & offSet < GetitsSize()) 112: return pType[offSet]; 113: throw xBoundary(); 114: return pType[0]; 115: } 116: 117: const int& Array:: operator[] (int offSet) const 118: { 119: int size = GetitsSize(); 120: if (offSet > = 0 & & offSet < GetitsSize()) 121: return pType[offSet]; 122: throw xBoundary(); 123: return pType[0]; 124: } 125: 126: int main() 127: { 128: 129: try 130: { 131: Array intArray(9); 132: for (int j = 0: j< 100; j++) 133: { 134: intArray[j] - j; 135: cout < < " intArray[" < < j < < " ] okay...\n"; 136: } 137: } 138: catch (Array:: xBoundary) 139: { 140: cout < < " Unable to process your input! \n"; 141: } 142: catch (Array;: xSize& theExoeption) 143: { 144: theException.PrintError(); 145: } 146: catch (...) 147: { 148: cout < < " Something went wrong! \n"; 149: } 150: cout < < " Done.\n"; 151: return 0; 152: }
Результат: Too small! Received: 9 Done.
Анализ: В листинге 20.5 показано объявление виртуального метода PrintError() в классе xSize, который выводит сообщения об ошибках и истинный размер класса. Этот метод замешается в каждом производном классе исключения. В строке 142 объявляется объект исключения, который является ссылкой. При вызове функции PrintError() со ссылкой на объект благодаря полиморфизму вызывается нужная версия функции PrintError(). В результате программный код становится яснее, проще для понимания, а следовательно, и для дальнейшей поддержки.
Исключения и шаблоны
При создании исключений, предназначенных для работы с шаблонами, есть два варианта решений. Можно создавать исключение прямо в шаблоне, и тогда они будут доступны для каждого экземпляра шаблона, а можно использовать классы исключений, созданные вне объявления шаблона. Оба этих подхода показаны в листинге 20.6. Листинг 20.6. Использование исключений с шаблонами 1: #include < iostream.h> 2: 3: const int DefaultSize = 10; 4: class xBoundary { }; 5: 6: template < class T> 7: class Array 8: { 9: public: 10: // конструкторы 11: Array(int itsSize = DefaultSize); 12: Array(const Array & rhs); 13: ~Array() { delete [] pType; } 14: 15: // операторы 16: Array& operator=(const Array< T> & ); 17: T& operator[](int offSet); 18: const T& operator[](int offSet) const; 19: 20: // методы доступа 21: int GetitsSize() const { return itsSize; } 22: 23: // функция-друг 24: friend ostream& operator< < (ostream&, const Array< T> & ); 25: 26: // определение классов исключений 27: 28: class xSize { }; 29: 30: private: 31: int *pType; 32: int itsSize; 33: }; 34: 35: template < class T> 36: Array< T>:: Array(int size): 37: itsSize(size) 38: { 39: if (size < 10 || size > 30000) 40: throw xSize(); 41: рТуре = new T[size]; 42: for (int i = 0; i< size; i++) 43: pType[i] = 0; 44: } 45: 46: template < class T> 47: Array< T> & Array< T>:: operator=(const Array< T> & rhs) 48: { 49: if (this == & rhs) 50: return *this; 51: delete [] рТуре; 52: itsSize = rhs.GetitsSize(); 53: рТуре = new T[itsSize]; 54: for (int i = 0; i< itsSize; i++) 55: pType[i] = rhs[i]; 56: } 57: template < class T> 58: Array< T>:: Array(const Array< T> & rhs) 59: { 60: itsSize = rhs.GetitsSize(); 61: рТуре = new T[itsSize]; 62: for (int i = 0; i< itsSize; i++) 63: pType[i] = rhs[i]; 64: } 65: 66: template < class T> 67: T& Array< T>:: operator[](int offSet) 68: { 69: int size = GetitsSize(); 70: if (offSet > = 0 & & offSet < GetitsSize()) 71: return pType[offSet]; 72: throw xBoundary(): 73: return pType[0]; 74: } 75: 76: template < class T> 77: const T& Array< T>:: operator[](int offSet) const 78: { 79: int mysize = GetitsSize(); 80: if (offSet > = 0 & & offSet < GetitsSize()) 81: return pType[offSet]; 82: throw xBoundary(); 83: } 84: 85: template < class T> 86: ostream& operator< < (ostream& output, const Array< T> & theArray) 87: { 88: for (int i = 0; i< theArray, GetitsSize(); i++) 89: output < < " [" < < i < < " ] " < < theArray[i] < < endl; 90: return output; 91: } 92: 93: 94: int main() 95: { 96: 97: try 98: { 99: Array< int> intArray(9); 100: for (int j = 0; j< 100; j++) 101: { 102: intArray[j] = j; 103: cout < < " intArray[" < < j < < " ] okay..." < < endl; 104: } 105: } 106: catch (xBoundary) 107: { 108: cout < < " Unable to process your input! \n"; 109: } 110: catch (Array< int>:: xSize) 111: { 112: cout < < " Bad Size! \n"; 113: } 114: 115: cout < < " Done.\n"; 116: return 0; 117: }
Результат: You asked for an array of zero objects! Done
Анализ: Первое исключение, xBoundary, объявлено вне определения шаблона в строке 4; второе исключение, xSize, — внутри определения шаблона в строке 28. Исключение xBoundary не связано с классом шаблона, но его можно использовать так же, как и любой другой класс. Исключение xSize связано с шаблоном и должно вызываться для экземпляра класса Array. Обратите внимание на разницу в синтаксисе двух операторов catch. Строка 106 содержит выражение catch (xBoundary), а строка 110 — выражение catch (Array< int>:: xSize). Второй вариант связан с обращением к исключению экземпляра целочисленного массива.
Исключения без ошибок
Когда программисты C++ после работы собираются за чаркой виртуального пива в баре киберпространства, в их задушевных беседах часто затрагивается вопрос, можно ли использовать исключения не только для отслеживания ошибок, но и для выполнения рутинных процедур. Есть мнение, что использование исключений следует ограничить только отслеживанием предсказуемых исключительных ситуаций, для чего, собственно, исключения и создавались. В то же время другие считают, что исключения предоставляют эффективный способ возврата сквозь несколько уровней вызовов функций, не подвергаясь при этом опасности утечки памяти. Чаще всего приводится следующий пример. Пользователь формирует запрос на некоторую операцию в среде GU1 (графический интерфейс пользователя). Часть кода, которая перехватывает этот запрос, должна вызвать функцию-член менеджера диалоговых окон, которая, в свою очередь, вызывает код, обрабатывающий этот запрос. Этот код вызывает другой код, который решает, какое диалоговое окно использовать, и, в свою очередь, вызывает код, чтобы отобразить на экране это диалоговое окно. И теперь уже этот код наконец-то вызывает другой код, который обрабатывает данные, вводимые пользователем. Если пользователь щелкнет на кнопке Cancel (Отменить), код должен возвратиться к самому первому вызывающему методу, где обрабатывался первоначальный запрос. Один подход к решению этой проблемы состоит в том, чтобы поместить блок try сразу за тем блоком программы, где формируется исходный запрос, и перехватывать объект исключения CancelDialog, который генерируется обработчиком сообщений для кнопки Cancel. Это безопасно и эффективно, хотя щелчок на кнопке Cancel по сути своей не относится к исключительной ситуации. Чтобы решить, насколько правомочно такое использование исключений, попытайтесь ответить на следующие вопросы: станет ли в результате программа проще или, наоборот, труднее для понимания; действительно ли уменьшится риск возникновения ошибок и утечки памяти; труднее или проще теперь станет поддержка такой программы? Безусловно, объективно ответить на эти вопросы сложно: многое зависит от привычек и субъективных взглядов программиста, из-за чего, кстати, и возникают споры вокруг этих вопросов.
Ошибки и отладка программы
Почти все современные среды разработки содержат один или несколько встроенных эффективных отладчиков. Основная идея использования отладчика такова: отладчик загружает и выполняет исходный код программы в режиме, удобном для отслеживания выполнения отдельных строк программы и выявления ошибок. Все компиляторы позволяют компилировать программы с использованием символов или без них. Компилирование с символами указывает компилятору на необходимость установки взаимосвязей между исходным кодом файлов источников и сгенерированной программой, благодаря чему отладчик может указать на строку исходного кода, которая соответствует следующему действию в вашей программе. Полноэкранные символьные отладчики превосходно справляются с этой сложной работой. После загрузки отладчик считывает весь исходный код программы и отображает его в окне. Отладчик позволяет проходить в пошаговом режиме через все строки программы в порядке их выполнения. При работе с большинством отладчиков можно переключаться между исходным кодом и выводом на экран, чтобы видеть результаты выполнения каждой команды. Полезной также является возможность определения текущего значения любой переменной, в том числе переменных-членов классов и значений в ячейках области динамического обмена, на которые ссылаются указатели программы, а также просмотр сложных структур данных. Отладчики предоставляют ряд утилит, позволяющих устанавливать в коде программы точки останова, выводить контрольные значения переменных, исследовать особенности распределения памяти и просматривать код ассемблера. Точка останова
Точки останова — это команды, предназначенные для отладчика и означающие, что программа должна остановиться перед выполнением указанной строки. Это средство позволяет экономить время при отладке, выполняя программу в обычном режиме до того места, где установлена точка останова. После остановки выполнения программы можно проанализировать текущие значения переменных или продолжить работу программы в пошаговом режиме.
Анализ значений переменных
Можно указать отладчику на отображение значения конкретной переменной или на останов программы, когда заданная переменная будет читаться или записываться. Отладчик даже позволяет изменить значение переменной в процессе выполнения программы.
Исследование памяти
Время от времени важно просматривать реальные значения, содержащиеся в памяти. Современные отладчики могут отображать эти значения в понятном для пользователя виде, т.е. строки отображаются как символы, а числовые значения — как десятичные цифры, а не в двоичном коде. Современные отладчики C++ могут даже показывать целые классы с текущими значениями всех переменных-членов, включая указатель this.
Код ассемблера
Хотя чтения исходного кода иногда бывает достаточно для обнаружения ошибки, тем не менее можно указать отладчику на отображение реального кода ассемблера, сгенерированного для каждой строки исходного кода. Вы можете просмотреть значения регистраторов памяти и флагов и при желании настолько углубиться в дебри машинного кода, насколько нужно. Научитесь пользоваться своим отладчиком. Это может оказаться самым мощным оружием в вашей священной войне с ошибками. Ошибки выполнения программы считаются наиболее трудными для поиска и устранения, и мощный отладчик в состоянии помочь вам в этом.
Резюме
Сегодня вы узнали, как создавать и использовать исключения, т.е. объекты, которые могут быть созданы в тех местах программы, где исполняемый код не может обработать ошибку или другую исключительную ситуацию, возникшую во время выполнения программы. Другие части программы, расположенные выше в стеке вызовов, выполняют блоки catch, которые перехватывают исключение и отвечают на возникшую исключительную ситуацию соответствующим образом. Исключения — это нормальные созданные пользователем объекты, которые можно передавать в функции как значения или как ссылки. Они могут содержать данные и методы, а блок catch может использовать эти данные, чтобы определить, как справиться с возникшими проблемами. Можно создать конструкции из нескольких блоков catch, но следует учитывать, что, как только исключение будет перехвачено отдельным оператором catch, оно не будет передаваться последующим блокам catch. Очень важно правильно упорядочить блоки catch, чтобы специфические блоки стояли выше более общих блоков. На этом занятии также рассматривались некоторые основные принципы работы символьных отладчиков, включая использование таких средств, как точки останова, анализ значений переменных и т.д. Эти средства позволяют выполнить останов программы в той части, которая вызывает появление ошибки, и просмотреть значения переменных в ходе программы.
Вопросы и ответы
Зачем тратить время на программирование исключений? Не лучше ли устранять ошибки по мере их возникновения? Часто одна и та же ошибка может возникать при выполнении разных функций программы. Использование исключений позволяет собрать коды отслеживания ошибок в одном месте программы. Кроме того, далеко не всегда возможно вписать код устранения ошибки в том месте программы, где эта ошибка возникает. Зачем создавать исключения как объекты? Не проще ли записать код устранения ошибки? Объекты более гибки и универсальны в использовании, чем обычные программные блоки. С объектами можно передать больше информации и снабдить конструктор и деструктор класса исключения функциями устранения возникшей ошибки. Почему бы не использовать исключения не только для отслеживания исключительных ситуаций, но и для выполнения рутинных процессов? Разве не удобно использовать исключения для быстрого и безопасного возвращения по стеку вызовов к исходному состоянию программы? Безусловно, и многие программисты на C++ используют исключения именно в этих целях. Но следует помнить, что прохождение исключения по стеку вызовов может оказаться не таким уж безопасным. Так, если объект был создан в области динамического обмена, а потом удален в стеке вызовов, это может привести к утечке памяти. Впрочем, при тщательном анализе программы и использовании современного компилятора эту проблему можно предупредить. Кроме того, многие программисты считают, что использование исключений не по прямому назначению делает программу слишком запутанной и нелогичной. Всегда ли следует перехватывать исключения сразу за блоком try, генерирующим это исключение? Нет, в стеке вызовов перехват исключения может осуществляться в любом месте, после чего стек вызовов будет пройден то того места, где происходит обработка исключения. Зачем использовать утилиту отладки, если те же функции можно осуществлять прямо во время компиляции с помощью объекта cout и условного выражения #ifdef debug? В действительности утилита отладки предоставляет значительно больше средств и возможностей, таких как пошаговое выполнение программы, установка точек останова и анализ текущих значений переменных. При этом вам не приходится перегружать свой код многочисленными командами препроцессора и выражениями, которые никак не связаны с основным назначением программы.
Коллоквиум
В этом разделе предлагаются вопросы для самоконтроля и укрепления полученных знаний, а также ряд упражнений, которые помогут закрепить ваши практические навыки. Попытайтесь самостоятельно ответить на вопросы теста и выполнить задания, а потом сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, если для вас остались неясными хотя бы некоторые из предложенных ниже вопросов. Контрольные вопросы
1. Что такое исключение? 2. Для чего нужен блок try? 3. Для чего используется оператор catch? 4. Какую информацию может содержать исключение? 5. Когда создается объект исключения? 6. Следует ли передавать исключения как значения или как ссылки? 7. Будет ли оператор catch перехватывать производные исключения, если он настроен на базовый класс исключения? 8. Если используются два оператора catch, один из которых настроен на базовое сообщение, а второй — на производное, то в каком порядке их следует расположить? 9. Что означает оператор catch(...)? 10. Что такое точка останова?
Упражнения
1. Запишите блок try и оператор catch для отслеживания и обработки простого исключения. 2. Добавьте в исключение, полученное в упражнении 1, переменную-член и метод доступа и используйте их в блоке оператора catch. 3. Унаследуйте новое исключение от исключения, полученного в упражнении 2. Измените блок оператора catch таким образом, чтобы в нем происходила обработка как производного, так и базового исключений. 4. Измените код упражнения 3, чтобы получить трехуровневый вызов функции. 5. Жучки: что не правильно в следующем коде? #include " string" //класс строк сlass xOutOfMemory { public: xOutOfMemory(){ theMsg = new сhar[20]; strcpy(theMsg, " trror in momory" ); } ~xOutOfMemory(){ delete [] theMsg; cout < < " Memory restored, " < < endl; } char * Message() { return theMsg; } private: char > > theMsg; }; main() { try { char * var = new char; if ( var == 0 ) { xOutOfMemory * px = new xOutOfMemory; throw px; } } catch( xOutOfMemory * theException ) { cout < < theException-> Message() < < endl; delete theException; } return 0; }
6. Данный пример содержит потенциальную ошибку, подобную возникающей при попытке выделить память для показа сообщения об ошибке в случае обнаружения нехватки свободной памяти. Вы можете протестировать эту программу, изменив строку if (var == 0) на if (1), которая вызовет создание исключения.
День 21-й. Что дальше
Примите наши поздравления! Вы почти завершили изучение полного трехнедельного интенсивного курса введения в C++. К этому моменту у вас должно быть ясное понимание языка C++, но в современном программировании всегда найдутся еще не изученные области. В этой главе будут рассмотрены некоторые опущенные выше подробности, а затем намечен курс для дальнейшего освоения C++. Большая часть кода файлов источника представлена командами на языке C++. Компилятор превращает этот код в программу на машинном языке. Однако перед запуском компилятора запускается препроцессор, благодаря чему можно воспользоваться возможностями условной компиляции. Итак, сегодня вы узнаете: • Что представляет собой условная компиляция и как с ней обращаться • Как записывать макросы препроцессора • Как использовать препроцессор для обнаружения ошибок • Как управлять значениями отдельных битов и использовать их в качестве флагов • Какие шаги следует предпринять для дальнейшего эффективного изучения C++ Процессор и компилятор
При каждом запуске компилятора сначала запускается препроцессор, который ищет команды препроцессора, начинающиеся с символа фунта (#). При выполнении любой из этих команд в текст исходного кода вносятся некоторые изменения, в результате чего создается новый файл исходного кода. Этот новый файл является временным, и вы обычно его не видите, но можете дать команду компилятору сохранить его для последующего просмотра и использования. Компилятор читает не исходный файл источника, а результат работы препроцессора и компилирует его в исполняемый файл программы. Вам уже приходилось встречаться с директивой препроцессора #include: она предписывает найти файл, имя которого следует за ней, и вставить текст этого файла по месту вызова. Этот эффект подобен следующему: вы полностью вводите данный файл прямо в свою исходную программу, причем к тому времени, когда компилятор получит исходный код, файл будет уже на месте.
Популярное:
|
Последнее изменение этой страницы: 2017-03-08; Просмотров: 818; Нарушение авторского права страницы