PDA

View Full Version : Уроци по С++



ivakavlad
10-20-2010, 22:12
Тук ще постна общо 18 глави с уроци по С++ поради това че са много дълги ще ги потвам на части моля потребителите да не пишат докато не свърша със всичките 18 глави ще ви информирам за това като свърша после който иска да добави нещо да заповяда

П.П.Ако модераторите решът, че темата си заслужава може да я направят важна, но това вече те си го преценяват аз почвам с уроците.

ivakavlad
10-20-2010, 22:15
Глава 1

Основни елементи
от програмирането на C++


C++ е език за обектно-ориентирано програмиране. Създаден е от Бярн Страуструп от AT&T Bell Laboratories в края на 1985 година. C++ е разширение на езика C в следните три направлнения:
създаване и използване на абстрактни типове данни;
обектно-ориентирано програмиране;
подобрения на конструкции на езика C (производни типове, наследяване, полиморфизъм).
През първите шест месеца след описанието му се появиха над 20 търговски реализации на езика, предназначени за различни компютърни системи. От тогава до сега C++ се разраства чрез добавяне на много нови функции и затова процесът на стандартизацията му продължава и до момента. C++ е пример за език, който с времето расте и се развива. Всеки път, когато потребителите му са забелязвали някакви пропуски или недостатъци, те са го обогатявали със съответните нови възможности.
За разлика от C++, езикът Паскал е създаден планомерно главно за целите на обучението. Проф. Вирт добре е обмислил и доказал езика. Тъй като Паскал е създаден с ясна цел, отделните му компоненти са логически свързани и лесно могат да бъдат комбинирани. Разрастващите се езици, към които принадлежи C++ са доста объркани тъй като хора с различни вкусове правят различни нововъведения. Освен това, заради мобилността на програмите, не е възможно премахването на стари конструкции, даже да съществуват удобни техни подобрения. Така разрастващият се C++ събира в себе си голям брой възможности, които не винаги добре се съвместяват.
Езиците, създадени от компетентни хора, по принцип са лесни за научаване и използване. Разрастващите се езици обаче държат монопола на пазара. Сега C++ е водещия език за програмиране с общо предназначение. Лошото е, че не е много лесен за усвояване, има си своите неудобства и капани. Но той има и огромни приложения – от програми на ниско, почти машинно ниво, до програми от най-висока степен на абстракция.
Целта на настоящия курс по програмиране е не да ви научи на всички възможности на C++, а на изкуството и науката програмиране.
При началното запознаване с езика, възникват два естествени въпроса:
Какво е програма на C++ и как се пише тя?
Как се изпълнява програма на C++?
Ще отговорим на тези въпроси чрез примеp за програма на C++, след което ще дадем някои дефиниции и основни означения.

ivakavlad
10-20-2010, 22:16
2.1. Пример за програма на C++

Задача 1. Да се напише програма, която намира периметъра и лицето на правоъгълник със страни 2,3 и 3,7.
Една програма,която решава задачата е следната:
Program Zad1.cpp
#include <iostream.h>
int main()
{double a = 2.3;
double b = 3.7;
double p, s;
/* намиране на периметъра на правоъгълника */
p = 2*(a+b);
/* намиране на лицето на правоъгълника */
s = a*b;
/* извеждане на периметъра */
cout << "p= " << p << "\n";
/* извеждане на лицето */
cout << "s= " << s << "\n";
return 0;
}
Първият ред
#include <iostream.h>
е директива към компилатора на C++. Чрез нея към файла, съдържащ програмата Zad1.cpp, се включва файлът с име iostream.h (При някои реализации на C++ разширението “.h” се пропуска). Този файл съдържа различни дефиниции и декларации, необходими за реализациите на операциите за поточен вход и изход. В програма Zad1.cpp се нуждаем от тази директива заради извеждането върху екрана на периметъра и лицето на правоъгълника.
Конструкцията
int main()
{ …
return 0;
}
дефинира функция, наречена main (главна). Всяка програма на C++ трябва да има функция main. Повечето програми съдържат и други функции освен нея.
Дефиницията на main започва с думата int (съкращение от integer), показваща, че main връща цяло число, а не дроб или низ, например. Между фигурните скобки { и } е записана редица от дефиниции и оператори, която се нарича тяло на функцията. Компонентите на тялото се отделят със знака ; и се изпълняват последователно. С оператора return се означава краят на функцията. Стойността 0 означава, че тя се е изпълнила успешно. Ако програмата завърши изпълнението си и върне стойност различна от 0, това означава, че е възникнала грешка.
Конструкциите
double a = 2.3;
double b = 3.7;
double p, s;
дефинират променливите a, b, p и s от реалния тип double, като в първите два случая се дават начални стойности на a и b (2.3 и 3.7 съответно). Казва се още, че a и b са инициализирани съответно с 2.3 и 3.7.
Променливата е място за съхранение на данни, което може да съдържа различни стойности по време на изпълнение на програмата. Означава се чрез редица от букви, цифри и долна черта, започваща с буква или долна черта. Променливите имат три характеристики: тип, име и стойност. Преди да бъдат използвани, трябва да бъдат дефинирани.
C++ е строго типизиран език за програмиране. Всяка променлива има тип, който явно се указва при дефинирането й. Пропускането на типа на променливата води до сериозни грешки. Фиг. 1. илюстрира непълно дефинирането на променливи.


Дефиниране на променливи
Синтаксис
<име_на_тип> <променлива> [ = <израз> ]
{, <променлива> [ = <израз> ] };
където
<име_на_тип> е дума, означаваща име на тип като int, double и др.;
<израз> е правило за получаване на стойност – цяла, реална, знакова и др. тип, съвместим с <име_на_тип>.
Семантика
Дефиницията свързва променливата с множеството от допустимите стойности на типа, от който е променливата или с конкретна стойност от това множество. За целта се отделя определено количество оперативна памет (толкова, колкото да се запише най-голямата константа от множеството от допустимите стойности на съответния тип) и се именува с името на променливата. Тази памет е с неопределено съдържание или съдържа стойността на указания израз, ако е направена инициализация.
Не се допуска една и съща променлива да има няколко дефиниции в рамките на една и съща функция.



Фиг. 1.

В случая на програмата Zad1.cpp за a, b, p и s се отделят по 8 байта оперативна памет и
ОП
a b p s
2.3 3.7 - -
8 байта 8 байта 8 байта 8 байта
Неопределеността на p и s ще означаваме с -.
След дефинициите на променливите a, b, p и s е разположен коментарът
/* намиране на периметъра на правоъгълника */
Той е предназначен за програмиста и подсеща за смисъла на следващото действие.
Коментарът (Фиг. 2) е произволен текст, ограден със знаците /* и */. Игнорира се напълно от компилатора.


Коментар
Синтаксис
<коментар> ::= /* <редица_от_знаци> */
Семантика
Пояснява програмен фрагмент. Предназначен е за програмиста. Игнорира се от компилатора на езика.


Фиг. 2.

Конструкциите
p = 2*(a+b);
s = a*b;
са оператори за присвояване на стойност (Фиг. 3). Чрез тях променливите p и s получават текущи стойности. Операторът
p = 2*(a+b);
пресмята стойността на аритметичния израз 2*(a+b) и записва полученото реално число (в случая 12.0) в паметта, именувана с p. Аналогично, операторът
s = a*b;
пресмята стойността на аритметичния израз a*b и записва полученото реално число (в случая 8.51) в паметта, именувана със s.
По-подробно ще разгледаме този оператор в следващата глава. На този етап оставяме с интуитивната представа за <израз>. Ще се ограничим с лява страна от вида променлива. Ще отбележим само, че езикът C++ поддържа псевдоними, което дава възможност лявата страна на оператора за присвояване да бъде израз, чиято стойност е псевдоним на модифицируем обект.

ivakavlad
10-20-2010, 22:18
Оператор за присвояване

Синтаксис
<променлива> = <израз>;
като <променлива> и <израз> са от един и същ тип.
Семантика
Пресмята стойността на <израз> и я записва в паметта, именувана с променливата от лявата страна на знака за присвояване =.

Фиг. 3.

Да се върнем към дефинициите на променливите a и b и операторите за присвояване и return на Zad1.cpp. Забелязваме, че в тях са използвани два вида числа: цели (2 и 0) и реални (2.3 и 3.7). Целите числа се записват като в математиката, а при реалните, знакът запетая се заменя с точка. Умножението е отбелязано със *, а събирането – с +. Забелязваме също, че изразите 2*(a+b) и a*b са реални, каквито са и променливите p и s от левите страни на знака = в операторите за присвояване.
Конструкциите
cout << "p= " << p << "\n";
cout << "s= " << s << "\n";
са оператори за извеждане. Наричат се още оператори за поточен изход. Еквивалентни са на редицата от оператори
cout << "p= ";
cout << p;
cout << "\n";
cout << "s= ";
cout << s;
cout << "\n";
Операторът << означава “изпрати към”. Обектът (променливата) cout (произнася се “си-аут”) е името на стандартния изходен поток, който обикновено е екрана или прозорез на екрана.
Редица от знаци, оградена в кавички, се нарича знаков низ, или символен низ, или само низ. В програмата Zad1.cpp “p= “ и “s= “ са низове. Низът “\n“ съдържа двойката знаци \ (backslash) и n, но те представляват един-единствен знак, който се нарича знак за нов ред. Операторът
cout << "p= ";
извежда върху екрана низа
p=
Операторът
cout << p;
извежда върху екрана стойността на p, а
cout << "\n";
премества курсора на следващия ред на екрана, т.е. указва следващото извеждане да бъде на нов ред.
Фиг. 7. показва по-детайлно синтаксиса и семантиката на оператора за извеждане.

Изпълнение на Zad1.cpp
След обработката на директивата
#include <iostream.h>
файлът iostream.h е включен във файла, съдържащ функцията main на Zad1.cpp. Изпълнението на тялото на main започва с изпълнение на дефинициите
double a = 2.3;
double b = 3.7;
double p, s;
в резултат, на което в ОП се отделят по 8 байта за променливите a, b, p и s, т.е.
ОП
a b p s
2.3 3.7 - -
Коментарите се пропускат. След изпълнението на операторите за присвояване:
p = 2*(a+b);
s = a*b;
променливите p и s се свързват с 12.0 и 8.51 съответно, т.е.
ОП
a b p s
2.3 3.7 12.0 8.51
Операторите
cout << "p= " << p << "\n";
cout << "s= " << s << "\n";
извеждат върху екрана на монитора
p= 12
s= 8.51
Изпълнението на оператора
return 0;
преустановява работата на програмата сигнализирайки, че тя е завършила успешно.
Забележка: Реалните числа се извеждат с възможно минималния брой знаци. Така реалното число 12.0 се извежда като 12.
В същност, описаните действия се извършват над машинния еквивалент на програмата Zad1.cpp. А как се достига до него?

ivakavlad
10-20-2010, 22:19
Изпълнение на програма на езика C++

За целта се използва някаква среда за програмиране на C++. Ние ще изплзваме Visual C++ версия 6.0.
Изпълнението се осъществява чрез преминаване през следните стъпки:
Създаване на изходен код
Чрез текстовия редактор на средата, текстът на програмата се записва във файл. Неговото име се състои от две части – име и разширение. Разширението подсказва предназначението на файла. Различно е за отделните реализации на езика. Често срещано разширение за изходни файлове е “.cpp” или “.c”.
Примерната програма е записана във файла Zad1.cpp.
Компилиране
Тази стъпка се изпълнява от компилатора на езика. Първата част от работата на компилатора на C++ е откриването на грешки – синтактични и грешки, свързани с типа на данните. Съобщението за грешка съдържа номера на реда, където е открита грешка и кратко описание на предполагаемата причина за нея. Добре е грешките да се корегират в последователността, в която са обявени, защото една грешка може да доведе до т. нар. “каскаден ефект”, при който компилаторът открива повече грешки, отколкото реално съществуват. Коригираният текст на програмата трябва да се компилира отново. Втората част от работата на компилатора е превеждане (транслиране) на изходния (sourse) код на програмата в т. нар. обектен код. Обектният код се състои от машинни инструкции и информация за това, как да се зареди програмата в ОП, преди да започне изпълнението й. Обектният код се записва в отделен файл, обикновено със старото име, но с разширение “.obj” или “.o”.
Обектният файл съдържа само “превода” на програмата, а не и на библиотеките, които са декларирани в нея (в случая на програмата Zad1.cpp файлът Zad1.obj не съдържа обектния код на iostream.h). Авторите на пакета iostream.h са описали всички необходими действия и са записали нужния машинен код в библиотеката iostream.h.
Свързване
Обектният файл и необходимите части от библиотеки се свързват в т. нар. изпълним файл. Това се извършва от специална програма, наречена свързваща програма или свързващ редактор (linker). Изпълнимият файл има името на изходния файл, но разширението му обикновено е “.exe”. Той съдържа целия машинен код, необходим за изпълнението на програмата. Този файл може да се изпълни и извън средата за програмиране на езика C++.
Фиг. 4 илюстрира стъпките на изпълнение на програма на C++.

ivakavlad
10-20-2010, 22:22
Фиг. 4

Програмистката дейност, свързана с изпълнението на програма на C++, преминава през три стъпки като реализира цикъла “редактиране-компилиране-настройка”. Започва се с редактора като се пише изходният файл. Компилира се програмата и ако има синтактични грешки, чрез редактора се поправят грешките. Когато програмата е “изчистена” от синтактичните грешки, започва изпълнението й. Ако възникнат грешки по време на изпълнението, се осъществява връщане отново в редактора и се поправят предполагаемите грешки. После пак се компилира и стартира програмата. Цикълът “редактиране-компилиране-настройка” е илюстриран на фиг.5

ivakavlad
10-20-2010, 22:23
2.2. Основни означения

Всяка програма на C++ е записана като редица от знаци, които принадлежат на азбуката на езика.

Азбука на C++

Азбуката на езика включва:
главните и малки букви на латинската азбука;
цифрите;
специалните символи
+ - * / = ( ) [ ] { } | : ; “ ‘ < > , . _ ! @ # $ % ^ ~
Някой от тези знаци, по определени правила, са групирани в думи (лексеми) на езика.

Думи на езика

Думите на езика са идентификатори, запазени и стандартни думи, константи, оператори и препинателни знаци.

Идентификатори

Редица от букви, цифри и знака за подчертаване (долна черта), започваща с буква или знака за подчертаване, се нарича идентификатор.
Примери:
Редиците от знаци
A12 help Help double int15_12 rat_number INT1213 Int15_12
са идентификатори, а редиците
1ba ab+1 a(1) a’b
не са идентификатори. В първия случай редицата започва със цифра, а в останалите – редиците съдържат недопустими за идентификатор знаци.
Идентификаторите могат да са с произволна дължина. В съвременните компилатори максималният брой знаци на идентификаторите може да се задава, като подразбиращата се стойност е 32.
Забележка: При идентификаторите се прави разлика между малки и главни букви, така help, Help, HELP, HeLp и HElp са различни идентификатори.
Идентификаторите се използват за означаване на имена на променливи, константи, типове, функции, класове, обекти и други компоненти на програмите.
Препоръка: Не започвайте вашите идентификатори със знака за подчертаване. Такива идентификатори се използват от компилатора на C++ за вътрешно предназначение.
Допълнение: Чрез метаезика на Бекус-Наур, синтаксисът на променливите се определя по следния начин:
<променлива> ::= <идентификатор>

Някои идентификатори са резервирани в езика.

Запазени думи

Това са такива идентификатори, които се използват в програмите по стандартен, по предварително определен начин и които не могат да бъдат използвани по друг начин. Чрез тях се означават декларации, дефиниции, оператори, модификатори и други конструкции. Реализацията Visual C++ 6.0 съдържа около 70 такива думи.
В програмата Zad1.cpp са използвани запазените думи int, double, return.

Стандартни думи

Това са такива идентификатори, които се използват в програмите по стандартен, по предварително определен начин. Тези идентификатори могат да се използват и по други начини, например като обикновени идентификатори.
В програмата Zad1.cpp е използвана стандартната дума cout.
Например,
#include <iostream.h>
int main()
{int cout = 21;
return 0;
}
е допустима програма на C++. В нея идентификаторът cout е използван като име на променлива. Правенето на опит за използване на cout по стандартния начин води до грешка. Така фрагментът
#include <iostream.h>
int main()
{int cout = 21;
cout << cout << “\n”;
return 0;
}
е недопустим.
Препоръка: Стандартните думи да се използват само по стандартния начин.

Константи

Данна, която не може да бъде променяна, се нарича константа. Има числови, знакови, низови и др. видове константи.
Целите и реалните числа са числови константи. Целите числа се записват както в математиката и могат да бъдат задавани в десетична, шестнадесетична или осмична бройна система. Реалните числа се записват по два начина: във формат с фиксирана точка (например, 2.34 -12345.098) и в експоненциален формат (например, 5.23е-3 и 5.23Е-3 означават 5.23 умножено с 10-3).
Низ, знаков низ или символен низ е крайна редица от знаци, оградени в кавички. Например, редиците: “Това е низ.”, “1+23-34”, “Hellow\n” са низове.
Забележка: Операторът
cout << “Hellow\n”;
извежда върху екрана поздрава Hellow и премества курсора на нов ред.

Оператори

В C++ има три групи оператори: аритметично - логически, управляващи и оператори за управление на динамичната памет.

аритметично-логически оператори
Наричат се още аритметично-логически операции. Те реализират основните аритметични и логически операции като: събиране (+), изваждане (-), умножение (*), деление (/), логическо И (&&, and), логическо ИЛИ (||, or) и др. В програмата Zad1.cpp бяха използвани * и +.

управляващи оператори
Това са конструкции, които управляват изчислителния процес. Такива са условния оператор, оператора за цикъл, за безусловен преход и др.

операторите за управление на динамичната памет
Те позволяват по време на изпълнение на програмата да бъде заделяна и съответно освобождавана динамична памет.

Препинателни знаци

Използват се ; < > { } ( ) и др. знаци.

Разделяне на думите

В C++ разделителите на думите са интервалът, вертикалната и хоризонталната табулации и знакът за нов ред.

Коментари

Коментарите са текстове, които не се обработват от компилатора, а служат само като пояснения за програмистите. В C++ има два начина за означаване на коментари. Единият начин е, текстът да се огради с /* и */. Използвахме го вече в Zad1.cpp. Тези коментари не могат да бъдат влагани. Другият начин са коментарите, които започват с // и завършват с края на текущия ред.
Коментарите са допустими навсякъде, където е допустим разделител.
Забележка: Не се препоръчва използването на коментари от вида // в редовете на директивите на компилатора.

ivakavlad
10-20-2010, 22:24
2.3. Вход и изход

Програма Zad1.cpp намира периметъра и лицето само на правоъгълник със страни 2.3 и 3.7. Нека решим тази задача в общия случай.
Задача 2. Да се напише програма, която въвежда размерите на правоъгълник и намира периметъра и лицето му.

Програмата Zad2.cpp решава тази задача.
Program Zad2.cpp
#include <iostream.h>
int main()
{// въвеждане на едната страна
cout << "a= ";
double a;
cin >> a;
// въвеждане на другата страна
cout << "b= ";
double b;
cin >> b;
// намиране на периметъра
double p;
p = 2*(a+b);
// намиране на лицето
double s;
s = a*b;
// извеждане на периметъра
cout << "p= " << p << "\n";
// извеждане на лицето
cout << "s= " << s << "\n";
return 0;
}
Когато програмата бъде стартирана, върху екрана ще се появи подсещането
a=
което е покана за въвеждане размерите на едната страна на правоъгълника. Курсорът стои след знака =. Очаква се да бъде въведено число (цяло или реално), след което да бъде натиснат клавишът ENTER.
Следва покана за въвеждане на стойност за другата страна на правоъгълника, след което програмата ще изведе резултата и ще завърши изпълнението си.
Въвеждането на стойността на променливата a се осъществява с оператора за вход
cin >> a;
Обектът cin е името на стандартния входен поток, обикновено клавиатурата на компютъра. Изпълнението му води до пауза до въвеждане на число и натискане на клавиша ENTER. Нека за стойност на a е въведено 5.65, следвано от ENTER. В буфера на клавиатурата се записва
cin




След изпълнението на
cin >> a;
променливата a се свързва с 5.65, а в буфера на клавиатурата остава знакът \n, т.е.
cin



ОП
a
5.65
Въвеждането на стойността на променливата b се осъществява с оператора за вход
cin >> b;
Изпълнението му води до пауза до въвеждане на число и натискане на клавиша ENTER. Нека е въведено 8.3, следвано от ENTER. В буфера на клавиатурата имаме:
cin



Изпълнението на оператора
cin >> b;
прескача знака \n, свързва 8.3 с променливата b, а в буфера на клавиатурата отново остава знакът \n, т.е.
cin



ОП
a b
5.65 8.3
Чрез оператора за вход могат да се въвеждат стойности на повече от една променлива. Фиг. 6 съдържа по-пълно негово описание.
Входът от клавиатурата е буфериран. Това означава, че всяка редица от натиснати клавиши се разглежда като пакет, който се обработва чак след като се натисне клавишът ENTER.


Оператор за вход >>
Синтаксис
cin >> <променлива>;
където
- cin е обект (променлива) от клас (тип) istream, свързан с клавиатурата,
- <променлива> е идентификатор, дефиниран, като променлива от “допустим тип”, преди оператора за въвеждане. (Типовете int, long, double са допустими).
Семантика
Извлича (въвежда) от cin (клавиатурата) поредната дума и я прехвърля в аргумента-приемник <променлива>. Конструкцията
cin >> <променлива>
е израз от тип istream със стойност левия му аргумент, т.е. резултатът от изпълнението на оператора >> е cin. Това позволява няколко думи да бъдат извличани чрез верига от оператори >>.
Следователно, допустим е следният по-общ синтаксис на >>:
cin >> <променлива> { >> <променлива>};
Операторът >> се изпълнява отляво надясно. Такива оператори се наричат ляво асоциативни. Така операторът
cin >> променлива1 >> променлива2 >> … >> променливаn;
е еквивалентен на редицата от оператори:
cin >> променлива1;
cin >> променлива2;

cin >> променливаn;
Освен това, ако операцията въвеждане е завършила успешно, състоянието на cin е true, в противен случай състоянието на cin е false.


Фиг. 6.

В случая от Фиг. 6, настъпва пауза. Компилаторът очаква да бъдат въведени n стойности - за променлива1, променлива2,, …, променливаn, съответно и бъде натиснат клавишът ENTER. Тези стойности трябва да бъдат въведени по подходящ начин (на един ред, на отделни редове или по няколко данни на последователни редове, слепени или разделени с интервали, табулации или знака за нов ред), като стойностi трябва да бъде от тип, съвместим с типа на променливаi (i = 1, 2, …, n).
Пример: Да разгледаме програмния фрагмент:
double a, b, c;
cin >> a >> b >> c;
Операторът за вход изисква да бъдат въведени три реални числа за a, b и c съответно. Ако се въведат
1.1 2.2 3.3 ENTER
променливата a ще се свърже с 1.1, b – с 2.2 и c – със 3.3. Същият резултат ще се получи, ако се въведе
1.1 2.2 ENTER
3.3 ENTER
или
1.1 ENTER
2.2 3.3 ENTER
или
1.1 ENTER
2.2 ENTER
3.7 ENTER
или даже ако се въведе
1.1 2.2 3.3 4.4 ENTER
В последния случай, стойността 4.4 ще остане необработена в буфера на клавиатурата и ще обслужи следващо четене, ако има. Този начин на действие съвсем не е приемлив. Още по-лошо ще стане когато се въведат данни от неподходящ тип.
Пример: Да разгледаме фрагмента:
int a;
cin >> a;
Той дефинира целочислена променлива a (a е променлива от тип int), след което настъпва пауза в очакване да бъде въведено цяло число. Нека сме въвели 1.25, следвано от ENTER. Състоянието на буфера на клавиатурата е:
cin




Операторът
cin >> a;
свързва a с 1, но не прескача останалата информация от буфера и тя ще обслужи следващо четене, което води до непредсказуем резултат. Още по-неприятна е ситуацията, когато вместо цяло число за стойност на a се въведе някакъв низ, например one, следван от ENTER. В този случай, изпълнението на
cin >> a;
ще доведе до
cin




и
ОП
a
-
т.е. стойността на променливата a не се променя (остава неопределена), а буферът на клавиатурата изпада в състояние fail. За съжаление системата не извежда съобщение за грешка, което да уведоми за възникналия проблем.
Засега препоръчваме въвеждането на коректни входни данни. Преудоляването на недостатъците, илюстрирани по-горе, ще разгледаме в следващите части на книгата.

Вече използвахме оператора за изход. Фиг. 7 описва неговите синтаксис и семантика.

ivakavlad
10-20-2010, 22:26
2.4. Структура на програмата на C++

Когато програмата е малка, естествено е целият й код да бъде записан в един файл. Когато програмите са по-големи или когато се работи в колектив, ситуацията е по-различна. Налага се да се раздели кодът в отделни изходни (source) файлове. Причините, поради които се налага разделянето, са следните. Компилирането на файл отнема време и е глупаво да се чака компилаторът да транслира отново и отново код, който не е бил променян. Трябва да се компилират само файловете, които са били променяни.

Оператор за изход <<
Синтаксис
cout << <израз>;
където
- cout е обект (променлива) от клас (тип) ostream и предварително свързан с екрана на компютъра;
- <израз> е израз от допустим тип. Представата за израз продължава да бъде тази от математиката. Допустими типове са bool, int, short, long, double, float и др.
Семантика
Операторът << изпраща (извежда) към (върху) cout (екрана на компютъра) стойността на <израз>. Конструкцията
cout << <израз>
е израз от тип ostream и има стойност първия му аргумент, т.е. резултатът от изпълнението на оператора << в горния случай е cout. Това позвалява чрез верига от оператори << да бъдат изведени стойностите на повече от един <израз>, т.е. допустим е следният по-общ синтаксис:
cout << <израз> { << <израз>};
Операторът << се изпълнява отляво надясно (ляво асоциативен е). Така операторът
cout << израз1 << израз2 << … << изразn
е еквивалентен на редицата от оператори:
cout << израз1;
cout << израз2;

cout << изразn;


Фиг. 7.

Друга причина е работата в колектив. Би било трудно много програмисти да редактират едновременно един файл. Затова кодът на програмата се разделя така, че всеки програмист да отговаря за един или няколко файлове.
Ако програмата се състои от няколко файла, трябва да се каже на компилатора как да компилира и изгради цялата програма. Това ще направим в следващите раздели. Сега ще дадем най-обща представа за структурата на изходните файлове. Ще ги наричаме още модули.
Изходните файлове се организират по следния начин:

<изходен_файл> ::= <заглавен_блок_с_коментари>
<заглавни_файлове>
<константи>
<класове>
<глобални_променливи>
<функции>

Заглавен блок с коментари

Всеки модул започва със заглавен блок с коментари, даващи информация за целта му, използвания компилатор и операционна среда, за името на програмиста и датата на създаването му. Заглавният коментар може да съдържа забележки, свързани с описания на структури от данни, аргументи, формат на файлове, правила, уговорки.

Заглавни файлове

В тази част на модула са изброени всички необходими заглавни файлове. Например
#include <iostream.h>
#include <cmath.h>
Забелязваме, че за разделител е използван знакът за нов ред, а не ;.

Константи

В тази част се описват константите, необходими за модула. Вече имаме някаква минимална представа за тях. По-подробно описание на синтаксиса и семантиката им е дадена на Фиг. 8. За да бъде програмата по-лесна за четене и модифициране, е полезно да се дават символични имена не само на променливите, а и на константите. Това става чрез дефинирането на константи.

Задача 3. Да се напише програма, която въвежда радиуса на окръжност и намира и извежда дължината на окръжността и лицето на кръга с дадения радиус.
Една програма, която решава задачата е следната:

Program Zad3.cpp
#include <iostream.h>
const double PI = 3.1415926535898;
int main()
{ double r;
cout << “r= “;
cin >> r;
double p = 2 * PI * r;
double s = PI * r * r;
cout << “p=” << p << “\n”;
cout << “s=” << s << “\n;
return 0;
}
В тази програма е дефинирана реална константа с име PI и стойност 3.1415926535898, след което е използвано името PI.

Дефиниране на константи
Синтаксис
const <име_на_тип> <име_на_константа> = <израз>;
където
const е запазена дума (съкращение от constant);
<име_на_тип> е идентификатор, означаващ име на тип;
<име_на_константа> е идентификатор, обикновено състоящ се от главни букви, за да се различава визуално от променливите.
<израз> е израз от тип, съвместим с <име_на_тип>.
Семантика
Свързва <име_на_константа> със стойността на <израз>. Правенето на опит да бъде променяна стойността на константата предизвиква грешка.

Фиг. 8.

Примери:
const int MAXINT = 32767;
const double RI = 2.5 * MAXINT;

Предимства на декларирането на константите:
- Програмите стават по-ясни и четливи.
- Лесно (само на едно място) се променят.
- Вероятността за грешки, възможни при многократното изписване на стойността на константата, намалява.
Забележка: Тъй като в програмата Zad3.cpp е използвана само една
функция (main), декларацията на константата PI може да се постави във функцията main, преди първото нейно използване.

Класове

Тази част съдържа дефинициите на класовете, използвани в модула.
В езика C++ има стандартен набор от типове данни като int, double, float, char, string и др. Този набор може да бъде разширен чрез дефинирането на класове.
Дефинирането на клас въвежда нов тип, който може да бъде интегриран в езика. Класовете са в основата на обектно-ориетираното програмиране, за което е предназначен езика C++.
Дефинирането и използването на класове ще бъде разгледано по-късно.

Глобални променливи

Езикът поддържа глобални променливи. Те са променливи, които се дефинират извън функциите и които са “видими” за всички функции, дефинирани след тях. Декларират се както се дефинират другите (локалните) променливи. Използването на много глобални променливи е лош стил за програмиране и не се препоръчва. Всяка глобална променлива трябва да е съпроводена с коментар, обясняващ предназначението й.

Функции
Всеки модул задължително съдържа функция main. Възможно е да съдържа и други функции. Тогава те се изброяват в тази част на модула. Ако функциите са подредени така, че всяка от тях е дефинирана преди да бъде извикана, тогава main трябва да бъде последна. В противен случай, в началото на тази част на модула, трябва да се декларират всички функции.



Задачи

Задача 1. Кои от следните редици от знаци са идентификатори, кои не и защо?
а) a б) x1 в) x1 г) x’ д) x1x2
е) sin ж) sin x з) cos(x) и) x-1 к) 2a
л) min 1 м) Beta н) a1+a2 о) k”m п) sin’x
Задача 2. Намерете синтактичните грешки в следващата програма:
include <iostream>
int Main()
{ cout >> “a, b = “;
cin << a, b;
cout << “The product of “ << a << “and” << b << “is: “
<< a*b < “\n”
return 0;
}
Задача 3. Напишете програма, която разменя стойностите на две числови променливи.
Задача 4. Напишете програма, която намира минималното (максималното) от две цели числа.
Задача 5. Напишете програма, която изписва с главни букви текста Hellow World.
Упътване: Голямата буква H може да се представи така:
о о
о о
о о
ооооо
о о
о о
о о
и да се реализира по следния начин:
char* let_H = “o o\no o\no o\nooooo\no o\no o\no o\n”;
където char* означава тип низ.



Допълнителна литература

Г. Симов, Програмиране на C++, С., СИМ, 1993.
К. Хорстман, Принципи на програмирането със C++, С., СОФТЕХ, 2000.
П. Лукас, Наръчник на програмиста, С., Техника, 1994.

ivakavlad
10-20-2010, 22:28
Глава2

Скаларни типове данни


Езикът C++ е изключително мощен по отношение на типовете данни, които притежава. Най-общо, типовете му могат да бъдат разделени на: вградени и абстрактни.
Вградените типове са предварително дефинирани и се поддържат от неговото ядро.
Абстрактните типове се дефинират от програмиста. За целта се определят съответни класове.
Една непълна класификация на вградените типове данни е дадена на Фиг. 1.

Вградени типове данни


Скаларни типове Съставни типове


Булев масив
Числови типове вектор
Изброен
Указател
Псевдоним

Фиг. 1.

Скаларни са типовете данни, които се състоят от една компонента (число, знак и др.).
Съставни типове са онези типове данни, компонентите на които са редици от елементи.
Типът указател дава средства за динамично разпределение на паметта.
В тази глава ще разгледаме само някои скаларни типове данни.
Всеки тип се определя с множество от допустими стойности (множество от стойности) и операции и вградени функции, които могат да се прилагат над елементите от множеството от стойностите му.

ivakavlad
10-20-2010, 22:29
3.1. Логически тип

Нарича се още булев тип в чест на Дж. Бул, английски логик, поставил основите на математическата логика.
Типът е стандартен, вграден в реализацията. За означаването му се използва запазената дума bool (съкращение от boolean).

Множество от стойности

Състои се от два елемента – стойностите true (истина) и false (лъжа). Тези стойности се наричат още булеви константи.

<булева_константа> ::= true | false.

Променлива величина, множеството от допустимите стойности, на която съвпада с множеството от стойности на типа булев, се нарича булева или логическа променлива или променлива от тип булев. Дефинира се по обичайния начин.
Примери:
bool b1, b2;
bool b3 = false;
Дефиницията свързва булевите променливи с множеството от стойности на типа булев или с конкретна стойност от това множество като отделя по 1 байт оперативна памет за всяка от тях. Съдържанието на тази памет е неопределено или е булевата константа, свързана с дефинираната прменлива, в случай, че тя е инициализирана.
След дефиницията от примера по-горе, имаме:
ОП
b1 b2 b3
- - false
1 байт 1 байт 1 байт
Съдържанието на паметта, именувана с b1 и b2 е неопределено, а това на именуваната с b3 е false. В същност, вместо false в паметта е записан кодът (вътрешното представяне) на false - 0.
Вътрешните представяния на булевите константи са:
false 0
true 1

Операции и вградени функции

Логически операции

Конюнкция (логическо умножение)

Тя е двуаргументна (бинарна) операция. Означава се с and или && (за Visual C++, 6.0) и се дефинира по следния начин:


А B A and B
false false false
false rue false
true false false
true true true


Операцията се поставя между двата си аргумента. Такива операции се наричат инфиксни.

Дизюнкция (логическо събиране)
Тя е бинарна, инфиксна операция. Означава се с or или || (за Visual C++, 6.0) и се дефинира по следния начин:


