Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Books / 3_C#_2005_для_чайников_(Дэвис-2008)

.pdf
Скачиваний:
86
Добавлен:
24.03.2015
Размер:
15.46 Mб
Скачать

Функция T e s t () не может обратиться к методу B a s e C l a s s . F u n c t i o n () из on екта подкласса sb, поскольку он скрыт методом S u b c l a s s . F u n c t i o n ( ) . Вы намер вались сделать одно из двух перечисленных действий.

Хотели скрыть метод базового класса. В этом случае добавьте ключевое слово new в определение S u b c l a s s , как показано в следующем фрагменте исход ного текста:

p u b l i c c l a s s S u b c l a s s : B a s e C l a s s

{

new p u b l i c v o i d F u n c t i o n ( )

{

} }

Намеревались полиморфно наследовать базовый класс. В этом случае вы долщ| объявить два класса следующим образом:

p u b l i c c l a s s B a s e C l a s s

{

p u b l i c v i r t u a l v o i d F u n c t i o n ( )

{

}

}

p u b l i c c l a s s S u b c l a s s : B a s e C l a s s

{

p u b l i c o v e r r i d e v o i d F u n c t i o n ( )

{

}

}

См. детальное описание проблемы в главе 13, "Полиморфизм".

Это не ошибка, а всего лишь предупреждение.

Это сообщение говорит о том, что класс, который вы хотите наследовать, опечатан, поэтому вы не можете ни наследовать его, ни изменить любое из его свойств. Обычно опечатываются только библиотечные классы. Обойти опечатывание невозможно, но вы можете попытаться воспользоваться классом с помощью отношения СОДЕРЖИТ (см. главу 13, "Полиморфизм").

382

Часть VI.

Великолепные д е с я т к и

Реализация интерфейса представляет собой обещание предоставить определение для всех его методов. Данное сообщение свидетельствует о том, что вы нарушили это обе­ щание и не реализовали указанный метод. Такое могло произойти по ряду причин.

Ваша собака просто сожрала вашу работу. Конечно, это шутка — вы просто забыли о необходимости реализовать указанный метод. В следующий раз будьте внимательнее. Вы ошиблись при написании имени метода или дали ему неверные аргументы.

Рассмотрим следующий пример:

i n t e r f a c e Me

 

void

a F u n c t i o n ( f l o a t

f ) ;

public

c l a s s M y C l a s s :

Me

p u b l i c v o i d a F u n c t i o n ( d o u b l e d )

Класс M y C l a s s не реализует функцию интерфейса a F u n c t i o n ( f l o a t ) . Функция aFunction ( d o u b l e ) не играет роли, поскольку аргументы этих двух функций не сов­ падают.

Вернитесь к исходному тексту программы и продолжите реализацию методов, пока интерфейс не будет реализован полностью (см. главу 14, "Интерфейсы и структуры").

Неполная реализация интерфейса — практически то же самое, что и попытка создать конкретный класс из абстрактного, не перекрывая при этом все его аб­ страктные методы.

Этим сообщением С# ставит вас в известность, что ваш метод, который объявлен как извращающий значение, на одном или нескольких путях выполнения не возвращает ни­ кто. Это может случиться по двум причинам.

У вас имеется конструкция if, которая содержит выход без возврата значения.

Более вероятно, что вы вычислили значение, но не возвращаете его.

Обе возможности иллюстрируются следующим исходным текстом: public c l a s s MyClas s

л'ава

16. Десять наиболее распространенных ошибок компиляции

383

 

p u b l i c s t r i n g C o n v e r t T o S t r i n g ( i n t n )

{

// К о н в е р т и р у е м i n t n в s t r i n g s s t r i n g s = n . T o S t r i n g ( ) ;

}

p u b l i c s t r i n g C o n v e r t P o s i t i v e N u m b e r s ( i n t n )

