Функции

advertisement
Функции
Любая C-программа состоит из одной или более функций
Одна и только одна функция должна называться main()
Функции нельзя определять внутри других функций
Общий вид функции
тип имя_функции (список аргументов)
{
«тело» функции
}
Обратите внимание
Все аргументы функции должны объявляться отдельно, то есть
для каждого из них надо указывать и тип, и имя
f(double a, double b, int j) /* правильный список */
f(double a, b, int j)
/* неправильный */
Если тип возвращаемого значения или тип аргумента не указан,
то считается, что int
Устаревшее поведение, не рекомендуется!
add_one(i) {
return i+1;
}
> gcc test.c
warning: return type defaults to ‘int’ [-Wimplicit-int]
add_one(i) {
^
In function ‘add_one’:
warning: type of ‘i’ defaults to ‘int’ [-Wimplicit-int]
Передача параметров по значению
Что будет напечатано?
void f(int a) {
a++;
}
int main() {
int a = 5;
f(a);
/* a++ */
printf(" %d \n",a);
return 0;
}
Передача параметров по значению
Что будет напечатано?
void f(int a) {
a++;
}
int main() {
int a = 5;
f(a);
/* a++ */
printf(" %d \n",a);
return 0;
}
Output: 5
В С в функцию передаются только значения переменных!
Аргументы функции получают значения в момент вызова и изменение
этих переменных внутри функции не распространяется «наружу»
Оператор return и возвращаемое значение
return
1 обеспечивает немедленный выход из функции
2
возвращает значение (если функция не void)
Основные виды возвращаемых значений:
результаты вычислений: sqrt(), sin()
значения, показывающее успешно ли была выполнена функция:
main(), fclose(), printf()
нет возвращаемых значений: exit(),perror(),free()
Обратите внимание
Использовать возвращаемое значение не обязательно, как, например,
в случае printf()
Объявление и определение функций
Определение функции — собственно вся функция целиком
Объявление функции или прототип — информация о функции:
список параметров и тип возвращаемого значения;
задача прототипа – описать интерфейс функции
Пример:
int main() {
int i = 2;
int j = sqr(i); /* ERROR: What is sqr()? */
printf(" i = %d j = %d\n",i,j);
return 0;
}
Объявление функций
/* prototypes: */
double sqr(double x);
double fun1(void);
double fun2();
/* 1 argument double */
/* 0 arguments */
/* undefined number of arguments */
int main() {
int i = 2;
int j = sqr(i); /* OK: sqr( (double) i) */
printf(" i = %d j = %d\n",i,j);
return 0;
}
Определение функции
double sqr(double x)
return x*x;
}
{
/* called function */
Заголовочные файлы стандартной библиотеки
Интерфейс для библиотек описывается с помощью прототипов
функций в заголовочных файлах (.h = header):
stdio.h – ввод/вывод
math.h – математические функции
stdlib.h – функции общего назначения
limits.h – пределы и константы для целых типов данных
float.h – параметры типов для чисел с плавающей точкой
Пример включения заголовочных файлов
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
Встраиваемые (inline) функции (C99)
Оптимизация вызова функции
Предполагает, что вызов inline функции будет максимально
быстрым: код функции будет вставляться на место вызова
Синтаксически нет различия в вызове обычной и inline
функции
Пример
static inline double Sqr(double x) {return x*x;}
Обратите внимание
static для любой функции означает, что эта функция видна
только в том файле, где она определена.
Такие функции можно целиком помещать в заголовочный файл.
Может привести к «раздуванию» кода. Следует использовать
для действительно маленьких функций.
Обычная и встраиваемая версии функции
(C99)
Иногда необходимо иметь две «версии» одной функции
встраиваемую и обычную, (например, что бы передать указатель
на функцию)
Для таких случаев общая стратегия в С99:
Определяем в заголовочном файле inline function и включаем
этот файл везде, где требуется эта функция
В одном единственном .c-файле добавляем объявление:
external function
1
в заголовочном файле header.h:
inline double Sqr(double x) {return x*x;}
2
в main.c -файле:
#include "header.h"
extern double Sqr(double x); /* prototype */
3
в остальных .c -файлах:
#include "header.h"
Несколько возвращаемых значений?
Найти действительный корни уравнения: x 2 + px + q = 0
Возможное решение: глобальные переменные
double xmin, xmax;
int quad_eq(double p, double q) {
double d = p*p-4*q;
if( fabs(d) < 2.2e-16 ) { /* almost zero */
xmin = xmax = -0.5*p;
return 1;
} else if( d > 0 ) {
d = sqrt(d);
xmin = -0.5*(p+d);
xmax = -0.5*(p-d);
return 2;
}
return 0;
}
. . . продолжение
void test_quad_eq(double p, double q) {
int ret = quad_eq(p,q);
switch( ret ) {
case 0: printf(" no solution\n"); break;
case 1: printf(" one root: %f\n", xmin); break;
case 2: printf(" two roots: %f, %f\n", xmin,xmax);
}
}
int main() {
test_quad_eq(-3,2);
test_quad_eq(-2,1);
test_quad_eq(-3,5);
return 0;
}
two roots: 1.000000, 2.000000
one root: 1.000000
no solution
Глобальные переменные – pro et contra
Следует избегать использовать глобальные переменные!
Затрудняют понимание программы
Скрывают явные связи между функциями и переменными
Невозможно установить правила использования
Делают невозможным параллелизм исполнения
Когда глобальные переменные имеет смысл использовать?
Небольшие, «изолированные» программы
Слишком велики затраты чтобы избежать использование
глобальных переменных: простые вещи следует делать просто
Глобальные переменные – pro et contra
Следует избегать использовать глобальные переменные!
Затрудняют понимание программы
Скрывают явные связи между функциями и переменными
Невозможно установить правила использования
Делают невозможным параллелизм исполнения
Когда глобальные переменные имеет смысл использовать?
Небольшие, «изолированные» программы
Слишком велики затраты чтобы избежать использование
глобальных переменных: простые вещи следует делать просто
Плохие мотивы для глобальных переменных
Меньше набирать текста, программа становится короче . . .
Передавать переменные в функции так утомительно . . .
Не знаю куда бы поместить эту переменную . . .
Адреса и указатели
Схема организации
памяти
/* variables b and c */
int b = 0x01101101;
int c = 0;
/* a - pointer to an int */
int* a = NULL;
/* assign address of b to a */
a = &b;
/* to dereference косвенный доступ */
c = *a;
/* c=0x01101101 */
*a = 0;
/* now b is 0 */
Адреса и указатели
Схема организации
памяти
/* variables b and c */
int b = 0x01101101;
int c = 0;
/* a - pointer to an int */
int* a = NULL;
/* assign address of b to a */
a = &b;
/* to dereference косвенный доступ */
c = *a;
/* c=0x01101101 */
*a = 0;
/* now b is 0 */
Byte addressing: 1KiB = 1024B; 1MiB = 1024KiB = 1048576B; . . .
x86-32: max address (32-bit) = 232 − 1 = 4,294,967,295 B (4GiB);
x86-64: max address (48-bit/52-bit) = 256 TiB / 4 PiB;
Объявление указателей
int *a, *b;
char* c;
void *pv;
int **ppa;
/*
/*
/*
/*
указатели
указатель
указатель
указатель
на
на
на
на
int */
char */
void */
указатель на int */
int (*f)(double) /* указатель на функцию которая
возвращает int и имеет один
аргумент типа double
*/
Объявление указателей
int *a, *b;
char* c;
void *pv;
int **ppa;
/*
/*
/*
/*
указатели
указатель
указатель
указатель
на
на
на
на
int */
char */
void */
указатель на int */
int (*f)(double) /* указатель на функцию которая
возвращает int и имеет один
аргумент типа double
*/
Функция scanf() – новый взгляд
int i;
int j;
int* pj = &j;
scanf("%d",&i); /* читаем i */
scanf("%d",pj); /* читаем j */
Несколько возвращаемых значений: адреса
корни уравнения: x 2 + px + q = 0
int QuadEq(double p, double q, double* x1, double* x2) {
double d = p*p-4*q;
if( fabs(d) < 2.2e-16 ) { /* almost zero */
*x1 = *x2 = -0.5*p;
return 1;
} else if( d > 0. ) {
d = sqrt(d);
*x1 = -0.5*(p+d);
*x2 = -0.5*(p-d);
return 2;
}
return 0;
}
void TestQuadEq(double p, double q) {
double x1 = 0, x2 = 0;
int ret = QuadEq(p,q,&x1,&x2);
switch( ret ) {
case 0: printf(" no solution\n"); break;
case 1: printf(" one root: %f\n", x1); break;
case 2: printf(" two roots: %f, %f\n", x1,x2);
}
}
TestQuadEq(-3,2);
TestQuadEq(-2,1);
TestQuadEq(-3,5);
two roots: 1.000000, 2.000000
one root: 1.000000
no solution
Еще один пример: функция swap()
void swap(int* x, int* y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
int main() {
int i = 2;
int j = 40;
printf("before swap: i = %d, j = %d\n",i,j);
swap(&i,&j);
printf(" after swap: i = %d, j = %d\n",i,j);
return 0;
}
Output:
before swap: i = 2, j = 40
after swap: i = 40, j = 2
Рекурсия
От латинского recursio – круговорот
Рекурсивная функция – функции
вызывающая сама себя.
Пример: Гамма-функция Γ(z) = Γ(z + 1)/z
double gamma_recursion(double z) {
if( z < 10.5 ) {
if( fabs(z) < 2.e-16) {
return HUGE_VAL; /* a large value, possibly +oo */
}
return gamma_recursion(z+1)/z;
}
return exp(log_gam(z));
}
Преимущества рекурсии
Простота написания некоторых алгоритмов
Недостатки рекурсивных функций в С
Обычно приводит к замедлению выполнения
Может вызвать «переполнение стека» (Stack overflow)
Числа Фибоначчи: f0 = 1;
f1 = 1;
fn+1 = fn + fn−1
unsigned int fibonacci(unsigned int n) {
return (n < 2) ? 1 : fibonacci(n-1) + fibonacci(n-2);
}
fibonacci(46) – время выполнения ∼ 20 секунд . . .
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
fibonacci(46) = 1836311903
«Правильная» рекурсия для чисел Фибоначчи
typedef unsigned int Uint;
Uint fib_toc(Uint n, Uint current, Uint next) {
if( n == 0 ) return current;
return fib_toc(n-1,next,current+next);
}
Uint fibonacci_toc(Uint n) {
return (n < 2) ? n : fib_toc(n,0,1);
}
fibonacci_toc(46) = 1836311903
Сравните вызов fib_toc(n,0,1) с циклом:
for(current=0,next=1; n != 0; n--) {
Uint tmp = current;
current = next;
next += tmp;
}
Download