Цикл foreach

При обробці масивів і рядків дуже часто, використають оператори циклів. Всі оператори циклів, які застосовуються в мові ISO/ANSI C++, також використаються й у и в C++/CLI. Але мова C++/CLI пропонує ще один розкішний тип циклу, називана for each. Він призначений спеціально для ітерації по об'єктах, що належить до певного набору. Застосуємо цикл for each для ітерації по символах рядка.

Використання циклу for each для доступу до кожного символу в String

 

Створіть новий проект консольної програми CLR і модифікуйте код у такий спосіб:

 

// Аналіз рядка за допомогою циклу for each

#include "stdafx.h"

using namespace System;

int main()

{

int vowels = 0;

int consonants = 0;

String^ proverb = L"This is a simple text for \"for each\" cycle" ;

for each (wchar_t ch in proverb)

{

if(Char::IsLetter(ch))

{

ch = Char::ToLower(ch) ; // Перетворити в нижній регістр

switch(ch)

{

case 'a':

case 'e':

case 'i':

case 'о':

case 'u':

++vowels;

break;

default:

++consonants;

break;

}

}

}

Console::WriteLine(proverb);

Console::WriteLine(L"Текстовий рядок містить {0} голосних й {1} згодних.",vowels, consonants);

return 0;

}

Результат роботи програми буде наступний:

 

 

Можна також виконати ітерацію по всіх елементах масиву, скориставшись циклом for each:

array<int>~ values = { 3, 5, 6, 8, б};

for each(int item in values)

{

item = 2*item + 1;

Console::Write("{0,5}",item);

}

Усередині циклу змінна item посилається на кожен елемент масиву по черзі. Перший оператор у тілі циклу заміняє значення поточного елемента подвоєним старим його значенням плюс одиниця. Другий оператор циклу виводить нове значення, вирівнюючи його вправо в поле шириною п'ять символів, тому в результаті виходить наступний висновок:

7 11 13 17 13

 

Розглянемо ще кілька прикладів використання рядків і масивів мовою C++/CLI.

Як уже було сказано раніше, об'єкт String - незмінний, тобто будучи один раз инициализированным, він не може бути змінений. Клас String містить методи, які можна використати для зміни об'єкта String , такі, як Insert (Вставка), Replace (Заміна) і PadLeft. Однак, у дійсності, зазначені методи ніколи не змінюють вихідний об'єкт. Замість цього вони повертають новий об'єкт String , що містить змінений текст. Якщо ми хочемо одержати можливість змінювати вихідні дані, то варто використати клас StringBuilder, а не на сам клас String. У наступному прикладі показано, що метод Replace (Заміна) не впливає на вміст вихідного об'єкта String (Рядок), але змінює вміст об'єкта StringBuilder:

 

#include "stdafx.h"

using namespace System;

using namespace System::Text; // для StringBuilder

 

void main(void)

{

Console::WriteLine("String is immutable:"); // ("Рядок є незмінною: ") ;

String ^psl = "Hello World"; // Рядок "Привіт, Мир"

 

String ^ps2 = psl->Replace('H','J'); // Заміна

Console::WriteLine(psl) ;

Console::WriteLine(ps2);

Console::WriteLine("StringBuilder can be modified:"); // ("StringBuilder може змінюватися: ") ;

StringBuilder ^psbl = gcnew StringBuilder("Hello World");// Привіт, Мир

StringBuilder ^psb2 = psbl->Replace('H', 'J'); // Заміна

Console::WriteLine(psbl);

Console::WriteLine(psb2);

}

 

Метод ToString забезпечує подання об'єкта String для будь-якого

керованого типу даних. Хоча метод ToString не є автоматично доступним для некерованих класів, він доступний для впакованих значимих й упакованих примітивних типів, таких, як int або float (із плаваючою крапкою).

Метод ToString найбільше часто використається для висновку інформації, а також при налагодженні, і створювані керовані класи звичайно заміняють ToString так, щоб він повертав певну розроблювачем, удобочитаемую інформацію про об'єкт. Метод Object::ToString просто повертає повне ім'я класу даного об'єкта і його реалізація (не особливо корисна, втім) доступна через спадкування будь-якому керованому типу. Наступний приклад демонструє деякі аспекти роботи методу ToString:

 

#include "stdafx.h"

using namespace System;

// клас збирача сміття ClassWithToString

value class ClassWithToString

{

public:

virtual String ^ToString() override // перевантаження методу

{

// повернути новий Рядок ("SomeClass - скасування");

return gcnew String("SomeClass - override");

}

};

value class ClassNoToString // клас збирача сміття ClassNoToString

{

};

void main(void)