А B A or B
false false false
false true true
true false true
true true true


Логическо отрицание

Тя е едноаргументна (унарна) операция. Означава се с not или ! (за Visual C++, 6.0) и се дефинира по следния начин:


А not A
false true
true false


Поставя се пред единствения си аргумент. Такива оператори се наричат префиксни.
Забележка: Може да няма разделител между оператора ! и константите true и false, т.е. записите !true и !false са допустими.
Допълнение: Смисълът на операторите and, or и not е разширен чрез разширяване смисъла на булевите константи. Прието е, че true е всяка стойност, различна от 0 и че false е стойността 0.

Операции за сравнение

Над булевите данни могат да се извършват следните инфиксни операции за сравнение:
== - за равно
!= - за различно
> - за по-голямо
>= - за по-голямо или равно
< - за по-малко
<= - за по-малко или равно
Сравняват се кодовете.
Примери:
false < true е true
false > false е false
true >= false е true

Въвеждане

Не е възможно въвеждане на стойност на булева променлива чрез оператора >>, т.е. операторът
cin >> b1;
е недопустим, където b1 е булевата променлива, дефинирана по-горе.

Извеждане

Осъществява се чрез оператора
cout << <булева_константа>;
или по-общо
cout << <булев_израз>;
където синтактичната категория <булев_израз> е определена по-долу.
Извежда се кодът на булевата константа или кодът на булевата константа, която е стойност на <булев_израз>.

Булеви изрази

Булевите изрази са правила за получаване на булева стойност. Дефинират се рекурсивно по следния начин:
Булевите константи са булеви изрази.
Булевите променливи са булеви изрази.
Прилагането на булевите оператори not (!), and (&&), or (||) над булеви изрази е булев израз.
Прилагането на операциите за сравнение ==, !=, >, >=, <, <= към булеви изрази е булев израз.

Примери: Нека имаме дефиницията
bool b, b1, b2;
Следните изрази са булеви:

true b b1 b2 !false !!b !b1 || b2
!!!b && !!!!!b2 b < !b2 false >= b b1 == b2 > b b != b1

Тази дефиниция е непълна. Ще отбележим само, че сравнението на аритметични изрази чрез изброените по-горе операции за сравнение, е булев израз. Освен това, аритметичен израз, поставен на място, където синтаксисът изисква булев израз, изпълнява ролята на булев израз. Това е резултат от разширяването смисъла на булевите константи true и false, чрез приемането всяка стойност, различна от 0 да се интерпретира като true и 0 да се интерпретира като false.
Засега отлагаме разглеждането на семантиката на булевите изрази. Това ще направим след разглеждане на аритметичните изрази

ivakavlad
10-20-2010, 22:31
3.2. Числени типове

3.2.1. Целочислени типове

Ще разгледаме целочисления тип int.
Типът е стандартен, вграден в реализацията на езика. За означаването му се използва запазената дума int.

Множество от стойности

Множеството от стойности на типа int зависи от хардуера и реализацията и не се дефинира от ANSI (American National Standarts Institute). Състои се от целите числа от някакъв интервал. За реализацията Visual C++ 6.0, това е интервалът [-2147483648, 2147483647].
Целите числа се записват като в математиката, т.е.
<цяло_число> ::= [+|-]<цяло_без_знак>
<цяло_без_знак> ::= <цифра>|
<цифра><цяло_без_знак>
<цифра> ::= 0| 1| … |9.
Обикновено знакът + се пропуска.
Допълнение: Целите числа могат да са в десетична, осмична и шестнадесетична позиционна система. Осмичните числа започват с 0 (нула), а шестнадесетичните - с 0x (нула, x).

Елементите от множеството от стойности на типа int се наричат константи от тип int.

Променлива величина, множеството от допустимите стойности, на която съвпада с множеството от стойности на типа int, се нарича цяла променлива или променлива от тип int.
Дефинира се по обичайния начин. Дефиницията свързва променливите от тип int с множеството от стойности на типа int или с конкретна стойност от това множество като отделя по 4 байта (1 дума) оперативна памет за всяка от тях. Ако променливата не е била инициализирана, съдържанието на свързната с нея памет е неопределено, а в противен случай – съдържа указаната при инициализацията стойност.
Примери:
int i;
int j = 56;
След тези дефиниции, имаме:
ОП
i j
- 56
4 байта 4 байта

Операции и вградени фунции

Аритметични операции

Унарни операции
Записват се пред или след единствения си аргумент.

+, - са префиксни операции. Потвърждават или променят
знака на аргумента си.
Примери: Нека
int i = 12, j = -7;
Следните означения съдържат унарна операция + или -:
-i +j -j +i -567

Бинарни операции
Имат два аргумента. Следните аритметични операции са инфиксни:

+ - събиране
- - изваждане
* - умножение
/ - целочислено деление
% - остатък от целочислено деление.

Примери:
15 – 1235 = -1220 13 / 5 = 2
15 + 1235 = 1250 13 % 5 = 3
-15 * 123 = -1845 23 % 3 = 2
Забележка: Допустимо е използването на два знака за аритметични операции, но единият трябва да е унарен. Например, допустими са 5-+4, 5+-4, имащи стойност 1, а също 5*-4, равно на –20.

Логически операции

Логическите операции конюнкция, дизюнкция и отрицание могат да се прилагат над целочислени константи. Дефинират се по същия начин, като целите числа, които са различни от 0 се интерпретират true, а 0 – като false.
Примери:
123 and 0 е false
0 or 15 е true
not 67 е false

Операции за сравнение

Над целочислени константи могат да се извършват следните инфиксни операции за сравнение:
== - за равно != - за различно
> - за по-голямо >= - за по-голямо или равно
< - за по-малко <= - за по-малко или равно.
Наредбата на целите числа е като в математиката.
Примери:
123 < 234 е true
-123456 > 324 е false
23451 >= 0 е true

Вградени функции

В езика C++ има набор от вградени функции. Обръщението към такива функции има следния синтаксис:
<име_на_функция>(<израз>, <израз>, …, <израз>)
и връща стойност от типа на функцията.

Тук ще разгледаме само едноаргументната целочислена функция abs.

аbs(x) – намира |x|, където x е цял израз
(в частност цяла константа).

Примери:
abs(-1587) = 1587 abs(0) = 0 abs(23) = 23

За използването на тази функция е необходимо в частта на заглавните файлове да се включи директивата:
#include <math.h>
Библиотеката math.h съдържа богат набор от функции. В някой реализации тя има име math или cmath.

Въвеждане

Реализира се по стандартния и разгледан вече начин.
Пример: Ако
int i, j;
операторът
cin >> i >> j;
въвежда стойности на целите променливи i и j. Очаква се въвеждане от стандартния входен поток на две цели константи от тип int, разделени с интервал, знаците за хоризонтална или вертикална табулация или знака за преминаване на нов ред.

Извеждане

Реализира се чрез оператора
cout << <цяла_константа>;
или по-общо
cout << <цял_израз>;
В текущата позиция на курсора се извежда константата или стойността на целия израз. Използва се минималното количество позиции, необходими за записване на цялото число.
Пример: Нека имаме дефиницията
int i = 1234, j = 9876;
Операторът
cout << i << j << “\n”;
извежда върху екрана стойностите на i и j, но слепени
12349876
Този изход не е ясен. Налага се да се извърши форматиране на изхода. То се осъществява чрез подходящи манипулатори.

Манипулатор setw
Setw е вградена функция.
Синтаксис
setw(<цял_израз>)
Семантика
Стойността на <цял_израз> задава широчината на полето на следващия изход.
Пример: Операторът
cout << setw(10);
не извежда нищо. Той “манипулира” следващото извеждане като указва, че в поле с широчина 10 отдясно приравнена, ще бъде записана следващата извеждана цяла константа.
Забележка: Този манипулатор важи само за първото след него извеждане.
Пример: Нека
ОП
i j
1234 9876
Операторът
cout << setw(10) << i << j << “\n”;
извежда отново стойностите на i и j слепени, като 1234 се предшества от 6 интервала, т.е.
******12349876
където интервалът е означен със знака *.
Операторът
cout << setw(10) << i << setw(10) << j << “\n”;
извежда
******1234******9876

Манипулатори dec, oct и hex

Целите числа се извеждаt в десетична позиционна система. Ако се налага изходът им да е в осмична или шестнадесетична позиционна система, се използват манипулаторите oct и hex съответно. Всеки от тях е в сила, докато друг манипулатор за позиционна система не е указан за следващ извод. Връщането към десетична позиционна система се осъществява чрез манипулатора dec.

dec – манипулатор, задаващ всички следващи изходи (докато не е указан друг манипулатор, променящ позиционната система) на цели числа да са в десетична позиционна система;

oct – манипулатор, задаващ всички следващи изходи (докато не е указан друг манипулатор, променящ позиционната система) на цели числа да са в осмиична позиционна система;

hex - манипулатор, задаващ всички следващи изходи (докато не е указан друг манипулатор, променящ позиционната система) на цели числа да са в шестнадесетична позиционна система.

Пример: Нека имаме
ОП
i j
23
След изпълнението на операторите
cout << setw(10) << dec << i << setw(10) << j << “\n”;
cout << setw(10) << oct << i << setw(10) << j << “\n”;
cout << setw(10) << hex << i << setw(10) << j << “\n”;
имаме:
********12********23
********14********27
*********c********17
Забележка: Преди използване на манипулаторите е необходимо да се включи заглавният файл iomanip.h, т.е. в частта за заглавни файлове да се запише директивата
#include <iomanip.h>

Други целочислени типове

Други цели типове се получават от int като се използват модификаторите short, long, signed и unsigned. Тези модификатори доопределят някои аспекти на типа int.
За реализацията Visual C++ 6.0 са в сила:

Тип Диапазон Необходима памет
short int -32768 до 32767 2 байта
unsigned short int 0 до 65535 2 байта
long int -2147483648 до 2147483647 4 байта
unsigned long int 0 до 4294967295 4 байта
unsigned int 0 до 4294967295 4 байта

Запазената дума int при тези типове се подразбира и може да бъде пропусната. Типовете short int (или само short) и long int (или само long) са съкратен запис на signed short int и signed long int.

ivakavlad
10-20-2010, 22:33
3.2.2. Реални типове

Ще разгледаме реалния тип double.
Типът е стандартен, вграден във всички реализации на езика.

Множество от стойности

Множеството от стойности на типа double се състои от реалните числа от -1.74*10308 до 1.7*10308 . Записват се във два формата – като числа с фиксирана и като числа с плаваща запетая (експоненциален формат).

<реално_число> ::= <цяло_число>.<цяло_число_без_знак>|
<цяло_число>Е<порядък> |
<цяло_число>.<цяло_число_без_знак>E<порядък> |
<порядък> ::= <цяло_число>
При експоненциалния формат може да се използва и малката латинска буква e.
Примери: Следните числа
123.3454 -10343.034 123Е13 -1.23е-4
са коректно записани реални числа.
Смисълът на експоненциалния формат е реално число, получено след умножаване на числото пред E (e) с 10 на степен числото след E (e).
Примери: 12.5Е4 е реалното число 125000.0, а –1234.025е-3 е реалното число –1.234025.
Елементите от множеството от стойности на типа double се наричат реални константи или по-точно константи от реалния тип double.
Променлива величина, множеството от допустимите стойности, на която съвпада с множеството от стойности на типа double, се нарича реална променлива или променлива от тип double.
Дефинира се по обичайния начин. Дефиницията свързва реалните променливи с множеството от стойности на типа double или с конкретна стойност от това множество, като отделя по 8 байта оперативна памет за всяка от тях. Ако променливата не е била инициализирана, съдържанието на свързната с нея памет е неопределено, а в противен случай – съдържа указаната при инициализацията стойност.
Примери:
double i;
double j = 5699.876;
След тази дефиниция, имаме:
ОП
i j
- 5699.876
8 байта 8 байта

Операции и вградени функции

Аритметични операции

Унарни операции

+, - Префиксни са. Потвърждават или променят знака на
аргумента си.
Примери: Нека
double i = 1.2, j = -7.5;
Следните конструкции съдържат унарна операция + или -:
-i +j -j +i -56.7

Бинарни операции

Имат два аргумента. Следните аритметичните оператори са инфиксни:

+ - събиране
- - изваждане
* - умножение
/ - деление (поне единият аргумент е реален)

Примери:
15.3 – 12.2 = 3.1 13.0 / 5 = 2.6
15 + 12.35 = 27.35 13 / 5.0 = 2.6
-1.5 * 12.3 = -18.45

Логически операции

Логическите операции конюнкция, дизюнкция и отрицание могат да се прилагат над реални константи. Дефинират се по същия начин, като реалните числа, които са различни от 0.0 се интерпретират като true, а 0.0 – като false.
Примери:
123.6 and 0.0 е false
0.0 or 15.67 е true
not 67.7 е false

Операции за сравнение

Над реални данни могат да се извършват следните инфиксни операции за сравнение:
== - за равно != - за различно
> - за по-голямо >= - за по-голямо или равно
< - за по-малко <= - за по-малко или равно.
Наредбата на реалните числа е като в математиката.
Примери:
123.56 < 234.09 е true
-123456.9888 > 324.0098 е false
23451.6 >= 0 е true

Допълнение: Сравнението за равно на две реални числа x и y се реализира обикновено чрез релацията: |x – y| < e, където e = 10-14 за тип double. По-добър начин е да се използва релацията:


Вградени функции

При цял или реален аргумент, следните функции връщат реален резултат от тип double:
Sin(x) - синус, sin x, x е в радиани
cos(x) - косинус, cos x, x е в радиани
tan(x) - тангенс, tg x, x е в радиани
аSin(x) - аркуссинус, arcsin x

ivakavlad
10-20-2010, 22:35
3.2.3. Аритметични изрази

Аритметичните изрази са правила за получаване на числови константи. Има два вида аритметични изрази: цели и реални.

<аритметичен_израз> ::= <цял_израз> | <реален_израз>

Цели аритметични изрази

Целите аритметични изрази са правила за получаване на константи от тип int или разновидностите му. Дефинират се рекурсивно по следния начин:

Целите константи са цели аритметични изрази.
Примери: 123 –2345 –32767
0 22233345 –87
са цели аритметични изрази.

Целите променливи са цели аритметични изрази.
Примери: Ако имаме дефиницията:
int i, j;
short p, q, r;
i j
са цели аритметични изрази от тип int, a
p q r
са цели аритметични изрази от тип short.

- Прилагането на унарните операции + и – към цели аритметични изрази е цял аритметичен израз.
Примери: -i +j -j
са цели аритметични изрази от тип int, а
+p -p +r -q
са цели аритметични изрази от тип short.

- Прилагането на бинарните аритметични операции +, -, *, / и % към цели аритметични изрази, е цял аритметичен израз.
Пример: i % 10 + j * i - p -i + j / 5
са цели аритметични изрази от тип int,
r – p / 12 – q r % q – p r + p – q
са цели аритметични изрази от тип short.

- Цели функции, приложени над цели аритметични изрази, са цели аритметични изрази.
Примери: abs(i+j) е цял аритметичен израз от тил int, а abs(p-r) е цял аритметичен израз от тип short.

Реални аритметични изрази

Реалните аритметични изрази са правила за получаване на константа от тип double или float. Дефинират се рекурсивно по следния начин:

Реалните константи са реални аритметични изрази.
Примери: 1.23е-3 –2345е2 –3.2767 0.0
222.33345 –8.7009
са реални аритметични изрази.
Забележка: Реална константа от диапазона на тип float, но с повече от 7 значещи цифри се приема от компилатора за реално число от тип double.

Реалните променливи са реални аритметични изрази.
Примери: Ако имаме дефинициите:
double i, j;
float p, q, r;
i j
са реални аритметични изрази от тип double, a
p q r
са реални аритметични изрази от тип float.

- Прилагането на унарните операции + и – към реални аритметични изрази е реален аритметичен израз.
Примери: -i +j -j
са реални аритметични изрази от тип double, а
+p -p +r -q
са реални аритметични изрази от тип float.

- Прилагането на бинарните аритметични операции +, -, * и / към аритметични изрази, поне един от които е реален, е реален аритметичен израз.
Пример: i % 10 + j*i - p -i + j/5
са реални аритметични изрази от тип double, а
–p/12 – q r%q – p r + p – q
са реални аритметични изрази от тип float.

- Реални функции, приложени над реални или цели аритметични изрази, са реални аритметични изрази.
Примери: fabs(i+j) sin(i-p) cos(p/r-q) floor(p)
ceil(r-p+i) exp(p) log(r-p*q)
са реални аритметични изрази от тип double.

Семантика на аритметичните изрази

За пресмятане на стойностите на аритметичните изрази се използват следният приоритет на операциите и вградените функции:
1. Вградени функции
2. Действията в скобите
3. Операции в следния приоритет
- +, - (унарни) най-висок
- *, /, %
- +, - (бинарни)
- <<, >> най-нисък
Забележка 1: Операторите >> и << са за побитови измествания надясно и наляво съответно. Те са предефинирани за входно/изходни операции. В случая имаме предвид тази тяхна употреба.
Забележка 2: Инфиксните операции, които са разположени на един и същ ред са с еднакъв приоритет. Тези оператори се изпълняват отляво надясно. Унарните операции се изпълняват отдясно наляво.
Пример: Нека имаме дефиницията
double x = 23.56, y = -123.5;
Изпълнението на оператора
cout << sin(x) + ceil(y) * x – cos(y);
ще се извърши по следния начин: отначало ще се пресметнат стойностите на sin(x), ceil(y) и cos(y), след това ще изпълни операцията * над стойността на ceil(y) и x, полученото ще събере със стойността на sin(x), след което от полученото реално число ще се извади пресметнатата вече стойност на cos(y). Накрая върху екрана ще се изведе получената реална константа.
Аритметичните изрази могат да съдържат операнди от различни типове. За да се пресметне стойността на такъв израз, автоматично се извършва преобразуване на типовете на операндите му. Без загуба на точността се осъществяват следните преобразувания:
Тип Преобразува се до тип
bool всички числови типове
short int
unsigned short unsigned int
float double

т.е.конвертира се от “по-малък” тип (в байтове) към “по-голям” тип.
За да се пресметне стойността на аритметичен израз с операнди от различни типове, последователно се прилагат правилата по-долу, докато се уеднаквят типовете (ако е възможно).
Ако има операнд от тип: Другите операнди се преобразуват до:
double double
float float
unsigned int unsigned int
int int
unsigned short unsigned short
short short


Семантика на булевите изрази

Булевите изрази са правила за получаване на булева стойност. За пресмятане на стойностите им се използва следният приоритет на операциите и вградените функции:
1. Вградени функции
2. Действията в скобите
3. Операции в следния приоритет
- !, not, +, - (унарни) най-висок
- *, /, %
- +, - (бинарни)
>> << (вход/изход)
<, <=, >, >=
==, !=
&&
|| най-нисък

Забележка: Инфиксните операции, които са разположени на един и същ ред са с еднакъв приоритет. Тези оператори се изпълняват отляво надясно, т.е. лявоасоциативни са. Унарните оператори се изпълняват отдясно наляво, т.е. дясноасоциативни са.
Примери: а) Нека имаме дефинициите:
double x = 23.56, y = -123.5;
bool b1, b2, b3;
b1 = true;
b2 = !b1;
b3 = b1||b2;
Изпълнението на оператора
cout << sin(x) + ceil(y) * x > 12398;
ще сигнализира грешка – некоректни аргументи на <<. Това е така, заради нарушения приоритет. Операторът << е с по-висок приоритет от този на операторите за сравнение. Налага се вторият аргумент на << да бъде ограден в скоби, т.е.операторът
cout << (sin(x) + ceil(y) * x > 12398);
вече работи добре.
б) Изпълнението на оператора
cout << b1 && b2 || b3 << “\n”;
също съобщава грешка – некоректни аргументи на <<. Отново е нарушен приоритетът на операциите. Налага се аргументът b1 && b2 || b3 на << да се огради в скоби, т.е.
cout << (b1 && b2 || b3) << “\n”;
вече работи добре.

Задачи върху типовете булев, цял и реален

Задача 5. Кои от следните редици от знаци са числа в C++?
а) 061 б) –31 в) 1/5 г) +910.009
д) VII е) 0.(3) ж) sin(0) з) 134+12
Решение: а), б), г).
Задача 6. Да се запишат на C++ следните числа:
а) 6! б) LXXIV в) –0,4(6) г) 138,2(38)
д) 11/4 е) p ж) 1,2 .10-1 з) –23,(1) .102
В дробната част да се укажат до 4 цифри.
Решение:
а) 120 б) 74 в) –0.4667 г) 138.2384
д) 2.7500 е) 3.1416 ж) 0.1200 з) –2311.1111
Задача 7. Да се запишат на езика C++ следните математически формули:


а) a + b.c – a2b3c4


Решение:
a) a + b * c – a * a * b * b * b * c * c * c * c
б) (a * b) / c + c / (a * b)
в) (1 + x + x*x/2)*(1 + x*x*x/6 + x*x*x*x*x/120)
или
(1 + x + pow(x,2))/(1 + pow(x, 3)/6 + pow(x, 5)/120)
г) sqrt(1 + sqrt(2 + sqrt(3 + sqrt(4))))
Задача 8. Какво ще бъде изведено след изпълнението на следната програма:
#include <iostream.h>
#include <math.h>
int main()
{ cout << "x=";
double x;
cin >>x;
bool b;
b = x < ceil(x);
cout << "x=";
cin >> x;
b = b && (x < floor(x));
cout << "b= " << b << "\n";
return 0;
}
ако като вход бъдат зададени числата
a) 2.7 и 0.8 б) 2.7 и –0.8 в) –2.7 и –0.8.
Решение: а) ТЪй като булевият израз 2.7 < ceil(2.7) има стойност true, а true && (0.8 < floor(0.8)) - е false, ще бъде изведено 0 (false).
Задача 9. Какъв ще е резултатът от изпълнението на програмата
#include <iostream.h>
int main()
{int a, b;
cin >> a >> b >> a >> b >> a;
cout << a << " " << b << " "
<< a << " " << b << " "
<< a << "\n";
return 0;
}
ако като вход са зададени числата 1, 2, 3, 4 и 5?
Решение: След обработката на дефиницията int a, b; за променливите a и b са отделени по 4 байта ОП, т.е.
ОП
а b
- -
а след изпълнението на оператора за четене >>, a и b получават отначало стойностите 1 и 2. След това стойностите им се променят на 3 и 4 съответно. Най-накрая a става 5, т,е,
a b
5 4
Тогава програмата извежда
5 4 5 4 5
Задача 10. Да се запише булев израз, който има стойност true, ако посоченото условие е в сила, и стойност false, ако условието не е в сила.
а) цялото число a се дели на 5;
б) точката x принадлежи на отсечката [2, 6];
в) точката x не принадлежи на отсечката [2, 6];
г) точката x принадлежи на отсечката [2, 6] или на отсечката [-4, -2];
д) поне едно от числата a, b и c е отрицателно;
е) числата a, b и c са равни помежду си.
Решение:
a % 5 == 0
б) x >= 2 && x <= 6
в) x < 2 || x > 6 или !( x >= 2 && x <= 6)
г) x >= 2 && x <= 6 || x >= -4 && x <= -2
е) a == b && a == c

Задача 11. Да се напише програма, която въвежда координатите на точка от равнината и извежда 1, ако точката принадлежи на фигурата по-долу и – 0, в противен случай.
y
2




-2 -1 0 1 2
x






-2


Решение:
#include <iostream.h>
#include <math.h>
int main()
{cout << "x= ";
double x;
cin >> x;
cout << "y= ";
double y;
cin >> y;
bool b1 = x*x + y*y <= 4 && y >= 0;
bool b2 = fabs(x) <= 1 && y < 0 && y >= -2;
cout << (b1 || b2) << "\n";
return 0;
}


Задачи


Задача 1. Да се запишат на езика C++ следните математически формули:







Задача 2. Кои от следните редици от символи са правилно записани изрази на езика C++:
а) 1 + |y| г) 1 + sqrt(sin((u+v)/10))
б) -abs(x) + sin z д) -6 + xy
в) abs(x) + cos(abs(y - 1.7)) е) 1/-2 + Beta.

Задача 3. Да се запишат в традиционна (математическа) форма следните изрази, записани в синтаксиса на езика C++:
а) sqrt(a+b) - sqrt(a-b) в) x*y/(u+v)-(u-v)/y*(a+b)
б) a + b/(c+d)-(a+b)/c+d г) 1+exp(cos((x+y)/2)).

Задача 4. Да се пресметне стойността на израза:
а) cos(0) + abs(1/(1/3-1))
б) abs(a-10) + sin(a-1), за a = 1;
в) cos(-2+2*x) +sqrt(fabs(x-5)), за x = 1;
г) sin(sin(x*x-1)*sin(x*x-1)) + cos(x*x*x-1)*abs(x-2),
за x = 1;
д) sin(sin(x*x-1))+cos(x*x*x-1)*cos(abs(x-2)-1)/y*a +
sqrt(abs(y)-x), за x = 1, y = -2, a = 2.

Задача 5. В аритметичния израз
а) a/b*c/d*е/f*h
б) a+b/x-2*y
в) a+b/x-2*y


да се поставят скоби така, че полученият израз да съответствува на математическата формула:


Задача 6. Да се напише израз на езика C++, който да изразява:
а) периметърa на квадрат с лице, равно на a;
б) лицето на равностранен триъгълник с периметър, равен на p.


Задача 7. Да се напише програма, която пресмята стойността на v1, където


Задача 8. Да се пресметне стойността на израза:
a) pow(x, 2) + pow(y, 2) <= 4 при x = 0.6, y = -1.2
б) p % 7 == p / 5 - 2 при p = 15
в) floor(10*k+16.3)/2 == 0 при k = 0.185
г) !((k+325)%2 == 1) при k = 28
д) u*v != 0 && v > u при u = 2, v = 1
е) x || !y при x = false, y = true.

Задача 9. Да се запише булев израз, който да има стойност истина, ако посоченото условие е вярно и стойност - лъжа, ако условието не е вярно:
а) цялото число p се дели на 4 или на 7;
б) уравнението a.x2 + b.x + c = 0 (a ≠ 0) няма реални корени;
в) точка с координати (a, b) лежи във вътрешността на кръг с радиус 5 и център (0, 1).
г) точка с координати (a, b) лежи извън кръга с център (c, d) и радиус f;
д) точка принадлежи на частта от кръга с център (0, 0) и радиус 5 в трети квадрант;
е) точка принадлежи на венеца с център (0, 0) и радиуси 5 и 10;
ж) x принадлежи на отсечката [0, 1];
з) x = max{a, b, c}
и) x != max{a, b, c} (операцията ! да не се използва);
к) поне една от булевите променливи x и y има стойност true;
л) и двете булеви променливи x и y имат стойност true;
м) нито едно от числата a, b и c е положително;
н) цифрата 7 влиза в записа на положителното трицифрено число p;
о) цифрите на трицифреното число m са различни;
п) поне две от цифрите на трицифреното число m са равни помежду си.

Допълнителна литература

К. Хорстман, Принципи на програмирането със C++, С., СОФТЕХ, 2000.
П. Лукас, Наръчник на програмиста, С., Техника, 1994.
Ст. Липман, Езикът C++ в примери, “КОЛХИДА ТРЕЙД” КООП, С. 1993.

ivakavlad
10-20-2010, 22:36
Глава 3

Основни структури за управление на изчислителния процес


В тази глава ще разгледаме управляващите оператори в езика. Ще ги наричаме само оператори.

4.1. Oператор за присвояване

Това е един от най-важните оператори на езика. Вече многократно го използвахме, а също в глава 2 описахме неговите синтаксис и семантика. В тази глава ще го разгледаме по-подробно. Ще напомним неговите синтаксис и семантика.
Синтаксис
<променлива> = <израз>;
- където <променлива> е идентификатор, дефиниран вече като променлива,
- <израз> е израз от тип, съвместим с типа на <променлива>.
Семантика
Намира се стойността на <израз>. Ако тя е от тип, различен от типа на <променлива>, конвертира се ако е възможно до него и се записва в именуваната с <променлива> памет.

• Ако <променлива> е от тип <bool>, <израз> може да бъде от тип bool или от кой да е числов тип.
• Ако <променлива> е от тип double, всички числови типове, а също типът bool, могат да са типове на <израз>.
• Ако <променлива> е от тип float, типовете float, short, unsiged short и bool, могат да са типове на <израз>. Ако <израз> е от тип int, unsigned int или double, присвояването може да се извърши със загуба на точност. Компилаторът предупреждава за това.
• Ако <променлива> е от тип int, типовете int, long int, short int и bool, могат да са типове на <израз>. В този случай ако <израз> е от тип double или float, дробната част на стойността на <израз> ще бъде отрязана и ако полученото цяло е извън множеството от стойности на типа int, ще се получи случаен резултат. Компилаторът издава предупреждение за това.
• Ако <променлива> е от тип short int, типовете short int и bool, могат да са типове на <израз>. В противен случай се извършват преобразувания, които водят до загуба на точност или даже до случайни резултати. Много компилатори не предупреждават за това.

Ще отбележим, че в рамките на една функция не са възможни две дефиниции на една и съща променлива, но на една и съща променлива може да й бъдат присвоявани многократно различни стойности.
Пример: Не са допустими

double a = 1.5;

double a = a + 5.1;

но са допустими присвояванията:

double a = 1.5;

a = a + 34.5;

a = 0.5 + sin(a);

В езика C++ са въведени някои съкратени форми на оператора за присвояване. Например, заради честото използване на оператора:
a = a + 1;
той съкратено се означава с
a++;
Въведено е също и съкращението a-- на оператора a = a-1;
В същност ++ и -- са реализирани като постфиксни унарни оператори увеличаващи съответно намаляващи аргумента си с 1. Приоритетът им е един и същ с този на унарните оператори +, - и !.
Забележка: От оператора ++, за добавяне на 1, идва името на езика C++ - вариант на езика C, към който са добавени много подобрения и нови черти.
Допълнение: Операторът за присвояване = е претоварен и с функцията на дясноасоциативна инфиксна аритметично-логическа операция с приоритет по-нисък от този на дизюнкцията ||. Това позволява на оператора за присвояване
X = y;
където x е променлива, а y – израз, да се гледа като на израз от тип – типа на x и стойност – стойността на y, ако е от типа на x или стойността на y, но преобразувана до типа на x.
Пример: Програмата
#include <iostream.h>
int main()
{int a;
double b;
b = 3.2342;
cout << (a = b) << "\n";
return 0;
}
е допустима. Резултът от изпълнението й е 3, като компилаторът издава предупреждение за загуба на информация при преобразуването от тип double в тип int. Изразът a = b е от тип int и има стойност 3. Ограждането му в скоби е необходимо заради по-ниския приоритет на = от този на <<.
Допълнение: Допустим е операторът:
X = y = 5;
Тъй като = е дясноасоциативен, отначало променливата y се свързва със 5, което е стойността на израза y = 5. След това x се свързва с 5, което е стойността на целия израз.
Някои компилатори издават предупреждение при тази употреба на оператора =. Затова не препоръчваме да се използва = като аритметичен оператор.

Задачи върху оператора за присвояване

Задача 12. Нека са дадени дефинициите
double x, y, z;
int m, n, p;
Кои от следните редици от символи са оператори за присвояване:
а) -x = y; б) x = -y; в) m + n = p;
г) p = x + y; д) z = x - y е) z = m + n;
ж) sin(0) = 0; з) x n + sin(z) к) 4 = sin(p + 5)?

В случаите а), в), ж) и к) редиците не са оператори за присвояване, тъй като на израз се присвоява израз. В случай г) редицата от символи е оператор за присвояване, но тъй като на цяла променлива се присвоява стойността на реален израз, компилаторът ще направи предупреждение за загуба на точност, а в случай з) е пропуснат символът '=' от знака за присвояване.

Задача 13. Да се напише програма, която въвежда стойности на реалните променливи a и b, след което разменя и извежда стойностите им (Например, ако a = 5.6, а b = -3.4, след изпълнението на програмата a да става -3.4 , а b да получава стойността 5.6).

Програма Zad13.cpp решава задачата.
Program Zad13.cpp
#include <iostream.h>
int main()
{cout << "a= ";
double a;
cin >> a;
cout << "b= ";
double b;
cin >> b;
double x;
x = a;
a = b;
b = x;
cout << "a= " << a << "\n";
cout << "b =" << b << "\n";
return 0;
}
В тази програма се използва работна променлива x, която съхранява първоначалната стойност на променливата a.

Задача 14. Да се напише програма, която въвежда положително трицифрено число и извежда на отделни редове цифрите на стотиците, на десетиците и на единиците на числото.

Програма Zad14.cpp решава задачата.
Program Zad14.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{ cout << "a – three-digit, integer and positive? ";
int a ;
cin >> a;
short s, d, e;
s = a / 100;
d = a / 10 % 10;
e = a % 10;
cout << setw(10) << "stotici: " << setw(5) << s << "\n";
cout << setw(10) << "desetici:" << setw(5) << d << "\n";
cout << setw(10) << "edinici: " << setw(5) << e << "\n";
return 0;
}

Задача 15. На цялата променлива b да се присвои първата цифра на дробната част на положителното реално число x (Например, ако x = 52.467, то b = 4).

Програма Zad15.cpp решава задачата
Program Zad15.cpp
#include<iostream.h>
#include <math.h>
int main()
{ cout << "x>0? ";
double x;
cin >> x;
int i = floor(x * 10);
int b = i % 10;
cout << x << "\n";
cout << b << "\n";
return 0;
}

Задача 16. Да се напише програма, която извежда 1, ако в записа на положителното четирицифрено число a, всички цифри са различни и 0 - в противен случай.

Програма Zad16.cpp решава задачата.
Program Zad20.cpp
#include <iostream.h>
int main()
{ cout << "a - four-digit, integer and positive? ";
int a ;
cin >> a;
short h, s, d, e;
h = a / 1000;
s = a / 100 % 10;
d = a / 10 % 10;
e = a % 10;
cout << (h != s && h != d && h != e &&
s != d && s != e && d != e) << "\n";
return 0;
}

ivakavlad
10-20-2010, 22:38
4.2. Празен оператор

Това е най-простия оператор на C++. Описанието му е дадено на Фиг. 1.

Синтаксис
;
Операторът не съдържа никакви символи. Завършва със знака ;.
Семантика
Не извършва никакви действия. Използва се когато синтаксисът на някакъв оператор изисква присъствието на поне един оператор, а логиката на програмата не изисква такъв.


