Тема 8. Вказівки і посилання 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Тема 8. Вказівки і посилання



ПЛАН

1. Вказівки.

2. Масиви і вказівки.

3. Використання посилань у С++.

 

Вказівки

Змінні вказівки (або, як їх звичайно називають, просто вказівки) - це змінні, що містять адресу даних.

Єдина різниця між звичайною змінною і вказівкою полягає в їхнім вмісті.

До вказівок відносяться два оператори:

* оператор “значення за адресою”;

& оператор “адреса значення”.

Це бінарні оператори, що стоять перед змінною або виразом.

Наприклад: &(num),*(p+3);

Оголошення вказівок.

int *p_age;

 

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

p_age – вказівкана ціле, тобто p_age – змінна, що містить адресу цілих значень. Якщо необхідно працювати з плаваючою точкою, то можна оголосити вказівку із відповідним типом.

 

float *point

Ініціалізація вказівок (присвоювання значень вказівкам)

Існує п’ять засобів завдання початкового значення змінній-вказівці:

− описати вказівку поза будь-якою функцією або постачити його розпорядженням static. Початковим значенням є нульова адреса пам’яті - 0. Перед тим, як почати користуватися вказівкою, варто зарезервувати пам’ять під значення;

− привласнити вказівці адресу змінної;

− привласнити вказівці значення іншої вказівки, до цього моменту вже правильно ініціалізованної;

− використовувати оператори динамічного розподілу пам’яті, такі як new, delete і delete[];

− привласнити значення конкретної адреси (константи), при цьому необхідно привести константу до типу вказівки.

 

Приклади ініціалізації вказівок:

 

int *p1;

 

Void main()

{

static int *p2;

 

int *p3, s = 10;

p3 = &s;

 

int *p4 = &s,*p5;

p5 = p4;

}

 

Присвоювання адреси змінної age змінній p_age

 

int age=30;

int *p_age;

p_age=&age;

 

Можна ініціалізовувати під час оголошення:

 

int age=30;

int *p_age=&age;

 

Адреса Пам’ять
350606 /age/  
 
350982 /p_age/  
   

 

Для виведення значення змінної age можна використовувати 2 варіанта:

1) cout << age;

2) cout << *p_age;

Оператор * ставиться перед змінними вказівками тільки в двох випадках:

1. При оголошенні змінної-вказівки.

2. Для отримання значення, на який вона вказує.

Після оголошення вказівки на конкретний тип об’єкта C++, спроба використання цієї вказівки для посилання на інший тип призведе до помилки:

 

{

int* k;

int j;

char c;

 

j = 5;

c = ’C’;

k = &j; //це припустимо

k = &c; //це призведе до помилки

}

 

Існує спеціальний тип вказівки на об’єкти C++ будь-якого типу, що виключає помилки компілятора, що стосуються покажчиків. Вказівки типу void* можна використовувати в будь-яких цілях:

 

{

void* k;

int j;

float f;

char c;

 

//тут усе правильно

k = &j;

k = &f;

k = &c;

}

 

Вказівки void мають важливу властивість: типізована вказівка може бути привласнена вказівці void, але не навпаки. Щоб привласнити вказівку void типізованій вказівці, необхідно використовувати явне перетворення типів, інакше компілятор видає повідомлення про помилку:

 

Void main()

{

int* ip;

int i = 5;

char* cp;

char c = ’A’;

 

ip = &i; //правильно

vp = &i; //правильно

 

cp = &c; //правильно

vp = &c; //правильно

 

ip = vp; //цього робити неможна

cp = vp; //цього робити неможна

 

vp = ip; //правильно

vp = cp; //правильно

}

 

У C++ такі оператори, як new, delete і delete[], виділяють і звільняють блоки пам’яті в купі. Такий вид пам’яті звичайно називають динамічною пам’яттю, тому що вона створюється і знищується при виконанні програми.

Припустимо, необхідно виділити пам’ять для масиву з 10 цілих чисел. Для цього достатньо написати:

 

int* ip;

ip = new int[10];

або ще коротше:

 

int* ip = new int[10];

 

Даний код оголошує вказівку ip і ініціалізує її значенням, що повернуте new. Оператор new повертає вказівку на початок виділеного блока пам’яті.

Якщо ви запитуєте пам’ять за допомогою оператора new, необхідно зазначити точний її обсяг. Цей обсяг зовсім необов’язково знати під час компіляції, але він повинен бути відомий під час виконання програми:

 

//Введення значення змінної

int n;

cin >> n;

//запитується блок пам’яті для збереження n цілих

int* ip = new int[n];

 

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

Оператор new поверне ненульову вказівку, навіть якщо запросити нуль байтів. При використанні вказівки, повернутої new, неминуче виникнення проблем, тому що для даного блоку пам’яті реально не виділено.

 

