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

37.ИНФОРМАТИКА Book си

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

 

Часть 1. Язык Си

31

 

 

 

int

a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};

 

int

a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

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

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

Количество инициализированных элементов может быть меньше размера массива. В этом случае остальные значения массива остаются неопределенными. Например:

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

Здесь описан массив размером в 10 элементов, первые три из которых имеют заданные значения, а остальные значения остались неопределенными, однако их значения можно определить в программе (a[5]=6). Соответственно, можно написать:

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

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

Заметим, что эти две строки не равносильны. Чтобы понять это, напишем первую строку более подробно:

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

Довольно интересный эффект дает следующая строка:

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

Она объявляет массив не из 9, а из 4∙3=12 элементов. Значения последних трех элементов остаются неопределенными, то есть эта строка эквивалентна следующей:

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

В качестве примера работы с массивом далее приведена программа, сортирующая одномерный массив по возрастанию.

#include <stdio.h> void main() {

int a[10]={1,23,5,7,3,1,4,0,20,12};

31

32 Си и Си++

int i,j,c;

printf("Неотсортированный массив: "); for (i=0; i<10; i++) printf("%d ",a[i]); printf("\n");

for (i=1; i<10; i++)

for (j=10; j>=i; j--) if (a[j-1]>a[j]) {

c=a[j-1]; a[j-1]=a[j]; a[j]=c;

}

printf("Отсортированный массив: ");

for (i=0; i<10; i++) printf("%d ",a[i]); printf("\n");

}

Массивы символов или строки

В языке Си отдельного типа строки символов нет, а работа со строками реализована путем использования одномерных массивов типа char. Символьная строка в Си — это одномерный массив типа char, заканчивающийся нулевым байтом (символ: '\0'). Это следует учитывать при описании соответствующего массива символов. Так, если строка должна содержать N символов, то в описании массива следует указать N+1 элемент.

Например, описание char str[12] предполагает, что строка содержит 11 символов, а последний байт зарезервирован под нулевой байт. Конечно, мы задали обычный одномерный массив, но если мы хотим трактовать его как строку символов, то это будет строка максимум из 11 элементов.

Хотя в языке Си нет специального типа строки, язык допускает строковые константы. Строковая константа — это список литер, заключенных в двойные кавычки, например, "Borland C". В конец строковой константы не надо ставить символ '\0'. Это сделает компилятор, и строка "Borland C" в памяти будет выглядеть так:

'B','o','r','l','a','n','d',' ','C','\0'.

Присваивание начальных значений элементам массива для строк может осуществляться как для обыкновенного массива, так и с использованием строковой константы. Следующие два вида объявления аналогичны.

32

Часть 1. Язык Си

33

 

 

char st1[]=

{'B','o','r','l','a','n','d',' ','C','\0'}; char st2[]="Borland C";

Следует обратить внимание, что во втором случае символ '\0' ставится автоматически и его не надо писать в строковой константе.

Есть два простых способа ввести строку с клавиатуры. Первый способ — воспользоваться функцией scanf со спецификатором ввода %s. Надо помнить, что функция scanf вводит символы до первого пробельного символа. Второй способ — воспользоваться специальной библиотечной функцией gets, объявленной в файле stdio.h. Функция gets позволяет вводить строки, содержащие пробелы. Ввод оканчивается нажатием клавиши ―Enter‖. Обе функции автоматически ставят в конец строки нулевой байт. Не забудьте зарезервировать для него место. В качестве параметра в этих функциях используется просто имя массива.

Вывод строк производится функциями printf или puts. Обе функции выводят содержимое массива до первого нулевого байта. Функция puts добавляет в конец выводимой строки символ новой строки. В функции printf переход на новую строку надо предусматривать в строке формата самим.

Рассмотрим пример.

#include <stdio.h>/* Ввод строки с клавиатуры */ void main() {

char str[80];

/* Зарезервировали память под строку */ char V[]=

" Введите строку длиной менее 80 символов:\n";

рrintf("%s", V);

gets(str);

/* читает строку с клавиатуры,

 

пока не нажмете клавишу Enter */

рrintf("Bы ввели строку "); puts(str);

printf("%s", V); scanf("%s", str);

/* читает строку с клавиатуры, пока не встретится пробел (& не ставится)*/

рrintf("Вы ввели строку %s\n", str);

33

34

Си и Си++

}

Для работы со строками существует специальная библиотека, описание которой находится в файле string.h.

Указатели

Указатель — это переменная, которая содержит адрес некоторого объекта. Здесь имеется в виду адрес в памяти компьютера. Указатель объявляется следующим образом:

<тип> *<имя переменной>;

В этом объявлении ―тип‖ — некоторый тип языка Си, определяющий тип объекта, на который указывает указатель (адрес которого содержит); * — означает, что следующая за ней переменная является указателем. Например:

