WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!


Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 17 |

Область видимости является подобластью области существования имени. Если элемент языка, чьё имя находится в области своего существования, тем не менее, недоступен по этому имени, то будем говорить, что это имя скрыто или замаскировано.

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

Переменные из объемлющих блоков, как и глобальные, видимы во внутренних блоках.

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

int i=3;

{ int c = i; // с становится = =3;

… int i = 0; // имя i маскирует внешнее имя i;

cout <<”c = ”<< c <<”, i= ”<< i <<”.\n”;

} // конец области существования имен i, c из блока;

// опять видно имя i, объявленное перед блоком.

cout <<” i = ”<< i <<”.\n”;

… Здесь будет выведено c=3, i=0.

i=3.

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

int i=5; // Глобальная переменная;

void main (){ int i=1; // локальная переменная.

i++;

::i++;

cout<<”i=”<

С помощью операции :: нельзя обратиться к скрытому локальному объекту.

11. Классы памяти В С++ существуют 3 класса памяти, или хранения.

1) Статическая память – статические данные, размещаемые в сегменте данных;

2) Автоматические данные, размещаемые в специальном стеке (сегмент стека) или как частный случай, в регистрах процессора;

3) Динамические данные, явно размещаемые в динамической памяти с помощью операций new и delete.

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

int i=3, j; // глобальные переменные. Класс памяти – static;

void main(){ int a; // Автоматическая переменная;

static float b[1000], c=2.3; // статические переменные;

… } int f(){int d; // автоматическая переменная;

static int m=2, k; // статические переменные m, k.

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

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

void f();

void main(){ for (int i = 3; i > 0; i– –) f();

} void f(){static int i; int j = 0;

cout<<”i =”<

i = 0 j = i = 1 j = i = 2 j = Заметим, что если служебное слово static применено к глобальной переменной или к имени функции, то оно имеет другой смысл. В этом случае и глобальная переменная, и функция становятся видимы только в пределах файла, где они определены и невидимы из других файлов.

12. Объявления объектов и типов При объявлениях можно использовать одновременно более одного модификатора (это * [ ] и ( ) ). Это даёт возможность создавать бесконечное множество сложных описателей типов. В то же время, некоторые комбинации недопустимы:

элементами массива не могут быть функции;

функции не могут возвращать массив или функцию.

При интерпретации сложных описателей квадратные и круглые скобки (справа от идентификатора) имеют приоритет перед * (слева от идентификатора). Квадратные или круглые скобки имеют один и тот же приоритет.

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

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

Если они есть, то проинтерпретировать эту часть описателя и затем посмотреть налево в поиске *.

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

Интерпретировать спецификатор типа.

Например, при использовании конструкции int *(*array[10]) ( );

объявляется имя array как массив из 10 указателей на функцию без аргументов, возвращающую указатель на целое значение.

13. Имена типов В некоторых конструкциях языка явно используются имена типов (операции sizeof, new, cast). Имя типа – это просто объявление объекта такого типа, в котором опущено имя самого объекта.

Примеры имён типов:

int – целое, double – плавающее с двойной точностью, int * – указатель на целое, int * [10] – массив указателей на целое, int (*)[10] – указатель на массив на 10 целых, int * (void) – функция без аргументов, возвращающая указатель на целое, int (*) (void) – указатель на функцию без аргументов, возвращающую результат целого типа, int (*(void))[ ] – функция без аргументов, возвращающая указатель на массив целых.

14. Синоним имени типа Синоним имени типа строится при помощи ключевого слова typedef. Выражение, в котором присутствует это ключевое слово, является описанием некоторого имени. Наличие слова typedef говорит о том, что объявляемый идентификатор становится не именем объекта некоторого типа, а синонимом имени этого типа.



int INTEGER; // INTEGER – имя переменной типа int typedef int INT; // INT – синоним типа int typedef unsigned size_t;

typedef char string [255];

typedef void (*FPTR) (int);

Последние две строки определяют string как синоним типа «строка из 255 символов», а FPTR – синоним типа «указатель на функцию, имеющую один аргумент типа int и не возвращающую никакого результата».

После объявления с помощью typedef новое имя становится полноценным именем типа:

string array; // array – массив из 255 символов FPTR func_pointer; // void (*func_pointer)(int);

typedef string STRING;

Использование typedef может упростить понимание сложных имен типов. Так, тип int (*(void))[ ] можно построить так:

typedef int intarray[]; // тип «массив целых» typedef intarray * ptrtointarray; //тип указателя на массив целых typedef ptrtointarray TYPE (void);

Теперь имя TYPE можно использовать, например, в прототипах:

double fun (int, int, TYPE);

15. Правила преобразования стандартных типов В любых случаях выполняются два преобразования:

имя массива преобразуется к указателю на его первый элемент;

имя функции преобразуется к указателю на эту функцию.

15.1. Явные преобразования Разрешены любые преобразования стандартных типов одного к другому. При преобразовании более длинного типа к более короткому происходит потеря разрядов; при преобразовании более короткого целочисленного типа к более длинному свободные разряды заполняются (если короткий тип – беззнаковый), или происходит размножение знакового разряда (для типа со знаком).

Разрешены любые преобразования друг на друга указателей, а также ссылок. Явное преобразование типов делается посредством операции приведения типов (cast), которая имеет две формы:

(имя_типа) операнд // Традиционная форма;

или имя_типа (операнд) // функциональная форма.

Здесь имя_типа задаёт тип, а операнд является величиной, которая должна быть преобразована к заданному типу.

Отметим, что во второй форме имя_типа должно быть простым идентификатором, например, полученным с помощью typedef.

Примеры:

double d = (double)5;

int i = int(d);

int *ip = &i;

float *fp = (float*) ip;

typedef float* FP;

fp = FP(ip);

15.2. Неявные преобразования стандартных базовых типов Для стандартных базовых типов компилятор может выполнять любые преобразования одного типа к другому:

int i=’A’; // i = 65;

char c=256; // Теряются 8 старших битов; с станет равно ’\0’;

int j=-1;

long l=j;

long m=32768; // Двоичное представление числа // содержит единственную единицу в 15 разряде.

short int k=m; // k = -32768, так как 15-й разряд для short – знаковый.

unsigned u=m; // u = double d=0.999999;

long n=d; // n = При выполнении арифметических операций также происходит неявное преобразование типов. Правила здесь такие:

а) типы char, short, enum преобразуются к типу int, а unsigned short – к unsigned int; тип float преобразуется к double;

б) затем, если один из операндов имеет тип long double, то и второй преобразуется к long double;

