C# для чайников
.pdfАналогично, в следующем фрагменте кода сохраняются ссылки на строки strinJ
описывающие модель и производителя myCar:
myCar . sManufacturer = "BMW"; myCar.sModel = " I s e t t a " ;
(Isetta— небольшой автомобиль, который производился в 1950-х годах и имел одя
дверь впереди.)
Пример объектно-основанной программы
Программа V e h i c l e D a t a O n l y очень проста и делает следующее:
определяет класс V e h i c l e ; создает объект myCar; указывает свойства myCar;
получает значения свойств myCar и выводит их на экран.
Вот код программы V e h i c l e D a t a O n l y :
/ / V e h i c l e D a t a O n l y |
|
|
|
||
// |
Создает |
объект V e h i c l e , |
заполняет |
ег о |
члены информацией, |
// |
вводимой |
с клавиатуры, |
и выводит |
ее на |
экран |
u s i n g System ;
namespace V e h i c l e D a t a O n l y
{
p u b l i c c l a s s V e h i c l e
{
p u b l i c |
s t r i n g sModel; |
p u b l i c |
s t r i n g sManufacturer ; |
p u b l i c |
i n t nNumOfDoors,- |
p u b l i c |
i n t nNumOf Wheels,- |
//Модель
//Производитель
//Количество дверей
//Количество колес
}
p u b l i c c l a s s Program
{
//Начало программы
s t a t i c v o i d M a i n ( s t r i n g [ ] a r g s )
{
//Приглашение пользователю
C o n s o l e . W r i t e L i n e ( " В в е д и т е информацию о машине");
// Создание экземпляра V e h i c l e V e h i c l e myCar = new V e h i c l e (),-
//Ввод информации для членов класса
Console . Write("Модел ь = " ) ;
s t r i n g s = C o n s o l e . R e a d L i n e ( ) ; myCar.sModel = s;
118 |
Часть III. Объектно-основанное программировав |
// Можно присваивать значения непосредственно Console . Write("Производител ь = " ) ;
myCar . sManufacturer = C o n s o l e . R e a d L i n e О ;
//Остальные данные имеют тип i n t
Console . Write("Количеств о дверей = |
" ) ; |
||
s = C o n s o l e . R e a d L i n e ( ) ; |
|
||
myCar.nNumOfDoors |
= |
C o n v e r t . T o I n t 3 2 ( s ) , - |
|
Console . Write("Количеств о колес = |
" ) ; |
||
s = C o n s o l e . R e a d L i n e ( ) ; |
|
||
myCar.nNumOfWheels |
= |
C o n v e r t . T o I n t 3 2 ( s ) ; |
|
// Вывод полученной |
информации |
|
|
Console . WriteLine("\пВаш а машина: |
" ) ; |
||
Console . WriteLine(myCar . sManufacture r + " " + |
|||
|
myCar . sModel) ; |
|
|
C o n s o l e . W r i t e L i n e ( " c |
" + myCar.nNumOfDoors + |
||
|
" |
дверями, " |
|
+"на " + myCar.nNumOfWheels
+" колесах" ) ;
//Ожидаем подтверждения пользователя
Console . WriteLine("Нажмите <Enter> для " + "завершения программы.. . ") ;
C o n s o l e . R e a d ( ) ;
Листинг программы начинается с определения класса V e h i c l e .
Определение класса может находиться как до, так и после класса Program — это не имеет значения. Однако вам нужно принять какой-то один стиль и сле довать ему.
Программа создает объект myCar класса V e h i c l e , а затем заполняет все его поля информацией, вводимой пользователем с клавиатуры. Проверка корректности входных данных не производится. Затем программа выводит введенные данные на экран в немно го ином формате.
Вывод этой программы выглядит следующим образом:
Введите информацию о машине
Модель |
|
= |
M e t r o p o l i t a n |
||
Производитель |
= |
Nash |
|||
Количество |
дверей |
= |
2 |
||
Количество |
колес |
|
= |
4 |
|
Ваша м а ш и н а : |
|
|
|
|
|
Nash Metropolita n |
|
|
|||
с 2 дверями, |
на |
4 |
колесах |
||
Нажмите <Enter> |
для |
завершения программы... |
Глава В, Объединение данных - классы и массивы |
119 |
Вызов Read ( ) , в отличие от ReadLine ( ) , оставляет курсор сразу за введен! ной строкой. Это приводит к тому, что ввод пользователя находится на той nil строке, что и приглашение. Кроме того, добавление символа новой стромГ
'\ п ' создает пустую строку без необходимости вызывать WriteLin e ( ) .
От л и ч ие между объектами
Заводы в Детройте в состоянии выпускать множество автомобилей и отслеживать кажду»| выпущенную машину и при этом не путать их. Аналогично, программа может создать не сколько объектов одного и того же класса, как показано в следующем фрагменте:
V e h i c l e c a r l |
= new |
V e h i c l e О ; |
c a r l . s M a n u f a c t u r e r |
= "Studebaker" ; |
|
c a r l . s M o d e l |
= "Avanti" ; |
|
// Следующий |
код никак не влияет на c a r l |
|
V e h i c l e c a r 2 |
= new |
V e h i c l e ( ) ; |
c a r 2 . s M a n u f a c t u r e r |
= "Hudson"; |
c a r 2 . s M o d e l = " H o r n e t " ;
Создание объекта c a r 2 и присваивание ему имени и производителя никак не влияй] на объект c a r l .
Возможность различать объекты одного класса очень важна при программировании, Объект может быть создан, с ним могут быть выполнены различные действия — и он всегда выступает как единый объект, отличный от других подобных ему объектов.
С с ы л к и
Оператор "точка" и оператор присваивания — единственные два оператора, опреде ленные для ссылочных типов. Рассмотрим следующий фрагмент исходного текста:
//Создание нулевой ссылки
V e h i c l e yourCar;
//Присваивание значения ссылке
yourCar = new V e h i c l e ( ) ;
// Использование точки для обращения к члену yourCar . sManufacture r = "Rambler";
// Создание новой ссылки, |
которая указывает на тот же объект |
V e h i c l e yourSpousalCa r = |
yourCar; |
В первой строке создается объект yourCar, причем без присваивания ему значения, Такая неинициализированная ссылка называется нулевым объектом (null object). Любые попытки использовать неинициализированную ссылку приводят к немедленной генера ции ошибки, которая прекращает выполнение программы.
Компилятор С# может перехватить большинство попыток использования не инициализированной ссылки и сгенерировать предупреждение в процессе ком пиляции программы. Если вам каким-то образом удалось провести компьютер то обращение к неинициализированной ссылке при выполнении программь приведет к ее аварийному останову.
Второе выражение создает новый объект V e h i c l e и присваивает его ссылочной пе-1 ременной y o u r C a r . И последняя строка кода присваивает ссылке y o u r S p o u s a l C a r l
120 |
Часть III. Объектно-основанное программирование |
ссылку yourCar. Как показано на рис. 6.1, это приводит к тому, что yourSpousalCa r
ссылается на тот же объект, что и yourCar.
Рис. 6.1. Взаимоотношения между дву мя ссылками на один и тот же объект
Эффект от следующих двух вызовов одинаков:
// Создание вашей |
машины |
|
Vehicle yourCar |
= |
new V e h i c l e () ; |
yourCar. sModel |
= |
" K a i s e r " ; |
//Эта машина принадлежит и вашей жене Vehicle yourSpousalCa r = yourCar;
//Изменяя одну машину, вы изменяете и другую
yourSpousalCar. sModel = "Henry J " ;
Console.WriteLine("Ваша машина - " + y o u r C a r . s M o d e l ) ;
Выполнение данной программы приводит к выводу на экран названия модели Henry J, а не Kaiser . Обратите внимание, что yourSpousalCa r не указывает на yourCar— вместо этого просто и yourSpousalCar , и yourCar указывают,на один и тот же объект.
Кроме того, ссылка y o u r S p o u s a l C a r будет корректна, даже если окажется "потерянной" (например, при выходе за пределы области видимости), как показано
вследующем фрагменте:
//Создание вашей машины
Vehicle yourCar |
= |
new V e h i c l e () ; |
yourCar. sModel |
= |
" K a i s e r " ; |
//Эта машина принадлежит и вашей жене Vehicle yourSpousalCa r = yourCar;
//Когда она забирае т с е б е вашу машину...
yourCar = n u l l ; // yourCar теперь ссылается на "нулевой
//объект"
//. . . yourSpousalCar ссылается на все ту же машину
Console.WriteLine("Ваша машина - " + y o u r S p o u s a l C a r . s M o d e l ) ;
Выполнение этого фрагмента исходного текста выводит на экран сообщение "Ваша машина - K a i s e r " несмотря на то, что ссылка yourCa r стала недейст вительной.
Объект перестал быть достижимым по ссылке yourCar. Но он не будет пол ностью недостижимым, пока не будут "потеряны" или обнулены обе ссылки — и yourCar, и yourSpousalCar .
После этого — вернее будет сказать, в некоторый непредсказуемый момент после этого — сборщик мусора (garbage collector) С# вернет память, использованную ранее под
Глава 6. Объединение данных - классы и массивы |
121 |
объект, все ссылки на который утрачены. Дополнительные сведения о сборке мусора бу-1 дут приведены в конце главы 12, "Наследование".
К л а с с ы , с о д е р ж а щ и е к л а с с ы
Члены класса могут, в свою очередь, быть ссылками на другие классы. Например! транспортное средство имеет двигатель, свойствами которого являются, в частности! мощность и рабочий объем. Можно поместить эти параметры непосредственно в клася V e h i c l e следующим образом:
p u b l i c c l a s s V e h i c l e
{ |
|
|
p u b l i c |
s t r i n g sModel; |
|
p u b l i c |
s t r i n g sManufacturer ; |
|
p u b l i c |
i n t |
nNumOfDoors; |
p u b l i c |
i n t |
nNumOfWheels; |
p u b l i c |
i n t |
nPower; |
p u b l i c |
doubl e d i s p l a c e m e n t ; |
}
//Модель
//Производитель
//Количество дверей
//Количество колес
//Мощность двигателя
//Рабочий объем
Однако мощность и рабочий объем двигателя не являются свойствами автомобиля,! так как, например, джип моего сына может поставляться с одним из двух двигателей! с совершенно разными мощностями.
Двигатель является самодостаточной концепцией и может быть описан отдельным! классом:
p u b l i c c l a s s Motor |
|
|
|
{ |
|
|
|
p u b l i c |
i n t nPower; |
// |
Мощность |
p u b l i c |
doubl e d i s p l a c e m e n t ; |
// |
Рабочий объем |
}
Вы можете внести этот класс в класс V e h i c l e следующим образом: p u b l i c c l a s s V e h i c l e
{ |
p u b l i c |
s t r i n g sModel; |
|
|
|||
|
p u b l i c |
s t r i n g sManufacturer ; |
|
|
p u b l i c |
i n t |
nNumOfDoors; |
|
p u b l i c |
i n t |
nNumOfWheels; |
|
p u b l i c |
Motor motor ; |
}
/ / Модель / / Производитель
/ / Количество дверей / / Количество колес
Соответственно, создание s o n s C a r теперь выглядит так:
//Сначала создае м двигатель
Motor l a r g e r M o t o r |
= |
new M o t o r ( ) ; |
largerMotor . nPowe r = |
23 0; |
|
l a r g e r M o t o r . d i s p l a c e m e n t = 4 . 0 ; |
||
// Теперь создае м |
автомобиль |
|
V e h i c l e sonsCa r = |
new V e h i c l e ( ) ; |
|
sonsCar . sMode l = |
"Cherokee S p o r t " ; |
|
s o n s C a r . s M a n f a c t u r e r = " J e e p " ; |
||
sonsCar.nNumOfDoors = 2; |
||
sonsCar.nNumOfWheels |
= 4; |
//Присоединяем двигатель к автомобилю
sonsCar . moto r = l a r g e r M o t o r ;
122 |
Насть III. Объектно-основанное программирование |
Доступ к рабочему объему двигателя из V e h i c l e можно получить в два этапа, как показано в приведенном фрагменте:
Motor m = sonsCar . motor ;
Console.WriteLine("Рабочий объем равен " + m . d i s p l a c e m e n t ) ; Однако можно получить эту величину и непосредственно:
Console.Writeline ("Рабочий объем равен " +
s o n s C a r . m o t o r . d i s p l a c e m e n t ) ;
Влюбом случае доступ к значению d i s p l a c e m e n t осуществляется через класс Motor.
Этот фрагмент взят из исходного текста программы VehicleAndMotor, которую можно найти на прилагаемом компакт-диске.
Статические ч л е н ы к л а с с а
Большинство членов-данных описывают отдельные объекты. Рассмотрим следующий
класс Саг: |
|
|
public c l a s s Car |
|
|
( |
|
|
public |
s t r i n g s L i c e n s e P l a t e ; // Номерной знак |
автомобиля |
I |
|
|
Номерной |
знак является свойством объекта, описывающим |
каждый автомобиль |
и уникальным для каждого автомобиля. Присваивание номерного знака одному автомо
билю не меняет номерной знак другого: |
|
Car cousinsCar = new Car () ; |
|
cousinsCar. s L i c e n s e P l a t e |
= "XYZ123"; |
Car yourCar = new Car () ; |
|
yourCar. s L i c e n s e P l a t e = |
"ABC789"; |
Однако имеются и такие свойства, которые присущи всем автомобилям. Например, количество выпущенных автомобилей является свойством класса Саг, но не отдельного объекта. Свойство класса помечается специальным ключевым словом s t a t i c , как пока
зано в следующем фрагменте исходного текста: |
|
|
public class Car |
|
|
{ |
|
|
public |
s t a t i c |
|
int |
nNumberOfCars; // Количество выпущенных |
автомобилей |
public |
s t r i n g s L i c e n s e P l a t e ; // Номерной знак |
автомобиля |
}
Обращение к статическим членам выполняется не посредством объекта, а через сам класс, как показано в следующем фрагменте исходного текста:
// Создание нового объекта класса Саг
Car newCar = new Car () ; newCar.sLicensePlate = "ABC123";
// Увеличиваем количество автомобилей на 1
Car.nNumberOf Cars++ ;
(пава 6. Объединение данных - классы и массивы |
123 |
О б р а щ е н ие к члену объекта |
n e w C a r . s L i c e n s e P l a t e выполняется посред |
ством объекта n e w C a r , в то |
время как обращение к (статическому) члену |
C a r . n N u m b e r O f C a r s осуществляется с п о м о щ ь ю имени класса. Все объект типа С а г совместно используют один и тот же член n N u m b e r O f C a r s .
Определение константных членов-данных
Одним специальным видом статических членов является член, представляющий бой константу. Значение такой константной переменной должно быть указано в объяви нии, и не может изменяться нигде в программе, как показано в следующем фрагмент исходного текста:
c l a s s P r o g r a m
{
/ / Ч и с л о д н е й в г о д у ( в к л ю ч а я в и с о к о с н ы й г о д )
p u b l i c |
const i n t n D a y s I n Y e a r = 3 6 6 ; |
p u b l i c |
s t a t i c v o i d M a i n ( s t r i n g [ ] a r g s ) |
{
/ / Э т о м а с с и в — о н и х б у д е т р а с с к а з а н о н е м н о г о п о з ж е i n t [ ] n M a x T e m p e r a t u r e s = n e w i n t [ n D a y s I n Y e a r ] ;
f o r ( i n t i n d e x = 0 ; i n d e x < n D a y s I n Y e a r ; i n d e x + + )
{
/ / В ы ч и с л е н и е с р е д н е й т е м п е р а т у р ы д л я к а ж д о г о д н я г о д а
Константу n D a y s I n Y e a r можно использовать везде в вашей программе вместо ла 366. Константные переменные очень полезны, так как позволяют заме "магические числа" (в данном случае— 366) описательным именем n D a y s I n Y e a r , * повышает удобочитаемость программы и облегчает ее сопровождение.
С# имеет еще один способ объявления констант. Возможно, вам больше поим вится использовать не модификатор c o n s t , а модификатор r e a d o n l y :
p u b l i c readonly i n t n D a y s I n Y e a r = 3 6 6 ;
Как и при применении модификатора c o n s t , после того как вы присвоите конст инициализирующее значение, оно не может быть изменено нигде в программе. Xотя причины этого совета носят слишком технический характер, чтобы описывать их в стоящей книге, при объявлении констант предпочтительно использовать модификат r e a d o n l y .
Для констант имеется собственное соглашение по именованию. Вместо именован их так же, как и переменных (как в примере с n D a y s I n Y e a r ) многие программы предпочитают использовать прописные буквы с разделением слов подчеркиваниями DAYS_IN_YEAR. Такое соглашение ясно отделяет константы от обычных переменных.
В вашем распоряжении есть переменные, хранящие отдельные единственные значе ния. Классы могут использоваться для описания составных объектов. Но вам нужна еще
124 |
Часть III. Объектно-основанное программирова |
одна конструкция для хранения множества объектов, например, коллекции старинных автомобилей Билла Гейтса. Встроенный класс A r r a y представляет собой структуру, ко торая может содержать последовательности однотипных элементов (чисел типа i n t , double, объекты V e h i c l e и т.п.).
Зачем нужны массивы
Рассмотрим задачу определения среднего из десяти чисел с плавающей точкой. Каж дое из 10 чисел требует собственной переменной для хранения значения типа d o u b l e (усреднение целых чисел может привести к ошибке округления, описанной в главе 3, "Объявление переменных-значений"):
double dO = 5; double dl = 2 ; double d2 = 7; double d3 = 3 . 5 ; double d4 = 6 . 5 ; double d5 = 8; double d6 = 1; double d7 = 9; double d8 = 1; double d9 = 3;
Теперь нужно просуммировать все эти значения и разделить полученную сумму на 10:
double dSum = dO |
+ dl |
+ d2 + d3 + d4 + |
d5 + d6 |
+ d7 + d8 + d9; |
|
double dAverage = |
dSum / 10; |
Перечислять все элементы — очень утомительно, даже если их всего 10. А теперь
представьте, что вам надо усреднить 10000 чисел...
Массив фиксированного размера
Ксчастью, вам не нужно отдельно именовать каждый из элементов. С# предоставляет
враспоряжение программиста массивы, которые могут хранить последовательности зна чений. Используя массив, вы можете переписать приведенный выше фрагмент следую щим образом:
double[] dArray = {5, 2, 7, 3 . 5 , 6 . 5 , 8, 1, 9, 1, 3} ;
Класс Array использует специальный синтаксис, который делает его более удобным в применении. Двойные квадратные скобки [] указывают на способ доступа к отдельным элементам массива:
dArray[0] соответствует dO dArray[1] соответствует dl
Нулевой элемент массива соответствует do, первый — dl и так далее.
Номера элементов массива — 0, 1, 2, ... — известны как их индексы.
(пава 6. Объединение данных |
125 |
- классы и массивы |
|
В С# индексы массивов начинаются с 0, а не с 1. Таким образом, элемент с индексом 1 не является первым элементом массива. Не забывайте об этом!
Использование d A r r a y было бы слабым улучшением, если бы в качестве индекса массива нельзя было использовать переменную. Применять цикл f o r существенно проще, чем записывать каждый элемент вручную, что и демонстрирует программа F i x e d A r r a y A v e r a g e .
11 |
' F i x e d A r r a y A v e r a g e |
|
|
|
|||
// |
Усреднение |
массива |
чисел |
фиксированного |
р а з м е р а |
с |
|
/ / и с п о л ь з о в а н и е м ц и к л а |
|
|
|
||||
n a m e s p a c e |
F i x e d A r r a y A v e r a g e |
|
|
|
|||
|
u s i n g |
S y s t e m ; |
|
|
|
|
|
|
p u b l i c |
c l a s s |
P r o g r a m |
|
|
|
|
|
{ |
|
|
|
|
|
|
|
p u b l i c s t a t i c |
v o i d M a i n ( s t r i n g [ ] |
a r g s ) |
|
|||
|
d o u b l e [ ] |
d A r r a y = |
|
|
|
{ 5 , 2 , 7 , 3 . 5 , 6 . 5 , 8 , 1 , 9 , 1 , 3 } ;
/ / Н а к о п л е н и е с у м м ы э л е м е н т о в / / м а с с и в а в п е р е м е н н о й d S u m d o u b l e d S u m = 0 ,-
f o r ( i n t i = 0 ; i < 1 0 ; i + + \
I
d S u m = d S u m + d A r r a y [ i ] ;
/ / В ы ч и с л е н и е с р е д н е г о з н а ч е н и я d o u b l e d A v e r a g e = d S u m / 1 0 ;
C o n s o l e . W r i t e L i n e ( d A v e r a g e ) ;
/ / О ж и д а е м п о д т в е р ж д е н и я п о л ь з о в а т е л я
C o n s o l e . W r i t e L i n e ( " Н а ж м и т е < E n t e r > д л я " +
" з а в е р ш е н и я п р о г р а м м ы . . . " ) ;
C o n s o l e . R e a d ( ) ;
Программа начинает работу с инициализации переменной d S u m значением 0. Затем программа циклически проходит по всем элементам массива d A r r a y и прибавляет га к dSum . По окончании цикла сумма всех элементов массива хранится в dSum . Раздели! ее на количество элементов массива, получаем искомое среднее значение.
Проверка границ массива
Программа F i x e d A r r a y A v e r a g e должна циклически проходить по массиву из 10 элементов. К счастью, цикл разработан так, что проходит ровно по 10 элементам мас сива. Ну а если была бы допущена ошибка и проход был бы сделан не по 10 элементам, а по иному их количеству? Следует рассмотреть два основных случая.
126 |
Часть III. Объектно-основанное программирование |
Что произойдет при выполнении 9 итераций? С# не трактует такую ситуацию как ошибочную. Если вы хотите рассмотреть только 9 из 10 элементов, то как С# может указывать вам, что именно вам нужно делать? Конечно, среднее значение при этом будет неверным, но программе это неизвестно.
Что произойдет при выполнении 11 (или большего количества) итераций?
В этом случае С# примет свои меры и не позволит индексу выйти за дозволенные пре делы, чтобы вы не смогли случайно переписать какие-нибудь важные данные впамяти. Чтобы убедиться в этом, измените сравнение в цикле f o r , заменив 10 на 11: for (in t i = 0 ; i < 1 1 ; i + +)
При выполнении программы вы получите диалоговое окно со следующим сообщени
ем об ошибке: |
|
I n d e x O u t O f R a n g e E x c e p t i o n w a s |
u n h a n d l e d |
Index was o u t s i d e t h e b o u n d s |
o f t h e a r r a y . |
Здесь C# сообщает о происшедшей неприятности— исключении I n d e x O u t O f RangeException, из названия которого и из поясняющего текста становится понят на причина ошибки, — выход индекса за пределы допустимого диапазона. (Кроме этого, выводится детальная информация о том, где именно и что произошло, но пока то вы не настолько знаете С#, чтобы разобраться в этом.)
Массив переменного размера
Массив, используемый в программе F i x e d A r r a y A v e r a g e , сталкивается с двумя серьезными проблемами:
его размер фиксирован и равен 10 элементам;
что еще хуже, значения этих элементов указываются непосредственно в тексте программы.
Значительно более гибкой была бы программа, которая могла бы считывать переменной количество значений, вводимое пользователем, ведь она могла бы работать не только с определенными в программе F i x e d A r r a y A v e r a g e значениями, но и с другими мно жествами значений.
Формат объявления массива переменного размера немного отличается от объявления «ива фиксированного размера:
doublet] d A r r a y = n e w d o u b l e [ N ] ;
Здесь N — количество элементов в выделяемом массиве.
Модифицированная версия программы V a r i a b l e A r r a y A v e r a g e позво ляет пользователю указать количество вводимых значений. Поскольку про грамма сохраняет введенные значения, она может не только вычислить среднее значение, но и вывести результат в удобном виде.
;// V a r i a b l e A r r a y A v e r a g e |
|
|
|
|
|
|
//Вычисление |
с р е д н е г о |
з н а ч е н и я |
м а с с и в а , |
р а з м е р к о т о р о г о |
||
//указывается |
п о л ь з о в а т е л е м |
в о |
в р е м я |
р а б о т ы п р о г р а м м ы . |
||
[// Накопление |
в в е д е н н ы х |
д а н н ы х |
в м а с с и в е |
п о з в о л я е т |
||
[//обращаться |
к ним н е о д н о к р а т н о , в ч а с т н о с т и , д л я г е н е р а ц и и |
|||||
[//привлекательно в ы г л я д я щ е г о |
в ы в о д а |
н а |
э к р а н . |
Гюва 6, Объединение данных - классы и массивы |
127 |