Задачки с решения ;)

“Примерна” задача №8.

#include <iostream>
using namespace std;

#define M 100
typedef float ElementType;

struct Queue
{
    int f;  //номер на фиктивен елемент
			//преди началото на опашката.
    int r;  //номер на eлементa в края на опашката.

	ElementType QueueArray[M];
};

void enqueue(Queue *q, ElementType x)
{
    if ((q->r + 1) % M == q->f)
    {
        cout << "StackOverflowException: "
			<< "Index is greater than the maximum size!" << endl;
        exit(1);
    }

	q->r = (q->r + 1) % M;
    q->QueueArray[q->r] = x;
}

int main()
{
    Queue queue;
    queue.f = 0;
	queue.r = 0;
    cout << queue.r << endl;

	for (int index = 0; index < M; index++)
    {
		enqueue(&queue, index);
        cout << queue.r << endl;
    }

    return 0;
}

Output-а на програмата:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
StackOverflowException: Index is greater than the maximum size!

Забележка: Не забравяйте за кирилизацията.

Реалният фрагмент от код.

#include <iostream>
using namespace std;

#define M 100
typedef float ElementType;

struct Queue
{
    int f;  //номер на фиктивен елемент
			//преди началото на опашката.
    int r;  //номер на eлементa в края на опашката.

	ElementType QueueArray[M];
};

void enqueue(Queue *q, ElementType x)
{
    if ((q->r + 1) % M == q->f)
    {
        cout << "ПРЕПЪЛВАНЕ" << endl;
        exit(1);
    }

	...
    q->QueueArray[q->r] = x;
}

int main()
{
    Queue queue;
    queue.f = 0;
	queue.r = 0;
    cout << queue.r << endl;

	for (int index = 0; index < M; index++)
    {
		enqueue(&queue, index);
        cout << queue.r << endl;
    }

    return 0;
}

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

Не се плашете от първия ред на фрагмента от код, но там дефинираме константа с име М и стойност 100, която ще служи за максимален размер на опашката.
Имаме структура Queue, която има данни, служещи за следене на началото и края на опашката. Масивът от числа с плаваща запетая е, за да пазим последователността от елементи, в него се записват самите стойностите на данните, които желаем да добавим в опашката, за разлика от променливите f и r.

Опашката е структура от данни, която добавя данните си отзад, а ги изтрива в обратен ред. Първият влязъл е първият излязъл.

Операциите за въвеждане и изваждане на елементи от него са: enqueue и dequeue.

Функцията enqueue:
Първата работа е да проверим дали можем да добавим елемент. По принцип същото нещо може да се ползва и от опашката, но ще спазим “добрата концепция” на Павлов. Ако се окаже, че остатъка от деление даде нулевия елемент, се извежда съобщение за грешка и програмата спира. Ако ли не увеличаваме индекса и след това се добавя конкретната стойност на този елемент.

« 1 2 3 4 5 6 7 8 »

Задачки с решения ;)

“Примерна” задача №7.

#include <iostream>
using namespace std;

#define M 100

struct Stack
{
    int t;  //номер на елемента във върха на стека
    float stack_array[M];
};

void push(Stack *s, float x)
{
    s->t++;
    if (s->t > M)
    {
        cout << "StackOverflowException: "
			<< "Index is greater than the maximum size!" << endl;
        exit(1);
    }

    s->stack_array[s->t - 1] = x;
}

float pop(Stack *s)
{
    if (s->t == 0)
    {
        cout << "OutOfRangeException: "
			<< "Index is less than the minimum size!" << endl;
        exit(1);
    }

    s->t--;

    return s->stack_array[s->t];
}

int main()
{
    Stack stack;
    stack.t = 0;
    cout << stack.t << endl;

    //Ако index расте до <= M,
	//ще бъде предизвикана StackOverflowException
    for (int index = 0; index < M; index++)
    {
        push(&stack, index + index * 0.1f);
        cout << stack.t << endl;
    }

    for (int index = 0; index <= M; index++)
    {
        pop(&stack);
        cout << stack.t << endl;
    }

    return 0;
}