{

int i = 3;

 

Console::WriteLine(i.ToString()); // перевантаження String^

Console::WriteLine(i); // перевантаження int

 

ClassWithToString ^psc = gcnew ClassWithToString;

Console::WriteLine(psc->ToString()); // перевантаження String^

Console::WriteLine(psc); // перевантаження Object^

 

ClassNoToString ^psoc = gcnew ClassNoToString;

Console::WriteLine(psoc->ToString()); // перевантаження String^

Console::WriteLine(psoc); // перевантаження Object^

 

array<int>^ agc= gcnew array<int>(5); // масив збирача сміття

Console::WriteLine(agc->ToString()); // перевантаження String

Console::WriteLine(agc); // перевантаження Object*

}

Результат роботи програми наведений нижче.

 

 

Помітьте, що метод ToString можна викликати явно як аргумент перевантаженого методу WriteLine об'єкта String, а можна викликати перевантажений метод WriteLine об'єкта String, що сам викличе метод ToString. Помітьте також, що навіть керований масив (який, насправді, є керованим типом) підтримує метод ToString.

Керовані строкові лiтерали String (Рядок) і некеровані строкові лiтерали ASCII й Unicode (завдяки автоматичному впакуванню) можна використати у вираженнях, у яких очікується використання керованого строкового об'єкта String (Рядок). Однак керований строковий об'єкт String (Рядок) не можна використати там, де очікується поява змінних некерованих типів. Наступний приклад доводить це. Зверніть увагу на закоментованi рядки. Спробуйте їх разкоментeвати і подивитеся, що відбудеться.

 

//MixingStringTypes.срр

 

#include "stdafx.h"

#include < stdio.h >

#include <stdlib.h>

#include <wchar.h> // для wchar__t

 

using namespace System;

 

void ExpectingManagedString(String ^str) // Рядок керована

{

Console::WriteLine("From ManagedString : {0}",str);

}

void ExpectingASCIIString(char *str) // Рядок ASCII

{

printf("From ASCIIString : %s \n",str);

}

void ExpectingUnicodeString(wchar_t *str) // Рядок Unicode

{

printf("From UnicodeString : %S\n",str);

}

 

void main(void)

