Главная

Популярная публикация

Научная публикация

Случайная публикация

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

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Имя_структуры.имя_елемента

Массивы

Как известно, массив- это конечная совокупность данных одного типа. Можно говорить о массивах целых чисел, массивов символов и.т.д. Мы можем даже определить масссив, элементы которого - массивы(массив массивов), определяя, таким образм, многомерные массивы. Любой массив в программе должен быть описан: после имени массива добаляют квадратные скобки [],внутри которых обычно стоит число, показывающее количество элементов массива. Например, запись int x[10]; определяет x как массив из 10 целых чисел. В случае многомерных массивов показывают столько пар скобок, какова размерность массива, а число внутри скобок показывает размер массива по данному измерению. Например, описание двумерного массива выглядит так: int a[2][5];.Такое описание можно трактовать как матрицу из 2 строк и 5 столбцов. Для обрщения к некоторому элементу массива указывают его имя и индекс, заключенный в квадратные скобки(для многомерног массива - несколько индексов, заключенные в отдельные квадратные скобки): a[1][3], x[i] a[0][k+2].Индексы массива в Си всегда начинаются с 0, а не с 1, т.е. описание int x[5];порождает элементы x[0], x[1], x[2], x[3], x[4], x[5].Индекс может быть не только целой константой или целой переменной, но и любым выражением целого типа. Переменная с индексами в программе используется наравне с простой переменной (например, в операторе присваивания, в функциях ввода- вывода). Начальные значения массивам в языке Си могут быть присвоены при компиляции только в том случае, если они объявлены с классом памяти externили static, например: static int a[6]={5,0,4,-17,49,1}; обеспечивает присвоения a[0]=5; a[1]=0; a[2]=4... a[5]=1.Как видите, для начального присвоения значений некоторому массиву надо в описании поместить справа от знака = список инициирующих значений, заключенные в фигурные скобки и разделенные запятыми. Двумерный массив можно инициировать так: static int matr[2][5] = {{3,4,0,1,2},{6,5,1,4,9}}; Матрица хранится в памяти построчно, т.е. самый правый индекс в наборе индексов массива меняется наиболее быстро. Пусть, например, в заданном массиве из 10 целых чисел надо изменить порядок следования его элементов на обратный без привлечения вспомагательного массива. Соответствующая прорамма приведена на примере 3.6. Пример 3.6
/*обращение массива*/ #include <stdio.h> main() { int p,i=0; static a[10]={10,11,12,13,14, 15,16,17,18,19}; while(i<10/2) { p=a[i]; a[i]=a[9-i]; a[9-i]=p; i++; } i=0; while(i<10) printf(" %d",a[i++]); }

Следующяя программа (пример 3.7)позволяет в целочисленном массиве найти разность максимального и минимального элемента. Обратите внимание, что функция fmax при первом обращении к ней дает максимальный элемент массива, а при повторном вызове - минимальный, так как предварительно мы изменили знаки элементов на противоположные. Это изменение знаков учитывается при вызове функции printf. В языке Си отсутствует возможность динамически распределять память под массивы: надо при описании массива задать точно его размер. Но если тот же массив описывается еще раз в другой программе, размеры можно не указывать;достаточно после имени сохранить пару квадратных скобок, например int x[]. Если при вызове функции в качестве аргумента ей передается имя массива, то, в отличае от простых переменных, берется фактически адрес начала этого массива. Поэтому записи fmax(a, 10) и fmax(&a[0], 10) равносильны.

Пример 3.7

/*в массиве найти разность мин. и макс. элементов */ int fmax(x,n) int x[],n; { int max, i=0; max=x[0]; while(i<n) { if(x[i]> max) max=x[i]; i++; } return(max); } #include <stdio.h> main() { static int a[10]= {1,-2,3,-4,5,-6,7,-8,9,-13}; max=fmax(a,10); i=0; while(i<10) { a[i]=-a[i]; i++; } main=fmax(a,10); printf("макс-мин=%d\n",max+min); }

В следующем пример 3.8 массив описан как внешний. Функция main подсчитывает наибольшее число одинаковых идущих подряд элементов массива, определенного вне функции main.

Пример 3.8

/*макс одинаковых подряд*/ #include <stdio.h> int a[]={5,6,6,6,4,3,3,3,3,3,8}; int n=10; main() { int i,k,max; i=k=max=1; while(i<n) { if(a[i]==a[i-1]) k++; else { if(k>max)max=k; k=1; } i++; } printf("kmax=%d\n",(k>max)?k:max); }