Output-а на програмата:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
99
98
97
96
95
94
93
92
91
90
89
88
87
86
85
84
83
82
81
80
79
78
77
76
75
74
73
72
71
70
69
68
67
66
65
64
63
62
61
60
59
58
57
56
55
54
53
52
51
50
49
48
47
46
45
44
43
42
41
40
39
38
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
OutOfRangeException: Index is less than the minimum size!

Забележка: Не забравяйте за кирилизацията.

Реалният фрагмент от код.

#include <iostream>
using namespace std;

#define M 100

struct Stack
{
	int t;	//номер на елемента във върха на стека
	float stack_array[M];
};

void push(Stack *s, float x)
{
	...

	if (s->t > M)
	{
		cout << "\nПРЕПЪЛВАНЕ\n";
		exit(1);
	}

	s->stack_array[s->t - 1] = x;
}

float pop(Stack *s)
{
	...

	s->t--;

	return s->stack_array[s->t];
}

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

Не се плашете от първия ред на фрагмента от код, но там дефинираме константа с име М и стойност 100, която ще служи за максимален размер на стека.
Имаме структура Stack, която има данна, служеща за отчитане на броя на елементите. Масивът от числа с плаваща запетая е, за да пазим последователността от елементи, в него се записват самите стойностите на данните, които желаем да добавим в стека, за разлика от променливата t.

Стекът е структура от данни, която добавя данните си отпред или най-отгоре, а ги изтрива в обратен ред. Последният влязъл е първият излязъл.

Операциите за въвеждане и изваждане на елементи от него са: push и pop.

Сега към функцията push:
Първата работа като добавим елемент е да увеличим броя на елементите, а понеже t ни служи точно като брояч, за това на мястото на многоточието поставяме операцията t++;.

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

Функция pop:
Първата работа на мястото на многоточието трябва да проверим дали не сме достигнали минимума на стека. Ако е достигнат извежда съобщение за грешка, че сме излезли извън големината на стека. След това трябва да прекъснем програмата. Останалото е аналогично на по-горе. Намаляваме броя на елементите и връщаме новия последен елемент.

« 1 2 3 4 5 6 7 8 »

Задачки с решения ;)

“Примерна” задача №6.

#include <iostream>
using namespace std;

struct Student
{
	long nomer;	//Факултетен номер
	float sr_uspeh;	//Среден успех
	Student *next;
};

//first - указател към начало на свързания списък
//key - ключ, факултетния номер се разглежда като ключ
//value - стойност, която ще актуализира средния успех
void search(Student *first, long key, float value)
{
	Student *ptr = first;
	
	while (ptr)
	{
		if (ptr->nomer == key)
		{
			break;
		}
		else
		{
			ptr = ptr -> next;
		}
	}

	if (ptr)
	{
		cout << "Студент с факултетен № " << first->nomer 
		   << " и среден успех: " << first->sr_uspeh << endl;

		ptr->sr_uspeh = value;

		cout << "Студент с факултетен № " << first->nomer 
		   << " и среден успех: " << first->sr_uspeh << endl;
	}
	else
	{
		cout << "\nНяма елемент с указания ключ.";
	}
}

int main()
{
	Student student1, student2;
	student1.nomer = 12345;
	student1.sr_uspeh = 4.78;
	student1.next = &student2;
	student2.nomer = 54321;
	student2.sr_uspeh = 5.99;
	student2.next = NULL;

	search(&student1, 12345, 5.12);
	search(&student1, 12345, 4);
	search(&student2, 54321, 5.00);
	search(&student2, 353253, 5.00);

	return 0;
}

Output-а на програмата:

