Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
самост1_1new_druk!!!.doc
Скачиваний:
22
Добавлен:
13.11.2019
Размер:
1.61 Mб
Скачать

Проблеми, що пов'язані з вказівниками

Некоректним використанням покажчиків може бути:

• спроба працювати з неініціалізованим покажчиком, тобто з покажчиком, що не містить адреси ОП, що виділена змінній;

• втрата вказівника, тобто значення покажчика через присвоювання йому нового значення до звільнення ОП, яку він адресує;

• незвільнення ОП, що виділена за допомогою функції malloc();

• спроба повернути як результат роботи функції адресу локальної змінної класу auto.

При оголошенні покажчика на скалярне значення будь-якого типу ОП для значення, що адресується, не резервується. Виділяється тільки ОП для покажчика, але покажчик при цьому не має значення.

Якщо покажчик має специфікатор static, то ініціюється початкове значення покажчика, рівне нулю static int *pi, *pj; /* pi = NULL; pj= NULL; */

Спроба працювати з непроініціалізованим покажчиком:

int *х; /* змінній-покажчику 'х' виділена ОП, але 'х' не містить значення адреси ОП для змінної */

*х = 123; /* - груба помилка */

 Таке присвоювання помилкове, тому що змінна-покажчик х не має значення адреси, за яким має бути розташоване значення змінної.  Компілятор видасть попередження: Warning: Possible use of 'x' before definition

При цьому випадкове значення покажчика (сміття) може бути неприпустимим адресним значенням!

Наприклад, воно може збігатися з адресами розміщення програми або даних. Компілятор не виявляє цю помилку, це повинен робити програміст!

Помилки не буде у випадку використання функції malloc()

int *х; /* х – ім'я покажчика, він одержав ОП */

х = (int *) malloc ( sizeof(int)); /* Виділена ОП цілому значенню, на яке вказує 'x' */ *х = 123; /* змінна, на яку вказує 'х', одержала значення 123*/

   Щоб уникнути помилок при роботі з функціями не слід повертати як результат їхнього виконання адреси локальних змінних функції. Оскільки при виході з функції пам'ять для таких змінних звільняється, повернута адреса може бути використаною системою й інформація за цією адресою може бути невірною. Можна повернути адресу ОП, що виділена з купи.

 Одна з можливих помилок - подвійна вказівка на дані, розташовані у купі, і зменшення об'єму доступної ОП через незвільнення отриманої ОП.

Приклад фрагмента програми з подвійною вказівкою і зменшенням об'єму доступної ОП через незвільнення ОП

#include<alloc.h> void main ()

{

     /* Виділення ОП динамічним змінним х, у и z: */

     int *х = (int *) malloc ( sizeof(int)),

    *у = (int *) malloc ( sizeof(int)),

   *z = (int *) malloc ( sizeof(int));

  /* Ініціалізація значення покажчиків х, у, z;*/

     *х = 14; *у = 15; *z = 17;

     /*Динамічні змінні одержали конкретні цілі значення*/

     y=x; /* груба помилка - втрата покажчика на динамічну  змінну без попереднього звільнення її ОП */

}

 У наведеному вище прикладі немає оголошення імен змінних, є тільки покажчики на ці змінні. Після виконання оператора y = х; х та у є двома покажчиками на ту саму ОП змінної . Тобто *х = 14; і *у = 14. Крім того, 2 байти, виділені змінній, яку адресував y для розміщення цілого значення, стають недоступними, тому що значення y, його адреса, замінені значенням х. А в купі ці 2 байти для вважаються зайнятими.  Щоб уникнути такої помилки треба попередньо звільнити ОП, виділену змінній , а потім виконати присвоювання значення змінній у.

free (у); /* звільнення ОП, виділеної змінної '*у' */

у = х; /* присвоювання нового значення змінній 'у' */

Алгоритми сортування

Сортування – один із найпоширеніших алгоритмів, що вико­ристовується при розв'язуванні завдань на комп'ютері. Під сортуванням мають на увазі упорядкування даних за певною ознакою: списку учнів класу за алфавітом, списку учасників змагань за зменшенням кількості набраних балів тощо. У подібних випадках зручно зберігати дані в масивах. Числові дані упоряд­ковують за зростанням або за спаданням.

