Разделы презентаций


Вказівники в мові С

Що таке вказівник?Оперативну пам’ять варто розглядати як масив послідовно пронумерованих комірок, з якими можна працювати по роздільності чи зв’язаними ділянками.Вказівник – це змінна, що містить адресу початку певної ділянки оперативної пам’яті.

Слайды и текст этой презентации

Слайд 1Вказівники в мові С
Поняття вказівника та його використання

Вказівники в мові С Поняття вказівника та його використання

Слайд 2Що таке вказівник?
Оперативну пам’ять варто розглядати як масив послідовно пронумерованих

комірок, з якими можна працювати по роздільності чи зв’язаними ділянками.
Вказівник

– це змінна, що містить адресу початку певної ділянки оперативної пам’яті.
Вказівник – це фізична сутність, а отже під нього виділяється пам’ять як і під будь-яку змінну.
char *p;
std::cout << &p; //вказівник як і будь-яка змінна має свою адресу розміщення
Розмір вказівника визначається розміром машинного слова заданої платформи.
char *p;
sizeof(p); //4//поверне розмір самого вказівника
Оголошення вказівника : char *pC; int *pI;
До будь-якої зміннлї можна застосувати операція взяття адреси - &;
char a;
&a; //оператор & поверне адресу розміщення змінної a
Наступні рядки коду описують сутність вказівника.
//Оголошення вказівника р та змінної с
char *p, c;
//Запис до p адресу змінної с
p = &c; //операція взяття адреси
//звернення до с через вказівник р
*p= 10; //розіменування вказівника
Вказівник може бути будь-якого типу.
По-суті тип вказівника нам говорить про:
1) кількість байт, що потрібно взяти від адреси-початку
2) як цю послідовність байт трактувати( цілочисельний тип, раціональний, структура, …)
Також є особливий тип вказівника void*. Вказівник такого типу містить лише адресу і до нього не може бути застосовано операцію розіменування. Для цього ми повинні використати явне привденення типів.
void *pV = &c;
(char)pV = 5; //приведення до типу char та звернення до ділянки, що містить с
sizeof(pV); //розмір безтипового вказівника як і будь-якого = 4 байти
Висячий вказівник – вказівник, який посилається на неіснуючу змінну. Рекомендується присвоювати таким вказівникам NULL.
Що таке вказівник?Оперативну пам’ять варто розглядати як масив послідовно пронумерованих комірок, з якими можна працювати по роздільності

Слайд 3Операції над вказівниками. Адресна арифметика
Над вказівниками можна здійснити наступні операції:
Розіменування

(*) – звернення до ділянки памяті за адресою, що міститься

у вказівнику.
*ptr = 5; //значення lvalue є посилання на комірку памяті, яке адресується цим вказівником
sizeof(*ptr); //операція визначення розміру поверне кількість байт обєкту на який посилається ptr
sizeof(ptr); // операція визначення розміру поверне розмір заданого вказівника
Присвоєння (=) – побітове копіювання вмістимого вказівника. При цьому здійснюється перевірка типів цих вказівників.
Важливо зазначити, що на етапі компіляції перевіряються типи лівої та правої частин відносно =, тобто
char a, *pC=&a; int *pI; pi = pC;
призведе до помилки. Тут потрібне явне приведення до типу.
Обійти це обмеження простим копіюванням вмістимого вказівників, використавши функцію
void memcpy(void *, const void *, size_t);
Важливо зазначити, що ми копіюємо вмістиме вказівників, а не ділянки ОП, адреси яких вміщує один із вказівників. Тому
ми передаємо адреси самих вказівників memcpy(&pI, &pC, sizeof(pI));
Порівняння (>, <, >=, <=, ==, !=) – результатом будь-якої операції є ненульове значення в разі істинності.
if(pI == (int*)pC) { std::cout << “pI == pC”} //потрібне явне приведення типу
Можна використати функцію int memcmp(const void*, vonst void*, size_t); , що порівнює побайтово і повертає, яка ділянка більша або 0 у разі рівності.
if(!memcmp(&pC, &pI, 4) { printf(“pC == pI”); } //порівняння вмістимого вказівників без приведення типів
Збільшення/зменшення адрес – до вказівника можна додати чи відняти довільне ціле число
ptr = ptr + 3; // ptr = ptr + 3*sizeof(*ptr)
Віднімання вказівників – різниця двох вказівників дасть кількість об’єктів, що може бути розміщено між об’єктами, на які посилаються задані вказівники.
char arr[5], *p1=mas, *p2=&mas[3]
printf(“%d”, ptr2 – ptr1); // 3


Операції над вказівниками.  Адресна арифметикаНад вказівниками можна здійснити наступні операції:Розіменування (*) – звернення до ділянки памяті

Слайд 4Кваліфікатор доступу const та volatile
Вказівники можуть бути незмінними ( const

), тобто можуть посилатися лише на один об’єкт, так і

посилатися на константний об’єкт, тобто значенння обєкту не можна змінити за допомогою такого вказівника.
int i, а;
const int *p1 = &i; //вказівник на константну змінну
//*p1 = 5; //помилка, вказівник не може змінити значення змінної, на яку посилається
int * const p2 = &i;//константний вказівник на змінну
//int const *p2; //помилка, обов’язково повинен бути проініцілізованим
//p2 = &a; //помилка; константний вказівник адресує лише одну змінну
*p2 = 3;
Вказівники на константні змінні використовують перш за все при передачі праметрів до функцій. В тілі функції заборонено змінювати змінні, на які послаються дані вказівники.
void f(const int *p); //по суті передасться копія вказівника, що буде адресувати ту ж змінну типу
Має зміст і такий код const int * const p=&a; , що оголошує константний вказівник на константну змінну.
Якщо потрібно відмінити оптимізацію для якогось вказівника, то його можна оголосити як volatile.
int * volatile pV = &a; //такий вказівник не буде оптимізовано, а тому до нього буде доступ із інших потоків
Можна заборонити оптимізовувати змінну, на яку посилається заданий вказівник.
volatile int b; //volatile змінна
volatile int * pv = &b; //вказівник на volatile змінну
Має зміст і такий код volatile int * volatile p; , що говорить, що не можна оптимізовувати ні сам вказівник ні змінну, на яку він посилається.
Важливо зауважити, що const стосується можливості явного перезапису змінної. Volatile же стосується заборони оптимізації змінної. Тому наступний код не є суперечливим.
const volatile int * const volatile p = &a; //константний неоптимізуючий вказівник на константну неоптимізуючу змінну типу int
Кваліфікатор доступу const та volatileВказівники можуть бути незмінними ( const ), тобто можуть посилатися лише на один

Слайд 5Вказівники та масиви (1)
В С++, як і в ANSI C,

ім'я масиву є адресою його першого елемента, тобто:
char ach[30];

// тут ach є адресою &ach[0];
Це робить простою організацію вказівників на масиви даних
Нехай задано int a[10];
int* pa;
// перший варіант організації вказівника j на масив achArray
pa=&a[0];
// другий варіант організації вказівника j на масив achArray
pa=a;
Процес організаціі доступу до елементів масиву через вказівник складається з двох етапів:
ініціалізація вказівника адресою першого чи останнього елемента масиву;
використання циклічного оператора для доступу до елементів масива або маніпуляції адресою, яку містить вказівник. Зокрема, якщо j=&ach[0], то наступні звертання до ³-го елемента масиву ach є рівносильними
ach[i] = j[i] = *(j+i);




Зауважимо: хоча операції * i & мають найвищий пріоритет, проте операція *j++ приведе до значення елемента масиву з індексом збільшеним на одиницю (читається даний вираз справа наліво). Тобто, при умові j=&ach[0]
*j++ = achArray[1] = *(j+1) = *(j++).
Для того щоб збільшити на одиницю значення елемента масиву, треба записати (*j)++.

Вказівники та масиви (1)В С++, як і в ANSI C, ім'я масиву є адресою його першого елемента,

Слайд 6Вказівники та масиви (2)
Звертання до елемента масиву за допомогою оператора

індексації ach[i] вимагає проведеннянаступних операцій:
Обчислення вказівника (a+i)
Розіменування отриманого вказівника

*(a+i)

Як наслідок звернення до елемента масиву можна провести за один крок. Нище показано фрагмент коду сортування бульбашкою. При кожній наступній ітерації вказівники вже посилатимуться на потрібні елементи.
for(int i = 0; i < n; i++) {
for(int *p1 = arr, *p2 = p1+1, j = 0; j < n-i-1; p1++, p2++, j++) {
if (*p1 > *p2) { //arr[j] > arr[j+1]
*p1 += *p2; //arr[j] += arr[j+1];
*p2 = *p1 - *p2; //arr[j+1] = arr[j] - arr[j+1];
*p1 = *p1 - *p2; //arr[j+1] = arr[j] - arr[j+1];
}
}
}
Рядкова константа “I’m a string” є масивом символів із додатковим термінальним нулем у кінці ‘\0’.

Потрібно відрізняти:
char *pmessage = "now is the time"; /*вказівник*/
char amessage[] = "now is the time"; /* массив */

У першому випадку до pmessage запишеться адреса константного рядка. У другому ж створиться масив символів.
Відповідно запис: *pmessage = ‘S’; призведе до помилки.

Вказівники та масиви (2)Звертання до елемента масиву за допомогою оператора індексації ach[i] вимагає проведеннянаступних операцій: Обчислення вказівника

Слайд 7Вказівники і двовимірні масиви
Доступ до матриці (двовимірного масиву) організовується як

масив вказівників на рядки, або як вказівник на вказівник, наприклад.



#include
const unsigned int row=10;
const unsigned int col=10;
void main()
{
double arfl[row][col]; //оголошення матриці та виділення під неї памяті size=row*col*sizeof(double) (8)
double* p1[row]; //оголошення масиву вказівників та виділення під нього памяті size=row*sizeof(double*) (4)
double** p2=p1; //оголошення подвійного вказівника size=4
int i,j; //оголошенні лічильників циклів
for (i=0; i {
for (j=0; j arfl[i][j]=double(i+j); // матриці
p1[i]=&arfl[i][0]; // вказівників
}
double Suma=0,SumaP1=0,SumaP2=0;
for (i=0; i for (j=0; j {
Suma+=arfl[i][j]; // через індентифікатор матриці
SumaP1+=(*(p1+i))[j];// через масив вказівників
SumaP2+=*(*(p2+i)+j);// через подвійний вказівник
}
sdt::cout<<"Сума елементів матриці розрахована чеоез:"<<"\n";
sdt::cout<<" -індентифікатор масиву: "< sdt::cout<<" - подвійний вказівник: "< sdt::cout<<" - масив вказівників: "<}
Важливо зауважити, що рядки 20,21,22 всерівно приведуться до такого типу *(*(р+і)+j). Різниця полягає у оголошенні. В рядку 6 виділиться пам’ять під матрицю, в 7 – під одновимірний масив, а у 8 – лише під один вказівник.
Вказівники і двовимірні масивиДоступ до матриці (двовимірного масиву) організовується як масив вказівників на рядки, або як вказівник

Слайд 8Приклад передавання подвійного масиву у функцію
#include
using namespace std;
void

printArray1(int* tab[3], int dim1, int dim2)
{
int

w,k;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << tab[w][k] << " ";
cout << endl;
}
}
void printArray2(int** tab, int dim1, int dim2)
{
int w,k;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << tab[w][k] << " ";
cout << endl;
}
}
void printArray3(int tab[][3], int dim1, int dim2)
{
int w,k;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << tab[w][k] << " ";
cout << endl;

}
}
main(void)
{
int t[][3] = {{6,2,4},{3,2,1}};
int *tt[2];
int ** ttt;
tt[0] = t[0]; tt[1] = t[1];
ttt = (int**)tt;
ttt[0] = t[0]; ttt[1] = t[1];
// printArray1(t,2,3); - неправильний виклик
printArray1(tt,2,3);
printArray1(ttt,2,3);
// printArray2(t,2,3); - неправильний виклик
printArray2(tt,2,3);
printArray2(ttt,2,3);
printArray3(t,2,3);
// printArray3(tt,2,3);
// printArray3(ttt,2,3); - неправильний виклик
}

Приклад передавання подвійного масиву у функцію #include using namespace std;void printArray1(int* tab[3], int dim1, int dim2) {

Слайд 9Приклад використання подвійного масиву
void printArray(int** ttt, int dim1, int dim2)

{
int w,k;

cout

int** tab =ttt;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << tab[w][k] << " ";
cout << endl;
}

cout <<"-------------------"< tab =ttt;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << *(*(tab+(w*(dim1-1)))+k) << "";
cout << endl;
}

cout <<"-------------------"< tab =ttt;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)

cout << *(tab[w]+k) << " ";
cout << endl;
}

cout <<"-------------------"< tab =ttt;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << *(*(tab+w)+k) << " ";
cout << endl;
}

cout <<"-------------------"< tab =ttt;
for (w = 0; w < dim1; w++)
{
for (k = 0; k < dim2; k++)
cout << *((*tab)++) << " ";
cout << endl;
}
}

Проблемний код

Приклад використання подвійного масивуvoid printArray(int** ttt, int dim1, int dim2) {   int w,k;

Слайд 10С++ допускає створення вказівників на структури і організовувати доступ до

полів структури, незалежно від того чи це змінна чи функція,

за допомогою операції доступу -> "стрілка", наприклад
struct MyStrt
{
int i;
int Mas[10];
// int Mas[10] = {1,2};
};

main()
{
MyStrt My;
MyStrt* p=&My;
(*p).i=1;
for(int i = 0; i < 10; i++)
p->Mas[i] = i;
}

Якщо замість вказівника використовувати посилання, то оператор доступу -> "стрілка" треба замінити на оператор доступу . "крапка".
Подібно до структур організовуються вказівники на класи та об'єднання.

Вказівники і структури

Заборонена дія

Організація доступу через оператор розіменування «*-множення» та оператор доступу «. -крапка». Дужки обов’язкові.

Організація доступу через оператор доступу «-> - стрілка»


Слайд 11Вказівники на функції
C++ підтримує мехазнім організації вказівників, які зберігають адресу

функції, тобто адрес першого виконавчого оператора. Загальний синтаксис оголошення вказівника

на функцію має вигляд
type (*pointerFunction)(paramList)[=functionAddress];
Тоді загальний синтаксис виклику функції через вказівник виглядає так
(*pointerFunction)(paramList);
При ініціалізації вказівників на функції іменем конкретної функції треба забезпечити відповідність типу, який повертає функція, i cписку її параметрів до оголошення вказівника. Після ініціалізації вказівника на функцію його можна використовувати для виклику цих функцій, наприклад

int funct1(int, double); // прототипи функцій
int funct2(int, double);

main()
{
int (*p)(int, double); // оголошення вказівника на функцію
p=funct1; // організація вказівника на funct1
int i=p(1,2); // виклик funct1 через вказівник
p=funct2; // переадресація вказівника на funct2
i=p(1,2); // виклик funct2 через вказівник
i=(*p)(1,2); // виклик в старому форматі
}

int funct1(int i1, double i2) { // оголошення функцій
std::cout << “\nfunct1:\n”;
return (i1+int(i2));
}

int funct2(int i1, double i2) {
std::cout << “\nfunct2:\n”;
return (i1+int(i2));
}

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

Слайд 12Використання вказівників для накладання інтерфейсів
Накласти інтерфейс на деяку ділянку пам’яті

означає певним чином страктувати її, тобто до накладання інтерфейсу ми

сприймаємо цю ділянку як послідовність байт, а після як послідовність змінних, що мають певний тип.
Важливо зауважити, що ми можемо накладати інтерфейси вже на існуючі змінні. Для цього використовують вказівники.
Наприклад:
int * I=3; //змінна типу int
std::cout << I; //3
char *pC = (char*)&I; //накладання інтерфейсу на перший байт змінної I
*pC = 5; //доступ до першого байта змінної і
std::cout << (int)*pC; //5
Для прикладу змінимо значення константної змінної
const volatile int i = 7; //volatile потрібен для заборони оптимізації константи
std::cout << i; //7
int * pI = (int*)&i; //накладання інтерфейсу на існуючу змінну i
//I = 8; //помилка. Значення константи заборонено змінювати явно!
*pI = 8; //доступ до ділянки ОП, де розташована змінна і та трактування її іншим інтерфейсом - int
std::cout << *pI; //8
Важливо зауважити, що даний код вимагає явного приведення типів (const int* до int*). Цього можна уникнути за допомогою функції void memcpy(void* p1, const void*p2, size_t size); , що просто перезаписує size байт ділянки ОП, на яку вказує p1, із ділянки , на яку вказує p2. Тут не виконується жодна з перевірок правильності приведення типів.
const volatile int * p = &I; //вказівник на неоптимізуючу константу
memcpy(&pI, &p, sizeof(p)); //запис адреси, що містить p, до вказівника pI
Використання вказівників для накладання інтерфейсівНакласти інтерфейс на деяку ділянку пам’яті означає певним чином страктувати її, тобто до

Слайд 13Динамічні змінні
У мові С є можливість виділяти пам’ять під час

виконання програми. Ця можливість забезпечується функціями :
void* malloc(size_t size);

//виділення динамічної пам’яті розміром size та повернення вказівника на цю область
void* calloc(size_t num, size_t size); //виділення динамічної пам’яті розміром size*num для розміщення масиву із num едемнтів
void* realloc(void *ptr, size_t newsize); //зміна обсягу ділянки динамічної пам’яті
Результатом роботи кожної із цих функцій є виділення деякої ділянки динамічної пам’мяті та повернення безтипового вказівника на початок цієї ділянки. При цьому це всього лиш набір байт без будь-якого типу.
Для доступу до цієї пам’яті та її трактування потрібно мати хоча б один вказівник певного типу.
Для прикладу:
int *i = malloc(sizeof(*i)); //виділення дин. пам. розміром 4 байти та накладання на неї інтерфейсу типу int
*i = 5; //звернення до 5 елемента масиву за допомогою оператора розіменування
char *arr = calloc(10, sizeof(*arr)); //виділення дин. пам. під масив типу char із 10 елементів
arr[5] = 6; //звернення до 5 елемента масиву за допомогою оператора індексації
std::cout << *(arr+5); //звернення до 5 елемента масиву за допомогою адресної арифметики оператора //розіменування
Маючи один лише вказівник на будь-який тип, можливо і не відомий, можна виділити динамічну пам’ять під змінну цього типу.
struct{ //оголошення анонімної структури
int a;
char b;
} *p;
void *pTmp = malloc(sizeof(*p)); //виділення потрібної кількості байт під зберігання об’кту
memcpy(&p, &pTmp, sizeof(p)); //накладння інтерфейсу на щойно виділену пам’ять
Тут важливо зазначити, що не можна використати оператор = (присвоювання), оскільки нам потрібно явно вказати тип приведення, якого в нас немає.
Динамічні змінніУ мові С є можливість виділяти пам’ять під час виконання програми. Ця можливість забезпечується функціями :

Обратная связь

Если не удалось найти и скачать доклад-презентацию, Вы можете заказать его на нашем сайте. Мы постараемся найти нужный Вам материал и отправим по электронной почте. Не стесняйтесь обращаться к нам, если у вас возникли вопросы или пожелания:

Email: Нажмите что бы посмотреть 

Что такое TheSlide.ru?

Это сайт презентации, докладов, проектов в PowerPoint. Здесь удобно  хранить и делиться своими презентациями с другими пользователями.


Для правообладателей

Яндекс.Метрика