{

char *simp_str="Simple line";

wchar_t *uni_str=L"Unicode line";

String ^man_str=L"Manage line";

 

char uni2simp[30];

// очікується керований тип

ExpectingManagedString(man_str);

ExpectingManagedString("Simple line - literal");

ExpectingManagedString(gcnew String(simp_str));

ExpectingManagedString(L"Unicode line - literal") ;

ExpectingManagedString(gcnew String(uni_str)) ;

Console::WriteLine();

// очікується простий символьний рядок

//ExpectingASCIIString((char*)man_str );

ExpectingASCIIString(simp_str);

ExpectingASCIIString("Simple line - literal") ;

wcstombs(uni2simp, uni_str, wcslen(uni_str);

ExpectingASCIIString(uni2simp) ;

// ExpectingASCIIString(L"Unicode line - literal") ;

Console::WriteLine();

// очікується рядок Unicode

ExpectingUnicodeString(uni_str);

ExpectingUnicodeString(L"Unicode line - literal");

//ExpectingUnicodeString ((wchar_t*)simp_str);

//ExpectingUnicodeString((wchar_t*)man_str);

}

 

Результат роботи виглядає в такий спосіб

 

 

Наступний приклад показує, як можна використати оброблювач виключень при спробі доступу до неіснуючого елемента керованого масиву. Зверніть увагу, що масив містить п'ять елементів, а в циклі виробляється спроба установити значення шостого. Програма у звичайному C++ виконала б таку дію, зменів уміст пам'яті за межами масиву. Ніхто не скаже точно, чим це могло б закінчитися. При перевірці коректності адреси виконуються дві дії: по-перше, запобігає зміна вмісту пам'яті за межами масиву; по-друге, програмі повідомляється, що виникла подібна ситуація, тим самим даючи можливість виправити помилку ще на стадії тестування. У звичайному C++ така помилка часто не проявляється доти, поки програма, по незрозумілих причинах, не припиняє работу, звичайно в місці коду, що далеко відстоїть від самої помилки.

 

#include "stdafx.h"

using namespace System;

 

void main ()

{

array<int>^ intArray = gcnew array<int> (5); // керований масив з 5 эл-тов

for (int i=-2; i<6; i++) // більше чим є!!!

{

try

{

intArray[i] = i;

Console::WriteLine("Assigned A[{0}]={1}",i,i);

}

catch (IndexOutOfRangeException^ piore)

{

// повідомлення про настання виняткової ситуації

Console::WriteLine("Oooops!");

Console::WriteLine(piore->Message+" Index={0}",i);

}

}

}

 

 

У наступному прикладі ілюструється робота з масивами, і рівняються керований двовимірний масив і звичайний некерований двовимірний же масив. Звернете ще раз увага на те, що при роботі з некерованим масивом використається старий синтаксис доступу до елементів масиву [][], тоді як при роботі з керованим масивом, що є істинно двовимірним, використається синтаксис [ , ]. Хоча в цьому прикладі при використанні синтаксису [ ] [ ] кожний з пiдмасивiв має однакову кількість елементів, в інших випадках вони можуть мати різні розміри (т.зв. масив з нерівним правим краєм). Синтаксис [, ] припускає використання істинно прямокутного масиву.

 

#include "stdafx.h"

using namespace System;

 

void main ()

{

// керований одномірний масив int

Console::WriteLine("managed ID array of int");

array<int>^ intArray = gcnew array<int>(5);

for (int i=0; i<intArray->Length; i++)

{

intArray[i] = i;

Console::Write(intArray[i]); // Висновок елемента масиву

Console::Write("\t");

}

Console::WriteLine();

 

// керований двовимірний масив Рядків керований тип, що використає

Console::WriteLine("managed 2D array of Strings");

array<String^,2>^ str2DArray = gcnew array<String^,2> (2,3); // новий Рядок

// L8-13.cpp : main project file.

//Arrayl.срр

#include "stdafx.h"

using namespace System;

 

void main ()

{

// керований одномірний масив int

Console::WriteLine("managed 1D array of int");

array<int>^ intArray = gcnew array<int>(5);

for (int i=0; i<intArray->Length; i++)

{

intArray[i] = i;

Console::Write(intArray[i]); // Запис

Console::Write("\t"); // Запис

}

Console::WriteLine();

 

// керований двовимірний масив Рядків керований тип, що використає

Console::WriteLine("managed 2D array of Strings");

array<String^,2>^ str2DArray = gcnew array<String^,2> (2,3);

// новий Рядок

for(int row=0; row<str2DArray->GetLength(0); row++)

// цикл по рядках

{

for (int col=0; col<str2DArray->GetLength(1); col++)

// цикл по стовпцях

{

str2DArray[row,col] = (row*10 + col).ToString();

// str2DArray [рядок, стовпець] == (row*10 + стовпець).ToString ();

Console::Write(str2DArray[row,col]); // висновок елемента масиву

Console::Write("\t");

}

Console::WriteLine();

}

Console::WriteLine();

 

// некерований двовимірний масив int (для порівняння)

Console::WriteLine("unmanaged 2D array of int");

int int2DArray[2][3];

for(int row=0; row<2; row++) // по рядках

{

for(int col=0; col<3; col++) // по стовпцях

{

int2DArray[row][col] = row*10 + col;

// int2DArray [рядок] [стовпець] = row*10 + стовпець;

Console::Write(int2DArray[row][col]); // висновок елемента масиву

Console::Write("\t");

}

Console::WriteLine();

}

}

for(int row=0; row<str2DArray->GetLength(0); row++) // цикл по рядках

{

for (int col=0; col<str2DArray->GetLength(1); col++) // цикл по стовпцях

{

str2DArray[row,col] = (row*10 + col).ToString();

// str2DArray [рядок, стовпець] == (row*10 + стовпець).ToString ();

Console::Write(str2DArray[row,col]); // висновок елемента масиву

Console::Write("\t");

}

Console::WriteLine();

}

Console::WriteLine();

 

// некерований двовимірний масив int (для порівняння)

Console::WriteLine("unmanaged 2D array of int");

int int2DArray[2][3];

for(int row=0; row<2; row++) // по рядках

{

for(int col=0; col<3; col++) // по стовпцях

{

int2DArray[row][col] = row*10 + col;

// int2DArray [рядок] [стовпець] = row*10 + стовпець;

Console::Write(int2DArray[row][col]); // висновок елемента масиву

Console::Write("\t");

}

Console::WriteLine();

}

}

 

У висновку варто згадати про наявності в складі бібліотеки .NET класів колекцій, призначених для організації даних, у тому числі класи визначення списків, черг, стекiв, словників.

Нижче наведений приклад використання динамічно зростаючого, при необхідності масиву.

 

#include "stdafx.h"

using namespace System;

using namespace System::Collections;

 

int main()

{

// Створюємо й инициализируем ArrayList.

ArrayList^ myAL = gcnew ArrayList;

myAL->Add( "Програма" );

myAL->Add( "№" );

myAL->Add( "1" );

myAL->Add( "Демонстрація колекцій" );

myAL->Add( "!" );

// Displays the properties and values of the ArrayList.

Console::WriteLine( "myAL" );

Console::WriteLine( " Кількість: {0}", myAL->Count );

Console::WriteLine( " Ємність : {0}", myAL->Capacity );

Console::WriteLine();

for each (String^ s in myAL)

{

Console::WriteLine("Індекс:{0}Значення:{1}",myAL->IndexOf(s),s );

}

}