37.ИНФОРМАТИКА Book си
.pdfЧасть 1. Язык Си |
41 |
|
|
/* Если b>0, a*a*...*a |
b множителей */ |
for(; b<0; b++) p/=a; |
|
/* Если b<0, 1/a/a/.../a -b раз */
return p;
}
/*II. С помощью рекурсии*/ float pow_n2(float a, int b)
{
if (a==0) return 0;
if (b>0) return pow_n2(a,b-1)*a; if (b<0) return pow_n2(a,b+1)/a; return 1;
/* Если раньше не вышли, значит, b=0 */
}
void main(void) { float a; int b; clrscr();
printf("Введите a(float)= "); scanf("%f",&a);
do {
printf("Введите b(int)= "); scanf("%d",&b);
} while (a==0 && b<=0)
printf("a^b (вариант I )= %f\n",pow_n1(a,b)); printf("a^b (вариант II)= %f\n",pow_n2(a,b)); getch();
}
Первый вариант реализует возведение в степень через цикл. Второй вариант написан с использованием рекурсивного вызова функции. Очень часто первый вариант предпочтительнее, т.к. второй способ требует времени и памяти для многократного вызова функции, однако текст программы зачастую имеет громоздкий вид, что затрудняет читабельность программы и иногда приводит к логическим ошибкам. Второй вариант часто бывает более наглядным, чем первый, и позволяет организовывать сложные алгоритмы с меньшей затратой времени написания программы.
Заметим, что, несмотря на то, что в теле первой функции параметр
41
42 |
Си и Си++ |
|
|
b изменяется и к концу работы функции становится равным нулю, значение b в функции main не изменяется. Это происходит из-за того, что значения всех параметров заносятся в стек функции, и функция работает не с исходными переменными, а с их копиями, находящимися в стеке. Поэтому значения переменных, передаваемых в функцию как параметры, изменяться не могут. Однако бывают случаи, когда значения параметров должны изменяться функцией, это делается через указатели. Через указатели также передаются массивы и соответственно строки.
Передача параметров функции по адресу
В языке Си вызванная функция не может изменить переменные, указанные в качестве фактических параметров при обращении к ней. Если необходимо, функцию можно приспособить для изменения аргументов. Надо передавать в качестве параметра не значение, а адрес переменной, которую нужно изменять, т.е. передавать указатель на переменную. Такой прием в языке Си называется передачей параметра по адресу. Вызванная функция должна описывать аргумент как указатель и обращаться к фактической переменной косвенно, через этот указатель. Если в качестве аргумента используется имя массива, то значение, передаваемое функции, фактически есть адрес первого элемента массива.
Иллюстрацию использования этих двух способов передачи параметров дает следующая программа:
#include <stdio.h>
/* Передача по значению */ void swap(int a, int b) {
int tmp=a; a=b; b=tmp;
}
/* Передача по адресу */
void swap1 (int *a, int *b) { int tmp=*a;
*a=*b; *b=tmp;
}
void main(void) { int х=5, y=10;
printf("Сначала x= %d y= %d\n", x, y); swap (x, у);
42
Часть 1. Язык Си |
43 |
|
|
printf("Теперь x= %d y= %d\n", x, y); swap1(&x, &y);
/* Передаются адреса переменных */ printf("Теперь x= %d y= %d\n", x, y);
}
Результатом работы этой программы будет следующее:
Сначала x= 5 y= 10
Теперь x= 5 y= 10 Теперь x= 10 y= 5
Значения переменных x и y изменились только в случае передачи переменных по адресу. Мы передали функции swap1 адреса переменных x и y. Еще одна особенность состоит в том, что в первом случае мы можем вызвать функцию swap с указанием конкретных значений аргументов: swap(5, 10). Вызвать функцию swap1 в ви-
де swap1(&5, &10) нельзя.
Передача массивов и строк
Если в качестве параметров функции используются массивы, то их можно передать только с помощью передачи по адресу. В качестве аргумента функции надо указать адрес начала массива. Сделать это можно тремя способами:
func(int ar[10]); func(int ar[]); func(int *ar);
Все они приведут к одному и тому же результату.
Следует помнить, что при передаче массива в качестве аргумента функции возможно появление побочного эффекта, а именно, изменения значений элементов массива в результате работы функции.
Примером использования массивов в качестве аргументов функции будет программа, включающая три процедуры: перемножение двух матриц размером 3×3 (mult), сложение двух матриц 3×3 (sum) и вывод на экран матрицы 3×3 (print).
#include <stdio.h> #include <conio.h>
void mult(int U[3][3],int V[3][3],int W[3][3]);
43
44 |
|
Си и Си++ |
|
|
|
|
void |
sum (int U[3][3],int V[3][3],int W[3][3]); |
|
void |
print(int U[3][3], char *str); |
void main(void) {
int A[3][3]= {0,1,2,3,4,5,6,7,8}; int B[3][3]= {1,2,3,4,5,6,7,8,9}; int C[3][3];
clrscr();
print(A,"A");
print(B,"B");
sum(A,B,C); print(C,"C= A+B"); mult(A,B,C); print(C,"C= A*B"); getch();
}
void mult(int U[3][3],int V[3][3],int W[3][3]) { int i, j, k;
for(i=0;i<3;i++) for(j=0;j<3;j++) { W[i][j]=0;
for(k=0;k<3;k++)
W[i][j] += U[i][k]*V[k][j];
}
}
void sum(int U[3][3],int V[3][3],int W[3][3]) { int i, j;
for(i=0;i<3;i++) for(j=0;j<3;j++) W[i][j] = U[i][j]+V[i][j];
}
void print(int U[3][3], char *str) { int i;
printf("Массив %s\n",str); for(i=0; i<3; i++)
printf("%d %d %d\n", U[i][0],U[i][1],U[i][2]);
}
В качестве аргумента функции можно указать U[][3], но нельзя
44
Часть 1. Язык Си |
45 |
|
|
указать U[][]. Точно так же нельзя указать **U или *U. Лучшим решением в случае использования многомерного массива в качестве аргумента функции является указание массива со всеми его размерами
(U[3][3]).
Функции, возвращающие указатель
Использование функций, возвращающих указатели, имеет некоторые особенности. Их значениями являются адреса памяти данных определенного типа. Соответственно должна быть описана и функция. Рассмотрим пример программы с функцией, возвращающей указатель на тип char. Эта функция находит в строке первый пробел и возвращает его адрес.
#include <stdio.h>
/* возвращает адрес первого пробела */
char* find(char* |
str) |
{ |
|
|
int |
i; |
|
|
|
for |
(i=0; str[i] != '\0'; |
i++) |
||
|
if(str[i]==' ') return |
&str[i]; |
||
return NULL; |
/* |
если пробела нет, |
возвращается нулевой указатель */
}
void main () {
char a[]="Строка с пробелами"; char *s;
s=find(a);
if (s) printf("\"%s\", \"%s\"\n",a,s+1); else printf("\"%s\". Пробелов нет!\n",a);
}
Результатом работы будет строка:
"Строка с пробелами", "с пробелами"
Данная функция получает указатель на char, т.е. строку, и возвращает указатель на первый пробел. В теле функции в цикле перебираются символы строки, пока не будет найден пробел. Как только пробел найден, оператор return прекращает работу функции и возвращает указатель на текущий символ. Если же пробел не был найден, то возвращается нулевой указатель.
45
46 Си и Си++
В программе объявляется строка с уже заданным начальным значением и указатель на символ. Вызывается функция find. Как параметр передается строка, а принимается указатель на char. Если указатель s имеет ненулевое значение (в строке есть пробел), то на экран выводится исходная строка и через запятую часть строки после первого пробела. Заметим, что указатель s можно рассматривать как строку, т.к. он указывает на элемент массива символов, который заканчивается нулевым символом. Соответственно если указатель сдвинуть на следующий элемент строки (s+1), то получится часть строки из символов, стоящих после пробела. В случае, если в строке нет пробела (s=NULL), то выведется только исходная строка и сообщение: ―Пробелов нет!‖.
Заметим, что результатом работы данной функции является указатель на символ строки, а не строка, поэтому изменение значений в полученной ―строке‖ является также изменением исходной строки.
1.9 Типы, определяемые пользователем
Кроме стандартных типов данных, язык Си позволяет создавать еще 5 типов данных:
структура, битовое поле, объединение, перечислимый тип,
новое имя (псевдоним) для уже существующего типа.
Структура
Структура объединяет несколько переменных, возможно, разного типа. Переменные, которые объединены структурой, называются чле-
нами, элементами или полями структуры. Структуры в языке Си похожи на записи в Паскале. Объявление структуры имеет следующий синтаксис:
struct [<имя типа>] {<список объявлений элементов>} [<список имен>];
Объявление структуры может задавать имя структурного типа или список имен переменных (имена объявляемых переменных данной структуры, записанные через запятую). Можно одновременно объявить имя структурного типа и список переменных. ―Список имен‖ может состоять не только из самих переменных, но и из указателей на
46
Часть 1. Язык Си |
47 |
|
|
данную структуру, и массивов.
Если при объявлении структуры было задано ―имя типа‖ (―тег‖), то этот структурный тип можно использовать в дальнейшем при объявлении переменных:
struct <имя типа> <список имен>;
Пример определения структуры:
struct student { char name[50]; int kurs;
char group[8]; int stip;
};
Объявление структуры является оператором, и поэтому в конце должна стоять точка с запятой. При этом пока никакая переменная не объявлена. Выделения памяти под переменную не произошло. Под именем student задан частный вид структуры; говорят, что задан шаблон структуры (тип структуры) и определен новый тип struct student. Для того чтобы объявить конкретные переменные типа struct student, можно написать
struct student stud1, stud2;
Теперь объявлены две переменные — stud1 и stud2. Компилятор автоматически выделит под них место в памяти компьютера. Под каждую из переменных типа структуры выделяется непрерывный участок памяти. Задание шаблона структуры и объявление переменных может производиться и в одном операторе:
struct student { char name[50]; int kurs;
char group[8]; int stip;
} stud1, stud2;
Здесь одновременно задается структура с именем student и объявляются переменные stud1 и stud2. Однако если тип структуры student больше нигде не используется, то можно написать:
47
48 |
Си и Си++ |
|
|
struct |
{ |
char name[50]; int kurs;
char group[8]; int stip;
} stud1, stud2;
Доступ к конкретному элементу структуры осуществляется с помощью операции ―точка‖.
stud1.kurs=2; |
/* |
2-ой |
курс */ |
stud1.stip=1; |
/* |
есть |
стипендия */ |
/* копирование строк */ strcpy(stud1.name, "Иванов М.С."); strcpy(stud1.group, "РЛ1-32");
. . .
printf("Студент: %s, курс: %d, группа: %s", stud1.name, stud1.kurs, stud1.group);
if (stud1.stip)
printf(", стипендию получает.\n"); else printf(", стипендии нет.\n");
Из структур, как и из переменных другого типа, можно создавать массивы. Поскольку у нас уже есть описанная структура student, то можем объявить массив следующим образом:
struct student StudKurs[200];
Этот оператор создаст в памяти 200 переменных типа структуры с шаблоном student и именами StudKurs[0], StudKurs[1] и т.д.
Если объявлены две переменные типа структуры с одним шаблоном, можно сделать присваивание
stud1 = stud2;
при этом произойдет побитовое копирование каждого поля одной переменной в соответствующее поле другой переменной. В то же время нельзя использовать операцию присваивания переменных типа структуры, шаблоны которых описаны под разными именами, пусть даже совсем идентично:
struct first { int a; char b; }; struct second { int a; char b; };
48
|
Часть 1. Язык Си |
49 |
|
|
|
|
|
void main() { |
|
|
|
struct first A, C; |
|
|
|
struct second B; |
|
|
|
A.a =1; |
A.b ='f'; |
|
|
C=A; |
|
|
|
B.a=A.a; |
B.b=A.b; |
/* но не B=A; */ |
|
} |
|
|
|
Переменная типа структуры может быть как глобальной, так и локальной. Можно, естественно, использовать структуру или ее элемент как любую другую переменную в качестве параметра функции. Например:
func1(first); func2(&second);
Можно также создавать указатели на структуру и передавать аргумент типа структуры по ссылке. Если мы передаем структуру по значению, то все элементы структуры заносятся в стек. Если структура простая и содержит мало элементов, то это не так страшно. Если же структура в качестве своего элемента содержит массив, то стек может переполниться. При передаче по ссылке в стек заносится только адрес структуры. При этом копирования структуры не происходит, а также появляется возможность изменять содержимое элементов структуры.
struct vect { float x,y,z; } c;
struct vect *a; /* объявление указателя */ a = &c;
указателю а присваивается адрес переменной c. Получить значение элемента x переменной c можно так:
(*a).x;
Использование указателей на структуру встречается часто, поэтому, есть еще один способ получить значение элемента x структуры a. Для этого вводится специальная операция -> (стрелка), то есть обычно вместо (*a).x пишут:
a->x;
Битовое поле
В отличие от других языков программирования, язык Си обеспечивает доступ к одному или нескольким битам в байте или слове. Это
49
50 |
Си и Си++ |
|
|
имеет свои преимущества. Если несколько переменных принимают только два значения (например, логические), можно использовать по 1 биту на каждую. Наряду с побитовыми операциями в языке Си есть встроенный метод, позволяющий иметь доступ к биту, — это поля битов. В действительности поля битов — это специальный тип членов структуры, в котором определено, из скольких бит состоит каждый элемент. Объявление битового поля имеет следующий вид:
<тип> [<имя>]: <длина в битах>;
В этом объявлении структуры ―тип‖ может быть unsigned int или signed int. Недопустимы массивы битовых полей, указатели на битовые поля и функции, возвращающие битовые поля. ―Имя‖ может быть пропущено, тогда соответствующее количество бит не используется (пропускается). Размер структуры кратен байту. Так, если указать:
struct TooBit{
unsigned one_bit : 1, two_bit : 1; } a;
то для переменной a будет выделено 8 бит, но использоваться будут только первые два. Работа в программе с такой структурой аналогична работе с обычной структурой.
a.one_bit=1; a.two_bit=0;
В структуре могут быть смешаны обычные переменные и битовые поля.
struct St{
unsigned a:3,b:1,c:4,d,e; float f,h;
};
Эта структура состоит из трех битовых полей a, b и с, двух беззнаковых целых чисел d и e и двух действительных чисел f и h. Она занимает 13 байт.
Объединение
Объединение (смесь) позволяет в разные моменты времени хранить в одном объекте значения различного типа. В отличие от структуры в объединении элементы хранятся в одной и той же области памяти. Объявляется объединение так же, как и структура, например:
50