Студент с факултетен № 12345 със среден успех: 4.78
Студент с факултетен № 12345 със среден успех: 5.12
Студент с факултетен № 12345 със среден успех: 5.12
Студент с факултетен № 12345 със среден успех: 4
Студент с факултетен № 54321 със среден успех: 5.99
Студент с факултетен № 54321 със среден успех: 5

Няма елемент с указания ключ.

Забележка: Не забравяйте за кирилизацията.

Реалният фрагмент от код.

struct Student
{
	long nomer;		//Факултетен номер
	float sr_uspeh;	//Среден успех
	Student *next;
};

//first - указател към начало на свързания списък
//key - ключ, факултетния номер се разглежда като ключ
//value - стойност, която ще актуализира средния успех
void search(Student *first, long key, float value)
{
	Student *ptr = first;
	
	while (ptr)
	{
		if (ptr->nomer == key)
		{
			..........
		}
		else
		{
			..........
		}
	}

	if (ptr)
	{
		ptr->sr_uspeh = value;
	}
	else
	{
		cout << "\nНяма елемент с указания ключ.";
	}
}

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

Няма да обяснявам работата на програмата, тя е просто за визуализация, че всичко е ОК и как сработва. Сега директно към фрагмента от код. След като намерим даден елемент е хубаво да спрем да го търсим, затова слагаме break; и излизаме от цикъла, ако не го намерим трябва да го насочим към следващия и цикъла ще свърши или като го намери или като указателя посочи NULL.

« 1 2 3 4 5 6 7 8 »

Задачки с решения ;)

“Примерна” задача №5.

#include <iostream>
using namespace std;

struct CN
{
	char character;
	CN *next;
} *first, cn;

void list(char *word)
{
	cn.character = *word;
	cn.next = first;
	first = new CN;
	*first = cn;

	if (*word)
	{
		list(word + 1);
	}
}

void print(CN *first)
{
	if (!first)
	{
		cout << "\n result = ";
	}
	else
	{
		print(first->next);
		cout << first->character;
	}
}

int main()
{
	first = NULL;
	list("droll");
	print(first->next->next);

	return 0;
}

Output-а на програмата:


 result = drol

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

Първо се нулира обекта first от структурата CN. След това извикваме функцията list с параметър низа “droll”. Във функцията list, чрез пряка рекурсия, се създава свързан списък, като елементите са от символите от подадения низ.

Малко по-детайлно обяснение на функцията list. Ролята на обекта cn от CN е само да взима първия символ от низа и да задава предходния елемент. first от своя страна взима вече готовия елемент, копира го и го добавя като нов елемент на структурата. След това проверката ни служи за дъно на рекурсията, тъй като все някога свършва низа. Ако низа не е свършил извикваме отново функцията със същия низ, но без първия символ. След като свърши низа последния елемент е със следните параметри: first->character = ‘\ 0’ и first->next = ‘l’.

Сега преминаваме към функцията print. В частния случай ние извикваме
(first->next)->next, ще се опитам да го обясня по-разбираемо:
first->character = ‘\ 0’
first->next = ‘l’, като това ще го кръстим previous (previous->character = ‘l’ и previous->next = ‘l’)
first->next->next = previous->next = ‘l’
Реално ние като извикваме първоначално функцията print ще и подадем елемента ‘l’, а не ‘\ 0’, тъй като сме се преместили 2 елемента наляво (нищо, че операцията се казва next, явно Павлов вирее в арабския свят – там се пише отдясно наляво).
Влизаме във функцията и проверяваме дали текущия елемент не е различен от NULL, ако не е извикваме, чрез пряка рекурсия, функцията с параметър предходния на текущия елемент (first->next). Това го правим докато не достигнем предходния елемент на ‘d’, който NULL и тогава извеждаме “\n result = “, а след това се връщаме обратно по пътя на рекурсията и печатаме символите от ‘d’ до първото ‘l’, защото първоначално извикваме функцията с този параметър.

Забележка: Защо достъпваме елементите на first със “->”, а не cn с “.”, защото first е указател към обект, а cn e самият обект.

