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

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

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

Часть 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