Программирование на языке C++

Функции

Показывать лекцию целиком

Объявление и определение функций

Презентацию к лекции Вы можете скачать здесь.

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

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

Любая программа на С++ состоит из функций, одна из которых должна иметь имя main (с нее начинается выполнение программы).

Функция начинает выполняться в момент вызова. Любая функция должна быть объявлена и определена. Как и для других величин, объявлений может быть несколько, а определение только одно.

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

Объявление функции (прототип, заголовок, сигнатура) задает ее имя, тип возвращаемого значения и список передаваемых параметров.

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

[ класс ] тип имя ([ список_параметров ])[throw ( исключения )]{ тело функции }

Рассмотрим составные части определения.

  • С помощью необязательного модификатора класса можно явно задать область видимости функции, используя ключевые слова extern и static:
  • extern - глобальная видимость во всех модулях программы (по умолчанию);
  • static - видимость только в пределах модуля, в котором определена функция.
  • Тип возвращаемого функцией значения может быть любым, кроме массива и функции (но может быть указателем на массив или функцию). Если функция не должна возвращать значение, указывается тип void.
  • Список параметров определяет величины, которые требуется передать в функцию при ее вызове. Элементы списка параметров разделяются запятыми. Для каждого параметра, передаваемого в функцию, указывается его тип и имя (в объявлении имена можно опускать).
  • В определении, в объявлении и при вызове одной и той же функции типы и порядок следования параметров должны совпадать.

    На имена параметров ограничений по соответствию не накладывается.

    Функцию можно определить как встроенную с помощью модификатора inline, который рекомендует компилятору вместо обращения к функции помещать ее код непосредственно в каждую точку вызова. Модификатор inline ставится перед типом функции. Он применяется для коротких функций, чтобы снизить накладные расходы на вызов.

    Директива inline носит рекомендательный характер и выполняется компилятором по мере возможности.

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

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

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

    Пример функции, возвращающей сумму двух целых величин:

    #include <iostream>
    using namespace std;
    int sum(int a, int b);	// объявление функции
    int main(){
    	int a = 2, b = 3, c, d;
    	c = sum(a, b);	//вызов функции
    	cin >> d;
    	cout << sum(c, d);  // вызов функции
    }
    int sum(int a, int b)	// определение функции
    {return (a+b);}

    Пример функции, выводящей на экран поля переданной ей структуры:

    #include <iostream>
    using namespace std;
    struct Worker
    {		
    	char fio[30];
    	int date, code;
    	float salary;
    };
    void print_worker(Worker);	//объявление функции
    int main()
    {
    Worker stuff[100];
    ...	/* формирование массива stuff */
    for (int i = 0; i<100; i++)
      print_worker(stuff[i]); /*	вызов функции */
    }
    void print_worker(Worker w)	//определение функции
    	{cout << w.fio << ' ' 
    		  << w.date << ' ' 
    		  << w.code << ' ' 
    		  << w.salary;}

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

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

    #include <iostream>
    using namespace std;
    void f(int a)
    {
    cout << "n  m\n";	
    while (a--)
      {
      static int n = 0;
      int m = 0;
      cout << n++ << ' ' 
    	   << m++ << '\n';
    	}
    }
    int main(){ f(3);}

    Статическая переменная n размещается в сегменте данных и инициализируется один раз при первом выполнении оператора, содержащего ее определение. Программа выведет на экран:

    n m
    0 0
    1 0
    2 0

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

    Глобальные переменные

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

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

    Возвращаемое значение

    Возврат из функции в вызвавшую ее функцию реализуется оператором

    return [ выражение ];

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

    Примеры:

    int f1(){ return 1; } 	//правильно
    void f2(){ return 1; }	//неправильно, f2 не должна возвращать значение
    double f3(){ return 1; }	//правильно, 1 преобразуется к типу double

    ВНИМАНИЕ

    Нельзя возвращать из функции указатель на локальную переменную.

    Пример:

    int* f() 
    {
    int a = 5;
    return a;     // нельзя!
    }

    Параметры функции

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

    При передаче по значению объявление параметра похоже на объявление переменной:

    тип имя

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

    параметр = выражение

    Выражение вычисляется; если тип полученного значения не соответствует типу параметра, то выполняется (если это возможно) преобразование типов, и значение присваивается параметру. В частности, если в качестве аргумента задана константа или переменная, совпадающая по типу с параметром, то значение просто копируется в параметр. Копирование требует времени, поэтому способ передачи параметров по значению обычно применяется для данных встроенных типов, время копирования которых мало.

    Никакие изменения значения параметра внутри функции не отражаются на значении переменной-аргумента, так как параметр является локальной переменной.

    При передаче по ссылке объявление параметра представляет собой объявление ссылки без инициализации:

    тип имя

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

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

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

    void swap( int a, int b )   // определение функции обмена
    {   
    int t = a; a = b; b = t; }
    // ...
    int x = 5, y = 6;
    swap( x, y );           // вызов функции обмена

    Функция swap фактически работает с исходными переменными x и y. Изменение функцией нелокальных переменных называется побочным эффектом.

    Использование параметров-ссылок вместо передачи по значению более эффективно, поскольку не требует времени и памяти для копирования аргументов в локальные переменные. Это имеет значение при передаче структур данных большого объема.

    Пример передачи параметров:

    #include <iostream>
    using namespace std;
    void f(int i, int* j, int k);
    int main()
    {   
    int i = 1, j = 2, k = 3;
    cout <<"i j k\n";
    cout << i <<' '
         << j <<' '<< k 
         <<'\n';
    f(i, j, k);
    cout << i <<' '
        << j <<' '<< k;
    }
    void f(int i, int* j, int k)
    {    i++; (*j)++; k++; }

    Результат работы программы:

    i j k 
    1 2 3
    1 3 4

    Первый параметр ( i ) передается по значению. Его изменение в функции не влияет на исходное значение. Второй параметр ( j ) передается по адресу с помощью указателя, при этом для передачи в функцию адреса фактического параметра используется операция взятия адреса, а для получения его значения в функции требуется операция разыменования. Третий параметр ( k ) передается по адресу с помощью ссылки.

    Если требуется запретить изменение параметра, используется модификатор const:

    int f(const char*);
    char* t(char* a, const int* b);

    СОВЕТ

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

    Параметры, передаваемые в функцию, могут быть любого типа (например, вещественного, структурой, перечислением, объединением, указателем), кроме массива или функции, которые передаются с помощью указателей.

    Передача массивов в качестве параметров

    Массив всегда передается по адресу. При этом информация о количестве элементов массива теряется, и следует передавать его размерность через отдельный параметр:

    #include <iostream>
    using namespace std;
    int sum(const int* mas, const int n);
    int const n = 10;
    void main()
    {
    int marks[n] = {3, 4, 5, 4, 4};
    cout << "Сумма элементов массива: " 
    	 << sum(marks, n);
    }
    int sum(const int* mas, const int n) 
    /*варианты: int sum(int mas[], int n) или */
    /*int sum(int mas[n], int n) (n должна быть константой) */
    {   int s = 0;
    	for (int i = 0 ; i<n; i++) s += mas[i];
    	return s;}

    При передаче многомерных массивов все размерности, если они не известны на этапе компиляции, должны передаваться в качестве параметров.

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

    #include <cstdio> using namespace std;
    int sum(const int *a, const int nstr, const int nstb); 
    void main()
    { 
    int b[2][2] = {{2, 2}, {4, 3}};
    /* имя массива передавать нельзя из-за несоответствия типов */
    printf("b  %d\n", sum(b[0][0], 2, 2));	
    int i, j, nstr, nstb, *a; 
    printf("Введите количество строк и столбцов: \n");
    scanf("%d%d", nstr, nstb); 
    a = (int *)malloc( nstr*nstb*sizeof(int) ); 
    for (i = 0; i<nstr; i++)
      for (j = 0; j<nstb; j++)
        scanf("%d", a[i*nstb+j]); 	
    printf("a %d\n", sum(a, nstr, nstb)); 
    }
    int sum(const int *a, const int nstr, const int nstb)
    {int i, j, s = 0;
      for (i = 0; i<nstr; i++)
    	for (j = 0; j<nstb; j++)s += a[i*nstb + j];
    	return s;
    }

    Для того чтобы работать с двумерным массивом естественным образом, можно применить альтернативный способ выделения памяти:

    #include <iostream>
    using namespace std;
    int sum(const int **a, const int nstr, const int nstb);
    void main() 
    {
    int nstr, nstb;
    cin >> nstr >> nstb;
    int **a;
    a = new int* [nstr];
    for (int i = 0; i<nstr; i++)
      a[i] = new int [nstb];
    /* * формирование матрицы a */
    cout << sum(a, nstr, nstb);
    }
    int sum(const int **a, const int nstr, const int nstb)
    {
    int i, j, s = 0;
    for (i = 0; i<nstr; i++)
    for (j = 0; j<nstb; j++)s += a[i][j];
    return s;
    }

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

    Передача имен функций в качестве параметров

    Функцию можно вызвать через указатель на нее. Для этого объявляется указатель соответствующего типа и ему с помощью операции взятия адреса присваивается адрес функции:

    void f(int a ){ /* * */ }	//определение функции
    void (*pf)(int);		//указатель на функцию
    ...
    pf = f; /* указателю присваивается адрес */
    		 /*функции (можно написать pf = f;) */
    pf(10);		 /* функция f вызывается через указатель pf */
    	     /*(можно написать (*pf)(10) ) */

    Для того чтобы сделать программу более читаемой, при описании указателей на функции используют переименование типов ( typedef ). Можно объявлять массивы указателей на функции (это может быть полезно, например, при реализации меню):

    /* описание типа PF как указателя на функцию с одним параметром типа int */
    typedef void (*Pf)(int);	
    /* описание и инициализация массива указателей  */
    PF menu[]={new, open, save}	
    menu[1](10);	 //вызов функции open

    Указатели на функции передаются в подпрограмму таким же образом, как и параметры других типов:

    void fun(PF pf)	/* функция fun получает в качестве*/
    			/* параметра указатель типа PF  */
    {* pf(10); *}	//вызов функции, переданной через указатель

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

    Параметры со значениями по умолчанию

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

    int f(int a, int b = 0);
    void f1(int, int = 100, char* = 0);	
    void err(int errValue = errno);	//	errno - глобальная переменная 
    f(100); f(a, 1);			// варианты вызова функции f
    // варианты вызова функции f1
    f1(a); 
    f1(a, 10); 
    f1(a, 10, "Vasia"); 
    f1(a,,"Vasia")	 // неверно!

    Функции стандартной библиотеки

    Любая программа на С++ содержит обращения к стандартной библиотеке, в которой находятся определения типов, констант, макросов, функций и классов.

    Чтобы использовать их в программе, требуется с помощью директивы #include включить в исходный текст программы заголовочные файлы, в которых находятся соответствующие объявления.

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

    Ниже приведен краткий обзор основных функций.

    Функции ввода/вывода

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

    Для использования функций ввода/вывода в стиле С необходимо подключить к программе заголовочный файл <stdio.h> или <cstdio>. При вводе/выводе данные рассматриваются как поток байтов. Физически поток представляет собой файл или устройство, например, клавиатуру или дисплей, рассматривающиеся как частный случай файла.

    Открытие потока

    Работа с потоком начинается с его открытия. Поток можно открыть для чтения и/или записи в двоичном или текстовом режиме. Функция открытия потока имеет формат:

    FILE* fopen(const char* filename, const char* mode);

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

    "r" - файл открывается для чтения;

    "w" - открывается пустой файл для записи (если файл существует, он стирается);

    "a+" - файл открывается для добавления информации в его конец;

    "r+" - файл открывается для чтения и записи (файл должен существовать);

    "w+" - открывается пустой файл для чтения и записи (если файл существует, он стирается);

    "a" - файл открывается для чтения и добавления информации в его конец.

    Режим открытия может также содержать символы t (текстовый режим) или b (двоичный режим), отличающиеся обработкой символов перехода на новую строку. По умолчанию файл открывается в текстовом режиме, при котором комбинация символов "возврат каретки" и "перевод строки" (0x13 0x10) при вводе преобразуются в одиночный символ перевода строки (при выводе выполняется обратное преобразование). В двоичном режиме преобразования не выполняются.

    Пример:

    FILE *f = fopen("d:\\cpp\\data.txt", "rb+");

    Указатель f используется в дальнейших операциях с потоком. Его передают функциям ввода/вывода в качестве параметра.

    При открытии потока с ним связывается область памяти, называемая буфером. При выводе вся информация направляется в буфер и накапливается там до заполнения буфера или до закрытия потока. Чтение осуществляется блоками, равными размеру буфера, и данные читаются из буфера.

    Существует пять предопределенных потоков, которые открываются в начале работы программы: стандартный ввод stdin, стандартный вывод stdout, стандартный вывод сообщений об ошибках stderr, стандартный дополнительный поток stdaux и стандартная печать stdprn.

    Ввод/вывод в поток

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

    Операции ввода/вывода выполняются начиная с текущей позиции потока, определяемой положением указателя потока. Указатель устанавливается при открытии на начало или конец файла (в соответствии с режимом открытия) и изменяется автоматически после каждой операции ввода/вывода.

    Ниже перечислены основные функции ввода/вывода потока.

  • Чтение и запись потока байтов выполняют функции fread и fwrite.
  • Чтение символа из потока - getc, fgetc, из стандартного потока stdin - getchar.
  • Запись символа в поток - putc, fputc, в стандартный поток stdout - putchar.
  • Чтение строки из потока - fgets, из стандартного потока stdin - gets.
  • Запись строки в поток - fputs, в стандартный поток stdout - puts.
  • Форматированный ввод из потока - fscanf, из стандартного потока stdin - scanf, из строки - sscanf.
  • Форматированный вывод в поток - fprintf, в стандартный поток stdout - printf, в строку - sprintf.
  • Закрытие потока

    Поток закрывается либо при завершении программы, либо явным образом с помощью функции fclose:

    int fclose(FILE*);

    Перед закрытием потока информация из связанных с ним буферов выгружается на диск. Рекомендуется явным образом закрывать потоки, открытые для записи.

    Пример работы с потоками

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

    #include <cstdio>
    #include <cstdlib>
    #include <cconio>
    #include <iostream>
    using namespace std;
    int main()
    {
    FILE *fi, *fo;
    if((fi = fopen("d:\\c\\binfile.txt", "r"))==0)
      {
      cout << "Ошибка открытия входного файла"; 
      return 1;
      };
    if((fo = fopen("d:\\c\\binfile.out", "w+b"))==0)
      {
      cout << "Ошибка открытия выходного файла"; 
      return 1;
      };
    const int dl = 80;
    char	  s[dl];
    struct 
      {
      char type[20];
      int	 opt, rozn;
      char	 comm[40];
      }mon;
    while (fgets(s, dl, fi))
      {
      // Преобразование строки в структуру:
      strncpy(mon.type, s, 19);	//Описание strncpy см. в след. разделе
      mon.opt = atoi(s[20]);	//Описание atoi см. в след. разделе
      mon.rozn = atoi(s[25]);
      strncpy(mon.comm, s[30], 40);
      fwrite(mon, sizeof mon, 1, fo);	
      }
    fclose(fi);
    int i; cin >> i;			//	Номер записи
    // Установка указателя текущей позиции файла на запись i
    fseek(fo, (sizeof mon)*i, SEEK_SET);
    fread(mon, sizeof mon, 1, fo);
    cout << "mon.type " 
    	 << mon.type << " opt " 
    	 << mon.opt << " rozn "
    	 << mon.rozn << endl;
    	fclose(fo);
    }

    Допустим, что в файле хранятся сведения о мониторах. В каждой строке указан тип, оптовая и розничная цены и примечание. Для простоты данные в каждой строке записаны единообразно: первые 20 символов занимает тип монитора, далее по 5 символов оптовая и розничная цена, затем примечание длиной не более 40 символов.

    Функции работы со строками и символами

    Строка представляет собой массив символов, заканчивающийся нуль-символом. В С++ есть две возможности работы со строками: функции, унаследованные из библиотеки С (заголовочный файл <string.h> или <cstring>), и библиотечный класс С++ string, предоставляющий более широкие возможности представления, обработки и контроля строк.

    Библиотека С содержит функции копирования строк ( strcpy, strncpy ), сравнения ( strcmp, strncmp ), объединения строк ( strcat, strncat ), поиска подстроки ( strstr ), поиска вхождения символа ( strchr, strrchr, strpbrk ), определения длины строки ( strlen ) и др.

    В заголовочных файлах <stdlib.h> и <cstdlib> содержатся полезные функции преобразования строк в числа:

    double atof(const char* p);	//преобразует переданную строку в double
    int atoi(const char* p);		//преобразует переданную строку в int
    long atol(const char* p);		//преобразует переданную строку в long

    Пробелы и табуляции в начале строки пропускаются. Преобразование прекращается при встрече недопустимого символа или конца строки. Если строку нельзя преобразовать в число, возвращается 0.

    Для работы с символами в стандартной библиотеке (заголовочные файлы <ctype.h> и <cctype>) есть следующие функции:

    Имя Проверка на принадлежность символа множеству
    isalnum букв и цифр (A-Z, a-z, 0-9)
    isalfa букв (A-Z, a-z)
    iscntrl управляющих символов (с кодами 0..31 и 127)
    isdigit цифр (0-9)
    isgraph Печатаемых символов, кроме пробела ( isalfa | isdigit | ispunct )
    islower букв нижнего регистра (a-z)
    isprint Печатаемых символов
    ispunct знаков пунктуации
    isspace символов-разделителей
    isupper букв верхнего регистра (A-Z)
    isxdigit шестнадцатеричных цифр (A-F, a-f, 0-9)

    Математические функции

    С++ унаследовал из С стандартные математические функции, описание которых находится в заголовочных файлах <stdlib.h>, <math.h> и <cmath>. Они позволяют получить абсолютное значение ( abs, fabs ), округленное число ( ceil, floor ), квадратный корень ( sqrt ), степень ( pow ), значения тригонометрических функций ( sin, cos, tan, sinh, и т. п.), экспоненту ( exp ), логарифм ( log, log10 ), дробную и целую часть числа ( modf ), остаток от деления ( fmod ) и некоторые другие.

    Модульное программирование

    С увеличением объема программы становится неудобно хранить ее в одном файле. Разбиение программы на функции является первым шагом в повышении уровня абстракции программы, следующий - группировка функций и связанных с ними данных в отдельные файлы (модули), компилируемые раздельно.

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

    Модуль содержит данные и функции их обработки. Для того чтобы использовать модуль, нужно знать только его интерфейс. Интерфейсом модуля являются заголовки всех функций и описания доступных извне типов, переменных и констант. Описания глобальных программных объектов во всех модулях программы должны быть согласованы.

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

    Директивы препроцессора

    Препроцессором называется первая фаза компилятора. Инструкции препроцессора называются директивами. Они должны начинаться с символа #, перед которым в строке могут находиться только пробельные символы.

    Директива #include

    Директива #include <имя_файла> вставляет содержимое указанного файла в ту точку исходного файла, где она записана. Включаемый файл также может содержать директивы #include.

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

    Директива #include является простейшим средством обеспечения согласованности объявлений в различных файлах, включая в них информацию об интерфейсе из заголовочных файлов. Заголовочные файлы обычно имеют расширение .h и могут содержать:

  • определения типов, констант, встроенных функций, шаблонов, перечислений;
  • объявления функций, данных, имен, шаблонов;
  • пространства имен;
  • директивы препроцессора;
  • комментарии.
  • В заголовочном файле не должно быть определений функций и данных.

    Директива #define

    Директива #define определяет подстановку в тексте программы. Она используется для определения:

  • символических констант. Формат определения символической константы:
    /* Все вхождения имени заменяются на текст подстановки */
    #define имя текст_подстановки
  • макросов, которые выглядят как функции, но реализуются подстановкой их текста в текст программы. Формат определения макроса:
    #define имя( параметры ) текст_подстановки
  • символов, управляющих условной компиляцией. Они используются вместе с директивами #ifdef и #ifndef. Формат:
    #define имя
  • Примеры:

    #define M 1000
    #define Vasia "Василий Иванович"
    #define MAX(a,b) ((x)>(y)?(x):(y))
    #define __cplusplus

    Параметры используются при макроподстановке, например, если в тексте программы используется вызов макроса y = MAX(sum1, sum2);, он будет заменен на

    y=((sum1)>(sum2)?(sum1):(sum2));

    Использования макросов и символических констант в программах следует избегать.

    Области действия идентификаторов

    Каждый программный объект имеет область действия, которая определяется видом и местом его объявления. Существуют следующие области действия: блок, файл, функция, прототип функции, класс и поименованная область.

    Блок. Идентификаторы, описанные внутри блока, являются локальными. Область действия идентификатора начинается в точке определения и заканчивается в конце блока, видимость - в пределах блока и внутренних блоков, время жизни - локальное. После выхода из блока память освобождается.

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

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

    Прототип функции. Идентификаторы, указанные в списке параметров прототипа (объявления) функции, имеют областью действия только прототип функции.

    Класс. Элементы структур, объединений и классов (за исключением статических элементов) являются видимыми лишь в пределах класса. Они образуются при создании переменной указанного типа и разрушаются при ее уничтожении.

    Поименованная область. С++ позволяет явным образом задать область определения имен как часть глобальной области с помощью оператора namespace.

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

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

    struct Node
    {
    int Node;
    int i;
    }Node;

    В данном случае противоречия нет, поскольку имена типа, переменной и элемента структуры относятся к разным пространствам. В С++ определено четыре раздельных класса идентификаторов.

  • К одному пространству имен относятся имена переменных, функций, типов, определенных пользователем ( typedef ) и констант перечислений в пределах одной области видимости. Все они, кроме имен функций, могут быть переопределены во вложенных блоках.
  • Другой класс имен образуют имена типов перечислений, структур, классов и объединений. Каждое имя должно отличаться от имен других типов в той же области видимости.
  • Отдельный класс составляют элементы каждой структуры, класса и объединения. Имя элемента должно быть уникально внутри структуры, но может совпадать с именами элементов других структур.
  • Метки образуют отдельное пространство имен.
  • Внешние объявления

    Любая функция автоматически видна во всех модулях программы. Если требуется ограничить область действия функции файлом, в котором она описана, используется модификатор static.

    Для того чтобы сделать доступной в нескольких модулях переменную или константу, необходимо:

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

    Все описания одной и той же переменной должны быть согласованы.

    Пример описания двух глобальных переменных в файлах one.cpp и two.cpp с помощью заголовочного файла my_header.h:

    // my_header.h - внешние объявления
    extern int a; 
    extern double b; 
    ...
    // one.cpp
    #include "my_header.h"
    int a;
    ...
    // two.cpp
    #include "my_header.h"
    double b;
    ...

    Если переменная описана как static, область ее действия ограничивается файлом, в котором она описана.

    Поименованные области

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

    Объявление поименованной области (ее также называют пространством имен) имеет формат:

    namespace [ имя_области ]{ /* Объявления */}

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

    Если имя области не задано, компилятор определяет его самостоятельно с помощью уникального идентификатора, различного для каждого модуля. Объявление объекта в непоименованной области равнозначно его описанию как глобального с модификатором static.

    Нельзя получить доступ из одного файла к элементу неименованной области другого файла.

    Пример.

    namespace demo{
    	int i = 1;
    	int k = 0;
    	void func1(int);
    	void func2(int) { /* ... */}
    }
    namespace demo{			// Расширение
    	// int i = 2;	       Неверно - двойное определение
    	void func1(double);	// Перегрузка
    	void func2(int);		// Верно (повторное объявление)
    }

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

    void demo::func1(int) { /* ... */}

    Это применяется для разделения интерфейса и реализации. Таким способом нельзя объявить новый элемент пространства имен.

    Объекты, объявленные внутри области, являются видимыми с момента объявления. К ним можно явно обращаться с помощью имени области и оператора доступа к области видимости ::, например:

    demo::i = 100; demo::func2(10);

    Если имя часто используется вне своего пространства, можно объявить его доступным с помощью оператора using:

    using demo::i;

    После этого можно использовать имя без явного указания области.

    Если требуется сделать доступными все имена из какой-либо области, используется оператор using namespace:

    using namespace demo;

    Операторы using и using namespace можно использовать и внутри объявления поименованной области, чтобы сделать в ней доступными объявления из другой области.

    Пространства имен стандартной библиотеки

    Объекты стандартной библиотеки определены в пространстве имен std. Например, объявления стандартных средств ввода/вывода С в заголовочном файле <stdio.h> помещены в пространство имен следующим образом:

    // stdio.h
    namespace std
    {
    int printf(const char**);
    ... 
    }
    using namespace std;

    Это обеспечивает совместимость сверху вниз. Для тех, кто не желает присутствия неявно доступных имен, определен новый заголовочный файл <cstdio>:

    // cstdio
    namespace std{
    int printf(const char**);
    ... 
    }

    Если в программу включен файл <cstdio>, нужно указывать имя пространства имен явным образом:

    std::printf("*");

    Механизм пространств имен вместе с директивой #include обеспечивают необходимую при написании больших программ гибкость путем сочетания логического группирования связанных величин и ограничения доступа к ненужным средствам.

    Вернуться к учебному плану