Фиг. 1.

Забележка: Излишни празни оператори не предизвикват грешка при компилация. Например, редицата от оператори
a = 150;;;
b = 50;;;;
c = a + b;;
се състои от: оператора за присвояване a = 150, 2 празни оператора, оператора за присвояване b = 50, 3 празни оператора, оператора за присвояване c = a + b и 1 празен оператор и е напълно допустим програмен фрагмент.
Други примери ще дадем по-късно.

4.3. Блок

Често синтаксисът на някакъв оператор на езика изисква използването на един оператор, а логиката на задачата – редица от оператори. В този случай се налага оформянето на блок (Фиг. 2.).

Синтаксис
{ <оператор1>
<оператор2>
. . .
<операторn>
}
Семантика
Обединява няколко оператора в един, наречен блок. Може да бъде поставен навсякъде, където по синтаксис стои оператор.
Дефинициите в блока, се отнасят само за него, т.е. не могат да се използват извън него.

Фиг. 2.
Пример: Операторът
{cout << “a= “;
double a;
cin >> a;
cout << “b= “;
double b;
cin >> b;
double c = (a+b)/2;
cout << “average{a, b} = “ << c << “\n”;
}
е блок. Опитът за използване на променливите a, b и c след блока, предизвиква грешка.
Препоръка: Двойката фигурни скобки, отварящи и затварящи блока да се поставят една под друга.
Забележка: За разлика от другите оператори, блокът не завършва със знака ;.

4.4. Условни оператори

Чрез тези оператори се реализират разклоняващи се изчислителни процеси. Оператор, който дава възможност да се изпълни (или не) един или друг оператор в зависимост от някакво условие, се нарича условен. Ще разгледаме следните условни оператори: if, if/else и switch.

ivakavlad
10-20-2010, 22:47
4.4.1. Условен оператор if

Чрез този условен оператор се реализира разклоняващ се изчислителен процес от вид, илюстриран на Фиг. 3.


не


да





Фиг. 3.

Ако указаното условие е в сила, изпълняват се определени действия, а ако не – тези действия се прескачат. И в двата случая след това се изпълняват общи действия.
Условието се задава чрез някакъв булев израз, а действията – чрез оператор. Фиг. 4 описва подробно синтаксиса и семантиката на този оператор.


Синтаксис
if (<условие>) <оператор>
където
- if (ако) е запазена дума
- <условие> е булев израз;
- <оператор> е произволен оператор.
Семантика
Пресмята се стойността на булевия израз, представящ условието. Ако резултатът е true, изпълнява се <оператор>. В противен случай <оператор> не се изпълнява, т.е.

false

true
if (<условие>) <оператор>




Фиг. 4.

Забележки:
1. Булевият израз, определящ <условие>, трябва да бъде определен. Огражда се в кръгли скобки.
2. Операторът след условието е точно един. Ако е необходимо няколко оператора да се изпълнят, трябва да се обединят в блок.

Задача 17. Да се напише програма, която намира май-малкото от три дадени реални числа.

Ще реализираме следните стъпки:
а) Въвеждане на стойности на реалните променливи a, b и c.
б) Инициализиране със стойността на a на работна реална променлива min, която ще играе и ролята на изходна променлива.
в) Сравняване на b с min. Ако стойността на b е по-малка от запомнения в min текущ минимум, запомня се b в min. В противен случай, min не се променя. Така min съдържа min{a, b}.
г) Сравняване на c с min. Ако стойността на c е по-малка от запомнения в min текущ минимум, c се запомня в min. В противен случай, min не се променя. Така min съдържа min{a, b, c}.
д) Извеждане на резултата – стойността на min.

Програма Zad17.cpp реализира този алгоритъм.
Program Zad17.cpp
#include <iostream.h>
int main()
{cout << "a= ";
double a;
cin >> a;
cout << "b= ";
double b;
cin >> b;
cout << "c= ";
double c;
cin >> c;
double min = a;
if (b < min) min = b;
if (c < min) min = c;
cout << "min{" << a << ", " << b << ", "
<< c << "}= " << min << "\n";
return 0;
}
Ако вместо очаквано реално число, при въвеждане на стойности за променливите a, b и c, се въведе произволен низ, не представляващ число, буферът на клавиатурата, свързан със cin ще изпадне в състояние fail, а обектът cin ще има стойност false. Добрият стил за програмиране изисква в такъв случай програмата да прекъсне изпълнението си с подходящо съобщение за грешка. Програмата от задача 18 реализира този стил.

Задача 18. Да се напише програма, която намира най-малкото от три дадени реални числа. Програмата да извършва проверка за коректност на входните данни.

Програма Zad18.cpp решава задачата.
Program Zad18.cpp
#include <iostream.h>
int main()
{cout << "a= ";
double a;
cin >> a;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
cout << "b= ";
double b;
cin >> b;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
cout << "c= ";
double c;
cin >> c;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
double min = a;
if (b < min) min = b;
if (c < min) min = c;
cout << "min{" << a << ", " << b << ", "
<< c << "}= " << min << "\n";
return 0;
}
Ще напомним, че операторът return предизвиква преустановяване работата на програмата, а стойността 1 – че е възникнала грешка.

Задача 19. Да се сортира във възходящ ред редица от три реални числа, запомнени в променливите a, b и c.

Ще реализираме следните стъпки:
а) Въвеждане на стойности за a, b и c.
б) Сравняване на стойностите на a и b. Ако е в сила релацията b<a, извършва се размяна на стойностите на a и b. В противен случай – размяната не се извършва.
в) Сравняване на стойностите на a и c. Ако е в сила релацията c<a, извършва се размяна на стойностите на a и c. В противен случай – размяната не се извършва. След това действие, променливата a съдържа най-малката стойност на редицата.
г) Сравняване на стойностите на b и c. Ако е в сила релацията c<b, извършва се размяна на стойностите на b и c. В противен случай – размяната не се извършва.
е) Извеждане на стойностите на a, b, и c.

Програма Zad19.cpp реализира това описание.
Program Zad19.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{ cout << "a= ";
double a;
cin >> a;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
cout << "b= ";
double b;
cin >> b;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
cout << "c= ";
double c;
cin >> c;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
if (b < a) {double x = a; a = b; b = x;}
if (c < a) {double x = c; c = a; a = x;}
if (c < b) {double x = c; c = b; b = x;}
cout << setprecision(2) << setiosflags(ios :: fixed);
cout << setw(10) << a << setw(10) << b << setw(10)
<< c << "\n";
return 0;
}
Забележка: Променливата x е видима (може да се използва) само в блоковете, където е дефинирана.

ivakavlad
10-20-2010, 22:49
4.4.2. Оператор if/else

Операторът се използва за избор на една от две възможни алтернативи в зависимост от стойността на дадено условие.
Чрез него се реализира разклоняващ се изчислителен процес от вид, илюстриран на Фиг. 5.


не


да





Фиг. 5.

Ако указаното условие е в сила, се изпълняват се едни действия, а ако не – други действия. И в двата случая след това се изпълняват общи действия.
Условието се задава чрез някакъв булев израз, а действия 1 и действия 2 – чрез оператори. Фиг. 6 описва подробно синтаксиса и семантиката на този оператор.


Синтаксис
if (<условие>) <оператор1> else <оператор2>
където
- if (ако) и else (иначе) са запазени думи
- <оператор1> и <оператор2> са произволни оператори;
- <условие> е булев израз.
Семантика
Пресмята се стойността на булевия израз, представящ условието. Ако резултатът е true, изпълнява се <оператор1>. В противен случай се изпълнява <оператор2>, т.е.

false
true
if (<условие>) <оператор1> else <оператор2>




Фиг. 6.

Забележки:
1. Булевият израз, определящ <условие>, трябва да бъде напълно определен. Задължително се огражда в кръгли скобки.
2. Операторът след условието е точно един. Ако е необходимо няколко оператора да се изпълнят, трябва да се обединят в блок.
3. Операторът след else е точно един. Ако е необходимо няколко оператора да се изпълнят, трябва да се обединят в блок.


Задача 20. Променливата y зависи от променливата x. Зависимостта е следната:
Да се напише програма, която по дадено x намира съответната стойност на y.

Програма Zad20.cpp решава задачата.
Program Zad20.cpp
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
int main()
{ cout << "x= ";
double x;
cin >> x;
if (!cin)
{cout << "Error, bad input!!! \n";
return 1;
}
double y;
if (x >= 1) y = log10(x) + 1.82;
else y = x*x - 7*x + 8.82;
cout << setprecision(3) << setiosflags(ios :: fixed);
cout << setw(10) << x << setw(10) << y << "\n";
return 0;
}
След въвеждането на стойността на променливата x, програмата извършва проверка за валидност на въведената стойност. Изпълнението на оператора if/else води до пресмятане на стойността на булевия израз x >= 1. Ако тя е true, се изпълнява операторът за присвояване y = log10(x) + 1.82;. В противен случай се изпълнява операторът за присвояване y = x*x - 7*x + 8.82;, след което се извежда резултатът.

Вложени условни оператори

В условните оператори:
if (<условие>) <оператор>
if (<условие>) <оператор1> else <оператор2>
<оператор>, <оператор1> и <оператор2> са произволни оператори, в т. число могат да бъдат условни оператори. В този случай имаме вложени условни оператори.
При влагането е възможно да възникнат двусмислици. Ако в един условен оператор има повече запазени думи if отколкото else, възниква въпросът, за кой от операторите if се отнася съответното else. Например, нека разгледаме оператора
if (x >= 0) if ( x >= 5) x = 1/x; else x = -x;
Възможни са следните две различни тълкувания на този оператор:
а) if оператор, тялото на който е if/else оператор, т.е.
if (x >= 0)
if (x >= 5) x = 1/x; else x = -x;
При това тълкувание, ако преди изпълнението на if оператора x има стойност –5, след изпълнението му, стойността на x остава непроменена.
б) if/else оператор, с if оператор след <условие>, т.е.
if (x >= 0) if ( x >= 5) x = 1/x;
else x = -x;
При това тълкувание, ако преди изпълнението на if/else оператора x има стойност –5, след изпълнението му, стойността на x става 5.
Записът чрез съответни подравнявания, не влияе на компилатора. В езика C++ има правило, което определя начина по който се изпълняват вложени условни оператори.


Правило: Всяко else се съчетава в един условен оператор с най-близкото преди него несъчетано if. Текстът се гледа отляво надясно.


Според това правило, компилаторът на C++ ще приеме първото тълкувание за горните вложени условни оператори.
Препоръка: Условен оператор да се влага в друг условен оператор само след else. Ако се налага да се вложи след условието, вложеният условен оператор да се направи блок.

Задачи върху операторите if и if/else

Задача 21. Ако променливата a има стойност 8, определете каква стойност ще има променливата b след изпълнението на оператора
if (a > 4) b = 5; else
if (a < 4) b = -5; else
if (a == 8) b = 8; else b = 3;

Тъй като е в сила условието a > 4, променливата b ще получи стойността 5.

Задача 22. Стойността на y зависи от x. Зависимостта е следната:

Да се напише програма, която по дадено x, намира стойността на y.

Програма Zad22.cpp решава задачата.
Program Zad22.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{ cout << "x= ";
double x;
cin >> x;
if (!cin)
{ cout << "Error, Bad input\n";
return 1;
}
double y;
if (x <= 2) y = x; else
if (x <= 3) y = 2; else y = x-1;
cout << setprecision(3) << setiosflags(ios :: fixed);
cout << setw(10) << x << setw(10) << y << "\n";
return 0;
}
Забележка: В програма Zad22.cpp след първото else е в сила условието x > 2. Затова не е нужно то да се проверява.

Задача 23. Да се напише програма, която въвежда три реални числа a, b и c и извежда 0, ако не съществува триъгълник със страни a, b и c. Ако такъв триъгълник съществува, да извежда 3, 2 или 1 в зависимост от това какъв е триъгълникът - равностранен, равнобедрен или разностранен съответно.

Програма Zad23.cpp решава задачата.
Program Zad23.cpp
#include <iostream.h>
int main()
{ cout << "a= ";
double a;
cin >> a;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
cout << "b= ";
double b;
cin >> b;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
cout << "c= ";
double c;
cin >> c;
if (!cin)
{cout << "Error, bad input \n";
return 1;
}
bool x = a <= 0 || b <= 0 || c <= 0 ||
a+b <= c || a+c <= b || b+c <= a;
if (x) cout << 0 << "\n"; else
if (a == b && b == c) cout << 3 << "\n"; else
if (a == b || a == c || b == c) cout << 2 << "\n"; else
cout << 1 << "\n";
return 0;
}
Булевата променлива x е помощна. Тя има стойност true, ако a, b и c не са страни на триъгълник. Получена е след прилагане отрицание на условието a, b и c да са страни на триъгълник, т.е. на условието
a > 0 && b > 0 && c > 0 && a + b > c && a + c > b && b + c > a
като са използвани законите на де Морган.

Закони на де Морган:
!!A е еквивалентно на A
!(А || B) е еквивалентно на !A && !B
!(A && B) е еквивалентно на !A || !B

Задача 24. Да се напише програма, която на цялата променлива k присвоява номера на квадранта, в който се намира точка с координати (x, y). Точката не лежи на координатните оси, т.е. x.y ≠ 0.

Програмата Zad24.cpp решава задачата.
Program Zad24.cpp
#include <iostream.h>
int main()
{ cout << "x=";
double x;
cin >> x;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
cout << "y=";
double y;
cin >> y;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (x*y == 0)
{cout << "The input is incorrect! \n";
return 1;
}
int k;
if (x*y>0) {if (x>0) k = 1; else k= 3;}
else
if (x>0) k = 4; else k = 2;
cout << "The point is in: " << k << "\n";
return 0;
}

ivakavlad
10-20-2010, 22:50
4.4.3. Оператор switch

Често се налага да се избере за изпълнение един измежду множество от варианти. Пример за това дава следната задача.

Задача 25. Да се напише програма, която въвежда цифра, след което я извежда с думи.

За решаването на задачата трябва да се реализира следната неелементарна функция:



Последното може да стане чрез следната програма:
#include <iostream.h>
int main()
{cout << "i= ";
int i;
cin >> i;
if (!cin)
{cout << "Error, bad input!\n";
return 1;
}
if (i < 0 || i > 9)
{cout << "Bad input \n";
return 1;
}
else
if (i == 0) cout << "zero \n";
else if (i == 1) cout << "one \n";
else if (i == 2) cout << "two \n";
else if (i == 3) cout << "three \n";
else if (i == 4) cout << "four \n";
else if (i == 5) cout << "five \n";
else if (i == 6) cout << "six \n";
else if (i == 7) cout << "seven \n";
else if (i == 8) cout << "eight \n";
else if (i == 9) cout << "nine \n";
return 0;
}
В нея са използвани вложени if и if/else оператори, условията на които сравняват променливата i с цифрите 0, 1, 2, …, 9.

Има по-удобна форма за реализиране на това влагане. Постига се чрез оператора за избор на вариант switch.
Програма Zad25.cpp е друго решение на задачата.
Program Zad25.cpp
#include <iostream.h>
int main()
{cout << "i= ";
int i;
cin >> i;
if (!cin)
{cout << "Error, bad input!\n";
return 1;
}
if (i < 0 || i > 9)
{cout << "Bad input \n";
return 1;
}
else
switch (i)
{case 0 : cout << "zero \n"; break;
case 1 : cout << "one \n"; break;
case 2 : cout << "two \n"; break;
case 3 : cout << "three \n"; break;
case 4 : cout << "four \n"; break;
case 5 : cout << "five \n"; break;
case 6 : cout << "six \n"; break;
case 7 : cout << "seven \n"; break;
case 8 : cout << "eight \n"; break;
case 9 : cout << "nine \n"; break;
}
return 0;
}
Операторът switch започва със запазената дума switch (ключ), следван от, ограден в кръгли скобки, цял израз. Между фигурните скобки са изброени вариантите на оператора. Описанието им започва със запазената дума case (случай, вариант), следвана в случая от цифра, наречена етикет, двоеточие и редица от оператори.
Изпълнение на програмата
След въвеждането на стойност на променливата i се извършва проверка за коректност на въведеното. Нека въведената стойност е 7. Изпълнението на оператора switch причинява да бъде пресметната стойността на израза i – в случая 7. След това последователно сравнява тази стойност със стойностите на етикетите до намиране на етикета 7 и изпълнява редицата от оператори след него. В резултат върху екрана се извежда
seven
курсурът се премества на нов ред и се прекъсва изпълнението на оператора switch. Последното е причинено от оператора break в края на редицата от оператори за варианта с етикет 7.

Операторът switch реализира избор на вариант от множество варианти (възможности). Синтаксисът и семантиката му са дадени на Фиг. 7.


Синтаксис
switch (<израз>)
{ case <израз1> : <редица_от_оператори1>
case <израз2> : <редица_от_оператори2>

case <изразn-1> : <редица_от_операториn-1>
[default : <редица_от_операториn>]
}
където
- switch (ключ), case (случай, избор или вариант) и default (по премълчаване) са запазени думи на езика;
- <израз> е израз от допустим тип (Типовете bool, int и char са допустими, реалните типове double и float не са допустими). Ще го наричаме още switch-израз.
- <израз1>, <израз2>, …, <изразn-1> са константни изрази, задължително с различни стойности.
- <редица_от_операториi> (i = 1, 2, …, n) се дефинира по следния начин:
<редица_от_оператори> ::= <празно>|
<оператор>|
<оператор><редица_от_оператори>
Семантика
Намира се стойността на switch-израза. Получената константа се сравнява последователно със стойностите на етикетите <израз1>, <израз2>, … При съвпадение, се изпълняват операторите на съответния вариант и операторите на всички варианти, разположени след него, до срещане на оператор break. В противен случай, ако участва default-вариант, се изпълнява редицата от оператори, която му съответства и в случай, че не участва такъв – не следват никакви действия от оператора switch.


Фиг. 7.

Между фигурните скобки са изброени вариантите на оператора. Всеки вариант (без евентуално един) започва със запазената дума case, следвана от израз (нарича се още case–израз или етикет), който се пресмята по време на компилация. Такива изрази се наричат константни. Те не зависят от входните данни. След константния израз се поставя знакът двоеточие, следван от редица от оператори (оператори на варианта), която може да е празна. Сред вариантите може да има един (не е задължителен), който няма case-израз и започва със запазената дума default. Той се изпълнява в случай, че никой от останалите варианти не е бил изпълнен.
Съществува възможност програмистът да съобщи на компилатора, че желае да се изпълни само редицата от оператори на варианта с етикет, съвпадащ със стойността на switch-израза, а не и всички следващи го. Това се реализира чрез използване на оператор break в края на редицата от оператори на варианта. Този оператор предизвиква прекъсване на изпълнението на оператора switch и предаване на управлението на първия оператор след него (Фиг. 8.).
Операторът break принадлежи към групата на т. нар. оператори за преход. Тези оператори предават управлението безусловно в някаква точка на програмата.


Синтаксис
break;
Семантика
Прекратява изпълнеието на най-вътрешния съдържащ го оператор switch или оператор за цикъл. Изпълнението на програмата продължава от оператора, следващ (съдържащ) прекъснатия.


Фиг. 8.

Програмистът съзнателно пропуска оператора break, когато за няколко различни стойности от множеството от стойности, трябва да се извършат еднакви действия.
Забележка: Ако в оператора switch не е използван операторът break, ще бъдe изпълнена редицата от оператори на варианта, чийто case-израз съвпада със стойността на switch-израза и също всички след него.

Използването на оператора switch има едно единствено предимство пред операторите if и if/else – прави реализацията по-ясна. Основен негов недостатък е, че може да се прилага при много специални обстоятелства, произтичащи от наложените ограничения на типа на switch-израза, а именно, той трябва да е цял, булев или символен. Освен това, използването на оператора break, затруднява доказването на важни математически свойства на програмите, използващи break.

Задачи върху оператора switch

Задача 26. Да се напише програма, която по зададено реално число x намира стойността на един от следните изрази:
y = x - 5
y = sin(x)
y = cos(x)
y = exp(x).
Изборът на желания израз да става по следния начин: при въвеждане на цифрата 1 се избира първият, на 2 – вторият, на 3 – третият и на 4 – четвъртия израз.

Програма Zad25.cpp решава задачата.
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
int main()
{cout << "=================================\n";
cout << "| y = x-5 -> 1 |\n";
cout << "| y = sin(x) -> 2 |\n ";
cout << "| y = cos(x) -> 3 |\n";
cout << "| y = exp(x) -> 4 |\n";
cout << "=================================\n";
cout << " 1, 2, 3 or 4? \n";
int i;
cin >> i;
if (!cin)
{cout << "Error, Bad input!!! \n";
return 1;
}
if (i == 1 || i == 2 || i == 3 || i == 4)
{cout << "x= ";
double x;
cin >> x;
if (!cin)
{cout << "Error, Bad input!! \n";
return 1;
}
double y;
switch (i)
{case 1: y = x - 5; break;
case 2: y = sin(x); break;
case 3: y = cos(x); break;
case 4: y = exp(x); break;
}
cout << "y= " << y << "\n";
}
else
{cout << "Error, Bad choise!!\n";
return 1;
}
return 0;
}


Задачи

Задача 1. Явява ли се условен оператор редицата от символи:
а) if (x < y) x = 0; else y = 0;
б) if (x > y) x = 0; else cin >> y;
в) if (x >= y) x = 0; y = 0; else cout << z;
г) if (x < y) ; else z = 5;
д) if (x < y < z) then z = z + 1;
е) if (x != y) z = z+1; x = x + y;
Задача 2. Кое условие е в сила след запазената дума else на условния оператор:
а) if (a > 1 && a < 5) b = 5; else b = 10;
б) if (a < 1 || a > 5) b = a; else a = b;
в) if (a = b || a = c || b = c) c = a + b; else c = a – b;
Задача 3. Да се намерят грешките в следните оператори:
а) if (1 < x < 2) x = x + 1; y = 0;
else x = 0; y = y + 1;
б) if (1 < x) && (x < 2)
{x = x + 1;
y = 0;
};
else
{x = 0;
y = y + 1;
};
Задача 4. Да се напише програма, която по дадено реално число x намира стойността на y, където

Задача 5. Да се напише програма, която по зададени стойности на реалните променливи a, b и c намира:
а) min{a+b+c, a.b.c} + 15.2
б) max{a2 - b3 + c, a – 17.3 b, 3.1 a + 3.5 b – 8 c} - 17.9.
Задача 6. Да се напише програма, която увеличава по-малкото от две дадени цели неравни числа пет пъти, а по-голямото число намалява 8 пъти.
Задача 7. Да се напише програма, която въвежда четири реални числа и ги извежда във възходящ (низходящ) ред върху екрана.
Задача 8. Дадени са три числа a, b и c. Да се напише програма, в резултат от изпълнението на която, ако е в сила релацията a ≥ b ≥ c, числата се удвояват, в противен случай числата се заменят с техните абсолютни стойности.
Задача 9. Да се намери стойността на z след изпълнението на операторите
z = 0;
if (x > 0) if (y > 0) z = 1; else z = 2;
ако:
а) x = y = 1 б) x = 1, y = -1 в) x = -1, y = 1

Задача 10. Да се запише указаното действие чрез един условен оператор:

б) d = max(a, b, c)

Задача 11. Да се напише условен оператор, който е еквивалентен на оператора за присвояване
x = a || b && c;
където всички променливи са булеви и в който не се използват логически операции (Например, операторът x = not a; е еквивалентен на оператора if (a) x = false; else x = true;).
Задача 12. Да се напише оператор за присвояване, еквивалентен на условния оператор
if (a) x = b; else x = c;
(всички променливи са булеви).
Задача 13. Да се напише програма, която по зададено число a, намира корена на уравнението f(x) = 0, където

ivakavlad
10-20-2010, 22:53
Глава 4

4.5. Оператори за цикъл

Операторите за цикъл се използват за реализиране на циклични изчислителни процеси.
Изчислителен процес, при който оператор или група оператори се изпълняват многократно за различни стойности на техни параметри, се нарича цикличен.
Съществуват два вида циклични процеси:
индуктивни и
итеративни.
Цикличен изчислителен процес, при който броят на повторенията е известен предварително, се нарича индуктивен цикличен процес.

Пример: По дадени цяло число n и реално число x, да се намери сумата

Ако S има начална стойност 1, за да се намери сумата е необходимо n пъти да се повторят следните действия:

а) конструиране на събираемо

б) добавяне на събираемото към S.


Цикличен изчислителен процес, при който броят на повторенията не е известен предварително, се нарича итеративен цикличен процес. При тези циклични процеси, броят на повторенията зависи от някакво условие.
Пример: По дадени реални числа x и e > 0, да се намери сумата

където сумирането продължава до добавяне на събираемо, абсолютната стойност на което е по-малка от e.
Ако S има начална стойност 1, за да се намери сумата е необходимо да се повторят следните действия:

а) конструиране на събираемо

б) добавяне на събираемото към S
докато абсолютната стойност на последното добавено към сумата S събираемо стане по-малка от e.
В този случай, броят на повторенията зависи от стойностите на x и e.
В езика C++ има три оператора за цикъл:
оператор for
Чрез него могат да бъдат реализирани произволни циклични процеси, но се използва главно за реализиране на индуктивни циклични процеси.
оператори while и do/while
Използват се за реализиране на произволни циклични процеси – индуктивни и итеративни.

4.5.1. Оператор for

Използва се основно за реализиране на индуктивни изчислителни процеси. Чрез пример ще илюстрираме използването му.

Задача 26. Да се напише програма, която по зададено естествено число n, намира факториела му.

Тъй като n! = 1.2. … .(n-1).n, следната редица от оператори го реализира:
int fact = 1;
fact = fact * 1;
fact = fact * 2;

fact = fact * (n-1);
fact = fact * n;
В нея операторите за присвояване са написани по следния общ шаблон:
fact = fact * i;
където i е цяла променлива, получаваща последователно стойностите 1, 2, …, (n-1), n.
Следователно, за да се намери n! трябва да се реализира повтаряне изпълнението на оператора fact = fact * i, за i = 1, 2, …, n. Това може да стане с помощта на оператора for.

Програма Zad26.cpp решава задачата.
Program Zad26.cpp
#include <iostream.h>
int main()
{cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
if (n <= 0)
{cout << "Incorrect Input! \n";
return 1;
}
int fact = 1;
for (int i = 1; i <= n; i++)
fact = fact * i;
cout << n << "! = " << fact << "\n";
return 0;
}
Операторът for е в одебелен шрифт. Той започва със запазената дума for (за). В кръгли скобки след нея е реализaцията на конструкцията i = 1, 2, …, n. Тя се състои от три части: инициализация (int i = 1;), условие (i <= n) и корекция (i++), отделени с ;. Забележете, че операторът i++ не завършва с ;. След този фрагмент е записан операторът fact = fact * i;, описващ действията, които се повтарят. Нарича се тяло на цикъла.
Изпълнението на програмата започва с въвеждане стойност на променливата n и проверка валидността на въведеното. Нека n = 3. След дефиницията на цялата променлива fact, в ОП за нея са отделени 4 байта, инициализирани с 1. Изпълнението на оператора for предизвиква за цялата променлива i да бъдат заделени 4 байта, които да се инициализират също с 1. Следва проверка на условието i<=n и тъй като то е истина (1<=3), се изпълнява операторът fact = fact*i;, след което fact получава стойност 1. Изпълнението на оператора i++ увеличава текущата стойност на i с 1 и новата й стойност вече е 2. Отново следва проверка на условието i<=n и тъй като то е истина (2<=3), се изпълнява операторът fact = fact*i;, след което fact получава стойност 2. Изпълнението на оператора i++ увеличава текущата стойност на i с 1 и новата й стойност вече е 3. Пак следва проверка на условието i<=n и тъй като то отново е истина (3 <= 3), се изпълнява операторът fact = fact*i;, след което fact получава стойност 2*3, т.е. 6. Изпълнението на оператора i++ увеличава текущата стойност на i с 1 и новата й стойност вече е 4. Условието i<=n е лъжа и изпълнението на оператора for завършва.
Въпреки, че променливата i е дефинирана в оператора for, тя е “видима” (може да се използва) след изпълнението му, като стойността й е първата, за която стойността на условието i<=n не е в сила (в случая 4).
На Фиг. 9 е дадено детайлно описание на оператора for.

Синтаксис
for (<инициализация>; <условие>; <корекция>)
<оператор>
където
- for (за) е запазена дума.
- <инициализация> е или точно една дефиниция с инициализация на една или повече променливи, или няколко оператора за присвояване или въвеждане, отделени със , и не завършващи с ;.
- <условие> е булев израз.
- <корекция> е един или няколко оператора, незавършващи с ;. В случай, че са няколко, отделят се със ,.
- <оператор> е точно един произволен оператор. Нарича се тяло на цикъла.
Семантика
Изпълнението започва с изпълнение на частта <инициализация>. След това се намира стойността на <условие>. Ако в резултат се е получило false, изпълнението на оператора for завършва, без тялото да се е изпълнило нито веднъж. В противен случай последователно се повтарят следните действия:
- Изпълнение на тялото на цикъла;
- Изпълнение на операторите от частта <корекция>;
- Пресмятане стойността на <условие>
докато стойността на <условие> е true.
Следната схема илюстрира изпълнението му:

false

true
for (<инициализация>; <условие>; <корекция>)

<оператор>



Фиг. 9.

Възможно е частите <инициализция>, <условие> и <корекция> поотделно или заедно, да са празни. Разделителите (;) между тях обаче трябва да фигурират. Ако частта <условие> е празна, подразбира се true.

Забележки:
1. Тялото на оператора for е точно един оператор. Ако повече оператори трябва да се използват, се оформя блок.
2. Частта <инициализация> се изпълнява само веднъж – в началото на цикъла. Възможно е да се изнесе пред оператора for и остане празна (Пример 1). В нея не са допустими редици от оператори и дефиниция на променливи, т.е. недопустими са:
for (int i, i = 4; …
или
int i;
for (i = 4, int j = 5; …
а също две дефиниции, например
for(int i=3, double a = 3.5; …
Нарича се така, тъй като в нея обикновено се инициализират една или повече променливи.
3. Частта <корекция> се нарича така, тъй като обикновено чрез нея се модифицират стойностите на променливите, инициализирани в частта <инициализация>. Тя може да се премести в тялото на оператора for като се оформи блок от вида {<оператор> <корекция>;} (Пример 2).
4. Ако частта <условие> е празна, подразбира се true. За да се избегне зацикляне, от тялото на цикъла при определени условия трябва да се излезе принудително, например чрез оператора break (Пример 3). Това обаче е лош стил на програмиране и ние не го препоръчваме.
5. Следствие разширената интерпретация на true и false, частта <условие> може да бъде и аритметичен израз. Това също е лош стил на програмиране и не го препоръчваме.

Примери:
1. int i = 1;
for (; i<= n; i++)
fact = fact* i;
2. for (int i = 1; i<= n;)
{fact = fact* i;
i++;
}
3. for (int i = 1; ; i++)
if (i > n) break;
else fact = fact* i;

Област на променливите, дефинирани в заглавната част на for

Под област на променлива се разбира мястото в програмата, където променливата може да се използва. Казва се още където тя е “видима”.
Съгласно стандарта ANSI, областта на променлива, дефинирана в заглавната част на цикъла for започва от дефиницията й и продължава до края на цикъла.
Това значи, че тези променливи не са видими след оператора for, в който са дефинирани, т.е. фрагментът
for (int i = 1; i <= n; i++)
{…
}
for (i = 1; i <= m; i++)
{…
}
е недопустим - ще предизвика синтактична грешка заради недефинирана променлива i в заглавната част на втория оператор for.
Но тъй като това е ново решение на специалистите, поддържащи езика, повечето реализации в т.число и реализацията на Visual C++ 6.0, използват стария вариант, според който областта на променлива, дефинирана в заглавната част на цикъла for започва от дефиницията й и продължава до края на блока, в който се намира оператора for. Така, за реализацията на Visual C++ 6.0, горният фрагмент е напълно допустим. А фрагментът
for (int i = 1; i <= n; i++)
{…
}
for (int i = 1; i <= m; i++)
{…
}
сигнализира повторна дефиниция на променливата i.


Задачи върху оператора for

Задача 27. За какво може да бъде използвана следната програма?

Program Zad27.cpp
#include <iostream.h>
int main()
{cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n <= 0)
{cout << "Incorrect input! \n";
return 1;
}
int f = 1;
for (int i = 1; i <= n; cin >> i)
f = f * i;
cout << f << "\n";
return 0;
}
Забележете, че тази програма илюстрира използването на оператора for за реализиране на итеративни циклични процеси. Това обаче се счита за лош стил за програмиране.

Препоръка: Използвайте оператора for само за реализиране на индуктивни циклични процеси. Освен това, ако for има вида:
for(i = start; i < (или i <= ) end; i = i + increment)
{ …
}
не променяйте i, start, end и increment в тялото на цикъла. Това е лош стил за програмиране. Ако цикличният процес, който трябва да реализирате, не се вмества в тази схема, не използвайте оператора for.


Задача 28. Да се напише програма, която по зададени x – реално и n – естествено число, пресмята сумата

Програма Zad28.cpp решава задачата.
Program Zad28.cpp
#include <iostream.h>
int main()
{cout << "x= ";
double x;
cin >> x;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
cout << "n= ";
short n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n <= 0)
{cout << "Incorrect input! \n";
return 1;
}
double x1 = 1;
double s = 1;
for (int i = 1; i <= n; i++)
{x1 = x1 * x/i;
s = s + x1;
}
cout << "s= " << s << "\n";
return 0;
}


Задача 29. Нека n и m са дадени естествени числа, n ≥ 1, m > 1. Да се напише програма,която определя броя на елементите от серията числа

които са кратни на m.

Програма Zad29.cpp решава задачата.
Program Zad29.cpp
#include <iostream.h>
int main()
{cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1)
{cout << "Incorrect input! \n";
return 1;
}
cout << "m= ";
int m;
cin >> m;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (m <= 1)
{cout << "Incorrect input! \n";
return 1;
}
int br = 0;
for (int i = 1; i <= n; i++)
if ((i*i*i + 7*i*i + n*n*n) % 7 == 0) br++;
cout << "br= " << br << "\n";
return 0;
}

Задача 30. Дадено е естественото число n, n ≥ 1. Да се напише

програма, която намира най-голямото число от серията числа:

Програма Zad30.cpp решава задачата.
Program Zad30.cpp
#include <iostream.h>
#include <math.h>
int main()
{cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1)
{cout << "Incorrect input! \n";
return 1;
}
double max = cos(n+1/n);
for (int i = 2; i <= n; i++)
{double p = i*i*cos(n+i/n);
if (p > max) max = p;
}
cout << "max= " << max << "\n";
return 0;
}

Задача 31. Да се напише програма, която извежда върху екрана таблицата от стойностите на функциите sin x и cos x в интервала [0, 1].
Програма Zad31.cpp решава задачата.
Program Zad31.cpp
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
int main()
{cout << setprecision(5) << setiosflags(ios :: fixed);
for (double x = 0; x <= 1; x = x + 0.1)
cout << setw(10) << x << setw(10) << sin(x)
<< setw(10) << cos(x) << "\n";
return 0;
}

ivakavlad
10-20-2010, 22:55
4.5.2. Оператор while

Чрез този оператор може да се реализира произволен цикличен процес. С пример ще илюстрираме използването му.


Задача 32. Да се напише програма, която по дадени реални числа x и e (e > 0), прилижено пресмята сумата

Сумирането да продължи докато абсолютната стойност на последното добавено събираемо стане по-малка от e.

В тази задача броят на повторенията предварително не е известен, а зависи от условието |x1|< e, където с x1 е означено произволно събираемо. За решаването й е необходимо да се премине през следните стъпки:
Въвеждане на стойности на x и e.
Инициализация x1 = 1; s = 1.
Докато е в сила условието |x1|

ivakavlad
10-20-2010, 22:57
4.5.3. Оператор do/while

Използва се за реализиране на произволни циклични процеси. Ще го илюстрираме чрез пример, след което ще опишем неговите синтаксис и семантика. За целта ще използваме задача 37.

Програма Zad37_1.cpp реализира тази задача, като използва оператора do/while.
Program Zad37_1.cpp
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
int main()
{cout << "x= ";
double x;
cin >> x;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (x < -1 || x > 1)
{cout << "Incorrect Input! \n";
return 1;
}
cout << "eps= ";
double eps;
cin >> eps;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (eps <= 0)
{cout << "Incorrect input! \n";
return 1;
}
double x1;
double x2 = x;
double s = x;
int i = 2;
do
{x1 = x2;
x2 = -x1 * x * x / (i*(i+1));
s = s + x2;
i = i + 2;
} while (fabs(x1-x2) >= eps);
cout << setprecision(5) << setiosflags(ios :: fixed);
cout << "s=" << setw(10) << s << "\n";
return 0;
}
Операторът do/while в нея е в одебелен шрифт. Започва със запазената дума do (прави, повтаряй следното), следва оператор (в случая блок), който определя действията, които се повтарят и затова се нарича тяло на цикъла. Запазената дума while (докато) отделя тялото на оператора от булевия израз fabs(x1-x2) >= eps. Последният е ограден в кръгли скобки и определя условието за завършване изпълнението на цикъла.
Ще проследим изпълнението на програмата за x = 1 и eps = 0.5.
След изпълнението на операторите за въвеждане и дефинициите на s, x1, x2 и i, състоянието на паметта е следното:
x eps x1 x2 s i
1.0 0.5 - 1.0 1.0 2
Изпълнението на оператора за цикъл започва с изпълнение на тялото на цикъла – блока
{x1 = x2;
x2 = -x1 * x * x / (i*(i+1));
s = s + x2;
i = i + 2;
}
след което се получава:
x eps x1 x2 s i
1.0 0.5 1.0 -0.16667 0.83333 4
Пресмята се стойността на булевия израз fabs(x1-x2) >= eps и тъй като тя е true, повторно се изпълняват операторите от блока, съставящ тялото. В резултат имаме:
x eps x1 x2 s i
1.0 0.5 -0.16667 0.00833 0.84167 6
Сега вече стойността на булевия израз, определящ условието за завършване, има стойност false. Изпълнението на оператора за цикъл завършва. С извеждането на стойността на сумата s завършва и изпълнението на програмата.
Забелязваме, че в тази програма, настройката на променливите x1 и x2 става в тялото на цикъла do/while, а не извън него. Това се обуславя от факта, че тялото на този вид цикъл поне веднъж ще се изпълни.
Описанието на синтаксиса и семантиката на оператора do/while е илюстрирано на Фиг. 11.


Синтаксис
do
<оператор>
while (<условие>);
където
- do (прави, повтаряй докато …) и while (докато) са запазени думи на езика.
- <оператор> е точно един оператор. Той описва действията, които се повтарят и се нарича тяло на цикъла.
- <условие> е булев израз. Нарича се условие за завършване на цикъла. Огражда се в кръгли скобки.
Семантика
Изпълнява се тялото на цикъла, след което се пресмята стойността на <условие>. Ако то е false, изпълнението на оператора do/while завършва. В противен случай се повтарят действията: изпълнение на тялото и пресмятане стойността на <условие>, докато стойността на <условие> е true. Веднага, след като стойността му стане false, изпълнението на оператора завършва.


Фиг. 11.
Забележки:
1. Между запазените думи do и while стои точно един оператор. Ако няколко действия трябва да се извършат, оформя се блок.
2. Дефинициите в тялото, не са видими в <условие>. Например, не е допустим фрагментът:
double x2 = x;
double s = x;
int i = 2;
do
{double x1 = x2;
x2 = -x1 * x * x / (i*(i+1));
s = s + x2;
i = i + 2;
} while (fabs(x1-x2) >= eps);
Компилаторът ще съобщи, че x1 не е дефиниран на линия:
} while (fabs(x1-x2) >= eps);
Следователно, всички променливи в <условие> трябва да са дефинирани извън оператора do/while.
3. Следствие разширената интерпретация на true и false, частта <условие> може да е аритметичен израз. Това е лош стил за програмиране и ние не го препоръчваме.
4. Операторът do/while завършва с ;.

Задачи върху оператора do/while

Задача 38. Да се напише програма, която намира произведението на целите числа от m до n, където m и n са дадени естествени числа и m ≤ n. За целта да се използва операторът do/while.

Програма Zad38.cpp решава задачата.
Program Zad38.cpp
#include <iostream.h>
int main()
{cout << "m= ";
int m;
cin >> m;
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
if (m <= 0)
{cout << "Incorrect Input! \n";
return 1;
}
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
if (n <= 0)
{cout << "Incorrect Input! \n";
return 1;
}
if (m > n)
{cout << "Incorrect Input! \n";
return 1;
}
int prod = 1;
int i = m;
do
{prod = prod * i;
i++;
} while (i <= n);
cout << prod << "\n";
return 0;
}
Тъй като е в сила релацията m ≤ n, произведението ще съдържа поне един елемент от редицата от цели числа {m, m+1, m+2, …, n}. Това прави възможно използването на оператора do/while.

Задача 39. Да се напише програма, в резултат от изпълнението на която се изяснява, има ли сред числата от серията: i3 – 3.i + n3, i = 1, 2, …, n, число, кратно на 5. Ако има, да се изведе true, иначе – false.

Решението на тази задача изисква последователно да се конструират елементите от серията числа. Това продължава до намиране на първото число, кратно на 5, или до изчерпване на редицата без число с това свойство да е намерено.

Програма Zad39_1.cpp е едно решение на задачата.
Program Zad39_1.cpp
#include <iostream.h>
int main()
{cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad Input!!! \n";
return 1;
}
if (n <= 0 )
{cout << "Incorrect Input! \n";
return 1;
}
int i = 0;
int a;
do
{i++;
a = i*i*i - 3*i + n*n*n;
} while (a%5 != 0 && i < n);
if (a%5 == 0) cout << "true\n";
else cout << "false\n";
return 0;
}

Възникват два въпроса:
1) Ако условието условие (a%5 != 0 && i < n) стане лъжа, тъй като се излиза от цикъла, правилно ли следващият оператор определя резултата?
От законите на де Морган следва, че е истина a%5 == 0 || i = n. Ако a%5 == 0, тъй като a е i-тият елемент на серията и i ≤ n, наистина в серията съществува елемент с исканото свойство. Ако a%5 не е 0, следва че i = n ще е в сила, т.е. a е n – тият елемент и за него свойството не е в сила. Но тъй като са сканирани всички елементи от серията, наистина в нея не съществува елемент с търсеното свойство.
2) Ще се стигне ли до състояние, при което горното условие наистина ще е лъжа?
Условието (a%5 != 0 && i < n) ще е лъжа, ако a%5 == 0 || i = n е в сила и се достига или когато в серията има елемент с търсеното свойство, или е сканирана цялата серия и i указва последния й елемент. Ако в серията няма елемент с исканото свойство, тъй като i е инициализирано с 0 и се увеличава с 1 на всяка стъпка от изпълнението на програмата, в един момент ще стане вярно условието i = n, т.е. цикълът ще завърши изпълнението си.

Друго решение дава програмата Zad39_2.cpp. То не съдържа фрагментът, въвеждащ стойност на променливата n.

Program Zad39_2.cpp
#include <iostream.h>
int main()
{ …
int i = 1;
int a;
do
{a = i*i*i - 3*i + n*n*n;
i++;
} while (a%5 != 0 && i <= n);
if (a%5 == 0) cout << "true\n";
else cout << "false\n";
return 0;
}
Лошото при това решение, че в тялото на цикъла има разминаване на елемента от серията, запомнен в a, и поредния му номер.

Задача 40. Да се напише програма, която въвежда естествено число и установява, дали цифрата 5 участва в записа на числото.

Програма Zad40.cpp решава задачата.
Program Zad40.cpp
#include <iostream.h>
int main()
{cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad Input!!! \n";
return 1;
}
if (n <= 0 )
{cout << "Incorrect Input! \n";
return 1;
}
int d;
do
{d = n % 10;
n = n / 10;
} while (d != 5n && n != 0);
if (d == 5) cout << "true\n";
else cout << "false\n";
return 0;
}
В тялото на цикъла последователно се намират цифрата на единиците на числото n и числото без цифрата на единиците си. Това продължава докато поредната цифра е различна от 5 и останалото число е различно от 0.

Задача 41. Нека a е неотрицателно реално число. Да се напише програма, която приближено пресмята квадратен корен от a по метода на Нютон.
Упътване: (метод на Нютон) Дефинира се редица от реални числа x0, x1, x2, x3, … по следния начин:


Сумирането да продължи докато абсолютната стойност на разликата на последните два констуирани елемента на редицата е по-малка от e, e>0 е дадено достатъчно малко реално число.

Програма Zad41.cpp решава задачата.
Program Zad41.cpp
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
int main()
{cout << "a= ";
double a;
cin >> a;
if (!cin)
{cout << "Bad Input! \n";
return 1;
}
if (a < 0)
{cout << "Incorrect Input! \n";
return 1;
}
cout << "eps= ";
double eps;
cin >> eps;
if (!cin)
{cout << "Bad Input! \n";
return 1;
}
if (eps <= 0 || eps > 0.5)
{cout << "Incorrect Input! \n";
return 1;
}
double x0;
double x1 = 1;
do
{x0 = x1;
x1 = 0.5*(x0 + a / x0);
} while (fabs (x1-x0) >= eps);
cout << setprecision(6) << setiosflags(ios :: fixed);
cout << "sqrt(" << a << ")= " << setw(10) << x1 << "\n";
return 0;
}

ivakavlad
10-20-2010, 22:58
4.5.4. Вложени оператори за цикъл

Тялото на кой де е от операторите за цикъл е произволен оператор. Възможно е да е оператор за цикъл или блок, съдържащ оператор за цикъл. В тези случаи се говори за вложени оператори за цикъл.
Пример: Програмният фрагмент
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 5; j++)
cout << “(“ << i << “, “ << j << “)\n”;
съдържа вложен оператор for и се изпълнява по следния начин: Променливата i получава последователно целите стойности 1, 2 и 3. За всяка от тези стойности, променливата j получава стойностите 1, 2, 3, 4 и 5 и за тях се изпълнява операторът
cout << “(“ << i << “, “ << j << “)\n”;
В резултат се конструират и извеждат на отделни редове всички двойки от вида (i, j), където i = 1, 2, 3 и j = 1, 2, 3, 4, 5.

При влагането на цикли, а също при използването на блокове, възникват проблеми, свързани с видимостта на дефинираните променливи.

Област на променлива

Общото правило за дефиниране на променлива е, дефиницията й да е възможно най-близко до мястото където променливата ще се използва най-напред.
Областта на една променлива започва от нейната дефиниция и продължава до края на блока, в който променливата е дефинирана. На Фиг. 12 за променливите a, b и c са определени областите им.

int main()
{…
double a;

for ( … )
{…
double b; област на a

for ( … ) област на b
{ …
int c;
… област на c
}
}
return 0;
}

Фиг. 12

Променлива, дефинирана в някакъв блок, се нарича локална променлива за блока.
Променлива, дефинирана извън даден блок, но така, че областта й включва блока, се нарича нелокална променлива за този блок.
Всяка променлива е видима – може да се използва в областта си. Така b и c не могат да се използват навсякъде в тялото на main, а само с означените области.

Възниква въпросът: Може ли променливи с еднакви имена да бъдат дефинирани в различни блокове на програма?
Ако областите на променливите не се припокриват, очевидно няма проблем. Ако обаче те са вложени една в друга, пак е възможно, но е реализирано следното правило: локалната променлива “скрива” нелокалната в областта си.
Пример:
int main()
{…
double i;

for ( … )
{… област на
int i; double i
… област на int i;
}

}
Според правилото, в тялото на оператора for е видима цялата променлива i (локална за тялото), а не нелокалната double i.
Ако това води до конфликт с желанията ви, преименувайте например локалната за тялото на for променлива int i.

Задачи върху вложени оператори за цикъл

Задача 42. Да се напише програма, която намира всички решения на деофантовото уравнение a1.x1 + a2.x2 + a3.x3 + a4.x4 = a, където a1, a2, a3, a4 и a са дадени цели числа, а неизвестните x1, x2, x3 и x4 приемат стойности от интервала [p, q] / p и q са дадени цели числа, p<q /.

Програма Zad42.cpp решава задачата.
Program Zad42.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{cout << "a1= ";
int a1;
cin >> a1;
if (!cin)
{cout << "Error, Bad Input!\n";
return 1;
}
cout << "a2= ";
int a2;
cin >> a2;
if (!cin)
{cout << "Error, Bad Input!\n";
return 1;
}
cout << "a3= ";
int a3;
cin >> a3;
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
cout << "a4= ";
int a4;
cin >> a4;
if (!cin)
{cout << "Error, Bad Input!\n";
return 1;
}
cout << "a= ";
int a;
cin >> a;
if (!cin)
{cout << "Error, Bad Input!\n";
return 1;
}
cout << "p= ";
int p;
cin >> p;
if (!cin)
{cout << "Error, Bad Input!\n";
return 1;
}
cout << "q= ";
int q;
cin >> q;
if (!cin)
{cout << "Error, Bad Input!\n";
return 1;
}
if (p>=q)
{cout << "Error!\n";
return 1;
}
for (int x1 = p; x1<= q; x1++)
for (int x2 = p; x2 <= q; x2++)
for (int x3 = p; x3 <= q; x3++)
for (int x4 = p; x4 <= q; x4++)
if (a1*x1 + a2*x2 + a3*x3 + a4*x4 == a)
cout << setw(5) << x1 << setw(5) << x2
<< setw(5) << x3 << setw(5) << x4 << "\n";
return 0;
}

Задача 43. Да се напише програма, която проверява дали съществува решение на деофантовото уравнение a1.x1 + a2.x2 + a3.x3 + a4.x4 = a в интервала [p, q], където a1, a2, a3, a4, a, p и q са дадени цели числа, p<q.

Програма Zad43.cpp решава задачата. Ще пропуснем дефинициите на променливите a1, a2, a3, a4, a, p и q. Те са аналогични на тези от задача 42. Условието p < q прави подходящ оператора do/while.
Program Zad43.cpp
#include <iostream.h>
int main()
{…
if (p>=q)
{cout << "Error!!\n";
return 1;
}
int x1 = p;
bool b;
do
{int x2 = p;
do
{int x3 = p;
do
{int x4 = p;
do
{b = a1*x1 + a2*x2 + a3*x3 + a4*x4 == a;
x4++;
} while (!b && x4 <= q);
x3++;
} while (!b && x3 <= q);
x2++;
} while (!b && x2 <= q);
x1++;
} while (!b && x1 <= q);
if (b) cout << "yes\n";
else cout << "no\n";
return 0;
}


Задачи

Задача 1. Да се напише програма, която по дадено реално число x намира стойността на y:
а) y = ( ...((( x + 2) x + 3) x + 4) x +... + 10) x + 11
б) y= ( ...(((11x + 10)x + 9)x + 8)x + ... + 2)x +1.
Задача 2. Да се напише програма, която намира сумата от кубовете на всички цели числа, намиращи се в интервала (x + lnx, x2 + 2x + ex), където x > 1.
Задача 3. Дадено е естественото число n (n ≥ 1). Да се напише програма, която намира броя на тези елементи от серията числа i3 - 7.i.n + n3 , i = 1, 2, ..., n, които са кратни на 3 или на 7.
Задача 4. Да се напише програма, която по дадено реално число x, намира стойността на сумата
а) y = sinx + sinx2 + sinx3 + ... + sinxn;
б) y = sinx + sin2x + sin3x + ... + sinnx;
в) y = sinx + sinsinx + sinsinsinx + ... + sinsin ...sinx

n пъти

Задача 5. Да се напише програма, която намира

Задача 6. Да се напише програма, която по дадено естествено число n ( n >= 1 ) намира стойността на f:
а) f = (2n)!! = 2.4.6. ... .2n;
б) f = (2n-1)!! = 1.3.5. ... .(2n-1);
в) f = n!!.


Задача 7. Дадено е естественото число n (n ≥ 1). Да се напише програма, която пресмята сумата:



(Да не се използват функциите exp и lоg).
Задача 8. Да се напише програма, която извежда в нарастващ ред всички трицифрени естествени числа, които не съдържат еднакви цифри (/ и % да не се използват).
Задача 9. Да се напише програма, която намира и извежда броя на точките с цели координати, попадащи в кръга с радиус R (R > 0) и център - координатното начало.
Задача 10. Да се напише програма, която извежда таблицата на истинност за булевата функция f = (a and b) or not (b or c) в следния вид
a b c f
------------------------------------
true true true true
true true false true
. . .
false false false true

Задача 11. Да се напише програма, която извежда върху екрана следната таблица:
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7

Задача 12. Дадено е естествено число n (n ≥ 1). Да се напише програма, която намира и извежда първите n елемента от серията числа

(Да не се използват функциите exp и log).
Задача 13. Едно естествено число е съвършено, ако е равно на сумата от своите делители (без самото число). Например, 6 е съвършено, защото 6 = 1+2+3. Да се напише програма, която намира всички съвършени числа ненадминаващи дадено естествено число n.
Задача 14. Да се напише програма, която намира всички трицифрени числа от интервала [m, n], на които като се задраска цифрата на десетиците, намаляват цяло число пъти (m и n са дадени естествени числа, m < n).
Задача 15. Да се напише програма, която намира всички четирицифрени числа от интервала [m, n], на които като се задраска цифрата на стотиците, се делят на 11 (m и n са дадени естествени числа, m < n).
Задача 16. Да се напише програма, която намира всички четирицифрени числа от интервала [m, n], в записа на които участва цифрата 5 (m и n са дадени естествени числа, m < n).
Задача 17. Да се напише програма, която намира всички четирицифрени числа от интервала [m, n], цифрите на които образуват намаляваща редица (m и n са дадени естествени числа, m < n).
Задача 18. Да се напише програма, която намира всички петцифрени числа от интервала [m, n], цифрите на които са различни (m и n са дадени естествени числа, m < n).
Задача 19. Дадено е естествено число n (n > 1). Да се напише програма, която намира всички прости числа от интервала [2, n].
Задача 20. Да се напише програма, която намира всички прости делители на дадено естествено число n.

Задача 21. Да се напише програма, която по дадени реални числа x, a и e,e>0, приближено пресмята сумата.

Събирането да продължи докато бъде добавено събираемо, абсолютната стойност на което е по-малка от e (e > 0 е дадено реално достатъчно малко число).
Задача 22. Да се напише програма, която намира броя на цифрите в десетичния запис на дадено естествено число.
Задача 23. Да се напише програма, която проверява дали дадено естествено число е щастливо, т.е. едно и също е при четене отляво надясно и отдясно наляво.
Задача 24. Да се напише програма, която проверява дали сумата от цифрите на дадено естествено число е кратна на 3.
Задача 25. Да се напише програма, която намира всички естествени числа, ненадминаващи дадено естествено число n, които при преместване на първата им цифра най-отзад, се увеличават k пъти (k е дадено естествено число, k > 1).
Задача 26. Да се напише програма, която намира всички естествени числа от интервала [m, n], на които като се задраска k – тата цифра (отляво надясно), намаляват цяло число пъти (m, n и k са дадени естествени числа, m < n).
Задача 27. Да се напише програма, която намира всички естествени числа от интервала [m, n], на които като се задраска k – тата цифра (надясно наляво), намаляват цяло число пъти (m, n и k са дадени естествени числа, m < n).
Задача 28. За дадено естествено число да се провери дали цифрите му, гледани отляво надясно, образуват монотонно растяща редица.
Задача 29. За дадено естествено число да се провери дали цифрите му са различни.
Задача 30. Да се намерят всички прости делители на дадено естествено число.
Задача 31. Числата на Фибоначи се дефинират по следния начин:



Да се напише програма, която намира сумата на числата на Фибоначи от интервала [a, b] (a и b са дадени естествени числа).
Задача 32. За естествените числа n и m операцията ++ се определя по следния начин: n oo m = n + m + n%m. Да се напише програма, която намира всички двойки (n, m) от естествени числа, за които е в сила
n oo m = m oo n (m и n са естествени числа от интервала [a, b]).
Задача 33. За естествените числа n и m операцията ++ се определя по следния начин: n oo m = n + m + n%m. Да се напише програма, която проверява дали съществува двойкА (n, m) от естествени числа, за която е в сила релацията n oo m = m oo n (m и n са естествени числа от интервала [a, b]).


Допълнителна литература

К. Хорстман, Принципи на програмирането със C++, С., СОФТЕХ, 2000.
Ст. Липман, Езикът C++ в примери, “КОЛХИДА ТРЕЙД” КООП, С. 1993.
B. Stroustrup, C++ Programming Language. Third Edition,Addison – Wesley, 1997.

ivakavlad
10-20-2010, 23:00
Глава 5

Съставни типове данни.
Тип масив


Структура от данни масив

Под структура от данни се разбира организирана информация, която може да бъде описана, създадена и обработена с помощта на програма.
За да се определи една структура от данни е необходимо да се направи:
- логическо описание на структурата, което я описва на базата на декомпозицията й на по-прости структури, а също на декомпозиция на операциите над структурата на по-прости операции.
- физическо представяне на структурата, което дава методи за представяне на структурата в паметта на компютъра.
В предходните глави разгледахме структурите числа и символи. За всяка от тях в езика C++ са дадени съответни типове данни, които ги реализират. Тъй като елементите на тези структури се състоят от една компонента, те се наричат прости, или скаларни.
Структури от данни, компонентите на които са редици от елементи, се наричат съставни.
Структури от данни, за които операциите включване и изключване на елемент не са допустими, се наричат статични, в противен случай - динамични.
В тази глава ще разгледаме структурата от данни масив и средствата, които я реализират.

Логическо описание

Масивът е крайна редица от фиксиран брой елементи от един и същ тип. Към всеки елемент от редицата е възможен пряк достъп, който се осъществява чрез индекс. Операциите включване и изключване на елемент в/от масива са недопустими, т.е. масивът е статична структура от данни.

Физическо представяне

Елементите на масива се записват последователно в паметта на компютъра, като за всеки елемент на редицата се отделя определено количество памет.

В езика C++ структурата масив се реализира чрез типа масив.

ivakavlad
10-20-2010, 23:02
Тип масив

В C++ структурата от данни масив е реализирана малко ограничено. Разглежда се като крайна редица от елементи от един и същ тип с пряк достъп до всеки елемент, осъществяващ чрез индекс с цели стойности, започващи от 0 и нарастващи с 1 до указана горна граница.

Задаване на масив

Типът масив се определя чрез задаване на типа и броя на елементите на редицата, определяща масив. Нека T е име или дефиниция на произволен тип, различен от псевдоним, void и функционален. За типа T и константния израз от интегрален или изброен тип с положителна стойност size, T[size] е тип масив от size елемента от тип T. Елементите се индексират от 0 до size–1. T се нарича базов тип за типа масив, а size – горна граница.
Примери:
int[5] е масив от 5 елемента от тип int, индексирани от 0 до 4;
double[10] е масив от 10 елемента от тип double, индексирани от 0 до 9;
bool[4] е масив от 4 елемента от тип bool, индексирани от 0 до 3.

Множество от стойности

Множеството от стойности на типа T[size] се състои от всички редици от по size елемента, които са произволни константи от тип T. Достъпът до елементите на редиците е пряк и се осъществява с помощта на индекс, като достъпът до първия елемент се осъществява с индекс със стойност 0, до последния – с индекс със стойност size-1, а до всеки от останалите елементи – с индекс със стойност с 1 по-голяма от тази на индекса на предишния елемент.
Примери:
1. Множеството от стойности на типа int[5] се състои от всички редици от по 5 цели числа. Достъпът до елементите на редиците се осъществява с индекс със стойности 0, 1, 2, 3 и 4.

int[5]




0 1 2 3 4

2. Множеството от стойности на типа double[10] се състои от всички редици от по 10 реални числа. Достъпът до елементите на редиците се осъществява с индекс със стойности 0, 1, 2, 3 и т.н. 9.

double[10]




0 1 9
Елементите от множеството от стойности на даден тип масив са константите на този тип масив.
Примери:
1. Следните редици {1,2,3,4,5}, {-3, 0, 1, 2, 0}, {12, -14, 8, 23, 1000} са константи от тип int[5].

2. Редиците {1.5, -2.3, 3.4, 4.9, 5.0, -11.6, -123, 13.7, -32.12, 0.98}, {-13, 0.5, 11.9, 21.98, 0.03, 1e2, -134.9, 0.09, 12.3, 15.6} са константи от тип double[10].

Променлива величина, множеството от допустимите стойности на която съвпада с множеството от стойности на даден тип масив, се нарича променлива от дадения тип масив. Понякога ще я наричаме само масив.
Фиг. 1 определя дефиницията на променлива от тип масив. Тук общоприетият запис е нарушен. Променливата се записва между името на типа и размерността.


<дефиниция_на_променлива_от_ тип_масив> ::=
T <променлива>[size]; |
T <променлива>[size] = {<редица_от_константни_израз >};
където
Т e име или дефиниция на произволен тип, различен от псевдоним, void, функционален;
<променлива> ::= <идентификатор>
size е константен израз от интегрален или изброен тип със положителна стойност;
<редица_от_константни_израз > ::= <константен_израз>|
<константен_израз>, <редица_от_константни_израз >
като константните изрази са от тип T или от тип, съвместим с него.


Фиг. 1.

Примери:
int a[5];
double c[10];
bool b[3];
enum {FALSE, TRUE} x[20];
double p[4] = {1.25, 2.5, 9.25, 4.12};

Вторият случай от дефиницията от Фиг. 1 се нарича дефиниция на масив с инициализация. При нея е възможно size да се пропусне. Тогава за стойност на size се подразбира броят на константните изрази, изброени при инициализацията. Ако size е указано и изброените константни изрази в инициализацията са по-малко от size, останалите се инициализират с 0.

Примери:
Дефиницията
int q[5] = {1, 2, 3};
е еквивалентна на
int q[] = {1, 2, 3, 0, 0};
Дефиницията
double r[] = {0, 1, 2, 3};
e еквивалентна на
double r[4] = {0, 1, 2, 3};
Забележка: Не са възможни конструкции от вида:
int q[5];
q = {0, 1, 2, 3, 4};
а също
int q[];
и
double r[4] = {0.5, 1.2, 2.4, 1.2, 3.4};
Фрагментите
<променлива>[size] и
<променлива>[size] = {<редица_от_константни_израз >}
от дефиницията от Фиг. 1. могат да се повтарят. За разделител се използва знакът запетая.
Пример: Дефиницията
double m1[20], m2[35], proben[30];
е еквивалентна на дефинициите
double m1[20];
double m2[35];
double proben[30];

Инициализацията е един начин за свързване на променлива от тип масив с конкретна константа от множеството от стойности на този тип масив. Друг начин предоставят т.нар. индексирани променливи. С всяка променлива от тип масив е свързан набор от индексирани променливи. Фиг. 2. илюстрира техния синтаксис.


<индексирана_променлива> ::=
<променлива_от_тип_масив>[<индекс>]
където
<индекс> e израз от интегрален или изброен тип.
Всяка индексирана променлива е от базовия тип.


Фиг. 2.

Примери:
1. С променливата a от примера по-горе са свързани индексираните променливи a[0], a[1], a[2], a[3] и a[4], които са от тип int.
2. С променливата b са свързани индексираните променливи b[0], b[1],…, b[9], които са от тип double.
3. С променливата x са свързани индексираните променливи x[0], x[1],…, x[19], които са от тип enum {FALSE, TRUE}.

Дефиницията на променлива от тип масив не само свързва променливата с множеството от стойности на указания тип, но и отделя определено количество памет (обикновено 4B), в която записва адреса в паметта на първата индексирана променлива на масива. Останалите индексирани променливи се разполагат последователно след първата. За всяка индексирана променлива се отделя по толкова памет, колкото базовият тип изисква.
Пример:
ОП
а a[0] a[1] … a[4] b b[0] b[1] … b[9] …
адрес - - - адрес - - -
на a[0] на b[0]
4B 4B 4B … 4B 4B 8B 8B … 8B

За краткост, вместо “адрес на а[0]” ще записваме стрелка от а към a[0]. Съдържанието на отделената за индексираните променливи памет е неопределено освен ако не е зададена дефиниция с инициализация. Тогава в клетките се записват инициализиращите стойности.
Пример: Разпределението на паметта за променливите p и q, дефинирани в примерите по-горе, е следното:

ОП
p p[0] p[1] p[2] p[3]
1.25 2.5 9.25 4.12

q q[0] q[1] q[2] q[3] q[4]
1 2 3 0 0

Операции и вградени функции

Не са възможни операции над масиви като цяло, но всички операции и вградени функции, които базовият тип допуска, са възможни за индексираните променливи, свързани с масива.
Пример: Недопустими са:
int a[5], b[5];
cin >> a >> b;
a = b;
а също a == b или a != b.
Операторът
cout << a;
извежда адреса на a[0].

Задачи върху тип масив

Задача 48. Да се напише програма, която въвежда последователно n числа, след което ги извежда в обратен ред.

Програма Zad48.cpp решава задачата.
Program Zad48.cpp
#include <iostream.h>
int main()
{double x[100];
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 0 || n > 100)
{cout << "Incorrect input! \n";
return 1;
}
for (int i = 0; i <= n-1; i++)
{cout << "x[" << i << "]= ";
cin >> x[i];
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
}
for (i = n-1; i >= 0; i--)
cout << x[i] << "\n";
return 0;
}

Изпълнение на програма Zad48.cpp

Дефиницията double x[100]; води до отделяне на 800B ОП, които се именуват последователно с x[0], x[1], …, x[99] и са с неопределено съдържание. Освен това се отделят 4B ОП за променливата x, в които записва адресът на индексираната променлива x[0]. Следващият програмен фрагмент въвежда стойност на n (броя на елементите на масива, които ще бъдат използвани). Операторът
for (int i = 0; i <= n-1; i++)
{cout << "x[" << i << "]= ";
cin >> x[i];
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
}
въвежда стойности на целите променливи x[0], x[1], …, x[n-1]. Всяка въведена стойност е предшествана от подсещане. Операторът
for (i = n-1; i >= 0; i--)
cout << x[i] << "\n";
извежда в обратен ред компонентите на масива x.

Забележка: Фрагментите:
… и …
cout << "n= "; int n = 10;
int n; int x[10];
cin >> n;
int x[n];

са недопустими, тъй като n не е константен израз. Фрагментът

const int n = 10;
double x[n];
е допустим.

ivakavlad
10-20-2010, 23:03
Някои приложения на структурата от данни масив

Търсене на елемент в редица

Нека са дадени редица от елементи a0, a1, …, an-1, елемент x и релация r. Могат да се формулират две основни зaдачи, свързани с търсене на елемент в редицата, който да е в релация r с елемента x.
a) Да се намерят всички елементи на редицата, които са в релация r с елемента x.
б) Да се установи, съществува ли елемент от редицата, който е в релация r с елемента x.
Съществуват редица методи, които решават едната, другата или и двете задачи. Ще разгледаме метода на последователното търсене, чрез който магат да се решат и двете задачи. Методът се състои в следното: последователно се обхождат елементите на редицата и за всеки елемент се проверява дали е в релация r с елемента x. При първата задача процесът продължава до изчерпване на редицата, а при втората – до намиране на първия елемент ak (k = 0, 1, …, n-1), който е в релация r с x, или до изчерпване на редицата без да е намерен елемент с търсеното свойство.
Следващите четири задачи илюстрират този метод.

Задача 49. Дадени са редицата от цели числа a0, a1, …, an-1 (n ≥ 1) и цялото число x. Да се напише програма, която намира колко пъти x се съдържа в редицата.

В случая релацията r е операцията ==. Налага се всеки елемент на редицата да бъде сравнен с x, т.е. имаме задача от първия вид. Тя описва индуктивен цикличен процес.
Програма Zad49.cpp решава задачата.

Program Zad49.cpp
#include <iostream.h>
int main()
{int a[20];
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1 || n > 20)
{cout << "Incorrect input! \n";
return 1;
}
int i;
for (i = 0; i <= n-1; i++)
{cout << "a[" << i << "]= ";
cin >> a[i];
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
}
int x;
cout << "x= ";
cin >> x;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
int br = 0;
for (i = 0; i <= n-1; i++)
if (a[i] == x) br++;
cout << "number = " << br << "\n";
return 0;
}

Задача 50. Дадени са редицата от цели числа a0, a1, …, an-1 (n ≥ 1) и цялото число x. Да се напише програма, която проверява дали x се съдържа в редицата.