« 1 2 3 4 5 6 7 8 »

Задачки с решения ;)

“Примерна” задача №4.

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int day = 1, int month = 1, int year = 1)
	{
		this->day = day;
		this->month = month;
		this->year = year;
	}

	void Print()
	{
		cout << "Day: " << this->day
		   << " Month: " << this->month
		   << " Year: " << this->year << endl; 	
	}

	Date operator++(int); 
private: 	
	int day;	//Ден 	
	int month;	//Месец 	
	int year;	//Година 	

	void helpIncrement(); 	
	//Обслужваща функция за увеличаване 	
	//на датата с един ден.
};

Date Date::operator++(int dummy) 
{ 	
	Date temp = *this;
	helpIncrement();

	return temp;
}

void Date::helpIncrement() 
{ 	
	this->day++;
}

int main()
{
	Date date(10, 10, 10);
	date.Print();
	(date++).Print();
	date.Print();

	return 0;
}

Output-а на програмата:

Day: 10 Month: 10 Year: 10
Day: 10 Month: 10 Year: 10
Day: 11 Month: 10 Year: 10

Забележка: Програмата е модифицирана повече, от колкото е реалния фрагмент от код.

Ето и реалния фрагмент от код, като въпроса към него, какъв е проблема.

#include <iostream>
using namespace std;

class Date
{
public:
	...

	Date& operator++(int);
private:
	int month;	//Месец
	int day;	//Ден
	int year;	//Година

	void helpIncrement();
	//Обслужваща функция за увеличаване
	//на датата с един ден.
};

Date& Date::operator++(int)
{
	Date temp = *this;
	helpIncrement();

	return temp;
}

Обяснението на програмата е следното:

Няма да се пускам в обяснение на работата на класа, а директно към проблема във фрагмента. Eдинствената грешка, е липсата на име на променливата, която подаваме в дефиницията на operator++. Други грешки по време на компилация или изпълнение няма да получим, но пък не е правилно да връщаме псевдоним на датата, вместо същия обект, за това оправяме фрагмента, да не връща Date&, а Date.

« 1 2 3 4 5 6 7 8 »

Задачки с решения ;)

“Примерна” задача №3.

#include <iostream>
using namespace std;

class Point
{
public:
	Point(float = 0, float = 0);
	~Point();
protected:
	float x, y;
};

Point::Point(float a, float b)
{
	x = a;
	y = b;

	cout << "Constructor of Point: "
		<< '[' << a << ", " << b << ']' << endl;
}

Point::~Point()
{
	cout << "Destructor of Point: "
		<< '[' << x << ", " << y << ']' << endl;
}

class Circle : public Point
{
public:
	Circle(float r = 0.0, float x = 0.0, float y = 0.0);
	~Circle();
private:
	float radius;
};

Circle::Circle(float r, float a, float b)
	: Point(a, b)
{
	radius = r;

	cout << "Constructor of Circle: "
		<< radius << '[' << a << ", " << b << ']' << endl;
}

Circle::~Circle()
{
	cout << "Destructor of Circle: "
		<< radius << '[' << x << ", " << y << ']' << endl;
}

int main()
{
	{
		Point p(1.1f, 2.2f);
	}
	Circle circle1(4.5, 7.2f, 2.9f);
	Circle circle2(10, 5, 5);

	return 0;
}

Output-а на програмата:

Constructor of Point: [1.1, 2.2]
Destructor of Point: [1.1, 2.2]
Constructor of Point: [7.2, 2.9]
Constructor of Circle: 4.5[7.2, 2.9]
Constructor of Point: [5, 5]
Constructor of Circle: 10[5, 5]
Destructor of Circle: 10[5, 5]
Destructor of Point: [5, 5]
Destructor of Circle: 4.5[7.2, 2.9]
Destructor of Point: [7.2, 2.9]

