Questions
.pdfОбсудите то, что вам удалось узнать об увеличении размера массива.
После создания массива как объекта невозможно изменять его размер. Возможно лишь создать новый объект-массив и ссылку на него передать в интересующую нас переменную.
Старый же объект-массив все равно будет существовать еще некоторое время, пока сборщик мусора не удалит его.
Однако, если в процессе работы размер массива должен динамически меняться, тогда следует применять один из классов коллекции.
Классы коллекций делятся на три основных категории: общего назначения,
специализированные и ориентированные на побитовую организацию данных. Классы общего назначения можно использовать для хранения объектов любого типа. Класс ArrayList предназначен для поддержки динамических массивов, которые при необходимости могут увеличиваться или сокращаться. В С# стандартные массивы имеют фиксированную длину, которая не может измениться во время выполнения программы. В таких случаях и используется класс ArrayList.
Объект класса ArrayList представляет собой массив переменной длины, элементами которого являются объектные ссылки. Любой объект класса ArrayList создается с некоторым начальным размером. При превышении этого размера коллекция автоматически его увеличивает. В случае удаления объектов массив можно сократить.
6. Опишите ситуации, в которых предпочтительней использовать массивы, а не коллекции.
Массивы лучше использовать в тех случаях, когда заранее известно количество элементов массива и при этом все элементы имеют один тип. Если типы элементов разные, то лучше использовать коллекции, но и в случае с обычным массивом тоже можно взаимодействовать если тип массива объявить object. Кроме того, массивы позволяют реализовывать многомерность своей структуры и могут содержать в себе другие массивы.
7. Опишите ситуации, в которых предпочтительней использовать коллекции, а не массивы.
Коллекции удобны тем, что, на мой взгляд, очень гибкие в отношении проведения операций над их элементами. Главное достоинство коллекций и преимущество над массивами – это то, что массивы не имеют возможности динамически изменять когда-либо объявленный их размер, в
коллекциях же, напротив, достаточно легко реализовывается добавление (удаление) ее элементов и многое другое. Если мы заранее не знаем будущий размер уже сейчас используемого массива, то логичнее воспользоваться определенным классом коллекции. А если еще все элементы коллекции имеют один тип, тогда имеет смысл использовать обобщенные коллекции.
3. Управляющие конструкции. Классы. Структуры
Остановимся на различиях между классами (типами-ссылками, reference-types) и структурами
(типами-значениями, value-types).
Классы — это ссылочные типы. Это означает, что к объектам классов доступ осуществляется через ссылку. Этим они отличаются от типов значений, к которым в С#
реализован прямой доступ. Доступ к объектам классов через ссылки увеличивает расходы системных ресурсов, в том числе и памяти. Структура подобна классу, но она относится к типу значений, а не к ссылочным типам. Структуры не могут наследовать другие структуры или классы. Структуры не могут использоваться в качестве базовых для других структур или классов. (Однако, подобно другим С#-типам, структуры наследуют класс object). Структура может реализовать один или несколько интерфейсов. Структуры могут также определять конструкторы, но не деструкторы. Однако для структуры нельзя определить конструктор по умолчанию (без параметров). Дело в том, что конструктор по умолчанию автоматически определяется для всех структур, и его изменить нельзя. Поскольку структуры не поддерживают наследования, члены структуры нельзя определять с использованием модификаторов abstract, virtual или protected. Объект структуры можно создать с помощью оператора new, подобно любому объекту класса, но это не обязательно. Если использовать оператор new, вызывается указанный конструктор, а если не использовать его, объект все равно будет создан, но не инициализирован. В этом случае необходимо выполнить инициализацию вручную. Приприсваивании одной структуры другой создается копия этого объекта. Это — очень важное отличие struct-
объекта от сlass-объекта.
Поскольку структуры — это типы значений, они обрабатываются напрямую, а не через ссылки. Таким образом, тип struct не требует отдельной ссылочной переменной. Это означает,
что при использовании структур расходуется меньший объем памяти. Более того, благодаря
прямому доступу к структурам, при работе с ними не снижается производительность, что имеет место при доступе к объектам классов. Поскольку классы — ссылочные типы, доступ к их объектам осуществляется через ссылки. Такая косвенность увеличивает затраты системных ресурсов при каждом доступе. Структуры этим не страдают. В общем случае, если вам нужно хранить небольшую группу связанных данных, но не нужно обеспечивать наследование и использовать другие достоинства ссылочных типов, тип struct может оказаться более предпочтительным вариантом.
Однако, при передаче структур в методы в качестве параметров, передаются непосредственно сами структуры, если происходит передача объекта некоторого класса, то передается ссылка, а не сам объект. Передача ссылки более быстрый и менее ресурсоёмкий процесс.
1. Пусть есть следующее объявление строки:
string s = "One, Two, Three, Four, Five";
- напишите программу, которая разделить строку на элементы, отделенные запятыми
(воспользуйтесь функцией String.Split) и помещает их в массив.
- переберите элементы массива используя сначала оператор foreach, а потом еще раз, используя оператор for.
Пример:
using System;
public class Split
{
public static void Main()
{
string s = "One, Two, Three, Four, Five"; //Выводим начальное значение
Console.WriteLine("Начальная строка s имеет вид:\n"); Console.WriteLine("s = \"" + s + "\"\n"); Console.WriteLine("=================================\n");
//Заменяем в строке пары символов ", " на " " s = s.Replace(", ", " ");
//Выводим результат замен
Console.WriteLine("\n\nТеперь, обработанная строка s имеет вид:\n"); Console.WriteLine("s = \"" + s + "\"\n"); Console.WriteLine("=================================\n\n\n\nМассив split
содержит элементы:\n");
//Объявляем массив split и заносим в него элементы со строки s string[] split = s.Split(' ');
//string [] split = words.Split(new Char [] {' ', ',', '.', ':'});
//Выводим элементы массива на консоль
Console.WriteLine("\n\nВыводит конструкция foreach\n"); int m = 0;
foreach (string n in split)
{
Console.WriteLine("split [" + m + "] = " + n); m++;
}
Console.WriteLine("\n\nВыводит конструкция for\n");
for (int i = 0; i < split.Length; i++)//foreach (string x in split)
{
Console.WriteLine("split [" + i + "] = " + split[i]);
}
Console.ReadLine();
}
}
Мы разбили строку относительно пробелов между словами. Можно было и убрать все пробелы и разделить строку поэлементно по заглавным буквам.
2. Изучите нижеприведенный пример и объясните, почему выбрасывается исключение когда вызывается метод CheckType с параметром string, но работает нормально, когда в качестве параметра этому методу передается значение типа int.
using System;
class IsExample
{
static void Main(string[] args)
{
int i = 1;
string s = "Hello, world"; CheckType(i); CheckType(s);
}
static void CheckType(object o)
{
int j; string t;
if (o is int)
{
Console.WriteLine("o is an int");
}
else
{
Console.WriteLine("o is not an int");
}
j = (int) o;
}
}
Дело в том, что запись j = (int) o; нормально работает когда переменная о содержит значение типа int (или совместимого с ним – конвертируемого), а при хранении ею стрингового значения операция явного приведения типов дает исключение поскольку тип string и int не являются взаимно конвертируемыми типами.
Приемр:
using System;
class IsExample
{
static void Main(string[] args)
{
int i = 1;
string s = "Hello, world"; CheckType(i); CheckType(s); Console.Read();
}
static void CheckType(object o)
{
int j; string t;
if (o is int)
{
Console.WriteLine("o is an int");
}
else
{
Console.WriteLine("o is not an int");
}
try
{
j = (int)o;
}
catch
{
Console.WriteLine("Ошибка конвертации: невозможно преобразовать в тип
интежер");
}
}
}
3. Скомпилируйте следующий код:
using System;
class SimpleTypes
{
static public void Main(string[] args)
{
CalculateSum();
}
static void CalculateSum()
{
byte a, b; a = 255; b = 122;
Console.Out.WriteLine((byte) (a * b)); Console.Out.WriteLine(a * b);
}
}
Ответьте на вопросы:
- почему в консоль выводятся два разных значения?
Дело в том, что при умножении байтовых значений, результат возвращается типа int, но не byte. Это потому, что байт может содержать максимум восемь бит данных, а умножение двух байт скорее всего даст результат, который не уместится в один тип byte. Поэтому используется неявное преобразование типов от меньшего к большему для исключения потери данных. Но если мы хотим получить результат в байтах, то необходимо осуществить процедуру явного преобразования результата типа интежер в тип байт.
4. Какие варианты из следующих: |
|
A. do {i++;} while {i <= 10}; |
-- |
B. do {i++;} while (i <= 10); |
+ |
C. do() {i++;} while() {i <= 10}; |
-- |
D. do() {i++;} while (i <= 10); |
-- |
E. do (i++;) while (i <= 10); |
-- |
являются правильными циклами do/while? Почему остальные варианты не верны? Обсудите на форуме.
A, C, D, E – неправильные. B – правильное.
В варианте A нарушен синтаксис – после ключевого слова while операторы должны быть записаны в круглых, а не фигурных скобках. В вариантах C и D наличие подряд идущих пар круглых и фигурных скобок недопустимо. После ключевого слова do необязательны фигурные скобки (если один оператор), то-есть возможна пара круглых скобок, но обязательно круглые скобки должны быть размещены внутри фигурных.
5. Структура и класс – что из них является типом-значением (value-type), а что типом-ссылкой
(reference-type)? Где создаются объекты каждого типа? Структура – это тип-значения. Класс – тип-ссылка. Соответственно размещаются в стеке и куче.
Проанализируйте, скомпилируйте и запустите файл stuct.cs и объясните как он работает, учитывая разницу между типами-значениями (value-types) и типами-ссылками (reference-types)
using System;
struct PointStruct
{
public int x; public int y;
}
class PointClass
{
public int x; public int y;
}
class StructureExample
{
static void Main()
{
PointStruct ps1; ps1.x = 10; ps1.y = 20;
PointStruct ps2 = ps1; ps2.x = -10;
ps2.y = -20;
Console.WriteLine("Examining the behavior of structures");
Console.WriteLine("ps1.x = " + ps1.x);
Console.WriteLine("ps1.y = " + ps1.y);
Console.WriteLine("ps2.x = " + ps2.x);
Console.WriteLine("ps2.y = " + ps2.y);
PointClass pc1 = new PointClass(); pc1.x = 10;
pc1.y = 20;
PointClass pc2 = pc1; pc2.x = -10;
pc2.y = -20;
Console.WriteLine("Examining the behavior of classes"); Console.WriteLine("pc1.x = " + pc1.x); Console.WriteLine("pc1.y = " + pc1.y); Console.WriteLine("pc2.x = " + pc2.x); Console.WriteLine("pc2.y = " + pc2.y);
}
}
Работает следующим образом.
Описывается структура PointStruct, описыается класс PointClass. Создается структурный объект ps1 и его внутренним переменным присваиваются значения. Потом создается новый структурный объект ps2 и в него копируется по значению все содержимое ps1. Внутренним переменным ps2 присваиваются новые значения. Изменения значений переменных ps2 не отражаются на значениях переменных ps1 поскольку они – два разных объекта (передача данных произошла по значению, - путем создания новой копии данных).
Аналогичные действия проведены для классовых объектов. Только здесь происходит передача значений по ссылке и создание нового объекта не происходит, только новая ссылка на старый объект организовывается. Потому изменения в объекте через ссылку pc2 отображается и на ссылке pc1, если так можно выразится.
В чем ключевые отличия между классами и структурами, по отношению к типам-ссылкам
(reference-types) и типам-значениям (value-types)?
Классы – ссылочные типы. Их объекты создаются в управляемой куче. Структуры – значимые типы. Их объекты создаются в стэке. Отсюда и разный принцип работы с ними.
-где размещаются переменные ps1 и ps2? В стеке.
-где размещаются переменные pc1 и pc2? В куче.
-происходит ли выделение памяти для значения переменной pc2 на самом деле? Почему? Нет.
Потому, что опущен оператор pc2 = new PointClass(); А ключевое слово new как раз и выделяет область памяти для прежде объявленной некой переменной. В нашем случае записью PointClass pc1 = new PointClass(); мы объявили и выдели область пямяти под ссылочную переменную типа
PointClass pc1, а записью PointClass pc2 = pc1; мы объявили ссылочную переменную pc2 (тоже типа PointClass), но памяти под объект не выделили поскольку нового объекта то для нее (для pc2)
не создали, а лишь «сослали» на уже существующий объект – на уже существующую область памяти.
- Почему присвоение значений полям ps2.x и ps2.y НЕ оказывает влияния на поля ps1.x и ps1.y?
Потому что присвоение этим переменным происходит не по ссылке а по значению, то-есть создается новая копия данных, стоящих справа от оператора присваивания.
- Почему присвоение значений полям pc2.x и pc2.y оказывает влияние на значение полей pc1.x и pc2.y? Потому, что здесь присваивается ссылка (адресс) на объект, который все-равно остается в единственном числе. И pc2.x, и pc1.x (аналогичнго и для у) ссылаются на один объект – являются разными ссылками на Единственный объект.
6. В C# всё является объектами. Приведите короткий пример, демонстрирующий это утверждение. int i = new int ( ) ; - ключевое слово new в первую очередь предназначено для создания объектов и выделения под них памяти. Выше приведенная запись возможна. И для типа-значения (int) мы воспользовались оператором new (предназначенным для использования в ссылочных типах). Но в этом случае динамического выделения памяти не происходит. А переменная i инициализируется нулем.
Кроме того, все типы данных унаследованы от класса object.
4. Перегрузка методов и операторов
Рассмотрим следующий пример, содержащий три метода взгляните на код приведенный Speak.
using System;
class Dog
{
public string s;
}
class Cat
{
public string s;
}
//An example to demonstrate method overloading (and not the best example regarding
//object oriented programming!)
class OverloadExample
{
static void Main()
{
Dog dog = new Dog(); dog.s = "I'm a dog";
Cat cat = new Cat(); cat.s = "I'm a cat";
Speak(dog);
Speak(cat);
Speak((object) dog); // Explicit cast to force the right overloaded method to be called
}
// Speak is overloaded three times: one method expects a Dog as it's argument,
the
// second expects a Cat as it's argument and the third expects an object as it's argument.
static void Speak(Dog d)
{
Console.WriteLine("A dog barks");
}
static void Speak(Cat c)
{
Console.WriteLine("A cat meows");
}
static void Speak(object o)
{
Console.WriteLine("I have no idea what an object does.");
}
}
Когда вы скомпилируете и запустите этот пример, то увидите, что когда мы вызываем метод Speak
передавая объект типа Dog, то этот метод печатает “A dog barks”. Аналогично, если мы передаем в метод Speak объект типа Cat, этот метод печатает “A cat meows”. Третий интересный пример – метод Speak, которому передается переменная типа object. Учитывая, что в C# все является объектами, Вы можете спросить – каким образом компилятор различает – какой метод вызывать для объектов типа Dog и Cat. Компилятор выбирает такой метод, который наиболее точно соответствует типу параметров. Если точное соответствие не найдено, компилятор начинает исследовать дерево наследования и проверять, можно ли вызвать какой-либо метод для базовых классов. Если и в этом случае не найдено соответствие – компилятор выдает сообщение об ошибке.
В нашем примере мы специально преобразовали объект типа Dog к базовому типу, чтобы был вызван метод с сигнатурой Speak(object o).