В този случай се изисква при първото срещане на елемент от редицата, който е равен на x, да се преустанови работата с подходящо съобщение. Броят на сравненията на x с елементите от редицата е ограничен отгоре от n, но не е известен.
Програма Zad50.cpp решава задачата. Фрагментът, реализиращ входа е същия като в Zad49.cpp и затова е пропуснат.

Program Zad50.cpp
#include <iostream.h>
int main()
{int a[20];

i = 0;
while (a[i] != x && i < n-1)
i++;
if (a[i] == x) cout << "yes \n";
else cout << "no \n";
return 0;
}

Обхождането на редицата става чрез промяна на стойностите на индекса i - започват от 0 и на всяка стъпка от изпълнението на тялото на цикъла се увеличават с 1. Максималната им стойност е n-1. При излизането от цикъла ще е в сила отрицанието на условието (a[i] != x && i < n-1), т.е. (a[i] == x || i == n-1). Ако е в сила a[i] == x, тъй като сме осигурили a[i] да е елемент на редицата, отговорът “yes” е коректен. В противен случай е в сила i == n-1, т.е. сканиран е и последният елемент на редицата и за него не е вярно a[i] == x. Това е реализирано чрез отговора “no” от алтернативата на условния оператор.
Фрагментът
i = -1;
do
i++;
while (a[i] != x && i < n-1);
if (a[i] == x) cout << "yes \n";
else cout << "no \n";
реализира търсенето чрез използване на оператора do/while.

Задача 51. Да се напише програма, която установява, дали редицата от цели числа a0, a1, ..., an-1 е монотонно намаляваща.

a) За решаването на задачата е необходимо да се установи, дали за всяко i (0 ≤ i ≤ n-2) е в сила релацията a[i] >= a[i+1]. Това може да се реализира като се провери дали броят на целите числа i (0≤i≤n-2), за които е в сила релацията a[i] ≥ a[i+1], е равен на n-1.
Програмата Zad51_1.cpp реализира този начин за проверка дали редица е монотонно намаляваща. Фрагментите, реализиращи въвеждането на n и масива a, са известни вече и затова са пропуснати.

Program Zad51_1.cpp
#include <iostream.h>
int main()
{int a[100];
// дефиниране и въвеждане на стойност на n

// въвеждане на масива а

int br = 0;
for (i = 0; i <= n-2; i++)
if (a[i] >= a[i+1]) br++;
if (br == n-1) cout << "yes \n";
else cout << "no \n";
return 0;
}

б) Задачата може да се сведе до търсене на i (i = 0, 1,..., n-2), така че ai < ai+1, т.е. до задача за съществуване.
Програма Zad51_2.cpp реализира този начин за проверка дали редица е монотонно намаляваща. Фрагментите, реализиращи въвеждането на n и масива a отново са пропуснати.

Program Zad51_2.cpp;
#include <iostream.h>
int main()
{int a[100];
//въвеждане на размерността n и масива a

i = 0;
while (a[i] >= a[i+1] && i < n-2) i++;
if (a[i] >= a[i+1]) cout << "yes \n";
else cout << "no \n";
return 0;
}
Решение б) е по-ефективно, тъй като при първото срещане на a[i], така че релацията a[i] < a[i+1] е в сила, изпълнението на цикъла завършва. Решение а) реализира последователно търсене е пълно изчерпване, а решение б) – задача за съществуване на елемент в редица, който е в определена релация с друг елемент (в случая съседния му).

Задача 52. Да се напише програма, която установява, дали редицата от цели числа a0, a1, ..., an-1 се състои от различни елементи.

a) За решаването на задачата е необходимо да се установи, дали за всяка двойка (i, j): 0 ≤ i ≤ n-2 и i+1 ≤ j ≤ n-1 е в сила релацията a[i] != a[j]. Това може да се постигне като се провери дали броят на двойките (i, j): 0 ≤ i ≤ n-2 и i+1 ≤ j ≤ n-1, за които е в сила релацията a[i] != a[j], е равен на n*(n-1)/2.
Програма Zad52_1.cpp реализира горната формулировка на задачата – търсене с пълно изчерпване. Фрагментите, реализиращи въвеждането на n и масива a отново са пропуснати.

Program Zad52_1.cpp
#include <iostream.h>
int main()
{int a[100];
//въвеждане на размерността n и масива a

int br = 0;
for (i = 0; i <= n-2; i++)
for (int j = i+1; j <= n-1; j++)
if (a[i] != a[j]) br++;
if (br == n*(n-1)/2) cout << "yes \n";
else cout << "no \n";
return 0;
}
б) Задачата може да се сведе до проверка за съществуване на двойка индекси (i, j): 0 ≤ i ≤ n-2 и i+1 ≤ j ≤ n-1, за които не е в сила релацията a[i] != a[j]. Програма Zad52_2.cpp решава задачата.

Program Zad52_2.cpp
#include <iostream.h>
int main()
{int a[100];
// въвеждане стойности на размерността n и масива a

i = -1;
int j;
do
{i++;
j = i+1;
while (a[i] != a[j] && j < n-1) j++;
} while (a[i] != a[j] && i < n-2);
if (a[i] != a[i+1]) cout << "yes \n";
else cout << "no \n";
return 0;
}
Решение б) е по-ефективно, тъй като при първото срещане на a[i] и a[j], така че релацията a[i] == a[j] е в сила, изпълнението на операторите за цикъл завършва. То реализира задача за съществуване на метода за търсене.

Сортиране на редица

Съществуват много методи за сортиране на редици от елементи. В тази глава ще разгледаме метода на пряката селекция и чрез него ще реализираме възходяща сортировка на редица.

Метод на пряката селекция

Разглежда се редицата a0, a1, …, an-1 и се извършват следните действия:
Намира се k, така че ak = min{a0, a1, …, an-1}.
Разменят се стойностите на ak и a0.
Така на първо място в редицата се установява най-малкият й елемент.
Разглежда се редицата a1, a2, …, an-1 и се извършват действията:
Намира се k, така че ak = min{a1, a2, …, an-1}.
Разменят се стойностите на ak и a1.
Така на второ място в редицата се установява следващият по големина елемент на редицата и т.н.
Разглежда се редицата an-2, an-1 и се извършват действията:
Намира се k, така че ak = min{an-2, an-1}.
Разменят се стойностите на ak и an-2.
Получената редица е сортирана във възходящ ред.

Задача 53. Да се сортира във възходящ ред по метода на пряката селекция числовата редица a0, a1, …, an-1 (n ≥ 1).

Програма Zad53.cpp решава задачата. Фрагментът за въвеждане стойности на размерността n и масива a отново е пропуснат.

Program Zad53.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{int a[100];
...
int i;
for (i = 0; i <= n-2; i++)
{int min = a[i];
int k = i;
for (int j = i+1; j <= n-1; j++)
if (a[j] < min)
{min = a[j];
k = j;
}
int x = a[i]; a[i] = a[k]; a[k] = x;
}
for (i = 0; i <= n-1; i++)
cout << setw(10) << a[i];
cout << '\n';
return 0;
}

Сливане на редици

Сливането е вид сортиране. Нека са дадени сортираните във възходящ ред редици:
a0, a1, …, an-1
b0, b1, …, bn-1
Да се слеят редиците означава да се конструира нова, сортирана във възходящ ред редица, съставена от елементите на дадените редици. Осъществява се по следния начин:
- Поставят се “указатели” към първите елементи на редиците {ai} и {bj}.
- Докато има елементи и в двете редици, се сравняват елементите, сочени от “указателите”. По-малкият елемент се записва в новата редица, след което се прескача.
- След изчерпване на елементите на едната от дадените редици, елементите на другата от “указателя” (включително) се прехвърлят в новата редица.

Задача 54. Дадени са сортираните във възходящ ред редици:
a0, a1, …, an-1
b0, b1, …, bm-1
(n ≥ 1, m ≥ 1). Да се напише програма, която слива двете редици в редицата
c0, c1, …, ck-1.

Програма Zad54.cpp решава задачата.
Program Zad54.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{int a[20];
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1 || n > 20)
{cout << "Incorrect input! \n";
return 1;
}
int i;
for (i = 0; i <= n-1; i++)
{cout << "a[" << i << "]= ";
cin >> a[i];
}
int b[10];
cout << "m= ";
int m;
cin >> m;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (m < 1 || m > 10)
{cout << "Incorrect input! \n";
return 1;
}
for (i = 0; i <= m-1; i++)
{cout << "b[" << i << "]= ";
cin >> b[i];
}
int p1 = 0, p2 = 0;
int c[30];
int p3 = -1;
while (p1 <= n-1 && p2 <= m-1)
if (a[p1] <= b[p2])
{p3++;
c[p3] = a[p1];
p1++;
}
else
{p3++;
c[p3] = b[p2];
p2++;
}
if (p1 > n-1)
for (i = p2; i <= m-1; i++)
{p3++;
c[p3] = b[i];
}
else
for (i = p1; i <= n-1; i++)
{p3++;
c[p3] = a[i];
} // извеждане на редицата
for (i=0; i<=p3; i++)
cout << setw(10) << c[i];
cout << '\n';
return 0;
}

Разглежданите досега масиви се наричат едномерни. Те реализират крайни редици от елементи от скаларен тип. Възможно е обаче типът на елементите да е масив. В този случай се говори за многомерни масиви.

ivakavlad
10-20-2010, 23:05
Многомерни масиви

Масив, базовият тип на който е едномерен масив, се нарича двумерен. Масив, базовият тип на който е двумерен масив, се нарича тримерен и т.н. На практика се използват масиви с размерност най-много 3.

Задаване на многомерни масиви

Нека Т e име или дефиниция на произволен тип, различен от псевдоним, void и функционален, size1, size2, …, sizen (n>1 е дадено цяло число) са константни изрази от интегрален или изброен тип с положителни стойности. T[size1][size2] … [sizen] е тип n-мерен масив от тип T. T се нарича базов тип за типа масив.
Примери:
int [5][3] е двумерен масив от тип int;
double [4][5][3] е тримерен масив от тип double;

Множество от стойности

Множеството от стойности на типа T[size1][size2] … [sizen] се състои от всички редици от по size1 елемента, които са произволни константи от тип T[size2] … [sizen]. Достъпът до елементите на редиците е пряк и се осъществява с помощта на индекс, като достъпът до първия елемент се осъществява с индекс със стойност 0, до последния – с индекс със стойност size1-1, а до всеки от останалите елементи – с индекс със стойност с 1 по-голяма от тази на индекса на предишния елемент. Елементите от множеството от стойности на даден тип многомерен масив са константите на този тип масив.
Примери:
1. Множеството от стойности на типа int[5][3] се състои от всички редици от по 5 елемента, които са едномерни масиви от тип int[3]. Достъпът до елементите на редиците се осъществява с индекс със стойности 0, 1, 2, 3 и 4.

0 1 2 0 1 2 0 1 2

int[5][3]




0 1 4

2. Множеството от стойности на типа double[4][5][3] се състои от всички редици от по 4 константи от тип double[5][3]. Достъпът до елементите на редиците се осъществява с индекс със стойности 0, 1, 2 и 3.

double[4][5][3]




0 1 2 3
където с констi (i = 0, 1, 2, 3) е означена произволна константа от тип double[5][3].
Променлива величина, множеството от допустимите стойности на която съвпада с множеството от стойности на даден тип масив, се нарича променлива от дадения тип масив или само масив. Фиг. 3 дава обобщение на синтаксиса на дефиницията на променлива от тип масив.


<дефиниция_на_променлива_от_ тип_многомерен_масив> ::=
T <променлива>[size1][size2] … [sizen]; |
T <променлива>[size1][size2]…[sizen]
= {<редица_от_константи_от_тип T1>};|
T <променлива>[size1][size2]…[sizen]
= {<редица_от_константи_от_тип T>};

където
Т e име или дефиниция на произволен тип, различен от псевдоним, void и функционален;
Т1 е име на типа T[size2]…[sizen];
size1, size2, …sizen са константни изрази от интегрален или изброен тип със положителни стойности;
<променлива> ::= <идентификатор>
<редица_от_константи_от_тип T1> ::= <константa_от_тип Т1>|
<константа_от_тип Т1>, <редица_от_константи_от_тип Т1>
а <редица_от_константи_от_тип Т> се определя по аналогичен начин.


Фиг. 3.

Примери:
int x[10][20];
double y[20][10][5];
int z[3][2] = {{1, 3},
{5, 7},
{2, 9}};
int t[2][3][2] = {{{1, 3}, {5, 7}, {6, 9}},
{{7, 8}, {1, 8}, {-1, -4}};

Фрагментите
<променлива>[size1][size2] … [sizen],
<променлива>[size1][size2]…[sizen] ={<редица_от_константи_от_тип T1>}
<променлива>[size1][size2]…[sizen] ={<редица_от_константи_от_тип T>};
от дефиницията от Фиг. 3, могат да се повтарят. За разделител се използва символът запетая.
Примери:
int a[3][4], b[2][3][2] = {{{1, 2}, {3, 4}, {5, 6}},
{{7, 8}, {9, 0}, {1, 2}}};
double c[2][3] = {1, 2, 3, 4, 5, 6}, d[3][4][5][6];

При дефиницията с инициализация, от Фиг. 3., е възможно size1 да се пропусне. Тогава за стойност на size1 се подразбира броят на редиците от константи на най-външно ниво, изброени при инициализацията.
Пример: Дефиницията
int s[][2][3] = {{{1,2,3}, {4, 5, 6}},
{{7, 8, 9}, {10, 11, 12}},
{{13, 14, 15}, {16, 17, 18}}};
е еквивалентна на
int s[3][2][3] = {{{1,2,3}, {4, 5, 6}},
{{7, 8, 9}, {10, 11, 12}},
{{13, 14, 15}, {16, 17, 18}}};
Ако изброените константни изрази в инициализацията на ниво i са по-малко от sizei, останалите се инициализират с нулеви стойности.
Примери: Дефиницията
int fi[5][6] = {{1, 2}, {5}, {3, 4, 5},
{2, 3, 4, 5}, {2, 0, 4}};
е еквивалентна на
int fi[5][6] = { {1, 2, 0, 0, 0, 0},
{5, 0, 0, 0, 0, 0},
{3, 4, 5, 0, 0, 0},
{2, 3, 4, 5, 0, 0},
{2, 0, 4, 0, 0, 0}};
Вложените фигурни скобки не са задължителни. Следното инициализиране
int ma[4][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
е еквивалентно на
int ma[4][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {9, 10, 11}};
но е по-неясно.
Следващата дефиниция
int ma[4][3] = {{0}, {1}, {2}, {3}};
е еквивалентна на
int ma[4][3] = {{0, 0, 0 }, {1, 0, 0}, {2, 0, 0}, {3, 0, 0}};
и е различна от
int ma[4][3] = {0, 1, 2, 3};
която пък е еквивалентна на
int ma[4][3] = {{0, 1, 2}, {3, 0, 0}, {0, 0, 0}, {0, 0, 0}};

Инициализацията е един начин за свързване на променлива от тип масив с конкретна константа от множеството от стойности на този тип масив. Друг начин предоставят т.нар. индексирани променливи. С всяка променлива от тип масив е свързан набор от индексирани променливи. Фиг. 4. обобщава техния синтаксис.


<индексирана_променлива> ::=
<променлива_от_тип_масив>[<индекс1>][<индекс2>][<индексn>]
където
<индексi> e израз от интегрален или изброен тип.
Всяка индексирана променлива е от базовия тип.


Фиг. 4.

Примери:
1. С променливата x, дефинирана по-горе, са свързани индексираните променливи
x[0][0], x[0][1], …, x[0][19],
x[1][0], x[1][1], …, x[1][19],

x[9][0], x[9][1], …, x[9][19],
които са от тип int.
2. С променливата y са свързани следните реални индексирани променливи:
y[i][0][0], y[i][0][1], …, y[i][0][4],
y[i][1][0], y[i][1][1], …, y[i][1][4],

y[i][9][0], y[i][9][1], …, y[i][9][4],
за i = 0, 1, …, 19.
Дефиницията на променлива от тип многомерен масив не само свързва променливата с множеството от стойности на указания тип, но и отделя 4B памет, в която записва адреса на първата индексирана променлива на масива. Останалите индексирани променливи се разполагат последователно след първата по по-бързото нарастване на по-далечните си индекси. За всяка индексирана променлива се отделя толкова памет, колкото базовият тип изисква. Следващият пример илюстрира по-подробно представянето.
Пример:
ОП
x


x[0] x[0][0] x[0][1] … x[0][19]
- - -

x[1] x[1][0] x[1][1] … x[1][19] …
- - -

x[9] x[9][0] x[9][1] … x[9][19]
- - -
като за всяка индексирана променлива са отделени 4B ОП, които са с неопределено съдържание, тъй като x не е дефинирана с инициализация. Освен това, чрез индексираните променливи x[0], x[1], ..., x[9] могат да се намерят адресите на x[0][0], x[1][0], ..., x[9][0] съответно, т.е.
cout << x[0] << x[1] << ... << x[9];
ще изведе адресите на x[0][0], x[1][0], ... и x[9][0].

Забележка: Двумерните масиви разполагат в ОП индексираните си променливи по по-бързото нарастване на втория индекс. Това физическо представяне се нарича представяне по редове. Тези масиви могат да бъдат използвани за реализация и работа с матрици и др. правоъгълни таблици.
Важно допълнение: При работа с масиви трябва да се има предвид, че повечето реализации не проверяват дали стойностите на индексите са в рамките на границите, зададени при техните дефиниции. Тази особеност крие опасност от допускане на труднооткриваеми грешки.

Често допускана грешка: В Паскал, Ада и др. процедурни езици, индексите на индексираните променливи се ограждат само в една двойка квадратни скобки и се отделят със запетаи. По навик, при програмиране на C++, често се използва същото означение. Това е неправилно, но за съжаление не винаги е съпроводено със съобщение за грешка, тъй като в езика C++ съществуват т.нар. comma-изрази. Използвахме ги вече в заглавните части на оператора за цикъл for. Comma-изразите са изрази, отделени със запетаи. Стойността на най-десния израз е стойността на comma-израза. Операцията за последователно изпълнение запетая е лявоасоциативна. Така 1+3, 8, 21-15 е comma-израз със стойност 6, а [1, 2] е comma-израз със стойност [2]. В C++ ma[1,2] означава адреса на индексираната променлива ma[2][0] (индексът [0] се добавя автоматично).

Задачи върху многомерни масиви

Задача 55. Да се напише програма, която въвежда елементите на правоъгълна матрица a[nxм] и намира и извежда матрицата, получена от дадената като всеки от нейните елементи е увеличен с 1.

Програма Zad55.cpp решава задачата.
Program Zad55.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{int a[10][20];
// въвеждане на броя на редовете на матрицата
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1 || n > 10)
{cout << "Incorrect input! \n";
return 1;
}
// въвеждане на броя на стълбовете на матрицата
cout << "m= ";
int m;
cin >> m;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (m < 1 || m > 20)
{cout << "Incorrect input! \n";
return 1;
}
// въвеждане на матрицата по редове
int i, j;
for (i = 0; i <= n-1; i++)
for (j = 0; j <= m-1; j++)
{cout << "a[" << i << ", " << j << "]= ";
cin >> a[i][j];
if (!cin)
{cout << "Error, Bad Input! \n";
return 1;
}
}
// конструиране на нова матрица b
int b[10][20];
for (i = 0; i <= n-1; i++)
for (j = 0; j <= m-1; j++)
b[i][j] = a[i][j] + 1;
// извеждане на матрицата b по редове
for (i = 0; i <= n-1; i++)
{for (j = 0; j <= m-1; j++)
cout << setw(6) << b[i][j];
cout << '\n';
}
return 0;
}

Забележка: За реализиране на операциите извеждане и конструиране се извърши обхождане на елементите на двумерен масив по редове.

Задача 56. Да се напише програма, която намира и извежда сумата от елементите на всеки стълб на квадратната матрица a[nxn].

Програма Zad56.cpp решава задачата. В нея въвеждането на матрицата и нейната размерност са пропуснати, тъй като са аналогични на тези от Zad55.cpp.
Program Zad56.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{int a[10][10];
int n;

int i, j;
for (j = 0; j <= n-1; j++)
{int s = 0;
for (i = 0; i <= n-1; i++)
s += a[i][j]; // s = s + a[I][j]
cout << setw(10) << j << setw(10) << s << "\n";
}
return 0;
}
Реализирано е обхождане на масива по стълбове (първият индекс се изменя по-бързо).

Задача 57. Да се напише програмен фрагмент, който намира номерата на редовете на целочислената квадратна матрица a[nxn], в които има елемент, равен на цялото число x.

for (i = 0; i <= n-1; i++)
{j = -1;
do
j++;
while (a[i][j] != x && j < n-1);
if (a[i][j] == x) cout << setw(5) << i << '\n';
}

Фрагментът реализира последователно обхождане на всички редове на матрицата и за всеки ред проверява дали съществува елемент, равен на дадения елемент x.

Задача 58. Да се напише програмен фрагмент, който обхожда кватратната матрица a[nxn] по диагонали, започвайки от елемента a00, както е показано по-долу:
a00 a01 a02 … a0,n-1
a10 a11 a12 … a1,n-1

an-1,0 an-1,1 an-1,2 … an-1,n-1


int k;
for (k = 0; k <= n-1; k++)
{for (i = k; i >= 0; i--)
cout << "(" << i << ", "<< k-i << ") ";
cout << '\n';
}
for (k = n; k <= 2*n-2; k++)
{for (i = n-1; i >= k-n+1; i--)
cout << "(" << i << ", "<< k-i << ") ";
cout << '\n';
}


Задача 59. Да се напише програмен фрагмент, който обхожда кватратната матрица a[nxn] по диагонали, започвайки от елемента an-1,0, както е показано по-долу:

a00 a01 a02 … a0,n-1
a10 a11 a12 … a1,n-1

an-2,0 an-2,1 an-2,2 … an-2,n-1
an-1,0 an-1,1 an-1,2 … an-1,n-1


int k;
for (k = n-1; k >= 0; k--)
{for (i = k; i <= n-1; i++)
cout << "(" << i << ", "<< i-k << ") ";
cout << '\n';
}

for (k = -1; k >= 1-n; k--)
{for (i = 0; i <= n+k-1; i++)
cout << "(" << i << ", "<< i-k << ") ";
cout << '\n';
}


Задача 60. Да се напише програма, която:
а) въвежда по редове елементите на квадратната реална матрица A с размерност n x n;
б) от матрицата A конструира редицата B: b0, b2,..., bm-1, където m = n.n, при което първите n елемента на B съвпадат с елементите на първия стълб на A, вторите n елемента на B съвпадат с елементите на втория стълб на A и т.н., последните n елемента на B съвпадат с елементите на последния стълб на A;
в) сортира във възходящ ред елементите на редицата B;
г) образува нова квадратна матрица A с размерност n x n, като елементите от първия ред на A съвпадат с първите n елемента на B, елементите от втория ред на A съвпадат с вторите n елемента на B и т.н. елементите от n - тия ред на A съвпадат с последните n елемента на B;
д) извежда по редове новата матрица A.

Програма Zad60.cpp решава задачата.

Program Zad60.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{int a[10][10];
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1 || n > 10)
{cout << "Incorrect input! \n";
return 1;
}
// въвеждане на масива a
int i, j;
for (i = 0; i <= n-1; i++)
for (j = 0; j <= n-1; j++)
{cout << "a[" << i<< "][" << j << "]= ";
cin >> a[i][j];
}
// извеждане на елементите на a по редове
for (i = 0; i <= n-1; i++)
{for (j = 0; j <= n-1; j++)
cout << setw(5) << a[i][j];
cout << "\n";
}
// развиване на матрицата a по стълбове
int b[100];
int m = -1;
for (j = 0; j <= n-1; j++)
for (i = 0; i <= n-1; i++)
{m++;
b[m] = a[i][j];
}
m++; // m е броя на елементите на редицата b
// извеждане на редицата b
for (i = 0; i <= m-1; i++)
cout << setw(5) << b[i];
cout << '\n';
// сортиране на b по метода на пряката селекция
for (i = 0; i <= m-2; i++)
{int k = i;
int min = b[i];
for (j = i+1; j <= m-1; j++)
if (b[j] < min)
{min = b[j];
k = j;
}
int x = b[i]; b[i] = b[k]; b[k] = x;
}
// извеждане на сортираната b
for (i = 0; i <= m-1; i++)
cout << setw(5) << b[i];
cout << '\n';
// конструиране на новата матрица a
m = -1;
for (i = 0; i <= n-1; i++)
for (j = 0; j <= n-1; j++)
{m++;
a[i][j] = b[m];
}
// извеждане на матрицата a
for (i = 0; i <= n-1; i++)
{for (j = 0; j <= n-1; j++)
cout << setw(10) << a[i][j];
cout << '\n';
}
return 0;
}

ivakavlad
10-20-2010, 23:07
Символни низове

Структура от данни низ

Логическо описание
Редица от краен брой символи, заградени в кавички, се нарича символен низ или само низ.
Броят на символите в редицата се нарича дължина на низа.
Примери: “xyz” е символен низ с дължина 3,
“This is a string.” е символен низ с дължина 17, а
“” е символен низ с дължина 0. Нарича се празен низ.
Низ, който се съдържа в даден низ се нарича негов подниз.
Пример: Низът “ is a s “ е подниз на низа “This is a string.”, а низът “ is a sing” не е негов подниз.
Конкатенация на два низа е низ, получен като в края на първия низ се запише вторият. Нарича се още слепване на низове.
Пример: Конкатенацията на низовете “a+b” и “=b+a” е низът “a+b=b+a”, а конкатенацията на “=b+a” с “a+b” е низът “=b+aa+b”. Забелязваме, че редът на аргументите е от значение.
Два символни низа се сравняват по следния начин: Сравнява се всеки символ от първия низ със символа от съответната позиция на втория низ. Сравнението продължава до намиране на два различни символа или до края на поне един от символните низове. Ако кодът на символ от първия низ е по-малък от кода на съответния символ от втория низ, или първият низ е изчерпен, приема се, че първият низ е по-малък от втория. Ако пък е по-голям или вторият низ е изчерпен – приема се, че първият низ е по-голям от втория. Ако в процеса на сравнение и двата низа едновременно са изчерпени, те са развни. Това сравнение се нарича лексикографско.
Примери: “abbc” е равен на “abbc”
“abbc” е по-малък от “abbcaaa”
“abbc” е по-голям от “aa”
“abbcc” е по-голям от “abbc”.

Физическо представяне
В ОП низовете се представят последователно.
Символни низове в езика C++

