Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






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






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

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

Листинг 2.5. Пример задания функции.
double square(double x) {
x = x*x;
return x; }
int main() {
double arg = 5;
double sq1=square(arg);
double sq2=square(3);
return 0; }

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

int square(int x) {
x = x*x;
return x;
printf(“%d”, x); }

при вызове данной функции оператор printf() не будет выполнен никогда, т.к. оператор return завершит работу функции square. Оператор return является обязательным, если функция возвращает какие-либо значения. Если же она имеет тип void, т.е. ничего не возвращает, то оператор return может не использоваться.

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



Функция может принимать произвольное число аргументов, но возвращает только один или не одного (тип void). Для задания нескольких аргументов функции используется следующая конструкция:

void show(int x, int y, int z) {}

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

void show(int x, y, z) {} //неверное объявление

Если число пользовательских функций велико (50 и выше), то возникает неудобство в их визуальном представлении в общем тексте программы. Действительно, имея список из 100 разных функций с их реализациями, в них становится сложно ориентироваться и вносить необходимые изменения. Для решения данной проблемы в языке С++ при создании своих функций можно пользоваться правилом: сначала задаются объявления функции, а затем их реализации. Этот подход демонстрируется в листинге 2.6.

Листинг 2.6. Использование прототипов функций.
#include
double square(double x);
void my_puts(char ch, int cnt);
int main() {
double sq = square(4.5);
char ch;
ch = ‘a’;
my_puts(ch, 10);
return 0; }
double square(double x) {
x=x*x;
return x; }
void my_puts(char ch, int cnt) {
for(int i = 0; i < cnt; i++)
putchar(ch); }

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

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

Листинг 2.7. Пример использования перегруженных функций.
#include
double abs(double arg);
float abs(float arg);
int abs(int arg);
int main() {
double a_d = -5.6;
float a_f = -3.2;
int a_i;
a_d = abs(a_d);
a_f = abs(a_f);
a_i = abs(-8);
return 0; }
double abs(double arg) {
if(arg < 0) arg = arg*(-1);
return arg; }
float abs(float arg) {
return (arg < 0)? –arg: arg; }
int abs(int arg) {
return (arg < 0)? –arg: arg; }

В представленной программе задаются три функции с именем abs и разными входными и выходными аргументами для вычисления модуля числа. Благодаря такому объявлению при вычислении модуля разных типов переменных в функции main() используется вызов функции с одним и тем же именем abs. При этом компилятор в зависимости от типа переменной автоматически выберет нужную функцию. Такой подход к объявлению функций называется перегрузкой.

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

void some_func(int a = 1, int b = 2, int c = 3) {
printf(“a = %d, b = %d, c = %d\n”, a, b, c); }

Благодаря начальной инициализации значений переменных, функция some_func() может быть вызвана с разным набором аргументов:

int main(void) {
show_func();
show_func(10);
show_func(10, 20);
show_func(10, 20, 30);
return 0; }

В результате, на экране появятся следующие строки:

a = 1, b = 2, c = 3
a = 10, b = 2, c = 3
a = 10, b = 20, c = 3
a = 10, b = 20, c = 30

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

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

void my_func(int a, int b = 1, int c = 1); //правильное объявление
void my_func(int a, int b, int c = 1); //правильное объявление
void my_func(int a=1, int b, int c = 1); //неправильное объявление
void my_func(int a, int b = 1, int c); //неправильное объявление

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

Листинг 2.8. Пример использования рекурсивных функций.
#include
void up_and_down(int);
int main(void) {
up_and_down(1);
return 0; }
void up_and_down(int n) {
printf(“Уровень вниз %d\n”, n);
if(n < 4) up_and_down(n+1);
printf(“Уровень вверх %d\n”, n); }

Результатом работы этой программы будет вывод на экран следующих строк:

Уровень вниз 1
Уровень вниз 2
Уровень вниз 3
Уровень вниз 4
Уровень вверх 4
Уровень вверх 3
Уровень вверх 2
Уровень вверх 1

