Объявления объектов и типов. Синоним имени типа. 


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



ЗНАЕТЕ ЛИ ВЫ?

Объявления объектов и типов. Синоним имени типа.



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

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

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

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

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

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

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

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

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

Например, при использовании конструкции

int *(*array[10]) ();

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

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

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

int - целое

double - плавающее с двойной точностью

int * - указатель на целое

int * [10] - массив указателей на целое

int(*)[10] - указатель на массив на 10 целых

int * (void) - функция без аргументов, возвращающая указатель на целое.

int (*) (void) - указатель на функцию без аргументов, возвращающую результат целого типа.

Синоним имени типа строится при помощи ключевого слова 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);

 

 

13. Правила преобразования стандартных типов. Неявные преобразования стандартных базовых типов. Преобразования производных стандартных типов.

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

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

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

Явные преобразования

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

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

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

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

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

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

Примеры:

double d = (double)5;

int i = int(d);

int *ip = &i;

float fp = (float*) ip;

typedef float* FP;

fp = FP(ip);

Неявные преобразования стандартных базовых типов

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

int i='A'; // i = 65;

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

int j=-1;

long l=j;

long m=32768; // Двоичное представление числа 32768

// содержит единственную единицу в 15 разряде.

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

unsigned u=m; // u = 32768

double d=0.999999;

long n=d; // n = 0

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

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

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

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

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

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

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

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

Пример 1.

int g = 10, t = 5, 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; }

Преобразование производных стандартных типов

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

int *ip;

void *vp=ip;

ip=vp; // Ошибка!

ip=(int*)vp; // Теперь верно.

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

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

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

 

Функции. Передача аргументов. Указатели на функции.

Определение и вызов функции

Программа С++ состоит из одной или нескольких функций. Функции разбивают большие задачи на маленькие подзадачи.

Имя одной из функций - 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<< "\n";

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

cout<<" max= "<< c<< "\n";}

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

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

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<< str<< "\n"; }

Заметим, что библиотечная функция strcpy имеет другое описание и ее заголовок имеет вид:

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

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

 

Передача аргументов

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

Рассмотрим, например, вариант функции, возводящей целое 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 < size; i++) res+ = array[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);

Указатели на функции

Указатель на функцию определим следующим образом:

Тип_функции (*имя_указателя) (список параметров);

Например, int (*fptr) (double);

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

Пример:

void f1(void){

cout<<"\n Выполняется f1().";

}

void f2(void){

cout<<"\n Выполняется f2().";

}

void main(){

void (*ptr)(void);

ptr = f2;

(*ptr)(); //вызов функции f2();

ptr = f1;

(*ptr)(); //вызов f1();

ptr(); //альтернативная запись!

}

Результат: Выполняется f2().

Выполняется f1().

Выполняется f1().

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

Проиллюстрируем это при решении следующей задачи.

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

// Файл TRAP.CPP

double trap(double a, double b, int n, double (*func)(double)){

double x, h = (b-a)/n, i = ((*func) (a) + (*func) (b))/2;

for (x = a; n >1; n - -) i + = (*func)((x+=h);

return h*i; }

//Файл INTEGRAL.CPP

#include

#include

#include

#include "trap.cpp"

double f1(double x){

return x*x+sin(3+x);}

double f2(double x){

return x/(x*x+1)+exp(-2*x*x);}

void main(){

double a=1, b=3;

double i = trap(a, b, 50, f1);

cout << "интеграл от первой функции = "<< i<<'\n';

i = trap(a, b, 50, f2);

cout << "интеграл от второй функции = "<< i<<'\n';

}

Отметим, что в теле функции trap можно также писать обращения func(a), func(b) и т.д.

 

 



Поделиться:


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

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