Съществуват два начина за разглеждане на низовете в езика C++:
като масиви от символи и
като указатели към тип char.
За щастие, те са семантично еквивалентни.
В тази част ще разгледаме символните низове като масиви от символи.
Дефиницията
char str1[100];
определя променливата str1 за масив от 100 символа, а
char str2[5] = {‘a’, ‘b’, ‘c’};
дефинира масива от символи str2 и го инициализира. Тъй като при инициализацията са указани по-малко от 5 символа, останалите се допълват с нулевия символ, който се означава със символа \0, а понякога и само с 0. Така последната дефиниция е еквивалентна на дефиниците:
char str2[5] = ‘a’, ‘b’, ‘c’, ‘\0’, ‘\0’};
char str2[5] = ‘a’, ‘b’, ‘c’, 0, 0};
Всички действия, които описахме, за работа с едномерни масиви, са валидни и за масиви от символи с изключение на извеждането. Операторът
cout << str2;
няма да изведе адреса на str2 (както беше при масивите от друг тип), а текста
abc
Има обаче една особеност. Ако инициализацията на променливата str2 е пълна и не завършва със символа \0, т.е. има вида
char str2[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
операторът
cout << str2;
извежда текста
abcde<неопределено>

Имайки пред вид всичко това, бихме могли да напишем подходящи програмни фрагменти, които въвеждат, извеждат, копират, сравняват, извличат части, конкатенират низове. Тъй като операциите се извършват над индексираните променливи, налага се да се поддържа целочислена променлива, съдържаща дължината на низа.
В езика са дадени средства, реализиращи низа като скаларна структура. За целта низът се разглежда като редица от символи, завършваща с нулевия символ \0, наречен още знак за край на низ. Тази организация има предимството, че не е необходимо с всеки низ да се пази в променлива дължината му, тъй като знакът за край на низ позволява да се определи краят му.
Примери: Дефинициите
char m[5] = {‘a, ‘b’, ‘b’, ‘a’, ‘\0’};
char n[10] = {‘x’, ‘y’, ‘z’, ‘1’, ‘2’, ‘+’, ‘\0’};
свързват променливте от тип масив от символи m и n с низовете “abba” и “xyz12+” съответно. Знакът за край на низ ‘\0’ не се включва явно в низа.
Този начин за инициализация не е много удобен. Следните дефиниции са еквивалентни на горните.
char m[5] = “abba”;
char n[10] = “xyz12+”;
Забелязваме, че ако низ съдържащ n символа трябва да се свърже с масив от символи, минималната дължина на масива трябва да бъде n+1, за да се поберат n-те символа плюс символът \0.

Задаване на низове

Типът char[size], където size е константен израз от интегрален или изброен тип, може да бъде използван за задаване на тип низ с максимална дължина size-1.
Пример:
char[5] може да се използва за задаване на тип низ с максимална дължина 4.

Множество от стойности

Множеството от стойности на типа низ, зададен чрез char[size], се състои от всички низове с дължина 0, 1, 2, ..., size-1
Примери:
Множеството от стойности на типа низ, зададен чрез char[5] се състои от всички низове с дължина 0, 1, 2, 3 и 4.

множество от стойности
на тип низ, зададен чрез char[5]





2. Множеството от стойности на типа char[10] се състои от всички низове с дължина 0, 1, 2, ..., 9.
множество от стойности
на тип низ, зададен чрез char[10]






Елементите от множеството от стойности на даден тип низ са неговите константи. Например, “a+b=c-a*e” е константа от тип char[10].
Променлива величина, множеството от допустимите стойности на която съвпада с множеството от стойности на даден тип низ, се нарича променлива от този тип низ. Понякога ще я наричаме само низ.
Фиг. 5 определя дефиницията на променлива от тип низ.


<дефиниция_на_променлива_от_ тип_низ> ::=
char <променлива>[size]; |
char <променлива>[size] = “<редица_от_символи>”;
char <променлива>[size] = {<редица_от_константни_израз >};|
където
<променлива> ::= <идентификатор>
size е константен израз от интегрален или изброен тип със положителна стойност;
<редица_от_константни_израз > ::= <константен_израз>|
<константен_израз>, <редица_от_константни_израз >
като константните изрази са от тип char.
<редица_от_символи> ::= <празно>| <символ> |
<символ><редица_от_символи>
с максимална дължина size-1.


Фиг. 5.

Примери:
char s1[5];
char s2[10] = “x+y”;
char s3[8] = {‘1’, ‘2’, ‘3’, ‘\0’};

Ако редицата от константни изрази съдържа по-малко от size израза, може да не завършва със знака за край на низ. Системата автоматично го добавя. А ако съдържа точно size константни израза, задължително трябва да завършва със знака за край на низ \0, или само 0.
При дефиниция на низ с инициализация е възможно size да се пропусне. Тогава инициализацията трябва да съдържа символа ‘\0’ и за стойност на size се подразбира броят на константните изрази, изброени при инициализацията, включително ‘\0’. Ако size е указано и изброените константни изрази в инициализацията са по-малко от size, останалите се инициализират с ‘\0’.

Примери:
Дефиницията
char q[5] = {‘a’, ‘b’};
е еквивалентна на
char q[5] = {‘a’, ‘b’, ‘\0’, ‘\0’, ‘\0’};
и на
char q[5] = “ab”;
а
char r[] = {‘a’, ‘b’, ‘\0’}; или
char r[] = “ab”;
са еквивалентни на
char r[3] = {‘a’, ‘b’, ‘\0’}; или
char r[3] = “ab”;
Забележка: Не са възможни конструкции от вида:
char q[5];
q = {‘a’, ‘v’, ‘s’}; или
char r[5];
r = “avs”;
т.е. на променлива от тип низ не може да бъде присвоявана константа от тип низ.
Недопустими са също дефиниции от вида:
char q[4] = {‘a’, ‘s’, ‘d’, ‘f’, ‘g’, ‘h’}; или
char q[];

Инициализацията е един начин за свързване на променлива от тип низ с конкретна константа от множеството от стойности на този тип низ. Друг начин предоставят индексираните променливи.
Примери:
q[0] = ‘a’; q[1] = ‘s’; q[2] = ‘d’;
Дефиницията на променлива от тип низ не само свързва променливата с множеството от стойности на указания тип, но и отделя определено количество памет (обикновено 4B), в която записва адреса на първата индексирана променлива, свързана с променливата от тип низ. Останалите индексирани променливи се разполагат последователно след първата. За всяка индексирана променлива се отделя по 1B ОП. Съдържанието на отделената за индексираните променливи памет е неопределено освен ако не е зададена дефиниция с инициализация. Тогава в клетките се записват инициализиращите стойности, допълнени със знака за край на низ.
Пример: След дефиницията
char s[4];
char w[10] = “abba”;
разпределението на паметта има вида:
ОП
s s[0] s[1] s[2] s[3]
- - - -

w w[0] w[1] w[2] w[3] w[4] w[5] ... w[9] …
97 98 98 97 0 0 0


Операции и вградени функции

Въвеждане на стойност

Реализира се по стандартния начин - чрез оператора cin.
Пример:
char s[5], t[3];
cin >> s >> t;
Настъпва пауза в очакване да се въведат два низа с дължина не по-голяма от 4 в първия и не по-голяма от 2 във втория случай. Водещите интервали, табулации и преминаване на нов ред се пренебрегват. За разделител на низовете се използват интервалът, табулациите и знака за преминаване на нов ред. Знакът за край на низ автоматично се добавя в края на всяка от въведените знакови комбинации. При въвеждане на низовете не се извършва проверка за достигане на указаната горна граница. Това може да доведе до труднооткриваеми грешки.

Извеждане на низ

Реализира се също по стандарния начин.
Пример:
Операторът
cout << s;
извежда низа, който е стойност на s. Не е нужно да се грижим за дължината му. Знакът за край на низ идентифицира края на му.

Дължина на низ

Намира се чрез функцията strlen.
Синтаксис
strlen(<str>)
където
<str> е произволен низ.
Семантика
Намира дължината на <str>.
Пример:
strlen(“abc”) намира 3, strlen(“”) намира 0.
За използване на тази функция е необходимо да се включи заглавният файл string.h.

Конкатенация на низове

Реализира се чрез функцията strcat.
Синтаксис
strcat(<var_str>, <str>)
където
<var_str> е променлива от тип низ, а
<str> е низ (константа, променлива или по-общо израз).
Семантика
Конкатенира низа, който е стойност на <var_str> с низа <str>. Резултатът от конкатенацията се връща от функцията, а също се съдържа в променливата <var_str>. За използване на тази функция е необходимо да се включи заглавният файл string.h.

Пример:
#include <iostream.h>
#include <string.h>
int main()
{char a[10];
cout << "a= ";
cin >> a; // въвеждане на стойност на a
char b[4];
cout << "b= ";
cin >> b; // въвеждане на стойност на b
strcat(a, b); // конкатениране на a и b, резултатът е в a
cout << a << ‘\n’; // извеждане на a
cout << strlen(strcat(a, b)) << '\n'; //повторна конкатенация
return 0;
}
Забележка: Функцията strcat може да се използва и като оператор, и като израз.

Сравняване на низове

Реализира се чрез функцията strcmp.
Синтаксис
strcmp(<str1>, <str2>)
където
<str1> и <str2> са низове (константи, променливи или по-общо изрази).
Семантика
Низовете <str1> и <str2> се сравняват лексикографски. Функцията strcmp е целочислена. Резултатът от обръщение към нея е цяло число с отрицателна стойност (-1 за реализацията Visual C++ 6.0), ако <str1> е по-малък от <str2>, 0 – ако <str1> е равен на <str2> и с положителна стойност (1 за реализацията Visual C++ 6.0), ако <str1> е по-голям от <str2>.
За използване на strcmp е необходимо да се включи заглавният файл string.h.

Примери:
1. char a[10] = “qwerty”, b[15] = “qwerty”;
if (!strcmp(a, b)) cout << “yes \n”; else cout << “no \n”;
извежда yes, тъй като strcpm(a, b) връща 0 (низовете са равни), !strcmp(a, b) e 1 (true).
2. char a[10] = “qwe”, b[15] = “qwerty”;
if (strcmp(a, b)) cout << “yes \n”; else cout << “no \n”;
извежда yes, тъй като strcpm(a, b) връща -1 (a е по-малък от b).
3. char a[10] = “qwerty”, b[15] = “qwer”;
if (strcmp(a, b)) cout << “yes \n”; else cout << “no \n”;
извежда yes, тъй като strcpm(a, b) връща 1 (a е по-голям от b).

Копиране на низ

Реализира се чрез функцията strcpy.
Синтаксис
strcpy(<var_str>, <str>)
където
<var_str> е променлива от тип низ, а
<str> е низ (константа, променлива или по-общо израз).
Семантика
Копира <str1> в <var_str>. Ако <str1> е по-дълъг от допустимата за <val_str> дължина, са възможни труднооткриваеми грешки. Резултатът от копирането се връща от функцията, а също се съдържа в <var_str>.
За използване на тази функция е необходимо да се включи заглавният файл string.h.
Пример: Програмният фрагмент
char a[10];
strcpy(a, "1234567");
cout << a << "\n";
извежда
1234567

Търсене на низ в друг низ

Реализира се чрез функцията strstr.
Синтаксис
strstr(<str1>, <str2>)
където
<str1> и <str2> са произволни низове (константи, променливи или по-общо изрази).
Семантика
Търси <str2> в <str1>. Ако <str2> се съдържа в <str1>, strstr връща подниза на <str1> започващ от първото срещане на <str2> до края на <str1>. Ако <str2> не се съдържа в <str1>, strstr връща “нулев указател”. Последното означава, че в позиция на условие, функционалното обръщение ще има стойност false, но при опит за извеждане, ще предизвиква грешка.
За използване на тази функция е необходимо да се включи заглавният файл string.h.
Примери: Програмният фрагмент
char str1[15] = "asemadaemada", str2[10]= "ema";
cout << strstr(str1, str2) << "\n";
извежда
emadaemada
а
char str1[15] = "asemadaemada", str2[10]= "ema";
cout << strstr(str2, str1) << "\n";
предизвиква съобщение за грешка по време на изпълнение.

Преобразуване на низ в цяло число

Реализира се чрез функцията atoi.
Синтаксис
atoi(<str>)
където <str> е произволен низ (константа, променлива или по-общо израз от тип низ).
Семантика
Преобразува символния низ в число от тип int. Водещите интервали, табулации и знака за преминаване на нов ред се пренебрегват. Символният низ се сканира до първия символ различен от цифра. Ако низът започва със символ различен от цифра и знак, функцията връща 0.
За използване на тази функция е необходимо да се включи заглавният файл stdlib.h.

Примери:
Програмният фрагмент
char s[15] = "-123a45";
cout << atoi(s) << "\n";
извежда –123, а
char s[15] = "b123a45";
cout << atoi(s) << "\n";
извежда 0.

Преобразуване на низ в реално число

Реализира се чрез функцията atof.
Синтаксис
atof(<str>)
където <str> е произволен низ (константа, променлива или по-общо израз от тип низ).
Семантика
Преобразува символния низ в число от тип double. Водещите интервали, табулации и знака за преминаване на нов ред се пренебрегват. Символният низ се сканира до първия символ различен от цифра. Ако низът започва със символ различен от цифра, знак или точка, функцията връща 0.
За използване на тази функция е необходимо да се включи заглавният файл stdlib.h.

Примери:
Програмният фрагмент
char s[15] = "-123.35a45";
cout << atof(st) << "\n";
извежда –123.35, а
char st[15] = ".123.34c35a45";
cout << atof(st) << "\n";
извежда 0.123.

Допълнение:

Конкатенация на n символа от низ с друг низ

Реализира се чрез функцията strncat.
Синтаксис
strncat(<var_str>, <str>, n)
където
<var_str> е променлива от тип низ,
<str> е низ (константа, променлива или по-общо израз), а
n е цял израз с неотрицателна стойност.
Семантика
Копира първите n символа от <str> в края на низа, който е стойност на <var_str>. Копирането завършва когато са прехвърлени n символа, или е достигнат краят на <str>. Резултатът е в променливата <var_str>. За използване на тази функция е необходимо да се включи заглавният файл string.h.

Пример: Резултатът от изпълнението на фрагмента:
chat a[10] = “aaaaa”;
strncat(a, “qwertyqwerty”, 5);
cout << a;
е
aaaaaqwert
а на
strncat(a, “qwertyqwerty”, -5);
cout << a;
предизвиква съобщение за грешка.

Копиране на n символа в символен низ

Реализира се чрез функцията strncpy.
Синтаксис
strncpy(<var_str>, <str>, n)
където
<var_str> е променлива от тип низ,
<str> е низ (константа, променлива или по-общо израз), а
n е цял израз с неотрицателна стойност.
Семантика
Копира първите n символа на <str1> в <var_str>. Ако <str> има по-малко от n символа, ‘\0’ се копира до тогава докато не се запишат n символа. Параметърът <var_str> трябва да е от вида char[n] и съдържа резултатния низ. За използване на тази функция е необходимо да се включи заглавният файл string.h.
Примери: 1. Програмният фрагмент
char a[10];
strncpy(a, "1234567", 8);
cout << a << "\n";
извежда
1234567
Изпълнява се по следния начин: тъй като дължината на низа “1234567” е по-малка от 8, допълва се с един знак ‘\0’ и се свързва с променливата a.
2. Програмният фрагмент
char a[10];
strncpy(a, "123456789", 5);
cout << a << "\n";
извежда
12345<неопределено>
Изпълнява се по следния начин: тъй като дължината на низа “123456789” е по-голяма от 5, низът “12345” се свързва с променливата a, но не става допълване с ‘\0’, което личи по резултата.

Сравняване на n символа на низове

Реализира се чрез функцията strncmp.
Синтаксис
strncmp(<str1>, <str2>, n)
където
<str1> и <str2> са низове (константи, променливи или по-общо изрази), а
n е цял израз с неотрицателна стойност.
Семантика
Сравнява първите n символа на <str1> със символите от съответната позиция на <str2>. Сравнението продължава до намиране на два различни символа или до края на един от символните низове.
Резултатът от функцията strncmp е цяло число с отрицателна стойност, ако <str1> е по-малък от <str2>, 0 – ако <str1> е равен на <str2> и с положителна стойност, ако <str1> е по-голям от <str2>.
За използване на strncmp е необходимо да се включи заглавният файл string.h.

Примери:
1. char a[10] = “qwer”, b[15] = “qwerty”;
if (!strncmp(a, b, 3)) cout << “yes \n”;
else cout << “no \n”;
извежда yes, тъй като strncpm(a, b) връща 0 (низовете са равни), !strncmp(a, b) e 1 (true).
2. char a[10] = “qwer”, b[15] = “qwerty”;
if (strncmp(a, b, 5)) cout << “yes \n”;
else cout << “no \n”;
извежда yes, тъй като strncpm(a, b) връща -1 (a е по-малък от b).
3. char a[10] = “qwerty”, b[15] = “qwer”;
if (strncmp(a, b, 5)) cout << “yes \n”;
else cout << “no \n”;
извежда yes, тъй като strncpm(a, b) връща 1 (a е по-голям от b).

Търсене на символ в низ

Реализира се чрез функцията strchr, съдържаща се в string.h.
Синтаксис
strchr(<str>, <expr>)
където
<str> е произволен низ, а
<expr> e израз от интегрален или изброен тип с положителна стойност, означаваща ASCII код на символ.
Семантика
Търси първото срещане на символа, чийто ASCII код е равен на стойността на <expr>. Ако символът се среща, функцията връща подниза на <str> започващ от първото срещане на символа и продължаващ до края му. Ако символът не се среща - връща “нулев указател”. “нулев указател”. Последното означава, че в позиция на условие, функционялното обръщение ще има стойност false, но при опит за извеждане, ще предизвика грешка.
Примери: Операторът
cout << strchr(“qwerty”, ‘e’);
извежда
erty
Операторът
cout << strchr(“qwerty”, ‘p’);
извежда съобщение за грешка, а
if (strchr(“qwerty”, ‘p’)) cout << “yes \n”; else cout << “no \n”;
извежда no, тъй като ‘p’ не се среща в низа “qwerty”.

Търсене на първата разлика

Реализира се чрез функцията strspn.
Синтаксис
strspn(<str1>, <str2>)
където
<str1> и <str2> са произволни низове (константи, променливи или по-общо изрази).
Семантика
Проверява до коя позиция <str1> и <str2> съвпадат. Връща дължината на префикса до първия различен символ. За използване на тази функция е необходимо да се включи заглавният файл string.h.
Пример: Програмният фрагмент
char a[10]= "asdndf", b[15] = "asdsdfdhf";
cout << strspn(a, b) << "\n";
извежда 3 тъй като първият символ, по който се различават a и b е в позиция 4.

ivakavlad
10-20-2010, 23:07
Задачи

Задача 1. Да се напише програма, която намира скаларното произведение на реалните вектори a = (a0, a1, ..., an-1) и b = (b0, b1,..., bn-1), (1 ≤ n ≤ 50).
Задача 2. Да се напише програма, която въвежда n символа и намира и извежда минималния (максималния) от тях.
Задача 3. Да се напише програма, която:
а) въвежда редицата от n цели числа a0, a1, ..., an-1,
б) намира и извежда сумата на тези елементи на редицата, които се явяват удвоени нечетни числа.
Задача 4. Да се напише програма, която намира и извежда сумата от положителните и броя на отрицателните елементи на редицата от реални числа a0, a1, ..., an-1 (1 ≤ n ≤ 30).
Задача 5. Да се напише програма, която изчислява реципрочното число на произведението на тези елементи на редицата a0, a1, ..., an-1 (1 ≤ n ≤ 50), за които е в сила релацията 2 < аi < i!.
Задача 6. Да се напише програма, която изяснява, има ли в редицата от цели числа a0, a1, ..., an-1 (1 ≤ n ≤ 100) два последователни нулеви елемента.
Задача 7. Дадени са сортираните във възходящ ред числови редици a0, a1, ..., ak-1 и b0, b1, ..., bk-1 (1 ≤ k ≤ 40). Да се напише програма, която намира броя на равенствата от вида ai = bj (i = 0, ..., k-1, j = 0, ..., k-1).
Задача 8. Дадени са две редици от числа. Да се напише програма, която определя колко пъти първата редица се съдържа във втората.
Задача 9. Всяка редица от равни числа в едномерен сортиран масив, се нарича площадка. Да се напише програма, която намира началото и дължината на най-дългата площадка в даден сортиран във възходящ ред едномерен масив.
Задача 10. Да се напише програма, която по дадена числова редица a0, a1, ..., an-1 (1 ≤ n ≤ 20) намира дължината на максималната й ненамаляваща подредица ai1, ai2, ..., aiк (ai1 <= ai2 <= ... <= aiк, i1 < i2 < ... < iк).
Задача 11. Дадена е редицата от символи s0, s1, ..., sn-1 (1 ≤ n ≤ 15). Да се напише програма, която извежда отначало всички символи, които изобразяват цифри, след това всички символи, които изобразяват малки латински букви и накрая всички останали символи от редицата, запазвайки реда им в редицата.
Задача 12. Дадени са две цели числа, представени с масиви от символи. Да се напише програма, която установява дали първото от двете числа е по-голямо от второто.
Задача 13. Да се напише програма, която определя дали редицата от символи s0, s1, ..., sn-1 (1 ≤ n ≤ 45) е симетрична, т.е. четена отляво надясно и отдясно наляво е една и съща.
Задача 14. Дадена е редицата от естествени числа a0, a1, ..., an-1 (1 ≤ n ≤ 30). Да се напише програма, която установява има ли сред елементите на редицата не по-малко от два елемента, които са степени на 2.
Задача 15. Дадени са полиномите Pn(x) и Qm(x). Да се напише програма, която намира:
а) сумата им
б) произведението им.
Задача 16. За векторите a = (a0, a1, ..., an-1) и b = (b0, b1, ..., bn-1) (1 ≤ n ≤ 20), да се определи дали са линейно зависими.
Задача 17. Дадена е квадратна целочислена матрица A с размерност nxn, елементите на която са естествени числа. Да се напише програма, която намира:
а) сумата от елементите под главния диагонал, които са прости числа;
б) произведението от елементите над главния диагонал, в записа на цифрите на които се среща цифрата 5;
в) номера на първия неотрицателен елемент върху главния диагонал.
Задача 18. Дадена е квадратната реална матрица A с размерност nxn. Да се напише програма, която намира:
а) сумата от елементите върху вторичния главен диагонал;
б) произведението от елементите под (над) вторичния главен диагонал.
Задача 19. Дадена е реална правоъгълна матрица A с размерност nxm. Да се напише програма, която изтрива k-ти ред (стълб) на A. Изтриването означава да се преместят редовете (стълбовете) с един нагоре (наляво) и намаляване броя на редовете (стълбовете) с един.

Задача 20. Върху равнина са дадени n точки чрез матрицата

така, че (x0,i, x1,i) са координатите на i-тата точка. Точките по двойки са съединени с отсечки. Да се напише програма, която намира дължината на най-дългата отсечка.

Задача 21. Дадена е матрицата от цели числа

Да се напише програма, която намира сумата на тези елементи a1,i, (0 ≤ i ≤ n-1), за които a0,i имат стойността на най-голямото сред елементите от първия ред на матрицата.
Задача 22. Дадено е множеството M от двойки
M = {<x0, y0>, <x1, y1>, ..., <xn-1, yn-1>},
като xi и yi (0 ≤ i ≤ n-1) са цели числа. Да се напише програма, която проверява дали множеството M дефинира функция.
Упътване: Множеството M дефинира функция, ако от xi = xj следва yi = yj.


Задача 23. Да се напишат програми, която конструират матриците:

Задача 24. Дадена е целочислената квадратна матрица A от n-ти ред. Да се напише програма, която намира максималното от простите числа на А.
Задача 25. Казваме, че два стълба на една матрица си приличат, ако съвпадат множествата от числата, съставящи стълбовете. Да се напише програма, която намира номерата на всички стълбове на матрицата Anxm, които си приличат.
Задача 26. Матрицата А има седлова точка в a[i, j], ако a[i, j] е минимален елемент в i-я ред и максимален елемент в j-я стълб на A. Да се напише програма, която намира всички седлови точки на дадена матрица А.
Задача 27. Матрицата А има седлова точка в a[i, j], ако a[i, j] е минимален елемент в i-я ред и максимален елемент в j-я стълб на A. Да се напише програма, която установява дали съществува седлова точка в дадена матрица A.
Задача 28. Дадена е квадратна матрица A от n-ти ред. Да се напише програма, която установява дали съществува k (0 ≤ k ≤ n-1), така че k-я стълб на А да съвпада с k-я й ред.
Задача 29. Дадена е реалната квадратна матрица Anxn. Да се напише програма, която намира:
а) max { min {aij}} в) min{ max {aij}}
0≤i≤n-1 0≤j≤n-1 0≤i≤n-1 0≤j≤n-1
б) max{ min {aij}} г) min{ max {aij}}
0≤j≤n-1 0≤i≤n-1 0≤j≤n-1 0≤i≤n-1
Задача 30. Дадена е квадратната матрица Anxn (2 ≤ n ≤ 10). Да се напише програма, която определя явява ли се A ортонормирана, т.е. такава, че скаларното произведение на всеки два различни реда на A е равно на 0, а скаларното произведение на всеки ред на себе си е равно на 1?
Задача 31. Да се напише програма, която определя явява ли се квадратната матрица Anxn магически квадрат, т.е. такава, че сумата от елементите от всички редове и стълбове е еднаква.
Задача 32. Дадена е система от линейни уравнения от n-ти ред. Да се напише програма, която я решава.
Задача 33. С тройката (i, j, v) се представя елемента v от i-я ред и j-я стълб на матрица. Две матрици с размерност nxn са представени като редици от тройки. Тройките са подредени по редове. Ако тройката (i, j, v) отсъства, приема се, че v = 0. Да се напише програма, която събира матриците и представя резултата като редица от тройки.




Допълнителна литература

1. Ст. Липман, Езикът C++ в примери, “КОЛХИДА ТРЕЙД” КООП, С. 1993.
2. К. Хорстман, Принципи на програмирането със C++, С., СОФТЕХ, 2000.
3. B. Stroustrup, C++ Programming Language. Third Edition, Addison – Wesley, 1997.

ivakavlad
10-20-2010, 23:20
Глава 6

Типове указател и псевдоним


1. Тип указател

Променлива, това е място за съхранение на данни, което може да съдържа различни стойности. Идентифицира се с дадено от потребителя име (идентификатор). Има си и тип. Дефинира се като се указват задължително типът и името й. Типът определя броя на байтовете, в които ще се съхранява променливата, а също и множеството от операциите, които могат да се изпълняват над нея. Освен това, с променливата е свързана и стойност – неопределена или константа от типа, от който е тя. Нарича се още rvalue. Мястото в паметта, в което е записана rvalue се нарича адрес на променливата или lvalue. По-точно адресът е адреса на първия байт от множеството байтове, отделени за променливата.
Пример: Фрагментът
int i = 1024;
дефинира променлива с име i и тип int. Стойността й (rvalue) е 1024. i именува място от паметта (lvalue) с размери 4 байта, като lvalue е адреса на първия байт.

Намирането на адреса на дефинирана променлива става чрез унарния префиксен дясноасоциативен оператор & (амперсанд). Приоритетът му е същия като на унарните оператори +, -, !, ++, -- и др. Фиг. 1 описва оператора.


Синтаксис
&<променлива>
където <променлива> е вече дефинирана променлива.
Семантика
Намира адреса на <променлива>.


Фиг. 1.

Пример: &i е адреса на променливата i и може да се изведе чрез оператора cout << &i;
Операторът & не може да се прилага върху константи и изрази, т.е. &100 и &(i+5) са недопустими обръщения. Не е възможно също прилагането му и върху променливи от тип масив, тъй като те имат и смисъла на константни указатели.
Адресите могат да се присвояват на специален тип променливи, наречени променливи от тип указател или само указатели.

Задаване на тип указател

Нека T е име или дефиниция на тип. За типа T, T* е тип, наречен указател към T. T се нарича указван тип или тип на указателя.
Примери:
int* е тип указател към int;
enum {a, b, c}* е тип указател към enum {a, b, c}.

Множество от стойности

Състои се от адресите на данните от тип T, дефинирани в програмата, преди използването на T*. Те са константите на типа T*. Освен тях съществува специална константа с име NULL, наречена нулев указател. Тя може да бъде свързвана с всеки указател независимо от неговия тип. Тази константа се интерпретира като “сочи към никъде”, а в позиция на предикат е false.

Променлива величина, множеството от допустимите стойности, на която съвпада с множеството от стойности на типа T*, се нарича променлива от тип T* или променлива от тип указател към тип T. Дефинира се по стандартния начин. Фиг. 2 показва синтаксиса на дефиниция на променлива от тип указател.


Дефиниция на променлива от тип указател

T* <променлива> [= <стойност>]; |
T *<променлива> [= <стойност>];
където
T е име или дефиниция на тип;
<променлива> ::= <идентификатор>
<стойност> е шестнадесетично цяло число, представляващо адрес на данна от тип T или NULL.


Фиг. 2.

T определя типа на данните, които указателят адресира, а също и начина на интерпретацията им.
Възможно е фрагментите
<променлива> [=<стойност>] и
*<променлива>[=<стойност>]
да се повтарят. За разделител се използва запетаята. В първия случай обаче има особеност. Дефиницията
T* a, b;
е еквивалентна на
T* a;
T b;
т.е. само променливата a е указател.
Примери: Дефиницията
int *pint1, *pint2;
задава два указателя към тип int, а
int *pint1, pint2;
- указател pint1 към int и променлива pint2 от тип int.

Дефиницията на променлива от тип указател предизвиква в ОП да се отделят 4B, в които се записва някакъв адрес от множеството от стойности на съответния тип, ако дефиницията е с инициализация и неопределено или NULL, ако дефиницията не е с инициализация. (За реализацията Visual C++ 6.0 е неопределено). Този адрес е стойността на променливата от тип указател, а записаното на този адрес е съдържанието й.
Пример: Дефинициите
int i = 12;
int* p = &i; // p е инициализирано с адреса на i
double *q = NULL; // q е инициализирано с нулевия указтел
double x = 1.56;
double *r = &x; // r е инициализирано с адреса на x
предизвикват следното разпределение на паметта
ОП
i p q x r
12 0x00000000 1.56

Съвет: Всеки указател, който не сочи към конкретен адрес, е добре да се свърже с константата NULL. Ако по невнимание се опитате да използвате нулев указател, програмата ви може да извърши нарушение при достъп и да блокира, но това е по-добре, отколкото указателят да сочи към кой знай къде.

Операции и вградени функции

Извличане на съдържанието на указател

Осъществява се чрез префиксния, дясноасоциативен унарен оператор * (Фиг. 3).


Синтаксис
*<променлива_от_тип_указател>
Семантика
Извлича стойността на адреса, записан в <променлива_от_тип_ указател>, т.е. съдържанието на <променлива_от_тип_ указател>.


Фиг. 3.

Като използваме дефинициите от примера по-горе, имаме:
*p e 12 // 12 е съдържанието на p
*r е 1.56 // 1.56 е съдържанието на r
Освен, че намира съдържанието на променлива от тип указател, обръщението
*<променлива_от_тип_указател>
е данна от тип T (променлива или константа). Всички операции, допустими за типа T, са допустими и за нея.
Като използваме дефинициите от примера по-горе, *p и *r са цяла и реална променливи, съответно. След изпълнение на операторите за присвояване
*p = 20;
*r = 2.18;
стойността на i се променя на 20, а тази на r – на 2.18.

Аритметични и логически операции

Променливите от тип указател могат да участват като операнди в следните аритметични и логически операции +, -, ++, --, ==, !=, >, >=, < и <=. Изпълнението на аритметични операции върху указатели е свързано с някои особености, заради което аритметиката с указатели се нарича още адресна аритметика. Особеноста се изразява в т. нар. мащабиране. Ще го изясним чрез пример.
Да разгледаме фрагмента
int *p;
double *q;
...
p = p + 1;
q = q + 1;
Операторът p = p + 1; свързва p не със стойността на p, увеличена с 1, а с p + 1*4, където 4 е броя на байтовете, необходими за записване на данна от тип int (p е указател към int). Аналогично, q = q + 1; увеличава стойността на q не с 1, а с 8, тъй като q е указател към double (8 байта са необходими за записване на данна от този тип).
Общото правило е следното: Ако p е указател от тип T*, p+i е съкратен запис на p + i*sizeof(T), където sizeof(T) е функция, която намира броя на байтовете, необходими за записване на данна от тип Т.

Въвеждане

Не е възможно въвеждане на данни от тип указател чрез оператора cin. Свързването на указател със стойност става чрез инициализация или оператора за присвояване.

Извеждане

Осъществява се по стандартния начин - чрез оператора cout.

Допълнение
Типът, който се задава в дефиницията на променлива от тип указател, е информация за компилатора относно начина, по който да се интерпретира съдържанието на указателя. В контекста на горния пример *p са четири байта, които ще се интерпретират като цяло число от тип int. Аналогично, *q са осем байта, които ще се интерпретират като реално число от тип double.

Следващата програма илюстрира дефинирането и операциите за работа с указатели.
#include <iostream.h>
int main()
{int n = 10; // дефинира и инициализира цяла променлива
int* pn = &n; // дефинира и инициализира указател pn към n
// показва, че указателят сочи към n
cout << "n= " << n << " *pn= " << *pn << '\n';
// показва, че адресът на n е равен на стойността на pn
cout << "&n= "<< &n << " pn= " << pn << '\n';
// намиране на стойността на n чрез pn
int m = *pn; // == 10
// промяна на стойността на n чрез pn
*pn = 20;
// извеждане на стойността на n
cout << "n= " << n << '\n'; // n == 20
return 0;
}

В някои случаи е важна стойността на променливата от тип указател (адресът), а не нейното съдържание. Тогава тя се дефинира като указател към тип void. Този тип указатели са предвидени с цел една и съща променлива - указател да може в различни моменти да сочи към данни от различен тип. В този случай, при опит да се използва съдържанието на променливата от тип указател, ще се предизвика грешка. Съдържанието на променлива - указател към тип void може да се извлече само след привеждане на типа на указателя (void*) до типа на съдържанието. Това може да се осъществи чрез операторите за преобразуване на типове.
Пример:
int a = 100;
void* p; // дефинира указател към void
p = &a; // инициализира p
cout << *p; // грешка
cout << *((int*) p); // преобразува p в указател към int
// и тогава извлича съдържанието му.

В C++ е възможно да се дефинират указатели, които са константи, а също и указатели, които сочат към константи. И в двата случая се използва запазената дума const, която се поставя пред съответните елементи от дефинициите на указателите. Стойността на елемента, дефиниран като const (указателя или обекта, към който сочи) не може да бъде променяна.
Пример:
int i, j = 5;
int *pi; // pi е указател към int
int * const b = &i; // b е константен - указател към int
const int *c = &j; // c е указател към цяла константа.
b = &j; // грешка, b е константен указател
*c = 15; // грешка, *c е константа

ivakavlad
10-20-2010, 23:21
2. Указатели и масиви

В C++ има интересна и полезна връзка между указателите и масивите. Тя се състои в това, че имената на масивите са указатели към техните “първи” елементи. Това позволява указателите да се разглеждат като алтернативен начин за обхождане на елементите на даден масив.

Указатели и едномерни масиви

Нека a е масив, дефиниран по следния начин:
int a[100];
Тъй като a е указател към а[0], *a е стойността на a[0], т.е. *a и a[0] са два различни записа на стойността на първия елемент на масива. Тъй като елементите на масива са разположени последователно в паметта, a + 1 е адреса на a[1], a + 2 е адреса на a[2] и т.н. a + n-1 е адреса на a[n-1]. Тогава *(a+i) е друг запис на a[i] (i = 0, 1, ..., n-1).
Има обаче една особеност. Имената на масивите са константни указатели. Заради това, някои от аритметичните операции, приложими над указатели, не могат да се приложат над масиви. Такива са ++, -- и присвояването на стойност.

Следващата програма показва два начина за извеждане на елементите на масив.
#include <iostream.h>
int main()
{int a[] = {1, 2, 3, 4, 5, 6};
for (int i = 0; i <= 5; i++)
cout << a[i] << '\n';
for (i = 0; i <= 5; i++)
cout << *(a+i) << '\n';
return 0;
}
Фрагментът
for (i = 0; i <= 5; i++)
{cout << *a << '\n';
a++;
}
съобщава за грешка заради оператора a++ (a е константен указател и не може да бъде променян). Може да се поправи като се използва помощна променлива от тип указател към int, инициализирана с масива a, т.е.
int* p = a;
for (i = 0; i <= 5; i++)
{cout << *p << '\n';
p++;
}
Използването на указатели е по-бърз начин за достъп до елементите на масива и заради това се предпочита. Индексираните променливи правят кода по-ясен и разбираем. В процеса на компилация всички конструкции от вида a[i] се преобразуват в *(a+i), т.е. операторът за индексиране [...] се обработва от компилатора чрез адресна аритметика. Полезно е да отбележим, че операторът [] е лявоасоциативен и с по-висок приоритет от унарните оператори (в частност от оператора за извличане на съдържание *).

Указатели и двумерни масиви

Името на двумерен масив е константен указател към първия елемент на едномерен масив от константни указатели. Ще изясним с пример казаното.
Нека a е двумерен масив, дефиниран по следния начин:
int a[10][20];
Променливата a е константен указател към първия елемент на едномерния масив a[0], a[1], ..., a[9], като всяко a[i] е константен указател към a[i][0] (i = 0,1, ..., 9), т.е.
a


a[0] a[0][0] a[0][1] … a[0][19]

a[1] a[1][0] a[1][1] … a[1][19] …

a[9] a[9][0] a[9][1] … a[9][19]

Тогава
**a == a[0][0]
a[0] == *a a[1] == a[0]+1 ... a[9] == a[0]+9,
т.е.
a[i] == a[0] + i == *a + i
Като използваме, че операторът за индексиране е лявоасоциативен, получаваме:
a[i][j] == (*a + i) [j] == *((*a + i) + j).

Задача 67. Да се напише програма, която въвежда по редове правоъгълна матрица Anxk от реални числа и извежда матрицата, образувана от редовете на A от в четна позиция, като всеки елемент е увеличен с 1, след което извежда матрицата, образувана от редовете от нечетна позиция, като всеки елемент е увеличен с 2 и накрая, ако n е четно, извежда сумата на матриците от редовете от четните и нечетните позиции на A.

Програма Zad67.cpp решава задачата.
Program Zad67.cpp
#include <iostream.h>
#include <iomanip.h>
int main()
{int a[20][100];
int* p[20];
int* q[20];
cout << "n, k = ";
int n, k;
cin >> n >> k;
if (!cin)
{cout << "Error! \n";
return 0;
}
int i, j;
for (i = 0; i <= n-1; i++)
for (j = 0; j <= k-1; j++)
{cout << "a[" << i << "][" << j << "]= ";
cin >> *(*(a+i)+j);
}
for (i = 0; i <= n-1; i++)
{for(j = 0; j <= k-1; j++)
cout << setw(10) << *(*(a+i)+j);
cout << '\n';
}
cout << "\n\n first new array\n";

int m = -1;
for (i = 0; i <= n-1; i = i+2)
{m++;
*(p+m) = *(a+i);
}
for (i = 0; i <= m; i++)
{for(j = 0; j <= k-1; j++)
cout << setw(10) << *(*(p+i)+j)+1;
cout << '\n';
}
int l = -1;
for (i = 1; i <= n-1; i = i+2)
{l++;
q[l] = a[i];
}
cout << "\n\n second new array \n";
for (i = 0; i <= l; i++)
{for(j = 0; j <= k-1; j++)
cout << setw(10) << *(*(q+i)+j)+2;
cout << '\n';
}
cout << "\n\n third new array \n";
if (n%2 == 0)
for (i = 0; i <= m; i++)
{for (j = 0; j <= k-1; j++)
cout << setw(10) << *(*(p+i)+j) + *(*(q+i)+j);
cout << '\n';
}
return 0;
}

ivakavlad
10-20-2010, 23:21
3. Указатели и низове

Низовете са масиви от символи. Името на променлива от тип низ е константен указател, както и името на всеки друг масив. Така всичко, което казахме за връзката между масив и указател е в сила и за низ – указател.
Следващият пример илюстрира обхождане на низ чрез използване на указател към char. Обхождането продължава до достигане на знака за край на низ.
#include <iostream.h>
int main()
{char str[] = "C++Language"; // str е константен указател
char* pstr = str;
while (*pstr)
{cout << *pstr << '\n';
pstr++;
}// pstr вече не е свързан с низа “C++Language”.
return 0;
}
Тъй като низът е зададен чрез масива от символи str, str е константен указател и не може да бъде променяна стойността му. Затова се налага използването на помощната променлива pstr.
Ако низът е зададен чрез указател към char, както е в следващата програма, не се налага използването на такава.
#include <iostream.h>
int main()
{char* str = "C++Language"; // str е променлива
while (*str)
{cout << *str << '\n';
str++;
}
return 0;
}
Примерите показват, че задаването на низ като указател към char има предимство пред задаването като масив от символи. Ще отбележим обаче, че дефиницията
char* str = "C++Language";
не може да бъде заменена с
char* str;
cin >> str;
следвани с въвеждане на низа “C++Language”, докато дефиницията
char str[20];
позволява въвеждането му чрез cin, т.е.
cin >> str;

Има още една особеност при дефинирането на низ като указател към char за реализацията Visual C++ 6.0. Ще я илюстрираме с пример.
Нека дефинираме променлива от тип низ по следния начин:
char s[] = “abba”;
Операторът
*s = ‘A’;
е еквивалентен на s[0] = ‘A’ и ще замени първото срещане на символа ‘a’ в s с ‘A’. Така
cout << s;
извежда низа
Abba
Да разгледаме съответната дефиницията на s чрез указател към char
char* s = “abba”;
Операторът
*s = ‘A’;
би трябвало да замени първото срещане на символа ‘a’ в s с ‘A’, тъй като s съдържа адреса на първото ‘a’. Тук обаче реализацията на Visual C++ съобщава за грешка – нарушение на достъпа. Последното може да се избегне като опцията на компилатора /ZI се замени с /Zi. Това се реализира като в менюто Project се избере Settings, след това C/C++, където Category трябва да има опция General. Накрая, в Project Options се промени /ZI на /Zi. Опцията /Zi се грижи за проблемите при нарушаване на достъпа, едно неудобство, което трябва да се има предвид.