Void main()

{

//буде повернута значуща вказівка!

int* ip = new int[0];

}

 

Оператор new дозволяє ініціалізувати зазначеним значенням динамічно виділені дані, як показано нижче:

 

//ініціалізація масиву значенням 17

int* ip = new int[10](17);

 

//ініціалізація масиву символів нулями

char* ср = new char[10](0);

 

//ініціалізація цілого значенням 10

int* value = new int(10);

 

Якщо параметри ініціалізації не задані, у виділених блоках, повернутих new, буде сміття. На жаль, позначення ініціалізації дуже незручно. Використання круглих дужок за квадратними дужками виглядає дивно.

Створивши в глобальній купі об’єкт, ви відповідаєте за його видалення. Ця операція для компілятора більш складна, ніж операція створення об’єкта. Чому? Роздивимося такий код:

 

int* ip = new int[10];

delete ip;

 

Скільки пам’яті буде звільнено за допомогою операції delete? Оператору new передається інформація про число типізованих елементів, для котрих необхідно виділити пам’ять. Оператор delete одержує тільки типізовану вказівку. У C++ адміністратор купи використовує вказівку для пошуку виділеного блоку пам’яті. Купа також містить додаткову інформацію, включаючи розмір кожного виділеного блоку. Використовуючи цю приховану інформацію, адміністратор може визначити, скільки звільнити пам’яті.

Комітет ANSI у проект C++ формально додав новий оператор delete[] для обробки знищення масивів. Цей оператор повинен використовуватися для знищення масивів. Такий код показує його використання:

 

char* ср = new char[10];

delete[] ср;

 

Приклад п’ятого засобу завдання початкового значення вказівки. Виведення у початок екрана дисплея символів "AB" білим кольором на синьому фоні.

 

Void main()

{

char* c = (char*)0xB8000000; // початкова

// адреса відеопам’яті

*c = ’A’; c++

*c = 0x9F; c++;

*c = 66; c++;

*c = 0x9F;

}

 

Вказівки і параметри функції.

Для передачі даних у функції по посиланню використовуються оператори * і &. У функції, що передає, перед змінної стає оператор &, а в що приймає -*.

 

pr(&t); //Передача адреси змінної

 

void pr(int *t)

{

*t++;

return;

}

 

Приклади:

 

int num=123;

int *p_num=&num;

cout <<*p_num; // Виведення значення змінної num

cout <<p_num; // Виведення адреси змінної num

//Краще явно задати тип

cout << (unsigned long) p_num <<”\n”;

 

При кожнім виконанні програми адреса змінної num може змінюватися. Це залежить від конфігурації пам’яті.

Приклад функції свопінга двох змінних:

#include <iostream.h>

 

void swap(int *n1, int *n2)

{

int temp;

temp=*n1;

*n1 = *n2;

*n2=temp;

return;

}

 

Void main()

{

int i=10,j=20;

swap(& i,& j);

cout <<i<<” “<<j;

}

 

Оголошення масивів-вказівок.

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

 

int *iptr[10]

 

[0]  
[1]  
[2]  
[3]  
[4]  
[5]  
[6]  
[7]  
[8]  
[9]  

 

char *cp[20] //Масив із 20 вказівок на

//символьні змінні.

 

Масиви і вказівки

Аналогія масиву і вказівок.

Масив являє собою лише вказівку і більше нічого.

 

int ara[5]={10,20,30,40,50};

 

ara+0 – вказує на ara[0]

ara+1 – вказує на ara[1]

 

 
 

  Пам’ять
[0]  
[1]  
[2]  
[3]  
[4]  
   

 

cout <<*(ara+2); //Друк ara[2]

 

Хоча масиви є замаскованими вказівками, але вони відносяться до особливого типу вказівок. Масив являє собою константу-вказівку, а не змінну-вказівку.

Тому що не можна змінити константу, то неможливо змінити значення імені масиву

 

char ch[20];

ch=”Привіт”; //Неприпустима операція

 

Приклад:

 

float v1=500.0,v2=600.0;

float *pt;

pt=&v1;

cout << *pt; //500

pt=&v2;

cout << *pt; //600

 

Тут використовується змінна-вказівка pt, значення якої може змінюватися.

Використання вказівок на символи.

Роздивимося 2 рядки:

 

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

char *cp = ”Москва”;

 

Після початкової ініціалізації у вказівці на символьну змінну міститься адреса першого символу рядка.

 

cara=”Привіт”; //Невірно

cp=”Київ”; //Можна: занесення у вказівку

//нового рядка.

 

Приклад:

 

#include <iostream.h>

 
 

#include <string.h>

 

Void main()

{

char name[20]=”Іван Петров”;

char *t=name;

strcpy(t+5,”Сидоров”);

cout <<t; //Іван Сидоров

}

 