Функція sizeof

Але перед розглядом алгоритмів сортування розглянемо ще одну із функцій мови C++ –sizeof.

Функція визначення розміру sizeof використовується для обчислення розміру значення виразу чи типу в байтах і має дві форми:

sizeof (вираз)

sizeof (тип)

Наведемо приклад:

#include<iostream.h>

#include<conio.h>

int main()

{

double x=l;

cout<< "sizeof (float) : "<<sizeof (float )<<endl;

cout<< "sizeof (int) : "<<sizeof (int)<<endl;

cout<< "sizeof (x) : "<<sizeof (x)<<endl;

cout« "sizeof (x+1): "<<sizeof (x+l)<<endl;

getch(};

return 0;

}

Результат роботи програми:

sizeof(float): 4

sizeof(int): 4

sizeof(x): 8

sizeof (x+1): 8

Метод вставки.

Поступово порівнюємо перший елемент зі всіма іншими, доки не знайдемо елемент менший від нього. Перша позиція в масиві – позиція вставки. Знайдений менший елемент і його індекси запам’ятовують. Усі елементи масиву, починаючи від позиції вставки до елемента, що передує знайденому, зміщують праворуч. Значення знайденого елемента записують у позицію вставки і порівнюють його з елементами, що залишились. Якщо буде виявлено ще менший елемент, то повторюють процедуру вставки, і так доти, доки не дійдуть до кінця масиву – лише після цього перший елемент буде на місці. Розглядають масив без першого елемента і застосовують до нього описаний метод.

Зауваження. У модулі stdlib.h описана стандартна функція qsort(), яка дає змогу упорядкувати елементи масиву. Її сигнатура така:

qsort() (*<елемент>, <кількість елементів>, <розмір одного елементу>,

<функція порівняння>);

де *<елемент> – вказівник на перший елемент масиву, функція порівняння – стандартна функція порівняння або функція, створена користувачем.

Сортування простим пошуком

Розглянемо один з найпростіших алгоритмів сортування числового масиву за спаданням – метод простого пошуку. Переглянемо спочатку весь масив з 1-го до останнього елемента й знайдемо найбільший із них. Поміняємо місцями знайдений елемент і 1-й елемент масиву. Потім переглянемо всі елементи масиву, починаючи з 2-го, і знову знайдемо максимальний. Поміняємо його місцями з 2-м елементом масиву. Повторюватиме­мо ці дії до досягнення кінця масиву. Врешті залишиться переглянути тільки 2 останніх елементи, вибрати більший і поставити його на передостаннє місце в масиві. Тепер таблиця впорядкована в порядку спадання.

Отже описаний метод сортування полягає у повторенні двох етапів:

1) пошук серед невпорядкованих елементів масиву елемента з найбільшим значенням;

2) упорядкування таблиці шляхом перестановки знайденого елемента із черговим (1-м, 2-м, 3-м...) елементом таблиці.

Розглянемо ці завдання детальніше. Нехай заданий масив А, частину елементів якого від А[m] до А[n] треба упорядкувати за спаданням.

Побудуємо функцію для пошуку найбільшого серед вказаних елементів масиву А. Для цього будуть використовуватися змінні:

і – номер 1-го з невпорядкованих елементів масиву;

max – значення найбільшого із уже переглянутих елементів;

L – номер найбільшого серед переглянутих елементів.

Алгоритм починається із серії команд, у якій змінна max отримує значення елемента таблиці з індексом m. Цей елемент уже переглянутий і ми беремо m як значення для L. Переглянутий один елемент А[m], отже він – максимальний серед переглянутих, тому для і – номера першого з непереглянутих елементів, встановлюємо значення m+1.

Цикл працює в такий спосіб. Якщо max виявився меншим, ніж елемент А[і], потрібно змінити max, і ми це робимо, присвоюючи йому значення А[і]; відповідно з цим і значення L міняється, тепер воно дорівнює і. Оскільки елемент з номером і уже опрацьований, серія команд завершується збільшенням і на 1 (і++). Робота алгоритму завершується, коли всі елементи переглянуті і номер максимального елемента знайдений. Він і передається як результат в основну програму.

