Как было уже сказано выше, в C# можно создать массивы, в которых количество элементов в строках разное – ступенчатые массивы. В таком случае необходимо выполнять инициализацию каждой строки в отдельности.
Рассмотрим это на примере. Дан ступенчатый массив. Вывести номера его строк, в которых доля положительных элементов максимальна.
static void Main(string[] args)
{
float[][] b; //объявим неровный массив
int n, m;
Console.Write("Строк ");
n = Convert.ToInt32(Console.ReadLine());
b = new float[n][]; //определим количество строк
for (int i = 0; i < n; i++)
{
Console.Write("Элементов в строке"+i+" ");
m = Convert.ToInt32(Console.ReadLine());
b[i] = new float[m]; // определим количество
// элементов в i-ой строке
}
for(int i=0;i<b.Length;i++)
for (int j = 0; j < b[i].Length; j++)
{
Console.Write("b[" + i + "," + j + "]=");
b[i][j] = Convert.ToSingle(Console.ReadLine());
}
float []dol=new float[n]; // массив для долей
// положительных элементов в строке
int kol;
float maxdol=0; //максимальная доля
for(int i=0;i<b.Length;i++)
{
kol=0;
for (int j = 0; j < b[i].Length; j++)
if(b[i][j]>0)kol++;
if (b[i].Length!=0)
dol[i]=(float)kol/b[i].Length;
else
dol[i]=0;
if(maxdol<dol[i])maxdol=dol[i];
}
if (maxdol == 0)
Console.WriteLine
("Нет положительных элементов в массиве");
else
{
string s=""; //в эту переменную соберем номера строк
for(int i=0;i<b.Length;i++)
if (dol[i]==maxdol) s+=" "+i;
Console.WriteLine
("Максимальная доля в строках "+s);
}
Console.ReadLine();
}
Примечания.
- Если b неровный массив, то b.Length возвращает количество строк, а b[i].Length количество элементов в i–й строке.
- Нулевое количество элементов в строке не является ошибкой, в таком случае b[i].Length=0.
- присваивание string s=""; обязательно, даже для пустой строки.
Контрольные вопросы
1. Допустим, что в программе имеются объявления:
int i = 10, j = 9;
float x;
и оператор x = i / j; Какое знгачение получит х? Почему?
2. В каких случаях целесооюразно использовать ступенчатые массивы?
3. Когда целесообразно использовать оператор цикла foreach?
4. К чему равно значение свойства массива Length (для одномерных,
двумерных и ступенчатых массивов)?
5. Какое значение возвращает функция GetUpperBound(i) в
зависимости от значения i?
Работа с функциями
Общие принципы
В C#, как и в других языках, функции используют для двух целей:
- Для написания алгоритмов, которые должны выполняться многократно с разными исходными данными.
- Как средство структурирования программы: большие задачи целесообразно разделить на подзадачи, которые затем будут реализованы функциями.
В C# функция может иметь тип возвращаемого значения или не иметь его (иметь тип void). Формальные параметры-переменные могут быть переданы как по значению, так и по ссылке.
По умолчанию – по значению.
Для организации передачи по ссылке используют ключевые слова:
ref позволяет изменить в функции переданный параметр, но параметр должен иметь значение при обращении к функции.
оut позволяет получить в функции новое значение для параметра.
Параметры – массивы обозначаются аналогично их объявлению и передаются только по ссылке, при этом несущественно, представляют ли они исходные данные или результат выполнения функции.
Проиллюстрируем сказанное несколькими примерами.
Пример 1.
Найти номер первого отрицательного элемента в одномерном массиве, при отсутствии отрицательных возвращать отрицательное значение.
void Nom1(out int k, int []mas)
{
int i;
k=-8;
for(i=0;i<mas.Length;i++)
if(mas[i]<0){k=i;break;}
}
Параметр k получает значение в результате выполнения функции.
Второй вариант:
int Nom2(int []mas)
{
int i;
k=-8;
for(i=0;i<mas.Length;i++)
if(mas[i]<0){k=i;break;}
return k;
}
Пример 2.
Найти номер первого элемента одномерного массива в заданном интервале после заданного элемента. При его отсутствии – вернуть в качестве ответа заданный номер.
void Number(ref int k,double c1,double c2,double[]mas)
{
for(int i=k;i<mas.Length;i++)
if (mas[i] > c1 && mas[i] < c2) { k = i; break; }
}
Параметр k должен иметь значение до обращения к функции (отсутствие значения – синтаксическая ошибка!) и может менять своё значение в результате выполнения функции. Параметры с1 и с2 передаются по значению и могут быть лишь исходными данными в функции.
Использование в качестве параметров двумерных массивов, в том числе и неровных, не вносит ничего нового, и мы на этом останавливаться не будем.
2.2. Процедурное программирование в C#
Рассмотрим в этом параграфе компоновку программы с функциями. Вспомните структуру программы, описанной в § 1.1. Рассмотрим сначала, как можно использовать приведенные выше функции.
Функция из примера 1.
namespace ConApp3
{
class Program
{
static void Nom1(out int k, int[] mas)
{
int i;
k = -8;
for (i = 0; i < mas.Length; i++)
if (mas[i] < 0) { k = i; break; }
}
static void Main(string[] args)
{
int[] m ={5,9,2,6,7,56,-100};
int p;
Nom1(out p, m);
if (p < 0) Console.WriteLine("Нет отрицательных");
else
Console.WriteLine("Номер элемента "+p);
Console.ReadLine();
}
} }
Главная функция Main находится внутри класса Program и, поэтому имеет полное право работать его функциями с любым атрибутом доступа. Чтобы можно было вызывать функцию без создания экземпляра класса необходимо объявить функцию статической static. Обратите внимание на то, что и при вызове функции присутствует атрибут out.
Функция из примера 2.
namespace ConApp3
{
class Program
{
void Number(ref int k, double c1, double c2,
double[] mas)
{
for (int i = k; i < mas.Length; i++)
if (mas[i] > c1 && mas[i] < c2)
{ k = i; break; }
}
static void Main(string[] args)
{
double[] m ={5.1,9.4,2.7,6.8,7.1,56.8,100.0};
int q=2;
Program myclass = new Program(); //1
myclass. Number(ref q, 8.0, 15.0, m); //2
Console.WriteLine("Номер элемента "+q);
Console.ReadLine();
} } }
В этом примере описатель static отсутствует, поэтому перед вызовом функции необходимо создать экземпляр класса (строка // 1). После этого вызовы всех функций из класса Program выполняют через квалификатор (строка // 2). Обратите внимание на то, что и при вызове функции присутствует атрибут ref.
В завершение параграфа приведем пример, содержащий функции:
- ввода массива,
- обработки массива – нахождение среднего арифметического.
Кроме того, из этого примера видно, как в случае необходимости можно работать с глобальными переменными.
namespace ConApp4
{
class Program
{
static int t1 = 0;
//объявление глобальной переменной для функций
static void Inpt(int[][] k)
{ //ввод массива
int n, m;
t1++; //работа с глобальной переменной
for (int i = 0; i < k.Length; i++)
for (int j = 0; j < k[i].Length; j++)
{
Console.Write("massiv[" + i + "," + j + "]=");
k[i][j] = Convert.ToInt32(Console.ReadLine());
}
}
static float Proc(int[][] d)
{ // обработка массива
float sr = 0;
int kol = 0;
t1+=5; //работа с глобальной переменной
for(int i=0;i<d.Length;i++)
for (int j = 0; j < d[i].Length; j++)
{
sr += d[i][j];
kol++;
}
return sr / kol;
}
static void Main(string[] args)
{
int[][] mas;
int n, m;
Console.Write("Строк ");
n = Convert.ToInt32(Console.ReadLine());
mas = new int[n][]; //определим количество строк
for (int i = 0; i < n; i++)
{
Console.Write("Элементов в строке " + i + " ");
m = Convert.ToInt32(Console.ReadLine());
mas[i] = new int[m]; // определим количество
// элементов в i-ой строке
}
Inpt(mas);
Console.WriteLine("Среднее арифметическое " +
Proc(mas)+
+” t1=”+t1); //работа с глобальной переменной
Console.ReadLine();
} } }
В отличие от многих языков программирования в C# результатом функции может быть и массив, что иллюстрирует следующий пример формирования нового массива из положительных элементов исходного.
namespace ConApp6
{
class Program
{
static int[] fun1(int[] k)
{ // результат функции - массив
int kol = 0;
for (int i = 0; i < k.Length; i++)
if (k[i] > 0) kol++;
int []res=new int[kol]; //объявление результата - массива
kol = 0;
for (int i = 0; i < k.Length; i++)
if (k[i] > 0) res[kol++] = k[i];
return res; //возвращение массива в качестве результата
}
static void Main(string[] args)
{
int[] mas,arr; // mas исходный массив arr - результат
int n;
Console.Write("Count of Elements ");
n = Convert.ToInt32(Console.ReadLine());
mas = new int[n];
for (int i = 0; i < mas.Length; i++)
{
Console.Write("mas[" + i + "]=");
mas[i] = Convert.ToInt32(Console.ReadLine());
}
arr = fun1(mas); // инициализация массива - результата
// не требуется
for (int i = 0; i < arr.Length; i++)
Console.WriteLine("arr[" + i + "]="+arr[i]);
Console.ReadLine();
}
}
}
Контрольные вопросы
1. Для чего используют функции?
2. Для чего используют атрибуты out и ref?
3. Что означает атрибут static?
4. Для чего целесообразно использовать глобальные переменные?
3. Объектно-ориентированное программирование на C#
Общие принципы
Базовыми понятиями объектно-ориентированного программирования являются объект и класс. Объект – это какой-то реально существующий предмет со всеми его индивидуальными характеристиками. Класс – это множество объектов с одинаковыми характеристиками и одинаковым поведением. При определении значений характеристик класс превращается в объект. Характеристики класса задают данными, а поведение – методами. В C# методы представляют собой функции, среди методов выделяют конструктор и деструктор – функции особого назначения и с особыми правилами оформления.
Свойства объектно-ориентированного программирования:
- инкапсуляция (объединение в одной структуре данных – классе объявления данных и методов их обработки);
- наследование (класс может иметь одного предка, данные предка автоматически включаются в его состав, можно использовать методы предка);
- полиморфизм (можно иметь несколько реализаций одного метода с автоматическим выбором подходящего).
Рассмотрим в этой главе работу с классами в C#.