Если, как в данном примере, размер массива пропущен, то транслятор определит его дляну, считая присваиваемые значения во время начальной инициализации. Условная операция (k>max) ?k:max в операторе printf предусмотрена для того частного случая, когда весь массив состоит из одинаковых элементов. Приведем несколько примеров, в которых ведется обработка двумерных массивов. Но прежде одну полезную возможносить языка Си. Речь идет о препроцессорном утверждении #difine, позволяющем присваивать символические имена константам. В общем случае это утверждение записывают так: #define строка1 строка2 (точка с запятой не ставится).
Прежде чем исходный текст программы будет передан компилятору, он обрабатывается препроцессором, котоый всюду в исходном текстезаменит вхождение "строка1" на "строка2". Например, строка #difine max 80, записанная в начале программы, обеспечит всюду замену указанного имени max на соответствующую константу. Замена имени связана не только числами, но и текстами. А теперь вернемся к примерам. В следующей программе (пример 3.9) строится единичная матрица a[m][m], размер которой определяется с помощью конструкции #difine m 5. Сам алгоритм вычисления элементов матрицы основан на возведении (i/j)*(j/i) равно единице тогда и только тогда. когда i равно j. В остальных случаях оно равно нулю.

Пример 3.9

#define M 5 #include <stdio.h> main() { int a[M][M]; int j,i=0; while(i<M) { j=1; while(j<M) { a[i][j]=(i/j)*(j/i); printf("%d",a[i][j]); j++; } i++;printf("\n"); } }

В программе (пример 4.0) определяется минимальный элемент кажой строки матрицы и выполняется обмен местами найденого и диагональю этой же строки.
Обращаем внимание на следующее обстоятельство. Если двумерный массив надо передать ыункции, то описание параметра в ней должно обязательно включать в себя размер строки массива, а размер столбцов несущественен. Так, массив из трех строк и пяти столбцов можно описать как int a[3][5]; либо int a[][5];

Пример 4.0

/*обмен мин с диагональю*/ #include <stdio.h> #define M 4 main() { static a[M][M]={ { 3,4,1,5), {-1,6,7,0}, { 1,8,7,-1}, { 4,9,7,-1}}; int i, j, jmin, amin; i=0; while(i<M) { amin=a[i][0]; jmin=0;j=1; while(j<m) { if(a[i][j]<amin) { amin=a[i][j]; jmin=j; } j++; } a[i][jmin]=a[i][i]; a[i][i]=amin; i++; } i=0 while(i<M) { j=0; while(j<M) printf("%3d",a[i][j++]); printf("\n"); i++; } }

Массивы символов

Любая символьная константа, например "ОЙ У ПОЛИ КРИНИЧКА", представляет собой массив символов. Во внутреннем представлении компилятор завершает такой массив символом "\0", так что любая программа может по нему легко обнаружить конец строки. Поэтому строка занимает в паямяти на один символ больше, чем записано между двойными кавычками. Нумерация элементов массива начинается с нуля. Надо помнить, что, например 'T' - это символ(буква), а ' T ' - это строка, состоящая из двух символов: 'T' и '\0'. Отсюда следует, что пустых строк не бывает. Строка в языке Си - это разновидность константы и ее можно присваивать некоторой переменной, представляющей массив символов: char str[]="ТЕКСТ"; Такая запись и короче и понятнее, чем общепринятая для начальной инициализации массивов: char str[]={'Т','Е','К','С','Т',}; Если длина строки в квадратных скобках опущена, то она определяется автоматически, по количеству присваеваемых символов. В приведенном выше примере она равна шести. Запишем программу (пример 4.1), в которой функция main формирует строку по указанному выше правилу и вызывает функцию length,которая в качестве аргумента получает начальный адрес этой строки и вычисляет ее длину(без учета завеершающего символа). Эта функция представляет собой самостоятельный интерес и будет использована нами а дальнейшем как библиотечная. Пример 4.1
/*длина сторки*/ length(s); char s[]; { int l=0; while(s[l]!='\0') i++; return(i); } #include <stdio.h> main() { static char str[]="ОЙ У ПОЛИ КРИНИЧКА"; printf("%d",length(str)); }

Программа приведенная ниже (пример 4.2), выполняет сцепление двух строк. Собственно, сцепление выполняет функция concat(s,t), которая присоединят строку t к концу строки s. Объединяемые строки str1 и str2 объявлены как внешние, причем размер str1 достаточно большой, чтобы сохранить новую строку. Используя спецификацию формата %s, функция printf выводит всю строку сразу, причем она "знает", что печать остановить при достижении завершающего символа "\0".