#include<iostream.h>

#include<conio.h>

int MaxElement(int A[],int m,int n)

{

int max=A[m];

int L=m;

int i=m+l;

while(i<n)

{

if(A[i]>=max)

{

Max=A[i];

L=I;

}

i++;

}

return L;

}

int main()

{

int C[]={1,6,21,4,3,5,8,9,7,90,123,1,345};

int mc=0;

int nc=sizeof (C)/sizeof (int);

int Temp, Lc;

while(mc<nc)

{

Lc=MaxElement(C,mc,nc);

Temp=C[mc] //Обмін місцями 2-х

C[mc]=C[Lc]; //елементів, масиву

C[Lc]=Temp; //за допомогою допоміжної

mс++; //змінної Temp

}

for(int і=0;і<nс;і++)

cout<<" "<<C[i];

getch();

return 0;

}

Побудуємо тепер алгоритм упорядкування (функцію main), використовуючи MaxElement як допоміжну функцію.

Нехай заданий масив С, елементи якого нумеруються від 0 до nс. Присвоїмо необхідним для роботи змінним початкові значення: mc=0; nc=sizeof(С)/sizeof (int);. Завдяки використанню функції sizeof буде правильно опрацьований масив будь-якого розміру. Можна написати також:

nc=sizeof(С)/sizeof(C[0]);

– тоді робота цього оператора не залежатиме навіть від типу елементів масиву.

Нам необхідно переставити елементи масиву так, щоб вони йшли в порядку спадання. Застосувавши допоміжну функцію MaxElement(С,mc,nс) до масиву С ми визначимо номер Lc елемента цієї таблиці, що має найбільше значення. Після цього ми поміняємо значення елементів С [mс] й C[Lc]. Для обміну двох елементів масиву використаємо відомий вам лінійний алгоритм.

Тоді на mc-му місці масиву С виявиться найбільший з елемен­тів. На наступному кроці застосовуємо алгоритм MaxElement до частини масиву C з номерами від mс+1 до і знову визначаємо номер Lc максимального елемента. Поміняємо місцями елементи С[mс+1] й С[Lc], тоді на mс+1 місці виявиться найбільший з елементів, що залишилися. Далі будемо застосовувати алгоритм MaxElement до частини масиву С, що починається з номера m+2, потім з m+3 і т.д. й міняти місцями – відповідно С[mc+2] й C[Lc], C[mc+3] й C[Lc] і т.д. У результаті масив С виявиться впорядкованим за спаданням.

Функцію MaxElement можна спростити:

int L=m;

int i=m+l;

while(i<n)

{

if (A[i]>= A[L])

L=i;

i++;

}

return L;

}

Метод «бульбашки»

Метод бульбашки в більшості випадків є ефективнішим, ніж описаний вище метод простого пошуку. Для сортування, наприклад, за зроста­нням, з використан­ням цього алгоритму, весь масив перегля­дається кілька разів підряд. При кожному такому перегляді по­рівнюються послі­довно тільки сусідні елементи масиву: спо­чатку перший із дру­гим, потім другий із третім, і наприкінці – передостанній з останнім. Якщо при порівнянні виявиться, що попередній елемент більший від наступного, вони міняються місцями (для цього, як і у попередньому сортуванні використовуємо проміжну змінну Temp). Так, у результаті одного послідовного перегляду елементи, значення яких більші, ніж у сусідів, переміщуються на 1 або більше елементів ближче до кінця масиву.

Якщо ряд чисел зобразити не горизонтально, а вертикально, щоб елемент а[7] був зверху, а а[0] – знизу, стає зрозумілою назва – метод «бульбашки»: більші елементи, як бульбашки у воді, «спливають» на відповідні позиції.

#include<iostream.h>

#include<conio.h>

int main()

