Summary Files Commits Branches @commit @diff @stats
diff options
context:
space:
mode:
Commit7abf7f19e11c74e54ae2c9b42b1b8ed2ba40eab7 (patch)
Author
 Syndamia <kamen@syndamia.com>
2025-11-22 19:47:47 +0200
Committer
 Syndamia <kamen@syndamia.com>
2025-11-22 19:52:14 +0200
Files3b8b85e9d2785d476df8864f8911d47b373e69a0
syndamia.com-7abf7f19e11c74e54ae2c9b42b1b8ed2ba40eab7.tar
syndamia.com-7abf7f19e11c74e54ae2c9b42b1b8ed2ba40eab7.tar.gz
syndamia.com-7abf7f19e11c74e54ae2c9b42b1b8ed2ba40eab7.zip
Parent27fffba77f076db0bc80b2cd6ef8728022f2725b (diff)
feat(teaching/upp-2025): Add a page with C++ pitfalls
3 files changed, 405 insertions, 0 deletions
-rw-r--r--meta/meta.tsv1
-rw-r--r--src/teaching/upp-2025/cpp-pitfalls/index.du401
-rw-r--r--src/teaching/upp-2025/index.du3
diff --git a/meta/meta.tsv b/meta/meta.tsv
index 102c680..ce42c0e 100644
--- a/meta/meta.tsv
+++ b/meta/meta.tsv
@@ -88,6 +88,7 @@ teaching/oop-2023/week15/index.html Задачи - ООП, Седмица 15, 30
teaching/oop-2023/week16/index.html Задачи - ООП, Седмица 16, 06.06.2024 Условия на задачите за упражнение
teaching/upp-2025/index.html Увод в програмирането - практикум, Компютърни науки, 5 група Материали на проведените през 2025/2026 практикуми по Увод в програмирането на Компютърни науки (2 поток), 5 група.
+teaching/upp-2025/cpp-pitfalls/index.html Капани в C++ - УПП, КН, 2025-2026 Помощна страница с кажи-речи всички странности и неприятности, които трябва да се знаят за УП курса.
teaching/upp-2025/week01/index.html Задачи - УПП, Седмица 1, 02.10.2025 Условия на задачите за упражнение.
teaching/upp-2025/week01/concepts.html Общи концепции - УПП, Седмица 1, 02.10.2025 Общи концепции за компютрите, компилацията, git и GitHub Classroom.
teaching/upp-2025/week01/github_classroom.html GitHub Classroom упътване - УПП, Седмица 1, 02.10.2025 Упътване как да си създадете GitHub акаунт и как да използвате GitHub Classroom.
diff --git a/src/teaching/upp-2025/cpp-pitfalls/index.du b/src/teaching/upp-2025/cpp-pitfalls/index.du
new file mode 100644
index 0000000..85bc5e2
--- /dev/null
+++ b/src/teaching/upp-2025/cpp-pitfalls/index.du
@@ -0,0 +1,401 @@
+# Капани в C++ - УПП, КН, 2025-2026
+
+'define expected-reading 13 min
+'define created 22 November 2025
+'define edited 22 November 2025
+[$pagenav]
+
+.warn
+Този документ ще бъде обновяван до края на курса.
+Добавки ще бъдат правени!
+
+[$br2]
+
+:contents
+:numbered
+1. [url #section-1 Лоши практики]
+ .numbered
+ 1. [url #section-2 Записване/връщане на булеви стойности]
+
+2. [url #section-3 Типове на аритметични операции]
+
+3. [url #undefined-behaviour Undefined behaviour]
+ .numbered
+ 1. [url #section-4 Приоритет на скоби спрямо аритметични оператори]
+
+4. [url #section-5 Лениво оценяване на булеви изрази]
+5. [url #section-6 Връщане на масиви/матрици]
+6. [url #section-7 Тип на матрици]
+
+
+
+Тази страница описва кажи-речи всички странности и неприятности в C++, който ще трябва да знаете в този курс.
+
+Постарал съм се да опиша всичко възможно най-подробно и просто.
+Текста е много, но всичко е написано максимлано просто и детаилно.
+
+## Лоши практики
+
+Има някои типове изрази, които са коректен C++ код, но в този курс ще ги зачитаме за нещо некоректно.
+
+### Записване/връщане на булеви стойности
+
+Когато запишем булева стойност в променлива, лоша практика е да направим нещо такова:
+
+```c++
+if (condition)
+ myvar = true;
+else
+ myvar = false;
+
+Стойността на `[condition]` е булева сама по себе си, добрата практика е директно да запишем стойността:
+
+```c++
+myvar = condition;
+
+Като `[condition]` е какъвто и да е булев израз.
+
+[$br2]
+
+Еквивалентно имаме случая:
+
+```c++
+bool f() {
+ if (condition)
+ return true;
+ else
+ return false;
+}
+
+Коректното е просто да върнем `[condition]`, тя е булева:
+
+```c++
+bool f() {
+ return condition;
+}
+
+[$br2]
+
+Трябва да е очевидно, но изрази от типа на:
+
+```c++
+if (condition)
+ myvar = false;
+else
+ myvar = true;
+
+```c++
+bool f() {
+ if (condition)
+ return false;
+ else
+ return true;
+}
+
+Спадат в същата категория.
+Резултата просто трябва да се обърне, булево:
+
+```c++
+myvar = !condition;
+
+```c++
+bool f() {
+ return !condition;
+}
+
+## Типове на аритметични операции
+
+При събиране, изваждане, умножение и деление, винаги имплицитно се конвертира към най-големия тип!
+И върната стойност е от този тип.
+Например:
+
+```c++
+int x = 5;
+double y = 6;
+std::cout << (x * y); // double е по-голямо от int,
+ // x се конверира и резултата е double
+
+Ефекта от това е, че деление на цели числа връща цяло число (което закръгля резултата).
+Например:
+
+```c++
+int x = 5, y = 8;
+std::cout << (x / y); // деление на цели числа, резултата се закръгля надолу
+std::cout << (double)(x / y); // резултата от деление вече е закръглен,
+ // това преобразуване е твърде късно
+std::cout << ((double)x / y); // едното вече е с плаваща запетая,
+ // резултата също става double и няма закръгляне
+
+## Undefined behaviour
+
+C++ наследява идеята от C, че някои неща в стандарта на езика не са дефинирани.
+Тоест, какво ще се случи зависи от имплементацията на компилатора (това което превръща кода в `[.exe]`) и често се срещат разлики.
+
+### Приоритет на скоби спрямо аритметични оператори
+
+В C++ не е дефиниран приоритета на скобите спрямо аритметични операции.
+Тоест, ако имаш дълъг аритметичен израз, не се определя дали първо ще се изпълнят всички неща в скобите и след това ще се изпълнят външните аритметични операции или обратно.
+
+Този ефект е значим само при употреба на присвояващи оператори.
+Например:
+
+```c++
+int x = 9;
+int y = (x += 5) * (x += 1) * (x -= 8);
+
+Има два начина да се изчисли стойността на `[y]`:
+
+:ordered
+1. Първо изчисляваме всички изрази в скобите и след това изпълняваме умноженията.
+
+ .p
+ Тоест, първо правим `[x += 5]`, после `[x += 1]` и след това `[x -= 8]`.
+ Това променя стойността на `[x]` на 7 и стойността на `[y]` се свежда до `[x * x * x = 343]`
+
+2. Първо изчисляваме умноженията.
+ Това означава, че първо "слагаме" скоби около умноженията; израза се изпълнява все едно е:
+
+ ```c++
+((x += 5) * (x += 1)) * (x -= 8)
+
+ .p
+ *[(Умножението е ляво асоциативно)]*
+
+ .p
+ Тоест, първо правим `[x += 5]` и след това `[x += 1]`.
+ Стойността на `[x]` е 15 и в големите скоби изчисляваме `[x * x = 225]`.
+ Сега израза се свежда до:
+
+ ```c++
+225 * (x -= 8)
+
+ Изпълнява се `[x -= 8]`, `[x]` става 7, и `[y]` се свежда до `[225 * 7 = 1575]`.
+
+
+Първия метод се изпълнява от компилатора на Visual Studio, втория метод от gcc (много популярен C++ компилатор под Linux/MacOS).
+
+## Лениво оценяване на булеви изрази
+
+Всички булеви изрази се оценяват (изпълняват) лениво (мързеливо).
+Това означава, че ако израза е в състояние, където не е нужно да проверим останалите условия, няма да ги проверим.
+
+Двата главни примера се срещат при оператори "и" и "или".
+
+[$br2]
+
+```c++
+a || b || c || d
+
+Ако `[a]` е истина, тогава другите няма да се изпълнят, защото тяхната стойност не е от значение, общия израз ще си остане истина.
+Аналогично, ако `[a]` е лъжа, но `[b]` е истина, тогава `[c]` и `[d]` няма да се изпълнят.
+
+[$br2]
+
+```c++
+a && b && c && d
+
+Напълно еквивалентно работи и "или".
+Ако `[a]` е лъжа, тогава другите няма да се изпълнят, защото общия израз ще си остане лъжа.
+
+[$br2]
+
+Това е релевантно когато използваме присвояващи оператори в логически условия, например:
+
+```c++
+if (condition1 && ((myvar += 5) == 8))
+
+и когато използваме функции:
+
+```c++
+if (condition1 && f())
+
+И в двата случая, ако `[condition1]` е лъжа, тогава присвояването и извикването на функцията няма да се изпълнят.
+
+Дали това е желан ефект или не зависи от кода.
+
+## Връщане на масиви/матрици
+
+Всяка променлива, която е декларирана или дефинирана (без помощта на динамича памет) съществува в определен обхват (scope) и всички обхвати в него.
+
+Обхвати се определят с къдрави скоби.
+Тоест, нивото на къдрави скоби определя къде и кога една променлива е налична.
+
+Например (да, в C++ можем да правим ей така обхвати):
+
+```c++
+int main() {
+ {
+ int a = 5; // a се създава
+ std::cout << 5; // a се изкарва
+ } // a се освобождава
+ std::cout << a; // грешка, a е унищожено
+ {
+ std::cout << a; // грешка, a e унищожено
+ int b = 6; // b се създава
+ std::cout << b; // b се изкарва
+ {
+ std::cout << b; // b се изкарва
+ }
+ } // b се освобождава
+}
+
+Като цяло, няма особено значение дали говорим за къдравите скоби на функция, на `[if]`, на цикъл, ...
+
+[$br2]
+
+Затова следното е =[грешно]=:
+
+```c++
+int* f() {
+ int arr[5] = { 1, 2, 3, 4, 5 };
+ return arr;
+}
+
+`[arr]` е адрес в паметта (индекс, позиция, ..., която посочва клетка в паметта), някакво число.
+
+След като функцията свърши изпълнение и се върне стойността, ще сме "стигнали" къдравата скоба и `[arr]` ще бъде освободено.
+Може да зачитате, че стойностите се унищожават в този момент.
+
+Обаче, адреса сам по себе си е число, което си се връща и ние можем да работим с него както си искаме.
+
+Тоест, =[C++ няма да ни се скара, че използваме масив, въпреки че той е, на практика, унищожен!]=
+
+[$br2]
+
+Ако искаме действително да върнем масив от стойности, трябва да го създадем в =[обхвата в който функцията се извиква и да го подадем като аргумент.]=
+Например:
+
+```c++
+void f(int* arr) {
+ /* ... */
+ arr[0] = 28;
+ /* ... */
+}
+int main() {
+ /* ... */
+ int arr[5];
+ f(arr);
+ /* ... */
+}
+
+Всичко това важи и при матрици.
+
+## Тип на матрици
+
+```c++
+int mat[7][3];
+
+Какъв е типа на `[mat]`?
+
+От една страна е `[int[7][3]]`.
+Но какъв е типа като указател, тоест когато няма въведени редове =[и]= колони?
+
+Отговорите са два: `[int(*)[3]]` и `[int*]`.
+Ето защо.
+
+[$br2]
+
+Да си припомним как работят нормалните масиви.
+Когато декларираме следното:
+
+```c++
+int arr[14];
+
+Тогава в паметта се *[използва]* (заделя) =[последователност]= от клетки, която побира 14 `[int]` стойности.
+
+Също да си припомним, че `[arr]` е указател към първата от тези клетки.
+
+На кратко, това означава че `[arr]` е адрес (индекс, позиция, ..., число което посочва в коя клетка на паметта се намираме).
+
+Финално припомняме, че индексирането в масив е същото като отместване на адреса.
+Т.е. `[arr[6]]` е еквивалентно на `[*(arr + 6)]`.
+
+На кратко, ние преместваме нашия адрес (индекс в паметта) напред с 6 позиции и с `[*]` дереферираме (прочитаме стойността на подадения адрес).
+
+Това означава, че =[всички елементи в масива е нужно да бъдат поставени последователно в паметта]=.
+
+[$br2]
+
+Двумерните масиви =[работят по същия начин!]=
+
+В декларацията на `[mat]`, в паметта се заделя последователност от клетки в паметта, равна на размера на матрицата.
+Тоест `[7 * 3 = 21]` =[последователни]= *[позиции]* в паметта.
+
+Тогава, `[mat[2][1]]` на какво е еквивалентно?
+
+Отговорът е `[*(mat + 2 * 7 + 1)]`.
+
+Нашата таблица е разположена като последователност от клетки, където първо имаме клетките от първи ред, след това от втория ред и так. нат.
+
+Например, при следната дефиниция на матрица:
+
+```c++
+int nums[3][2] = {
+ { 8, 92 },
+ { 20, 50 },
+ { 7, 45 }
+};
+
+В паметта имаме разположени така:
+
+```
+8 92 20 50 7 45
+
+Как извеждаме елемента `[nums[2][1]]`?
+Това е последната стойност в матрицата, `[45]`.
+
+Щом искаме трети ред (втори по индекс), значи трябва да пропуснем първите два реда.
+Това са общо 4 числа, как го сметнахме? `[Два реда * брой колони на ред = 2 * 2]`.
+Сега сме на последния ред, искаме втория елемент (първи по индекс), значи отиваме на следващия елемент.
+
+Така показахме, че `[nums[2][1]]` е еквивалентно на `[*(nums + 2 * 2 + 1)]`.
+
+[$br2]
+
+Това трябва да поражда въпрос: не можем ли да използваме `[nums]` като едномерен масив и да напишем `[nums[2 * 2 + 1]]`?
+
+Можем!
+Затова е смислено да преобразуваме `[int[3][2]]` към `[int*]`.
+
+Единствения проблем, е че употребата става неприятна.
+Например:
+
+```c++
+void f(int* matrix) {
+ /* ... */
+ matrix[2 * 2 + 1];
+ /* ... */
+}
+
+[$br2]
+
+Забелязваме, че ако C++ знае броя колони, би трябвало да може да направи сам тази сметка с пропускането на редове.
+От там се поражда `[int(*)[2]]` като смислено преобразуване на `[int[3][2]]`.
+
+Това е най-обикновен указател, обаче му даваме броя колони (и с това, подсказваме че ще работим с матрица).
+С такъв тип указател можем да използваме синтаксиса с двете квадратни скоби.
+Например:
+
+```c++
+void g(int matrix(*)[2]) {
+ /* ... */
+ matrix[2][1];
+ /* ... */
+}
+
+[$br2]
+
+Очевиден проблем, е че броя колони трябва да бъде константен (зададен в типа), ако искаме да ползваме двата чифта квадратни скоби.
+
+Няма ли начин да ги използваме с неконстантен брой колони?
+Отговорът е =[не!]=
+
+Изкушаващо е да използваме `[int**]`, но този тип обозначава нещо друго: указател към указател.
+
+Ако имаме `[int** x]`, то тогава `[x[2][1]]` =[няма]= да бъде еквивалентно на `[*(x + 2 * колони + 1)]`!
+Ще бъде еквивалентно на `[*(*(x + 2) + 1)]`!
+
+Това защо е така ще разберем когато учим динамична памет.
+Тогава, матриците ни няма да бъдат последователност от елементи, ами ще бъдат последователност от (указатели към) масиви.
+
+Тоест всеки ред в матрицата ще може да се намира на напълно различни позиции в паметта.
diff --git a/src/teaching/upp-2025/index.du b/src/teaching/upp-2025/index.du
index a4a811a..4fc9600 100644
--- a/src/teaching/upp-2025/index.du
+++ b/src/teaching/upp-2025/index.du
@@ -12,6 +12,9 @@
| [url https://github.com/18ivan18/IntroductionToProgramming2025 Материали от семинари]
| [url https://learn.fmi.uni-sofia.bg/course/view.php?id=11601 Курс в мудъл]
+=[Допълнителни ресурси:]=
+[url ./cpp-pitfalls Капани в C++]
+
:table_3
|= &#8470;
|= Дата