SLC21 Week1 - Learn more about variable types. Subroutines. Practice problems.
Всім привіт! Вітаю з відкриттям 21 сезону Steemit Learning Challenge.
Запрошую продовжити вивчати програмування, або долучитися тих хто тільки приєднався. З одного боку це продовження попереднього курсу в SEC20, тож хто приймав участь минулого разу можуть продовжити навчатися далі. А з іншого боку це і як самостійний курс, хоч вже і не варто його розглядати як для абсолютних початківців. Так як він потребує самих мінімальних знань, але він все рівно для тих, хто робить перші кроки.
Щоб більш впевнено себе почувати, особливо хто долучився зараз - можете переглянути мої попередні уроки (особливо 3-6) та домашні завдання. Посилання наведень в таблиці внизу допису.
Більш заглибимося в типи даних С/С++
Як ви вже знаєте мови поділяються на строго типізовані і не типізовані, мови зі статичною типізацією і з динамічною типізацією. Мова С/С++ належить до мов з строгою статичною типізацією змінних. Тобто змінні перед використанням слід оголосити і тип змінної не може бути перевизначений далі в коді. Це приводить до деяких неочевидних чудес.
Чудо перше: 7/2=3
, 19/10=1
- це цілочисельне ділення. Компілятор не лише обчислює значення виразу 7/2
, а ще й "обчислює" тип результату int/int = int
. А якщо ділити ціле на ціле, то не вийде щось незвичне - результат буде теж цілий. І баг це такий, чи фіча(особливість)? Можливо баг який став фічею(особливість). Але маємо те, що маємо. Інколи це можна використати за потребою, а інколи слід обійти.
float f=7/2;
тут проблема полягає в тому, що ще до того як заносити значення в змінну f
воно, це значення, буде 3
, а не 3.5
.
В інших мовах існують спеціальні операції для такого цілочисельного ділення.
Як же це обійти? Як обчислити правильно?
Варто представити запис числа 7
не як ціле, а як дійсне значення - для цього слід написати 7.0
, тобто float f=7.0/2;
вже дасть очікуваний результат.
розглянемо такий код:
int a=7;
int b=2;
float f=a/b;
Як бути в цьому разі? Дехто з учнів мені пропонує написати int a=7.0;
та це нічого не дасть.
Слід написати float f=(float)a/b;
це примусова типізація, кастинг, тут змінна a
в процесі обчислення змінює свій тип. Але сама змінна залишається свого оголошеного типу, зміна відбувається лиш в поточний момент, тимчасово і лише для цього виразу.
Чудо друге
char k=124;
яке буде значення k
коли ми виконаємо, k=k+1;
- що за питання - буде 125,
а k++;
- очевидно що 126
а ++k;
- очевидно що 127
а k+=1;
- очевидно що 128, адже це все різні способи збільшити k на 1.
Та остання відповідь не вірна. Буде не 128. Адже всі типи даних обмежені, тобто їх змінні мають обмежені діапазони, і максимальне значення для char це 127. То що буде при спробі його збільшити ще на одиницю?
Дехто зі студентів мені відповідають що раз 127 край, межа - то від k++;
значення так і лишиться 127. Виходить так ніби комп'ютер відмовляється виконувати команду.
Інший приклад, якщо змінна unsigned char j=254;
тощо буде після j++
? Вірно буде - 255, а якщо ще раз j++
? Буде 256? - Буде 0!
Уявіть що на годиннику 55 секунд, що покаже годинник якщо пройде 7 секунд? 62 секунди - адже 55+7=62, так?
Але ж ми знаємо що 62 секунд не буває. Так само і в комп'ютері в типі змінних unsigned char
діапазон значень від 0 до 255, і 256 там просто не може бути фізично.
Сподіваюся вам не важко здогадатися що буде якщо unsigned char j=0;
та зробити j--;
??)
Такі чудеса я показав на типі даних char, unsigned char;
В інших типах даних, навіть інших мовах схожа історія. Тільки діапазони інші.
Тепер гадаю зрозуміло як може бути що 100x3=44? Адже 100х3 =300, 300-256=44
Але скільки ж буде коли 1004? 1004=400(в діапазоні не поміститься), 400-256=144(в діапазоні не поміститься), 144-256=-112(в діапазон входить)
Чудо третє
0.1 != 0.1
або 0.1 + 0.2 != 0.3
Запам'ятайте! Але як таке може бути і чому?
Тут багато причин. Сама перша - це десяткові дроби: 1.356; 0.2343; 12.010101 ... але є ще звичайні дроби, або просте звичайне ділення. І як обчислити 1/3? Адже це буде 1/3=0.3333333333... = 0.(3) Тобто це нескінченний, періодичний десятковий дріб. А пам'ять комп'ютера скінченна((, і виходить ми не можемо записати 1/3 в пам'яті комп'ютера. Вірніше, точно не можемо записати. але ви здивуєтеся коли я скажу що в пам'яті комп'ютера навіть 0.1 не можливо записати точно.
Тут правда варто б вам пояснити що таке системи числення ми послуговуємося десятковою системою де аж десять цифр 0123456789
, в комп'ютері ж лише дві цифри 01
. Тому наше скінченне 0.1 запишеться так, нескінченно.
0.1(10) = 0.0100110011001100...(2)
Дослідити це можна у такий спосіб
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout << setprecision(50) << (float)1/(float)3.0 << endl;
cout << setprecision(50) << (double)1/(double)3.0 << endl;
cout << setprecision(50) << (long double)1/(long double)3.0 << endl;
return 0;
}
Або ще простіше:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout << setprecision(50) << 1.f/3.f << endl;
cout << setprecision(50) << 1./3. << endl;
cout << setprecision(50) << 1.L/3.L << endl;
return 0;
}
Значення 50 в setprecision(50) тут явно зайве бо перебільшене, такої великою точності немає в мові С, я хотів підкреслити недоліки зберігання дійсних чисел. для використання setprecision()
слід підключити бібліотеку<iomanip>
хто пише код на С там це зробити простіше printf(".50f);
для double
та float
і printf(".50Lf);
для long double
для double правда кажуть що слід писати так printf(".50lf);
, але кажуть що тут l
не має значення(на противагу scanf)
Для float
точними є 6 знаків після коми(решта просто cміття-недостовірні дані), для double
15 знаків, і для long double
15, 18 або 33 знаки - в залежності від архітектури комп'ютера.
Хто хоче прочитає в інтернеті детальніше, головне тут запам'ятати що числа зберігаються з такими особливостями.
і що 0.1 + 0.2 != 0.3
Як ще тоді перевірити наприклад таке - if(a==0.3)...
? Для цього слід відповісти на запитання - а що значить рівні(==)? Тобто наскільки точно вони мають бути рівні? Точність у математиці позначають ε - епсілон. А модуль числа в мові С/С++ визначається функцією fabs()
.
Тобто перевірка на рівність if(a==0.3)...
запишеться так if(fabs(a-0.3<0.0001)...
, де 0.0001 - точність з якою можна вважати що ці числа рівні.
Функції
Розглянемо ще один момент - функції.
Пригадайте задачу з минулого заняття що малює лінію з *
int main()
{
int count=23;
for(int i=1; i<=count; i++)
cout<<"*";
cout<<"\n";
return 0;
}
Тут int main()
це і ж функція, на початку вивчення кажуть що по іншому і бути не може, ця функція обов'язкова. А тому її багато хто пише автоматично.
З одного боку це програма яка 'малює'/виводить ряд із 23 зірочок. Та що буде коли переписати main
на line
, програма перетвориться на підпрограму.
int line()
{
int count=23;
for(int i=1; i<=count; i++)
cout<<"*";
cout<<"\n";
return 0;
}
Але особливість мови С така що це тепер не буде працювати, адже main()
відсутня. То допишемо її.
int line()
{
int count=23;
for(int i=1; i<=count; i++)
cout<<"*";
cout<<"\n";
return 0;
}
int main()
{
return 0;
}
Після запуску нічого не відбудеться, адже наявність коду функції нічого не дає, необхідно її викликати.
int line()
{
int count=23;
for(int i=1; i<=count; i++)
cout<<"*";
cout<<"\n";
return 0;
}
int main()
{
line();
return 0;
}
Проте скільки б ми функцію не викликали вона весь час виводить 23 зірочки.
Якщо перенести int count=23;
у рядок до оголошення функції - то дана функція стане універсальною. Вона буде виводити стільки зірок, скільки вказано в її параметрах
int line(int count)
{
//int count=23;
for(int i=1; i<=count; i++)
cout<<"*";
cout<<"\n";
return 0;
}
int main()
{
line(23);
line(10);
return 0;
}
Отже функція дозволяє однією командою виконувати певну кількість команд. Це позбавляє від повторного написання коду.
Серед типів даних є особливий тип даних void
. В мові С/С++ не стали поділяти підпрограми на процедури та функції - назвали все функціями. Тільки деякі функції нічого не повертають, а точніше повертають.... нічого. Це те що в інших мовах називалося процедурою. Тобто функція int line(int count)
і не функція в буквальному розумінні, а процедура. То ж вона не має повертати ніякого (числового) значення. void line(int count)
- має бути так.
В цьому місці варто було б сказати про формальні та фактичні параметри та про передачу в параметри функції значень змінних, а не самих змінних зараз це пропущу, а пізніше зроблю вставку
Приклад 1. Напишемо функцію яка підраховує кількість цифр в числі.
Digit_count(int n)
Приклад, проста задача: написати функцію. яка приймає число як аргумент, і повідомляє(виводить) кількість цифр у даному числі - Digit_count(int n)
Задача хоч і проста - проте часто виявляється складною для початківців.
Очевидно що слід оголосити
int Digit_count(int n)
{
}
А як підрахувати кількість цифр у числі?
Представимо що це число n=94665
легко видно що цифр п'ять, але як я їх підрахував? Спроби пояснити самому собі такі елементарні дії - досить важко. Можливо саме тому важко бо дуже легко підрахувати таке мале число.
Навіть якщо уявити щось реальне, ящик з п'ятьма яблуками, легше не стане. Уявимо тоді що в ящику багато яблук, як їх порахувати? Одного разу прийдемо до думки що яблука слід перекладати в іншу коробку, і так вони зменшуватимуться, а ми їх рахуватимемо. Отже ключова тут дія - 'зменшувати на одне' поки яблука є.
Перенесемо аналогію на число. Зменшувати число на 1 доки воно є. Але зменшувати не відніманням 1, а слід відкидати цифру кожен раз, як одне яблуко. А як з числа прибрати цифру, одну - поділити його на 10.
int Digit_count(int n)
{
int k=0;
while(n>0)
{
n=n/10;
k++;
}
return k;// cout<<k;
}
Тобто доки число ще є, більше нуля while(n>0)
ми його ділимо на 10 - тобто відкидаємо останню цифру, кількість цифр зменшується на одну, отже ще одну цифру ми врахували - k++;
і цей цикл буде крутитися доти, доки в числі залишатимуться цифри.
Взагалі, коли ми пишемо функцію для чогось, то слід писати "мовчазну" функцію, тобто вона повинна нічого не виводити(коли про це явно не сказано) вона має повертати результат через return
Але тут я в коментарі все тки написав cout<<k;
так як на етапі навчання, або в процесі написання(відладки) такої функції можна робити такий вивід.
Приклад 2
Скільки шестизначних чисел таких що цифри першої половини більші за цифри другої половини.
По перше деякі задачі з допомогою комп'ютера вирішуються простим повним перебором. Тим паче що ми нещодавно вивчили цикли. То організуємо цикл що пробігає всі шестизначні числа від 100000 до 999999 включно. і хоч це краще зробити циклом for(int n=100000; n<=999999; n++)
я скористаюся циклом while
:
але в циклі while
я часто забуваю робити n++;
((
int n=100000, k=0;
while(n<=999999)
{
}
Раз ми вивчили функції то можна написати одну універсальну функцію яка видає за запитом вказану цифру числа: f(number, pos);
яка на запит f(783091, 2) видасть 8, а можна написати шість функцій під кожну цифру з шести f1(number), f2(number), ...f6(number)
. Саме в цій задачі і з навчальною метою мені більш до вподоби другий варіант.
inline int f1(int number){ return number%1000000/100000; }
inline int f2(int number){ return number%100000/10000; }
inline int f3(int number){ return number%10000/1000; }
inline int f4(int number){ return number%1000/100; }
inline int f5(int number){ return number%100/10; }
inline int f6(int number){ return number%10/1; }
Тут я оголосив функції як inline, це порада компілятору вставити код функції безпосередньо в код програми. Тобто це функція лиш формально. Але якби код тіла функції був вставлений самим програмістом це б ускладнило читання коду. Зараз оголошувати inline функції не обов'язково, компілятор часто вирішує на власний розсуд, складну функцію як inline він не зробить, а дуже просту функцію може зробити вставкою(тобто inline) і сам, без явної вказівки. Обов'язкове правило для домашніх робіт одну з функції в домашній роботі оголосити як inline, будь яку.
if( f1(n)>f4(n) && f1(n)>f5(n) && f1(n)>f6(n) &&
f2(n)>f4(n) && f2(n)>f5(n) && f2(n)>f6(n) &&
f3(n)>f4(n) && f3(n)>f5(n) && f3(n)>f6(n) )
k++;
Домашнє завдання
N | Завдання | бали | |||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | Придумайте власний приклад аналогічний до float f=7/2; який ілюструє втрату(спотворення) значення при виконанні ділення. Та покажіть як це виправити. Поясніть чому так відбувається і як ви це виправили? | 1 | |||||||||||||||||||||||||||||||||||||||
2 | Оберіть самостійно тип даних та проілюструйте обмеженість його діапазону: продемонструйте межу - знизу(перехід через мінімальне значення) і зверху(перехід через максимальне значення. Продемонструйте також перехід через межу при операції множення, поясніть результати. | 1 | |||||||||||||||||||||||||||||||||||||||
3 | знайдіть свій аналог що 0.1!=0.1 , тобто у якісь змінній, знаходиться певне значення, але перевірка показує що там інше число. Або 0.1+0.2!=0.3 Чому так? | 1 | |||||||||||||||||||||||||||||||||||||||
4 | На базі прикладу підрахунку цифр числа напишіть програму що визначає: (одну на власний вибір):
|
https://steemit.com/slc21w1sergeyk/@event-horizon/slc21-week1-learn-more-about-variable-types-subroutines-practice-problems
In last question(8) sum_div(10) = 1+2+5=8 Not =7
Here is my entry: https://steemit.com/slc21w1sergeyk/@mohammadfaisal/slc21-week1-learn-more-about-variable-types-subroutines-practice-problems
My entry
https://steemit.com/slc21w1sergeyk/@josepha/slc21-week1-learn-more-about-variable-types-subroutines-practice-problems
https://steemit.com/slc21w1sergeyk/@akmalshakir/slc21-week1-learn-more-about-variable-types-subroutines-practice-problems