Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

Signed char

Char

1

Signed int

Signed,int

~

Signed short int

Short, Signed short

2

Signed long int

Long, Signed long

4

Unsigned char

Unsigned char

1

Unsigned short int

Unsigned short

2

Unsigned long int

Unsigned long

4

Unsigned int

Unsigned

~

Float

 

4

Double

 

8

Long double

 

10

 

 

 

~-розмір залежить від типу комп`ютера

Існують деякі специфічні типи: void -відсутність типу даних;

enum-перелічувальний тип даних. Синтаксично задається так: enum[<тег>]{<список переліку>}[<описувач>][<описувач>…]

Список переліку задає послідовність іменованих констант, яким у випадку відсутності ініциалізатора присвоюється значення по замов-

чуванню 0,1,2,..

Приклад:

enum numbers{zero,one,two} a;

Якщо в списку переліку присутній константний вираз, то він змінює встановлену по замовчуванню послідовність ініціалізації констант:

enum numbers {zero,one,two=5,three,four} a; Тут zero==0, three==6, four==7 .

5.10.2 Перетворення типів

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

char c; int i; c=i ;

У випадку присвоювання більшого типу до меншого відбувається відкидання старших бітів( для цілих типів). У випадку присвоювання меншого типу до більшого відбувається розширення копією знакового біта або нуля. У випадку присвоювання плаваючого типу до цілого відбувається перетворення плаваючого типу до типу int, а потім

91

від типу int до типу операнда, що стоїть в лівій частині операції присвоювання.

Приклад.

Розглянемо програму, що переводить послідовність цифр в числовий еквівалент:

int atoi(char s[]) /* char* s */ {int i,n=0;

for (i=0;s[i]>’0’ && s[i]<’9’;i++) n=n*10+(s[i]-‘0’);

return n;

}

В цьому прикладі у рядку n=n*10+(s[i]-‘0’); в результаті віднімання s[i]-‘0’ отримуємо числове значення (цифру), що задається символом s[i]. Дійсно , s[i] містить код відповідної цифри. Тоді, щоб отримати числове значення, необхідно врахувати, що цифри розміщуються у системі кодування підряд, сама цифра ‘0’ як символ також має деякий код (для нас тут не важливо, який саме). Множення на 10 в циклі дозволяє постійно “зсувати” цифрове представлення вліво, залишаючи вільним молодший розряд для нової цифри. Розглянемо роботу програми.

Нехай на вхід функції подається рядок “3246”. Тоді результати роботи програми можемо проілюструвати в табличці:

№ ітерації (i)

значення s[i]

значення n в циклі

0

‘3’

3

1

‘2’

32

2

‘4’

324

3

‘6’

3246

5.10.3 Засіб typedef.

Мова С має спеціальний механізм, який дозволяє надавати певним конструкціям, що стосуються визначення типу, нових імен типів: typedef <специфікатор типу> <описувач> [ <описувач>,..]

Наприклад, typedef int* pint; визначає ідентифікатор pint як вказівник на тип int.

Легко бачити, що синтаксичне визначення засобу typedef аналогічне оголошенню. Відмінність полягає лише в тому, що замість специфікатора класу пам’яті стоїть ключове слово typedef. Тоді ідентифікатор, який стоїть в описувачі, буде виступати в якості імені нового типу даних. Відмітимо, що аналогічно як і оголошення, кон-

92

char* та

струкція typedef може розміщуватись як на внутрішньому рівні (всередині деякої функції) так і на зовнішньому.

Приклад:

typedef (*(*** newtype) [10]) (char*,int*); newtype a,b,c;

В прикладі для інтерпретації типу newtype можемо використати правило “розшифровки” складених описувачів. Тобто елемент типу newtype є “вказівником на вказівник на вказівник на масив з 10 елементів, що є вказівниками на функцію, що має аргументи типів

int*.

При визначенні типу з використанням вказівників на структуру допускається використання імені структури до її визначення:

typedef struct students *treeptr; struct student

{ char name [20]; int curs;

treeptr left,right;}

Засіб typedef аналогічний макровизначенню #define. Але існує ряд ситуацій, коли через директиву #define неможливо визначити таку конструкцію, яка визначається через директиву typedef:

typedef int*(*pfun)(char*,char*);

pfun a; //це аналогічно int *(*a)(char*,char*);

Засіб typedef використовується:

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

для скорочення оголошень;

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

з естетичних міркувань.

5.10.4 Вказівники та масиви.

5.10.4.1 Вказівники

Вказiвник - це змінна, яка зберігає адресу об'єкта деякого типу. Вказiвник на функцію містить адресу точки входу в функцію. Синтаксично визначення вказівника задається так: [<тип>] * <описувач> Приклад:

int * pi, i=12; pi=&i;

<тип> може задавати базовий тип, перелiковний, структурний та об'єднання.

93

Особливе мiсце займають вказiвники на тип void. Вказiвник на void може вказувати на значення будь - якого типу (містити адресу елемента будь-якого типу).

Приклад: void * p; int i; float f; p=&i; p=&f;

Але отримати значення вказівника на тип void , використовуючи його в арифметичному виразі, не можна. У виразах *v чи v++ буде помилка. Для того, щоб вказівник на тип void можна було б використовувати, необхідно застосовувати операцію явного приведення типу (тип*) <ідентифікатор>:

j=(int*)v;

*((int*)v)++;

Вказiвник на структуру , об'єднання чи перелiковний тип може бути оголошений до того, як цей тип буде визначений. Однак, працювати з ним до визначення типу не можна.

Змінна, оголошена як вказiвник, зберігає адресу пам'ятi. Але вказiвники на один i той самий тип даних не обов'язково мають однаковий розмір та формат.

Дії над вказiвниками :

1)Можна порівнювати їх з нулем i присвоювати їм значення 0.

2)Якщо р i q - це вказiвники на елементи одного масиву, то їх можна порівнювати.

3)До вказiвника можна додавати ціле число. При цьому адреса зміниться з врахуванням розміру типу даних, на який вказував вказівник.

Якщо описано int * p; , то p+4 буде містити адресу елементу типу int, що розміщений на віддалі 4 таких елементів від p:

2 байта

|_____|_____| |_____|_____| |_____|_____| |_____|_____| |_____|_____|

p

p+1

p+2

p+3

p+4

4)Від вказівника можна віднімати ціле число

5)Допускається віднімання вказiвникiв p i q. Результатом буде кількість елементів між p i q.

5.10.4.2 Масиви.

Синтаксично масив задається так:

94

[<тип>] <описувач>[ ] [<тип>] <описувач> [константний вираз]

Приклад: int array[12];

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

Описувач не може бути функцією чи мати тип void. Тобто не допускається масив типу void. Індексація масивів починається з 0. Перша синтаксична конструкція може використовуватись, якщо в оголошенні присутній iнiцiалiзатор, якщо масив оголошується як формальний параметр функції або якщо дане оголошення є посиланням на оголошення масиву в іншому місці програми:

int a[]={1,2,3} void f(int a[])

Виходячи з синтаксичної схеми, можемо записати схему опису багатомірного масиву. Багатомірний масив оголошується шляхом задання послідовності виразів:

[<тип>]<описувач> [<конст. вираз1>][<конст. вираз2>]...

Кожний елемент константного виразу визначає кількість елементів в даному вимiрi.

Елементи багатовимірних масивів розміщуються по рядках. Ініціалізація масивів здійснюється так:

int a[ ]={1,2,3};

int a[3][2]={{1,2},{3,4},{6,7}};

Причому присутність внутрішніх дужок не обов’язкова.

5.10.4.3 Вказiвники та масиви.

В мові С існує поняття індексного виразу. Індексний вираз задається за допомогою квадратних дужок. Значення індексного виразу обчислюється за наступною схемою: до вказівника додається константний вираз в дужках і потім береться значення утвореного вказівника. Якщо описати, наприклад, вказівник int *p; то можемо використовувати індексний вираз p[const], який еквівалентний конструкції *(p+const), де const - деякий константний вираз. Аналогічно, якщо описаний масив int a[5]; то можемо використовувати конструкцію виду *(а+3), яка еквівалентна а[3].

Виходячи з синтаксичної схеми індексного виразу, допускаються і від’ємні індекси масивів: a[-3]. Тоді, якщо описати масиви

int a[3]={1,3,5};

95

int b[4]={2,4,6,8};

то b[-1]==5, b[-2]==3, b[-3]==1.

Механізм обчислення індексного виразу допускає можливість зміни порядку слідування константного виразу та вказівника (чи імені масиву). Тому запис a[4] еквівалентний 4[a] (адже значення виразів *(a+4) та *(4+a) однакові).

Якщо ім’я масиву містить адресу першого елемента масиву, то можемо описати вказівник, ініціалізувавши його іменем масиву:

int a[4],*pa; pa=a;

Тоді pa[0]==a[0], pa[1]==a[1] і т.д.

Очевидно, можемо присвоїти вказівнику pa і адресу будь-якого іншого елемена масиву а: pa=&a[2];

Тоді pa[0]==a[2], pa[1]==a[3] і т.д.

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

Очевидно, що функції можна передавати i частину масиву. Приклад:

int f(char * s) {int i;

for (i=0;*s='\0';i++;s++); return i;}

/*функція обраховує кількість елементів рядка*/ char s[10];

Тоді можемо викликати функцію f(&s[i]); або f(s+i)

Допускається існування масиву вказiвникiв: int * a[20] ;

5.10.4.3 Вказівники на масив та багатовимірні масиви

Визначення int(*a)[20] задає вказівник на масив. В цьому випадку ім’я а теж задає адресу першого елемента масиву, але додавання до такого вказівника, наприклад, одиниці призведе до збільшення адреси на всю довжину масиву - в даному випадку 20 елементів типу int.

Для розуміння механізму інтерпретації багатовимірних масивів можемо провести наступні міркування. Одновимірний масив int

96

a[10] може бути розглянутий через вказівник (а містить адресу першого елемента):

int * pa; pa=a; pa++==a[1];

Двовимірний масив int a[5][10] інтерпретується як вказівник на масив з 10 елементів :

int (*pa)[10]; pa=a;

Тоді **(++pa)==a[1][0]; *(*(pa)+1)==a[1][1];

Аналогічно, якщо розглянути, наприклад, тривимірний масив: int b[10][20][30];

то він інтерпретується як вказівник на двовимірний масив розмірності

20 на 30.

int (*pb)[20][30]; pb=b;

Тоді ***(pb++)==b[1][0][0], *(**(pb++)+1)==b[1][0][1].

Ім’я b буде містити адресу тривимірного масиву (першого його елемента). Тоді b[0],b[1],…,b[9]-адреси відповідних двовимірних ма-

сивів, b[i][j]-одновимірних. Приклад:

int d[1][3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int d1[1][3][4]={111,2,3,4,5,6,7,8,9,10,11,12};

main()

{

printf(“%d”,***(d+1));

}

Буде надруковане значення 111-першого елементу масиву d1.

В загальному випадку доступ до елемента n-вимірного масиву a[N][N]…[N] здійснюється за наступним правилом: a[m1][m2]…[mn]=*(a[0][0]…[0]+m1*Nn-1 +m2*Nn-2+…+mn)

n-1 нульових індексів

При роботі з динамічними масивами необхідно особливо звернути увагу на виділення пам”яті за допомогою функцій malloc чи calloc. Це можна побачити у наступних прикладах.

5.10.4.4 Приклади

Приклад 1. Сортування методом “бульбашки”.

97

Розглянемо алгоритм сортування методом “бульбашки”. Будемо сортувати літери у рядку в порядку зростання їх кодів. Причому розглянемо два варіанти програми, коли до елементів рядка застосовується операція індексації та без використання цієї операції, чисто через вказівники. Ідея сортування методом бульбашки є досить простою. Розглядаючи масив зліва направо, перевіряємо, чи перший елемент менший за наступний. Якщо ця умова виконується, розглядається другий елемент масиву і т.д. Якщо ж виявиться, що біжучий елемент більший за наступний, то ці елементи міняються місцями і перегляд масиву починається спочатку.

Ось перший варіант програми:

#define YES 1 #define NO 0

bsort(char *s)

{

register int i; /* індекс літери, що порівнюється */ register int need = YES; /* чи потрібно продовжувати

сортування */

while( need ){

 

need = NO;

/* не потрібно */

for(i=0; s[i+1]; i++ )

/* умова циклу: ми порівнюємо i-у та i+1-шу літери, * тому і перевіряємо наявність i+1-шої літери */ if( s[i] > s[i+1] ){ /* в неправильному порядку */ swap( &s[i], &s[i+1] ); /* переставити */

need = YES; /* щось змінилось: потрібно * повторити перегляд масиву літер */

}

}

}

А ось другий варіант функції:

bpsort(char *s)

{

register char *p; register need = YES;

while( need ){ need = NO;

for( p = s; p[1] != '\0' ; p++ )

98

if( *p > *(p+1) ){

swap( p, p+1 ); need = YES;

}

}

}

/* обмін двох літер, що знаходяться за адресами s1 і s2

*/

swap( s1, s2 ) register char *s1, *s2;

{

char tmp; /* temporary */

tmp = *s1; *s1 = *s2; *s2 = tmp;

}

char sample1[] = "Homo homini lupus est - ergo bibamus!"; char sample2[ sizeof(sample1) ]; /* масив такого ж розмі-

ру */ main(){

strcpy( sample2, sample1 ); /* скопіювати */ bsort ( sample1 ); printf( "%s\n", sample1 ); bpsort( sample2 ); printf( "%s\n", sample2 );

}

Відмітимо, що використання специфікації пам”яті register в даному прикладі зоблено з метою збільшення швидкодії програми і не є принциповим. Слід звернути увагу на використання функції swap () (про неї сказано детально у розділі 7).

Приклад 2

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

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

sum=(int*)malloc(m*sizeof(int));

arr=(int*)malloc(n*m*sizeof(int));

При такому виділенні пам”ять повинна бути корректно звільнена за допомогою функції free():

free(sum);free(arr);

Ось текст програми:

99

#include<stdio.h>

#include<conio.h>

#include<stdlib.h> void main()

{int i,j,k,n,m,t,*sum,*arr; clrscr();

puts("Введи n таm");

scanf("%d%d",&n,&m);

sum=(int *)malloc(m*sizeof(int)); arr=(int *)malloc(n*m*sizeof(int)); for(i=0;i<n;i++) for(printf("\n"),j=0;j<m;j++)

{*(arr+i*m+j)=rand()%21;printf("%4d",*(arr+i*m+j

));}

printf("\n\n");

for(j=0;j<m;j++)

{for(i=0,sum[j]=0;i<n;i++) sum[j]+=*(arr+i*m+j); printf("%4d",sum[j]);

}

for(i=1;i<m;i++) for(j=0;j<m-i;j++) {if(sum[j]>=sum[j+1]) continue;

t=sum[j];sum[j]=sum[j+1];sum[j+1]=t;

for(k=0;k<n;k++)

{t=*(arr+k*m+j);*(arr+k*m+j)=*(arr+k*m+j+1);*(ar

r+k*m+j+1)=t;}

}

printf("\n\n");

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

for(printf("\n"),j=0;j<m;j++)

printf("%4d",*(arr+i*m+j));

printf("\n\n");

for(j=0;j<m;j++) printf("%4d",sum[j]);

free(sum);free(arr);

getch();

}

Приклад 3 Нехай у масиві записаний деякий текст. Обчислимо средню кі-

лькість слів у реченні та середню довжину речення.

#include<stdio.h>

#include<conio.h>

#include<string.h>

100

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]