Забележка: Ако конзолата не е кирилизирана, ще излязат интересни символи, на мястото на кирилицата. В програмата на Павлов, коментарите са на кирилица, така че може да се заяде, ако го вземете дословно от output-а. Също така друга важна забележка е, че примерните данни изрично трябва да се даде suffix(това, което седи след стойнността) f, защото double e по-голям тип от float и трябва да се cast-не изрично. Ако не е подаден ще даде компилационна грешка, но може и да не го поправяте в примера на Павлов, ако не е изрично казано.

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

Тук нищо сложно, изпълняваме конструктора на обект от тип Point, но понеже е сложен в тяло (scope), след излизане от него се извиква деструктора му, защото обекта съществува само в конкретното тяло (scope). След това си създаваме два обекта от тип Circle, а понеже Circle наследява Point се извиква конструктора на Point, а след това на Circle. Това се прави 2 пъти, защото създаваме два обекта от тип Circle. След като приключи програмата се извикват деструкторите на circle2, point2, circle1, point1 -> т.е. обратен на реда на създаването им. Важно е да се отблежи, че унищожаването става след завършване на програмата, като това може да се провери като си пуснете изпълнимия файл и в отделна конзола, за да се забележат разликите.

Конструкторите винаги се изпълняват от родителя -> наследника,

докато при деструкторите е обратното от наследника -> родителя.

« 1 2 3 4 5 6 7 8 »

Задачки с решения ;)

“Примерна” задача №2.

#include <iostream>
using namespace std;

class PoweredDevice
{
public:
	PoweredDevice(int nPower)
	{
		cout << "PoweredDevice: " << nPower << endl;
	}
};

class Scanner : virtual public PoweredDevice
{
public:
	Scanner(int nScanner, int nPower)
		: PoweredDevice(nPower)
	{
		cout << "Scanner: " << nScanner << endl;
	}
};

class Printer : virtual public PoweredDevice
{
public:
	Printer(int nPrinter, int nPower)
		: PoweredDevice(nPower)
	{
		cout << "Printer: " << nPrinter << endl;
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int nScanner, int nPrinter, int nPower)
		: Scanner(nScanner, nPower),
		Printer(nPrinter, nPower),
		PoweredDevice(nPower)
	{} //празно тяло, може и без него
};

int main()
{
	Copier cCopier(1, 2, 3);

	return 0;
}

Output-а на програмата:

PoweredDevice: 3
Scanner: 1
Printer: 2

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

Започваме със създаването на обект от клас-наследника Сopier, след това извикване на подходящия (единствения) конструктор на този клас, който реално нищо не прави освен да извика конкретните конструктори на своите родители (class Scanner, class Printer). Преди да ги изпълни се проверя дали съществуват родители на тези класове, тъй като техните конструктори извикват конструктора на техния общ родител (class PoweredDevice). Тук има една особеност понеже Scanner и Printer наследяват PoweredDevice (virtual public). Като следствие от наследяването ние не можем да създававаме обекти от класа, както и да се изпълняват неговите методи. Това е проблем, тъй като класовете-наследници Scanner и Printer, трябва да разширят класа-родител PoweredDevice, а те не могат директно да извикат конструктора му, нито да го предефинират. Затова ние в класа Copier, изрично извикваме конструктора на PoweredDevice, който е достъпен изцяло public, тъй като той се вижда през родителите на Copier, но пък виртуалното наследяване важи само за тях. Така след като изпълним конструктора на PoweredDevice, можем да продължим с извикването на останалите конструктори в реда им на наследяването им в декларацията на class Copier. Малко по-опростено казано след като се изпълни конструктора на PoweredDevice, нататък ще се извикат конструкторите в реда, в който са упоменати на първия ред на Copier преди тялото на класа.

Конструкторите винаги се изпълняват от родителя -> наследника,

докато при деструкторите е обратното от наследника -> родителя.

« 1 2 3 4 5 6 7 8 »