Объединения, встраиваемые функции 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Объединения, встраиваемые функции



 

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

Главное же в том, что в объединении в С++ все данные - члены находятся в одной области памяти. Объединения могут содержать конструкторы и деструкторы. Способность объединений связывать воедино программу и данные позволяет создавать типы данных, в которых все данные используют общую память. Это именно то, чего нельзя сделать, используя классы. Имеется несколько ограничений, накладываемых на использование объединений применительно к С++. Во-первых, они не могут наследовать какой бы то ни было класс, и они не могут использоваться в качестве базового класса для любого другого типа. Объединения не должны содержать объектов с конструктором и деструктором. Рассмотрим пример декларирования объединения в С++:

 

# include < iostream. h >

union bits

{

bits(double n);

void show_bits();

double d;

}

 

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

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

 

# include <iostream.h>

inline int even(int x)

{

return! (x%2);

}

main()

{

int n;

cin >>n;

if (even (n)) cout << ‘’ число является четным \ n ’’;

else cout << ‘’ число является нечетным \ n ’’;

return 0;

}

В этом примере функция even (), которая возвращает истину при четном аргументе, объявлена как встраиваемая функция. Это означает, что строка

 

if (even(n)) cout << ‘’ число является четным \n’’:

 

функционально идентична строке:

 

if (! (10%2)) cout << ‘’ число является четным \ n ’’;

Этот пример указывает также на другую важную особенность использования встраиваемой функции: она должна быть задана до ее первого вызова. Отметим, что спецификатор inline является запросом, а не командой для компилятора. Очень важно подчеркнуть и то, что некоторые компиляторы не воспринимают функцию как встраиваемую, если она содержит статическую переменную (static), оператор цикла, оператор switch или go to или, если функция является рекурсивной. Если определение функции - члена достаточно короткое, это определение можно включить в объявление класса. Поступив таким образом, мы заставляем, если это возможно, функцию стать встраиваемой. При этом ключевое слово inline  не используется. Рассмотрим сказанное на примере:

 

 

# include <iostream.h>

class samp

 {

int i, j;

public:

samp(int a, int b);

int divisible () { return! (i % j);} // определение встраиваемой функции

}

samp:: samp (int a, int b)

{

i = a;

j = b;

}

main

{

samp ob1 (10,2), ob2 (10,3);

if (ob1.devisible()) cout << ‘’ Число делится нацело \n’’;

 else cout << ‘’ Число не делится нацело \n’’;

if (ob2.devisible()) cout << ‘’ Число делится нацело \n’’;

 else cout << ‘’ Число не делится нацело \n’’;

 return 0;

}

Указатели и адреса

 

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

Унарный оператор & выдает адрес объекта, так что инструкция    

 

                                          p =& c;

присваивает адрес ячейки с  переменной p. Говорят, что р указывает на с.

Унарный оператор * есть оператор раскрытия ссылки. Примененный к указателю он выдает объект, на который данный указатель ссылается. Предположим, что х и у - целые, а ip – указатель на int. Рассмотрим пример.

 

int x =1, y =2, z [10];

int * ip;                               //  ip – указатель на int

ip =& x;                               //  теперь ip указывает на х

y =* ip;                               //   y теперь равен 1

* ip =0;                               //   x теперь равен 0

ip =& z [0];                          //  ip теперь указывает на z [0]

 

Функции в языке Си в качестве своих аргументов получают значения параметров. Поэтому прямой возможности, находясь в вызванной функции, изменить переменную вызывающей функции нет. Например, в программе сортировки может понадобится функция swap, переставляющая местами два неупорядоченных элемента. Для их перестановки недостаточно написать swap (a, b); где функция swap определена следующим образом:

 

void swap(int x, int y) // НЕВЕРНО

{

int temp; temp=x;

x=y; y=temp;

}

Поскольку swap получает лишь копии значений переменных a и b, она не может повлиять на переменные a, b той программы, которая к ней обратилась. Чтобы получить желаемый эффект, надо вызывающей программе передать указатели на те значения, которые должны быть изменены: swap (& a,& b); Так как оператор & получает адрес переменной, & a – есть указатель на а. В самой функции swap параметры должны быть описаны как указатели, при этом доступ к значениям параметров будет осуществляться через них косвенно.

 

void swap(int *px, int *py)

{

int temp;

temp=*px;

*px=*py;

* py = temp;

}

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

                                     double * dp, atof (char *);

означает, что выражение * dp и atof (s) имеют тип double, а аргумент функции atof есть указатель на char. Полезно знать, что указателю разрешено ссылаться только на объекты заданного типа. Существует одно исключение, указатель на void может ссылаться на объекты любого типа, но к такому указателю нельзя применять оператор раскрытия ссылки. Если ip ссылается на х целого типа, то * ip можно использовать в любом месте, где допустимо применение х. Например, оператор:

                                          * ip = * ip + 10;

увеличивает * ip на 10. Унарные операторы * и & имеют более высокий приоритет, чем арифметические операторы. Указатели сами являются переменными. В тексте программы они могут встречаться и без оператора раскрытия ссылки. Например, если iq есть указатель на int, то можно использовать оператор:

                                        i p = iq;

который копирует содержимое iq в ip, чтобы iq и ip ссылались на один и тот же объект.

 Рассмотрим связь указателей и массивов. Декларация:

                                       int a [10];

определяет массив а размера 10, то есть блок из 10 последовательных объектов с именами а[0], a [1],… a [9]. Запись a [ i ] отсылает к i –му элементу массива. Если pa есть указатель на int, то есть определен как:

                                          int * pa;

то в результате присваивания

                                    pa = & a [0];

pa будет указывать на нулевой элемент а, иначе говоря, p а будет содержать адрес элемента а[0]. Между индексированием и арифметикой с указателями существует очень тесная связь. По определению значение переменной или выражения типа массив есть адрес нулевого элемента массива. После присваивания:

                                ра = & a [0];

р a и a имеют одно и то же значение. Поскольку имя массива есть не что иное, как адрес его начального элемента, присваивание    pa =& a [0 ]; можно также записать в следующем виде: pa = a;     Интересно знать, что a [ i ] можно записать как *(a + i).

 

 



Поделиться:


Последнее изменение этой страницы: 2021-12-15; просмотров: 29; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.15.229.113 (0.015 с.)