3. Основи мови C#
3.1. Introduction
Спочатку ми розглядаємо C# як класичну мову програмування. Про класи ми поговоримо пізніше. У програмі є дві складові:
- дані
- інструкції, що оперують ними
Зазвичай намагаються відокремити дані від інструкцій:
![]() |
3.2. Дані в C#
У C# використовуються такі типи даних:
- цілі числа
- дійсні числа
- десяткові числа
- символи та символьні рядки
- логічні значення
- об'єкти
3.2.1. Вбудовані типи даних
Тип C# | Тип .NET | Представлені дані | Суфікс літеральних значень | Кодування | Діапазон значень |
Символ (S) | символ | 2 байти | символ Unicode (UTF-16) | ||
Строка (C) | рядок символів | посилання на послідовність символів Unicode | |||
Int32 (S) | ціле число | 4 байти | [-231, 231-1] [–2147483648, 2147483647] | ||
UInt32 (S) | .. | U | 4 байти | [0, 232-1] [0, 4294967295] | |
Int64 (S) | .. | L | 8 байт | [-263, 263 -1] [–9223372036854775808, 9223372036854775807] | |
UInt64 (S) | .. | UL | 8 байт | [0, 264 -1] [0, 18446744073709551615] | |
.. | 1 байт | [-27 , 27 -1] [-128,+127] | |||
Байт (и) | .. | 1 байт | [0 , 28 -1] [0,255] | ||
Int16 (S) | .. | 2 байти | [-215, 215-1] [-32768, 32767] | ||
UInt16 (S) | .. | 2 байти | [0, 216-1] [0,65535] | ||
Одиночний (S) | дійсне число | F | 4 байти | [1.5 10-45, 3.4 10+38] в абсолютному значенні | |
Double (S) | .. | D | 8 байт | [-1.7 10+308, 1.7 10+308] в абсолютному значенні | |
Десятичне число (S) | десятичне число | M | 16 байт | [1.0 10-28,7.9 10+28] у вигляді абсолютного значення з 28 значущими цифрами | |
Булеве значення (S) | .. | 1 байт | true, false | ||
Об'єкт (C) | посилання на об’єкт | посилання на об'єкт |
Вище наведено відповідність типів C# та їхніх еквівалентних типів .NET із коментарем (S), якщо цей тип є структурою, та (C), якщо тип є класом. Виявляється, що для 32-бітного цілого числа існує два можливі типи: int та Int32. Тип int є типом C#. Int32 — це структура, що належить до простору імен System. Отже, її повна назва — System.Int32. Тип int є псевдонімом у C#, який позначає структуру .NET System.Int32. Так само тип C# string є псевдонімом для типу .NET System.String. System.String є класом, а не структурою. Ці два поняття близькі, проте мають таку фундаментальну відмінність:
- змінною типу Structure керують через її значення
- змінною типу Classe керують через її адресу (посилання в об’єктно-орієнтованій мові).
Як structure, так і classe є складними типами, що мають атрибути та методи. Таким чином, можна написати:
У наведеному вище прикладі літерал 3 за замовчуванням має тип C# int, а отже, тип .NET System.Int32. Ця структура має метод GetType(), який повертає об’єкт, що інкапсулює характеристики типу даних System.Int32. Серед них властивість FullName повертає повну назву типу. Отже, бачимо, що літерал 3 є об’єктом, складнішим, ніж здається на перший погляд.
Ось програма, що ілюструє ці різні моменти:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
// приклад 1
int ent = 2;
float fl = 10.5F;
double d = -4.6;
string s = "essai";
uint ui = 5;
long l = 1000;
ulong ul = 1001;
byte octet = 5;
short sh = -4;
ushort ush = 10;
decimal dec = 10.67M;
bool b = true;
Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
}
}
}
- рядок 7: оголошення цілого числа ent
- рядок 19: тип змінної v можна отримати за допомогою v.GetType().FullName. Розмір структури S можна отримати за допомогою sizeof(S). Команда Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) виводить на екран текст, який є її першим параметром, замінюючи кожне позначення {i} значенням виразу parami.
- рядок 22: оскільки тип string позначає клас, а не структуру, оператор sizeof використовувати не можна.
Ось результат виконання:
У виведенні відображаються типи .NET, а не псевдоніми C#.
3.2.2. Запис літеральних даних
145, -7, 0xFF (шестнадцяткове) | |
100000L | |
134,789, -45E-18 (-45 × 10⁻¹⁸) | |
134.789F, -45E-18F (-45 10-18) | |
100000M | |
'A', 'b' | |
"сьогодні" "c:\\chap1\\paragraph3" @"c:\chap1\paragraph3" | |
true, false | |
new DateTime(1954,10,13) (рік, місяць, день) для 13.10.1954 |
Зверніть увагу на два літеральні рядки: "c:\\chap1\\paragraph3" та @"c:\chap1\paragraph3". У літеральних рядках символ \ інтерпретується. Так, «\n» позначає символ кінця рядка, а не послідовність двох символів \ та n. Якщо б ми хотіли отримати саме цю послідовність, слід було б написати «\\n», де послідовність \\ інтерпретується як один символ \. Можна також написати @"\n", щоб отримати той самий результат. Синтаксис @"текст" вимагає, щоб текст сприймався саме так, як він написаний. Іноді це називають рядком «verbatim».
3.2.3. Оголошення даних
3.2.3.1. Роль оголошень
Програма оперує даними, що характеризуються іменем та типом. Ці дані зберігаються в пам’яті. Під час компіляції програми компілятор виділяє кожному елементу даних місце в пам’яті, яке характеризується адресою та розміром. Він робить це, спираючись на оголошення, зроблені програмістом.
Крім того, вони дозволяють компілятору виявляти помилки в програмі. Так, операція
x=x*2;
буде визнана помилковою, якщо, наприклад, x є символьним рядком.
3.2.3.2. Оголошення констант
Синтаксис оголошення константи такий:
Наприклад:
Навіщо оголошувати константи?
- Програму буде легше читати, якщо константі надати змістовне ім’я:
- Вносити зміни в програму буде простіше, якщо «константа» раптом зміниться. Так, у наведеному вище прикладі, якщо ставка ПДВ зросте до 33%, єдиною зміною, яку потрібно буде внести, буде зміна оператора, що визначає її значення:
Якби в програмі явно використовувалося значення 0,186, то довелося б змінювати безліч команд.
3.2.3.3. Оголошення змінних
Змінна ідентифікується за іменем і належить до певного типу даних. У C# розрізняються великі та малі літери. Отже, змінні FIN та fin є різними.
Змінні можна ініціалізувати під час їх оголошення. Синтаксис оголошення однієї або декількох змінних такий:
де Identificateur_de_type — це попередньо визначений тип або тип, визначений програмістом. За бажанням змінну можна ініціалізувати одночасно з її оголошенням.
Також можна не вказувати точний тип змінної, використовуючи ключове слово var замість Identificateur_de_type:
Ключове слово var не означає, що змінні не мають конкретного типу. Змінна variablei має тип даних valeuri, який їй присвоєно. Ініціалізація тут є обов’язковою, щоб компілятор міг визначити тип змінної.
Ось приклад:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
int i=2;
Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
var j = 3;
Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
var aujourdhui = DateTime.Now;
Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
}
}
}
- рядок 6: явно типізовані дані
- рядок 7: (дані).GetType().Name — це скорочена назва (дані), (дані).GetType().FullName — це повна назва (дані)
- рядок 8: дані з неявним типом. Оскільки 3 має тип int, j матиме тип int.
- рядок 10: неявне типізування даних. Оскільки DateTime.Now має тип DateTime, то aujourdhui матиме тип DateTime.
Під час виконання отримуємо такий результат:
Змінна, тип якої неявним чином задано ключовим словом var, не може згодом змінити тип. Отже, після 10-го рядка коду не можна написати такий рядок:
var aujourdhui = "aujourd'hui";
Пізніше ми побачимо, що тип можна оголосити «на льоту» у виразі. У такому разі це анонімний тип — тип, якому користувач не дав імені. Ім’я цьому новому типу надасть компілятор. Якщо значення анонімного типу потрібно присвоїти змінній, єдиний спосіб оголосити її — це використати ключове слово var.
3.2.4. Перетворення між числами та рядками символів
nombre.ToString() | |
int.Parse(рядок) або System.Int32.Parse | |
long.Parse(рядок) або System.Int64.Parse | |
double.Parse (рядок) або System.Double.Parse (рядок) | |
float.Parse (рядок) або System.Float.Parse (рядок) |
Перетворення рядка на число може завершитися невдачею, якщо рядок не є дійсним числом. У такому випадку генерується фатальна помилка, яка називається винятком. Цю помилку можна обробити за допомогою наступного оператора try/catch:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Якщо функція не генерує винятку, то виконується наступна інструкція; в іншому випадку виконується тіло блоку catch, а потім — наступна інструкція. До питання обробки винятків ми повернемося пізніше. Ось програма, що демонструє деякі техніки перетворення між числами та символьними рядками. У цьому прикладі функція affiche виводить на екран значення свого параметра. Таким чином, affiche(S) виводить на екран значення S, де S має тип string.
using System;
namespace Chap1 {
class P01 {
static void Main(string[] args) {
// дані
const int i = 10;
const long l = 100000;
const float f = 45.78F;
double d = -14.98;
// число --> рядок
affiche(i.ToString());
affiche(l.ToString());
affiche(f.ToString());
affiche(d.ToString());
//логічне значення --> рядок
const bool b = false;
affiche(b.ToString());
// рядок --> ціле
int i1;
i1 = int.Parse("10");
affiche(i1.ToString());
try {
i1 = int.Parse("10.67");
affiche(i1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// рядок --> long
long l1;
l1 = long.Parse("100");
affiche(l1.ToString());
try {
l1 = long.Parse("10.675");
affiche(l1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// рядок --> double
double d1;
d1 = double.Parse("100,87");
affiche(d1.ToString());
try {
d1 = double.Parse("abcd");
affiche(d1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// рядок --> float
float f1;
f1 = float.Parse("100,87");
affiche(f1.ToString());
try {
d1 = float.Parse("abcd");
affiche(f1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
}// кінець main
public static void affiche(string S) {
Console.Out.WriteLine("S={0}",S);
}
}// кінець класу
}
У рядках 30–32 обробляється можливе виключення, яке може виникнути. e.Message — це повідомлення про помилку, пов’язане з виключенням e.
Отримано такі результати:
Слід зауважити, що дійсні числа у вигляді символьного рядка повинні містити кому, а не крапку. Отже, ми напишемо
але
3.2.5. Масиви даних
Масив у C# — це об’єкт, що дозволяє об’єднати дані одного типу під одним ідентифікатором. Його оголошення має такий вигляд:
n — кількість елементів, які може містити масив. Синтаксис Tableau[i] позначає елемент № i, де i належить до інтервалу [0,n-1]. Будь-яке посилання на елемент масиву Tableau[i], де i не належить до інтервалу [0,n-1], спричинить виняток. Масив можна ініціалізувати одночасно з його оголошенням:
або простіше:
Масиви мають властивість Length, яка позначає кількість елементів масиву.
Двовимірний масив можна оголосити так:
Type[,] масив = new Type[n,m];
де n — кількість рядків, m — кількість стовпців. Синтаксис Tableau[i,j] позначає елемент j у рядку i масиву. Двовимірний масив також можна ініціалізувати одночасно з його оголошенням:
або простіше:
Кількість елементів у кожному з вимірів можна отримати за допомогою методу GetLength(i), де i=0 позначає вимір, що відповідає першому індексу, i=1 — вимір, що відповідає другому індексу, …
Загальну кількість вимірів можна отримати за допомогою властивості Rank, а загальну кількість елементів — за допомогою властивості Length.
Масив масивів оголошується наступним чином:
Type[][] масив = new Type[n][];
Вищезазначена декларація створює масив із n рядків. Кожен елемент tableau[i] є посиланням на одновимірний масив. Ці посилання tableau[i] не ініціалізуються під час вищезазначеної декларації. Вони мають значення посилання null.
Наведений нижче приклад ілюструє створення масиву масивів:
// масив масивів
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//цикл
// ініціалізація
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//цикл for j
}//цикл for i
- рядок 2: масив noms із 3 елементами типу string[][]. Кожен елемент є покажчиком на масив (посиланням на об’єкт), елементи якого мають тип string[].
- рядки 3–5: 3 елементи масиву noms ініціалізовано. Кожен із них тепер «вказує» на масив елементів типу string[]. noms[i][j] — це елемент j масиву типу string [], на який посилається noms[i].
- рядок 9: ініціалізація елемента noms[i][j] у рамках подвійного циклу. Тут noms[i] — це масив із i+1 елементів. Оскільки noms[i] є масивом, то noms[i].Length — це кількість його елементів.
Ось приклад, що об’єднує три типи масивів, які ми щойно розглянули:
using System;
namespace Chap1 {
// масиви
using System;
// тестовий клас
public class P02 {
public static void Main() {
// ініціалізований одновимірний масив
int[] entiers = new int[] { 0, 10, 20, 30 };
for (int i = 0; i < entiers.Length; i++) {
Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
}//для
// ініціалізований двовимірний масив
double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
for (int i = 0; i < réels.GetLength(0); i++) {
for (int j = 0; j < réels.GetLength(1); j++) {
Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
}//цикл for j
}//цикл for i
// масив масивів
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//для
// ініціалізація
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//для j
}//для i
// виведення
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
}//для j
}//для i
}//Головна
}//клас
}//простір імен
Під час виконання ми отримуємо такі результати:
3.3. Елементарні оператори мови C#
Розрізняють
1 елементарні інструкції, що виконуються комп’ютером.
2 команди, що керують ходом виконання програми.
Елементарні інструкції стають очевидними, якщо розглянути структуру мікрокомп’ютера та його периферійних пристроїв.
![]() |
-
зчитування інформації з клавіатури
-
обробка інформації
-
виведення інформації на екран
3.3.1. Виведення інформації на екран
Існують різні оператори виведення інформації на екран:
Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)
де expression — це будь-який тип даних, який можна перетворити на рядок символів для відображення на екрані. Усі об’єкти C# або .NET мають метод ToString(), який використовується для здійснення цього перетворення.
Клас System.Console надає доступ до операцій запису на екран (Write, WriteLine). Клас Console має дві властивості Out та Error, які є потоками запису типу TextWriter:
- Console.WriteLine() еквівалентна Console.Out.WriteLine() і записує дані у потік Out, який зазвичай пов’язаний з екраном.
- Console.Error.WriteLine() записує дані у потік Error, який зазвичай також пов'язаний з екраном.
Потоки Out та Error можна перенаправити у текстові файли під час виконання програми, як ми побачимо незабаром.
3.3.2. Зчитування даних, введених з клавіатури
Потік даних, що надходить з клавіатури, позначається об’єктом Console.In типу TextReader. Цей тип об’єктів дозволяє зчитувати рядок тексту за допомогою методу ReadLine:
Клас Console надає метод ReadLine, який за замовчуванням пов’язаний із потоком In. Отже, можна написати:
Рядок, введений з клавіатури, зберігається у змінній ligne і потім може бути використаний програмою. Потік In можна перенаправити у файл, так само як потоки Out та Error.
3.3.3. Приклад вводу-виводу
Ось коротка програма, що ілюструє операції введення-виведення з клавіатури та на екран:
using System;
namespace Chap1 {
// тестовий клас
public class P03 {
public static void Main() {
// запис у потік Out
object obj = new object();
Console.Out.WriteLine(obj);
// запис у потік Error
int i = 10;
Console.Error.WriteLine("i=" + i);
// читання рядка, введеного з клавіатури
Console.Write("Tapez une ligne : ");
string ligne = Console.ReadLine();
Console.WriteLine("ligne={0}", ligne);
}//кінець функції main
}//кінець класу
}
- рядок 9: obj — це посилання на об’єкт
- рядок 10: obj виводиться на екран. За замовчуванням викликається метод obj.ToString().
- рядок 14: також можна написати:
Console.Error.WriteLine("i={0}",i);
Перший параметр «i={0}» — це формат відображення, інші параметри — вирази, що мають відобразитися. Елементи {n} — це «позиційні» параметри. Під час виконання параметр {n} замінюється значенням виразу № n.
Результат виконання такий:
- рядок 1: вивід, згенерований рядком 10 коду. Метод obj.ToString() вивів ім’я типу змінної obj: System.Object. Тип object є псевдонімом у C# для типу .NET System.Object.
3.3.4. Перенаправлення вводу-виводу
У DOS та UNIX існують три стандартні периферійні пристрої, які називаються:
- стандартний пристрій введення — за замовчуванням позначає клавіатуру та має номер 0
- стандартний пристрій виводу — за замовчуванням позначає екран і має номер 1
- стандартний пристрій помилок — за замовчуванням позначає екран і має номер 2
У C# потік запису Console.Out записує дані на пристрій 1, потік запису Console.Error записує дані на пристрій 2, а потік читання Console.In зчитує дані з пристрою 0.
Під час запуску програми в DOS або Unix можна вказати, які саме пристрої будуть пристроями 0, 1 та 2 для програми, що виконується. Розглянемо такий рядок команд:
За допомогою аргументів argi програми pg можна перенаправити стандартні пристрої вводу-виводу у файли:
стандартний вхідний потік № 0 перенаправляється у файл in.txt. Отже, у програмі потік Console.In отримуватиме дані з файлу in.txt. | |
перенаправляє вихід № 1 у файл out.txt. Це означає, що в програмі потік Console.Out запише свої дані у файл out.txt | |
те саме, але записані дані додаються до поточного вмісту файлу out.txt. | |
перенаправляє вихід № 2 у файл error.txt. Це призводить до того, що в програмі потік Console.Error запише свої дані у файл error.txt | |
те саме, але записані дані додаються до поточного вмісту файлу error.txt. | |
Пристрої 1 і 2 перенаправлені до файлів |
Слід зауважити, що для перенаправлення потоків вводу-виводу програми pg у файли програму pg змінювати не потрібно. Характер пристроїв 0, 1 та 2 визначає операційна система. Розглянемо таку програму:
using System;
namespace Chap1 {
// перенаправлення
public class P04 {
public static void Main(string[] args) {
// читання потоку In
string data = Console.In.ReadLine();
// запис потоку Out
Console.Out.WriteLine("écriture dans flux Out : " + data);
// запис потоку Error
Console.Error.WriteLine("écriture dans flux Error : " + data);
}//Головний
}//клас
}
Створимо виконуваний файл на основі цього вихідного коду:
![]() |
- у [1]: виконуваний файл створюється шляхом клацання правою кнопкою миші на проекті / Build
- в [2]: у вікні командного рядка виконуваний файл 04.exe було створено в папці bin/Release проекту.
Введемо такі команди у вікні командного рядка [2]:
- рядок 1: вносимо рядок test у файл in.txt
- рядки 2–3: виводимо вміст файлу in.txt для перевірки
- рядок 4: виконання програми 04.exe. Потік In перенаправляється у файл in.txt, потік Out — у файл out.txt, потік Error — у файл err.txt. Виконання не супроводжується виведенням даних на екран.
- рядки 5–6: вміст файлу out.txt. Цей вміст показує, що:
- файл in.txt було прочитано
- вивід на екран було перенаправлено до файлу out.txt
- рядки 7–8: аналогічна перевірка для файлу err.txt
Чітко видно, що потоки Out та In не записують дані на ті самі периферійні пристрої, оскільки їх вдалося перенаправити окремо.
3.3.5. Присвоєння значення виразу змінній
Тут нас цікавить операція variable=expression;
Вираз може бути арифметичним, відношенням, булевим або символьним
3.3.5.1. Інтерпретація операції присвоєння
Операція variable=expression;
сама по собі є виразом, обчислення якого відбувається наступним чином:
- обчислюється права частина присвоювання: результатом є значення V.
- значення V присвоюється змінній
- значення V є також значенням операції присвоєння, розглянутої цього разу як вираз.
Саме так працює операція
є допустимою. Через пріоритет обчислюватися буде оператор «=», що знаходиться найдалі праворуч. Отже, маємо
Вираз V2=вираз обчислюється і має значення V. Обчислення цього виразу призвело до присвоєння V значення V2. Наступний оператор = обчислюється у такій формі:
Значенням цього виразу знову є V. Його обчислення призводить до присвоєння V значенню V1.
Отже, операція V1=V2=вираз
є виразом, обчислення якого
- призводить до присвоєння значення expression змінним V1 та V2
- повертає значення expression.
Це можна узагальнити до виразу такого типу:
3.3.5.2. Арифметичний вираз
Оператори арифметичних виразів такі:
-
додавання
-
віднімання
* множення
/ ділення: результат є точним часткою, якщо хоча б один з операндів є дійсним числом. Якщо обидва операнди є цілими числами, результат є цілою часткою. Таким чином, 5/2 -> 2, а 5,0/2 -> 2,5.
% ділення: результатом є залишок незалежно від типу операндів, а частка є цілим числом. Отже, це операція за модулем.
Існують різні математичні функції. Ось деякі з них:
квадратний корінь | |
Косинус | |
Синус | |
Тангенс | |
x у y-му степені (x>0) | |
Експонента | |
Неперів логарифм | |
абсолютне значення |
тощо...
Усі ці функції визначені в класі C# під назвою Math. При їх використанні перед ними слід ставити префікс із назвою класу, в якому вони визначені. Отже, ми будемо писати:
Повне визначення класу Math виглядає так:





3.3.5.3. Пріоритети при обчисленні арифметичних виразів
Пріоритет операторів під час обчислення арифметичного виразу є таким (від найвищого до найнижчого):
Оператори одного блоку [ ] мають однаковий пріоритет.
3.3.5.4. Реляційні вирази
Використовуються такі оператори:
Пріоритети операторів
Результатом реляційного виразу є логічне значення false, якщо вираз є хибним, і true в іншому випадку.
Порівняння двох символів
Нехай є два символи C1 та C2. Їх можна порівняти за допомогою операторів
У цьому випадку порівнюються їхні коди Unicode, які є числами. Згідно з порядком Unicode маємо такі співвідношення:
Порівняння двох символьних рядків
Вони порівнюються символ за символом. Перша нерівність, виявлена між двома символами, зумовлює нерівність того самого знака для рядків.
Приклади:
Припустимо, що порівнюються рядки «Chat» і «Chien»
![]() |
Остання невідповідність дозволяє стверджувати, що «Chat» < «Chien».
Нехай потрібно порівняти рядки «Chat» і «Chaton». Рядки збігаються аж до вичерпання рядка «Chat». У цьому випадку вичерпаний рядок визнається «меншим». Отже, маємо співвідношення
Функції порівняння двох рядків
Для перевірки рівності або нерівності двох рядків можна використовувати відносні оператори == та !=, а також метод Equals класу System.String. Для відношень < <= > >= слід використовувати метод CompareTo класу System.String:
using System;
namespace Chap1 {
class P05 {
static void Main(string[] args) {
string chaine1="chat", chaine2="chien";
int n = chaine1.CompareTo(chaine2);
bool egal = chaine1.Equals(chaine2);
Console.WriteLine("i={0}, egal={1}", n, egal);
Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
}
}
}
У рядку 7 змінна i матиме значення:
0 якщо обидва рядки збігаються
1 якщо рядок № 1 > рядок № 2
-1 якщо рядок № 1 < рядок № 2
У рядку 8 змінна egal матиме значення true, якщо обидва рядки рівні, і false в іншому випадку. У рядку 10 використовуються оператори == та != для перевірки рівності або нерівності двох рядків.
Результати виконання:
3.3.5.5. Булеві вирази
Допустимі оператори: AND (&&) OR (||) NOT (!). Результатом булевого виразу є логічне значення.
Пріоритети операторів:
- !
- &&
- ||
double x = 3.5;
bool valide = x > 2 && x < 4;
Оператори порівняння мають пріоритет над операторами && та ||.
3.3.5.6. Бітова обробка
Оператори
Нехай i та j — два цілі числа.
зсуває i на n бітів вліво. Вхідні біти — нулі. | |
зсуває i на n бітів вправо. Якщо i є цілим числом зі знаком (signed char, int, long), біт знака зберігається. | |
виконує побітову логічну операцію ET над i та j. | |
виконує побітову логічну операцію «і» над i та j. | |
доповнює i до 1 | |
виконує операцію OU EXCLUSIF над i та j |
Нехай маємо такий код:
short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
- формат {0:x4} відображає параметр № 0 у шістнадцятковому форматі (x) з 4 символами (4).
Результати виконання такі:
3.3.5.7. Комбінація операторів
a=a+b можна записати як a+=b
a=a-b можна записати як a-=b
Те саме стосується операторів /, %, *, <<, >>, &, |, ^. Таким чином, a=a/2; можна записати як a/=2;
3.3.5.8. Оператори інкременту та декременту
Запис variable++ означає variable=variable+1 або variable+=1
Запис variable-- означає variable=variable-1 або variable-=1.
3.3.5.9. Тернарний оператор?
Вираз
оцінюється наступним чином:
1 вираження expr_cond обчислюється. Це умовне вираження, значенням якого є vrai або faux
2 Якщо вона істинна, значенням виразу стає expr1, а expr2 не обчислюється.
3 Якщо вона є хибною, відбувається зворотне: значенням виразу стає expr2, а expr1 не обчислюється.
Операція i=(j>4 ? j+1:j-1); присвоїть змінній i значення j+1, якщо j>4, і j-1 в іншому випадку. Це те саме, що написати if(j>4) i=j+1; else i=j-1;, але більш лаконічно.
3.3.5.10. Загальний порядок пріоритету операторів
gd | |
dg | |
dg | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
dg | |
dg |
gd вказує, що при рівному пріоритеті дотримується порядок зліва направо. Це означає, що коли у виразі є оператори з однаковим пріоритетом, то спочатку обчислюється оператор, розташований найдалі ліворуч у виразі. dg вказує на порядок справа наліво.
3.3.5.11. Зміни типу
У виразі можна тимчасово змінити кодування значення. Це називається зміною типу даних або, англійською, type casting. Синтаксис зміни типу значення у виразі такий:
Тоді значення набуває вказаного типу. Це призводить до зміни кодування значення.
using System;
namespace Chap1 {
class P06 {
static void Main(string[] args) {
int i = 3, j = 4;
float f1=i/j;
float f2=(float)i/j;
Console.WriteLine("f1={0}, f2={1}",f1,f2);
}
}
}
- у рядку 7 f1 матиме значення 0,0. Ділення 3/4 є цілочисельним, оскільки обидва операнди мають тип int.
- у рядку 8 (float)i — це значення i, перетворене на float. Тепер маємо ділення дійсного числа типу float на ціле число типу int. Далі виконується ділення між дійсними числами. Значення j також буде перетворено у тип float, після чого відбудеться ділення двох дійсних чисел. f2 тоді матиме значення 0,75.
Ось результати виконання:
В операції (float)i:
- i — це значення, точно закодоване на 2 байтах
- (float), а i — це те саме значення, закодоване з наближенням у форматі 4-байтового числа з плаваючою комою
Отже, відбувається перекодування значення i. Це перекодування відбувається лише на час обчислення, при цьому змінна i завжди зберігає свій тип int.
3.4. Інструкції для контролю ходу виконання програми
3.4.1. Зупинка
Метод Exit, визначений у класі Environment, дозволяє зупинити виконання програми.
syntaxe void Exit(int status)
action arrête le processus en cours et rend la valeur status au processus père
Exit призводить до завершення поточного процесу та повертає контроль викличному процесу. Значення status може бути використане цим процесом. У DOS ця змінна status передається у системну змінну ERRORLEVEL, значення якої можна перевірити у пакетному файлі. У Unix, з інтерпретатором команд Bourne Shell, значення status зберігається у змінній $?.
припинить виконання програми зі значенням статусу 0.
3.4.2. Проста структура вибору
Примітки:
- умова взята в дужки.
- кожна дія закінчується крапкою з комою.
- фігурні дужки не завершуються крапкою з комою.
- фігурні дужки потрібні лише в тому випадку, якщо є більше ніж одна дія.
- клаузула else може бути відсутньою.
- клаузули then немає.
Алгоритмічним еквівалентом цієї структури є структура «якщо … то … інакше»:
![]() |
Приклад
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
Структури вибору можна вкладати одна в одну:
Іноді виникає така проблема:
using System;
namespace Chap1 {
class P07 {
static void Main(string[] args) {
int n = 5;
if (n > 1)
if (n > 6)
Console.Out.WriteLine(">6");
else Console.Out.WriteLine("<=6");
}
}
}
У наведеному вище прикладі, до якого if відноситься else у рядку 10? За правилами, else завжди пов'язаний із найближчим if: у наведеному прикладі це if(n>6), у рядку 8. Розглянемо інший приклад:
if (n2 > 1) {
if (n2 > 6) Console.Out.WriteLine(">6");
} else Console.Out.WriteLine("<=1");
Тут ми хотіли прив’язати else до if(n2>1), а не else до if(n2>6). З огляду на попереднє зауваження, ми змушені використовувати фігурні дужки: if(n2>1) {...} else ...
3.4.3. Структура умов
Синтаксис такий:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default:
actions_sinon;
break;
}
Примітки
- значення контрольного виразу для switch може бути цілим числом, символом або рядком символів
- контрольний вираз взято в дужки.
- Умова default може бути відсутньою.
- Значення vi є можливими значеннями виразу. Якщо значення виразу дорівнює vi, виконуються дії, що йдуть після клаузули case vi.
- Інструкція break виводить із структури case.
- Кожен блок інструкцій, пов'язаний із значенням vi, повинен закінчуватися інструкцією переходу (break, goto, return тощо), інакше компілятор видасть помилку.
Приклад
В алгоритміці
selon la valeur de choix
cas 0
fin du module
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
На C#
3.4.4. Структури повторення
3.4.4.1. Відома кількість повторень
Структура for
Синтаксис такий:
for (i=id;i<=if;i=i+ip){
actions;
}
Примітки
- 3 аргументи for знаходяться в дужках і розділені крапками з комою.
- Кожна дія функції for закінчується крапкою з комою.
- фігурна дужка потрібна лише в тому випадку, якщо є більше ніж одна дія.
- за фігурною дужкою не ставиться крапка з комою.
Алгоритмічним еквівалентом є структура pour:
яку можна перетворити на структуру tantque:
Структура foreach
Синтаксис такий:
foreach (Type variable in collection)
instructions;
}
Примітки
- collection — це колекція об’єктів, яку можна перелічити. Колекцією об’єктів, яку ми вже знаємо, є масив
- Type — це тип об’єктів колекції. Для масиву це був би тип елементів масиву
- variable — це локальна змінна циклу, яка послідовно прийматиме всі значення колекції.
Отже, такий код:
string[] amis = { "paul", "hélène", "jacques", "sylvie" };
foreach (string nom in amis) {
Console.WriteLine(nom);
}
виведе:
3.4.4.2. Кількість повторень невідома
У C# існує безліч структур для цього випадку.
Конструкція while
while(condition){
actions;
}
Цикл виконується, доки виконується умова. Цикл може ніколи не виконатися.
Примітки:
- умова взята в дужки.
- кожна дія закінчується крапкою з комою.
- фігурна дужка потрібна лише в тому випадку, якщо є більше ніж одна дія.
- після фігурної дужки не ставиться крапка з комою.
Відповідною алгоритмічною структурою є структура «доки»:
Цикл «повторювати до» (do while)
Синтаксис такий:
do{
instructions;
}while(condition);
Цикл виконується доти, доки умова не стане хибною. У цьому випадку цикл виконується щонайменше один раз.
Примітки
- умова взята в дужки.
- Кожна дія закінчується крапкою з комою.
- фігурна дужка потрібна лише в тому випадку, якщо є більше ніж одна дія.
- після фігурної дужки не ставиться крапка з комою.
Відповідна алгоритмічна структура — це структура «повторювати … до»:
Структура «for»
Синтаксис такий:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
Цикл виконується, доки умова є істинною (оцінюється перед кожним проходом циклу). Instructions_départ виконуються перед першим входом у цикл. Instructions_fin_boucle виконуються після кожного проходу циклу.
Примітки
- різні інструкції в instructions_depart та instructions_fin_boucle розділені комами.
Відповідна алгоритмічна структура така:
Приклади
Усі наведені нижче фрагменти коду обчислюють суму перших 10 цілих чисел.
int i, somme, n=10;
for (i = 1, somme = 0; i <= n; i = i + 1)
somme = somme + i;
for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;
i = 1; somme = 0;
while (i <= n) { somme += i; i++; }
i = 1; somme = 0;
do somme += i++;
while (i <= n);
3.4.4.3. Інструкції з управління циклом
виходить із циклу for, while, do ... while. | |
переходить до наступної ітерації циклів for, while, do ... while |
3.5. Обробка винятків
Багато функцій C# можуть генерувати винятки, тобто помилки. Якщо функція може генерувати виняток, програміст повинен його обробляти, щоб зробити програми більш стійкими до помилок: завжди слід уникати неконтрольованого «зависання» програми.
Обробка винятку здійснюється за такою схемою:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Якщо функція не генерує винятку, то виконується наступна інструкція; в іншому випадку виконується тіло блоку catch, а потім — наступна інструкція. Зверніть увагу на наступні моменти:
- e — це об’єкт типу Exception або його похідного. Можна бути точнішим, використовуючи такі типи, як IndexOutOfRangeException, FormatException, SystemException тощо: існує кілька типів винятків. Записуючи catch (Exception e), ми вказуємо, що хочемо обробляти всі типи винятків. Якщо код блоку try може генерувати кілька типів винятків, можна бути більш конкретним, обробляючи виняток за допомогою кількох блоків catch:
try{
code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
traiter l'exception e1
}
} catch ( FormatException e2){
traiter l'exception e2
}
instruction suivante
- До умов try/catch можна додати умову finally:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
finally{
code exécuté après try ou catch
}
instruction suivante
Незалежно від того, чи є виняток, код у клаузулі finally завжди буде виконано.
- У клаузулі catch може виникнути ситуація, коли не потрібно використовувати доступний об’єкт Exception. Замість того, щоб писати catch (Exception e){..}, слід написати catch(Exception){...} або, ще простіше, catch {...}.
- Клас Exception має властивість Message, яка містить детальне повідомлення про помилку, що сталася. Отже, якщо потрібно відобразити це повідомлення, слід написати:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
...
}//catch
- Клас Exception має метод ToString, який повертає рядок, що вказує тип винятку, а також значення властивості Message. Таким чином, можна написати:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
...
}//catch
Також можна записати:
Компілятор присвоїть параметру {0} значення ex.ToString().
Наступний приклад ілюструє виняток, що виникає внаслідок використання елемента масиву, який не існує:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// оголошення та ініціалізація масиву
int[] tab = { 0, 1, 2, 3 };
int i;
// виведення масиву за допомогою циклу for
for (i = 0; i < tab.Length; i++)
Console.WriteLine("tab[{0}]={1}", i, tab[i]);
// виведення масиву за допомогою циклу for each
foreach (int élmt in tab) {
Console.WriteLine(élmt);
}
// генерація винятку
try {
tab[100] = 6;
} catch (Exception e) {
Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
return;
}//try-catch
finally {
Console.WriteLine("finally ...");
}
}
}
}
У наведеному вище прикладі рядок 18 спричинить виняток, оскільки масив tab не містить елемента № 100. Виконання програми дає такі результати:
- рядок 9: сталося виключення [System.IndexOutOfRangeException]
- рядок 11: виконано оператор finally (рядки 23–25) коду, хоча в рядку 21 була інструкція return для виходу з методу. Слід зауважити, що оператор finally завжди виконується.
Ось ще один приклад, де обробляється виняток, спричинений присвоєнням рядка символів змінній типу цілого числа, коли рядок не є цілим числом:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// приклад 2
// Запитується ім'я
Console.Write("Nom : ");
// прочитати відповідь
string nom = Console.ReadLine();
// запитується вік
int age = 0;
bool ageOK = false;
while (!ageOK) {
// питання
Console.Write("âge : ");
// читання та перевірка відповіді
try {
age = int.Parse(Console.ReadLine());
ageOK = age>=1;
} catch {
}//try-catch
if (!ageOK) {
Console.WriteLine("Age incorrect, recommencez...");
}
}//цикл while
// остаточне відображення
Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
}
}
}
- рядки 15–27: цикл введення віку людини
- рядок 20: рядок, введений з клавіатури, перетворюється на ціле число за допомогою методу int.Parse. Цей метод генерує виняток, якщо перетворення неможливе. Тому операція була розміщена в блоці try / catch.
- рядки 22–23: якщо генерується виняток, виконання переходить у блок catch, де нічого не відбувається. Таким чином, логічна змінна ageOK, значення якої було встановлено на false у рядку 14, залишиться на рівні false.
- рядок 21: якщо ми дійшли до цього рядка, це означає, що перетворення string -> int пройшло успішно. Однак перевіряємо, чи отримане ціле число дійсно більше або дорівнює 1.
- рядки 24–26: якщо вік вказано неправильно, видається повідомлення про помилку.
Деякі результати виконання:
3.6. Приклад програми — V1
Запропонуємо написати програму, що дозволяє обчислити податок платника податків. Розглянемо спрощений випадок, коли платник податків має задекларувати лише свою заробітну плату (дані за 2004 рік щодо доходів за 2003 рік):
- розраховуємо кількість часткок працівника nbParts=nbEnfants/2 +1, якщо він не одружений, nbEnfants/2+2, якщо він одружений, де nbEnfants — кількість його дітей.
- якщо у нього є щонайменше троє дітей, він отримує на пів частки більше
- його оподатковуваний дохід R обчислюється за формулою R=0,72*S, де S — його річна заробітна плата
- розраховується його сімейний коефіцієнт QF = R/nbParts
- розраховується його податок I. Розглянемо таку таблицю:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23888 | 0,2826 | 2668,39 |
38868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Кожен рядок має 3 поля. Щоб обчислити податок I, шукаємо перший рядок, у якому QF <= поле1. Наприклад, якщо QF = 5000, то знайдемо рядок
Тоді податок I дорівнює 0,0683*R - 291,09*nbParts. Якщо QF є таким, що співвідношення QF<=поле1 ніколи не виконується, то використовуються коефіцієнти останнього рядка. У даному випадку:
0 0,4809 9505,54
що дає податок I = 0,4809*R - 9505,54*nbParts.
Відповідна програма на C# виглядає так:
using System;
namespace Chap1 {
class Impots {
static void Main(string[] args) {
// таблиці даних, необхідні для розрахунку податку
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
// отримання сімейного стану
bool OK = false;
string reponse = null;
while (!OK) {
Console.Write("Etes-vous marié(e) (O/N) ? ");
reponse = Console.ReadLine().Trim().ToLower();
if (reponse != "o" && reponse != "n")
Console.Error.WriteLine("Réponse incorrecte. Recommencez");
else OK = true;
}//while
bool marie = reponse == "o";
// кількість дітей
OK = false;
int nbEnfants = 0;
while (!OK) {
Console.Write("Nombre d'enfants : ");
try {
nbEnfants = int.Parse(Console.ReadLine());
OK = nbEnfants >= 0;
} catch {
}// try
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// while
// зарплата
OK = false;
int salaire = 0;
while (!OK) {
Console.Write("Salaire annuel : ");
try {
salaire = int.Parse(Console.ReadLine());
OK = salaire >= 0;
} catch {
}// try
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// while
// розрахунок кількості часток
decimal nbParts;
if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// оподатковуваний дохід
decimal revenu = 0.72M * salaire;
// сімейний коефіцієнт
decimal QF = revenu / nbParts;
// пошук податкової групи, що відповідає QF
int i;
int nbTranches = limites.Length;
limites[nbTranches - 1] = QF;
i = 0;
while (QF > limites[i]) i++;
// податок
int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
// виведення результату
Console.WriteLine("Impôt à payer : {0} euros", impots);
}
}
}
- рядки 7–9: числові значення мають суфікс M (Money), щоб вони мали тип decimal.
- рядок 16:
- Console.ReadLine() повертає рядок C1, введений з клавіатури
- C1.Trim() видаляє пробіли на початку та в кінці C1 — повертає рядок C2
- C2.ToLower() повертає рядок C3, який є рядком C2, перетвореним у нижній регістр.
- рядок 21: булеве значення marie отримує значення true або false з відношення reponse=="o"
- рядок 29: рядок, введений з клавіатури, перетворюється на тип int. Якщо перетворення не вдається, генерується виняток.
- рядок 30: логічне значення OK отримує значення true або false з відношення nbEnfants>=0
- рядки 55–56: не можна просто написати nbEnfants/2. Якби nbEnfants дорівнювало 3, ми б отримали 3/2 — цілочисельне ділення, яке дало б 1, а не 1,5. Тому записуємо (decimal)nbEnfants, щоб один з операндів ділення став дійсним числом і таким чином отримати ділення між дійсними числами.
Ось приклади виконання:
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros
3.7. Аргументи головної програми
Головна функція Main може приймати як параметр масив рядків: String[] (або string[]). Цей масив містить аргументи командного рядка, що використовується для запуску програми. Отже, якщо запустити програму P за допомогою наступної команди (DOS):
P arg0 arg1 … argn
і якщо функція Main оголошена наступним чином:
то отримаємо args[0]="arg0", args[1]="arg1" … Ось приклад:
using System;
namespace Chap1 {
class P10 {
static void Main(string[] args) {
// виводиться список отриманих параметрів
Console.WriteLine("Il y a " + args.Length + " arguments");
for (int i = 0; i < args.Length; i++) {
Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
}
}
}
}
Щоб передати аргументи до коду, що виконується, слід вчинити так:
![]() |
- у [1]: клацніть правою кнопкою миші на проекті / Properties
- у [2]: вкладка [Debug]
- у [3]: ввести аргументи
Виконання дає такі результати:
Слід зауважити, що підпис
є дійсним, якщо функція Main не очікує параметрів.
3.8. Переліки
Перелік — це тип даних, область значень якого складається з набору цілочисельних констант. Розглянемо програму, яка має обробляти записи про іспит. Їх буде п’ять: Passable, AssezBien, Bien, TrèsBien, Excellent.
Тоді можна визначити перелік для цих п’яти констант:
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
Внутрішньо ці п’ять констант кодуються послідовними цілими числами, починаючи з 0 для першої константи, 1 для наступної тощо. Змінну можна оголосити такою, що приймає ці значення з переліку:
// змінна, яка приймає значення з переліку «Mentions»
Mentions maMention = Mentions.Passable;
Змінну можна порівнювати з різними можливими значеннями переліку:
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
Можна отримати всі значення переліку:
// список позначок у вигляді рядків
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
Так само, як простий тип int еквівалентний структурі System.Int32, простий тип enum еквівалентний структурі System.Enum. Ця структура має статичний метод GetValues, який дозволяє отримати всі значення переліченого типу, що передається як параметр. Цей параметр повинен бути об’єктом типу Type, який є класом інформації про тип даних. Тип змінної v отримується за допомогою v.GetType(). Тип типу T отримується за допомогою typeof(T). Отже, тут maMention.GetType() повертає об’єкт Type з переліку Mentions, а Enum.GetValues(maMention.GetType()) — список значень переліку Mentions.
Якщо тепер написати
//перелік позначок у вигляді цілих чисел
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
Рядок 2, змінна циклу має тип цілого числа. Тоді ми отримуємо список значень переліку у вигляді цілих чисел. Об’єкт типу System.Type, що відповідає типу даних Mentions, отримується за допомогою typeof(Mentions). Можна було б написати, як і раніше, maMention.GetType().
Наступна програма ілюструє те, про що щойно йшлося:
using System;
namespace Chap1 {
class P11 {
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
static void Main(string[] args) {
// змінна, значення якої беруться з переліку «Mentions»
Mentions maMention = Mentions.Passable;
// виведення значення змінної
Console.WriteLine("mention=" + maMention);
// тест із значенням з переліку
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
// список позначок у вигляді рядків
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
//список позначок у вигляді цілих чисел
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
}
}
}
Результати виконання такі:
3.9. Передача параметрів у функцію
Тут ми розглянемо спосіб передачі параметрів у функцію. Розглянемо таку статичну функцію:
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
У визначенні функції, рядок 1, a називається формальним параметром. Він використовується виключно для потреб визначення функції changeInt. Його цілком можна було б назвати b. Тепер розглянемо використання цієї функції:
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
Тут, в операторі рядка 3, ChangeInt(age), age є фактичним параметром, який передасть своє значення формальному параметру a. Нас цікавить, як формальний параметр отримує значення ефективного параметра.
3.9.1. Передача за значенням
Наступний приклад показує, що параметри функції за замовчуванням передаються за значенням, тобто значення фактичного параметра копіюється у відповідний формальний параметр. Ми маємо дві окремі сутності. Якщо функція змінює формальний параметр, фактичний параметр при цьому ніяк не змінюється.
using System;
namespace Chap1 {
class P12 {
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
}
}
Отримано такі результати:
Значення 20 фактичного параметра age було скопійовано у формальний параметр a (рядок 10). Потім цей параметр було змінено (рядок 11). Фактичний параметр залишився без змін. Цей спосіб передачі підходить для вхідних параметрів функції.
3.9.2. Передача за посиланням
При передачі за посиланням фактичний і формальний параметри є одним і тим самим об’єктом. Якщо функція змінює формальний параметр, фактичний параметр також змінюється. У C# перед обома параметрами має стояти ключове слово ref:
Ось приклад:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// приклад 2
int age2 = 20;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
та результати виконання:
Фактичний параметр змінився відповідно до зміни формального параметра. Цей спосіб передачі підходить для вихідних параметрів функції.
3.9.3. Передача за посиланням за допомогою ключового слова out
Розглянемо попередній приклад, у якому змінна age2 не ініціалізується до виклику функції changeInt:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// приклад 2
int age2;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
Під час компіляції цієї програми виникає помилка:
Цю проблему можна обійти, присвоївши початкову значення змінній age2. Також можна замінити ключове слово ref на ключове слово out. У цьому випадку ми вказуємо, що параметр є виключно вихідним і тому не потребує початкового значення:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// приклад 3
int age3;
ChangeInt3(out age3);
Console.WriteLine("Paramètre effectif age3=" + age3);
}
private static void ChangeInt3(out int a3) {
a3 = 30;
Console.WriteLine("Paramètre formel a3=" + a3);
}
}
}
Результати виконання такі:





