ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 21.12.2021
Просмотров: 973
Скачиваний: 3
void increase (void* data, int psize)
{
if ( psize == sizeof(char) ) {
char* pchar; pchar=(char*)data; ++(*pchar);
}
else if (psize == sizeof(int) ){
int* pint; pint=(int*)data; ++(*pint); }
}
int main ()
{
char a = 'x';
int b = 1602;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
printf("a= %c, b= %i \n", a, b);
}
Результати роботи програми:
Пояснення:
В функцію передаються по черзі адреса на зміну а(символьний тип) та на b (тип цілого числа). Функція приймає покажчик на void тому не важливо що ми передали з осовної програми. Але для роботи з переданими даними, нам потрібно розіменувати покажчик *data. Тут і виникає проблема. Так як ми не знаємо що за тип буле передано. Це вирішується конструкцією if…else де за допомого функції sizeof() ми можемо встановити розмір зміної за її покажчиком((!) Покажчик не залежить від типу! А лише від розрядності ОС, так для 32-х бітних він матиме розмір 4 байти, для 64-х бітних – 8 байтів) на комірку розміщення зміної. Таким чином маючи опції вибору (якщо розмір = 1 байт то це char, якщо =4 то це int)ми встановлюємо тип даних.
В Сі дозволяється створювати покажчики на покажчики, що вказують на дані чи інші покажчики. Для цього потрібно додавати при оголошені покажчик на покажчик ще один оператор «*»:
Приклад 27. Створення покажчика на покажчику.(рис. 5.12)
int x =0;
int *y = &x;
int **z = &y;
x = 'a';
y = &x;
z = &y;
Рисунок 5.12 – Покажчик на покажчику
Значення кожної змінної пишеться всередині кожної клітини, під клітинкою їх адреси в пам'яті.
Нове в цьому прикладі змінна с, яка може використовуватися в трьох різних представленнях, кожне з них буде мати інше значення:
Приклад 28. Різні види представлення.
z має тип символ ** і значення 1000
* z має тип символів * і значення 100
** z має тип символ і значення ‘a’
5.12 Арифметика покажчиків
Виконання арифметичних дій з покажчиками дещо відрізняється від дій з цілочисельними даними, даними з плаваючою точкою чи інших типів. Над ними можна виконувати дії додавання чи віднімання. Але ці дії мають різну поведінку з покажчиками залежно від розміру типу даних, до яких вони застосовуються.
Розглядаючи різні основні типи даних, ми бачили, що деякі займають більше або менше місця, ніж інші, у пам'яті. Наприклад char займає 1 байт, short займає 2 байта і long займає 4.
Приклад 29. Встановлення покажчиків на різні типи даних..
char * pchar;
short * pshort;
long * plong;
Нехай вони вказують на комірку 100.
Якщо ми виконаємо дії інкремента , то зміщення в памяті буде наступним:
Приклад 30. Здійснення інкременту.
pchar++; (аналог pchar = pchar + 1;)
pshort++; (аналог pshort = pshort + 1;)
plong++; (аналог plong = plong + 1;)
Рисунок 5.13 -
Таким чином при збільшені покажчик на один пункт ми зміщуємо його на відповідний розмір типу, тобто для char це 1 байт, для short це 2 байти, для long це 4 байти.
Приклад 31. Представлення операцій з покажчиками та результати їх використання.
#include <stdio.h>
#include <stdlib.h>
int main (){
int a[] = { 10, 20, 30, 40, 50 }, b;
int *p = a;
int *p2 = &a[3];
b = p2 - p;
printf(" 1) b = %i\n", b);
printf(" 2) *p = %i", *p);
printf(" *p++ = %d \n", *p++);
printf(" 3) *p = %i", *p);
printf(" *(++p) = %d \n", *(++p));
printf(" 4) *p = %i", *p);
printf(" *p+2 = %d \n", *p+2);
printf(" 5) *p = %i", *p);
printf(" *(p+2) = %d \n", *(p+2));
printf(" 6) *p = %i", *p);
printf(" *(p+8) = %d \n", *(p+8));
system("PAUSE");
return EXIT_SUCCESS;
}
Результат роботи програми:
Розглянемо детальніше дії:
b у нас різниця між покажчиками р і р2. Так як р2 покажчик на третій елемент, а р покажчик на перший то різниця між ними = 3 (тобто 3 розміра типу int або 3*4=12 байт );
Виводить на екран значення на яке вказує покажчик р, а також демонструє реакцію покажчика на оператори * і ++ (операція * виконується першою, і лише потім збільшується значення покажчика);
Значення р було зміщено на одне значення і тепер воно вказує на другий елемент масиву. В другій дії значення р змінюється до розіменовування тому воно дорівнює наступному значенню в масиві, тобто послідовність дій наступна: спочатку йде інкременування (обов’язково перед р має бути ++ , інакше результат буде як в попередньому полі!), а потім розіменовування;
Тут лише виводиться значення на 2 більше ніж те ,на яке вказує покажчик.
В цій стрічці значення самого покажчика не змінюється, відбувається лише зміщення по масиву на 2 елемента від того на яке він вказує.
Тут вказано що вихід за границі масиву ніяк не контролюється, тому потрібно зберігати обережність при використані покажчиків!
10. СТРУКТУРИ, ОБ’ЄДНАННЯ, ПЕРЕРАХУВАННЯ
7.1 Структури
Структура – це сукупність логічно зв’язаних даних різних типів.
При визначенні структури створюється новий тип, на основі простих і (або) похідних типів.
Синтаксис визначення структури:
struct <назва структури>
{
<опис елементів>
};
де назва структури - це імя типу структури;
опис елементів – програмний блок, де приволиться визначення елементів.
Визначення структури може не містити імені. Такі структури називаються анонімними.
Змінні типу структури називають об’єктом структури. Склад цих об’єктів визначатиметься при оголошенні. Для виділення пам’яті під обєкт структури необхідно оголосити змінну типу структури.
Синтаксис оголошення змінної типу структури:
struct <назва структури> < ім’я змінної>;
де ім’я змінної - це ідентифікатор, який використовується для доступу до обєкту стуктури;
назва структури – імя типу структури.
Приклад 1. Створення структури.
struct lab
{
int num;
char* name;
}; /* визначення структурного типу з іменем lab*/
/* … */
int main(void)
{
/* … */
struct lab Lаb_10;/* створення об’єкту Lab_10 типу структури
lab.*/
/* … */
return 0;
}
Можна одночасно визначати структурний тип і описувати за допомогою нього структуру:
Приклад 2. Оголошення об’єкту зразу після створення структури.
struct gr /* ім’я структурного типу*/
{
short int day; /* елемент структури*/
int month,year; /* однотипні елементи структури*/
} date; /* ім’я структурної змінної*/
Елементи структури називають полями (day, month, year). Поля можуть бути будь-якого базового чи похідного типу, наприклад, масивом, покажчиком, об’єднанням або іншою структурою.
Для звернення до полів структури використовуються уточнені імена через операцію вибору: “крапка” (“.”) при зверненні через ім’я структури і операцію непрямого доступу “->” при зверненні через покажчик.
Приклад 3. Присвоєння полям структури деяких значень.
date.day=10; /*звернення до поля day через об’єкт date */
struct gr *ptrdate=&date; /* створення покажчика ptrdate на об’єкт date */
ptrdate->year=2012; /*Ініціалізація поля year через покажчик на об'єкт ptrdate */
Введення/виведення структур виконується поелементно.
Приклад 4. Введення даних в поле day структури date.
scanf(“%i”, &date.day);
Структури одного типу можна копіювати. Структури, пам’ять під які виділяється на етапі компіляції, можна ініціалізувати, перераховуючи значення їх елементів.
Приклад 5. Ініціалізація полів структури Lab_10.
struct gr date = {1, 2, 3}.
Можна створювати масиви структур.
Приклад 6. Створення масиву структур.
/* структура для опису дати*/
struct date
{
int day, month, year;
};
/* … */
int main(void)
{
/* … */
struct date d[5]={{1,3,1980},{5,1,1990},{1,1,2002}};
/* масив з 5-ти структур типу date , кожна з
яких складається з 3-х елементів типу int,
яким надаються початкові значення*/
/* … */
return 0;
}
Приклад 7. Написати програму, яка зчитуватиме дані про студентів, і видаватиме інформацію про них за введеною фамілією. Приклад структури наведений у таблиці 7.1.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Spysok
{
char PIB[20]; /* прізвище студента*/
char Grup[10]; /* назва групи*/
int Ot[3]; /*3 оцінки*/
float S_Bal; /* середній бал*/
} *vid; /* покажчик на структуру*/
void Vvid(int nom, Spysok *vid)
{
printf("\n Vvedit vidomosti \n %i", (nom+1));
printf("\n PIB - ");
scanf("%s", vid->PIB);
printf(" Nomer Gr - ");
scanf("%s", vid->Grup);
float s=0;
for(int i=0; i<3; i++)
{
printf("\n Otsinki - ");
scanf("%i", &vid->Ot[i]);
s+=vid->Ot[i];
}
vid->S_Bal=s/3;
}
int main(void)
{
struct Spysok Stud[50];
int i, N;
char Litera;
printf ("\n Vvedit kilkist studentiv u grupi < 50 ");
scanf("%i", &N);
for(i=0; i<N; i++)
Vvid(i, &Stud[i]);
printf("\n Spysok studentiv");
for(i=0;i<N;i++)
printf("\n %s \n %s \n %f", Stud[i].PIB,
Stud[i].Grup, Stud[i].S_Bal);
printf("\n Poshuk vidomostey pro studentiv za
pershoyu literoyu prizvyscha\n");
scanf("%c", &Litera);
if(islower(Litera))
toupper(Litera);
printf("\n Vidomosti pro Students:");
int kod_p = 0;
for(i=0; i<N; i++)
if(Stud[i].PIB[0]==Litera)
{
kod_p = 1;
printf("\n %s \n %s \n %f", Stud[i].PIB,
Stud[i].Grup, Stud[i].S_Bal);
}
if(kod_p==0)
printf(" Takyx nemae!");
system("PAUSE");
return 0;
}
Результат роботи програми:
Таблиця 7.1 – Структура з даними про студентів (Приклад 6).
Назва |
Індекси |
|||||||||||
0 |
1 |
2 |
3 |
|||||||||
Stud.PIB |
Філіпчук |
Олєйнікова |
Білоус |
Подольський |
||||||||
Stud.Grup |
1СІ-10 |
1СІ-10 |
1СІ-10 |
1СІ-10 |
||||||||
Stud.Ot |
5 |
5 |
4 |
4 |
5 |
5 |
5 |
5 |
5 |
5 |
4 |
5 |
Stud.S_Bal |
4.66 |
4.66 |
5 |
4.66 |
Stud[0].PIB=”Філіпчук”
Stud[3].S_Bal=4.66
Stud[0].Ot[2]=4 (тому що рахуючи з 0, 2-га оцінка Філіпчук - 4)
Так само можна звертатись до кожного символу рядкових полів:
Stud[0].PIB[2]=’л’
Stud[1].Grup[3]=’-’
Перевизначення структур в межах однієї області видимості не дозволяються, за виключенням випадку коли перевизначення здійснюється в різних програмних блоках. У випадку визначення структури у окремому програмному блоці, область видимості цього визначення закінчується з закінченням програмного блоку, в якому міститься це визначення.
Приклад 8. Області видимості визначень структур.
struct st /* Глобально-видиме визначення */
{
int ivar;
char pstr[8];
};
void func(void)
{
struct st;/* Визначення, яке видиме тільки в межах
функції func */
{
int ivar;
char pstr[8];
float fvar;
};
/* ... */
}
/* ... */
Код, наведений у прикладі 8 є коректним. Але слід застерегти від подібного роду перевизначень, що суттєво ускладнюють розуміння і супроводження програм, написаних у такому стилі.
Для економії памяті в структурах дозволено визначати бітові поля. При використанні бітових полів збільшується час доступу до даних, що зберігаються в цих полях.
Бітове поле – це число, що займає деякий набір бітів і напряму не може бути адресоване процесором. Синтаксис визначення полів і доступу до них базується на синтаксисі структур.
Приклад 9. Визначення бітових полів.
struct pol
{
unsigned char R:2;
unsigned char G:3;
unsigned char B:3;
} color;
Рисунок *.* Розташування бітових полів у памяті
У прикладі 8 розглятуто варіант визначення структури pol, що містить бітові поля. Число, наступне за двокрапкою у описі елементу структури, задає ширину бітового поля. Зображення образу об’єкту color, який має тип pol у памяті представлено на рисунку *.*.
Приклад 10. Робота з бітовими полями.
#include <stdio.h>
#include <stdlib.h>
struct pol
{
unsigned char R:2;
unsigned char G:3;
unsigned char B:3;
};
int main (void)
{
struct pol color;
color.R = 3;
color.G = 6;
color.B = 3;
printf("R=%d\nG=%d\nB=%d\n", color.R, color.G, color.B);
system("pause");
return 0;
}
Результат роботи програми:
7.2 Ключове слово typedef
Ключове слово typedef призначено для введення синонімів до назв існуючих типів.
Синтаксис застосування typedef:
typedef <назва існуючого типу> <назва синоніму>;
Приклад 11. Оголошення та використання typedef.
typedef unsigned long int ULINT;
typedef float FLT;
typedef char SomeString[55];
ULINT b; /* еквівалентно unsigned long int b; */
SomeString str[10]; /* Масив із 10 покажчиків на масив з 55
елеметів типу char (еквівалентно char
str[10][55]) */
Синонімічні типи, введені за допомогою typedef, подібно до змінних, можуть мати обмежену область видимості, або бути глобальними.
Приклад 13. Області видимості типів, введених за допомогою typedef.
#include <stdio.h>
#include <stdlib.h>
typedef int cntr; /* тип cntr доступний для всієї подальшої
програми */
cntr f(void)
{
static cntr count=0;
return ++count;
}
int vizov(void)
{
typedef int chislo_viklikiv; /* тип chislo_viklikiv не
можна використовувати за межами vizov() */
chislo_viklikiv c;
while((c=f())<250);
return c;
}
int main(void)
{
printf("Chislo viklikiv funkcii f(): %d\n",vizov());
system("PAUSE");
return 0;
}
Результат роботи програми:
Програма, приведена у прикладі 13 ілюструє обмеженість область видимості типу chislo_viklikiv, який введено локально для конкретної функції. Використання цього типу за межами функції vizov приведе до помилки на етапі компіляції.
За допомогою typedef можна вводити необмежену кількість синонімів до єдиного типу, навіть коли області видимості синонімів перетинаються.
Але введення двох синонімів з однаковим ім’ям навіть до різних типів в одній області видимості призведе до генерації компілятором помилки. Виключення становить перевизначення синонімів в різних програмних блоках. У цьому випадку перевизначений синонім стає локальним для даного блоку і його дія закінчується по закінченню програмного блоку.
Приклад 14. Превизначення синонімів
void f(void)
{
typedef unsigned int ui;
/* ... */
{
/* ... */
typedef int ui;/* локальне пере визначення ui */
/* ... */
}
/* ... */
}
typedef дозволяє одночасно вводити декілька синонімів до одного типу. У цьому випадку синоніми записуються через кому. Допустимо замість імені типу при використанні typedef приводити саме визначення типу.
Приклад 15. Одночасне введення декількох синонімів та визначення типу всередині typedef
#include <stdio.h>
#include <stdlib.h>
typedef unsigned long int DWORD;
typedef unsigned short int WORD;
typedef struct tagBFHDR
{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BFHDR, *PBFHDR;
int main(void)
{
BFHDR bf;
BFHDR pbf = &bf;
pbf->bfType = 1;
bf.bfSize = 10;
printf(“%d\n”, pbf->bfSize + bf.bfType);
system(“PAUSE”);
return 0;
}
Результат роботи програми:
Код з прикладу 15 вводить у програму тип BFHDR, який є синонімом типу struct tagBFHDR та тип *PBFHDR, який є синонімом типу struct tagBFHDR*. Подальше використання введених синонімів нічим не відрізняється від використання типу-оригіналу, до якого ці синоніми були введені.