ivakavlad
10-20-2010, 23:22
4. Тип псевдоним

Чрез псевдонимите се задават алтернативни имена на обекти в общия смисъл на думата (променливи, константи и др.). В тази част ще ги разгледаме малко ограничено (псевдоними само за променливи).

Задаване на тип псевдоним

Нека T е име на тип. Типът T& е тип псевдоним на T. T се нарича базов тип на типа псевдоним.

Множество от стойности

Състои се от всички имена на дефинирани вече променливи от тип T.

Пример: Нека програмата съдържа следните дефиниции
int a, b = 5;
...
int x, y = 9, z = 8;
...
Множеството от стойности на типа int& съдържа имената a, b, x, y, z. Променлива величина, множеството от допустимите стойности на която съвпада с множеството от стойности на даден тип псевдоним, се нарича променлива от този тип псевдоним. Фиг. 4 илюстрира дефиницията.


<дефиниция_на_променлива_от_ тип_псевдоним> ::=
T& <var> = <defined_var_of_T>; |
T &<var> = <defined_var_of_T>;
където
T е име тип, а
<defined_var_of_T> е име на вече дефинирана променлива от тип T.
Нарича се инициализатор.


Фиг. 4.

Възможно е фрагментите
<var> = <defined_var_of_T> и
&<var> = <defined_var_of_T>
да се повтарят многократно. За разделител се използва символът запетая. Има обаче една особеност. Дефиницията
Т& а = b, c = d;
е еквивалентна на
Т& а = b;
Т c = d;
Пример: Дефинициите
int a = 5;
int& syna = a;
double r = 1.85;
double &syn1 = r, &syn2 = r;
int& syn3 = a, syn4 = a;
определят syna и syn3 за псевдоними на a, syn1 и syn2 за псевдоними на r и syn4 за променлива от тип int.
Дефинициите задължително са с инициализация – променлива от същия тип като на базовия тип на типа псевдоним. Освен това, след инициализацията, променливата псевдоним не може да се променя като й се присвоява нова променлива или чрез повторна дефиниция. Затова тя е “най-константната” променлива, която може да съществува.
Пример:
...
int a = 5;
int &syn = a; // syn е псевдоним на a
int b = 10;
int& syn = b; // error, повторна дефиниция
...

Операции и вградени функции

Дефиницията на променлива от тип псевдоним свързва променливата-псевдоним с инициализатора и всички операции и вградени функции, които могат да се прилагат над инициализатора, могат да се прилагат и над псевдонима й и обратно.

Примери:
1. int ii = 0;
int& rr = ii;
rr++;
int* pp = &rr;
Резултатът от изпълнението на първите два оператора е следния:
ii, rr

... 0 ...

Операторът rr++; не променя адреса на rr, а стойността на ii и тя от 0 става 1. В случая rr++ е еквивалентен на ii++. Адресът на rr е адреса на ii. Намира се чрез &rr. Чрез дефиницията
int* pp = &rr;
pp е определена като указател към int, инициализирана с адреса на rr.
2. int a = 5;
int &syn = a;
cout << syn << " " << a << '\n';
int b = 10;
syn = b;
cout << b << " " << a << “ “ << syn << '\n';
извежда
5
10 10
Операторът syn = b; е еквивалентен на a = b;.
3. int i = 1;
int& r = i; // r и i са свързани с едно и също цяло число
cout << r; // извежда 1
int x = r; // x има стойност 1
r = 2; // еквивалентно е на i = 2;
Допълнение: Възможно е типът на инициализатора да е различен от този на псевдонима. В този случай се създава нова, наречена временна, променлива от типа на псевдонима, която се инициализира със зададената от инициализатора стойност, преобразувана до типа на псевдонима.
Например, след дефиницията
double x = 12.56;
int& synx = x;
имаме
x synx

12.56 ... 12

8B 4B

Сега x и псевдонимът й synx са различни променливи и промяната на x няма да влияе на synx и обратно.

Константни псевдоними
В C++ е възможно да се дефинират псевдоними, които са константи. За целта се използва запазената дума const, която се поставя пред дефиницията на променливата от тип псевдоним. По такъв начин псевдонимът не може да променя стойността си, но ако е псевдоним на променлива, промяната на стойността му може да стане чрез промяна на променливата.
Пример: Фрагментът
int i = 125;
const int& syni = i;
cout << i << " " << syni << '\n';
syni = 25;
cout << i << " " << syni << '\n';
ще съобщи за грешка (syni е константа и не може да е лява страна на оператор за присвояване), но фрагментът
int i = 125;
const int& syni = i;
cout << i << " " << syni << '\n';
i = i + 25;
cout << i << " " << syni << '\n';
ще изведе
125 125
150 150
Последното показва, че константен псевдоним на променлива защитава промяната на стойността на променливата чрез псевдонима.





Допълнителна литература

1. Ст. Липман, Езикът C++ в примери, “КОЛХИДА ТРЕЙД” КООП, С. 1993.
2. B. Stroustrup, C++ Programming Language. Third Edition, Addison – Wesley, 1997.

ivakavlad
10-20-2010, 23:24
Глава 7

Функции

Добавянето на нови оператори и функции в приложенията, реализирани на езика C++, се осъществява чрез функциите. Те са основни структурни единици, от които се изграждат програмите на езика. Всяка функция се състои от множество от оператори, оформени подходящо за да се използват като обобщено действие или операция. След като една функция бъде дефинирана, тя може да бъде изпълнявана многократно за различни входни данни.
Програмите на езика C++ се състоят от една или повече функции. Сред тях задължително трябва да има точно една с име main и наречена главна функция. Тя е първата функция, която се изпълнява при стартиране на програмата. Главната функция от своя страна може да се обръща към други функции. Нормалното изпълнение на програмата завършва с изпълнението на главната функция (Възможно е изпълнението да завърши принудително с изпълнението на функция, различна от главната).
Използването на функции има следните предимства:
- Програмите стават ясни и лесни за тестване и модифициране.
- Избягва се многократното повтаряне на едни и същи програмни фрагменти. Те се дефинират еднократно като функции, след което могат да бъдат изпълнявани произволен брой пъти.
- Постига се икономия на памет, тъй като кодът на функцията се съхранява само на едно място в паметта, независимо от броя на нейните изпълнения.

Ще разгледаме най-общо разпределението на оперативната памет за изпълнима програма на C++. Чрез няколко примерни програми ще покажем дефинирането, обръщението и изпълнението на функции, след което ще направим съответните обобщения.

ivakavlad
10-20-2010, 23:26
1. Разпределение на ОП за изпълнима програма

Разпределението на ОП зависи от изчислителната система, от типа на операционната система, а също от модела памет. Най-общо се състои от: програмен код, област на статичните данни, област на динамичните данни и програмен стек (Фиг. 1).
краен адрес на ОП

указател на стека
(запълване в посока
към малките адреси)
О










начален адрес на ОП


Фиг. 1.

Програмен код

В тази част е записан изпълнимият код на всички функции, изграждащи потребителската програма.
Област на статичните данни
В нея са записани глобалните обекти на програмата.

Област на динамичните данни
За реализиране на динамични структури от данни (списъци, дървета, графи, ...) се използват средства за динамично разпределение на паметта. Чрез тях се заделя и освобождава памет в процеса на изпълнение на програмата, а не преди това (при компилирането й). Тази памет е от областта на динамичните данни.

Програмен стек
Този вид памет съхранява данните на функциите на програмата. Стекът е динамична структура, организирана по правилото “последен влязъл – пръв излязъл”. Той е редица от елементи с пряк достъп до елементите от единия си край, наречен връх. Достъпът се реализира чрез указател. Операцията включване се осъществява само пред елемента от върха, а операцията изключване – само за елемента от върха.
Елементите на програмния стек са “блокове” от памет, съхраняващи данни, дефинирани в някаква функция. Наричат се стекови рамки.

ivakavlad
10-20-2010, 23:28
2. Примери за програми, които дефинират и използват функции

Задача 68. Да се напише програма, която въвежда стойности на естествените числа a, b, c и d и намира и извежда най-големият общ делител на числата a и b, след това на c и d и накрая на a, b, c и d.

Програма Zad68.cpp решава задачата. Тя се състои от две функции: gcd и main. Функцията gcd(x, y) намира най-големия общ делител на естествените числа x и y. Тъй като main се обръща към (извиква) gcd, функцията gcd трябва да бъде известна преди функцията main. Най-лесният начин да се постигне това е във файла, съдържащ програмата, първо да се постави дефиницията на gcd, а след това тази на main. Ще бъде показан алтернативен начин по-късно.
Описанието на функцията gcd прилича на това на функцията main. Състои се от заглавие
int gcd(int x, int y)
и тяло
{while (x != y)
if (x >= y) x = x-y; else y = y-x;
return x;
}
Заглавието определя, че gcd е име на двуаргументна целочислена функция с цели аргументи, т.е.

gcd: int x int int

Името е произволен идентификатор. В случая е направен мнемонически избор. Запазената дума int пред името на функцията е типа й (по-точно е типа на резултата на функцията). В кръгли сkобки и отделени със запетая са описани параметрите x и y на gcd. Те са различни идентификатори. Предшестват се от типовете си. Наричат се формални параметри за функцията.
Тялото на функцията е блок, реализиращ алгоритъма на Евклид за намиране на най-големия общ делител на естествените числа x и y. Завършва с оператора
return x;
чрез който се прекратява изпълнението на функцията като стойността на израза след return се връща като стойност на gcd в мястото, в случая в main, в което е направено обръщението към нея.

Program Zad68.cpp
#include <iostream.h>
int gcd(int x, int y)
{while (x != y)
if (x >= y) x = x-y; else y = y-x;
return x;
}
int main()
{cout << "a, b, c, d= ";
int a, b, c, d;
cin >> a >> b >> c >> d;
if (!cin || a < 1 || b < 1 || c < 1 || d < 1)
{cout << "Error \n";
return 1;
}
int r = gcd(a, b);
cout << "gcd{" << a << ", " << b << "}= " << r << "\n";
int s = gcd(c, d);
cout << "gcd{" << c << ", " << d << "}= " << s << "\n";
cout << "gcd{" << a << ", " << b << ", " << c << ", "
<< d << "}= " << gcd(r, s) << "\n";
return 0;
}

Изпълнение на програма Zad68.cpp

Дефинициите на функциите main и gcd се записват в областта на паметта, определена за програмния код. Изпълнението на програмата започва с изпълнение на функцията main. Фрагментът
cout << "a, b, c, d= ";
int a, b, c, d;
cin >> a >> b >> c >> d;
if (!cin || a < 1 || b < 1 || c < 1 || d < 1)
{cout << "Error \n";
return 1;
}
дефинира и въвежда стойности на целите променливи a, b, c и d като осигурява да са естествени числа. Нека за a, b, c и d са въведени 14, 21, 42 и 7 съответно. В тази последователност те се записват в дъното на програмния стек (Фиг. 2.). Така на дъното на стека се оформя “блок” от памет за main с достатъчно големи размери, който освен променливите от main съдържа и някои “вътрешни” данни. Този блок се нарича стекова рамка на main.
Операторът
int r = gcd(a, b);
дефинира цялата променлива r като в стековата рамка на main, веднага след променливата d отделя 4B, в които ще запише резултатът от обръщението gcd(a, b) към функцията gcd. Променливите a и b се наричат фактически параметри за това обръщение. Забелязваме, че типът им е същия като на съответните им формални параметри x и y.

a a 0x0066FDF4
b 0x0066FDF0
c 0x0066FDEC
d 0x0066FDE8 стекова рамка
r на main
0x0066FDE4


указател на стека
main ... 0x0040100F

gcd 0x0040100A програмен код





Фиг. 2.

Обръщение към gcd(a, b)
В програмния стек се генерира нов блок памет – стекова рамка за функцията gcd. В него се записват формалните и локалните параметри на gcd, а също и някои “вътрешни” данни като return-адреса и адреса на стековата рамка на main.
Обръщението се осъществява на два етапа:

а) Свързване на формалните с фактическите параметри
В стековата рамка на gcd, се отделят по 4 байта за формалните параметри x и y в обратен ред на реда, в които са записани в заглавието. В тази памет се откопирват стойностите на съответните им фактически параметри. Отделят се също 4B за т. нар. return-адрес, адреса на мястото в main, където ще се върне резултатът, а също се отделя памет, в която се записва адресът на предишната стекова рамка, т.е.

памет за gcd (I-во обръщение към него)

y 0x0066FD90
x 0x0066FD8C
0x0066FD88

return-адрес

адрес на предишната
стекова рамка
указател на стека

б) Изпълнение на тялото на gcd
Тъй като е в сила y > x, стойността на y се променя на 7, т.е.

памет в стека за gcd

y 0x0066FD90
x 0x0066FD8C
0x0066FD88

return-адрес

адрес на предишната
стекова рамка
указател на стека

Сега пък е в сила x > y, което води до промяна стойността му на 7, т.е.




памет в стека за gcd

y 0x0066FD90
x 0x0066FD8C
0x0066FD88

return-адрес

адрес на предишната
стекова рамка
указател на стека

Операторът за цикъл завършва изпълнението си. Изпълнението на оператора
return x;
преустановява изпълнението на gcd като връща в main в мястото на прекъсването (return-адреса) стойността 7 на обръщението gcd(a, b). Отделената за gcd стекова рамка се освобождава. Указателят на стека сочи края на стековата рамка на main. Изпълнението на програмата продължава с инициализацията на r. Резултатът от обръщението gcd(14, 21) се записва в отделената за r памет.
Операторът
cout << "gcd{" << a << ", " << b << "}= " << r << "\n";
извежда получения резултат.
Изпълнението на останалите обръщения към gcd се реализира по същия начин. При обръщението към всяко от тях в стека се създава стекова рамка на gcd, а след завършване на обръщението, рамката се освобождава. При достигане до оператора return 0; от main, се освобождава и стековата рамка на main.

Функцията gcd реализира най-простото и “чисто” дефиниране и използване на функции – получава входните си стойности единствено чрез формалните си параметри и връща резултата си чрез оператора return. Забелязваме, че обръщението gcd(a, b) работи с копия на стойностите на а и b, запомнени в x и y, а не със самите a и b. В процеса на изпълнение на тялото на gcd, стойностите на x и y се променят, но това не оказва влияние на стойностите на фактическите параметри a и b.
Такова свързване на формалните с фактическите параметри се нарича свързване по стойност или още предаване на параметрите по стойност. При него фактическите параметри магат да бъдат не само променливи, но и изрази от типове, съвместими с типовете на съответните формални параметри. Обръщението gcd(gcd(a, b), gcd(c, d)) е коректно и намира най-големия общ делител на a, b, c и d.

В редица случаи се налага функцията да получи входа си чрез някои от формалните си параметри и да върне резултат не по обичайния начин – чрез оператора return, а чрез същите или други параметри. Задача 69 дава пример за това.

Задача 69. Да се напише програма, която въвежда стойности на реалните числа a, b, c и d, след което разменя стойностите на a и b и на c и d съответно.

Ако дефинираме функция swapi(double* x, double* y), която разменя стойностите на реалните числа към които сочат указателите x и y, обръщението swapi(&a, &b) ще размени стойностите на a и b, а обръщението swapi(&c, &d) ще размени стойностите на c и d. Програма Zad69.cpp решава задачата. Тя се състои от функциите: swapi и main. Тъ0й като main се обръща към (извиква) swapi, функцията swapi трябва да бъде известна преди функцията main. Затова във файла, съдържащ програмата, първо се поставя swapi, а след това main.

Program Zad69.cpp
#include <iostream.h>
#include <iomanip.h>
void swapi(double* x, double* y)
{double work = *x;
*x = *y;
*y = work;
return;
}
0int main()
{cout << "a, b, c, d= ";
double a, b, c, d;
cin >> a >> b >> c >> d;
cout << setprecision(2) << setiosflags(ios :: fixed);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
swapi(&a, &b);
swapi(&c, &d);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
return 0;
}

Функцията swapi има подобна структура като на gcd. Но и заглавието, и тялото й са по-различни. Типът на swapi е указан чрез запазената дума void. Това означава, че функцията не връща стойност чрез оператора return. Затова в тялото на swapi е пропуснат изразът след return. Формалните параметри x и y са указатели към double, а в тялото се работи със съдържанията на указателите.
Забелязваме също, че обръщенията към swapi в main
swapi(&a, &b);
swapi(&c, &d);
не участват като аргументи на операции, а са оператори.

Изпълнение на програма Zad69.cpp

Дефинициите на функциите main и swapi се записват в областта на паметта, определена за програмния код. Изпълнението на програмата започва с изпълнение на функцията main. Фрагментът
cout << "a, b, c, d= ";
double a, b, c, d;
cin >> a >> b >> c >> d;
cout << setprecision(2) << setiosflags(ios :: fixed);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
дефинира и въвежда стойности за реалните променливи a, b, c и d и ги извежда върху екрана според дефинираното форматиране. Нека за стойности на a, b, c и d са въведени 1.5, 2.75, 3.25 и 8.2 съответно (Фиг. 3).

a a 0x0066FDF0
b 0x0066FDE8
c 0x0066FDE0 стекова рамка
на main
d 0x0066FDD8


указател на стека


main ... 0x00401046






swapi 0x00401019 програмен код





Фиг. 3.
Обръщението
swapi(&a, &b);
се изпълнява на два етапа по следния начин:

а) Свързване на формалните с фактическите параметри
В стека се конструира нова рамка – рамката на swapi. Oтделят се по 4 байта за формалните параметри x и y, в която памет се записват адресите на съответните им фактически параметри, още 4B, в които се записва адресът на swapi(c, d), от където трябва да се продължи изпълнението на main (return-адреса), а също и памет, в която се записва адресът на предишната стекова рамка (в случая на main).


y 0x0066FD38

x 0x0066FD34

return- стекова рамка
адрес (адрес от main) на swapi

адрес на
предишна стекова рамка

указател на стека
б) Изпълнение на тялото на swapi
Изпълнява се като блок. За реалната променлива work се отделят 8 байта в стековата рамка на swapi, в които се записва съдържанието на x, в случая 1.5, т.е.

y 0x0066FD38
x 0x0066FD34

return- 0x0066FD.. стекова рамка
адрес адрес от main на swapi

адрес на предишната
стекова рамка
work 0x0066FD24

указател на стека
Oператорът
*x = *y;
променя съдържанието на x с това на y, а операторът
*y = work;
променя съдържанието на y като го свързва със стойността на work, т.е.


стекова рамка на main


a a 0x0066FDF0
b 0x0066FDE8
c 0x0066FDE0
d 0x0066FDD8




Операторът return; прекъсва работа на на swapi и предава управлението в точката на извикването му в главната функция (return-адреса). Стековата рамка, отделена за swapi се освобождава. Указателят на стека сочи стековата рамка на main. В резултат стойностите на променливите a и b са разменени.
Обръщението swapi(c, d) се изпълнява по аналогичен начин. За нея се генерира нова стекова рамка (на същите адреси), която се освобождава когато изпълнението на swapi завърши.

Функцията swapi получава входните си стойности чрез формалните си параметри и връща резултата си чрез тях. Забелязваме, че обръщението swapi(&a, &b) работи не с копия на стойностите на а и b, а с адресите им. В процеса на изпълнение на тялото се променят стойностите на фактическите параметри a и b при първото обръщение към нея и на c и d – при второто.
Такова свързване на формалните с фактическите параметри се нарича свързване на параметрите по указател или още предаване на параметрите по указател или свързване по адрес. При този вид предаване на параметрите, фактическите параметри задължително са променливи или адреси на променливи.

Освен тези два начина на предаване на параметри, в езика C++ има още един – предаване на параметри по псевдоним. Той е сравнително по-удобен от предаването по указател и се предпочита от програмистите.
Ще го илюстрираме чрез същата задача. Програма Zad69_1.cpp реализира функция swapi, в която предаването на параметрите е по псевдоним.

Program Zad69_1.cpp
#include <iostream.h>
#include <iomanip.h>
void swapi(double& x, double& y)
{double work = x;
x = y;
y = work;
return;
}
int main()
{cout << "a, b, c, d= ";
double a, b, c, d;
cin >> a >> b >> c >> d;
cout << setprecision(2) << setiosflags(ios :: fixed);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
swapi(a, b);
swapi(c, d);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
return 0;
}

Ще проследим изпълнението и на тази модификация.
Изпълнението на програмата започва с изпълнение на функцията main. Фрагментът
cout << "a, b, c, d= ";
double a, b, c, d;
cin >> a >> b >> c >> d;
cout << setprecision(2) << setiosflags(ios :: fixed);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
дефинира и въвежда стойности за реалните променливи a, b, c и d и ги извежда върху екрана според дефинираното форматиране. Нека за стойности на a, b, c и d отново са въведени 1.5, 2.75, 3.25 и 8.2 съответно. След обработката му в стека се конструира стековата рамка на main.

памет на main

x a a 0x0066FDF0
y b 0x0066FDE8
c 0x0066FDE0
d 0x0066FDD8




Обръщението
swapi(a, b);
се изпълнява на два етапа по следния начин:

а) Свързване на формалните с фактическите параметри
За целта се генерира нова стекова рамка – рамката на swapi. Указателят на стека сочи тази рамка. Тъй като формалните параметри x и y са псевдоними на променливите a и b, за тях памет в стековата рамка на swapi не се отделя. Параметърът x “прелита” и се “закачва” за фактическия параметър a и аналогично y “прелита” и се “закачва” за фактическия параметър b от стековата рамка на main. Така всички действия с x и y в swapi се изпълняват с фактическите параметри a и b от main съответно.
б) Изпълнение на тялото на swapi
Изпълнява се като блок. В рамката на swapi, за реалната променлива work се отделят 8 байта, в които се записва стойността на x, в случая 1.5, т.е.

стекова рамка на swapi


return-
адрес (адрес от main) стекова рамка
на swapi
адрес на последната
стекова рамка
work 0x0066FD24
указател на стека

Oператорът
x = y;
присвоява на a стойността на b, а операторът
y = work;
променя стойността на променливата b като й присвоява стойността на work, т.е.

x a a 0x0066FDF0
y b 0x0066FDE8
c 0x0066FDE0
d 0x0066FDD8




Операторът return; прекъсва работа на на swapi и предава управлението на return-адреса от главната функция. Стековата рамка на swapi се освобождава. Указателят на стека сочи стековата рамка на main. В резултат, стойностите на променливите a и b са разменени. Променливите a и b са “освободени от“ x и y. Следва изпълнение на обръщението
swapi(c, d);
което се реализира по същия начин (даже на същите адреси в стека).
Забелязваме, че фактическите параметри, съответстващи на формални параметри-псевдоними са променливи.
Тази реализация на swapi е по-ясна от съответната с указатели. Тялото й реализира размяна на стойностите на две реални променливи без да се налага използването на адреси.
Нека в тялото на main на Zad69_1.cpp преди оператора return; включим фрагмента:
int m, n;
cin >> m >> n;
swapi(m, n);
cout << setw(10) << m << setw(10) << n << "\n";
Някои реализации (visual C++ 6.0) ще сигнализират грешка на третата линия – невъзможност за преобразуване на параметър от int в double &, други обаче ще имат нормално поведение, но няма да разменят стойностите на m и n. Последното е така, тъй като при несъответствие на типа на псевдонима с типа на инициализатора, в стековата рамка на swapi, се създават “временни” променливи x и y, в които се запомнят конвертираните стойности на инициализаторите. Извършва се размяната, но само в стековата рамка на swapi.

При предаване на параметрите по указател или по псевдоним, фактическите параметри са променливи, за разлика от предаването на параметри по стойност, когато фактическите параметри могат да са изрази в общия случай.
Възможно е някои параметри да се подават по стойност, други по псевдоним или указател, а също функцията да връща резултат и чрез оператора return. Примери ще бъдат дадени в следващите части на изложението. Ще бъдат обсъдени също предимствата и недостатъците на всеки от начините за предаване на параметрите.
Ако функция не връща резултат чрез return (типът й е void), се нарича още процедура.
Разгледаните програми се състояха от две функции. По-сериозните приложения съдържат повече функции. Подредбата им може да започва с main, след която в произволен ред да се дефинират останалите функции. В този случай, дефиницията на main трябва да се предшества от декларациите на останалите функции. Декларацията на една функция се състои от заглавието й, следвано от ;. Имената на формалните параметри могат да се пропуснат. Например, програмата от Zad69_1.cpp може да се запише във вида:
Program Zad69_1.cpp
#include <iostream.h>
#include <iomanip.h>
void swapi(double&, double&); // декларация на swapi
int main()
{cout << "a, b, c, d= ";
double a, b, c, d;
cin >> a >> b >> c >> d;
cout << setprecision(2) << setiosflags(ios :: fixed);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
swapi(a, b);
swapi(c, d);
cout << setw(10) << a << setw(10) << b
<< setw(10) << c << setw(10) << d << ‘\n’;
return 0;
}
void swapi(double& x, double& y) // дефиниция на swapi
{double work = x;
x = y;
y = work;
return;
}

ivakavlad
10-20-2010, 23:29
3. Дефиниране на функции

Синтаксис на дефиницията
Дефиницията на функция се състои от две части: заглавие (прототип) и тяло. Синтаксисът й е показан на Фиг. 4.


[<модификатор>][<тип_на_функцията>]<име_на_функция>
(<формални_параметри>)
{<тяло>
}
където
<модификатор>::= inline|static| ...
<тип_на_резултата> ::= <име_на_тип> | <дефиниция_на_тип>
<име_на_функция> ::= <идентификатор>
<формални_параметри> :: <празно> | void |
<параметър> {, <параметър>}
<параметър> ::= <тип>[ & |opc * [const]opc] opc <име_на_параметър>
<тип> ::= <име_на_тип>
<име_на_параметър> ::= <идентификатор>
<тяло> ::= <редица_от_оператори_и_дефин иции>


Фиг. 4.

Модификаторите са спецификатори, които задават препоръка за компилатора (inline), класа памет (extern или static) и др. Ще дадем примери в следващите разглеждания. Ако е пропуснат, подразбира се extern.
Типът на функцията е произволен без масив и функционален, но се допуска да е указател към такива обекти. Ако е пропуснат, подразбира се int.
Името на функцията е произволен идентификатор. Допуска се нееднозначност.
Списъкът от формални параметри (нарича се още сигнатура) може да е празен или void. Например, следната функция извежда текст:
void printtext(void)
{cout << “This is text!!!\n”
cout << “ .....”;
return;
}
В случай, че е непразен, имената на параметрите трябва да са различни. Те заедно с името определят еднозначно функцията. Формалните параметри са: параметри – стойности, параметри – указатели и параметри – псевдоними. Името на параметъра се предшества от тип.
Примери:
int a, int const& b, double& x, int const * y, const int* a
Засега няма да използваме параметри, специфицирани със const.

Тялото на функцията е редица от дефиниции и оператори. Тя описва алгоритъма, реализиращ функцията. Може да съдържа един или повече оператора return.
Операторът return (Фиг. 5) връща резултата на функцията в мястото на извикването.


Синтаксис
return [<израз>]
където
return е запазена дума
<израз> е произволен израз от тип <тип_на_функцията> или съвместим с него. Ако типът на функцията е void, <израз> се пропуска.
Семантика
Пресмята се стойността на <израз>, конвертира се до типа на функцията (ако е възможно) и връщайки получената стойност в мястото на извикването на функцията, прекратява изпълнението й.


Фиг. 5.


Забележка: Ако функцията не е от тип void, тя задължително трябва да върне стойност. Това означава, че операторът return трябва да се намира във всички клонове на тялото. В противен случай, повечето компилатори ще изведат съобщение или предупреждение за грешка. Възможно е обаче функцията да върне случайна стойност, което е лошо. По-добре е функцията да върне някаква безобидна стойност, отколкото случайна.

Функциите могат да се дефинират в произволно място на програмата, но не и в други функции. Преди да се извика една функция, тя трябва да е “позната” на компилатора. Това става, като дефиницията на функцията се постави пред main или когато функцията се дефинира на произволно място в частта за дефиниране на функции, а преди дефинициите на функциите се постави само нейната декларация.

<декларация_на_функция> ::=
[<модификатор>][<тип_на_резултата>]<име_на_функция>
([<формални_параметри>]);

Възможно е имената на параметрите във <формални_параметри> да се пропуснат.

Семантика на дефиницията
Описанието на функция задава параметрите, които носят входа и изхода, типа на резултата, а също и алгоритъма, за реализиране на действието, което функцията дефинира. Параметрите-стойности най-често задават входа на функцията. Параметрите-указатели и псевдоними са входно-изходните параметри за нея. Алгоритъмът се описва в тялото на функцията. Изпълнението на функцията завършва при достигане на края на тялото или след изпълнение на оператор return [<израз>];.

ivakavlad
10-20-2010, 23:29
4. Обръщение към функция

Синтаксис
<обръщение_към_функция> ::=
<име_на_функция>() |
<име_на_функция>(void) |
<име_на_функция>(<фактически_параметри>)
където <фактически_параметри> са толкова на брой, колкото са формалните параметри. Освен по брой, формалните и фактическите параметри трябра да си съответстват по тип, по вид и по смисъл.
Съответствието по тип означава, че типът на i-тия фактически параметър трябва да съвпада (да е съвместим) с типа на i-тия формален параметър. Съответствието по вид се състои в следното: ако формалният параметър е параметър-указател, съответният му фактически параметър задължително е променлива или адрес на променлива, ако е параметър-псевдоним, съответният му фактически параметър задължително е променлива (за реализацията Visual C++, 6.0 от същия тип) и ако е параметър-стойност – съответният му фактически параметър е израз.

Семантика
Обръщението към функция е унарна операция с най-висок приоритет и с операнд - името на функцията. Последното пък е указател със стойност адреса на мястото в паметта където е записан програмният код на функцията. Ако функцията определя процедура, обръщението към нея се оформя като оператор (завършва с ;). Опитът за използването й като израз предизвиква грешка. Ако функцията връща резултат както чрез return, така и чрез някой от формалните си параметри, обръщението към нея може да се разглежда и като оператор, и като израз. И ако функцията връща резултат единствено чрез оператора return, обръщението към нея има единствено смисъла на израз. Използването му като оператор не води до грешка, но не предизвиква видим резултат.

Обръщението към функция предизвиква генериране на нова стекова рамка и се осъществява на следните два етапа:

1. Свързване на формалните с фактическите параметри

За целта първият формален параметър се свързва с първия фактически, вторият формален параметър се свързва с втория фактически и т.н. последният формален параметър се свързва с последния фактически параметър. Свързването се реализира по различни начини в зависимост от вида на формалния параметър.
а) формален параметър – стойност
В този случай се намира стойността на съответния му фактически параметър. В стековата рамка на функцията за формалния параметър се отделя толкова памет, колкото типът му изисква и в нея се откопирва стойността на фактическия параметър.
б) формален параметър – указател
В този случай в стековата рамка на функцията за формалния параметър се отделят 4B, в които се записва стойността на фактическия параметър, която е адрес на променлива. Действията, описани в тялото се изпълняват със съдържанието на формалния параметър - указател. По такъв начин е възможна промяна на стойността на променливата, чийто адреа е предаден като фактически параметър.
в) формален параметър – псевдоним
Формалният параметър-псевдоним се свързва с адреса на фактическия. За него в стековата рамка на функцията памет не се отделя. Той просто “прелита” и се “закачва” за фактическия си параметър. Действията с него се извършват над фактическия параметър.

2. Изпълнение на тялото на функцията

Аналогично е на изпълнението на блок.

При всяко обръщение към функция в програмния стек се включва нов “блок” от данни. В него се съхраняват формалните параметри на функцията, нейните локални променливи, а също и някои “вътрешни” данни като return-адреса и др. Този блок се нарича стекова рамка на функцията.
В дъното на стека е стековата рамка на main. На върха на стека е стековата рамка на функцията, която се обработва в момента. Под нея е стековата рамка на функцията, извикала функцията, обработваща се в момента. Ако изпълнението на една функция завършва, нейната стекова рамка се отстранява от стека.
Видът на стековата рамка зависи от реализацията. С точност до наредба, тя има вида:











Област на идентификаторите в програмата на C++

Идентификаторите означават имена на константи, променливи, формални параметри, функции, класове. Най-общо казано, има три вида области на идентификаторите: глобална, локална и област за клас. Областите се задават неявно – чрез позицията на идентификатора в програмата и явно – чрез декларация. Отново разглеждането ще е непълно, заради пропускането на класовете и явното задаване на област.
Глобални идентификатори
Дефинираните пред всички функции константи и променливи могат да се използват във всички функции на модула, освен ако не е дефиниран локален идентификатор със същото име в някоя функция на модула. Наричат се глобални идентификатори, а областта им – глобална.
Локални идентификатори
Повечето константи и променливи имат локална област. Те са дефинирани вътре във функциите и не са достъпни за кода в другите функции на модула. Областта им се определя според общото правило – започва от мястото на дефинирането и завършва в края на оператора (блока), в който идентификаторът е дефиниран. Формалните параметри на функциите също имат локална видимост. Областта им е тялото на функцията.
В различните области могат да се използват еднакви идентификатори. Ако областта на един идентификатор се съдържа в областта на друг, последният се нарича нелокален за първоначалния. В този случай е в сила правилото: Локалният идентификатор “скрива” нелокалния в областта си.
Областта на функция започва от нейното дефиниране и продължава до края на модула, в който функцията е дефинирана. Ако дефинициите на функциите са предшествани от тяхните декларации, редът на дефиниране на функциите в модула не е от значение – функциите са видими в целия модул. Препоръчва се също дефинирането на заглавен файл с прототипите (декларациите) на функциите.

ivakavlad
10-20-2010, 23:31
5. Масивите като формални параметри

едномерни масиви
Съществуват различни начини за задаване на формални параметри от тип едномерен масив.
а) традиционен
Дефиницията
T a[]
където T е скаларен тип, задава параметър a от тип едномерен масив с базов тип T. Може да се укаже горна граница на масива, но компилаторът я пренебрегва.
Примери:
int a[] - a е параметър от тип масив от цели числа,
int a[10] - еквивалентна е на int a[],
double b[] - b е параметър от тип масив от реални числа,
char c[] - c е параметър от тип масив от символи.
б) чрез указател
Дефиницията
T* p
където T е скаларен тип, задава параметър p от тип указател към тип T. От връзката между масив и указател следва, че тази дефиниция може да се използва и за дефиниране на формален параметър от тип масив.
Примери: Следните дефиниции на формални параметри са еквивалентни на тези от примера по-горе:
int* a - a е параметър от тип указател към int
double* b - b е параметър от тип указател към double.
char* c - c е параметър от тип указател към char.
И в двата случая фактическият параметър се указва с името на едномерен масив от същия тип. Необходимо е също на функцията да се подаде като параметър и размерът на масива.