Полученный результат работы программы объясняется следующим образом. Вначале функция main() вызывает функцию up_and_down() с аргументом 1. В результате аргумент n данной функции принимает значение 1 и функция printf() печатает первую строку. Затем выполняется проверка и если n < 4, то снова вызывается функция up_and_down() с аргументом на 1 больше n+1. В результате вновь вызванная функция печатает вторую строку. Данный процесс продолжается до тех пор, пока значение аргумента не станет равным 4. В этом случае оператор if не сработает и вызовется функция printf(), которая печатает пятую строку «Уровень вверх 4». Затем функция завершает свою работу и управление передается функции, которая вызывала данную функцию. Это функция up_and_down() с аргументом n=3, которая также продолжает свою работу и переходит к оператору printf(), который печатает 6 строку «Уровень вверх 3». Этот процесс продолжается до тех пор, пока не будет достигнут исходный уровень, т.е. первый вызов функции up_and_down() и управление вновь будет передано функции main(), которая завершит работу программы.

Каждое имя в С++ программе должно относиться к уникальной сущности (объекту, функции, типу или шаблону). Это не значит, что оно встречается только один раз во всей программе: его можно повторно использовать для обозначения другой сущности, если только есть некоторый контекст, помогающий различить разные значения одного и того же имени. Контекстом, служащим для такого различения, служит область видимости. В С++ поддерживается три их типа: локальная область видимости, область видимости пространства имен и область видимости класса.Локальная область – это часть исходного текста программы, содержащаяся в определении функции (или в блоке). Любая функция имеет собственную такую часть, и каждая составная инструкция (или блок) внутри функции также представляет собой отдельную локальную область.Область видимости пространства имен – часть исходного текста программы, не содержащаяся внутри объявления или определения функции или определения класса. Самая внешняя часть называется глобальной областью видимости или глобальной областью видимости пространства имен.Объекты, функции, типы и шаблоны могут быть определены в глобальной области видимости. Программисту разрешено задать пользовательские пространства имен, заключенные внутри глобальной области с помощью определения пространства имен. Каждое такое пространство является отдельной областью видимости. Пользовательское пространство, как и глобальное, может содержать объявления и определения объектов, функций, типов и шаблонов, а также вложенные пользовательские пространства имен. Каждое определение класса представляет собой отдельную область видимости класса. Имя может обозначать различные сущности в зависимости от области видимости. В следующем фрагменте программы имя s1 относится к четырем разным сущностям:

#include < iostream>

#include < string>

// сравниваем s1 и s2 лексикографически

int lexicoCompare(const string & sl, const string & s2) {... }

// сравниваем длины s1 и s2

int sizeCompare(const string & sl, const string & s2) {... }

typedef int (PFI)(const string &, const string &);

// сортируем массив строк

void sort(string *s1, string *s2, PFI compare =lexicoCompare)

{... }

string sl[10] = { " a", " light", " drizzle", " was", " falling",

" when", " they", " left", " the", " school" };

int main(){

// вызов sort() со значением по умолчанию параметра compare

// s1 - глобальный массив

sort(s1, s1 + sizeof(s1)/sizeof(s1[0]) - 1);

// выводим результат сортировки

for (int i = 0; i < sizeof(s1) / sizeof(s1[0]); ++i)

cout < < s1[ i ].c_str() < < " \n\t"; }

Поскольку определения функций lexicoCompare(), sizeCompare() и sort() представляют собой различные области видимости и все они отличны от глобальной, в каждой из этих областей можно завести переменную с именем s1.Имя, введенное с помощью объявления, можно использовать от точки объявления до конца области видимости (включая вложенные области). Так, имя s1 параметра функции lexicoCompare() разрешается употреблять до конца ее области видимости, то есть до конца ее определения.Имя глобального массива s1 видимо с точки его объявления до конца исходного файла, включая вложенные области, такие, как определение функции main().
В общем случае имя должно обозначать одну сущность внутри одной области видимости. Если в предыдущем примере после объявления массива s1 добавить следующую строку, компилятор выдаст сообщение об ошибке:

void s1(); // ошибка: повторное объявление s1

Перегруженные функции являются исключением из правила: можно завести несколько одноименных функций в одной области видимости, если они отличаются списком параметров. В С++ имя должно быть объявлено до момента его первого использования в выражении. В противном случае компилятор выдаст сообщение об ошибке. Процесс сопоставления имени, используемого в выражении, с его объявлением называется разрешением. С помощью этого процесса имя получает конкретный смысл. Разрешение имени зависит от способа его употребления и от его области видимости. Мы рассмотрим этот процесс в различных контекстах. Области видимости и разрешение имен – понятия времени компиляции. Они применимы к отдельным частям текста программы. Компилятор интерпретирует текст программы согласно правилам областей видимости и правилам разрешения имен. Локальная область видимостиЛокальная область видимости – это часть исходного текста программы, содержащаяся в определении функции (или блоке внутри тела функции). Все функции имеют свои локальные области видимости. Каждая составная инструкция (или блок) внутри функции также представляет собой отдельную локальную область. Такие области могут быть вложенными. Например, следующее определение функции содержит два их уровня (функция выполняет двоичный поиск в отсортированном векторе целых чисел):

const int notFound = -1; // глобальная область видимости

int binSearch(const vector< int> & vec, int val)

{ // локальная область видимости: уровень #1

int low = 0;

int high = vec.size() - 1;

while (low < = high)

{ // локальная область видимости: уровень #2

int mid = (low + high) / 2;

if (val < vec[ mid ])

high = mid - 1;

else low = mid + 1; }

return notFound; // локальная область видимости: уровень #1}

Первая локальная область видимости – тело функции binSearch(). В ней объявлены параметры функции vec и val, а также переменные low и high. Цикл while внутри функции задает вложенную локальную область, в которой определена одна переменная mid. Параметры vec и val и переменные low и high видны во вложенной области. Глобальная область видимости включает в себя обе локальных. В ней определена одна целая константа notFound.Имена параметров функции vec и val принадлежат к первой локальной области видимости тела функции, и в ней использовать те же имена для других сущностей нельзя. Например:

int binSearch(const vector< int> & vec, int val)

{ // локальная область видимости: уровень #1

int val; // ошибка: неверное переопределение val

Имена параметров употребляются как внутри тела функции binSearch(), так и внутри вложенной области видимости цикла while. Параметры vec и val недоступны вне тела функции binSearch().Разрешение имени в локальной области видимости происходит следующим образом: просматривается та область, где оно встретилось. Если объявление найдено, имя разрешено. Если нет, просматривается область видимости, включающая текущую. Этот процесс продолжается до тех пор, пока объявление не будет найдено либо не будет достигнута глобальная область видимости. Если и там имени нет, оно будет считаться ошибочным.Из-за порядка просмотра областей видимости в процессе разрешения имен объявление из внешней области может быть скрыто объявлением того же имени во вложенной области. Если бы в предыдущем примере переменная low была объявлена в глобальной области видимости перед определением функции binSearch(), то использование low в локальной области видимости цикла while все равно относилось бы к локальному объявлению, скрывающему глобальное:

int low;

int binSearch(const vector< int> & vec, int val){

// локальное объявление low

// скрывает глобальное объявление

int low = 0;

//...

// low - локальная переменная

while (low < = high){//... } //...}

Для некоторых инструкций языка C++ разрешено объявлять переменные внутри управляющей части. Например, в цикле for переменную можно определить внутри инструкции инициализации:

for (int index = 0; index < vecSize; ++index){

// переменная index видна только здесь

if (vec[ index ] == someValue)

break; }// ошибка: переменная index не видна

if (index! = vecSize) // элемент найден.Подобные переменные видны только в локальной области самого цикла for и вложенных в него (это верно для стандарта С++, в предыдущих версиях языка поведение было иным). Компилятор рассматривает это объявление так же, как если бы оно было записано в виде:

// представление компилятора

{ // невидимый блок

int index = 0;

for (; index < vecSize; ++index){ //... }}

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

int index = 0;

for (; index < vecSize; ++index){ //...}

// правильно: переменная index видна

if (index! = vecSize) // элемент найден

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

void fooBar(int *ia, int sz){

for (int i=0; i< sz; ++i)... // правильно

for (int i=0; i< sz; ++i)... // правильно, другое i

for (int i=0; i< sz; ++i)... // правильно, другое i}

Аналогично переменная может быть объявлена внутри условия инструкций if и switch, а также внутри условия циклов while и for. Например:

if (int *pi = getValue()){

// pi! = 0 -- *pi можно использовать здесь

int result = calc(*pi); //...}

else{

// здесь pi тоже видна

// pi == 0

cout < < " ошибка: getValue() завершилась неудачно" < < endl; }

Переменные, определенные в условии инструкции if, как переменная pi, видны только внутри if и соответствующей части else, а также во вложенных областях. Значением условия является значение этой переменной, которое она получает в результате инициализации. Если pi равна 0 (нулевой указатель), условие ложно и выполняется ветвь else. Если pi инициализируется любым другим значением, условие истинно и выполняется ветвь if.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.