в) иначе, если один из операндов имеет тип double, то и второй преобразуется к double;

г) иначе, если один из операндов имеет тип unsigned long, то и второй преобразуется к unsigned long;

д) иначе, если один из операндов имеет тип unsigned, то и второй преобразуется к unsigned;

е) иначе, если один из операндов имеет тип long, то и второй преобразуется к long;

ж) иначе оба операнда имеют тип int.

Пример 1.

int g = 10, t = 5;

double t2=t*t/2;

double s = g*t2; // s станет равно 120;

double s0 = g*t*t/2.0; // s0 станет равно 125.

Пример 2.

Функция atoi (упрощенная), которая ставит в соответствие строке цифр её числовой эквивалент:

int atoi ( char s[ ] ){ int i, n = 0;

for (i = 0; s[i] >= ’0’&& s[i] <= ’9’; ++i) n = 10*n + s[i] – ’0’; // Преобразование char в int.

return n;

} 15.3. Преобразование производных стандартных типов Для указателей разрешено неявное преобразование указателя на любой тип к указателю на тип void. Все другие преобразования должны быть явными.

int *ip;

void *vp=ip;

ip=vp; // Ошибка! ip=(int*)vp; // Теперь верно.

float *fp=ip; // Ошибка.

float *fp=(float*)ip; // Верно.

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

16. Функции 16.1. Определение и вызов функции Программа С++ состоит из одной или нескольких функций.

Функции разбивают большие задачи на маленькие подзадачи.

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

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

С использованием функции связаны 3 понятия – определение функции, объявление функции и вызов функции.

Определение функции имеет вид тип имя ( список описаний аргументов ){ операторы } Здесь имя – это имя функции;





тип – тип возвращаемого функцией значения;

операторы в фигурных скобках { } часто называют телом функции.

Аргументы в списке описаний называют формальными параметрами.

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

int max(int a, int b){ return(a>=b) a:b; } Это определение говорит о том, что функция с именем max имеет два целых аргумента и возвращает целое значение. Если функция действительно должна возвращать значение какого-либо типа, то в ее теле обязательно должен присутствовать оператор return выражение; при выполнении этого оператора выполнение функции прекращается, управление передается в функцию, вызывающую данную функцию, а значением функции будет значение выражения.

int max(int a, int b) { return (a >=b) a:b; } void main( ) { int i = 2, j = 3;

int c = max( i, j );

cout<<“ max= “<

c = max( i*i, j )*max( 5, i – j );

cout<<“ max= “<

Если у функции нет формальных параметров, то она определяется, например, так:

double f(void){тело функции};

или, эквивалентно, double f( ) {тело функции};

Обращаются в программе к этой функции, например, так:

a = b*f( ) + c;

Функция может и не возвращать никакого значения. В этом случае ее определение таково:

void имя (список описаний аргументов){ операторы } Вызов такой функции имеет вид имя (список фактических аргументов);

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

В качестве примера приведем функцию, копирующую одну строку в другую:

void copy (char* to, char* from){ while(* to ++ = *from ++ );} void main(){ char str1[ ]=“string1”;

char str2[ ]=“string2”;

copy(str2, str1);

cout<

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

Её действие – копирование строки from в строку to и, кроме того, она возвращает указатель на строку to, т.е. в ней есть оператор return to.

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

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

int power (int x, int n){ for (int p = 1; n > 0; n – –) p* = x;

return p;

} Аргумент n используется как временная переменная. Что бы ни происходило с n внутри функции power, это никак не влияет на фактический аргумент, с которым первоначально обратились к этой функции в вызываемой функции:

void main (){ … int n=6, x=3;

x=power(x, n); // n – не меняется.

… } Рассмотрим процесс вызова функции более подробно. При вызове функции:

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

при вызове функции в стек записывается точка возврата – адрес той части программы, где находится вызов функции;

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

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

В качестве примера рассмотрим функцию, меняющую местами свои аргументы:

void swap (int* x, int* y){ int t = *x;

*x = *y;

*y = t;

} Обратиться к этой функции можно так:

int a = 3, b = 7;

swap (&a, &b);

Теперь а =7, и b=3.

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

int summa (int array[ ], int size){ int res=0;

for (int i = 0; i

return res;

} В заголовке int array[ ] можно заменить на int* array, а выражение в теле функции array[i] заменить на *(array+i), или даже на *array ++, так как array не является именем массива, и следовательно, не является константным указателем. К функции summa можно обратиться так:

int mas[100];

for (int i = 0; i < 100; i++) mas[i] = 2*i + 1;

int j = summa (mas, 100);

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 17 |










© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.