{

/ / П р е о б р а з у е м т о л ь к о п о л о ж и т е л ь н ы е ч и с л а

i f

(n

>

0)

 

 

{

 

 

 

 

 

 

s t r i n g s = n . T o S t r i n g ( ) ;

 

 

r e t u r n

s ;

 

 

C o n s o l e . W r i t e L i n e ( " А р г у м е н т {о}

н е к о р р е к т е н " , n ) ;

/ /

Т р е б у е т с я еще один

о п е р а т о р

r e t u r n

>

 

 

 

 

 

}

 

 

 

 

 

Функция

C o n v e r t T o S t r i n g ()

вычисляет значение типа s t r i n g , но не возвраща­

ет его. Все, что нужно для исправления этой ошибки — добавить в конце функции re­ t u r n S;.

Функция C o n v e r t P o s i t i v e N u m b e r s () возвращает s t r i n g - в е р с и ю аргументап типа i n t , если п положительно. Кроме того, функция совершенно корректно генерируй сообщение об ошибке, если п не положительно. Однако в этом случае функция ничего не возвращает. Здесь вы должны вернуть либо n u l l , либо пустую строку "" — что болыв подходит для нужд вашего приложения (см. главу 7, "Функции функций").

Эта ошибка указывает, что С# ожидал закрывающую скобку в месте завершения ис­ ходного текста программы. Где-то по дороге вы забыли закрыть определение класса, функцию или блок. Вернитесь и внимательно просмотрите исходный текст программы, пока не найдете это место.

Такое сообщение об ошибке зачастую оказывается последним в серии практически бессмысленных сообщений. Не беспокойтесь о них до тех пор, пока не найдете место, где была потеряна закрывающая фигурная скобка. В поиске пар открывающихся и соот­ ветствующих закрывающихся скобок вам может помочь Visual Studio 2005.

384

Часть VI. Великолепные десятки

Глава 17

Десять основных отличий С# и С++

>Отсутствие глобальных данных и функций

>Все объекты размещаются вне кучи

>Переменные-указатели запрещены

>Обобщенные классы С# и шаблоны С++

) Никаких включаемых файлов

>Не конструирование, а инициализация

>Корректное определение типов переменных

>Нет множественного наследования >Проектирование хороших интерфейсов

Квалифицированная система типов

зык С# в большей или меньшей степени основан на языке программирования С++. Это и не удивительно, если вспомнить, что Microsoft создала Visual С++, 1ру из наиболее успешных сред программирования для Windows. Подавляющее боль­

шинство программ, с которыми приходится иметь дело, написаны на Visual С++.

I Однако С # — не просто новые одежды на старом языке: в нем имеется масса улучЬний, представляющих собой как новые возможности, так и замену старых возможно- •4 С++ более мощными. В этой главе будет рассказано о десятке самых важных усоиршенствований. Само собой, их гораздо больше, так что, в принципе, можно было бы рписать и о двадцати (и более) главных улучшениях С# по сравнению с С++.

Вы могли прийти к С# разными путями — например, из Java или Visual Basic. С# даже более схож с Java, чем с С++ что также не удивительно, поскольку язык Java тоже появился как усовершенствование С++, ориентированное на ис­ пользовании в Интернете. Между С# и Java много синтаксических различий, но все равно они выглядят как близнецы. Если вы можете читать программы на одном из них — то сможете это делать и на другом.

Что касается Visual Basic — т.е. Visual Basic .NET, а не старого Visual Basic 6.0 — то и синтаксис совершенно иной, однако Visual Basic .NET основан на той же инфраструктуре .NET Framework, что и С#, так что он дает практически одинаковый с С# код CIL (Common Intermediate Language, общий промежуточный язык) и способен к взаимодействию с С#. Класс С# может наследовать класс Visual Basic и наоборот, а ваша

программа может представлять комбинацию модулей С# и Visual Basic.

С++ позиционируется как объектно-ориентированный язык, и это так, в том смысле что вы можете программировать в объектно-ориентированном стиле с использование С++. Вы также можете отходить от объектно-ориентированного стиля программирова ния, размещая данные и функции в некотором глобальном пространстве, открытом для всех элементов программы и всех программистов с клавиатурой...

С# заставляет программиста быть преданным объектно-ориентированной присяге все функции и все члены-данные должны объединяться в классы. Вы хотите обратится функции или данным? Вы должны делать это с согласия автора класса — и никаких исключений здесь быть не может.

C/C++ позволяет выделять память несколькими способами, каждый из которых има свои недостатки.

Глобальные объекты существуют на протяжении всего времени жим программы. Программа может легко создать множество указателей на один и тот же глобальный объект. При изменении посредством одного из них изменения отражаются во всех. Указатель представляет собой переменную, содержащую адрес некоторого удаленного блока памяти. Технически ссылки С# явля ются указателями.

Стековые объекты уникальны для отдельных функций (и это хорошо),но они освобождаются при выходе из функции. Любой указатель на освобожден­ ный объект становится недействительным. Это было бы нормально, если бы никто не работал с этими указателями; однако ничто не мешает использовать указатель, даже если объекта, на который он указывает, уже нет. Стек С++ — это область памяти, отличная от кучи, и это действительно стек.

Объекты в куче создаются по мере необходимости. Эти объекты уникальны в рамках одной нити выполнения программы.

Проблема заключается в том, что очень легко забыть, на какой тип памяти ссы­ лается данный указатель. Объекты в куче следует освободить после того, как вы по­ работали с ними. Если забыть об этом, в программе образуется "утечка памяти", ко-' торая может привести к исчерпанию последней и неработоспособности программы, С другой стороны, если вы освободите блок в куче дважды или "освободите" блок глобальной или стековой памяти, ваша программа может вызвать проблемы, с кото рыми справятся только три веселые клавиши <Ctrl>, <Alt> и <Del>, и то не пооди­ ночке, а все вместе...

С# решает эту проблему путем выделения памяти для всех объектов вне кучи. Кроме того, С# использует сборку мусора для ее возврата в кучу. При работе с С# вы можете забыть о синем экране смерти из-за пересылки неверного блока памяти в куче.

386

Часть VI. Великолепные д е с я т к и

Введение указателей в С обеспечило успех этого языка программирования. Работа

суказателями— очень мощная возможность. Старые программисты на машинных язы-

ибыли несказанно рады, получив в свои руки такой инструмент. В С++ возможности работы с указателями унаследованы без изменений от С.

Ксожалению, ни программист, ни программа не в состоянии отличить хороший ука­ затель от плохого. Прочтите память по неинициализированному указателю— и ваша программа аварийно завершится, если, конечно, вам повезет... Если не повезет— она продолжит работу, сочтя случайный блок памяти корректным объектом...

Проблемы, связанные с указателями, трудно локализовать. Зачастую программа с не­ корректным указателем при каждом новом запуске ведет себя по-новому.

С# решает проблемы, связанные с указателями, очень просто — он попросту устраня­ ет их из языка. Используемые вместо них ссылки безопасны с точки зрения типов и не могут быть применены так, чтобы это приводило к краху программы.

Если вы сравните новые обобщенные возможности С# (см. главу 15, "Обобщенное программирование") с шаблонами С++, то обнаружите высокую степень схожести их (интаксиса. Однако хотя оба средства имеют общее предназначение, такое сходство яв­ ляется чисто внешним.

Как обобщенные классы, так и шаблоны безопасны с точки зрения типов, но реализованы они совершенно по-разному. Шаблоны инстанцируются в про­ цессе компиляции, в то время как инстанцирование обобщенных классов происходит во время выполнения программы. Это означает, что один и тот же шаблон в разных модулях дает в результате два различных типа, инстанцированных во время компиляции. Но один и тот же обобщенный класс в разных модулях дает только один тип, инстанцируемый во время выполне­ ния. Это приводит к меньшему "раздутию" кода для обобщенных классов по сравнению с шаблонами.

Наибольшее различие между обобщенными классами и шаблонами состоит пом, что обобщенные классы работают с несколькими языками, включая Visual Basic, С++ и другие языки .NET, в том числе С#. Шаблоны же используются только (рамках С++.

Что же лучше? Шаблоны более мощны — и более сложны, как и множество других идей в С++, но и больше подвержены ошибкам. Обобщенные классы проще в испольювании и реже приводят к ошибкам в программах.

Конечно, это всего лишь некоторые тезисы бурной дискуссии. Существенно большую информацию можно найти в блоге Брендона Брея (Brandon Bray) по адресу weblogs . a s p . n e t / b r a n b r a y / a r c h i v e / 2 0 0 3 / 1 1 / 1 9 / 5 1 0 2 3 . a s p x .

Г л а в а 17, Десять основных отличий С# и С++

387

С++ обеспечивает строгую проверку типов — и это хорошо. Он выполняет эот заставляя объявлять функции и классы в так называемых включаемых файлах, кото рые затем используются модулями. Однако правильное перечисление в правили порядке всех включаемых файлов для компиляции вашего модуля — задача не из простых.

С# избегает бессмысленной работы. Он ищет и находит определения всех классов Если вы вызываете класс S t u d e n t , С# находит определение этого класса, чтобы убе диться, что вы используете его корректно.

Сначала казалось, что конструкторы приносят большую пользу. Наличие специальн ной функции, гарантирующей корректную настройку всех членов-данных... Отличная идея! Единственная проблема в том, что приходится добавлять в каждый написанный класс тривиальный конструктор по умолчанию. Рассмотрим следующий пример:

p u b l i c c l a s s

A c c o u n t

{

 

 

p r i v a t e

d o u b l e b a l a n c e ;

p r i v a t e

i n t

n u m C h e c k s P r o c e s s e d ;

p r i v a t e

CheckBook c h e c k B o o k ;

p u b l i c A c c o u n t ( )

{

b a l a n c e = 0 . 0 ;

n u m C h e c k s P r o c e s s e d = 0;

c h e c k B o o k = new C h e c k B o o k ( ) ;

}

}

Почему же нельзя инициализировать члены-данные непосредственно и позволить языку программирования самому сгенерировать конструктор? С++ отвечает, почему; C# отвечает — почему нет? С# позволяет избавиться от ненужных конструкторов с помощью непосредственной инициализации:

p u b l i c c l a s s A c c o u n t

{

p r i v a t e d o u b l e b a l a n c e = 0 . 0 ;

p r i v a t e

i n t n u m C h e c k s P r o c e s s e d

p r i v a t e

CheckBook

c h e c k B o o k =

// Больше э т о не

н а д о д е л а т ь

=0 ;

new C h e c k B o o k ( ) ;

вк о н с т р у к т о р е

}

Более того, если все, что нужно — это соответствующая версия нуля для определенного типа, как в случае первых двух членов, С# примет необходимые меры автоматиче- ски, как минимум для членов-данных классов. Если вы хотите нечто, отличное от нуля, добавьте вашу собственную инициализацию к объявлению членов-данных. (Однако сле­ дует всегда инициализировать локальные переменные в функциях.)

388 Часть VI. Великолепные десятки

С++ очень политкорректен. Он и шагу не ступит ни на одном компьютере без того, чтобы определить требования к диапазону значений и размеру конкретных типов. Он указывает, что i n t имеет такой-то размер, a l o n g — больший. Все это приводит к по­ явлению ошибок при переносе программ с одного типа процессора на другой.

С# не заботится о таких мелочах. Он прямо говорит — i n t имеет 32 бит, a l o n g — 64 бит, и так должно быть.

С++ позволяет одному классу наследовать более чем один базовый класс. Например, класс S l e e p e r S o f а (диван-кровать) может наследовать классы Bed (кровать) и S o f a (диван). Наследование от двух классов звучит неплохо, и это и в самом деле бывает очень полезно. Проблема только в том, что множественное наследование может приво­ дить к некоторым трудно обнаружимым ошибкам.

С# не рискует и снижает количество возможных ошибок, запрещая множественное наследование. Однако в С# имеется возможность, которая в ряде ситуаций может заме­ нить множественное наследование, а именно — интерфейсы.

Когда программисты продираются сквозь кошмар множественного наследования и 90% времени проводят в отладчике, зачастую выясняется, что второй базовый класс нужен только для того, чтобы описать подкласс. Например, обычный класс может наследовать абстрактный класс P e r s i s t a b l e с абстрактными методами r e a d () и w r i t e ( ) . Это за­ ставляет подкласс реализовать методы r e a d () и w r i t e () и объявить всему миру, что эти методы доступны для использования.

После этого программисты осознают, что того же можно добиться существенно более легкими средствами — посредством интерфейса. Класс, который реализует интерфейс наподобие приведенного ниже, тоже обещает предоставить методы r e a d () и w r i t e ( ) : interface I P e r s i s t a b l e

{

void r e a d ( ) ; void write () ;

}

Так вы избегаете опасностей множественного наследования С++ и получаете желае­ мый результат.

Класс С++ — очень хорошая возможность языка. Он позволяет данным и связанным сними функциям быть объединенными в четкие пакеты, которые соответствуют челове-

Глава 17. Десять основных ОТЛИЧИЙ С# И С++

389

ческим представлениям о реальном мире. Единственная проблема заключается в том, что любой язык программирования должен обеспечить еще и простейшие типы переменных для хранения, например, целых чисел или чисел с плавающей точкой. Это определяет необходимость системы преобразования типов. Объекты классов и переменные типов значений находятся по разные стороны баррикад, хотя и участвуют вместе в одних и тех же программах. Программист вынужден все время помнить о том, что это разные вещей по своей природе.

С# разрушает эту баррикаду, отделяющую типы-значения от классов. Для каждом типа-значения имеется соответствующий класс, именуемый структурой (вы можете так же писать и собственные структуры; см. главу 14, "Интерфейсы и структуры"). Эти структуры могут легко объединяться с классами, позволяя программисту писать исход­ ный текст наподобие следующего:

M y C l a s s

m y O b j e c t =

new

M y C l a s s О,-

// Вывод

" m y O b j e c t "

в

с т р о к о в о м формате

C o n s o l e . W r i t e L i n e ( m y O b j e c t . T o S t r i n g ( ) ) ;

i n t

i =

5 ;

//

Вывод

i n t в с т р о к о в о м формате

C o n s o l e . W r i t e L i n e ( i . T o S t r i n g ( ) ) ;

//

Вывод

к о н с т а н т ы 5 в с т р о к о в о м формате

C o n s o l e . W r i t e L i n e ( 5 . T o S t r i n g ( ) ) ;

Можно вызвать один и тот же метод не только для переменной i n t и объекта класса MyClass, но даже для константы наподобие 5. Такое вавилонское смешение типов-

одна из мощных возможностей С#.

390

Часть VI. Великолепные десятки

Часть VII

Дополнительные главы