Главная Рефераты по авиации и космонавтике Рефераты по административному праву Рефераты по безопасности жизнедеятельности Рефераты по арбитражному процессу Рефераты по архитектуре Рефераты по астрономии Рефераты по банковскому делу Рефераты по сексологии Рефераты по информатике программированию Рефераты по биологии Рефераты по экономике Рефераты по москвоведению Рефераты по экологии Краткое содержание произведений Рефераты по физкультуре и спорту Топики по английскому языку Рефераты по математике Рефераты по музыке Остальные рефераты Рефераты по биржевому делу Рефераты по ботанике и сельскому хозяйству Рефераты по бухгалтерскому учету и аудиту Рефераты по валютным отношениям Рефераты по ветеринарии Рефераты для военной кафедры Рефераты по географии Рефераты по геодезии Рефераты по геологии Рефераты по геополитике Рефераты по государству и праву Рефераты по гражданскому праву и процессу Рефераты по кредитованию Рефераты по естествознанию Рефераты по истории техники Рефераты по журналистике Рефераты по зоологии Рефераты по инвестициям Рефераты по информатике Исторические личности Рефераты по кибернетике Рефераты по коммуникации и связи Рефераты по косметологии Рефераты по криминалистике Рефераты по криминологии Рефераты по науке и технике Рефераты по кулинарии Рефераты по культурологии |
Статья: Переход от С к С++Статья: Переход от С к С++Малышев Сергей Михайлович Хочу сразу же сказать, что эта статья отнюдь не претендует на полные и безоговорочные рекомендации по переходу от С к С++. Тут даны лишь некоторые из очень многочисленных и может быть наиболее распространенные из них. Итак, к делу... Для того чтобы освоиться с C++, необходимо некоторое время. Поскольку С является, по существу, подмножеством C++, все его старые "трюки" остаются в силе, но многие из них теряют свою значимость. Так, например, для программистов на C++ выражение "указатель на указатель" звучит немного забавно. Почему вместо указателя не была использована просто ссылка? С - достаточно простой язык. Макросы, указатели, структуры, массивы и функции - это почти все, что он в действительности предлагает. Каким бы сложным ни оказался алгоритм, его всегда можно реализовать, используя перечисленный набор средств. В C++ дело обстоит несколько иначе: наравне с макросами, указателями, структурами, массивами и функциями используются закрытые и защищенные члены классов, перегрузка функций, аргументы по умолчанию, конструкторы и деструкторы. Операции, определяемые пользователем, встроенные функции, ссылки, дружественные классы и функции, шаблоны, исключения, пространства имен и т. д. Очевидно, что более богатые средства проектирования предоставляют и более широкие возможности, а это в свою очередь, требует существенно иной культуры программирования. Столкнувшись с таким разнообразием выбора, многие теряются, продолжая крепко держаться за то, к чему они привыкли. По большей части в этом нет особого греха, но некоторые "привычки" из С идут вразрез с духом C++. От них просто необходимо избавиться! Давайте рассмотрим две наиболее частые и стойкие (на мой взгляд и собственный опыт) "привычки" из С - это использование директивы #define и функций scanf/printf. Предпочитайте const и inline использованию #define Этот правило лучше было бы назвать "Компилятор предпочтительнее препроцессора", поскольку #define зачастую вообще не относят к языку C++. В этом и заключается одна из проблем. Рассмотрим простой пример; попробуйте написать что-нибудь вроде: #define ASPECT_RATIO 1.653 Символическое обозначение может так и остаться неизвестным компилятору или быть удалено препроцессором, прежде чем код попадет в компилятор. Если это произойдет, то обозначение ASPECT_RATIO не окажется в таблице символов. Поэтому в ходе компиляции вы получите ошибку, связанную с использованием константы (в сообщении об ошибке будет сказано 1.653, а не ASPECT_RATIO). Это вызовет путаницу. Если файл заголовков писали не вы, а кто-либо другой, у вас не будет никакого представления о том, откуда взялось значение 1.653, и на поиски ответа вы потеряете много времени. Та же проблема может возникнуть и при отладке, поскольку обозначение, выбранное вами, будет отсутствовать в таблице символов. Указанная задача решается просто и быстро. Вместо использования макроса препроцессора определите константу: const double ASPECT_RATIO = 1.653; Однако есть два специальных случая, заслуживающих упоминания. Во-первых, при определении константных указателей могут возникнуть некоторые осложнения. Поскольку определения констант обычно выносятся в заголовочные файлы (где к ним получает доступ множество различных исходных файлов), важно, чтобы сам указатель был объявлен с const, в дополнение к объявлению const того, на что он указывает. Например, для определения в файле заголовков константной строки char* следует писать const дважды: const char* const constantString = "String is constant"; Во-вторых, иногда удобно определять некоторые константы, как относящиеся к конкретным классам, а это требует другого подхода. Для того чтобы ограничить область действия константы конкретным классом, необходимо сделать ее членом этого класса, а чтобы гарантировать, что существует только одна копия константы, требуется сделать ее статическим (static) членом класса: class GamePlayer { private: static const int NUM_TURNS = 5; // Объявление константы int scores[NUM_TURNS]; // Использование константы }; Остается еще одна небольшая проблема, поскольку все то, что вы видите выше - это объявление, а не определение NUM_TURNS. Если вам необходимо определить статические члены класса в файле реализации, то напишите следующее: сonst int GamePlayer::NUM_TURNS; // Обязательное объявление // находится в файле реализации Впрочем, терять сон из-за подобных пустяков не стоит. Если об определении забудете вы, то напомнит компоновщик. Старые компиляторы могут не поддерживать принятый здесь синтаксис, так как в более ранних версиях языка было запрещено задавать значения статических членов класса во время их объявления. Более того, инициализация в классе допускалась только для целых типов (таких как int, bool, char и пр.) и для констант. Если вышеприведенный синтаксис не работает, то начальное значение следует задавать в определении: class EngineeringConstants // Это находится в файле заголовка класса. { private: static const double FUDGE_FACTOR; }; А это находится в файле реализации класса: const double EngineeringConstants::FUDGE_FACTOR = 1.35; Единственное исключение обнаруживается тогда, когда для компиляции класса необходима константа. Например, при объявлении массива GamePlayer::scores в листинге, приведенном выше, в момент компиляции может потребоваться задание его размера. Для того чтобы работать с компилятором, ошибочно запрещающим инициализировать целые константы внутри класса, следует применять технику, которая иногда называется "трюком с перечислением". Она основана на том, что переменные перечисляемого типа можно использовать там, где ожидаются целые числа, поэтому GamePlayer определяют следующим образом: class GamePlayer { private: enum { NUM_TURNS = 5 }; //трюк с перечислением - делает из //NUM_TURNS символ со значением 5 int scores[NUM_TURNS]; //теперь нормально }; Если вы имеете дело не с примитивным компилятором, написанным до 1995 года и представляющим собой только исторический интерес, то считайте, что вам скорее всего повезло: необходимость использовать этот трюк вероятно отпадет сама собой. Вернемся к препроцессору. Другой частый случай неправильного использования директивы #define - создание макросов, которые выглядят как функции, но не обременены накладными расходами функционального вызова. Канонический пример - вычисление максимума двух значений: #define max(a, b) ((a)>(b)?(а):(b)) В этой небольшой строчке содержится так много недостатков, что даже не совсем понятно, с какого проще начать. Всякий раз, когда вы пишете макрос подобный этому, необходимо помнить, что все аргументы следует заключать в скобки. В противном случае у пользователей будут возникать серьезные проблемы с применением таких макросов в выражениях. Но, даже если вы все сделаете верно, посмотрите, какие странные (если не сказать - страшные) вещи могут при этом произойти: int а = 5, b = 0; mах(++а, b); //здесь переменная "а" - увеличивается дважды mах(++а, b+10); //а здесь - только 1 раз и это правильно Происходящее внутри mах зависит от того, что с чем сравнивается! Не верится? Проверьте сами. К счастью, вам нет нужды мириться с поведением, так сильно противоречащим привычной логике. Существует метод, позволяющий добиться такой же эффективности, как при использовании макросов. В таком случае обеспечиваются как предсказуемость поведения, так и контроль типов аргументов (что характерно для обычных функций). Этот результат достигается применением встраиваемых функций: inline int max(int a, int b) { return a > b ? a : b; } Новая версия max несколько отличается от предыдущей, поскольку она может работать только с целыми аргументами. Возникшую проблему удачно решает шаблон: template inline const Т& max(const Т& а, const T& b) { return а > b ? а : b; } Он генерирует целое семейство функций, каждая из которых берет два приводимых к одному типу объекта и возвращает ссылку (с модификатором const) на больший из двух объектов. Поскольку вам неизвестно, каким будет тип Т, для эффективности передача и возврат значения происходят по ссылке. Кстати говоря, прежде чем вы решите писать шаблон для какой-либо функции, подобной max, узнайте, не присутствует ли она уже в стандартной библиотеке. В случае с max вы можете воспользоваться плодами чужих усилий: max является частью стандартной библиотеки C++. Предпочитайте использованию О эти операторы sсanf() и printf()! Практически все формы обучения языку С и (увы) С++ начинаются с них. Да, они переносимы. Да, они эффективны. Да, вы уже даже знаете, как их нужно использовать. Но какой бы благоговейный восторг они ни вызывали, факт остается фактом: операторы sсanf() и printf() и им подобные далеки от совершенства. В частности, они не осуществляют контроль типа переменной и к тому же нерасширяемы. Поскольку контроль типов и расширяемость - краеугольные камни идеологии C++, то лучше всего с самого начала во всем опираться на них. Кроме того, семейство функций printf/scanf отделяет переменные, которые необходимо прочитать или записать, от форматирующей информации, управляющей записью и чтением. Неудивительно, что эти слабости функции printf/scanf - сила операторов " и ". int i; Rational r; // r является рациональным числом (класс Rational). cin " i " r; cout " i " r; Если этот код предназначен для компиляции, должны быть в наличии функции operator" и operator", которые могли бы работать с объектом типа Rational. Отсутствие данных функций является ошибкой. (Для int и других стандартных типов имеются стандартные версии этих операторов.) Более того, компилятор берет на себя заботу о том, какие версии операторов вызывать для разных переменных. Таким образом, вам нет необходимости беспокоиться о том, что первый читаемый или записываемый объект имеет тип int, а второй - Rational - с этим разберется компилятор! Кроме того, считывание объектов происходит с использованием той же синтаксической формы, что и при записи. Поэтому нет необходимости помнить о том, что, если вы работаете не с указателем, важно не забыть взять адрес, а если имеете дело с указателем, следует убедиться, что вы не берете адрес. Пусть о таких деталях заботится компилятор C++. Это его дело. У вас же есть задачи посерьезнее. И наконец, заметьте, что встроенные типы, подобные int, читаются и записываются совершенно аналогично типам, определенным пользователями, таким, например, как Rational. Попробуйте сделать то же самое, используя scanf/printf! Ниже приводится пример того, как можно написать функцию для вывода класса рациональных чисел: class Rational { public: Rational(int numerator=0, int denominator=1); int n, d; //числитель и знаменатель friend ostream& operator"(ostream& s, const Rational& r); }; ostream& operator"(ostream& s, const Rational& r) { s " r.n " '/' " r.d; return s; } Эта версия operator" демонстрирует некоторые тонкости (притом весьма важные!). Например, она не является функцией-членом, а объект Rational передается operator" по ссылке const, а не как объект. Соответствующая функция ввода, operator", объявляется и реализуется аналогичным образом. Как ни обидно это признавать, в ряде случаев имеет смысл вернуться к старому и проверенному способу. Во-первых, некоторые реализации операций потоков ввода/вывода менее эффективны, чем соответствующие операции С, и возможно (хотя маловероятно), что в отдельных приложениях это может оказаться существенным. Помните, однако: это относится не к потокам ввода/вывода вообще, а только к той или иной реализации. Во-вторых, библиотека потоков ввода/вывода, в ходе своей стандартизации претерпела ряд кардинальных изменений. Следовательно, приложения, требующие максимальной переносимости, могут столкнуться с тем фактом, что различные поставщики поддерживают различные приближения стандарта. И наконец, поскольку классы библиотеки потоков ввода/вывода имеют конструкторы, а функции <stdio.h> - нет, в редких случаях существенным будет порядок инициализации статических объектов, и стандартная библиотека С окажется более удобной просто потому, что вы можете ею пользоваться без опасений. Контроль типов и расширяемость, предлагаемые классами и функциями библиотеки потоков ввода/вывода, являются более важными, чем это может показаться, - не стоит отвергать их только из-за того, что вы привыкли к <stdio.h>. Между прочим, это не опечатка - в названии данного правила действительно фигурирует <iostream>, а не <iostream.h>. Строго говоря, такого заголовка, как <iostream.h>, не существует: Комитет по стандартам отказался от него в пользу названия <iostream> при сокращении имен стандартных файлов заголовков, отсутствующих в библиотеке языка С. Важно уяснить следующее: если (что весьма вероятно) ваш компилятор поддерживает как файл заголовков <iostream>, так и <iostream.h>, необходимо иметь в виду, что они слегка отличаются друг от друга. В частности, если вы включаете <iostream>, элементы библиотеки потоков ввода/вывода весьма удобно расположены в пространстве имен std; включая <iostream.h>, вы получаете те же элементы, но в глобальном пространстве имен. Их определение в нем может вести к конфликтам, предотвращению которых и должно было послужить введение понятия пространства имен. Кроме того, <iostream> короче, чем <iostream.h>. Для многих это оказывается достаточным аргументом в пользу нового названия. Вот на этом пока и все. Если будут вопросы - пишите. По результатам вашего любопытства могут появиться новые статьи. При написании данного текста активно использовалась книга Скотта Мейерса. Список литературы Scott Meyers Effective C++ Second Edition AWG 1998 Для подготовки данной работы были использованы материалы с сайта http://my-pc.jino.ru/ |
||
|