Пятница, 17.05.2024, 20:09
Приветствую Вас, Гость

БИЛЕТ 26.

1) Передача параметров в С++

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

При вызове f() в выражении val++ увеличивается локальная копия первого фактического параметра, тогда как в ref++ - сам второй фактический параметр увеличивается сам. Поэтому в функции

увеличится значение j, но не i. Первый параметр i передается по значению, а второй параметр j передается по ссылке. В $$2.3.10 мы говорили, что функции, которые изменяют свой передаваемый по ссылке параметр, труднее понять, и что поэтому лучше их избегать (см. также $$10.2.2). Но большие объекты, очевидно, гораздо эффективнее передавать по ссылке, чем по значению. Правда можно описать параметр со спецификацией const, чтобы гарантировать, что передача по ссылке используется только для эффективности, и вызываемая функция не может изменить значение объекта:

           void f(const large& arg)

           {// значение "arg" нельзя изменить без явных

             // операций преобразования типа

           }

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

           void g(large& arg); // считается, что в g() arg будет меняться

Отсюда мораль: используйте const всюду, где возможно.

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

           extern int strlen(const char*);  // из <string.h>

           extern char* strcpy(char* to, const char* from);

           extern int strcmp(const char*, const char*);

Значение такого приема растет вместе с ростом программы.

Отметим, что семантика передачи параметров отличается от семантики присваивания. Это различие существенно для параметров, являющихся const или ссылкой, а также для параметров с типом, определенным пользователем ($1.4.2).

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

           float fsqrt(const float&);  // функция sqrt в стиле Фортрана

           void g(double d)

           { float r;

             r = fsqrt(2.0f);  // передача ссылки на временную

                               // переменную, содержащую 2.0f

             r = fsqrt(r);     // передача ссылки на r

             r = fsqrt(d);     // передача ссылки на временную

                               // переменную, содержащую float(d)

           }

Запрет на преобразования типа для параметров-ссылок без спецификации const введен для того, чтобы избежать нелепых ошибок, связанных с использованием при передаче параметров временных переменных:

           void update(float& i);

           void g(double d)

           {float r;

             update(2.0f);    // ошибка: параметр-константа

             update(r);       // нормально: передается ссылка на r

             update(d);       // ошибка: здесь нужно преобразовывать тип

           }

 

2) Макроопределения и макроподстановки

Определение макроподстановки имеет вид:

#define имя замещающий-текст

Макроподстановка используется для простейшей замены: во всех местах, где встречается лексема имя, вместо нее будет помещен замещающий-текст. Имена в #define задаются по тем же правилам, что и имена обычных переменных. Замещающий текст может быть произвольным. Обычно замещающий текст завершает строку, в которой расположено слово #define, но в длинных определениях его можно продолжить на следующих строках, поставив в конце каждой продолжаемой строки обратную наклонную черту \. Область видимости имени, определенного в #define, простирается от данного определения до конца файла. В определении макроподстановки могут фигурировать более ранние #define-определения. Подстановка осуществляется только для тех имен, которые расположены вне текстов, заключенных в кавычки. Например, если YES определено с помощью #define, то никакой подстановки в printf("YES") или в YESMAN выполнено не будет.

Любое имя можно определить с произвольным замещающим текстом. Например:

#define forever for( ; ; ) /* бесконечный цикл */

определяет новое слово forever для бесконечного цикла.

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

#define max(A, B) ((A) > (B) ? (A) : (B))

Хотя обращения к max выглядят как обычные обращения к функции, они будут вызывать только текстовую замену. Каждый формальный параметр (в данном случае A и B) будет заменяться соответствующим ему аргументом. Так, строка

x = max(p+q, r+s);

будет заменена на строку

x = ((p+q) > (r+s) ? (p+q) : (r+s));

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

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

max(i++, j++) /* НЕВЕРНО */

вызовет увеличение i и j дважды. Кроме того, следует позаботиться о скобках, чтобы обеспечить нужный порядок вычислений. Задумайтесь, что случится, если при определении

#define square(x) x*x /* НЕВЕРНО */

вызвать square (z+1).