Використання арифметичних операцій із вказівками

ara[sub] → *(ara+sub)

 

int s[]={10,20,30};

int *ps=s; //Те ж саме int *ps=&s[0]

 

ps++;

cout <<*ps; //20 - виведення

ps++;

cout <<*ps; //30 - виведення

 

 

Приклад:

 

#include <iostream.h>

#include <iomanip.h>

 

Void main()

{

float ara[ ]={100,200,300,400,500};

float *fp = &ara[0];

cout << setiosflags(ios::fixed)

<< setprecision(1);

cout << *fp << "\n";

fp++;

cout << *fp << "\n";

fp++;

cout << *fp << "\n";

fp=ara;

cout << *(fp+2) << "\n";

cout <<(fp+0)[0] << " "<< (ara+0)[0] << "\n";

cout << (fp+4)[0] <<" "<< (ara+4)[0] << "\n";

cout << (fp-1)[2] <<" "<< (ara-1)[2] << "\n";

}

 

Результат:

 

100.0

200.0

300.0

300.0

100.0 100.0

500.0 500.0

200.0 200.0

Запам’ятовування масивів рядків.

char names[5][20]=

{

{"Наташа"},

{"Георгій"},

{"Ан"},

{"Оля"},

{"Льоша"}

};

 

Таблиця займає в пам’яті багато місця. Нераціонально використовується пам’ять.

Для усунення проблем раціонального використання масиву символьних вказівок кожна вказівка містить адресу рядка в пам’яті, і ці рядки можуть мати різну довжину.

1-й варіант 2-й варіант
char *names[5]= { {"Наташа"}, {"Георгій"}, {"Ан"}, {"Оля"}, {"Льоша"} }; char *names[]= { "Наташа", "Георгій", "Ан", "Оля", "Льоша" };

 

Рядки можуть знаходитися в будь-якому масиві пам’яті

 

[0] Наташа\0
[1] Георгій\0
[2] Ан\0
[3] Оля\0
[4] Льоша\0

 

cout << *names //Друкує Наташа

cout << *(names+1) //Георгій

 

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

Приклад:

При запровадженні чисел від 1 до 7 на екран виводиться день тижня

 

#include <iostream.h>

 

Void main()

{

char *dn[]=

{

"Понеділок",

"Вівторок",

"Середа",

"Четвер",

"П’ятниця",

"Субота",

"Неділя"

};

int day_num;

do

{

cout <<"Введіть день тижня(1-7)?";

cin >> day_num;

}

while ((day_num < 1)||(day_num > 7));

cout << *(day_num - 1 + dn) << "\n";

}

 

3. Використання посилань у С++

Типи посилань встановлюють псевдоніми об’єктів. Вони використовуються як типи параметрів функцій для передачі аргументів по посиланню. Для опису типу посилань використовується модифікатор & аналогічно модифікатору * для покажчиків. Посилання в основному використовуються як параметри. Щоб показати, як працюють посилання, роздивимося непараметричні оголошення.

Для встановлення псевдоніму, він повинен бути того ж типу, що і змінна.

 

int i;

int &ir = i;

 

Для змінної i встановлюється псевдонім ir. Присвоювання ir значення і його використання значення дасть той же результат, що і присвоювання значення і використання i.

 

ir = 3;

j = ir * i; //результат 9

 

Посилання (адреса) не може бути змінена.

У більшості мов програмування параметри передаються або по посиланню, або за значенням. У першому випадку підпрограма (функція) працює безпосередньо зі змінною, переданою в якості параметра, у другому ж їй доступна не сама змінна, а тільки її значення. Різниця очевидна: змінну, передану по посиланню, підпрограма може модифікувати, а за значенням - ні. Нижче приведені 4 версії функції обміну значень 2-х змінних. У swap1 використовується передача параметрів за значенням, обміну не буде. У swap2 використовується передача за значенням вказівок. У swap3 і swap4 передача по посиланню.

1) void swap1(int x, int y)

{

int z = x;

x = y;

y = z; //Обміну в зовнішній програмі не буде.

}

2) void swap2(int *x, int *y)

{

int z = *x;

*x = *y;

*y = z;

}

3) void swap3(int &x, &int y)

{

int z = x;

x = y;

y = z;

}

4) void swap4(double &x, double &y)

{

int z = x; //Компілятор дасть попередження,

x = y; //оскільки параметру посилання одного

y = z; //типу повинна бути привласнене посилання

//того ж типу

}

 

 

ТЕМА 9: РОБОТА З ФАЙЛАМИ

ПЛАН

1. Загальна характеристика роботи з файлами.

2. Послідовні файли.

3. Файли довільного доступу.

 



Поделиться:


Последнее изменение этой страницы: 2017-02-10; просмотров: 235; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.217.60.35 (0.135 с.)