{

const int n = 8;

int і ;

int a[n] = {9, 13, 8, 0, 5, 14, 10, 6};

cout <<"\nVivedennja masivu: \n";

for ( 1=0; і < n; i++)

cout << a[i] <<'\t' ;

int Temp, j=l;

bool prapor = false;

do

{

prapor = false;

for(i=0; і <n-j; i++)

if (a[i] > a[i+1])

{

Temp = a[i];

a[i]=a[i+1];

a[i+l]=Temp;

prapor=true;

}

j++;

}

while (prapor);

cout <<"\n Pislja sortuvannja: \n";

for (i = 0; і < n; i++)

cout << a[i] <<''\t'';

getch();

return 0;

}

Якщо провести такий послідовний перегляд масиву кілька разів, то «важкі» елементи остаточно «спливуть» і масив виявиться відсортованим.

Залишається невирішеним ще одне питання. Скільки разів потрібно виконувати такий перегляд? Адже може виявитися, що одного перегляду буде досить. Відповідь – стільки, скільки потрібно для повного сортування, тобто поки при повному перегляді від початку до кінця масиву жодна пара елементів не поміняється місцями. Для припинення сортування зручно скористатись логічною змінною, котрій спочатку присвоюється false, а потім присвоюється значення true при кожному обміні. Якщо при черговому перегляді не відбулося жодного обміну, значенням цієї змінної залишиться false і перегляди припиня­ються (цикл do. . .while (prapor);).

Сортування виконується за допомогою вкладеного циклу for.

Питання для самоконтролю:

  1. Як називається область, де зберігаються динамічні дані?

  2. Назвіть переваги використання динамічної пам'яті.

  3. Які є функції динамічного виділення пам'яті?

  4. Які виникають проблеми, що пов’язані з вказівниками?

  5. Що таке сортування?

  6. Для чого використовують сортування?

  7. Як визначити розмір виразу чи типу в байтах?

  8. Як визначити кількість елементів масиву?

  9. Як здійснити обмін місцями 2-х елементів масиву?

  10. Чому метод «бульбашки» більш ефективний, ніж метод простого пошуку?

  11. Завдяки чому виникла назва «метод бульбашки»?

Тема: Рядки в С++.

Реверс рядків.

Функція перетворення рядка strrev() змінює порядок слідування символів на зворотний (реверс рядка). Дана функція має прототип:

char * strrev(char* str)

Наступний приклад демонструє роботу функції strrev().

char str[]= “Привіт”;

cout<<strrev(str);

В результаті на екрані буде виведений рядок «тівирП». Ця функція також перетворює рядок-оригінал.

Функції перетворення типів.

Функції перетворення типів дуже часто використовуються, як випливає з їх назви, для перетворення одного типу даних в інший. В приведеній нижче таблиці перелічені основні функції, їх прототипи підключаються в заготовочному файлі stdlib.h.

Дуже часто дані функції використовуються для перетворення чисел, введених у вигляді символьних рядків, в числове представлення, а також для виконання визначених арифметичних операцій над ними і зворотнє перетворення в рядок символів. Розглянемо найбільш широко використовувані з них.

Найменування

Короткий опис

atof

Перетворює рядок символів в число з плаваючою крапкою

atoi

Перетворює рядок символів в рядок типу int

atol

Перетворює рядок символів в рядок типу long

ecvt

Перетворює число з плаваючою крапкою типу double в рядок символів; десяткова крапка і нак числа не включається в одержаний рядок; позиція крапки і знак числа повертаються окремо

fcvt

Аналогічно ecvt, але заокруглює одержане значення до заданого числа цифр

gcvt

Перетворює число з плаваючою крапкою типу double в рядок символів, включаючи символ десяткової крапки і використовує специфіковане число цифр

itoa

Перетворює число типу int в рядок символів

ltoa

Перетворює число типу long в рядок символів

strtod

Перетворює рядок символів в число з плаваючою крапкою типу double

strtol

Перетворює рядок символів в число з плаваючою крапкою типу long

strtoul

Перетворює рядок символів в число з плаваючою крапкою типу unsigned long

ultoa

Перетворює число типу unsigned long в рядок символів

Фунція atoi(), синтаксис якої

int atoi(const char* ptr)

перетворює ASCIIZ-рядок символів, на який вказує ptr, в число типу int. Якщо в рядку зустрічається символ, який не може бути перетворений, дана функція повертає 0. У випадку якщо перетворюване число перевищує діапазон представлення типу int, повертається тільки два молодших байти числа.