Тем не менее макросредства имеют свои достоинства. Практическим примером их использования является частое применение getchar и putchar из <stdio.h>, реализованных с помощью макросов, чтобы из6ежать расходов времени от вызова функции на каждый обрабатываемый символ. Функции в <ctype.h> обычно также реализуются с помощью макросов. Действие #define можно отменить с помощью #undef:

#undef getchar

int getchar(void) {...}

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

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

#define dprint(expr) printf(#expr " = %g\n", expr)

Обращение к

dprint(x/y);

развернется в

printf("x/y" " = %g\n", x/y);

а в результате конкатенации двух соседних строк получим

printf("x/y=%g\n", x/y);

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

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

#define paste(front, back) front ## back

так что paste(name, 1) сгенерирует имя name1.

Правила вложенных использований оператора ## не определены.

Макро - определения и расширения

Управляющая строка вида

#define идентификатор последовательность-лексем

заставляет препроцессор заменять идентификатор на последовательность-лексем; символы-разделители в начале и в конце последовательности лексем выбрасываются. Повторная строка #define с тем же идентификатором считается ошибкой, если последовательности лексем неидентичны (несовпадения в символах- разделителях при сравнении во внимание не принимаются). Строка вида

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

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

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

предписывает препроцессору "забыть" определение, данное идентификатору. Применение #undef к неизвестному идентификатору ошибкой не считается.

Если макроопределение было задано вторым способом, то текстовая последовательность, состоящая из его идентификатора, возможно, со следующими за ним символами-разделителями, знака (, списка лексем, разделенных запятыми, и знака ), представляет собой вызов макроса. Аргументами вызова макроса являются лексемы, разделенные запятыми (запятые, "закрытые" кавычками или вложенными скобками, в разделении аргументов не участвуют). Аргументы при их выделении макрорасширениям не подвергаются. Количество аргументов в вызове макроса должно соответствовать количеству параметров макроопределения. После выделения аргументов окружающие их символы-разделители выбрасываются. Затем в замещающей последовательности лексем макроса идентификаторы-параметры (если они не закавычены) заменяются на соответствующие им аргументы. Если в замещающей последовательности перед параметром не стоит знак # и ни перед ним, ни после него нет знака ##, то лексемы аргумента проверяются: не содержат ли они в себе макровызова, и если содержат, то прежде чем аргумент будет подставлен, производится соответствующее ему макрорасширение.

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

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

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

Если полученное расширение начинается со знака #, оно не будет воспринято как директива препроцессора.

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

Описанные возможности можно использовать для показа смысловой сущности констант, как, например, в

#define TABSIZE 100

int table[TABSIZE];

Определение

#define ABSDIFF(a,b) ((a)>(b) ? (a)-(b) : (b)-(a))

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

Если имеется определение

#define tempfile(dir) #dir "/%s"

то макровызов tempfile(/usr/tmp) даст в результате

"/usr/tmp" "/%s"

Далее эти две строки превратятся в одну строку. По макросу

#define cat(x,y) x ## y

вызов cat(var, 123) сгенерирует var123. Однако cat (cat (1,2),3) не даст желаемого, так как оператор ## воспрепятствует получению правильных аргументов для внешнего вызова cat. В результате будет выдана следующая цепочка лексем:

cat ( 1 , 2 )3

где )3 (результат "склеивания" последней лексемы первого аргумента с первой лексемой второго аргумента) не является правильной лексемой. Если второй уровень макроопределения задан в виде

#define xcat(x,y) cat(x,y)

то никаких коллизий здесь не возникает;

xcat(хсat(1, 2), 3)

в итоге даст 123, поскольку сам xcat не использует оператора ##.

Аналогично сработает и ABSDIFF(ABSDIFF(a, b), c), и мы получим правильный результат.


*************************************26 - 3

Найти все  делители заданного натурального числа n.


# include <stdio.h>

void main()

{

  const N=15120;

  long int n=N;

  int mas[10];

  int kol=0;

  for (int i=2;i<=n;)

    {

      if ((n%i)==0)

            {

              mas[kol]=i;

              kol++;

              while ((n%i)==0) n/=i;

            }

      else i++;

    }

}


*************************************26 - 4

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


#include<stdio.h>

 #include<conio.h>

 int a[]={5,4,3,2,0,4,5,6,8,7,2,1};

 int k,l,i;

 int n=sizeof(a)/sizeof(int);

 void main()

 {

 clrscr();