Задача 70. Да се напишат функции, които въвеждат и извеждат елементите на едномерен масив от цели числа. Като се използват тези функции да се напише програма, която въвежда редица от естествени числа, след което я извежда, а също извежда най-големия общ делител на елементите на редицата.

Програма Zad70.cpp решава задачата.
Program Zad70.cpp
#include <iostream.h>
int gcd(int, int);
void readarr(int, int[]);
void writearr(int, int[]);
int main()
{cout << "n= ";
int n;
cin >> n;
int a[20];
readarr(n, a);
writearr(n, a);
int x = a[0];
for (int i = 1; i <= n-1; i++)
x = gcd(x, a[i]);
cout << "gcd = " << x << '\n';
return 0;
}
int gcd(int a, int b)
{while (a != b)
if (a >= b) a = a-b; else b = b-a;
return a;
}
void readarr(int m, int arr[])
// m е размерността на масива
// arr е едномерен масив
{for (int i = 0; i <= m-1; i++)
{cout << "arr[" << i << "]= ";
cin >> arr[i];
}
}
void writearr(int m, int arr[])
// m е размерността на масива
// arr е едномерен масив
{for (int i = 0; i <= m-1; i++)
cout << "arr[" << i << "]= " << arr[i] << ‘\n’;
}
Изпълнение на програмата
Фрагментът
cout << "n= ";
int n;
cin >> n;
int a[20];
дефинира и въвежда стойност на n, а също дефинира променлива a от тип масив. Нека за n е въведено 5. В резултат е създадена стековата рамка на main. ОП до този момент има вида:

n 0x0066FDF4
a[19] 0x0066FDF0
a[18] 0x0066FDEC
a[17] 0x0066FDE8 стекова рамка
... на main

a[2] 0x0066FDAC
a[1] 0x0066FDA8
a[0] 0x0066FDA4


указател на стека


main ... 0x00401023

gcd 0x0040101E програмен код

readarr 0x00401019

writearr 0x00401014



Обръщението
readarr(n, a);
се реализира като се свързват формалните с фактическите параметри и се изпълни тялото. За целта се формира нова стекова рамка – тази на readarr, в която за формалния параметър arr се отделят 4B, в която памет се откопирва стойността на фактическия параметър a (адресът на a[0]), за m се отделят също 4B, в които се откопирва 5 – стойността на фактическия параметър n. Тялото на функцията се изпълнява като блок. Операторът за цикъл
for (int i = 0; i <= m-1; i++)
{cout << "arr[" << i << "]= ";
cin >> arr[i];
}
е еквивалентен на
for (int i = 0; i <= m-1; i++)
{cout << "arr[" << i << "]= ";
cin >> *(arr + i);
}
и се изпълнява по следния начин: За цялата променлива i се отделят 4B в стековата рамка на readarr. i последователно приема стойностите 0, 1, ..., 4 и за всяка стойност се изпълнява блокът
{cout << "arr[" << i << "]= ";
cin >> *(arr + i);
}
Операторът
cin >> *(arr + i);
въвежда стойност на индексираната променлива a[i], тъй като arr + i е адреса на i-тия елемент на a, а *(arr+i) е неговата стойност. Така във функцията се работи с формалния параметър arr, а в действителност действията се изпълняват с фактическия параметър – едномерния масив a. Функцията readarr работи с масива a, а не с негово копие.







n 0x0066FDF4

a[19] 0x0066FDF0
... стекова рамка
a[0] 0x0066FDA4 на main


arr
m стекова рамка
на readarr
return - адрес

i указател на стека

След достигане на края на функцията изпълнението й завършва и се освобождава стековата й рамка. В резултат, първите 5 елемента на масива a получават текущи стойности. Операторът
writearr(n, a);
се изпълнява по аналогичен начин. Отново се работи с фактическия параметър - масива a, а не с негово копие. В този случай, елементите a[0], a[1], ..., a[n-1] на a само се сканират и извеждат. Не се извършват промени над тях. За да ги защитим от неправомерен достъп, е добре формалният параметър arr да дефинираме като указател към цяла константа, т.е. като const int arr[]. Тогава всеки опит да се променя arr[i] (i = 0, 1, ..., n-1) в writearr ще предизвика грешка.
Фрагментът
int x = a[0];
for (int i = 1; i <= n-1; i++)
x = gcd(x, a[i]);
намира най-големия общ делител на елементите на редицата.
В заглавията на последните две процедури горните граници на индексите могат явно да се укажат. Например
void writearr(int m, int arr[20])
и
void readarr(int m, int arr[20])
са валидни заглавия, но компилаторът не се нуждае от горната граница. Трябват му само скобките [], за да разпознае параметър от тип масив. Може също да се използва второто представяне на формален параметър от тип масив, т.е.
void writearr(int m, int* arr)
и
void readarr(int m, int* arr)
Тези представяния на формалните параметри са напълно еквивлентни.
Забележки:
Функциите readarr и writearr работят с направо с масива a, а не с негови копия. Промените на елементите на масива се запазват след излизане от функцията.
Размерът на масивът не може да се разбере от неговото описание. Затова се налага използването на допълнителния параметър m в списъка от аргументи на функциите. Последното не се отнася за масивите, представляващи символни низове, тъй като те завършват със знака за край на низ ‘\0’.

Задача 71. Да се напише функция len(char* s), която намира дължината на символен низ, а също функция eqstrs(char*, char*), която сравнява два символни низа за лексикографско равно.

Функциите Function Zad71_1 и Function Zad71_2 решават задачата.
Function Zad71_1
int len(char* s)
{int k = 0;
while(*s)
{k++;
s++;}
return k;
}

Function Zad71_2
bool eqstrs(char* str1, char* str2)
{while (*str1 == *str2 && * str1)
{str1++; str2++;}
return *str1 == *str2;
}
Обърнете внимание, че тъй като всеки низ завършва със символа ‘\0’, който се интерпретира като false, изразът *s в оператора за цикъл while на функцията len, ще бъде истина и тялото ще се изпълнява до достигане на края на низа. Аналогична конструкция имаме и при дефиницията на функцията eqstrs.

Задача 72. Да се напише булева функция, която проверява дали цялото число x е елемент на редицата от цели числа a0, a1, ..., an-1.

Функцията Zad72.cpp решава задачата.

Function Zad72
bool search(int n, int a[], int x)
{int i = 0;
while (a[i] != x && i < n-1)i++;
return a[i]==x;
}

Обръщението
search(m, b, y)
проверява дали елементът y се съдържа в редицата b0, b1, ..., bm-1, а
search(k, b + m, y)
проверява дали y се съдържа в подредицата bm, bm+1, ..., bm+k-1.
Еквивалентна дефиниция на тази функция е:
bool search(int n, int* a, int x)
{int i = 0;
while (*(a+i) != x && i < n-1)i++;
return *(a+i)==x;
}

Задача 73. Да се напише функция, която проверява дали редицата от цели числа a0, a1, ..., an-1 е монотонно намаляваща.

Функцията Zad73 решава задачата.

Function Zad73
bool monnam(int n, int a[])
{int i = 0;
while (a[i] >= a[i+1] && i < n-2)i++;
return a[i] >= a[i+1];
}
или
bool monnam(int n, int* a)
{int i = 0;
while (*(a+i) >= *(a+i+1) && i < n-2)i++;
return *(a+i) >= *(a+i+1);
}
Обръщението
monnam(m, b)
проберява дали редицата b0, b1, ..., bm-1 е монотонно намаляваща, а
monnam(k, b + m)
- дали подредицата bm, bm+1, ..., bm+k-1 на b0, b1, ..., bm-1 е монотонно намаляваща.

Задача 74. Да се напише функция, която проверява дали редицата от цели числа a0, a1, ..., an-1 се състои от различни елементи.

Функцията Zad74 решава задачата.

Function Zad74
bool differ(int n, int a[])
{int i = -1;
bool b; int j;
do
{i++; j = i;
do
{j++;
b = a[i] != a[j];
}while (b && j < n-1);
}while (b && i < n-2);
return b;
}

Обръщението
differ(m, b, y)
проберява дали редицата b0, b1, ..., bm-1 се състои от различни елементи, а
differ(k, b + m)
- дали подредицата bm, bm+1, ..., bm+k-1 на b0, b1, ..., bm-1 се състои от различни елементи.

Задача 75. Да се напише програма, която въвежда две числови редици, сортира ги във възходящ ред, слива ги и извежда получената редица.

Програма Zad75.cpp решава задачата. За целта са дефинирани следните функции:
readarr – въвежда числова редица
writearr – извежда числова редица върху екрана
sortarr - сортира във възходящ ред елементите на числова редица
mergearrs - слива числови редици.

Program Zad75.cpp
#include <iostream.h>
#include <iomanip.h>
void writearr(int, double[]);
void readarr(int, double[]);
void sortarr(int, double[]);
void mergearrs(int, double[], int, double[], int&, double[]);

int main()
{cout << "n= ";
int n;
cin >> n;
double a[20];
readarr(n, a);
cout << endl;
writearr(n, a);
cout << endl;
sortarr(n, a);
cout << endl;
writearr(n, a);
cout << "m= ";
int m;
cin >> m;
double b[30];
readarr(m, b);
cout << endl;
writearr(m, b);
cout << endl;
sortarr(m, b);
cout << endl;
writearr(m, b);
cout << endl;
int p;
double c[50];
mergearrs(n, a, m, b, p, c);
writearr(p, c);
return 0;
}
void writearr(int m, double arr[])
{cout << setprecision(3) << setiosflags(ios::fixed);
for (int i = 0; i <= m-1; i++)
cout << setw(10) << arr[i];
cout << "\n";
}
void readarr(int m, double arr[])
{for (int i = 0; i <= m-1; i++)
{cout << "arr[" << i << "]= ";
cin >> arr[i];
}
}
void sortarr(int n, double a[])
{for (int i = 0; i <= n-2; i++)
{int k = i;
double min = a[i];
for (int j = i+1; j <= n-1; j++)
if (a[j] < min)
{min = a[j];
k = j;
}
double x = a[i]; a[i] = a[k]; a[k] = x;
}
}
void mergearrs(int n, double a[], int m, double b[],
int& k, double c[])
{int i = 0, j = 0;
k = -1;
while (i <= n-1 && j <= m-1)
if (a[i] <= b[j])
{k++;
c[k] = a[i];
i++;
}
else
{k++;
c[k] = b[j];
j++;
}
int l;
if (i > n-1)
for (l = j; l <= m-1; l++)
{k++;
c[k] = b[l];
}
else
for (l = i; l <= n-1; l++)
{k++;
c[k] = a[l];
}
k++;
}

многомерни масиви
Когато многомерен масив трябва да е формален параметър на функция, в описанието му трябва да присъстват като константи всички размери с изключение на първият. Например, декларацията
void readarr2(int n, int matr[][20]);
определя matr като двумерен масив (редица от двадесеторки от цели числа). Описанието
int (*matr)[20]
е еквивалентно на
int matr[][20]
Скобките, ограждащи *matr, са задължителни. В противен случай, тъй като [] е с по-висок приоритет от *, int *matr[20] ще се интерпретира като “matr е масив с 20 елемента от тип *int”.

Задача 76. Да се напише програма, която въвежда квадратна матрица от цели числа, след което я извежда като увеличава всеки от елементите на матрицата над главния диагонал с 5 и намалява всеки от елементите под главния диагонал с 5.

Програма Zad76.cpp решава задачата. Тя дефинира функциите:
readarr2 – въвежда квадратна матрица
writearr2 – извежда квадратна матрица
transff - увеличава всеки от елементите на матрицата над главния диагонал с 5 и намалява всеки от елементите под главния диагонал с 5.

Program Zad76.cpp
#include <iostream.h>
#include <iomanip.h>
void readarr2(int, int[][10]);
void writearr2(int, int[][10]);
void transff(int, int[][10]);
int main()
{int a[10][10];
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1 || n > 10)
{cout << "Incorrect input! \n";
return 1;
}
readarr2(n, a);
cout << '\n';
writearr2(n, a);
cout << '\n';
transff(n, a);
writearr2(n, a);
return 0;
}
void readarr2(int n, int arr[][10])
{for (int i = 0; i <= n-1; i++)
for (int j = 0; j <= n-1; j++)
cin >> arr[i][j];
}
void writearr2(int n, int arr[][10])
{for (int i = 0; i <= n-1; i++)
{for (int j = 0; j <= n-1; j++)
cout << setw(5) << arr[i][j];
cout << "\n";
}
}
void transff(int n, int arr[][10])
{int i, j;
for (i = 1; i <= n-1; i++)
for (j = 0; j <= i-1; j++)
arr[i][j] = arr[i][j] - 5;
for(i = 0; i <= n-2; i++)
for(j = i+1; j <= n-1; j++)
arr[i][j] = arr[i][j] + 5;
}
Обръщението
transff(k, a + m);

ще извърши същото действие над квадратната подматрица на дадената матрица:


Задача 77. Да се напише програма, която въвежда редица от думи не по-дълги от 14 знака и дума, също не по-дълга от 14 знака. Програмата да проверява дали думата се среща в редицата. За целта да се оформят подходящи функции.

Програма Zad77.cpp решава задачата. В нея са дефинирани функциите:
void readarrstr(int n, char s[][15]); - въвежда редица от n думи,
bool search(int n, char s[][15], char* x); - търси думата x в редицата s от n думи. За целта използва помощната функция
bool eqstrs(char* str1, char* str2);
от Задача 71.

Program Zad77.cpp
#include <iostream.h>
#include <string.h>
void readarrstr(int, char [][15]);
bool eqstrs(char*, char*);
bool search(int, char [][15], char*);
int main()
{char a[20][15];
cout << "n= ";
int n;
cin >> n;
readarrstr(n, a);
cout << "word: ";
char word[15];
cin >> word;
if (search(n, a, word)) cout << "yes \n";
else cout << "no \n";
return 0;
}
void readarrstr(int n, char s[][15])
{for(int i = 0; i <= n-1; i++)
{cout << "s[" << i << "]= ";
cin >> s[i];
}
}
bool eqstrs(char* str1, char* str2)
{while (*str1 && *str1 == *str2)
{str1++; str2++;}
if(*str1 != *str2) return false;
else return true;
}
bool search(int n, char s[][15], char* x)
{int i = 0;
while (!eqstrs(s[i], x) && i < n-1) i++;
return eqstrs(s[i], x);
}

Задача 78. Да се напише програма, която умножава две матрици.

Програма Zad78.cpp решава задачата. Тя дефинира следните функции:
readarr2 – въвежда матрица,
writearr2 – извежда матрица,
multmatr – умножава матрици.

Program Zad78.cpp
#include <iostream.h>
#include <iomanip.h>
void readarr2(int n, int m, double [][30]);
void writearr2(int n, int m, double [][30]);
void multmatr(int, int, int, double [][30],
double [][30], double [][30]);

int main()
{double a[10][30], b[20][30], c[10][30];
cout << "n= ";
int n;
cin >> n;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (n < 1 || n > 10)
{cout << "Incorrect input! \n";
return 1;
}
cout << "m= ";
int m;
cin >> m;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (m < 1 || m > 30)
{cout << "Incorrect input! \n";
return 1;
}
cout << "k= ";
int k;
cin >> k;
if (!cin)
{cout << "Error, Bad input! \n";
return 1;
}
if (k < 1 || k > 30)
{cout << "Incorrect input! \n";
return 1;
}
readarr2(n, m, a);
writearr2(n, m, a);
cout << "\n";
readarr2(m, k, b);
cout << "\n";
writearr2(m, k, b);
cout << "\n";
multmatr(n, m, k, a, b, c);
writearr2(n, k, c);
return 0;
}
void readarr2(int n, int m, double arr [][30])
{for (int i = 0; i <= n-1; i++)
for (int j = 0; j <= m-1; j++)
cin >> arr[i][j];
return;
}
void writearr2(int n, int m, double arr[][30])
{cout << setprecision(3) << setiosflags(ios::fixed);
for (int i = 0; i <= n-1; i++)
{for (int j = 0; j <= m-1; j++)
cout << setw(10) << arr[i][j];
cout << "\n";
}
return;
}
void multmatr(int n, int m, int k, double a[][30],
double b[][30], double c[][30])
{for (int i = 0; i <= n-1; i++)
for (int j = 0; j <= m-1; j++)
{c[i][j] = 0;
for (int p = 0; p <= m-1; p++)
c[i][j] += a[i][p] * b[p][j];

//c[I][j] = c[I][j]+a[I][j]* b[p][j]
}
}

ivakavlad
10-20-2010, 23:32
6. Масивите като върнати оценки

Въпреки, че масивите могат да са параметри на функции, функциите не могат да са от тип масив. Възможно е обаче да са от тип указател. Това позвлява дефинирането на функции, които връщват масиви.
Пример: В следващата програма е дефинирана функцията readarr, която въвежда стойности на едномерен масив. Тя връща резултат не само чрез променливата от тип масив arr, но и чрез оператора return. Това позволява обръщенията към нея да са както като оператор, така и като израз.

#include <iostream.h>
void writearr(int, int[]);
int* readarr(int, int[]);
int main()
{cout << "n= ";
int n;
cin >> n;
int a[20];
int* p = readarr(n, a);
writearr(n, p);
cout << endl;
return 0;
}
void writearr(int m, int arr[])
{for (int i = 0; i <= m-1; i++)
cout << "arr[" << i << "]= " << arr[i] << '\n';
return;
}
int* readarr(int m, int arr[])
{for (int i = 0; i <= m-1; i++)
{cout << "arr[" << i << "]= ";
cin >> arr[i];
}
return arr;
}

Въпрос: Допустима ли е конструкцията: readarr(n, a)[i], където i е цяло число от 0 до n-1? Ако това е така, какъв е резултатът от изпълнението му?

Задача 79. Да се напише функция, която намира и връща като резултат конкатенацията на два низа. Функцията да променя първия си аргумент като в резултат той да съдържа конкатенацията на низовете.
Програма Zad79.cpp решава задачата.

Program Zad79.cpp
#include <iostream.h>
int len(char*);
char *cat(char*, char*);
int main()
{char s1[100];
cout << "s1= ";
cin >> s1;
cout << "s2= ";
char s2[100];
cin >> s2;
cout << cat(s1, s2) << " " << s1 << '\n';
return 0;
}
int len(char* s)
{int k = 0;
while (*s)
{k++; s++;
}
return k;
}
char* cat(char *s1, char *s2)
{int i = len(s1);
while (*s2)
{s1[i] = *s2;
i++;
s2++;
}
s1[i] = '\0';
return s1;
}



Задачи

Задача 1. Въпреки многото й недостатъци, следващата програма е доста поучителна. Тя дефинирана функцията readarr, която има за формален параметър броя на елементите на масива и връща едномерен масив, определен чрез указател към първия му елемент.
#include <iostream.h>
int a[20];
void writearr(int, int[]);
int* readarr(int);
int main()
{cout << "n= ";
int n;
cin >> n;
int* p = readarr(n);
writearr(n, p);
cout << ‘\n’;
return 0;
}
void writearr(int m, int arr[])
{for (int i = 0; i <= m-1; i++)
cout << "arr[" << i << "]= " << arr[i] << '\n';
}
int* readarr(int m)
{for (int i = 0; i <= m-1; i++)
{cout << "a[" << i << "]= ";
cin >> a[i];
}
return a;
}

извършете експерименти с тази програма.

Задача 2. Да се напише програма, която въвежда полиномите:



и реалната променлива x и намира и извежда стойностите на полиномите в x.
Задача 3. Да се напише програма, която въвежда стойности на редиците:
a0, a1, ..., an-1,
b0, b1, ..., bm-1,
c0, c1, ..., cp-1

и намира и извежда AR1, AR2, BR1, BR2, CR1 и CR2, където за дадена редица x0, x1, ..., xk-1

Задача 4. Да се напише функция, която намира разстоянието между две точки в равнината, зададени чрез координатите си (x1, y1) и (x2, y2). Като се използва тази функция да се напише програма, която чете координатите на n точки (n ≥ 1) от равнината и намира и извежда разстоянието между всеки две от тях.
Задача 5. да се напише функция, която връща стойност true, ако a, b и c са страни на триъгълник и false - в противен случай. Като се използва тази функция, да се напише програма, която въвежда стойности на елементите на матрицата A3xn и определя кои от тройките (a[0][i], a[1][i], a[2][i]), i = 0, 1, ..., n-1 могат да служат за страни на триъгълник.
Задача 6. Да се напише функция, която връща стойност true ако редицата от цели числа x0, x1, ..., xk-1 има поне два последователни нулеви елемента. Като се използва тази функция, да се напише програма, която намира и извежда номерата на редовете на матрицата A [n x n], от цели числа, които имат поне два последователни нулеви елемента.

Задача 7. Да се напише функция, която намира сумата на два полинома. Като се използва тази функция, да се напише програма, която намира сумата на всеки два от полиномите:



Задача 8. Даден е триъгълник със страни a, b и c. Да се напише програма, която намира медианите на триъгълник, страните на който са медианите на дадения триъгълник.

Упътване: Медианата към страната a на триъгълника е равна на

Задача 9. Дадени са координатите на върховете на n триъгълника. Да се напише програма, която определя, кой от триъгълниците е с по-голямо лице.

Задача 10. Дадени са естественото число p > 1 и реалните квадратни матрици с размерности n x n - A, B и C. Да се напише програма, която намира матрицата




Допълнителна литература

Ст. Липман, Езикът C++ в примери, “КОЛХИДА ТРЕЙД” КООП, С. 1993.
2. И. Момчев, К. Чакъров, Програмиране III, C и C++, ТУ, С. 1996.
3. Д. Луис, C/C++ бърз справочник, ИнфоДАР, С. 1998.
4. B. Stroustrup, C++ Programming Language. Third Edition, Addison – Wesley, 1997.

ivakavlad
10-20-2010, 23:49
Глава 8

Програмиране от по-висок ред в C++


Функция, някои формални параметри на която са функции, се нарича функция от по-висок ред.
В езика C++ е възможно формален параметър на функция да е указател към функция, а също е възможно резултатът от изпълнението на функция да е указател към функция. Това позволява да се реализират функции от по-висок ред, а също такива които връщат функция.

ivakavlad
10-20-2010, 23:51
1.Указател към функция

Името на функция е константен указател, сочещ към първата машинна инструкция от изпълнимия й машинен код. В езика C++ е възможно да се дефинират променливи, които са указатели към функции (Фиг. 1).


<дефиниция_на_променлива_ук зател_към_функция> ::=
<тип_на_функция>(*<указател_към_функция>)(<формални_параметри>)
[= <име_на_функция>];
където
- <указател_към_функция> е идентификатор;
- <име_на_функция> e идентификатор, означаващ име на функция от тип <тип_на_функция> и параметри - <формални_параметри>;
- <тип_на_функция> и <формални_параметри> са аналогични на съответните от заглавието на дефиниция функция. Имената на параметрите могат да се пропуснат.


Фиг. 1.

Забележка: Скобите, ограждащи *<указател_към_функция>, са задължителни. В противен случай дефиницията ще се изтълкува от компилатора като декларация на функция с име < указател_към_функция>, с параметри - <формални_параметри> и тип – указател към <тип_на_функция>.
В резултат на дефиницията на променлива от тип указател към функция, за променливата се отделят 4B ОП, която е с неопределена стойност, ако дефиницията е без инициализация, и съдържа адреса на първата машинна команда от изпълнимия код на функцията, чрез която е направена инициализацията, ако дефиницията е с инициализация.

Примери:
double (*p)(double, double);
е дефиниция на променлива p от тип указател към функция от тип double с два аргумента също от тип double. В резултат за p се отделят 4B ОП, които са с неопределена стойност.
int (*q)(int, int*);
дефинира променлива q от тип указател към функция от тип int, с два аргумента, единият от които цял, а другият – указател към int. За q се отделят 4B ОП, които са с неопределена стойност.
3. Нека са дефинирани следните функции за сортиране на числови редици:
void bubblesort(int, int*); // метод на мехурчето
void mergesort(int, int*); // сортиране чрез сливане
void heapsort(int, int*); // пирамидално сортиране
Променливата r може да е указател към тези функции ако е дефинирана по следния начин:
void (*r)(int, int*);
r не може да е указател към функциите:
int f1(int, int*);
int f2(int, int*);
Указател към последните може да е променливата s, където:
int (*s)(int, int*);
Горните дефиниции на p, q, r и s са без инициализации.
Дефиниците на променливите x и y
void (*x)(int, int*) = bubbesort;
int (*y)(int, int*) = f2;
са с инициализация. За всяка от тях се отделят 4B ОП, в която памет се записват адресите на първите команди на изпълнимите кодове на bubblesort и f2 съответно.
Присвояването се извършва по стандартния начин.
Пример:
r = mergesort;
x = heapsort;
След инициализация на променлива от тип указател към функция, чрез променливата може да се осъществи обръщение към конкретна функция. Така се предоставя ефективен способ за предаване на управлението към потребителски и библиотечни функции.
Обръщението към функция освен директно може да се осъществява и индиректно – чрез указател към нея.
Пример:
void (*r)(int, int*) = bubblesort;
bubblesort(n, a); // директно обръщение
(*r)(n, a); // индиректно обръщение, чрез r.

Забележка: Някой компилатори, в това число и на Visual C++ 6.0, допускат извикването на функция чрез указател да се осъществява и само чрез името на указателя.
Пример:
void (*r)(int, int*) = bubblesort;
r(n, a); // индиректно обръщение към bubblessort,
// чрез r.

ivakavlad
10-20-2010, 23:51
2. Функциите като формални параметри

Указател към функция може да е формален параметър на функция. Ще илюстрираме тази възможност с няколко примери.


Задача 80. Да се напише функция, която реализира математическата абстракция:

където a и b са дадени реални числа (a ≤ b), f е реална едноаргументна функция, задаваща терма, а next е реална едноаргументна функция, задаваща стъпката за промяна на управляващия параметър на сумата.

За да решим задачата ще предложим няколко частни решения.
а) Да се дефинира функция, която намира стойността на сумата:
sin(a) + sin(a+1) + sin(a+2) + ... + sin(b),
където a и b са дадени реални числа. Функцията sum_sin решава задачата.

double sum_sin(double a, double b)
{double s = 0;
for (double i = a; i <= b + 1E-14; i = i + 1)
s = s + sin(i);
return s;
}

б) Да се дефинира функция, която намира стойността на сумата:
cos(a) + cos(a + 0.2) + cos(a + 0.4) + ... + sin(b)
където a и b са дадени реални числа. Функцията sum_cos решава задачата.

double sum_cos(double a, double b)
{double s = 0;
for (double i = a; i <= b + 1e-14; i = i + 0.2)
s = s + cos(i);
return s;
}
Забелязваме, че тези две функции се “приличат”. Написани са по следния общ шаблон:

double <name>(double a, double b)
{double s = 0;
for (double i = a; i <= b + 1e-14; i = <next>(i))
s = s + <f>(i);
return s;
}

Елементите, по които функциите sum_sin и sum_cos се различават са означени с <...> в шаблона. Това са две функции – f, означаваща терма и next - стъпката на сумата. Като използваме възможността формален параметър на функция да е указател към функция, можем да изнесем <f> и <next> като формални параметри на функцията и да обобщим тези частни случаи. Така стигаме до функцията sum:

Function Zad80.cpp
double sum(double a, double b, double (*f)(double),
double (*next)(double))
{double s = 0;
for (double i = a; i <= b + 1е-14; i = next(i))
s = s + f(i);
return s;
}

Обръщенията към sum:
sum(a, b, sin, next1)
sum(a, b, cos, next2)
където
int next1(double x)
{return x + 1;
}
int next2(double x)
{return x + 0.2;
}
реализират горните частни случаи.
sum е функция от по-висок ред. В нея третият и четвъртият параметри са указатели към функции.
Като използваме sum, може да дефинираме sum_sin и sum_cos по следния начин:

double sum_sin(double a, double b)
{return sum(a, b, sin, next1);
}
double sum_cos(double a, double b)
{return sum(a, b, cos, next2);
}


Задача 81. Да се напише функция, която реализира математическата абстракция:

където a и b са реални числа, f е реална едноаргументна функция, задаваща терма, а next - реална едноаргументна функция, задаваща стъпката за промяна на управляващия параметър на произведението. Да се включи тази функция в програма и се намерят:
tg(1) * tg(1.5) * tg(2) * tg(2.5) * tg(3)
и
arctg(1) * arctg(1.1) * arctg(1.2) * arctg(1.3).

Програма Zad81.cpp решава задачата.

Program Zad81.cpp
#include <iostream.h>
#include <math.h>
double prod(double, double, double (*)(double),
double (*) (double));
double next1(double);
double next2(double);
int main()
{cout << prod(1, 3, tan, next1) << '\n';
cout << prod(1, 1.3, atan, next2) << '\n';
return 0;
}
double prod(double a, double b, double (*f)(double),
double (*next)(double))
{double s = 1.0;
for (double i = a; i <= b + 1e-14; i = next(i))
s = s * f(i);
return s;
}
double next1(double x)
{return x + 0.5;
}
double next2(double x)
{return x + 0.1;
}

В тази програма е дефинирана функцията от по-висок ред prod, реализираща исканата абстракция. В нея третият и четвъртият параметри са указатели към функции. В главната програма са направени две обръщения към нея
prod(1, 3, tan, next1)
и
prod(1, 1.3, atan, next2),
намиращи търсените произведения.
Забелязваме, че функциите sum и prod си “приличат”. Написани са по следния общ шаблон.

double <name>(double a, double b, double (*f)(double),
double (*next)(double))
{double s = <null_val>;
for (double i = a; i <= b + 1е-14; i = next(i))
s = s <op> f(i);
return s;
}

И в този случай, елементите, по които sum и prod се различават са оградени с <...>. Това са операцията op и нулата на операцията - null_val. Отново ще изнесем op и null_val като формални параметри на функцията. Тъй като op е бинарна инфиксна операция, а не име на функция, ще дефинираме помощна реална функция с име op, с два реални параметъра и връщаща резултата от прилагането на операцията op към аргументите на функцията op. Така получаваме още едно обобщение на горните абстракции – функцията от по-висок ред accumulate (Задача 82).


Задача 82. Да се напише програма, която реализира следната математическа абстракция:

където с

ivakavlad
10-20-2010, 23:52
3. Функциите като върнати оценки

Функция може да върне като резултат указател към друга функция. Например, декларацията
int (*fun(int, int))(int*, int);
определя функцията fun с два цели аргумента и връщаща указател към функция от тип
int (*)(int*, int).
Ако зададем име на този тип чрез typedef, този запис може да се опрости:
typedef int (*fun-point)(int*, int);
fun-point fun(int, int);


Задача 84. Да се напише програма, която по зададено реално число x и символ (a, b, c или d) избира за изпълнение функция, определена чрез зависимостта:

Програма Zad84.cpp решава задачата.

Program Zad84.cpp
#include <iostream.h>
#include <math.h>
typedef double (*f_type)(double);
f_type table(char ch)
{switch(ch)
{case 'a': return sin; break;
case 'b': return cos; break;
case 'c': return exp; break;
case 'd': return log; break;
default: cout << "Error \n"; return tan;
}}
int main()
{char ch;
cout << "ch= ";
cin >> ch;
if (ch < 'a' || ch >'d') cout << "Incorrect input! \n";
else
{double x;
cout << "x= ";
cin >> x;
cout << table(ch)(x) << '\n';
}
return 0;
}

Илюстрираните в тази част възможности на езика C++ показват, че данните от тип функции съществено не се отличават от другите видове данни. Това показва високата степен на унифицираност в езика и води до увеличаване на изразителната му сила.


Задачи


Задача 1. Като използвате функцията от по-висок ред sum, намерете:



Задача 2. Като използвате функцията от по-висок ред prod, намерете:
а) xn, където x е дадено реално, а n – дадено естествено число.
б) n!, където n е дадено естествено число.
в) броят на вариациите от n елемента от k-ти клас (n и k са дадени естествени числа, 0 ≤ k ≤ n).
г) броят на комбинациите от n елемента от k-ти клас (n и k са дадени естествени числа, 0 ≤ k ≤ n).




Допълнителна литература

1. Ст. Липман, Езикът C++ в примери, “КОЛХИДА ТРЕЙД” КООП, С. 1993.
2. Д. Луис, C/C++ бърз справочник, ИНФОДАР, С. 1998.
3. М. Тодорова, Езици за функционално и логическо програмиране – функционално програмиране, СОФТЕХ, С., 1998.
4. B. Stroustrup, C++ Programming Language. Third Edition, Addison – Wesley, 1997.

ivakavlad
10-21-2010, 00:12
за днес стига толкова утре продължавам със следващите глави :)

Foreverbg
10-21-2010, 09:42
Защо си мисля, че това си го взел от един учебник?

capslock
10-21-2010, 16:29
Защо си мисля, че това си го взел от един учебник? защото можеби и така

GregoryHouse
10-21-2010, 17:36
Човека си играе да ви го дигитализира... Евала за усилията!

Foreverbg
10-21-2010, 19:16
Човека си играе да ви го дигитализира... Евала за усилията!
Какво дигитализира? :lol: Абсолютно същото го имам на компа. :D Да не говорим, че човек,който никога не се е занимавал с програмиране нищо няма да разбере.

GregoryHouse
10-22-2010, 03:31
Човека си играе да ви го дигитализира... Евала за усилията!
Какво дигитализира? :lol: Абсолютно същото го имам на компа. :D Да не говорим, че човек,който никога не се е занимавал с програмиране нищо няма да разбере.

Не знаех, че C++ е за хора които не се занимават с програмиране...Ко лаеш ся? Ко ти пречи човека да си пише и да си прави тема, да си влага усилия някакви? Българска му работа...

Foreverbg
10-22-2010, 09:02
Не знаех, че C++ е за хора които не се занимават с програмиране
:lol: :lol:
А това къде съм го написал? Да си пише бе мене не ме бърка. Въпроса е, че само дава copy и paste и прави теми с 1000 страници вместо да качи файловете на 1 страница. И такива като тебе после му свалят звезди за труда.