На відміну від неї, функція atol() перетворює задане рядкове число в тип long. Ця функція має аналогічний синтаксис:

int atoi(const char* ptr)

Якщо перетворюване число перевищує діапазон значень типу long, функція поверне випадкове число.

Розглянемо приклад перетворення рядка цифрових символів в ціле і довге ціле.

#include <stdlib.h>

#include <iostream.h>

int main()

{

char str[]=”70000”;

int i=atoi(str);

long l=atoll(str);

cout <<i<<”\n”;

cout<<l;

return 0;

}

Якщо використовується модель пам'яті, в якій тип int представляється двома байтами, результат роботи наведеної програми буде виглядати так:

4464

70000

Справа в тому, що число 70000 в шістнадцятковій системі числення представляється як 0х11170, але, оскільки функція atoi() при переповненні результату повертає тільки два молодших байти, змінна і прийме шістнадцяткове значення 0х1170, яке еквівалентне десятковому 4464. Так як atol() оперує з чотирьохбайтними числами, переповнення не буде і змінній l присвоїться значення 70000.

Функція atof(), визначена як

Double atof(const char* ptr)

виконує перетворення ASCIIZ-рядка в число з плаваючою крапкою типу double. Рядок символів повинен бути представлений з врахуванням формату: [пропуски][знак][цифри][.цифри][е|Е[знак]цифри],

де

[пропуски] – послідовність пропусків або табуляторів;

[знак] – символ ‘+’ або ‘-’;

[цифри] – десяткові цифри;

[е|Е] – символ показника степеня.

Перетворення символів припиняється, як тільки знайдений перший неконвертований символ або досягнений кінець рядка.

Функції оберненого перетворення itoa() і ltoa() виконують конвертування чисел типу int і long відповідно. Вони мають наступний синтаксис:

char*_ltoa(long num, char*str, int radix);

і

char*іtoa(long num, char*str, int radix);

або

char*_іtoa(long num, char*str, int radix);

Дані функції приймають в якості аргументу число num і перетворюють його в рядок str з врахуванням основи системи числення, представленої змінною radix. Наступний фрагмент програми перетворює ціле число 98765 в рядок, використовуючи десяткову систему числення:

int numb=98765;

char str[10];

itoa(numb,str,10);

cout<<numb<<’\n’<<str;

Функція strtod() перетворює рядок символів в число з плаваючою крапкою. Її синтаксис має наступний вигляд:

double strtod(const char*s,char**endptr);

Ця функція, як і функція atof(), перетворює рядок, на який вказує s, в число типу double, з тою лиш відмінністю, що у випадку припинення конвертування рядка повертається вказівник на перший символ, що не перетворюється. Це дозволяє організувати подальшу обробку частини рядка, що залишилася.

Функція gcvt() має прототип:

char*gcvt(double val, int ndec, char*buf);

і здійснює конвертування числа val типу double в ASCIIZ – рядок, поміщаючи його в буфер buf. Якщо число цифр, підлягаючих перетворенню, менше цілого числа, вказаного в ndec, в перетвореному числі вказуються символи знаку та десяткової точки, при цьому молодші розряди дробової частини відкидаються. В протилежному випадку число перетворюється в експоненціальну форму. Функція повертає вказівник на початок сформованого рядка.

Наступний приклад демонструє використання функції gcvt() для перетворення чисел, що мають різне представлення у масиви цифрових символів.

#include<stdlib.h>

#include<iostream.h>

int main(void)

{

char str[10];

double num;

int sig=4;

num=3.547;

gcvt(num,sig,str);

cout<<str<<’\n’;

num=-843.7105;

gcvt(num,sig,str);

cout<<str<<’\n’;

num=0.135e4;

gcvt(num,sig,str);

cout<<str<<’\n’;

return 0;

}

В результаті буде виведено:

3,547

-843,7

1350.

Питання для самоконтролю:

  1. Що таке реверс рядків?

  2. Як виконати реверс рядків в С++?

  3. Для чого використовуються функції перетворення типів?

  4. Які функції перетворення типів ви знаєте?

  5. Для чого призначена функція atof() та інші?