Керівник Мехед Дмитро Борисович
Перегляньте наступні відео:
https://www.youtube.com/watch?v=1eyS2PgTXBU
https://www.youtube.com/watch?v=o2GxmrWTngc
https://www.youtube.com/watch?v=aoO2PTUBaDw
Важливо!
Для виконання практичних завдань використовуйте запропоновані шаблони, на їх основі побудована перевірка.
Не пишіть зайвого коду, реалізуйте лише вимоги завдання.
Рекомендуємо перевіряти результат виконання завдання в браузері або ж в онлайн-редакторах, де є така можливість. Лише після цього копіюйте текст в поле та відправляйте на перевірку.
Не публікуте правильну реалізацію на форумі - такі пости будемо видаляти без попередження.
Якщо при перевірці практичного завдання виникла помилка - задавайте запитання на форумі, однак спочатку пошукайте серед вже створених розділів, щоб не дублювати теми.
1 можливий бал (оцінюється)
Опис завдання:
Підключіть файл з іменем script.js до HTML сторінки, вкінці сторінки (перед закриваючим <body> тегом) припустивши, що файл script.js файл лежить в тій же директорії, що і HTML файл, до якого він підключається.
Для виконання завдання скопіюйте шаблон:
<!DOCTYPE html><html><head><meta charset="UTF-8" /><title>Вивчаємо Javascript</title>
</head>
<body>
<p>Javascript – скріптова мова програмування, яка була створена для використання у веб-браузерах, щоб зробити веб-сайти більш динамічними та «живими»
</p>
</body>
</html>
На цьому занятті поговоримо про таке:
Важливі моменти
Змінні
Типи даних
Масиви
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=tB1w8OPyUUA
https://www.youtube.com/watch?v=K1GpvzPeHH0
https://www.youtube.com/watch?v=BMonOVU72ho
Важливо!
Для деяких завдань надано частину коду, не видаляйте та не змінюйте його, адже це вплине на перевірку, результат буде некоректним. None - не є частиною коду, необхідно видалити.
Не пишіть зайвого коду, реалізуйте лише вимоги завдання.
Всі стрічки повинні бути лише латинкою, кирилиця призведе до помилки!
Рекомендуємо перевіряти результат виконання завдання в браузері (консоль) або ж в онлайн-редакторах, де є така можливість. Лише після цього копіюйте текст в поле та відправляйте на перевірку. Не залишайте в коді для перевірки console.log() чи alert(), використовйте ці методи лише для перевірки роботи коду в консолі!
Не публікуте правильну реалізацію на форумі - такі пости будемо видаляти без попередження.
Якщо при перевірці практичного завдання виникла помилка - задавайте запитання на форумі, однак спочатку пошукайте серед вже створених розділів, щоб не дублювати теми.
Опис завдання:
Оголосити змінну «name» та призначити їй значення "Peter", а також змінну «age» і значення 20.
Перевіряються всі змінні на коректність присвоєних значень.
На цьому занятті поговоримо про таке:
Про Javascript. Javascript не Java :)
Область застосування Javascript
Що вміє Javascript
Чого не вміє Javascript
Перегляньте наступнe відео за посиланням:
https://www.youtube.com/watch?v=1eyS2PgTXBU\
Опис завдання:
Підключіть файл з іменем script.js до HTML сторінки, вкінці сторінки (перед закриваючим <body> тегом) припустивши, що файл script.js файл лежить в тій же директорії, що і HTML файл, до якого він підключається.
Для виконання завдання скопіюйте шаблон в робочу область.
<!DOCTYPE html><html><head><meta charset="UTF-8" /><title>Вивчаємо Javascript</title> </head> <body> <p>Javascript – скріптова мова програмування, яка була створена для використання у веб-браузерах, щоб зробити веб-сайти більш динамічними та «живими» </p> </body></html>«Базові поняття JS. Операції зі змінними. Масиви»
На цьому занятті поговоримо про таке:
Арифметика і присвоєння
Взаємодія з користувачем
1 можливий бал (оцінюється)
Опис завдання:
Оголосити змінні «first» та «second» призначити їм значення 10 та 20, а потім змінну «result», а їй суму значень змінних «first» та «second».
Перевіряються всі змінні на коректність присвоєних значень.
На цьому занятті поговоримо про таке:
Умови
Цикли
Switch
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=AdgMNkbcKic
https://www.youtube.com/watch?v=VLTiiS0qrmo
Важливо!
Для деяких завдань надано частину коду, не видаляйте та не змінюйте його, адже це вплине на перевірку, результат буде некоректним. None - не є частиною коду, необхідно видалити.
Не пишіть зайвого коду, реалізуйте лише вимоги завдання.
Всі стрічки повинні бути лише латинкою, кирилиця призведе до помилки!
Рекомендуємо перевіряти результат виконання завдання в браузері (консоль) або ж в онлайн-редакторах, де є така можливість. Лише після цього копіюйте текст в поле та відправляйте на перевірку. Не залишайте в коді для перевірки console.log() чи alert(), використовйте ці методи лише для перевірки роботи коду в консолі!
Не публікуте правильну реалізацію на форумі - такі пости будемо видаляти без попередження.
Якщо при перевірці практичного завдання виникла помилка - задавайте запитання на форумі, однак спочатку пошукайте серед вже створених розділів, щоб не дублювати теми.
Опис завдання:
1. Оголосити змінну «name» та призначити їй значення "Peter", а також змінну «age» і значення 20.
Перевіряються всі змінні на коректність присвоєних значень.
2. Оголосити змінні «first» та «second» призначити їм значення 10 та 20, а потім змінну «result», а їй суму значень змінних «first» та «second».
Перевіряються всі змінні на коректність присвоєних значень.
3. Змінній «result» має присвоюватись значення "Good" якщо «check» == true та "Bad", якщо «check» == false.
Перевіряється змінна «result» на коректність присвоєння значення.
4. Створити змінну «resultArray» (масив). Створити змінні «first» = 1, «second» = 2, «senseOfLife» = 42. За допомогою методу масива .push() додати в масив спочатку «first» потім «second». Після цього, за допомогою методу .unshift() додати змінну «senseOfLife».
Перевіряються всі змінні на коректність присвоєних значень та правильна послідовність змінних в масиві.
На цьому заняття поговоримо про таке:
Функції
Область видимості
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=3WRm6meDJkk
https://www.youtube.com/watch?v=VriukrD7BBg
Для перевірки та закріплення пройденого матеріалу пройдіть тест за посиланням:
https://courses.prometheus.org.ua/courses/course-v1:LITS+114+2017_T4/courseware/dce31ca2bda747b7b3f34df1e84d6cae/22d6e08771604b81b2c972b2883c3979/?activate_block_id=block-v1%3ALITS%2B114%2B2017_T4%2Btype%40sequential%2Bblock%4022d6e08771604b81b2c972b2883c3979
На цьому занятті поговоримо про таке:
DOM part
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=xAItEGhfy-0
https://www.youtube.com/watch?v=dNeU06zEFI8
Всі завдання лабораторних робіт призначені для закріплення знань і впливають на загальний результат оцінки курсу.
Важливо!
Для деяких завдань надано частину коду, не видаляйте та не змінюйте його, адже це вплине на перевірку, результат буде некоректним. None - не є частиною коду, необхідно видалити.
Не пишіть зайвого коду, реалізуйте лише вимоги завдання.
Всі стрічки повинні бути лише латинкою, кирилиця призведе до помилки!
Рекомендуємо перевіряти результат виконання завдання в браузері (консоль) або ж в онлайн-редакторах, де є така можливість. Лише після цього копіюйте текст в поле та відправляйте на перевірку. Не залишайте в коді для перевірки console.log() чи alert(), використовйте ці методи лише для перевірки роботи коду в консолі!
Не публікуте правильну реалізацію на форумі - такі пости будемо видаляти без попередження.
Якщо при перевірці практичного завдання виникла помилка - задавайте запитання на форумі, однак спочатку пошукайте серед вже створених розділів, щоб не дублювати теми.
1 можливий бал (оцінюється)
Опис завдання:
Правильно підключіть jQuery до HTML сторінки, використовуючи CDN (http://code.jquery.com/jquery-1.9.1.min.js).
Для виконання завдання скопіюйте шаблон в робочу область.
1 можливий бал (оцінюється)
Опис завдання:
Виберіть три DOM елемента за допомогою jQuery. Для пошуку елементів використовуйте $:
Зверніться до елемента <div id="test"></div> за id = "test" та присвойте вибраний елемент змінній id.
Зверніться до елемента <div class ="test"></div> за класом class = "test" та присвойте вибрані елементи змінній className
Зверніться до елемента <span></span> за тегом та присвойте вибрані елементи змінній tag
1 можливий бал (оцінюється)
Опис завдання:
Задано параграф: <p>Hello, World!!!</p>
Реалізуйте функцію, використовуючи jQuery, яка спрацює на click подію по тексту. Функція має змінити колір тексту на "red" та розмір тексту на "30px". Для пошуку елементів використовуйте $
В реалізації необхідно використати chaining
На цьому занятті поговоримо про таке:
Події
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=vXP3gWHoIOA
https://www.youtube.com/watch?v=5m93W5Jth3g
Для закріплення пройденого матеріалу пройдіть, будь ласка тест за посиланням:
https://courses.prometheus.org.ua/courses/course-v1:LITS+114+2017_T4/courseware/53435b438e4940ae86aa3e8498ef53ba/e705e1a31e0d4cee8741fc577c51c37f/?activate_block_id=block-v1%3ALITS%2B114%2B2017_T4%2Btype%40sequential%2Bblock%40e705e1a31e0d4cee8741fc577c51c37f
На цьому занятті поговоримо про таке:
Навіщо дебажити код
Web Development Tools
Console.log(); Debugger; Alert();
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=F3Gzd5FHoD0
https://www.youtube.com/watch?v=qPpPW9readM
Завдання 1.
Створити кнопку на веб сторінці, при натисканні на кнопку має виводитись alert з фразою: «Привіт!»
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/XQgbZE
Завдання 2.
Створити форму з кнопкою та полем вводу. При натисканні на кнопку має виводитись вміст поля вводу через alert.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/LvLVrq
Завдання 3.
Створити форму з кнопкою та двома полями вводу. Написати скрипт, який здійснює обмін вмістом між полями вводу, по натисканні на кнопку.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/LvLVMv
Завдання 4.
Створити кнопку, по натисканню на кнопку текст на ній має змінюватись.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/ZZyGNo
Завдання 5.
Створити веб сторінку з одним блоком та кнопкою.
Написати скрипт, який, при натисканні на кнопку, буде надавати блоку наступні стилі: width – 200px; height – 200px; background-color: magenta; text-align: center;
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/NmvvbB
Завдання 6*.
Додати ще одну кнопку до попередньої веб-сторінки. При натисканні на яку, колір блоку буде змінюватись на green.
Завдання 7.
Створити веб сторінку з заголовком, трьома абзацами з довільним текстом та кнопкою.
По кліку на кнопку зміст абзаців має змінитись на їх порядковий номер.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/MRvvxY
Для успішної здачі лабораторної роботи, надішліть у відповідь посилання на виконані вами завдання у середовищи CodePen або самі коди виконаних завдань.
Завдання 1.
Створити веб-сторінку з таблицею, що складається з п’яти рядків та п’яти стовпичків.
Виділити комірки по діагоналі, тобто написати скрипт, який змінить фон відповідних комірок, як показано на рисунку нижче.
Вам потрібно буде отримати з таблиці table все діагональні td.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/QPMMee
Завдання 2*.
Змінити код з попереднього завдання таким чином, щоб виділялась інша діагональ. Підказка – треба ввести додатковий лічильник у циклі for. Наприклад додати лічильник j (вказується через кому).
Завдання 3.
Створити калькулятор, який буде додавати і віднімати числа. Приклад реалізації на рисунку нижче.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/BEdwLe
Завдання 4*.
Розширте можливості калькулятор, додавши функції віднімання та ділення.
Завдання 5.
Створити цифровий годинник за допомогою js. Для цього використовувати вбудовані функції роботи з датою та часом у JS. Має вийти щось таке:
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/jRLGxr
Завдання 6*.
Додати до створенного годинника відображення секунд.
Завдання 7.
Написати скрипт - гру “Вгадай число”. Скрипт має генерувати випадкове число від 1 до 100. Для цього можна використовувати вбудовану функції random та floor із пакета Math. Почитати про них тут:
Завдання користувача відгадати число та записати його у textbox.
Скрипт має виводити підказки (більше/менше). У випадку якщо користувач вгадав число скрипт має виводити: “Вітаю, ви вгадали!”.
Також реалізувати кнопку “Зіграти ще”, яка буде стирати весь текст підказок та змінювати значення числа на нове.
Має вийти щось таке:
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/eoEGqy
Для успішної здачі лабораторної роботи, надішліть у відповідь посилання на виконані вами завдання у середовищи CodePen або самі коди виконаних завдань.
На цьому занятті поговоримо про таке:
Об’єкти у функція,. Конструктори
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=k4EjhGXr_dI&feature=youtu.be
Завдання 1.
Створити кнопку на веб сторінці, при натисканні на кнопку має виводитись alert з фразою: «Привіт!»
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/XQgbZE
Завдання 2.
Створити форму з кнопкою та полем вводу. При натисканні на кнопку має виводитись вміст поля вводу через alert.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/LvLVrq
Завдання 3.
Створити форму з кнопкою та двома полями вводу. Написати скрипт, який здійснює обмін вмістом між полями вводу, по натисканні на кнопку.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/LvLVMv
Завдання 4.
Створити кнопку, по натисканню на кнопку текст на ній має змінюватись.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/ZZyGNo
Завдання 5.
Створити веб сторінку з одним блоком та кнопкою.
Написати скрипт, який, при натисканні на кнопку, буде надавати блоку наступні стилі: width – 200px; height – 200px; background-color: magenta; text-align: center;
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/NmvvbB
Завдання 6*.
Додати ще одну кнопку до попередньої веб-сторінки. При натисканні на яку, колір блоку буде змінюватись на green.
Завдання 7.
Створити веб сторінку з заголовком, трьома абзацами з довільним текстом та кнопкою.
По кліку на кнопку зміст абзаців має змінитись на їх порядковий номер.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/MRvvxY
Для успішної здачі лабораторної роботи, надішліть у відповідь посилання на виконані вами завдання у середовищи CodePen або самі коди виконаних завдань.
Виконайте наступні завдання:
Завдання 1.
Створити веб-сторінку з таблицею, що складається з п’яти рядків та п’яти стовпичків.
Виділити комірки по діагоналі, тобто написати скрипт, який змінить фон відповідних комірок, як показано на рисунку нижче.
Вам потрібно буде отримати з таблиці table все діагональні td.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/QPMMee
Завдання 2*.
Змінити код з попереднього завдання таким чином, щоб виділялась інша діагональ. Підказка – треба ввести додатковий лічильник у циклі for. Наприклад додати лічильник j (вказується через кому).
Завдання 3.
Створити калькулятор, який буде додавати і віднімати числа. Приклад реалізації на рисунку нижче.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/BEdwLe
Завдання 4*.
Розширте можливості калькулятор, додавши функції віднімання та ділення.
Завдання 5.
Створити цифровий годинник за допомогою js. Для цього використовувати вбудовані функції роботи з датою та часом у JS. Має вийти щось таке:
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/jRLGxr
Завдання 6*.
Додати до створенного годинника відображення секунд.
Завдання 7.
Написати скрипт - гру “Вгадай число”. Скрипт має генерувати випадкове число від 1 до 100. Для цього можна використовувати вбудовану функції random та floor із пакета Math. Почитати про них тут:
Завдання користувача відгадати число та записати його у textbox.
Скрипт має виводити підказки (більше/менше). У випадку якщо користувач вгадав число скрипт має виводити: “Вітаю, ви вгадали!”.
Також реалізувати кнопку “Зіграти ще”, яка буде стирати весь текст підказок та змінювати значення числа на нове.
Має вийти щось таке:
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/eoEGqy
Для успішної здачі лабораторної роботи, надішліть у відповідь посилання на виконані вами завдання у середовищи CodePen або самі коди виконаних завдань.
Перегляньте наступні відео за посиланням:
https://www.youtube.com/watch?v=GEWG4ROD4tU&feature=youtu.be
Практичне заняття.
console.log('*** PERSON ***');
function Person (name) {
this.name = name;
}
// визначаємо властивості, методи
Person.prototype = {
eyes: 2,
mouth: 1,
sleep: function () {
return 'zzz';
}
};
// створюємо людину (Person)
const p1 = new Person('Nick');
// and we can do:
console.log(
`name: ${p1.name}`,
`eyes: ${p1.eyes}`,
`mouth: ${p1.mouth}`,
p1.sleep()
);
console.log('*** EMPLOYEE ***')
// Тепер, якщо у нас є «клас» співробітника, то можна успадковувати його властивості.
function Employee (name, salary) {
this.name = name;
this.salary = salary;
}
// прототип успадкування
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee; // Устанавливаем его конструктор
// Повторюємо те ж саме
// створюємо співробітника
const em1 = new Employee('John', 3000);
// і прописуємо наступне:
console.log(
`name: ${em1.name}`,
`salary: ${em1.salary} USD`,
`eyes: ${em1.eyes}`,
`mouth: ${em1.mouth}`,
em1.sleep()
);
Зареєструйтеся в онлайн редакторі коду https://codepen.io/trending
Робота з масивами та числами в JS. Створюємо власний калькулятор.
Завдання 1.
Створити змінну «resultArray» (масив). Створити змінні «first» = 1, «second» = 2, «senseOfLife» = 42. За допомогою методу масива .push() додати в масив спочатку «first» потім «second». Після цього, за допомогою методу .unshift() додати змінну «senseOfLife». Вивести значення масиву на сторінку за допомогою функції document.write();
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут – https://codepen.io/bazvlamar/pen/oOLyOE
Завдання 2.
Створити змінну «start» = 100 та пустий масив «result». Створити цикл, який заповнить масив «result» значеннями від 100 до 0 включно за допомогою декремента. Вивести значення масиву на сторінку за допомогою функції document.write().
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут -https://codepen.io/bazvlamar/pen/VNKEZa
Завдання 3.
Створити функцію з назвою isEven, яка буде приймати число і повертати булевий результат (True/False). True – число парне, False – число не парне. Для виконання нам знадобиться оператор залишок від ділення. Результат повернути за допомогою ключового слова "return". Вивести результат на сторінку за допомогою функції document.write().
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/eodPda
Завдання 4*.
Модифікувати попереднє завдання наступним чином:
Додати форму на веб сторінку з одним input та однією кнопкою з написом «Перевірити». Користувач буде вводити число в input, а натискання кнопки буде запускати скрипт, який визначить введене число парне чи ні, та виведе результат на веб сторінку.
Завдання 5.
Створити масив numbers з довільним числом елементів. Створити два пустих масива odd та even. Написати скрипт, який буде переносити числа з масиву numbers в odd, якщо вони не парні, або в масив even якщо вони парні. Результат виводити на веб-сторінку.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/XQjGye
/*
принцип дії такої програми: https://media.giphy.com/media/kWlFRjghngxxK/giphy.gif
*/
Завдання 6.
Створити функцію – калькулятор. Функція приймає 3 параметра:
перший параметр типу Number,
другий парамерт типу Number,
третій параметр типу String, в якому передається значення операції. Значеннями операції можуть бути: «+», «-» , «*» і «/».
Функція повинна повернути результат виконаної операції за допомогою return.
Спробуйте це зробити самостійно. Якщо не вийде, або для перевірки можете переглянути підказку тут - https://codepen.io/bazvlamar/pen/wZzOba
Завдання 7*.
Створити скрипт, який буде працювати як калькулятор з попереднього завдання. Параметри num1, num2, mark має задавати користувач через модальні вікна (prompt(title,default)) або через форму. Результат виконання має виводитись через alert() або на веб-сторінку.
Для успішної здачі лабораторної роботи, надішліть у відповідь посилання на виконані вами завдання у середовищи CodePen або самі коди виконаних завдань.
В рамках сьогоднішнього заняття Вам необхідно виконати наступні дії:
1. Скачати та встановити XAMPP.
2. Запустити phpMyAdmin та ознайомитись з його інтерфейсом.
3. Створитив власну базу даних.
4. Створити дві таблиці (Students та Groups). Структуру таблиць наведено на рисунку нижче.
5. Створити зв'язок між таблицями.
6. Заповнити таблиці тестовими данниими (2-3 записи в кожній таблиці).
В рамках ціє лабораторної роботи Вам необхідно виконати наступні дії:
1. Скачати та встановити XAMPP.
2. Запустити phpMyAdmin та ознайомитись з його інтерфейсом.
3. Створитив власну базу даних.
4. Створити дві таблиці (Students та Groups). Структуру таблиць наведено нижче.
5. Створити зв'язок між таблицями.
6. Заповнити таблиці тестовими данниими (2-3 записи в кожній таблиці).
Дмитро Мехед запрошує вас на заплановану конференцію: Zoom.
Тема: CТВОРЕННЯ СИСТЕМИ РЕЄСТРАЦІЇ ТА АВТОРИЗАЦІЇ КОРИСТУВАЧІВ ЗА ДОПОМОГОЮ PHP І MYSQL
Время: 18 дек. 2020 06:00 PM Хельсинки
Подключиться к конференции Zoom
https://us04web.zoom.us/j/72727665528?pwd=dHVBQ0Vlb2xsQTZzU2RsNHdLVlpQZz09
Идентификатор конференции: 727 2766 5528
Код доступа: nfX0Cd
В якості відповіді на це завдання, вам треба завантажити скріншот з вікном привітання вашої веб-форми з вашим ім'ям. Як показано на рисунку нижче.
Дмитро Мехед приглашает вас на запланированную конференцию: Zoom.
Подключиться к конференции Zoom
https://us04web.zoom.us/j/74264291093?pwd=ZEJzbStqcjZiTFNPK0hYcFQ4TGdIUT09
Идентификатор конференции: 742 6429 1093
Код доступа: PN3j6i
В якості відповіді на це завдання, вам треба завантажити скріншот з вікном привітання вашої веб-форми з вашим ім'ям. Як показано на рисунку нижче.
Робота в сервісі Zoom. Відеоконференція за попереднім запрошенням (Дмитро Мехед доцент кафедри Кібербезпеки та ММ приглашает вас на запланированную конференцию: Zoom.
Тема: Zoom meeting invitation - Zoom Meeting Дмитро Мехед доцент кафедри Кібербезпеки та ММ
Время: 30 дек. 2020 06:00 PM Хельсинки
Подключиться к конференции Zoom
https://us04web.zoom.us/j/73055805158?pwd=UkpjSkpwWGV6VGY4YXQzam1Uc290UT09
Идентификатор конференции: 730 5580 5158
Код доступа: PvK6b3).
Практична робота
Завдання
Створити документ form1.html, в якому створити власну форму (за варіантами) з
обов’язковим використанням наступних елементів:
введення тексту (text)
введення паролю (password)
прапорці (checkbox)
радіокнопки (radio)
кнопки («відправити», «очистити», «перейти») (button)
малюнок (image)
FIELDSET та LEGEND
Варіанти
1. Форма відвідувача лікарні
2. Форма відвідувача бібліотеки
3. Форма студентської анкети
4. Форма клієнта туристичної фірми
5. Форма відвідувача спортзала
6. Форма замовлення книжок
7. Форма замовлення авіаквитків
8. Форма замовлення товарів поштою
9. Форма покупці комп’ютерної техніки
10. Форма замовлення побутової техніки
Сьогодні говоритимо про наступне:
Дмитро Мехед приглашает вас на запланированную конференцию: Zoom.
Тема: Zoom meeting invitation - Zoom Meeting Дмитро Мехед
Время: 6 янв. 2021 06:00 PM Хельсинки
Подключиться к конференции Zoom
https://us04web.zoom.us/j/76332639494?pwd=d0MzckJkLy9ZQnU5RWFlRmhEcG9tUT09
Идентификатор конференции: 763 3263 9494
Код доступа: J2HYDU
Ми вже дізналися, що завдяки безлічі рівнів абстрагування та тим, хто до нас займався цією темою, ми можемо легко писати програми, які в остаточному підсумку будуть просто двійковими, 0 і 1.
Розв’язання задачі можна описати як прийом вхідних даних (задачі) та використання алгоритму для пошуку вихідних даних (розв’язку).
Комп'ютери представляють вхідні та вихідні дані за допомогою значної кількості бітів, двійкових цифр, 0 та 1, що увімкнені або вимкнекні. Із достатньою кількістю бітів ми можемо представляти не тільки великі числа, а й текст, зображення та відео.
Можна використати різні алгоритми, які будуть розв’язувати одну і ту саму задачу, але з різним часом виконання.
Ми можемо запусувати алгоритми точніше за допомогою псевдокоду, а також використовувати концепції функцій, циклів та умов.
За допомогою добровольців в аудиторії ми зробили бутерброди з арахісовим маслом та желе, хоча кожен з нас інтерпретував інструкції по-різному!
Виявляється, ми (люди), природно, робимо припущення та абстракції під час виконання інструкцій або навіть псевдокоду. Але, як ми бачили в мові програмування Scratch, а також побачимо в мові програмування С, тепер так робити не вдасться, і нам доведеться думати уважніше про кроки та випадки, з якими доведеться працювати нашим програмам.
Завдяки Scratch ми скористались напрацюваннями хлопців з MIT, які створили блоки та спрайти для створення власних програм. І ми також зробили власні блоки, такі як функція cough («кашель), це була наша власна абстракція.
Ми будемо використовувати нову мову C – це повністю текстова мова, яка містить складні службові слова та пунктуацію:
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
}
Це еквівалентно блоку «коли натиснуто зелений прапорець» та «сказати «Привіт, світ!»:
Ми можемо порівняти багато конструкцій в мові програмування C з блоками, які ми вже бачили та використовували у Scratch. Синтаксис тут грає значно меншу роль, ніж принципи, з якими ми вже ознайомилися.
Блок «Сказати «Привіт, світ!» - це функція, вона відповідає printf("hello, world\n");. У C функція виводу на екран виглядає як printf, де f означає «форматування», тобто ми можемо форматувати рядок різними способами. Потім ми використовуємо круглі дужки, щоб передати те, що ми хочемо вивести. Використовуються подвійні лапки, щоб виокремити текст або рядок і додати \n що означає новий рядок на екрані. (Наступного разу, коли ми використаємо функцію printf, наш текст буде розташований у новому рядку). Нарешті, ми додаємо крапку з комою ; щоб закінчити цей рядок коду в C.
Блок «встановити [counter] у (0)» створює змінну, а в C ми б сказали int counter = 0;, де int вказує, що тип нашої змінної є цілим числом:
«Змінити [counter] на (1)» — це counter = counter + 1; в C. (У C = не є знаком «дорівнює», де ми стверджуємо, що counter це те ж, що і counter + 1. На відміну від цього, = означає «скопіювати значення праворуч у значення ліворуч».) Можна також сказати counter += 1; або counter++; обидва з яких — «синтаксичний цукор», скорочення, що дозволяє писати менше слів чи знаків, але виконувати ті самі речі.
Умова буде відповідна до
if (x < y)
{
printf("x is less than y\n");
}
Зверніть увагу, що в C ми використовуємо фігурні дужки { і } (а також відступи) щоб вказати, як мають бути вкладені рядки коду.
Також можливі умови «if-else» (якщо-інакше)
if (x < y)
{
printf("x is less than y\n");
}
else
{
printf("x is not less than y\n");
}
З іншого боку, пробіли, нові рядки та відступи, як правило, не є синтаксично важливими в C, тобто вони не впливають на те, як наша програма в кінцевому підсумку працює, але для нашого коду дуже важливо дотримуватися домовленостей і мати гарний «стиль», аби код був читабельним для людей.
І навіть else if
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else if (x == y)
{
printf("x is equal to y\n");
}
Зверніть увагу, для порівняння двох значень у мові програмування C ми використовуємо ==, два знаки дорівнює.
Отже, за логікою, нам не потрібно if (x == y) в останній умові, оскільки це єдиний випадок, що лишився, і ми можемо просто використати else.
Цикли можна задавати ось так
while (true)
{
printf("hello, world\n");
}
Ключове слово while (поки) також вимагає певної умови, тому ми використовуємо true (істина) як булевий вираз, аби переконатися, що наш цикл буде працювати постійно. Наша програма перевірить, чи значення виразу вираховується як true (в цьому випадку це завжди істина), а потім запустить рядки всередині фігурних дужок. А потім буде повторюватися доти, поки вираз не перестане бути істинним (в цьому випадку він не зміниться).
for (int i = 0; i < 50; i++)
{
printf("hello, world\n");
}
Щоб написати цикл, який буде працювати визначену кількість разів, ми використовуємо ключове слово for і спочатку ми створюємо змінну i та присвоюємо їй значення 0. i традиційна назва для змінної, яка відстежує, скільки ітерацій циклів ми вже пройшли. Далі, ми перевіряємо, чи i < 50, щоразу, коли ми повертаємося вгору циклу, перш ніж запускати код усередині. Якщо цей вираз — true, ми запускаємо код всередині. Нарешті, після виконання коду, ми використовуємо i++ щоб додати одиницю до i, і цикл повторюється.
Ми також можемо отримати вхідні дані від користувача
Сьогодні говоритемемо про наступне:
Ми почали курс з вивчення Scratch, а потім перейшли на мову програмування С.
Нагадаємо, що ми пишемо вихідний код мовою програмування C, але ми маємо його спомпілювати на машинний код у двійковій системі, аби запустити на комп'ютері.
clang це компілятор, з яким ми вже познайомилися, а make – це утіліта, яка допомагає нам запускати clang автоматично.
Якщо б ми захотіли використати бібліотеку CS50 за допомогою #include <cs50.h>, та застосувати clang замість make, потрібно додати прапорець: clang hello.c -lcs50. Прапорець -l підключає файл cs50, який було встановлено у CS50 Sandbox.
Компіляція вихідного коду на машинний відбувається у такі кроки:
препроцессінг
компіляція
збирання
компонування
Препроцессінг складається з перегляду рядків, які починаються з #, як-от #include, перед тим, як виконувати все інше. Наприклад, #include <cs50.h> скаже clang знайти спочатку цей заголовковий файл, оскільки там міститься контент, який ми хочемо використати у нашій програмі. Потім, clang замінить вміст цих файлів в нашій програмі:
...
string get_string(string prompt);
int printf(const char *format, ...);
...
int main(void)
{
string name = get_string("Name: ");
printf("hello, %s\n", name);
}
Компіляція бере наш вихідний код на С та конвертує на код асемблера, який виглядає ось так:
...
main: # @main
.cfi_startproc
# BB#0:
pushq %rbp
.Ltmp0:
.cfi_def_cfa_offset 16
.Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
xorl %eax, %eax
movl %eax, %edi
movabsq $.L.str, %rsi
movb $0, %al
callq get_string
movabsq $.L.str.1, %rdi
movq %rax, -8(%rbp)
movq -8(%rbp), %rsi
movb $0, %al
callq printf
...
Це низькорівневі інструкції, тому краще зрозумілі центральному процессору, і вцілому працюють з байтами, а не з такими абстракціями, як назви змінних.
Наступний крок – взяти код асемблера та перекласти його на інструкції у двійковій системі – це називається збирання.
І наостанок потрібно все скомпонувати, коли вміст пов'язаних бібліотек, як cs50.c, фактично включаються до нашої програми у двійковому вигляді.
Дебаггінг
Уявімо, ми написали програму buggy0:
int main(void)
{
printf("hello, world\n")
}
Ми бачимо помилку, коли намагаємось скомпілювати цю програму за допомогою make про те, що ми не додали заголовковий файл.
Ми можемо запустити help50 make buggy0, що врешті-решт повідомить нам про необхідність додати #include <stdio.h>, який містить printf.
Ми все зробили, а тепер бачимо ще одну помилку та розуміємо, що ми забули поставити крапку з комою в кінці рядка.
Давайте розглянемо іншу програму:
#include <stdio.h>
int main(void)
{
for (int i = 0; i <= 10; i++)
{
printf("#\n");
}
}
Хм, ми думали, що буде лише 10 символів #, але їх вийшло 11. Якби ми не знали, в чому полягає проблема (оскільки наша програма працює так, як ми її написали), то могли б додати ще один рядок print на допомогу:
#include <stdio.h>
int main(void)
{
for (int i = 0; i <= 10; i++)
{
printf("i is %i\n", i);
printf("#\n");
}
}
Тепер, ми бачимо, що i почалась з 0 і продовжувалась до 10, але ми повинні його зупинити на 9.
Якби ми записали нашу програму без пробілів, як подано нижче, то вона все одно була б правильна:
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 10; i++)
{
printf("i is %i\n", i);
printf("#\n");
}
}
Однак, читати в такому вигляді складно, це поганий дизайн. Набагато легше буде побачити вкладеність, якщо ми використаємо відступи для циклів.
Ми можемо запустити style50 buggy2.c, і побачити рекомендації, що нам слід змінити.
Отже, нагадаємо, ми маємо три інструменти, які допоможуть нам покращити код:
help50
printf
style50
Пам’ять
В середині наших комп`ютерів містяться чіпи RAM, тобто оперативна пам'ять, що зберігає дані для короткочасного використання. Можна зберігати файли на жорсткому диску (або SSD) для довготривалого зберігання, але коли ми його відкриємо та будемо вносити зміни, все копіюється до RAM. Попри те, що обсяг RAM значно менший та вона є тимчасовою (поки не буде вимкнене живлення), вона значно швидша.
Можна розглядати байти, які зберігаються в RAM, у вигляді сітки:
Насправді там розташовані мільйони або навіть мільярди байтів на кожному чіпі.
У мові програмування С коли ми створюєму змінну типу char розміром в один байт, вона буде фізично збережена в одній із таких комірок у RAM. Ціле число на чотири байти займе чотири таких комірки.
Розв’яжіть наведені задачі:
Сьогодні говоритемемо про наступне:
У пам’яті ми можемо зберігати змінні послідовно, одна за одною. І в мові програмування С список збережених змінних, які розташовуються у суміжних комірках пам'яті, називається масивом.
Виявляється, що з самими тільки масивами можна робити дуже цікаві речі.
Розглянемо scores0.c:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Get scores from user
int score1 = get_int("Score 1: ");
int score2 = get_int("Score 2: ");
int score3 = get_int("Score 3: ");
// Generate first bar
printf("Score 1: ");
for (int i = 0; i < score1; i++)
{
printf("#");
}
printf("\n");
// Generate second bar
printf("Score 2: ");
for (int i = 0; i < score2; i++)
{
printf("#");
}
printf("\n");
// Generate third bar
printf("Score 3: ");
for (int i = 0; i < score3; i++)
{
printf("#");
}
printf("\n");
}
Ми отримуємо 3 результати (scores) від користувача та виводимо графічне позначення для кожного з них.
Наші 3 цілих числа, score1, score2, та score3 будуть збережені десь у пам’яті.
Ми можемо використати цикл, або почнемо розділяти на частини:
#include <cs50.h>
#include <stdio.h>
void chart(int score);
int main(void)
{
// Get scores from user
int score1 = get_int("Score 1: ");
int score2 = get_int("Score 2: ");
int score3 = get_int("Score 3: ");
// Chart first score
printf("Score 1: ");
chart(score1);
// Chart second score
printf("Score 2: ");
chart(score2);
// Chart third score
printf("Score 3: ");
chart(score3);
}
// Generate bar
void chart(int score)
{
// Output one hash per point
for (int i = 0; i < score; i++)
{
printf("#");
}
printf("\n");
}
Тепер маємо функцію chart яка буде виводити кожен результат.
Пам'ятайте, що нам потрібно мати на початку прототип, void chart(int score);. Ми моглиб мати всю функцію chart вгорі перед її використанням, але врешті наша функція main буде спускатись все нижче і шукати її буде все складніше.
За допомогою масивів ми можемо зібрати результати в циклі, а потім одержати до них доступ, також в циклі:
// Generates a bar chart of three scores using an array
#include <cs50.h>
#include <stdio.h>
void chart(int score);
int main(void)
{
// Get scores from user
int scores[3];
for (int i = 0; i < 3; i++)
{
scores[i] = get_int("Score %i: ", i + 1);
}
// Chart scores
for (int i = 0; i < 3; i++)
{
printf("Score %i: ", i + 1);
chart(scores[i]);
}
}
// Generate bar
void chart(int score)
{
// Output one hash per point
for (int i = 0; i < score; i++)
{
printf("#");
}
printf("\n");
}
Зверніть увагу, що ми застосовуємо int scores[3] для ініціалізації масиву з трьома цілими числами. Потім ми використовуємо scores[i] = ..., щоб зберегти значення в цьому масиві, використовуючи для цього індекс i від 0 до 2 (оскільки в нас три елементи).
Після цього ми використовуємо scores[i] для доступу до усіх значень в масиві за кожним індексом.
У нас повторюється число 3 кілька разів, тому ми можемо виокремити його у константу, тобто число, яке ми можемо один раз задати й використовувати глобально:
#include <cs50.h>
#include <stdio.h>
const int COUNT = 3;
void chart(int score);
int main(void)
{
// Get scores from user
int scores[COUNT];
for (int i = 0; i < COUNT; i++)
{
scores[i] = get_int("Score %i: ", i + 1);
}
// Chart scores
for (int i = 0; i < COUNT; i++)
{
printf("Score %i: ", i + 1);
chart(scores[i]);
}
}
// Generate bar
void chart(int score)
{
// Output one hash per point
for (int i = 0; i < score; i++)
{
printf("#");
}
printf("\n");
}
На початку ми використовуємо ключове слово const щоб визначити, що це незмінне значення. Його можна використовувати по всьому коду, і якщо ми захочемо щось змінити, достатньо змінити лише один раз. Врешті, назва COUNT написана великими літерами, щоб показати, що це константа (за домовленістю).
Ми можемо за допомогою функції chart вивести всі результати, а не по одному стовпчику за раз:
#include <cs50.h>
#include <math.h>
#include <stdio.h>
const int COUNT = 3;
void chart(int count, int scores[]);
int main(void)
{
// Get scores from user
int scores[COUNT];
for (int i = 0; i < COUNT; i++)
{
scores[i] = get_int("Score %i: ", i + 1);
}
// Chart scores
chart(COUNT, scores);
}
// Generate bars
void chart(int count, int scores[])
{
// Output one hash per point
for (int i = 0; i < count; i++)
{
for (int j = 0; j < scores[i]; j++)
{
printf("#");
}
printf("\n");
}
}
Передавши весь масив scores та кількість результатів count які ми хочемо вивести, ми можемо використати функцію chart для ітерування крізь масив scores. Фактично, chart наскільки великим є масив scores, тож ми обов'язково маємо передавати й count.
Рядки – це насправді просто масиви символів. Це можна побачити у string0.c:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("Input: ");
printf("Output: ");
for (int i = 0; i < strlen(s); i++)
{
printf("%c\n", s[i]);
}
}
Спочатку нам знадобиться нова бібліотека, string.h, для strlen, що показує нам довжину рядка. Далі ми використовуємо той же синтаксис для доступу до елементів масиву, s[i], аби вести окремі символи рядка s.
Ми можемо покращити дизайн нашої програми. string0 була не надто ефективна, оскільки перевіряємо довжину рядка після введення кожного символу, в нашій умові. Проте оскільки довжина рядка не змінюється, то ми можемо перевіряти її лише один раз:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("Input: ");
printf("Output:\n");
for (int i = 0, n = strlen(s); i < n; i++)
{
printf("%c\n", s[i]);
}
}
Зараз, на початку нашого циклу, ми ініціалізуємо дві змінні i та n та записуємо довжину рядка у n. Далі ми можемо щоразу перевіряти значення без вираховування довжини рядка.
n буде доступна виключно в межах циклу for хоча ми можемо ініціалізувати її і поза ним, якщо нам потрібно буде її повторне використання.
Коли рядок зберігається у пам’яті, кожен символ розміщується в одному байті в таблиці байтів. Десь, наприклад, слово Zamyla збережено у 6 байтах. Але нам потрібен ще один байт, для позначення кінця рядка:
Байт пам'яті, де збережено перший символ рядка, Z, позначено як s, оскільки ми назвали наш рядок s у коді вище. Далі, після останнього символу, a, ми маємо один байт з усіма 0 для позначення кінця рядку. І ьайт з усіма 0 називається нульовим символом, який можна також позначати як \0.
Якби ми хотіли написати власну версію strlen, наприклад, то нам треба знати наступне:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt for user's name
string s = get_string("Name: ");
// Count number of characters up until '\0' (aka NUL)
int n = 0;
while (s[n] != '\0')
{
n++;
}
printf("%i\n", n);
}
Тут ми перебираємо кожен символ рядка s, використовуючи той же синтаксис, що й для доступу до елементів масивів, і ми інкрементуємо лічильник, n, поки символ не дорівнюватиме нульовому символу, \0. Якщо він дорівнює, значить, ми в кінці рядка й можемо вивести значення n.
Оскільки ми знаємо, що кожен символ має числове, ASCII значення, то можна вивести й таке:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("String: ");
for (int i = 0; i < strlen(s); i++)
{
int c = (int) s[i];
printf("%c %i\n", s[i], c);
}
}
За допомогою (int) s[i], wми беремо значення s[i] та перетворюємо символьний тип на цілочисельний. Після цього ми можемо вивести як символи, так і їхні числові значення.
Технічно ми навіть можемо зробити printf("%c %i\n", s[i], s[i]);, а printf розпізнає значення s[i] як ціле число.
Тепер ми можемо поєднати те, що ми розглянули, аби створити програму, яка замінює маленькі літери на великі:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("Before: ");
printf("After: ");
for (int i = 0, n = strlen(s); i < n; i++)
{
if (s[i] >= 'a' && s[i] <= 'z')
{
printf("%c", s[i] - ('a' - 'A'));
}
else
{
printf("%c", s[i]);
}
}
printf("\n");
}
Спочатку ми отримуємо рядок s. Тоді, для кожного символу в рядку, якщо він в нижньому регістрі (його значення знаходиться між a та z), ми конвертуємо його на велику літеру. В іншому випадку, ми його просто виводимо.
Ми можемо перетворити малі літери у великі шляхом віднімання різниці між малою a та великою A. (Ми знаємо, що у малих літер більші значення, тому можна відняти різницю між ними та перетворити малі літери на великі.)
Також є функції з бібліотек, які ми можемо використати для досягнення того ж ефекту:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("Before: ");
printf("After: ");
for (int i = 0, n = strlen(s); i < n; i++)
{
if (islower(s[i]))
{
printf("%c", toupper(s[i]));
}
else
{
printf("%c", s[i]);
}
}
printf("\n");
}
Можна використати функції islower() та toupper() з бібліотеки ctype. (Про це можна довідатись виключно з документації до бібліотеки, яку інші люди написали задовго до нас.)
Ми можемо використовувати програму командного рядка, man, щоб прочитати інструкцію з використання інших програм, якщо така існує. Наприклад, ми можемо запустити man toupper для перегляду документації для цієї функції. Ми побачимо, що toupper повертає символ таким, яким він є, якщо він не написаний маленькими літерами, тож отримаємо:
for (int i = 0, n = strlen(s); i < n; i++)
{
printf("%c", toupper(s[i]));
}
Ми використовували такі програми як make та clang, які приймають додаткові слова після своєї назви в командному рядку. Але насправді, наші програми також можуть приймати аргументи командного рядка.
У argv0.c, ми змінюємо вигляд нашої функції main:
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
if (argc == 2)
{
printf("hello, %s\n", argv[1]);
}
else
{
printf("hello, world\n");
}
}
argc та argv - це дві змінні, які одержить функція main коли ми запустимо нашу програму з командного рядка. argcargc – це лічильник аргументів, тобто їх кількість, а argv - масив рядків, що є аргументами. Найперший аргумент, argv[0], буде назвою нашої програми (найперше надруковане слово, наприклад, ./hello). У цьому прикладі ми перевіримо, чи маємо ми два аргументи, і, якщо так, виведемо другий.
Ми можемо вивести кожен аргумент, один за одним:
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
for (int i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
}
Ми також можемо вивести кожен символ кожного аргументу:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(int argc, string argv[])
{
for (int i = 0; i < argc; i++)
{
for (int j = 0, n = strlen(argv[i]); j < n; j++)
{
printf("%c\n", argv[i][j]);
}
printf("\n");
}
}
За допомогою argv[i], ми отримуємо поточний аргумент із масиву аргументів, а з argv[i][j], символ з цього рядка.
Якби ми хотіли надіслати комусь повідомлення, ми б, можливо, захотіли його зашифрувати, тобто певним чином змінити повідомлення, щоб іншим було важко його прочитати. Оригінальний текст тоді ми назвемо звичайний текст, а текст після шифрування - зашифрований текст.
Таке повідомлення, як HI! може бути перетворене в ASCII, 72 73 33. Проте, кожен може з легкістю виконати цю дію і в зворотному порядку.
Розглянемо приклади шифрів з історії, починаючи ще з Першої світової війни, до поеми про поїздку Пола Рівера.
Загалом шифрування потребує не тільки самого тексту на вхід, а й додаткової інформації. Потрібен ключ, іноді це просто секретне число. Відкритий текст може бути зашифрований та розшифрований за допомогою ключа за певним алгоритмом.
Наприклад, якщо ми хочемо надіслати текст I L O V E Y O U, ми спочатку можемо його перетворити на ASCII: 73 76 79 86 69 89 79 85. Тоді, ми можемо зашифрувати його ключем 1 простим алгоритмом, де ми лише додаємо ключ до значення кожного елемента: 74 77 80 87 70 90 80 86. Після цього, якщо хтось перетворить ASCII назад в текст, то отримає J M P W F Z P V. Для розшифровки потрібно буде здогадатися значення кожної букви, методом спроб та поразок, але напевне знати без ключа неможливо. Цей алгоритм відомий під назвою шифр Цезаря.
Виявляється, що ми можемо вказувати на помилки в нашій програмі, повертаючи значення з функції main:
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("missing command-line argument\n");
return 1;
}
printf("hello, %s\n", argv[1]);
return 0;
}
Значення, що повертається main в нашій програмі, називається кодом виходу, і ми можемо це побачити в командному рядку. Якщо ми запустимо нашу програму через ./exit, можемо потім набрати echo $?, яка виведе повернене останньою програмою значення.
Коли ми почнемо писати складніші прошрами, то такі коди виходу допоможуть нам вчасно зрозуміти, що пішло не так, навіть якщо вони невидимі або незрозумілі для користувача.
Сьогодні говоритимемо про наступне:
Сьогодні говоритемемо про наступне:
Виявляється, що ми можемо вказувати на помилки в нашій програмі, повертаючи значення з функції main:
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("missing command-line argument\n");
return 1;
}
printf("hello, %s\n", argv[1]);
return 0;
}
Значення, що повертається main в нашій програмі, називається кодом виходу, і ми можемо це побачити в командному рядку. Якщо ми запустимо нашу програму через ./exit, можемо потім набрати echo $?, яка виведе повернене останньою програмою значення.
Коли ми почнемо писати складніші прошрами, то такі коди виходу допоможуть нам вчасно зрозуміти, що пішло не так, навіть якщо вони невидимі або незрозумілі для користувача.
З масивами ми можемо розв’язувати значно цікавіші задачі. Наприклад, ми можемо розглядати масиви як шафки, за дверима кожної з яких знаходиться якесь значення – ціле число чи символ. До того ж, комп’ютер здатний розглядати одну шафку або значення за раз.
Якби ми мали список чисел та хотіли знайти в ньому певне число, найкраще що можна зробити, це переглянути його значення один за одним або в довільному порядку.
Проте якби ми знали, що список був відсортований, то спочатку могли б поглянути в його середину і, відповідно, потім рухались би вліво або вправо.
Разом з добровольцями ми розглянули, як можна відсортувати список.
Наші добровольці почали у такому довільному порядку:
6 5 1 3 7 8 4 2
Ми поглянули на перші два числа та переставили їх, щоб вони були у правильному порядку:
5 6 1 3 7 8 4 2
• Потім ми розглянули наступну пару чисел, 6 та 1, та переставили їх місцями:
5 1 6 3 7 8 4 2
Ми повторюємо цю дію, поки найбільше число після першого проходу не опиниться з правого боку в кінці рядка:
5 1 6 3 7 4 2 8
(У лекції, цифра 1 була випадково поставлена занадто далеко!)
Ми повторюємо і щоразу, коли ми робимо один прохід, наступне найбільше число стає справа в кінці:
1 5 3 6 4 2 7 8
Нарешті наш список повністю відсортований. Спочатку ми порівняли 7 пар чисел, потім – 6 пар.
Ми повторно перемішали числа:
2 4 8 5 7 1 3 6
І цього разу ми шукали найменше число в списку та переміщували його в лівий бік:
1 4 8 5 7 2 3 6
1 2 8 5 7 4 3 6
1 2 3 5 7 4 8 6
Щоразу ми обирали найменше число і переставляли його з числом, яке знаходиться зліва у невідсортованій частині списку.
За допомогою цього алгоритму ми все ще проглядаємо список n - 1 разів, де n кількість людей, та нам потрібно порівняти кожне число, з найменшим.
Давайте спробуємо розв’язати це формальніше. Перший алгоритм, сортування бульбашкою, складається з порівнювання пар чисел, що знаходяться безпосередньо одна біля одної, поки найбільше число буде знаходитися справа в кінці. Ми можемо це записати в псевдокоді:
повторити поки не залишиться пробілів
для i від 0 до n-2
якщо i-ий та i+1-ий елементи не відсортовані
поміняти їх місцями
А сортування вибором може виглядати ось так:
для i від 0 до n-1
знайти найменший елемент між i-им та n-1-им
поміняти місцями найменший та i-ий елементи
Для першої перестановки нам потрібно зробити n - 1 порівнянь, щоб знайти найменше число. В кожному наступному проході ми зробили меншу кількість порівнянь, оскільки ми вже перемістили деякі цифри ліворуч:
(n – 1) + (n – 2) + ... + 1
n(n – 1)/2
(n^2 – n)/2
n^2 / 2 – n/2
Кожен наступний рядок стає простіше, і врешті ми отримуємо n^2 / 2 – n/2 як кількість порівнянь, які треба виконати. В комп'ютерних науках ми використовуємо O, нотація велике O для більшого спрощення, щоб сказати, що наш алгоритм має O(n^2) кроків, “порядку n в квадраті”. Це через те, що з ростом n лише n^2 має значення.
Наприклад, якщо б n був 1,000,000, ми б отримали:
n^2 / 2 – n/2
1,000,000^2 / 2 – 1,000,000/2
500,000,000,000 – 500,000
499,999,500,000
що є того ж порядку, що й n2.
• Виявляється, що є інші поширені порядки величин складності:
O(n2)
O(n log n)
O(n)
O(log n)
O(1)
Пошук в телефонній книзі по одній сторінці за раз має тривалість роботи O(n), оскільки для кожної сторінки потрібна одна дія. Використання бінарного пошуку могло б мати тривалість роботи O(log n), оскільки ми б розділяли задачу навпіл щоразу.
Давайте візьмемо інший масив чисел, але цього разу використаємо пустий масив такого ж розміру, як наш робочий:
4 2 7 5 6 8 3 1
_ _ _ _ _ _ _ _
Оскільки у нас 8 чисел, давайте поглянемо на першу половину, тобто на перші 4. Відсортуємо це рекурсивно, подивимося на ліву половину. Маючи 2 числа, 4 2, дивимося на ліву половину (відсортовану), та праву (відсортовану), та об'єднаємо їх, відсортувавши: 2 4. Ми перенесемо їх у наш другий масив:
_ _ 7 5 6 8 3 1
2 4 _ _ _ _ _ _
Повторимо це для правої частини першої половини:
_ _ | _ _ 6 8 3 1
2 4 | 5 7 _ _ _ _
потім об'єднаємо ці половини, щоб отримати відсортовану ліву частину:
_ _ _ _ 6 8 3 1
_ _ _ _ _ _ _ _
2 4 5 7 _ _ _ _
Повторимо те саме для правої частини:
_ _ _ _ | _ _ _ _
_ _ _ _ | _ _ _ _
2 4 5 7 | 1 3 6 8
Тепер можемо об'єднати обидві половини:
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
1 2 3 4 5 6 7 8
Кожне число було переставлене тричі, через те що ми поділили 8 на 2 три рази, або log n раз. Тому цей алгоритм займає O(n log n) часу щоб відсортувати список.
Розгляньте приклад Sorting Algorithms Animations та відео What different sorting algorithms sound like.
Ми детально розглянули процес компіляції, що складається з чотирьох кроків:
Спершу наш вихідний код попередньо оброблюється – відбувається препроцесінг, таким чином будь-які заголовкові файли на кшталт stdio.h які ми включаємо до коду, насправді включаються.
На наступному етапі наш код компілюється в код асемблера, тобто такі команди, які наш процесор може зрозуміти.
Далі цей код асемблера збирається у двійковий файл, що відповідає вище зазначеним командам асемблера.
o На кінцевому етапі ті скомпільовані файли бібліотеки, які ми хочемо включити, наприклад cs50.c або printf.c, лінкуються, або зв'язуються з нашою програмою.
Ми також розглянули кілька корисних інструментів:
help50, допомагає зрозуміти повідомлення про помилки
printf, допомагає зрозуміти програму під час її виконання
style50, перевіряє стиль написання нашого коду, щоб він легше читався та був краще узгодженим
Перше завдання цього тижня ми виконуємо у CS50 Lab. Нагадуємо, що для використання цього середовища вам знадобиться обліковий запис у GitHub. Для виконання інших завдань тижня вам знадобиться CS50 IDE.
Розв’яжіть наведені задачі:
Whodunit (на оцінку)
Використовуючи CS50 IDE, виконайте одне з наступних завдань (на оцінку):
Resize (простіший варіант)
Resize (складніший варіант)
Recover (на оцінку)
Перекладені пояснення до завдань, відео з українськими субтитрами та можливість надіслати завдання нам на перевірку ви знайдете у наступних вкладинках цього розділу.
CS50 IDE схожий на CS50 Sandbox, проте має більше можливостей. CS50 IDE – це онлайн середовище розробки з редактором коду та вікном термінала, а також інструментами для дебаггінга та колаборації:
Після того, як ми авторизуємося у середовищі, з’являється робочий простір, що схожий на CS50 Sandbox, проте тепер він зберігається в нашому обліковому записі.
Ми можемо створити новий файл, виконавши такі команди File > New File (або натиснути на зелений плюс), потім виконуємо такі команди File > Save для того, щоб зберегти цей файл як hello.c у теці ~/workspace/. А тепер можемо написати просту програму:
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
}
Ми повинні вручну зберегти це, виконавши такі команди File > Save або за допомогою гарячих клавіш.
Тепер у вікні термінала, що розташоване нижче, ми вводимо make hello і ./hello, щоб побачити, як працює наша програма.
Значок теки, розташований у верхньому лівому куті робочої області, показує усі файли, що знаходяться у каталозі (теці) з іменем ~/workspace/, ми можемо створювати теки і файли у цій теці. Значок ~ позначає наш домашній каталог у цьому середовищі, тобто всі файли що, пов’язані із нашим обліковим записом; а workspace - це тека всередині ~, яку ми можемо використовувати. (У каталозі ~ також знаходяться інші конфігураційні файли нашого облікового запису, проте нам не варто звертати на них увагу.)
У терміналі ми бачимо ~/workspace/ $ . Як і у попередніх випадках, частина запиту командного рядка $ дозволяє вводити після неї будь-яку команду, проте перша частина вказує, в якому каталозі знаходиться наш термінал. Наприклад, якщо ми надрукуємо ls, то побачимо текстову версію каталогу workspace. А команда ./hello, в свою чергу, відповідає файлу з іменем hello у ., тобто у поточній папці.
Ми можемо змінити наш каталог за допомогою команди cd, і якщо ми вводимо cd src3 (припускаємо, що в нас уже є тека з назвою src3), то побачимо що запит командного рядка змінився на ~/workspace/src3/ $ .
Ми можемо видалити файл і теки у графічному дереві файлів натиском правої кнопки миші по ним, як нам може бути вже відомо. Проте ми можемо зробити те ж саме у командному рядку командою rm hello, яка видалить файли. Команда видасть запит підтвердження: ми можемо надрукувати yes чи y (або ж n, якщо ми передумали).
Ми можемо створювати каталоги за допомогою команди mkdir test, або ж видаляти їх командою rmdir.
У CS50 IDE ми додали ще один новий інструмент, check50. Як і у випадку зі style50, ми використовуємо цей інструмент для автоматичної перевірки коректності вашої програми шляхом передачі в програму вхідних даних і аналізу вихідних даних.
Після того, як ви виконали задачі з домашніх завдань та власноруч перевірили з кількома варіантами вхідних даних, ми можемо ввести check50 cs50/2018/fall/hello. cs50/2018/fall/hello – індикатор тієї програмної специфікації, яка повинна бути перевірена check50. Щойно ми виконаємо цю команду, то побачимо, що check50 завантажує наш код і перевіряє його.
Тепер ми також зможемо застосовувати вбудований у CS50 IDE інструмент під назвою debugger.
Після компіляції коду ми можемо запустити команду debug50 ./hello, яка спочатку скаже нам встановити точку переривання. Точка переривання позначає той рядок коду, на якому debugger повинен призупинити нашу програму до того, як ми вирішимо її продовжувати. Наприклад, ми можемо натиснути зліва від рядка нашого коду, і там з’явиться червоне коло:
Після цього, якщо ми знову запустимо debug50 ./hello то побачимо, що справа відчиниться панель debugger:
Ми бачимо, що змінна name, яку ми створили, знаходиться у секції Local Variables, що її значення 0x0 (тобто null), і тип string, як ми й очікували.
Наша точка переривання призупинила програму перед 6 рядком, для продовження виконання на панелі debugger присутні кілька засобів управління. Блакитний трикутник продовжить роботу нашої програми до наступної точки переривання. Вигнута стрілка справа від трикутника перестрибне через рядок: виконає його і зупинить програму одразу ж після цього. Спрямована вниз стрілка «зайде» в рядок, якщо він містить викликану функцію. А стрілка, спрямована вгору і направо «вийде» з функції, якщо ми знаходимося в якійсь з них.
Отже, ми використовуємо вигнуту стрілку, щоб запустити наступний рядок і побачити, що після цього зміниться. Після того, як ми вводимо своє ім’я, то бачимо, що змінна name також оновлюється у debugger.
Ми можемо зберегти собі багато часу у майбутньому, якщо виділимо трохи цього ресурсу зараз для вивчення роботи debugger!
Сьогодні говоритимо про наступне:
У CS50 IDE ми додали ще один новий інструмент, check50. Як і у випадку зі style50, ми використовуємо цей інструмент для автоматичної перевірки коректності вашої програми шляхом передачі в програму вхідних даних і аналізу вихідних даних.
Після того, як ви виконали задачі з домашніх завдань та власноруч перевірили з кількома варіантами вхідних даних, ми можемо ввести check50 cs50/2018/fall/hello. cs50/2018/fall/hello – індикатор тієї програмної специфікації, яка повинна бути перевірена check50. Щойно ми виконаємо цю команду, то побачимо, що check50 завантажує наш код і перевіряє його.
Тепер ми також зможемо застосовувати вбудований у CS50 IDE інструмент під назвою debugger.
Після компіляції коду ми можемо запустити команду debug50 ./hello, яка спочатку скаже нам встановити точку переривання. Точка переривання позначає той рядок коду, на якому debugger повинен призупинити нашу програму до того, як ми вирішимо її продовжувати. Наприклад, ми можемо натиснути зліва від рядка нашого коду, і там з’явиться червоне коло:
Після цього, якщо ми знову запустимо debug50 ./hello то побачимо, що справа відчиниться панель debugger:
Ми бачимо, що змінна name, яку ми створили, знаходиться у секції Local Variables, що її значення 0x0 (тобто null), і тип string, як ми й очікували.
Наша точка переривання призупинила програму перед 6 рядком, для продовження виконання на панелі debugger присутні кілька засобів управління. Блакитний трикутник продовжить роботу нашої програми до наступної точки переривання. Вигнута стрілка справа від трикутника перестрибне через рядок: виконає його і зупинить програму одразу ж після цього. Спрямована вниз стрілка «зайде» в рядок, якщо він містить викликану функцію. А стрілка, спрямована вгору і направо «вийде» з функції, якщо ми знаходимося в якійсь з них.
Отже, ми використовуємо вигнуту стрілку, щоб запустити наступний рядок і побачити, що після цього зміниться. Після того, як ми вводимо своє ім’я, то бачимо, що змінна name також оновлюється у debugger.
Ми можемо зберегти собі багато часу у майбутньому, якщо виділимо трохи цього ресурсу зараз для вивчення роботи debugger!
До цього ми використовували так корисні функції з бібліотеки CS50, як get_int чи get_string, щоб отримати вхідні дані певного типу від користувача. Ці функції загалом непросто написати, тому що ми хочемо запитувати користувачів знову і знову, якщо їхні вхідні дані не є допустимими.
Сьогодні ми розглянемо тип string. Як ми вже дізналися, рядок – це просто масив символів, розташованих послідовно. Але давайте дізнаємося, що ж таке насправді змінна string.
Відкриємо compare0.c:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Get two integers
int i = get_int("i: ");
int j = get_int("j: ");
// Compare integers
if (i == j)
{
printf("same\n");
}
else
{
printf("different\n");
}
}
Як і можна було очікувати, якщо ми введемо однакові значення для i та j, то побачимо, що вони однакові.
У compare1.c, спробуємо виконати те ж саме, але вже з рядками:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Get two strings
string s = get_string("s: ");
string t = get_string("t: ");
if (s == t)
{
printf("same\n");
}
else
{
printf("different\n");
}
}
Хмммм, незалежно від того, що ми вводимо у наших рядках, програма вважає, що вони відрізняються.
Виявляється, що насправді string не є типом даних у С. Слово «рядок» використовується у комп’ютерних науках, проте зберігати рядки у С неможливо жодним чином. Натомість, ми визначаємо цей тип у бібліотеці CS50.
Пам’ятайте, що рядки – просто масив символів, тому коли ми виконуємо програму compare1, то вхідними даними користувача будуть два рядки, які у пам’яті можуть зберігатися ось так:
Кожний символ – один байт; і десь у нашій пам’яті зберігаються байти, які містять значення для кожного рядка.
Виявляється, кожний байт у пам’яті має числове розташування або ж адресу. Наприклад, символ B може мати адресу 100, а V може опинитися у 900 (це залежить від того, які частини пам’яті були доступні, тобто вільні):
Зауважте, що оскільки кожен рядок – масив символів, то усі символи в межах певного рядка мають послідовні адреси, оскільки вони зберігаються поряд у пам’яті. Проте власне рядки можуть мати адреси, які дуже сильно відрізняються.
Отже, get_string насправді повертає лише адресу першого символу в рядку. (Ми можемо сказати, де він закінчується, поглянувши на символ null, або \0.) Тепер ми можемо зробити висновок, що порівняння двох «рядків» насправді порівнює дві адреси (які завжди будуть відрізнятися, оскільки get_string зберігає вхідні дані щоразу у новому місці), навіть якщо символи, що зберігаються за цими адресами, однакові.
Інші типи даних у С, як int чи float, зазвичай передаються і зберігаються у вигляді їхніх значень, оскільки вони завжди представлені фіксованим числом байтів. А рядки, на противагу, передаються у вигляді власних адрес, оскільки вони можуть бути дуже довгими.
Якщо ми хочемо порівняти два рядки, то, здається, нам потрібно порівнювати кожний символ окремо:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
bool compare_strings(string a, string b);
int main(void)
{
// Get two strings
string s = get_string("s: ");
string t = get_string("t: ");
// Compare strings for equality
if (compare_strings(s, t))
{
printf("same\n");
}
else
{
printf("different\n");
}
}
bool compare_strings(string a, string b)
{
// Compare strings' lengths
if (strlen(a) != strlen(b))
{
return false;
}
// Compare strings character by character
for (int i = 0, n = strlen(a); i < n; i++)
{
// Different
if (a[i] != b[i])
{
return false;
}
}
// Same
return true;
}
Ми пишемо функцію під назвою compare_strings, яка приймає два рядки як аргументи та повертає bool, тобто булевий вираз.
Спершу ми порівнюємо довжину двох рядків і робимо return false якщо вони не однакові. Після цього ми можемо перевірити кожен символ і робимо return false якщо ми дійдемо до пари символів, які не збігаються.
Ми також повинні не забути додати вгору прототип bool compare_strings(string a, string b); .
string – синонім до char *. * у С (що також залежно від контексту позначає множення) означає, що типом даних є адреса. Таким чином char * – адреса для char. Більш формально такий тип змінної називається вказівник.
Тепер ми можемо замінити char * там, де ми використовували string:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
bool compare_strings(char *a, char *b);
int main(void)
{
// Get two strings
char *s = get_string("s: ");
char *t = get_string("t: ");
// Compare strings for equality
if (compare_strings(s, t))
{
printf("same\n");
}
else
{
printf("different\n");
}
}
bool compare_strings(char *a, char *b)
{
// Compare strings' lengths
if (strlen(a) != strlen(b))
{
return false;
}
// Compare strings character by character
for (int i = 0, n = strlen(a); i < n; i++)
{
// Different
if (a[i] != b[i])
{
return false;
}
}
// Same
return true;
}
Виявляється, що у string.h включена бібліотечна функція під назвою strcmp, написана іншими багато років тому, що порівнює два рядки:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Get two strings
char *s = get_string("s: ");
char *t = get_string("t: ");
// Compare strings for equality
if (strcmp(s, t) == 0)
{
printf("same\n");
}
else
{
printf("different\n");
}
}
Значення, що повертає strcmp, на основі документації на кшталт CS50 Reference, буде 0 якщо рядки рівні, або інші значення, якщо вони відрізняються.
Ми також повинні проводити перевірку на помилки, на які ми раніше не звертали увагу.
get_string повинна повертати адресу до першого байту рядка, але іноді вона може повертати NULL, недійсну адресу, яка позначає, що щось пішло не так. (І ця адреса має значення 0, спеціальна адреса, що не використовується для зберігання жодних даних.)
Для перевірки наявності помилок ми можемо зробити таке:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Get a string
char *s = get_string("s: ");
if (s == NULL)
{
return 1;
}
// Get another string
char *t = get_string("t: ");
if (t == NULL)
{
return 1;
}
// Compare strings for equality
if (strcmp(s, t) == 0)
{
printf("same\n");
}
else
{
printf("different\n");
}
return 0;
}
Якщо з якоїсь причини get_string не повертає дійсну адресу, ми власноруч повернемо код виходу 1, щоб указати, що відбулася помилка. Якщо ми продовжимо, то можливо відбудеться помилка сегментації, ми намагалися отримати доступ до пам’яті, до якої доступу не маємо (наприклад, до адреси NULL).
Ми можемо спростити умову до просто if (!s), оскільки «не s» буде «не 0», коли s – це NULL, що врешті вираховується у true.
Тепер давайте спробуємо скопіювати рядок:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Get a string
string s = get_string("s: ");
// Copy string's address
string t = s;
// Capitalize first letter in string
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// Print string twice
printf("s: %s\n", s);
printf("t: %s\n", t);
}
Ми беремо рядок s, копіюємо значення s у t. Після цього ми замінюємо великою першу літеру у рядку t.
Проте коли ми запускаємо нашу програму, то бачимо, що і s, і t тепер починаються з великих літер.
Оскільки ми надали однакового значення s і t, то вони стали покажчиками одного й того ж символу, тому ми замінили великими літерами один і той самий символ:
Для того, щоб насправді скопіювати рядок, попрацювати доведеться більше:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Get a string
char *s = get_string("s: ");
if (!s)
{
return 1;
}
// Allocate memory for another string
char *t = malloc((strlen(s) + 1) * sizeof(char));
if (!t)
{
return 1;
}
// Copy string into memory
for (int i = 0, n = strlen(s); i <= n; i++)
{
t[i] = s[i];
}
// Capitalize first letter in copy
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// Print strings
printf("s: %s\n", s);
printf("t: %s\n", t);
// Free memory
free(t);
return 0;
}
Ми створюємо нову змінну t типу char * за допомогою char *t. Тепер ми хочемо, щоб вона вказувала на новий блок пам’яті, що є достатньо великим для збереження копії рядка. За допомогою malloc, ми можемо виділити певну кількість байтів у пам’яті (яка ще не використовується для збереження інших даних), і ми передаємо ту кількість байтів, яку хочемо. Ми вже знаємо довжину s, тому ми додаємо 1 до термінального нуль-символу і множимо отримане на sizeof(char) (в результаті чого ми отримуємо кількість байтів для кожного окремого символу) для того, щоб запевнитися, що ми маємо достатньо пам’яті. Отже, наш остаточний рядок коду виглядає наступним чином char *t = malloc((strlen(s) + 1) * sizeof(char));.
Тепер ми копіюємо кожний символ окремо, тому тепер ми можемо замінити великою літерою лише першу літеру рядка t. Ми використовуємо i <= n, оскільки хочемо піднятися до однієї останньої n, щоб запевнитися, що ми копіюємо термінальний символ у рядку. Врешті-решт, після завершення попередніх дій, ми викликаємо free(t), яка сигналізує нашому комп’ютеру, що ці байти більш не використовуються програмою, тому ці байти у пам’яті можуть бути знову використані.
Ми також можемо використовувати бібліотечну функцію strcpy про яку можна дізнатися з документації, для копіювання рядка.
Витік пам’яті відбувається, коли ми виділяємо все більше і більше пам’яті для використання, проте не звільняємо її. В результаті наш комп’ютер починає працювати все повільніше і повільніше (оскільки він повинен компенсувати скорочення вільної пам’яті).
Давайте поглянемо, чому можуть виникати труднощі з отриманням вхідних даних від користувача:
#include <stdio.h>
int main(void)
{
int x;
printf("x: ");
scanf("%i", &x);
printf("x: %i\n", x);
}
scanf – функція, що отримує вхідні дані від користувача відповідно до певного формату. Ми передаємо %i на позначення того, що ми очікуємо на ціле число, &x ми використовуємо для отримання адреси x, тобто scanf може розмістити значення у правильному місці пам’яті.
Давайте тепер спробуємо отримати рядок:
#include <stdio.h>
int main(void)
{
char *s;
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
Оскільки ми не виділили пам’яті для байтів рядка, scanf не має місця для збереження вхідних даних.
Ми можемо виділити певну кількість байтів у вигляді масиву символів:
#include <stdio.h>
int main(void)
{
char s[5];
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
Тепер у нас є 5 байтів, в яких ми можемо зберігати вхідні дані.
Зауважте, що ми можемо передавати s як адресу, оскільки масиви можуть розглядатися як вказівники на перший елемент масиву.
Але якщо ми хочемо ввести значно довший рядок, врешті-решт відбудеться «помилка сегментації», за якої ми намагаємося отримати доступ до пам’яті, до якої доступу не маємо або не повинні мати. Виявляється, що scanf не знає скільки пам’яті виділено, отже, вона продовжує запис до пам’яті, починаючи з адреси s, такого об’єму вхідних даних, який був введений, навіть якщо було виділено менший об’єм пам’яті. get_string вирішує для нас цю проблему і виділяє достатній об’єм пам’яті. (І якщо вас це надзвичайно зацікавило, вихідний код для бібліотеки CS50 є у відкритому доступі!)
Щоб пов’язати все зазначене вище, пригадайте, що у комп’ютерах є фізичні мікросхеми для оперативної пам'яті (RAM), що зберігають усі наявні байти. Кожен байт має окрему адресу. Це ми можемо побачити за допомогою addresses.c:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Get two strings
string s = get_string("s: ");
string t = get_string("t: ");
// Print strings' addresses
printf("s: %p\n", s);
printf("t: %p\n", t);
}
У цьому випадку ми кажемо printf розглядати s та t як вказівники за допомогою %p, тому ми бачимо такі адреси, як 0x2331010 і 0x2331050.
Значення надзвичайно великі (тому що у пам’яті багато комірок) і вони зазвичай записуються у шістнадцятковій системі числення. Як двійкова і десяткова системи числення, шістнадцяткова - це спосіб представлення чисел: в ній є 16 можливих значень для однієї цифри, 0-9 і A-F. (Просто так сталося, що адреси для s і tне мали абеткових символів.) Значення у шістнадцятковій системі зазвичай починається з 0x, для позначення системи числення.
Раніше ми бачили значення 0x0 у дебаггері для змінної name, а потім інше значення після вводу рядка, що і було адресою нашого рядка.
Ми можемо подивитися на приклад переведення трьох байтів з десяткової системи у двійкову та у шістнядцяткову:
255 216 255
11111111 11011000 11111111
f f d 8 f f
Оскільки кожна цифра у шістнадцятковій системі має 16 можливих значень, що перетворюються на 4 двійкових цифри, кожний байт може бути виражений як дві шістнадцяткові цифри 0xff і 0xd8. Чотири 1у двійковій - це 16 у десятковій і f у шістнадцятковій системі.
У нас є два напої: молоко і апельсиновий сік, кожен з яких знаходиться у чашці. Ми хочемо обміняти напої між цими двома чашками, проте не можемо цього зробити без третьої чашки для переливання одного з напоїв у першу чергу.
Припустимо, що тепер ми хочемо обміняти значення двох цілих чисел.
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
Ми можемо виконати цю задачу дуже просто, якщо матимемо третю змінну як тимчасовий простір для зберігання.
Проте якщо ми спробуємо застосувати цю функцію у програмі, то не побачимо жодних змін:
#include <stdio.h>
void swap(int a, int b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i, y is %i\n", x, y);
swap(x, y);
printf("x is %i, y is %i\n", x, y);
}
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
Виявляється, що функція swap отримує свої власні змінні: a і b, коли вони передаються, що є копіями x та y, таким чином зміна їх значень не змінює x та y у функції main.
Наша функція swap може працювати шляхом передачі адрес x та y:
#include <stdio.h>
void swap(int *a, int *b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i, y is %i\n", x, y);
swap(&x, &y);
printf("x is %i, y is %i\n", x, y);
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
Адреси x та y передаються з main до swap, а ми використовуємо синтаксис *a, щоб перейти (або ж розіменувати) за вказівником та отримати значення, що там зберігається. Ми зберігаємо це до tmp, а потім ми беремо значення за адресою b та зберігаємо його як значення за адресою a. Врешті, ми зберігаємо значення tmp як значення за адресою b, на чому і завершуємо.
Ми натискаємо зліва від рядка int x = 1 щоб встановити точку переривання програми у виглядік червоного значку, і запускаємо debug50 ./swap знову, аби покроково, один рядок за раз, пройти по нашій програмі. Тепер ми можемо застосувати кнопку «увійти», щоб увійти до функції swap та подивитися, як вона працює.
Всередині пам’яті комп’ютера різні типи даних, які повинні зберігатися для роботи нашої програми, організовані у різних областях:
Область text – скомпільований двійковий код нашої програми. Коли ми запускаємо програму, то цей код завантажується у верхній частині пам’яті.
heap – відкритий простір, в якому malloc може отримати вільну пам’ять для використання нашою програмою.
Область stack використовується функціями нашої програми, коли вони викликаються. Наприклад, функція main знаходиться внизу стека та має змінні x та y. Функція swap коли її викликають, займає певну кількість пам’яті зверху над main, зі змінними a, b, та tmp:
Щойно функція swap поверне значення, пам’ять, яку вона використовувала, звільнюється для наступного виклику функцій; і ми втрачаємо все, що робили, окрім поверненого значення.
Отже, шляхом передачі адрес x та y з main до swap, ми насправді можемо змінити значення x та y.
Глобальні змінні розташовані в областях ініціалізованих та неініціалізованих даних, а змінні середовища з командного рядка також зберігаються у відповідній області.
Давайте поглянемо на частину коду з помилками:
int main(void)
{
int *x;
int *y;
x = malloc(sizeof(int));
*x = 42;
*y = 13;
y = x;
*y = 13;
}
Тут ми оголошуэмо два вказівники з назвами x та y. Ми виділяємо пам’ять для цілого числа для x, проте не для y. Отже, спроба зберегти значення 13 до *y може призвести до помилки сегментації.
Але якщо ми встановимо y однаковим з x, вказуючим на ту ж адресу, то можемо без проблем розмістити значення 13 за цією адресою.
Перегляньте ще одне відео, Pointer Fun with Binky.
Ви можете скористатись веб-сайтом StackOverflow, сайт з питаннями та відповідями, який часто використовують для знаходження відповідей на питання з програмування. Тепер ми розуміємо, що назва сайту походить від відсилки до переповнення стеку або ж викликів завеликої кількості функцій, які не можуть вміститися у пам’яті нашого комп’ютера.
Ми можемо створювати змінні нашого власного типу за допомогою концепції під назвою структури – struct.
Наприклад, якщо ми хочемо зберегти й імена, і гуртожитки кожного окремого студента, то можемо зробити масиви для кожного:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Space for students
int enrollment = get_int("Enrollment: ");
string names[enrollment];
string dorms[enrollment];
// Prompt for students' names and dorms
for (int i = 0; i < enrollment; i++)
{
names[i] = get_string("Name: ");
dorms[i] = get_string("Dorm: ");
}
// Print students' names and dorms
for (int i = 0; i < enrollment; i++)
{
printf("%s is in %s.\n", names[i], dorms[i]);
}
}
Проте, можливо, надалі ми захочемо додати інші фрагменти даних, тому треба запевнитися, що усі масиви мають правильну довжину і дані для однієї особи знаходяться за одним і тим же індексом та ін. Натомість, ми можемо використати структури у файлі struct.h, який містить:
typedef struct
{
char *name;
char *dorm;
}
student;
І файл struct.c, який містить:
#include <cs50.h>
#include <stdio.h>
#include "struct.h"
int main(void)
{
// Space for students
int enrollment = get_int("Enrollment: ");
student students[enrollment];
// Prompt for students' names and dorms
for (int i = 0; i < enrollment; i++)
{
students[i].name = get_string("Name: ");
students[i].dorm = get_string("Dorm: ");
}
// Print students' names and dorms
for (int i = 0; i < enrollment; i++)
{
printf("%s is in %s.\n", students[i].name, students[i].dorm);
}
}
Тепер student – наш власний тип змінної, що містить в собі дві змінні: name та dorm, до яких ми зможемо пізніше отримати доступ за допомогою .name і .dorm.
Ми навіть можемо відкривати і зберігати файли за допомогою фрагментів коду, на кшталт:
FILE *file = fopen("students.csv", "w");
if (file)
{
for (int i = 0; i < enrollment; i++)
{
fprintf(file, "%s,%s\n", students[i].name, students[i].dorm);
}
fclose(file);
}
Це лише короткий огляд того, що ми будемо розглядати у домашньому завданні!
Тепер якщо ми спробуємо збільшити розмір зображення, то у кінцевому результаті побачимо пікселі, з яких воно складається. Проте, оскільки зображення представлено обмеженою кількістю байтів, то ми не маємо можливості побачити деталі, що не були захоплені.
Зображення може бути представлене бітовою картою:
Кожне 1 позначає один білий піксель, а 0 - чорний.
У кольоровому зображені буде використовуватись понад один біт на піксель.
Файл зображення також буде містити особливі значення на початку файлу, щоб програми могли його правильно відкрити. У домашньому завданні ми розглянемо один такий тип файлів зображень - .bmp. Ми також будемо вчитися проводити цифрове налаштування зображень, змінювати їхній розмір чи фільтрувати їх як забажається.
Завершимо цю лекцію уривком з анімаційного серіалу «Футурама» під назвою «Let’s Enhance», який зображує реалістичний процес збільшення зображення
Поняття класу є фундаментальним в ООП і служить основою для створення об'єктів. В описі класу визначаються дані (тобто змінні) і код (тобто методи), який маніпулює цими даними. Об'єкти є екземплярами класу.
Методи і змінні, складові клас, називаються членами класу. При визначенні класу оголошуються дані, які він містить, і код, який маніпулює цими даними. Дані містяться в змінних екземпляра, які визначені класом, а код міститься в методах. У мові С # визначені кілька специфічних різновидів членів класу. До них відносяться: змінні екземпляра, статичні змінні, константи, методи, конструктори, деструктори, індексатори, події, оператори і властивості.
Ініціалізація змінних в об'єкті (як у примірнику класу) проводиться безпосередньо в конструкторі класу. У складі класу може бути визначено декілька конструкторів.
Синтаксис визначення класу:
class імя_класу
{
тип_доступу тип імя_змінної1;
тип_доступу тип імя_змінної2;
...
тип_доступу повертаємий_тип
імя_методу1 (список_параметрів)
{Тіло_методу}
}
де тип_доступу може приймати одне з наступних значень: public, private, protected, internal. Члени класу з типом доступу public є загальнодоступними (тобто доступні з будь-якої точки програми за межами даного класу), з типом доступу protected - всередині членів даного класу і його похідних, з типом доступу private - тільки для інших членів даного класу. Тип доступу internal застосовується для типів, доступних у межах однієї збірки.
Наведемо приклад опису класу:
class Animal
{
public string Name;
private int Weight;
protected int Type;
public int Animal (int W, int T, string N)
{
Weight = W;
Type = T;
Name = N;
}
public int GetWeight ()
{
return Weight;
}
}
Для створення об'єкта необхідно використовувати наступний синтаксис:
імя_класу імя_обєкту = new імя_класу ();
При створенні об'єкта (тобто екземпляра класу) відбувається виклик відповідного конструктора класу.
Поняття конструктора і деструктора.
Під конструктором класу будемо розуміти метод для ініціалізації об'єкту при його створенні. Конструктор має те ж ім'я, що і його клас. У конструкторах тип значення не вказується явно. Конструктори використовуються для присвоювання початкових значень змінним примірника, певним класом, і для виконання будь-яких інших процедур ініціалізації, необхідних для створення об'єкта.
Конструктор існує для будь-якого класу, незалежно від того, визначено він в явному вигляді чи ні. Замовчування мова С # передбачено наявність конструктора, який присвоює нульові значення всім змінним примірники (для змінних типів-значень) та значення null (для змінних посилального типу). У випадку явного визначення конструктора класу конструктор за умовчанням не використовується.
Синтаксис опису конструктора:
імя_класу (список_параметрів)
{Тіло_конструктора}
Під деструкцією будемо розуміти метод, який автоматично викликається при знищенні об'єкта класу (безпосередньо перед початком процедури "складання сміття"). Деструктори не мають параметрів і не повертають значень.
Синтаксис опису деструктора:
~ Імя_класу () {тіло_деструктора}
Під Наслідування будемо мати на увазі властивість, за допомогою якого один об'єкт може набувати властивостей іншого. При цьому підтримується концепція ієрархічної класифікації, що має напрям зверху вниз. При прийнятті концепції наслідування, для новостворюваних об'єктів необхідно визначати тільки ті властивості, які роблять його унікальним у межах свого класу. Об'єкт може успадковувати загальні атрибути від батьківських по відношенню до нього класів.
Синтаксис опису об'єкту:
class імя_классу: імя_батьківського_класу
{
тіло_класу
}
Приклад опису об'єкту:
class Predator: Animal
{
private int Speed;
}
Спадкування формує ієрархію класів на основі відношення часткового порядку ISA ("бути").
Ієрархія може бути побудована і для об'єктів. У цьому випадку вона має структуру, яка будується на основі відношення структурного входження ("частина-ціле"), при якому один об'єкт є частиною іншого.
Порядок виконання роботи:
Розробити методи і властивості для кожного з визначених класів.
Реалізувати програму на C # відповідно до варіанта виконання.
Побудувати ієрархію класів відповідно до варіанта завдання:
Студент, викладач, персона, завідувач кафедрою
Службовець, персона, робітник, інженер
Робочий, кадри, інженер, адміністрація
Деталь, механізм, виріб, вузол
Організація, страхова компанія, нафтогазова компанія, завод
Журнал, книга, друковане видання, підручник
Тест, іспит, випускний іспит, випробування
Місце, область, місто, мегаполіс
Іграшка, продукт, товар, молочний продукт
Квитанція, накладна, документ, рахунок
Автомобіль, поїзд, транспортний засіб, експрес
Двигун, двигун внутрішнього згоряння, дизель, реактивний двигун
Республіка, монархія, королівство, держава
Ссавець, парнокопитне, птах, тварина
Корабель, пароплав, вітрильник, корвет
З кожним алгоритмом, зазвичай, зв'язується інтуїтивне представлення про його складність. Однак, інтуїтивне представлення не дозволяє однозначно вибрати для вирішення конкретної задачі один із множини еквівалентних алгоритмів або визначити можливість застосування даного алгоритму для її вирішення. Тому важливою є формалізація оцінки складності алгоритмів.
Складність алгоритму — це кількісна оцінка ресурсів, що витрачаються алгоритмом. В якості ресурсів можна розглядати людські (на створення і розуміння алгоритму) і обчислювальні (на виконання алгоритму) ресурси. Тому розрізняють описову (інтелектуальну) і обчислювальну складність алгоритму.
Описова складність визначається виходячи з самого запису (тексту) алгоритму. Єдиного критерію її оцінки не існує. Зазвичай, описова складність характеризується довжиною запису алгоритму.
Як обчислювальні ресурси для виконання алгоритму розглядають пам'ять і процесорний час. Тому основними мірами обчислювальної складності алгоритмів є:
ємнісна (просторова) складність, яка характеризує кількість пам'яті, необхідної для виконання алгоритму;
часова складність, яка характеризує кількість часу, необхідного на виконання алгоритму (цей час, як правило, визначається кількістю елементарних операцій, які потрібно виконати для реалізації алгоритму).
Як відомо, комп'ютери мають обмежений обсяг пам'яті. Якщо дві програми реалізують ідентичні функції, то та, яка використовує менший обсяг пам'яті, характеризується меншою ємнісною складністю. Іноді пам'ять є домінуючим чинником в оцінці ефективності програм. Однак в останні роки у зв'язку із швидким її здешевленням ця складова ефективності поступово втрачає своє значення.
Для оцінки часової складності можна просто запустити кожен алгоритм на декількох задачах і порівняти час виконання. Інший спосіб - математично оцінити час виконання підрахунком операцій.
У цьому випадку складність алгоритму (і часова, і ємнісна) описується функцією f(n), де n - розмір вхідних даних (розмірність оброблюваного масиву, кількість слів в тексті, що аналізується, довжина послідовності в алгоритмі, число вершин оброблюваного графа тощо).
Знаходження точної залежності f(n) для конкретного алгоритму - задача досить складна. Найчастіше така докладна оцінка і не потрібна. З цієї причини зазвичай обмежуються лише асимптотичними оцінками швидкості росту цієї функції, які описують граничну поведінку складності алгоритму при збільшенні n (наскільки швидко або повільно зростає ця функція).
Найбільш популярною для оцінювання складності алгоритмів є верхня оцінка - О-нотація. Її позначення: f(n) = О(g(n)).
Ця нотація дозволяє враховувати у функції f(n) лише найбільш значущі елементи, відкидаючи другорядні.
Розглянемо алгоритм обчислення значення багаточлена степеня n в заданій точці x :
+
Pn(x) = anxn + an-1xn-1 + ... + aixi + ... + a1x1 + a0.
Алгоритм 1. Для кожного доданка, окрім a0, піднести x в задану степінь послідовним множенням і домножити на коефіцієнт. Потім доданки скласти.
Обчислення i-го доданку (i = 1 .. n) даним алгоритмом вимагає i множень. Значить, всього 1 + 2 + 3 + ... + n = n(n +1)/2 множень. Крім того, потрібно n +1 додавання. Всього n(n+1)/2 + n + 1 = n2/2 + 3n/2 + 1 операцій.
Алгоритм 2. Винесемо x за дужки і перепишемо багаточлен у вигляді
Pn(x) = a0+ x (a1+ x (a2+ ... x (ai+ .. x (an-1 + anx )))).
Наприклад ,
P3(x) = a3x3 + a2x2 + a1x1 + a0 = a0 + x (a1 + x (a2 + a3x)).
Будемо обчислювати вираз зсередини. Сама внутрішня дужка вимагає 1 множення і 1 додавання. Її значення використовується для наступної дужки. І так, 1 множення і 1 додавання на кожну дужку, яких n-1 штука. І ще після обчислення самої зовнішньої дужки помножити на x і додати a0. Всього n множень + n додавань = 2n операцій.
У функції f(n) = n2/2 + 3n/2 + 1 при досить великих n компонента n2 буде значно перевершувати інші складові, і тому характерна поведінка цієї функції визначається саме цією компонентою. Інші компоненти (порівняно повільно зростаючий доданок 3n/2 +1 і константний множник 1/2) можна відкинути і умовно записати, що дана функція має оцінку поведінки (в сенсі швидкості росту її значень) виду О(n2) [ читається як "О велике від ен квадрат"]. Фраза «складність алгоритму є О(n2)» означає, що при великих n час роботи алгоритму (або загальна кількість операцій) не більше ніж c · n2, де c - якась додатна константа.
Таким чином, O() - "урізана" оцінка часу роботи алгоритму, яку часто набагато простіше отримати, ніж точну формулу для кількості операцій.
Важливе теоретичне і практичне значення має класифікація алгоритмів, яка бере до увагу швидкість зростання цієї функції.
Виділяють такі основні класи алгоритмів:
лінійні: O(n)
Лінійне зростання — подвоєння розміру задачі подвоїть і необхідний час. При n = 1, тобто O(1) - сталий час роботи незалежно від розміру задачі.
логарифмічні: O(logn)
Логарифмічне зростання — подвоєння розміру задачі збільшує час роботи на сталу величину
поліноміальні: O(nm), де m - натуральне число, більше від одиниці (при m = 1 алгоритм є лінійним)
Квадратичне зростання — подвоєння розміру задачі вчетверо збільшує необхідний час.
Кубічне зростання — подвоєння розміру задачі збільшує необхідний час у вісім разів.
експоненційні: O(сn), де с - натуральне число, більше від одиниці
Експоненційне зростання — збільшення розміру задачі на 1 призводить до c-кратного збільшення необхідного часу; подвоєння розміру задачі підносить необхідний час у квадрат.
Для однієї й тієї ж задачі можуть існувати алгоритми різної складності. Наприклад, для попередньої задачі алгоритм 1 має складність О(n2), алгоритм 2 - О(n).
Часто буває і так, що більш повільний алгоритм працює завжди, а більш швидкий - лише за певних умов.
Правила формування оцінки O( ):
При оцінці за функцію береться кількість операцій, що зростає найшвидше.
Тобто, якщо в програмі одна функція, наприклад, множення, виконується O(n) разів, а додавання - O(n2) разів, то загальна складність програми - O(n2), оскільки при збільшенні n додавання стануть виконуватися настільки часто, що будуть впливати на швидкодію куди більше, ніж множення.
При оцінці O( ) константи не враховуються.
+
Нехай один алгоритм робить 2500n + 1000 операцій, а інший - 2n +1. Обидва вони мають оцінку O(n), так як їхній час виконання зростає лінійно.
Який час потрібний для виконання програми, що реалізує певний алгоритм? Чи можна взагалі отримати результати обчислення за даним алгоритмом на комп’ютері? На подібні питання відповідає теорія алгоритмів - розділ інформатики, що займається дослідженням складності алгоритмів для розв'язання задач на основі формально визначених моделей обчислювальних пристроїв.
Що таке складність алгоритму? Інтуїтивно можна виділити такі основні складові складності алгоритму:
1. Логічна складність — кількість людино-місяців, витрачених на створення алгоритму.
2. Статична складність — довжина опису алгоритмів (кількість операторів).
3. Часова складність — час виконання алгоритму.
4. Ємнісна складність — кількість умовних одиниць пам'яті, необхідних для роботи алгоритму.
Складність алгоритму дозволяє визначитися з вибором ефективного алгоритму серед тих, що побудовані для розв’язання конкретної проблеми.
Складність алгоритму – це кількісна характеристика, що відображує споживані алгоритмом ресурси під час свого виконання.
Оцінка складності
Складність алгоритмів зазвичай оцінюють за часом виконання або по використовуваній пам’яті. В обох випадках складність залежить від розмірів вхідних даних: масив з 100 елементів буде оброблений швидше, ніж аналогічний з 1000. При цьому мова йде не про точний час обчислень, який залежить від процесора, типу даних, мови програмування тощо. Оцінюється складність при прагненні розміру вхідних даних до нескінченності.
Часова складність алгори́тму — характеристика продуктивності алгоритму, що визначається кількістю елементарних операцій, які потрібно виконати для реалізації алгоритму.
При цьому вважають, що кожна елементарна операція виконується за однаковий час.
Часову складність оцінюють для найгіршого випадку і визначають як максимальний час, необхідний для обробки алгоритмом будь-якої множини з n елементів.
Часова складність алгоритму зазвичай визначається виразом O (f (n)) (або так званої О—нотації). Вираз O(f(n)) означає, що час виконання алгоритму зростає з тією ж швидкістю, що і функція f (n).
Поширені складності алгоритмів
• Якщо час роботи алгоритму не залежить від обсягу вхідних даних, то його часову складність позначають як O(1); приклад — визначення значення третього елемента масиву, для чого не потрібно ні запам’ятовувати елементи, ні проходити по ним декілька разів. Завжди потрібно просто дочекатися в потоці вхідних даних третій елемент і це буде результатом, на обчислення якого для будь—якої кількості даних потрібний один і той же час.
• Лінійна складність O (n): подвоєння розміру задачі подвоїть і необхідний час; приклади — алгоритм пошуку найбільшого елемента в невідсортованому масиві, для чого потрібно переглянути всі n елементів масиву; алгоритм додавання/віднімання чисел з n цифр.
• Квадратична складність O (n^2): час роботи алгоритму зростає пропорційно квадрату кількості оброблюваних елементів, подвоєння розміру задачі вчетверо збільшує необхідний час; приклад — алгоритм сортування бульбашкою, що виконує два вкладені цикли перебору масиву.
• Кубічна складність O (n^3): подвоєння розміру задачі збільшує необхідний час у вісім разів. Припустимо, певним алгоритмом потрібно виконати 4n^3+7n
умовних операцій, щоб обробити n елементів вхідних даних. При збільшенні n на час роботи буде значно більше впливати зведення n в куб, ніж множення його на 4 або ж додавання 7n.
Приклад:
Нехай дана послідовність з нулів та одиниць і нам потрібно з'ясувати, чи є там хоч одна одиниця. Яку складність матиме алгоритм розв’язання цієї задачі?
Розв’язання. Нехай n – кількість символів в послідовності. Алгоритм буде послідовно перевіряти, чи немає одиниці в поточному місці заданої послідовності, а потім рухатися далі, поки вхід не скінчиться. Оскільки одиниця дійсно може бути тільки одна, для отримання точної відповіді на це питання в гіршому випадку доведеться перевірити всі n символів входу. Таким чином, алгоритм має складність O (n), іншими словами, він лінійний.
Приклад:
Розглянемо код, який для масиву A[n, n] знаходить максимальний елемент у кожному рядку.
for i:=1 to N do begin
max:=A[i,1];
for j:=1 to N do
if A[i,j]>max then max:=A[i,j]
writeln(max);
end;
У цьому алгоритмі змінна і змінюється від 1 до n. При кожній зміні і, змінна j теж змінюється від 1 до n. Під час кожної з n ітерацій зовнішнього циклу, внутрішній цикл теж виконується n раз. Загальна кількість ітерацій внутрішнього циклу дорівнює n* n. Це визначає складність алгоритму O(n^2).
Методи перебирання
Алгоритми перебирання були історично першими алгоритмами, що розв’язують задачу загороджування. Основною метою їхнього створення є точне вирішення задачі про те, які саме частини елементів сцени повинні бути видалені. Тому ці алгоритми краще розроблені для одержання каркасних зображень. В алгоритмах перебирання, як правило, розглядається кожне ребро, що належить кожному з об'єктів сцени, і аналізується його взаємне розташування з усіма гранями кожного з об'єктів, що складають сцену. При цьому можливі наступні ситуації:
1. Ребро знаходиться в тому ж напівпросторі відносно грані, що і спостерігач.
У цьому випадку ребро цілком видиме відносно цієї грані.
2. Ребро цілком розташоване в напівпросторі, що не містить спостерігача.
Спочатку потрібно з'ясувати, чи не закрите воно гранню. При аналізі взаємного розташування проекції ребра і проекції контуру грані на картинну площину досить перевірити, чи розташована перша проекція поза другою або перетинається з нею. У першому випадку ребро видиме відносно грані. В другому випадку перевіряємо, чи лежить перша проекція цілком усередині проекції грані. Якщо так, то ребро цілком закрите гранню і його потрібно виключити з подальшого розгляду. Якщо ж воно частково закрито гранню, то потрібно виявити видимі частини проекції і перейти до аналізу взаємного розташування ребра з черговою гранню.
3. Ребро перетинається з площиною, що містить розглянуту грань.
У цьому випадку варто розбити ребро на дві частини точкою перетинання і застосувати описані вище процедури для кожної з частин. Після завершення процесу будуть відомі усі видимі частини ребра.
Витрати часу роботи такого алгоритму пропорційні добутку кількості ребер на кількість граней у сцені, тобто залежать квадратично від числа елементів сцени. Ніякі додаткові властивості об'єктів, що зображуються, не враховуються.
Кеширування по рівномірній сітці
При аналізі взаємного розташування ребер і граней багатогранника з метою виявлення видимих частин ребер основна частина витрат часу припадає на пошук тих пар ребро-грань, проекції яких мають на картинній площині фактичне перетинання. Ребро і грань, що складають таку пару, назвемо інцидентними.
Приведемо деякі прості необхідні умови інцидентності ребра і грані:
1. Оболонковий тест. Якщо ребро і грань інцидентні, то прямокутні оболонки проекцій ребра і грані на картинну площину з сторонами, рівнобіжними координатним осям, перетинаються. Якщо для пари ребро-грань ця умова не виконана, тобто прямокутні оболонки не перетинаються, то з подальшого аналізу таку пару можна виключити.
2. Розіб'ємо картинну площину за допомогою рівномірної сітки на прямокутні елементи. Назвемо ребро й елемент інцидентними, якщо елемент перетинається з проекцією ребра на картинну площину. Грань і елемент називаються інцидентними, якщо проекція грані перетинається з елементом. Вирахувавши попередньо для кожного елемента сітки всі інцидентні з ним грані, перейдемо до аналізу взаємного розташування ребер і граней. З цією метою для кожного ребра багатогранника знайдемо всі елементи, інцидентні з ним. Далі аналізу підлягають лише ті грані, що мають із ребром загальні інцидентні елементи. Список таких граней для кожного елемента в нас уже є.
Якщо порівняємо приблизно витрати часу на роботу алгоритмів без кеширування і з ним, то виявиться наступне:
При прямому переборі для повного аналізу необхідно операцій порядку О(N2). В алгоритмі з кешируванням кількість операцій, необхідних для повного аналізу видимості ребер, має порядок О(N).
Методи пріоритетів. Найпростіший варіант методу художника
Загальна схема методів пріоритетів складається в спробі попередньо упорядкувати елементи сцени по якійсь ознаці. Звичайно в якості параметра впорядкування вибирається глибина елемента в сцені.
Нехай потрібно зобразити поверхню в ортогональній проекції. Використовуючи стандартну тріангуляцію області завдання функції і кусочно-лінійне наближення, одержимо наближення поверхні у виді багатогранної поверхні, гранями якої є трикутники. Виберемо таку нумерацію вузлів сітки Aij=A(xi,yj), 0 ≤ i ≤ N, 0 ≤ j ≤ M, щоб точка (x0,y0) була найближчою до спостерігача.
Зафарбовуючи грані відповідним кольором, скинемо проекції граней на картинну площину:
for i:=n-1 dowto 0 do
for j:=m-1 downto 0 do
for k:=2 downto 1 do
drawface(f(i,j,k));
При такому упорядкуванні граней багатогранника проекції граней, що лежать ближче до спостерігача, скидаються на картинну площину пізніше граней, що лежать далі від точки спостереження. Більш точно, якщо грань F1 стоїть попереду грані F2 , то знайдеться їхня розділяюча гіперплощина, така що F1 належить одному напівпростору, а F2 і точка спостереження - додатковому напівпростору.
Метод бінарної розбивки простору
Цей метод використовує ідеї, закладені в алгоритмі художника, коли після попередньої обробки (сортування граней по глибині) на картинну площину скидаються грані, починаючи із самих далеких, а проекції граней, розташованих до спостерігача ближче, накладаються на проекції граней, скинутих раніше. При завершенні цього процесу утвориться правильне зображення сцени.
Для впорядкування граней використовується правило, відповідно до якого грань F1 стоїть попереду грані F2 , якщо грань F1 і центр проектування лежать по один бік від площини, що несе грань F2 . Таким чином, визначення порядку проходження граней залежить не тільки від їхнього взаємного розташування в просторі, але і від апріорного порядку, у якому ці грані розглядаються.
Якщо несуча площина перетинає грань, що тестується, то ми розрізаємо її уздовж цієї площини і включаємо в розгляд замість розрізаної дві нові грані.
Виберемо довільно деяку грань. На цьому кроці будемо називати її активною. Розділимо усієї грані, що залишилися, на дві групи: ті, що лежать в одному напівпросторі зі спостерігачем відносно активної грані і ті, що лежать в додатковому напівпросторі. Грані першої групи не можуть загороджуватися активною гранню, і активна грань не може бути загороджена ніякою гранню другої групи. Тому, якби ми спочатку одержали правильне зображення частини сцени, що складається з граней другої групи, потім зобразили б активну грань і, нарешті, побудували правильне зображення частини сцени, що складається з граней першої групи, то в результаті мали б вірне зображення сцени в цілому.
Таким чином, розбивка сцени на дві частини з використанням активної грані дозволяє звести задачу загороджування для вхідної множини граней до розв'язання двох незалежних задач загороджування усередині кожної групи граней. Це спостереження приводить до побудови рекурсивного алгоритму зображення сцени.
Для цього в кожній групі знову довільно виділяємо активну грань і робимо подальше ділення. Процес рекурсивно повторюється, поки не будуть відсортовані усі грані. Ця процедура нагадує відомий алгоритм швидкого сортування Хоара. Результат розбивки множини всіх граней може бути поданий у виді бінарного дерева, що має таку структуру:
У кожному вузлі дерева міститься грань, у лівій гілці дерева – лицьові грані відносно кореневої, у правій гілці – нелицьові. Для одержання зображення з використанням цього дерева досить скинути рекурсивно на площину зображення проекцій граней по наступному правилу:
1. процедура ”Зобразити дерево”,
2. зобразити праве піддерево (якщо воно не порожнє),
3. зобразити кореневу грань,
4. зобразити ліве піддерево (якщо воно не порожнє).
Описаний алгоритм аналізує сцену винятково в об'єктному просторі. При цьому зовсім неважливо, перетинаються грані чи ні і якої вони форми. Однак це є і слабкістю методу – він зовсім не враховує когерентності по видимості частин сцени, якщо вона присутня в сцені, що зображується.
Алгоритми сканування по рядках
Алгоритми сканування по рядках використовують послідовний вивід зображення частини сцени, отриманої в результаті перетинання всієї сцени сімейством горизонтальних площин, що відповідають горизонтальним рядкам розгорнення екрана. Кожна така площина (що називається площиною розгорнення) визначає сукупність відрізків прямих, що виникають у результаті перетинання з нею граней.
Для формування зображення потрібно розв’язати в кожній площині двомірну задачу загороджування, проаналізувавши взаємне розташування відрізків відносно спостерігача на площині сканування.
Аналіз у всіх площинах сканування дозволяє сформувати множину всіх рядків розгорнення зображення і, таким чином, синтезувати зображення в цілому. Цей підхід дозволяє також формувати каркасні зображення.
Цей метод використовує послідовно роботу в об'єктному просторі й у площині зображення, зводячи тривимірну задачу загороджування до послідовності двомірних задач загороджування (для відрізків на площині).
Нехай деяке ребро видиме у своїй кінцевій точці. Доти, поки стан видимості ребра не змінюється, продовжується вивід на картинну площину відрізка перетинання січної площини з гранню, що містить це ребро. Якщо стан видимості ребра усередині поточної скануючої площині змінився, тобто ребро стало невидимим, то, мабуть, ми опинимося в одній із точок зазначеного вище списку. І тепер границею виведеного на екран відрізка буде перетинання скануючої площини із поточним видимим ребром.
+
Метод сканування по рядках дозволяє використовувати відомий в обчислювальній геометрії принцип ”площини, що замітає” і відповідну структуру даних, пристосовану для роботи інформацією, що може динамічно змінюватися, про розташування відрізків перетинання скануючої площини з гранями багатогранника.
Метод сортування включенням (Insertion sort)
RAM - Машина з вільним доступом до пам’яті
Аналіз алгоритму сортування включенням
Асимптотичні позначення
2.1. Сортування включенням Приступаючи до вивчення аналізу алгоритмів, ми розглянемо достатньо простий алгоритм для задачі сортування – сортування методом включення. Нагадаємо формулювання проблеми: Вхід: послідовність n чисел a a an , , , 1 2 K Вихід: перестановка a a an ′, ′ , , ′ 1 2 K вхідної послідовності таким чином, що для всіх її членів виконується співвідношення n a′ ≤ a′ ≤ K ≤ a′ 1 2 . В реальних задачах часто потрібно сортувати не самі цілі числа, а певні значення, що вказують на, можливо, достатньо великі структури даних. Ці значення в такому випадку називаються ключами. Наприклад, в базі даних про працівників компанії таким ключем може бути номер паспорту або водійських прав. Алгоритм сортування включенням ефективно працює при сортуванні невеликої кількості елементів. Сортування включенням нагадує спосіб, яким послуговуються гравці для сортування карт, що вони мають на своїх руках. Нехай спочатку у лівій руці немає жодної карти і всі вони лежать сорочкою догори. Далі зі столу береться по одній карті, кожна з яких розміщується в потрібне для неї місце серед вже обраних карт. Щоби визначити, куди потрібно вставити нову карту, її масть та вага порівнюються з мастю та вагою карт в руці. Порівняння може проводитись, наприклад, зліва направо. В будь-який момент часу карти в руці будуть розсортовані і це будуть ті карти, які спочатку лежали в колоді на столі. Нижче наведений псевдокод методом включення під назвою Insertion_sort. На його вхід подається масив A[1…n], який містить послідовність n чисел для сортування (кількість елементів тут позначена як length[A]).
Вхідні числа сортуються без використання додаткової пам’яті: їх перестановка відбувається всередині масиву, а об’єм використаної для цього додаткової пам’яті не перевищує деяку сталу величину.
По закінченню роботи алгоритму Insertion_sort вхідний масив міститиме відсортовану послідовність. Insertion_sort(A) 1 for j ← 2 to length[A] 2 do key ← A[j] 3 // Включення елементу A[j] у відсортовану послідовність A[1..j-1] 4 i ← j-1 5 while i>0 and A[i]>key 6 do A[i+1] ← A[i] 7 i ← i-1 8 A[i+1] ← key
Інваріанти циклу дозволяють зрозуміти, чи коректно працює алгоритм. Необхідно показати, що інваріанти циклів володіють трьома наступним властивостями.
1. Ініціалізація. Вони виконуються перед першою ініціалізацією циклу.
2. Збереження. Якщо вони істинні перед черговою ітерацією циклу, то вони лишаються істинними й після неї.
3. Завершення. По закінченню циклу інваріанти дозволяють пересвідчитись у правильності алгоритму.
Якщо виконуються перші дві властивості, інваріанти циклу лишаються істинними перед кожною черговою ітерацією циклу. Можна помітити, що перші дві властивості інваріантів циклу схожі з математичною індукцією. Розглянемо, чи зберігаються ці властивості для сортування методом включення. Ініціалізація. Покажемо справедливість інваріанту циклу для першої ітерації, тобто при j=2. Таким чином, підмножина елементів A[1...j–1] складається тільки з одного елементу A[1], який зберігає початкове значення. Збереження. Покажемо, що інваріант циклу зберігається після кожної ітерації. Можна сказати, що в тілі зовнішнього циклу for відбувається зміщення елементів A[j–1], A[j–2], A[j–3], … на одну позицію до тих пір, поки не звільниться відповідне місце для елементу A[j] (рядки 4-7), куди він і розташовується. Завершення. Нарешті, подивимось, що відбувається по завершенню роботи циклу. При сортуванні методом включення зовнішній цикл for завершується, коли j більше за n, тобто коли j = n + 1. Підставив в формулювання інваріанту циклу значення n + 1, отримаємо таке твердження: у підмножині елементів A[1…n] знаходяться ті самі елементи, які були в ньому на початку роботи алгоритму, але вони розташовані у відсортованому порядку. Це і підтверджує коректність алгоритму.
Лекція №3 розбита на дві частини для більш зручного використання. Дана частина присвячена методу сортування злиттям:
Загальний опис методу
Процедура злиття
Аналіз ефективності алгоритму сортуванням методом злиттям
Сьогоднішнє заняття присвячене наступним темам:
Задача пошуку інверсій в масиві
Метод Штрассена добутку матриць
Ви можете скористатись Конспект заняття для детальнішого ознайомлення з матеріалом даної теми
Заняття присвячене основному методу, який використовується для розв'язання рекурентних співвідношень, які характеризують час роботи алгоритмів, що побудовані за методом декомпозиції.
Основний метод
Приклади
Доведення основного методу
Ви можете скористатись конспектом заняття
Заняття розбите на дві частини для більш зручного використання. Дана частина присвячена стандартному методу швидкого сортування:
Загальний опис методу
Процедура розбиття
Доведення коректності алгоритму
Аналіз ефективності алгоритму швидкого сортування
Ви можете скористатись конспектом заняття
Заняття присвячене задачі пошуку порядкових статистик в масиві. Для її розв'язання використовується алгоритм, який побудований на основі методу швидкого сортування.
Пошук мінімального та максимального елементів в масиві
Пошук порядкової статистики
Алгоритм для задачі пошуку порядкових статистик та його аналіз
Ви можете скористатись конспектом заняття
Сьогодні говоритимемо про наступне:
Нижня оцінка алгоритмів сортування
Сортування підрахунком
Сортування за розрядами
Тема: Лінійне сортування
Время: 5 мая 2021 04:00 PM Хельсинки
Подключиться к конференции Zoom
https://us04web.zoom.us/j/78565862982?pwd=MVFlMDdrRGhQdTBaV1RIdTdmWnJKUT09
Идентификатор конференции: 785 6586 2982
Код доступа: 5qY1j8
Більш детально з конспектом заняття можна ознайомитися за посиланням