char *ch; float *pf;

Здесь объявлены указатели ch и pf, указывающие на переменные типа char и float соответственно.

С указателями связаны две специальные операции: & и *. Обе эти операции являются унарными, т.е. имеют один операнд, перед которым они ставятся. Операция & является получением адреса объекта. Операция * является получением значения, расположенного по указанному адресу.

В объявлении переменной, являющейся указателем, очень важен базовый тип. Так, например, если указатель имеет базовый тип int, то переменная занимает 2 байта, char — 1 байт и т. д. К указателям можно применить операцию присваивания. Указатели одного и того же типа могут использоваться в операции присваивания, как и любые другие переменные.

Простейшие действия с указателями иллюстрируются следующей программой:

#include <stdio.h> void main() {

int x=1, y=5; int *p, *g; p=&x; g=&y;

printf("x=%d, *p=(x)=%d, y=%d, *g=(y)=%d\n", x,*p, y,*g);

34

Часть 1. Язык Си

35

 

 

*p=8; /* x=8 */

 

printf("x=%d, *p=(x)=%d, y=%d, *g=(y)=%d\n",

 

x,*p, y,*g);

p=g;

/* p=q=&y */

printf("x=%d, *p=(y)=%d, y=%d, *g=(y)=%d\n", x,*p, y,*g);

p=&x;

*p=*g; /* x=y */

printf("x=%d, *p=(x)=%d, y=%d, *g=(y)=%d\n", x,*p, y,*g);

}

После запуска программы на экране появятся четыре строки:

x=1, *p=(x)=1, y=5, *g=(y)=5 x=8, *p=(x)=8, y=5, *g=(y)=5 x=8, *p=(y)=5, y=5, *g=(y)=5 x=5, *p=(x)=5, y=5, *g=(y)=5

В первой строке показано, как передать указателю адрес переменной и использовать эту переменную через указатель. Во втором примере демонстрируется присвоение значения переменной через указатель. В третьем примере указателю p присваивается адрес, который находится в указателе g, т.е. p теперь, как и g, указывает на y. В последнем примере указатель p опять указывает на x, и теперь значение, лежащее по адресу p, приравнивается значению, лежащему по адресу g. Заметим, что в последнем примере, в отличие от третьего, значения указателей не были изменены, т.е. указатели указывают на те же переменные, что и в первых двух случаях.

Нельзя создать переменную типа void, но можно создать указатель на тип void. Указателю на void можно присвоить указатель любого другого типа. Однако при обратном присваивании необходимо использовать явное преобразование указателя на void.

void *pv; float f, *pf; pf=&f; pv=pf; pf=(float*)pv;

Как и над другими типами переменных, над указателями можно производить некоторые арифметические операции, например сложе-

35

36

Си и Си++

 

 

ние и вычитание, однако арифметические действия над указателями имеют свои особенности. При добавлении и вычитании из указателя числа указатель изменяется не на это число, а на число, умноженное на размер элемента данного типа (в байтах), т.е. если указатель указывает на тип int (2 байта), то после прибавления единицы значение указателя увеличится на 2, а если бы он указывал на переменную типа double (8 байт), то увеличился бы на 8. Новое значение указателя должно указывать не на следующий адрес памяти, а на адрес сле-

дующего элемента.

Пусть указатель р имеет значение 2000 и указывает на целое. Тогда в результате выполнения оператора р=р+3 значение указателя р будет 2006.

Можно также вычитать один указатель из другого. Так, если р и р1 — указатели на элементы одного и того же массива, то операция р-р1 дает такой же результат, как и вычитание индексов соответствующих элементов массива.

Другие арифметические операции над указателями запрещены, например, нельзя сложить два указателя, умножить указатель на число и т. д.

Указатели можно сравнивать. Применимы все 6 операций:

<, >, <=, >=, == и !=

Сравнение р<g означает, что адрес, находящийся в р, меньше адреса, находящегося в g. Если р и g указывают на элементы одного массива, то индекс элемента, на который указывает р, меньше индекса элемента, на который указывает g.

В языке Си существует важная связь между массивами и указателями. Принято, что имя массива — это адрес памяти, начиная с которого расположен массив, т.е. адрес первого элемента массива. Таким образом, если был объявлен массив

int plus[10]; int *pl;

то plus является указателем на массив, точнее, на первый элемент массива. Операторы

pl=plus; и pl=&plus[0];

приведут к одному и тому же результату. Для того, чтобы получить значение 6-го элемента массива plus, можно написать plus[5] или *(рl+5) — результат будет один и тот же.

36

Часть 1. Язык Си

37

 

 

Указатели, как и переменные любого другого типа, в свою очередь, тоже можно объединять в массивы. Так, например, объявление массива из 10 указателей на тип int имеет вид:

int *х[10];