Пример 4.2

/*сцепление строк*/ concat(s,t) char s[],t[]; { int i,j; i=j=0; /*поиск конца строки*/ while(s[i]!='\0')i++; while((s[i++]=t[j++])!='\0'); /*копия t*/ #include <stdio.h> char str1[45]="ОЙ ТАМ У ПОЛИ КРИНИЧКА"; char str2[]="ТАМ ХОРОША ВОДИЧКА"; main() { concat(str1,str2); printf("%s",str); }

На пример 4.3 приведена программа с функцией revers(s), переставляющей символы строки s в обратном порядке. В качестве библиотечной исползуется ранее рассматривавшаяся функция length. Функция revers меняет местами символы строки, симетричные относительно ее середины; если в строке нечетное число символов, то средний символ остается на месте.

Пример 4.3

/*вращение строки*/ #include stdio.h #include "length.c" revers(s) char s[]; { int l,i=0; l=length(s); while(i_=l/2) { c=s[i]; s[i]=s[l-i-1]; s[l-i-1]=c; i++; } } main() { static char str[]= "ОЙ У ПОЛИ КРИНИЧКА"; revers(str); printf("%s\n",str); }

Указатели

Указатель -это переменная, значением которой является адрессдругой переменной. Так как указатель может ссылаться на переменные разных типов, с указателем в языке Си связывается тип того объекта, на который он ссылается. Для описания указателей используется операция косвенной адресации *. Например, указатель целого типа ukописывается так: int *uk.Унарная операция &, примененная к некоторой переменной, показывает, что нам нужен адресс этой переменной, а не ее текущее значение. Если переменная ukобъявлена как указатель, то оператор присваивания uk=&xозначает: "взять адресс переменной x и присвоить его значение переменной-указателю uk". Унарная операция *.примененная к указателю, обеспечивает доступ к содержимому ячейки памяти, на которую ссылается указатель. Например, *ukможно описать словами как "то, что содержится по адресу, на который указывает uk".Указатели могут использоваться в выражениях. Если. например, переменная ukуказывает на целое x, то *ukможет во всех случаях использоваться вместо x;так, *uk+1увеличивает xна единицу, а *uk=0равносильно x=0.Два оператора присваивания uk=&x;y=*uk;выполняет то же самое, что и один оператор y=x.Польза от применения указателей в таких ситуациях, мягко выражаясь, невелика. Наиболее полно их преимущества при обработке массивов и, в частности, символьных строк. Указатели и массивы тесно связаны друг с другом. Прежде чем рассмотреть эту связь подробно, заметим, что если uk- некоторый указатель, то uk++увеличивает его значение и он теперь указывает на следющий, соседний адресуемый объект. Значение ukиспользуется в выражении, а затем увеличивается. Аналогично определяются операции uk--, ++uk, --uk.В общем случае указатель ukможно скаладывать с целым числом i.Оператор uk+=iпередвигает ссылку на iэлементов относительно текущего значения. Эти конструкции подчиняются правилам адресной арифметики. А теперь вернемся к массивам. Пусть имеется описание int a[5];Оно определяет массив размером 5 элементов, т.е. пять последовательно расположенных ячеек памяти a[0], a[1], a[2], a[3], a[4].Адресс i-го элемента массива равен сумме адреса начального елемента массива и смещения этого элемента на iединиц от начала массива. Это достигается индексированием: a[i]-i-й элемент массива. Но доступ к любому элементу массива может быть выполнен и с помощью указателей, причем, более эффективно. Если uk -указатель на целое, описанный как int *uk, то ukпосле выполнения операции uk=&a[0]содержит адресс a[0],а uk+iуказывает на i -й элемент массива. Таким образом, uk+iявляется адрессом a[i],а *(uk=1)- содержимым i- гоэлемента(операции *и & более приоритетны, чем арифметические операции). Так как имя массива в программе отождествляется с адресом его первого элемента, то выражение uk=&a[0]эквивалентно такому: uk=a.Поэтому значениеa[i]можно записать как *(a+i).Применив к этим двум элементам операцию взятия адреса, получим, что &a[i]и a+iидеитичны. Раньше, в связи с использованием функции scanf,мы говорили, что применение указателей в качестве аргументов функции дает способ обхода защиты значений вызывающей функции от действий вызванной функции. На примере 5.4 приведен текст программы с функцией obmen(x,y),которая меняет местами значения двух целых величин. Так как x,y -адреса переменных aиb,то *xи *y обеспечивают косвенный доступ значениям aи b.К сказанному добавим, что использование указателей позволяет нам обходить ограничения языка Си, согласно которым функциям может возращать только одно значение. Если в качестве функции передается имя массива, то оно фактически определяет адрес начала массива, т.е. является указателем. В качестве илюстрации напишем очередную версию функции length,вычисляющей длину строки. Если имя массива передается функции, то в самой функции можно исходить из того, что она работает с массивом, а можно исходить из того, что она работает с указателем. Пример 5.4
/*обмен a и b */ obmen(int *x,int *y) { int t; t=*x; *x=*y; *y=t; } #include <stdio.h> main() { int a,b; a=3;b=7; obmen(a,b); printf("a=%d b=%d",a,b); }

В определении функции формальные параметры char s[] и char *s совершенно идеитичны. Операция s++ ( пример 5.5 ) увеличение на единицу текущее значение указателя, первоначально указывающее на первый символ строки, а операция *s!='\0' сравнивает очередной символс признаком конца строки.

Пример 5.5

/*длина строки*/ length(s) char *s; { int i; for(i=0; *s!='\0';s++) i+++; return(i); }

Кроме ранее расмотренных операций адресной арифметики, к указателям можно применить операции сравнения == и !=. Даже операции отношения Б <,>= и т.п. работают правильно, если указатели ссылаются на элементы одного и того же массива. В последнем случае возможно даже вычитание ссылок: если u и s ссылаются на элементы одного массива, то u-s есть число элементов между u и s. Используем этот факт для составления еще одной версии функции length ( пример 5.6 ). Cначала u указывает на первый символ строки (char *u = s). Затем в цикле по очереди проверяется каждый символ, пока в конце концов не будет обнаружен "\0". Разность u-s дает как раз длину строки.

Пример 5.6

/*длина строки*/ length(s) char *s; { char *u=s; while(*u!='\0') u++; return(u-s); }

Для илюстрации основных аспектов применения указателей в СИ рассмотрим функцию копирования строки s1 в строку s2. Сначала приведем версию, основанную на работе с массивами(пример 5.7). Для сравнения рядом помещена версия с использованием указателей(пример 5.8).

Пример 5.7

/*копия строки*/ copy(s1,s2) char s1[],s2[]; { int i=0; while((s2[i]=s1[i])!='\0') i++; }

Пример 5.8

/*копия строки*/ copy(s1,s2) char *s1,*s2; { while((*s2=*s1)!='\0') { s2++;s1++; } }

Здесь операция копирования помещена непосредственно в условие, определяющее момент цикла: while((*s2=*s1)!='\0'). Продвижение вдоль массивов вплоть до тех пор, пока не встретится "\0", обеспечивают операторы s2++ и s1++. Их, однако, тоже можно поместить в проверку (пример 5.9).

Пример 5.9

/*копия строки*/ copy(s1,s2) char *s1,*s2; { while((*s2++=*s1++)!='\0'); }

Еще раз напомним, что унарные операции типа * и ++ выполняются справа налево. Значение *s++ cесть символ, на который указывает s до его увеличения. Так как значение "\0" есть нуль, а цикл while проверет, не нуль ли выражение в скобках, то это позволяет опустить явное сравнение с нулем(пример 6.0). Так постепенно функция копирования становится все более компактной и... все менее понятной. Но в системном программировании предпостение чаще отдают именно компактным и, следовательно, более эффективным по быстродействиб программам.

Пример 6.0

/*копия строки*/ copy(s1,s2) char *s1,*s2; { while(*s2++=*s1++); }

В языке Си, что некоторая литерная строка, выраженная как "строка", фактически рассматривается как указатель на нулевой элемент массива " строка". Допускается, например, такая интересная запись:

char *uk; uk="ИНФОРМАТИКА";

Последний оператор присваивает указателю адрес нулевого элемента строки, т.е. символа " И ". Возникает вопрос, где находится массив, содержащий символы " ИНФОРМАТИКА "? Мы его нигде не описывали. Ответ такой: эта строка - константа; она является частью функции, в которой встречается, точно также, как целая константа 4 или символьная константа "А" в операторах i=4; c="A";. Более детально пояснит сказанное программа на пример 6.1, которая печатает строку символов в обратном порядке.

Пример 6.1

#include <stdio.h> main() { char *uk1,*uk2; uk1=uk2="ИНФОРМАТИКА"; while(*uk2!='\0') putchar(*uk2++); putchar('\n'); while(--uk2 >= uk1) putchar(*uk2); putchar('\n'); }

В самом начале указателям uk1 и uk2 присваивается начальный адресс строки " ИНФОРМАТИКА ". Затем строка посимвольно печатается и одновременно указатель uk2 смещается вдоль строки. В конце вывода uk2 указывает на последний символ исходной строки. Во втором цикле while все тот же указатель uk2 начинает изменяться в обратном направлении, уменьнаясь до тех пор, пока он не будет указывать на нулевой элемент массива, обеспечивая выдачу строки в обратном порядке.

Структуры

Структура-это совокупность логически связанных переменных, возможно, различных типов, сгруппированных под одним именем для удобства дальнейшей обработки. Традиционным примером структуры служит учетная карточка работающего: служащий предприятия описывается набором атрибутов, таких, как табельный номер, имя, дата рождения, пол, адресс, зарплата. В свою очередь, некоторые из этих атрибутов сами могут оказаться структурами. Таковы, например: имя, дата рождения, адресс, имеющие несколько компонент. Элементы структуры обозначаются идентификаторами, с которыми можно связываьб описатели типов. В результате структуру можно рассматривать как единое целое и осуществлять при необходимости выбор составляющих ее элементов. Для образования структуры присваиваются имена каждому из включаемых элементов и структуре в целом. Затем посредством спечиального описания задают иерархию, порядок следования и типы элементов, включаемых в структуру. Так, для рассмотренного выще примера с анкетой служащего можно выбрать имена:
tab_nom - табельный номер; fio - фамилия, имя, отчество; pol - пол; summa - зарплата;

: Все эти понятия можно объединить в такую, например, структуру:

struct anketa { int tab_nom; char fio[30]; char data[10]; int pol; char adres[40]; float summa; };

Эта запись называется описанием структуры. Она начинается с ключевого слова struct и состоит из заключенного в фигурные скобки списка описаний. За словом struct может следовать необязательное имя, которое называется именем типа структуры(иногда его называют тэгом или ярлыком структуры). Этот ярлык именует структуру и в дальнейшем может использоваться для сокращения подробного описания. Переменные, упоминающиеся в записи, называются элементами. Следом за правой фигурной скобкой, заканчивающей список элементов, может следовать список переменных, так же, как и в случае базисных типов. Вот почему в приведенном выше описании структуры после закрывающей фигурной скобки стоит точка с запятой; она завершает пустой список. Описание struct {....} p1, p2, p3; синтаксически аналогично int p1, p2, p3; в том смысле, что каждый из операторов описывает p1, p2, p3 как переменные соответствующего типа и приводит к выделению для них памяти. Описание же структуры без последующего списка переменных не выделяет никакой памяти. Оно только определяет форму сируктуры и действует как шаблон. Если такое описание снабжено ярлыком (именем типа), то его можно позже использовать при определении фактических экземпляров структуры. Например, используя указание выше описане anketa, можно с помощью строки

struct anketa a0, a1,a2;

описать структурные переменные a0, a1, a2, каждая из которых строится по шаблону, введенному структурой anketa. Любая переменная a0, a1, a2 содержит в строго определенном порядке элементы tab_nom, fio, data, pol, adres, summa. Все переменные, как и все остальные переменные языкаЮ получают места в памяти.

Внешние и статические структуры можно инициировать, помещая следом за определением список начальных значений элементов:

struct anketa a0={ 1024, "Макагон В.М", "10.01.1943",0, "Одесса, Варневская, 23/99", 175.00};

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

имя_структуры.имя_елемента

Операция выделения элемента. (точка) связывает имя структуры в имя элемента. Например, мы можем с учетом введенных обозначений написать:

a0.data="10.01.43"; a1.summa=0.0; if(a2.pol==1) man=man+1;

Действия над структурами, в общем, ограничены. Все, что можно делать со структурой, - это взять ее адресс с помощью операции & и обращаться к ее элементам, как показано выше. Записи нельзя копировать или присваивать как единое целое; их нельзя передавать в функцию или получить оттуда целиком. Однако, к указателям на структуры это замечание не относится.

На практике структурные переменные обычно появляются в виде массива или списка. Нетрудно видеть, что наши три переменные a0, a1, a2 будет проще использовать, если их объединить в массив, сосотоящий из элементов типа struct anketa. Применив в программе описание

struct anketa a[3];

мы можем употреблять в ней, например, такие операторы:

<== предыдущая лекция | следующая лекция ==>
 | 


Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных