Программирование СИ++

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

Рубрика Программирование, компьютеры и кибернетика
Вид учебное пособие
Язык русский
Дата добавления 17.02.2012
Размер файла 135,0 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

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

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

Таким образом, прототип функции необходимо задавать в следующих случаях:

1. Функция возвращает значение типа, отличного от int.

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

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

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

Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Если спецификатор класса памяти не указан, то подразумевается класс памяти extern.

Вызов функции имеет следующий формат:

адресное-выражение ([список-выражений])

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

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

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

Выполнение вызова функции происходит следующим образом:

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

2. Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3. Управление передается на первый оператор функции.

4. Выполнение оператора return в теле функции возвращает управление и возможно, значение в вызывающую функцию. При отсутствии оператора return управление возвращается после выполнения последнего оператора тела функции, а возвращаемое значение не определено.

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

Пример:

int (*fun)(int x, int *y);

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

int *fun (intx,int *y);

будет интерпретироваться как объявление функции fun возвращающей указатель на int.

Вызов функции возможен только после инициализации значения указателя fun и имеет вид:

(*fun)(i,&j);

В этом выражении для получения адреса функции, на которую ссылается указатель fun, используется операция разадресации *.

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

Пример:

double (*fun1)(int x, int y);

double fun2(int k, int l);

fun1 = fun2; /* инициализация указателя на функцию */

(*fun1)(2,7); /* обращение к функции */

В рассмотренном примере указатель на функцию fun1 описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция, описанная иначе, чем указатель, произойдет ошибка.

Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).

Пример:

double proiz(double x, double dx, double (*f)(double x) );

double fun(double z);

int main()

{

double x; /* точка вычисления производной */

double dx; /* приращение */

double z; /* значение производной */

scanf("%f,%f",&x,&dx); /* ввод значений x и dx */

z=proiz(x,dx,fun); /* вызов функции */

printf("%f",z); /* печать значения производной */

return 0;

}

double proiz(double x,double dx, double (*f)(double z) )

{ /* функция, вычисляющая производную */

double xk, xk1, pr;

xk = fun(x);

xk1 = fun(x+dx);

pr = (xk1/xk-1)*xk/dx;

return pr;

}

double fun( double z)

{ /* функция, от которой вычисляется производная */

return (cos(z));

}

Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме

z=proiz(x,dx,cos);

а для вычисления производной от функции sin(x) в форме

z=proiz(x,dx,sin);

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

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

Классический пример рекурсии - это математическое определение факториала n!

n! = 1 при n=0;

n*(n-1)! при n>1 .

Функция, вычисляющая факториал, будет иметь следующий вид:

long fakt(int n)

{

return ( (n==1) ? 1 : n*fakt(n-1) );

}

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

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

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

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

Примерами функций с переменным числом параметров являются функции из библиотеки функций языка СИ, осуществляющие операции ввода-вывода информации (printf,scanf и т.п.). Подробно эти функции рассмотрены далее.

Программист может разрабатывать свои функции с переменным числом параметров. Для обеспечения удобного способа доступа к аргументам функции с переменным числом параметров имеются три макроопределения (макросы) va_start, va_arg, va_end, находящиеся в заголовочном файле stdarg.h. Эти макросы указывают на то, что функция, разработанная пользователем, имеет некоторое число обязательных аргументов, за которыми следует переменное число необязательных аргументов. Обязательные аргументы доступны через свои имена как при вызове обычной функции. Для извлечения необязательных аргументов используются макросы va_start, va_arg, va_end в следующем порядке:

Макрос va_start предназначен для установки аргумента arg_ptr на начало списка необязательных параметров и имеет вид функции с двумя параметрами:

void va_start(arg_ptr,prav_param);

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

va_list arg_ptr;

Макрос va_start должен быть использован до первого использования макроса va_arg.

Макрокоманда va_arg обеспечивает доступ к текущему параметру вызываемой функции и тоже имеет вид функции с двумя параметрами

type_arg va_arg(arg_ptr,type);

Эта макрокоманда извлекает значение типа type по адресу, заданному указателем arg_ptr, увеличивает значение указателя arg_ptr на длину использованного параметра (длина type) и таким образом параметр arg_ptr будет указывать на следующий параметр вызываемой функции. Макрокоманда va_arg используется столько раз, сколько необходимо для извлечения всех параметров вызываемой функции.

Макрос va_end используется по окончании обработки всех параметров функции и устанавливает указатель списка необязательных параметров на ноль (NULL).

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

Пример:

#include

int main()

{ int n;

int sred_znach(int,...);

n=sred_znach(2,3,4,-1);

/* вызов с четырьмя параметрами */

printf("n=%d",n);

n=sred_znach(5,6,7,8,9,-1);

/* вызов с шестью параметрами */

printf("n=%d",n);

return (0);

}

int sred_znach(int x,...);

{

int i=0, j=0, sum=0;

va_list uk_arg;

va_start(uk_arg,x); /* установка указателя uk_arg на */

/* первый необязятельный параметр */

if (x!=-1) sum=x; /* проверка на пустоту списка */

else return (0);

j++;

while ( (i=va_arg(uk_arg,int))!=-1)

/* выборка очередного */

{ /* параметра и проверка */

sum+=i; /* на конец списка */

j++;

}

va_end(uk_arg); /* закрытие списка параметров */

return (sum/j);

}

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

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

Функция main может иметь и третий параметр, который принято называть argp. Он служит для передачи в функцию main параметров операционной системы (среды), в которой выполняется СИ-программа.

Заголовок функции main имеет вид:

int main (int argc, char *argv[], char *argp[])

Если, например, командная строка СИ-программы имеет вид:

A:\>cprog working 'C program' 1

то аргументы argc, argv, argp представляются в памяти как показано на рис. 1.

argc [ 4 ]

argv [ ]--> [A:\cprog.exe\0]

[ ]--> [working\0]

[ ]--> [C program\0]

[ ]--> [1\0]

[NULL]

argp [ ]--> [path=A:\;C:\\0]

[ ]--> [lib=D:\LIB\0]

[ ]--> [include=D:\INCLUDE\0]

[ ]--> [conspec=C:\COMMAND.COM\]

[NULL]

Операционная система поддерживает передачу значений для параметров argc, argv, argp, а на пользователе лежит ответственность за передачу и использование фактических аргументов функции main.

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

Пример:

int main ( int argc, char *argv[], char *argp[])

{ int i=0;

printf ("\n Имя программы %s", argv[0]);

for (i=1; i>=argc; i++)

printf ("\n аргумент %d равен %s", argv[i]);

printf ("\n Параметры операционной системы:");

while (*argp)

{ printf ("\n %s",*argp);

argp++;

}

return (0);

}

Доступ к параметрам операционной системы можно также получить при помощи библиотечной функции geteuv, ее прототип имеет следующий вид:

char *geteuv (const char *varname);

Аргумент этой функции задает имя параметра среды, указатель на значение которой выдаст функция geteuv. Если указанный параметр не определен в среде в данный момент, то возвращаемое значение NULL.

Используя указатель, полученный функцией geteuv, можно только прочитать значение параметра операционной системы, но нельзя его изменить. Для изменения значения параметра системы предназначена функция puteuv.

Компилятор языка СИ строит СИ-программу таким образом, что вначале работы программы выполняется некоторая инициализация, включающая, кроме всего прочего, обработку аргументов, передаваемых функции main, и передачу ей значений параметров среды. Эти действия выполняются библиотечными функциями _setargv и _seteuv, которые всегда помещаются компилятором перед функцией main.

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

_setargv()

{ return; /* пустая функция */

}

-seteuv()

{ return ; /* пустая функция */

}

int main()

{ /* главная функция без аргументов */

...

...

renurn (0);

}

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

Выход из функций. Оператор return

Оператор return завершает выполнение функции, в которой он задан, и возвращает управление в вызывающую функцию, в точку, непосредственно следующую за вызовом. Функция main передает управление операционной системе. Формат оператора:

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

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

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

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

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

Пример:

int sum (int a, int b)

{ renurn (a+b); }

Функция sum имеет два формальных параметра a и b типа int, и возвращает значение типа int, о чем говорит описатель, стоящий перед именем функции.

Возвращаемое оператором return значение равно сумме фактических параметров.

Пример:

void prov (int a, double b)

{ double c;

if (a==10) return;

else { c=a+b;

if ((2*c-b)==11) return;

}

}

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

Преобразования при вызове функции

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

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

Эти преобразования выполняются независимо для каждого аргумента. Величины типа float преобразуются к double, величины типа char и short преобразуются к int, величины типов unsigned char и unsigned short преобразуются к unsigned int. Могут быть также выполнены неявные преобразования переменных типа указатель. Задавая прототипы функций, можно переопределить эти неявные преобразования и позволить компилятору выполнить контроль типов.

Встроенные функции

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

Синтаксис встроенной функции:

inline возврТип имяФункции (<список параметров>)

{<операторный блок>}

Примеры:

inline double cube (double x)

{ return x*x*x;}

inline char nextChar (char c)

{ return c+1;}

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

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

Аргументы по умолчанию

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

- если одному параметру присвоено значение по умолчанию, то значения по умолчанию надо присваивать и всем последующим параметрам;

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

Перегрузка функций

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

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

1.9.2 Классы памяти переменных и функций

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

При такой структуре исходной программы функции, находящиеся в разных исходных файлах могут использовать глобальные внешние переменные. Все функции в языке Си по определению внешние и всегда доступны из любых файлов. Например, если программа состоит из двух исходных файлов, как показано на рис. 2., то функция main может вызывать любую из трех функций fun1, fun2, fun3, а каждая из этих функций может вызывать любую другую.

fun1()

{ ... }

main ()

{ ... }

...

fun2()

{ ... }

fun3()

{ ... }

file1.c

file2.c

Рисунок 2 - Пример программы из двух файлов

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

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

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

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

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

Если объект объявлен внутри блока, то он видим в этом блоке, и во всех внутренних блоках. Если объект объявлен на внешнем уровне, то он видим от точки его объявления до конца данного исходного файла.

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

Спецификатор класса памяти в объявлении переменной может быть auto, register, static или extern. Если класс памяти не указан, то он определяется по умолчанию из контекста объявления.

Объекты классов auto и register имеют локальное время жизни. Спецификаторы static и extern определяют объекты с глобальным временем жизни.

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

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

Спецификатор класса памяти register предписывает компилятору распределить память для переменной в регистре, если это представляется возможным. Использование регистровой памяти обычно приводит к сокращению времени доступа к переменной. Переменная, объявленная с классом памяти register, имеет ту же область видимости, что и переменная auto. Число регистров, которые можно использовать для значений переменных, ограничено возможностями компьютера, и в том случае, если компилятор не имеет в распоряжении свободных регистров, то переменной выделяется память как для класса auto. Класс памяти register может быть указан только для переменных с типом int или указателей с размером, равным размеру int.

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

Пример:

/* объявления переменной i на внутреннем уровне

с классом памяти static. */

/* исходный файл file1.c */

main()

{ ...

}

fun1()

{

static int i=0; ...

}

/* исходный файл file2.c */

fun2()

{

static int i=0; ...

}

fun3()

{

static int i=0; ...

}

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

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

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

Пример:

/* объявления переменной i, являющейся именем внешнего

массива длинных целых чисел, на локальном уровне */

/* исходный файл file1.c */

main()

{ ... }

fun1()

{

extern long i[]; ...

}

/* исходный файл file2.c */

long i[MAX]={0};

fun2()

{ ... }

fun3()

{ ... }

Объявление переменной i[] как extern в приведенном примере делает ее видимой внутри функции fun1. Определение этой переменной находится в файле file2.c на глобальном уровне и должно быть только одно, в то время как объявлений с классом памяти extern может быть несколько.

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

Пример:

main()

{

extern int st[]; ...

}

static int st[MAX]={0};

fun1()

{ ... }

Объявление переменной со спецификатором extern информирует компилятор о том, что память для переменной выделять не требуется, так как это выполнено где-то в другом месте программы.

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

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

1. Переменная объявлена с классом памяти static. Такая переменная может быть инициализирована явно константным выражением, или по умолчанию нулевым значением. То есть обявления static int i=0 и static int i эквивалентны, и в обоих случаях переменной i будет присвоено значение 0.

2. Переменная объявлена без указания класса памяти, но с явной инициализацией. Такой переменной по умолчанию присваивается класс памяти static. То есть объявления int i=1 и static int i=1 будут эквивалентны.

Переменная, объявленная глобально, видима в пределах остатка исходного файла, в котором она определена. Выше своего описания и в других исходных файлах эта переменная невидима (если только она не объявлена с классом extern).

Глобальная переменная может быть определена только один раз в пределах своей области видимости. В другом исходном файле может быть объявлена другая глобальная переменная с таким же именем и с классом памяти static, конфликта при этом не возникает, так как каждая из этих переменных будет видимой только в своем исходном файле.

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

Классы памяти функций. Функции всегда определяются глобально. Они могут быть объявлены с классом памяти static или extern. Объявления функций на локальном и глобальном уровнях имеют одинаковый смысл. Правила определения области видимости для функций отличаются от правил видимости для переменных и состоят в следующем.

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

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

3. Если в объявлении функции отсутствует спецификатор класса памяти, то по умолчанию принимается класс extern.

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

Время жизни переменной (глобальной или локальной) определяется по следующим правилам.

1. Переменная, объявленная глобально (т.е. вне всех блоков), существует на протяжении всего времени выполнения программы.

2. Локальные переменные (т.е. объявленные внутри блока) с классом памяти register или auto, имеют время жизни только на период выполнения того блока, в котором они объявлены. Если локальная переменная объявлена с классом памяти static или extern, то она имеет время жизни на период выполнения всей программы.

Видимость переменных и функций в программе определяется следующими правилами.

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

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

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

4. Функции с классом памяти static видимы только в исходном файле, в котором они определены. Всякие другие функции видимы во всей программе.

Метки в функциях видимы на протяжении всей функции.

Имена формальных параметров, объявленные в списке параметров прототипа функции, видимы только от точки объявления параметра до конца объявления функции.

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

1. Объявления, содержащие спецификатор класса памяти extern, не могут содержать инициаторов.

2. Глобальные переменные всегда инициализируются, и если это не сделано явно, то они инициализируются нулевым значением.

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

4. Инициализация переменных с классом памяти auto или register выполняется всякий раз при входе в блок, в котором они объявлены. Если инициализация переменных в объявлении отсутствует, то их начальное значение не определено.

5. Начальными значениями для глобальных переменных и для переменных с классом памяти static должны быть константные выражения. Адреса таких переменных являются константами и эти константы можно использовать для инициализации объявленных глобально указателей. Адреса переменных с классом памяти auto или register не являются константами и их нельзя использовать в инициаторах.

Пример:

int global_var;

int func(void)

{

int local_var; /* по умолчанию auto */

static int *local_ptr=&local_var; /* так неправильно */

static int *global_ptr=&global_var; /* а так правильно */

register int *reg_ptr=&local_var; /* и так правильно */

}

В приведенном примере глобальная переменная global_var имеет глобальное время жизни и постоянный адрес в памяти, и этот адрес можно использовать для инициализации статического указателя global_ptr. Локальная переменная local_var, имеющая класс памяти auto размещается в памяти только на время работы функции func, адрес этой переменной не является константой и не может быть использован для инициализации статической переменной local_ptr. Для инициализации локальной регистровой переменной reg_ptr можно использовать неконстантные выражения, и, в частности, адрес переменной local_ptr.

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

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

Примером может служить директива #include

Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы:

#include "имя файла"

#include

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

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

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

язык операция переменная программа

1.9.4 Макроподстановки

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

Директива #define имеет две синтаксические формы:

#define идентификатор текст

#define идентификатор (список параметров) текст

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

Пример:

#define WIDTH 80

#define LENGTH (WIDTH+10)

Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.

Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.

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

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

Пример:

#define MAX(x,y) ((x)>(y))?(x):(y)

Эта директива заменит фрагмент

t=MAX(i,s[i]);

на фрагмент

t=((i)>(s[i])?(i):(s[i]);

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

Например, при наличии скобок фрагмент

t=MAX(i&j,s[i]||j);

будет заменен на фрагмент

t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);

а при отсутствии скобок - на фрагмент

t=(i&j>s[i]||j)?i&j:s[i]||j;

Условное выражение вычисляется в другом порядке.

Директива #undef используется для отмены действия директивы #define. Синтаксис этой директивы следующий:

#undef идентификатор

Директива отменяет действие текущего определения #define для указанного идентификатора. Не является ошибкой использование директивы #undef для идентификатора, который не был определен директивой #define.

Пример:

#undef WIDTH

#undef MAX

Эти директивы отменяют определение именованной константы WIDTH и макроопределения MAX.

Раздел II. Объектно-ориентированное программирование и классы C++

С введением классов в С++ появляются объектно-ориентированные программные конструкции.

2.1 Основы объектно-ориентированного программирования

Мы живем в мире объектов. Каждый объект имеет определенные атрибуты и набор действий, которые могут им (или над ними) производиться. Объекты могут существенно отличаться друг от друга, и вы можете ввести некоторую классификацию объектов по «классам. Например, апельсин можно причислить к классу фруктов, а наручные часы CASIO Data Bank - к классу наручных часов. Объектно - ориентированное программирование (ООП) использует при разработке программ понятия объектов.

На основе отдельно взятого класса вы можете построить целую иерархию классов. Например, класс часов CASIO Data Bank может являться частью иерархии классов, включающий в себя классы Swatch и Rolex. Кроме классов, основными понятиями ООП являются: объекты, методы, наследование и полиформизм. Термином класс определяется тип объектов. Каждый отдельный объект класса является представителем данного класса.

Классы. Объекты. Можно сказать, что объектам класса присущи общие свойства и функциональное назначение. Однако каждый объект всегда имеет свое, уникальное состояние, определяемое текущими значениями его свойств (атрибутов). Функциональное назначение класса определяется возможными действиями над представителями класса. В С++ свойства класса определяются понятием элементы данных, а действия - понятием функции - элементы. Говорят, что классы инкапсилируют элементы - данные и элементы - функции.

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

Сообщения и методы. В объектно-ориентированном программировании взаимодействие между объектами строится на основе событий, когда запросы посылаются от одного объекта к другому или происходит обмен запросами между объектами. Объект, получивший запрос, отвечает на него посредством соответствующего метода, а именно, соответствующей функции-элемента. В С++ не определяются строго понятия запроса и метода, как это делается в других языках ООП, таких, например, как SmallTalk. Тем не менее, некоторые находят удобным называть вызов (активацию) функции-элемента термином «запрос». Что касается терминов метод и функция- элемент - они являются синонимами. Запрос - это некоторое действие, совершаемое над объектом. А метод - это способ ответа объекта на пришедший запрос. Функция-элемент - это обычная функция, объявление которой внесено в определение класса.

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

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

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

Попробуем применить концепцию наследования к классу часов CASIO Data Bank. Предположим, что производитель часов задумал создать новую модель CASIO Data Comm, которая обладает теми же качествами, что и CASIO Data Bank, но еще имеет будильник. Вместо того, чтобы создавать новую модель заново (на языке ООП-новый класс), инженеры фирмы CASIO берут за основу модель CASIO Data Bank. В результате у новой модели могут появиться новые свойства и функции, могут быть пересмотрены старые функции. Таким образом, модель CASIO Data Comm наследует функции и свойства модели CASIO Data Bank. Можно сказать, что класс CASIO Data Comm является потомком CASIO Data Bank, или производным классом.

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

2.2 Базовые классы

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

Базовый класс определяется следующим образом:

class className

private:

<закрытые элементы-данные

закрытые конструкторы

закрытые функции-элементы

protected:

защищенные элементы-данные

защищенные конструкторы

защищенные функции-элементы

public:

открытые элементы-данные

открытые конструкторы

открытый деструктор

открытые функции-элементы

Пример:

class point

protected

double x;

double y;

public:

point (double xVal, double yVal);

double getX ( );

double getY ( );

void assign (double xVal, double yVal) ;

point& assign (point &pt);

Разделы класса. Классы С++ имеют три различных уровня доступа к своим элементам - как к данным, так и к функциям:

Закрытые (частные) элементы;

Защищенные элементы;

Открытые элементы.

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

Существуют следующие правила для разделения класса.

Разделы могут появиться в любом порядке.

Один и тот же раздел можно определять несколько раз.

Если не определен ни один раздел, компилятор (по умолчанию) объявляет все элементы закрытыми.

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

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

Класс может иметь несколько конструкторов.

Класс может иметь только один деструктор, который должен объявляться в открытом разделе класса.

Функции-элементы (в том числе конструкторы и деструкторы), состоящие из- нескольких операторов, должны определяться вне объявления класса. Определение функции может содержаться в том же файле, в котором определяется класс. Это напоминает порядок работы с обычными функциями: задание прототипа и определение функции.

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

Пример:

class point

proteсted:

double x;

double y;

public:

point (double xVal, double yVal);

double getX( ) ;

/ / другие функции-элементы

Определения конструктора и функций - элементов должны выглядеть так

point : : point (double xVal, double yVal)

/ / операторы

double point : : getX ( )

/ / операторы

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

В листинге приведен исходный текст программы RECT. CPP. Программа предлагает вам ввести длину и ширину прямоугольника (в данном примере прямоугольник является объектом). Затем программа выводит значения длины, ширины и площади определенного вами прямоугольника. Конструкторы. Конструкторы и деструкторы в C++ вызываются автоматически, что гарантирует правильное создание и разрушение объектов класса. Общий вид объявления конструктора:

class className

{

public:

className () ; //конструктор по умолчанию

className(const className &c); // конструктор копии

className (<список параметров;); //остальные конструкторы

};

Конструктор копии создает объект класса, копируя при этом данные из существующего объекта класса.

В C++ имеются следующие особенности и правила работы с конструкторами:

Имя конструктора класса должно совпадать с именем класса.

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

Класс может иметь несколько конструкторов или не иметь их совсем.

Конструктором по умолчанию является конструктор, не имеющий параметров, или конструктор, у которого все параметры имеют значения по умолчанию.

Примеры:

// класс с конструктором без параметров

class point1

{

protected:

double x;

double у;

public:

point1 () ;

// другие функции-элементы ;

};

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

class point2

{

protected:

double x;

double у;

public:

point2(double xVal = 0, double yVal = 0) ;

// другие функции-элементы

};

5. Конструктор копии создает объект класса на основе существующего объекта. Например:

class point

{

protected:

double x;

double у;

public:

point () ;

point(double xVal = 0, double yVal = 0) ;


Подобные документы

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

    контрольная работа [31,2 K], добавлен 12.12.2009

  • Элементы языка Object Pascal: идентификаторы, константы, переменные, выражения. Структура проекта Delphi. Операторы и метки. Типы данных языка OPascal. Статические и динамические массивы. Записи с вариантными полями. Совместимость и преобразование типов.

    курс лекций [385,4 K], добавлен 18.02.2012

  • Понятие и общая характеристика языка программирования РНР, принципы и этапы его работы, синтаксис и ассоциируемые массивы. Обработка исключений в языке Java. Работа с базами данных с помощью JDBC. Изучение порядка разработки графического интерфейса.

    презентация [192,3 K], добавлен 13.06.2014

  • Лингвистическая концепция языка Паскаль. Интегрированная инструментальная оболочка. Основы построения программ на ТП 7.0. Алфавит языка и специфика использования символов. Простые типы данных: константы и переменные. Циклические конструкции и операции.

    курсовая работа [284,6 K], добавлен 02.07.2011

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

    методичка [400,2 K], добавлен 06.07.2009

  • История создания и применение языка Basic. Стандартные математические и строковые функции. Операции и выражения языка. Блоки данных и подпрограммы. Операторы управления, цикла, ввода-вывода и преобразования информации. Константы, переменные, массивы.

    контрольная работа [2,3 M], добавлен 04.05.2015

  • Элементы и переменные, используемые для составления записи в Паскале. Основные числовые типы языка Turbo Pascal. Составление блок-схемы приложения, программирование по ней программы для вычисления функции. Последовательность выполнения алгоритма.

    лабораторная работа [256,9 K], добавлен 10.11.2015

  • Понятие алгоритма. Цикл программы. Структурная схема алгоритма. Элементы языка Тurbo Рascal. Алфавит. Идентификаторы. Комментарии. Лексика языка С++. ESC-последовательности. Операции. Ключевые слова. Комментарии.

    контрольная работа [43,0 K], добавлен 24.04.2006

  • Основы языка программирвоания C++. Элементы управления в Microsoft Visual C++. Алгоритмические конструкции языка программирования Visual C++ и базовые элементы управления. Глобальные константы и переменные. Управление программой с помощью клавиатуры.

    курсовая работа [1,7 M], добавлен 08.04.2015

  • Метод половинного деления и метод касательных. Переменные, константы, объявление типов данных. Объект WorkBook: его свойства, методы и события. Методы нахождения корней уравнений. Структурированные типы данных. Терминальные свойства объекта Workbook.

    курсовая работа [1,1 M], добавлен 14.07.2012

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.