После того, как указатель был объявлен, но до того, как ему было присвоено какое-то значение, указатель содержит неизвестное значение. Попытка использовать указатель до присвоения ему какого-то значения является неприятной ошибкой, так как она может нарушить работу не только вашей программы, но и операционной системы. Даже если этого не произойдет, результат работы программы будет неправильным и найти эту ошибку будет достаточно сложно.

Принято считать, что указатель, который указывает в ―никуда‖, должен иметь значение NULL, однако и это не делает его ―безопасным‖. После того как он попадет в правую или левую часть оператора присваивания, он вновь может стать ―опасным‖.

С другой стороны, нулевой указатель можно использовать, например, для обозначения конца массива указателей или для определения указателей, не указывающих ―никуда‖.

. . .

p=NULL;

. . .

/* Здесь может меняться значение p */ if (p) . . .

/* Если указатель p куда-то указывает, то ...*/

Если была попытка присвоить какое-либо значение тому, на что указывает указатель с нулевым значением, система выдает предупреждение ―Null pointer assignment‖, появляющееся во время или после окончания работы программы. Появление этого сообщения сигнализирует использование неинициализированного указателя в программе.

1.8 Функции в языке Си

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

37

38

Си и Си++

 

 

Объявление и описание функции

Основная форма описания функции имеет вид:

<тип> <имя>(<список параметров>) { <тело функции> }

―Тип‖ определяет тип значения, которое возвращает функция. Если тип не указан, то по умолчанию предполагается, что функция возвращает целое значение (типа int). Список параметров состоит из перечня типов и имен параметров, разделенных запятыми. Функция может не иметь параметров, но круглые скобки необходимы в любом случае. В списке параметров для каждого параметра должен быть указан тип.

Пример правильного списка параметров:

f(int х, int у, float z)

Пример неправильного списка параметров:

f(int x, у, float z)

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

Приведем простой пример функции для нахождения наибольшего из двух чисел:

int max(int a, int b) { int m;

if(a>b) m=а; else m=b; return m;

}

Возможно также написать эту функцию без использования дополнительной переменной:

int max(int a, int b) { if(a>b) return a; else return b;

}

Можно еще короче:

38

Часть 1. Язык Си

39

 

 

int max(int a, int b) { if(a>b) return a; return b;

}

А можно и так:

int max(int a, int b) { return (a>b)? a: b; }

В случае, когда оператора return в теле функции нет или за ним нет значения, то возвращаемое функцией значение неизвестно (не определено). Если функция должна возвращать значение, но не делает этого, компилятор выдает предупреждение. Все функции, возвращающие значения, могут использоваться в выражениях языка Си.

Особенностью стандарта ANSI языка Си является то, что для создания правильного машинного кода функции компилятору необходимо сообщить тип возвращаемого результата, а также все типы всех аргументов до первого вызова этой функции. Если функция вызывается в тексте программы раньше, чем она описывается, то компилятор ―не знает‖ о существовании данной функции. Для объявления в этих случаях используется понятие прототипа функции.

Прототип функции задается следующим образом:

<тип> <имя функции>(<список параметров>);

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

int max(int a, int b);

или так:

int max(int, int);

Два этих объявления абсолютно равносильны. Часто прототипы всех функций помещают перед определением всех функций. Это дает возможность вызывать функции, не задумываясь над тем, где они были описаны.

39

40

Си и Си++

 

 

Когда функция не возвращает никакого значения, она должна быть описана как функция типа void (пустая). Этим типом мы иногда прежде пользовались для функции main. Вы не обязаны объявлять функцию типа void, тогда она по умолчанию будет иметь тип int и не возвращать никакого значения. Это вызовет предупреждающее сообщение компилятора, но не будет препятствием для компиляции. Однако объявление типа возвращаемого значения функции является хорошим правилом. Эту конструкцию можно рассматривать как аналогию процедурам в Паскале.

Если функция не имеет аргументов, то при объявлении прототипа такой функции также следует вместо аргументов писать ключевое слово void. В старом стандарте языка Си, который должен поддерживаться новыми компиляторами, отсутствие аргументов в скобках не говорило об их отсутствии в данной функции вообще. Чтобы не было недоразумений или путаницы, желательно использовать ключевое слово void, если параметры у функции отсутствуют, например:

void clear(void);

Объявление главной функции main может иметь следующий вид:

void main(void);

Рекурсивный вызов функции

Приведем пример программы с двумя вариантами функции, реализующей возведение числа a в целую степень b:

#include <stdio.h> #include <conio.h>

/* Прототипы функций */ float pow_n1(float, int); float pow_n2(float, int);

/* Описания функций */

/*I. С помощью циклов*/ float pow_n1(float a, int b)

{

float

p=1;

/* a в нулевой степени =1 */

if (a==0) return 0;

for(;

b>0;

b--) p*=a;

40