Скачать презентацию Тема Адреса и указатели Лекция 30 09 13 Скачать презентацию Тема Адреса и указатели Лекция 30 09 13

Лекция-10-11.ppt

  • Количество слайдов: 51

Тема Адреса и указатели Лекция 30. 09. 13 г. 1 Тема Адреса и указатели Лекция 30. 09. 13 г. 1

Адресация памяти Ø Будем считать, что оперативная память компьютера представляет собой линейную последовательность пронумерованных Адресация памяти Ø Будем считать, что оперативная память компьютера представляет собой линейную последовательность пронумерованных ячеек памяти – байтов и каждая ячейка памяти имеет адрес. Ø Будем также считать, что компьютер имеет 32 разрядную архитектуру и адреса записываются как 32 -битные коды. Ø Наименьший адрес: 0 x 0000, наибольший – 0 x. FFFF. Ø Адресуемый объем памяти – 232 байт = 4 Гбайт 0 x 00000001 0 x 00000002 ……………. . 4 Гбайт 0 x. FFFFFFFE 0 x. FFFF Лекция 30. 09. 13 г. 2

Адреса объектов программы Ø Каждый объект программы занимает определенный участок оперативной памяти компьютера и, Адреса объектов программы Ø Каждый объект программы занимает определенный участок оперативной памяти компьютера и, следовательно, может быть охарактеризован адресом начала этого участка, который и считается адресом данного объекта. Ø В языке С имеется специальная унарная операция & для получения адреса объекта d в оперативной памяти. Ø Функция printf имеет специальный формат %p для вывода адреса. //Ex 031. с – адреса объектов #include #include int x; short y; char a[256], b[512]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(y) = %pn", &y); printf("addr(a) = %pn", &a); printf("addr(b) = %pn", &b); printf("addr(fact) = %pn", &fact); system("PAUSE"); return 0; Лекция 30. 09. 13 г. } 3

Размещение в памяти объектов программы fact 0 x 00401300 ? ? байт a 0 Размещение в памяти объектов программы fact 0 x 00401300 ? ? байт a 0 x 004050 DC y 0 x 004051 DC b 0 x 004051 EC 0 x 004053 EC 0 x 1 DC– 0 x 0 DC=0 x 100=256 16 байт 0 x 1 EC– 0 x 1 DC=0 x 010=16 512 байт x 256 байт 0 x 3 EC– 0 x 1 EC=0 x 200=512 16 байт Лекция 30. 09. 13 г. 4

Замечание Некоторые объекты программы не требуют применения к ним операции взятия адреса &, т. Замечание Некоторые объекты программы не требуют применения к ним операции взятия адреса &, т. к. они сами и являются адресами. К их числу относятся функции и массивы, т. е. значением имени функции и значением имени массива является адрес. Доказательство этого: //Ex 031 -1. с – адреса объектов #include a означает то же, что и &a #include b означает то же, что и &b int x; fact означает то же, что и &fact short y; char a[256], b[512]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(y) = %pn", &y); printf("addr(a) = %pn", a); printf("addr(b) = %pn", b); printf("addr(fact) = %pn", fact); system("PAUSE"); return 0; Лекция 30. 09. 13 г. } 5

О размерах участков памяти, выделяемых объектам Результаты работы программы позволяют предположить, что память объектам О размерах участков памяти, выделяемых объектам Результаты работы программы позволяют предположить, что память объектам программы выделяется блоками, кратными 16 байт. Проведем эксперимент - вместо y определим массив из 8 элементов, а размер массива b увеличим на 1: //Ex 031 -2. с – адреса объектов #include #include int x; short y[8]; char a[256], b[512+1]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(y) = %pn", y); printf("addr(a) = %pn", a); printf("addr(b) = %pn", b); printf("addr(fact) = %pn", fact); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 6

Иллюстрация Было: fact Стало: fact 0 x 00401300 ? ? байт a 0 x Иллюстрация Было: fact Стало: fact 0 x 00401300 ? ? байт a 0 x 004050 DC y 0 x 004051 DC b 0 x 004051 EC ? ? байт 16 байт 0 x 004050 DC y 0 x 004051 DC b 256 байт a 0 x 004051 EC 0 x 004053 EC 16 байт x 0 x 004053 FC 16 байт 8 * 2 байта 528 байт 512 байт x 256 байт 513 * 1 байт 16 байт 0 x 3 FC– 0 x 1 EC=0 x 210=528 Лекция 30. 09. 13 г. 7

Адресная арифметика Можно предположить, что адреса являются обычными числами (? ) и могут участвовать Адресная арифметика Можно предположить, что адреса являются обычными числами (? ) и могут участвовать в арифметических операциях. Проверим это: //Ex 032. с – адресная арифметика #include #include int x; short y[8]; char a[256], b[512+1]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(x)+2 = %pn", &x+2); printf("addr(y) = %pn", y); printf("addr(y)-128 = %pn", y-128); printf("addr(a) = %pn", a); printf("addr(a)+2 = %pn", a+2); printf("addr(b) = %pn", b); printf("addr(b)+528 = %pn", b+528); printf("addr(fact) = %pn", fact); printf("addr(fact)+2 = %pn", fact+2); system("PAUSE"); return 0; Лекция 30. 09. 13 г. } 8

Анализ результатов Ø int x: при увеличении адреса на 2 результат увеличился на 0 Анализ результатов Ø int x: при увеличении адреса на 2 результат увеличился на 0 x 404 -0 x 3 FC=8 , т. е. увеличение в 4 раза больше (а размер переменной типа int как раз и = 4 байтам) Ø short y[8]: при уменьшении адреса на 128 результат уменьшился на 0 x 51 DC-0 x 50 DC=0 x 100=256 , т. е. уменьшение в 2 раза больше (а размер переменной типа short как раз и = 2 байтам) Ø char a[256]: при увеличении адреса на 2 результат увеличился на 0 x. DE-0 x. DC=2 , что и требовалось, потому что размер переменной типа char = 1 байт Ø char b[513]: увеличение адреса на 528 в точности дало адрес переменной x. Ø int fact(int n): при увеличении адреса на 2 результат увеличился также на 2, что позволяет предположить, что «тип адреса» функции такой же, что и тип адреса переменной char. Лекция 30. 09. 13 г. 9

Выводы (правила адресной арифметики) 1. К адресу можно прибавлять или из адреса можно вычитать Выводы (правила адресной арифметики) 1. К адресу можно прибавлять или из адреса можно вычитать целые значения. При этом результат операции зависит от типа переменной, адрес которой участвует в операции. Если для данного типа размер переменной равен n байт и к адресу прибавляется i, то величина адреса изменится на i*n. 2. Никакие другие операции к адресам неприменимы, т. е. адреса нельзя умножать, делить, складывать между собой и пр. 3. Исключением является операция вычитания адресов однотипных объектов, результатом которой является целое число, равное количеству объектов данного типа, которые могут разместиться на участке памяти между адресами-операндами. Лекция 30. 09. 13 г. 10

Иллюстрация к вычитанию адресов //Ex 033. с – вычитание адресов #include <stdio. h> #include Иллюстрация к вычитанию адресов //Ex 033. с – вычитание адресов #include #include int x; short y[8]; char a[256], b[512+1]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(b)-addr(a) = %dn", printf("addr(a)-addr(b) = %dn", printf("addr(y)-addr(a) = %dn", printf("addr(x)-addr(a) = %dn", system("PAUSE"); return 0; } b-a); a-b); (char*)y-a); y-(short*)a); (void*)&x-(void*)a); (double*)&x-(double*)a); Лекция 30. 09. 13 г. 11

Иллюстрация к вычитанию адресов int fact(int) 0 x 00401300 ? ? байт char a[256] Иллюстрация к вычитанию адресов int fact(int) 0 x 00401300 ? ? байт char a[256] 0 x 004050 DC short y[8] 0 x 004051 DC char b[513] 256 байт 256 байт 272 байта 16 байт 800 байт 0 x 004051 EC 528 байт int x 0 x 004053 FC 16 байт Лекция 30. 09. 13 г. 12

Указатели Адреса объектов программы можно сохранять в переменных специального вида – указателях. Указатели образуют Указатели Адреса объектов программы можно сохранять в переменных специального вида – указателях. Указатели образуют не один конкретный тип данных (хотя все указатели имеют один размер – 4 байта); типов указателей существует столько, сколько существует типов данных, т. е. для любого типа данных Т существует тип указателя Т*. Примеры типов указателей: char*, int*, short*, long*, float*, double*, void*, … Указатели типа void* называются не типизированными (универсальными) указателями. При выполнении арифметических операций над такими указателями считается, что объекты, на которые они указывают, имеют размер 1 байт. //Ex 034. с – указатели main() { int *px = &x; short *py = &y; char *pa = a, *pb = void *pf = fact; printf("px = %pn", printf("py = %pn", printf("pa = %pn", printf("pb = %pn", printf("pf = %pn", system("PAUSE"); return 0; } b; px); py); pa); pb); pf); Лекция 30. 09. 13 г. 13

Доступ по указателю Ø В языке С существует одноместная операция * , применяемая к Доступ по указателю Ø В языке С существует одноместная операция * , применяемая к значениям, являющимся адресами (указателями). Результатом этой операции является объект программы, расположенный по данному адресу. Эта операция называется операцией ссылки по указателю, или операцией разыменования. Ø Операции & и * являются взаимно-дополняющими и, если они расположены подряд, взаимно компенсируют друга: Ø Пусть дано определение: int x; Тогда &x – адрес x; *&x – сама переменная x; &*&x – адрес x и т. д. Ø Переменная типа указатель может в разное время принимать различные значения. Следовательно, через одну и туже переменную-указатель можно обращаться к разным переменным (в разное время): int a=2, b=3, c=4; int* p=&a; *p=7; p=&b; *p=8; p=&c; *p=9; printf(“a=%d, b=%d, c=%dn”, a, b, c); //a=7, b=8, c=9 Лекция 30. 09. 13 г. 14

Указатели и аргументы функций В теории программирования известны два способа передачи аргументов в функцию: Указатели и аргументы функций В теории программирования известны два способа передачи аргументов в функцию: §передача по значению §передача по имени (ссылке) В обоих случаях аргументы функции являются её локальными переменными и «не видны» извне. Различие способов состоит в том, что при вызове функции её аргументлокальная переменная получает : §в первом случае - копию значения фактического аргумента §во втором случае - ссылку на фактический аргумент (т. е. его адрес) и, следовательно, может им распоряжаться Каждый из этих способов имеет свои преимущества и недостатки и программист должен обоснованно подходить к выбору наиболее подходящего для конкретной ситуации способа. Наиболее известны два случая, когда необходимо использовать передачу по имени: §функция должна изменить значение передаваемого ей аргумента §размер аргумента велик и передача его по значению приведет к большим затратам памяти В языке С «штатно» реализован первый способ (т. е. передача аргументов по значению), однако и второй способ легко реализуется, если в качестве аргумента передавать адрес (указатель). Лекция 30. 09. 13 г. 15

Указатели и аргументы функций Рассмотрим еще раз функцию swap (обмен значениями) //Ex 035. с Указатели и аргументы функций Рассмотрим еще раз функцию swap (обмен значениями) //Ex 035. с – swap с указателями #include #include void swap(int *a, int *b) { int c = *a; *a = *b; *b = c; } int main() { int x = 5, y = 7; printf("x = %d, y = %dn", x, y); swap(&x, &y); printf("x = %d, y = %dn", x, y); system("PAUSE"); return 0; } main x: 5 y: 7 swap a: &x b: &y В функцию swap передаются адреса Лекция 30. 09. 13 г. 16

Указатели и аргументы функций Второй пример – передача массива в функцию по ссылке main Указатели и аргументы функций Второй пример – передача массива в функцию по ссылке main //Ex 036. с #include #include int sum(int m[], int n) { int i, s; for(i=0, s=0; i

Тема Массивы Лекция 30. 09. 13 г. 18 Тема Массивы Лекция 30. 09. 13 г. 18

Указатели и массивы Массив - это одна из наиболее простых структур данных, представляющих собой Указатели и массивы Массив - это одна из наиболее простых структур данных, представляющих собой линейную последовательность фиксированного количества однотипных элементов, расположенных в памяти друг за другом непрерывно и допускающих прямой доступ к любому элементу по его номеру (индексу). Массив характеризуется именем, типом элементов и их количеством. Пример определения массива: int x[50] x: addr x[0] x[1] x[2] x[3] … … 0 1 2 3 … … x[49] 49 В языке С существует очень тесная связь между массивами и указателями, которая характеризуется простым утверждением: Имя массива – это константный указатель на его начало Из этого утверждения следуют важные соотношения (здесь Т – имя типа): x[i] эквивалентно *(x+i) T x[] эквивалентно T *x Лекция 30. 09. 13 г. 19

Определение массивов При работе с массивами имеется одно затруднение – размер массива (количество элементов) Определение массивов При работе с массивами имеется одно затруднение – размер массива (количество элементов) должен быть известен в момент выделения памяти массиву компилятором. Проблем не возникает в следующих случаях: Øразмер массива задается константным выражением Øразмер массива задается выражением, в которое входят константы и внешние по отношению к блоку, в котором производится определение массива, переменные (и эти переменные получили значения!) Øразмер массива не задается, а определяется списком инициализации //Ex 037. с #define N 100 int n 1 = 10; int main() { int n = 20; int a[N], b[30], c[n 1]; int g[] = {1, 2, 3, 4}; { int d[n]; } } Лекция 30. 09. 13 г. 20

Динамические массивы В тех случаях, когда размер массива невозможно знать заранее (например, эта величина Динамические массивы В тех случаях, когда размер массива невозможно знать заранее (например, эта величина вводится человеком), можно использовать динамические массивы, память для которых выделяется динамически системной функцией void *calloc(size_t n, size_t r) где n – количество элементов массива, r – размер элемента в байтах. Функция возвращает нетипизированный указатель на начало массива. //Ex 038. с – динамический массив #include #include int sum(int m[], int n) { int i, s; for(i=0, s=0; i

Версия только с указателями В этой версии программы используются только указатели. //Ex 038 -1. Версия только с указателями В этой версии программы используются только указатели. //Ex 038 -1. с – дин. массив (с указателями) #include #include int sum(int *m, int n) { int i, s; for(i=0, s=0; i

Освобождение памяти В программировании встречается неприятная ситуация, которая называется «утечка памяти» . Эта ситуация Освобождение памяти В программировании встречается неприятная ситуация, которая называется «утечка памяти» . Эта ситуация может возникнуть при использовании динамического выделения памяти функцией calloc. Для устранения этой ситуации рекомендуется освобождать неиспользуемую память с помощью функции void free(void *p) где p – нетипизированный указатель, получивший значение в результате вызова calloc. //Ex 038 -2. с – освобождение памяти int sum(int m[], int n) { int i, s; for(i=0, s=0; i

Символьные указатели Язык С не имеет отдельного типа данных для работы со строками символов Символьные указатели Язык С не имеет отдельного типа данных для работы со строками символов и они представляются массивами символов char[]. Например, строковая константа “I am a string” будет представлена в памяти символьным массивом из 14 элементов (13 символов+’’). Рассмотрим два способа определения объектов программы, с помощью которых можно работать со строками: char amessage[] = “I am a string” ; /* массив */ char *pmessage = “I am a string” ; /* указатель */ В первом случае amessage – это массив, занимающий 14 байтов памяти, заполненных символами из строковой константы и каждый символ в этом массиве можно изменять. При этом переменная amessage всегда будет указывать на один и тот же участок памяти и её значение изменить нельзя. Во втором случае pmessage – указатель размером 4 байта. С его помощью можно также получить доступ к любому символу строковой константы, но изменять символы нельзя. Кроме того, переменная pmessage в дальнейшем может получить другое значение, например, в результате присваивания: pmessage = “I am another string” ; Лекция 30. 09. 13 г. 24

Обработка строк При выполнении различных преобразований над строками необходимо очень внимательно подходить к учёту Обработка строк При выполнении различных преобразований над строками необходимо очень внимательно подходить к учёту разницы между представлением строк массивами и указателями. Рассмотрим известную задачу «обмен значениями» для двух строк. //Ex 039. с – обмен строк #include #include #define MAXLEN 200 void strcpy_(char* s, char* t) { while(*s++ = *t++); } void swaps(char* a, char *b) { char c[MAXLEN]; strcpy_(c, a); strcpy_(a, b); strcpy_(b, c); } main() { char s 1[MAXLEN] = "first"; char s 2[MAXLEN] = "second"; printf("%s, %sn", s 1, s 2); swaps(s 1, s 2); printf("%s, %sn", s 1, s 2); system("PAUSE"); return 0; Лекция 30. 09. 13 г. } Ok! 25

Обработка строк Модифицируем программу – в основной программе будем использовать указатели: //Ex 040. с Обработка строк Модифицируем программу – в основной программе будем использовать указатели: //Ex 040. с #include #include #define MAXLEN 200 void strcpy_(char* s, char* t) { while(*s++ = *t++); } void swaps(char* a, char *b) { char c[MAXLEN]; strcpy_(c, a); strcpy_(a, b); strcpy_(b, c); } main() { char *s 1 = "first"; char *s 2 = "second"; printf("%s, %sn", s 1, s 2); swaps(s 1, s 2); printf("%s, %sn", s 1, s 2); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. Err! 26

Обработка строк Основная причина неудачи предыдущей программы – попытка изменить символы в строковой константе Обработка строк Основная причина неудачи предыдущей программы – попытка изменить символы в строковой константе (это невозможно!). Однако в этом случае есть более лёгкое решение – строки оставить на месте, а поменять значения указателей: //Ex 041. с #include #include #define MAXLEN 200 void swaps(char* a, char *b) { char *c = a; a = b; b = c; } main() { char *s 1 = "first"; char *s 2 = "second"; printf("%s, %sn", s 1, s 2); swaps(s 1, s 2); printf("%s, %sn", s 1, s 2); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. Err! 27

Обработка строк В этот раз неудача объясняется тем, что значения указателей s 1 и Обработка строк В этот раз неудача объясняется тем, что значения указателей s 1 и s 2 в функции изменять нельзя по той же причине, которая была рассмотрена ранее в теме «два способа передачи аргументов в функцию» . Выход – использование адресов указателей s 1 и s 2 (т. е. указателей на указатели): //Ex 042. с #include #include void swaps(char** a, char** b) { char *c = *a; *a = *b; *b = c; } main() { char *s 1 = "first"; char *s 2 = "second"; printf("%s, %sn", s 1, s 2); swaps(&s 1, &s 2); printf("%s, %sn", s 1, s 2); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. Ok! 28

Иллюстрация к программе main //Ex 042. с #include <stdio. h> #include <stdlib. h> void Иллюстрация к программе main //Ex 042. с #include #include void swaps(char** a, char** b) { char *c = *a; *a = *b; *b = c; } main() { char *s 1 = "first"; char *s 2 = "second"; printf("%s, %sn", s 1, s 2); swaps(&s 1, &s 2); printf("%s, %sn", s 1, s 2); system("PAUSE"); return 0; } s 1: addr 1 first s 2: addr 2 second swaps a: &s 1 b: &s 2 после вызова swaps addr 2 first s 2: Лекция 30. 09. 13 г. s 1: addr 1 second 29

Массивы указателей Так как указатели являются обычными переменными, их можно хранить в массиве. Создадим Массивы указателей Так как указатели являются обычными переменными, их можно хранить в массиве. Создадим объект «массив строк» , проинициализируем его и выполним перестановку некоторых элементов этого массива: //Ex 043. с #include #include void swaps(char** a, char** b) { char *c = *a; *a = *b; *b = c; } main() { char* arrs[] = {"first", "second", "third", "fourth", "fifth"}; int i, n = sizeof(arrs)/sizeof(*arrs); for(i = 0; i < n; i++) printf("%d: %sn", i+1, arrs[i]); swaps(&arrs[0], &arrs[2]); swaps(&arrs[1], &arrs[4]); printf("----n"); for(i = 0; i < n; i++) printf("%d: %sn", i+1, arrs[i]); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 30

Иллюстрация к программе arrs → arrs[0] first arrs[1] arrs → arrs[0] first second arrs[1] Иллюстрация к программе arrs → arrs[0] first arrs[1] arrs → arrs[0] first second arrs[1] second arrs[2] third arrs[3] fourth arrs[4] fifth Лекция 30. 09. 13 г. 31

Многомерные массивы Синтаксис языка С допускает, чтобы при определении массива его элементами были массивы Многомерные массивы Синтаксис языка С допускает, чтобы при определении массива его элементами были массивы (одинакового размера!). В этом случае приходим к понятию многомерного прямоугольного массива. Например, популярный математический объект «матрица» может быть представлен двумерным массивом: //Ex 044. с – двумерный массив #include #include main() { int matr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int i, n = 3, j, m = 3; for(i = 0; i < n; i++) { for(j = 0; j < m; j++) printf("%4 d", matr[i][j]); printf("n"); } system("PAUSE"); return 0; } matr – массив из 3 -х элементов, каждый из которых является массивом из 3 -х элементов Лекция 30. 09. 13 г. 32

Многомерные массивы Можно задать двумерный массив неопределенного (по первому измерению!) размера : //Ex 045. Многомерные массивы Можно задать двумерный массив неопределенного (по первому измерению!) размера : //Ex 045. с – 2 D-массив неопред. размера #include #include main() { int matr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; int i, n = sizeof(matr)/sizeof(*matr); int j, m = 3; for(i = 0; i < n; i++) { for(j = 0; j < m; j++) printf("%4 d", matr[i][j]); printf("n"); } system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 33

Многомерные массивы и указатели По аналогии с одномерными массивами можно предположить, что справедливо соотношение: Многомерные массивы и указатели По аналогии с одномерными массивами можно предположить, что справедливо соотношение: x[i][j] эквивалентно *(*(x+i)+j). Проверим это: //Ex 046. с – массивы и указатели #include #include main() { int matr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; int i, n = sizeof(matr)/sizeof(*matr); int j, m = 3; for(i = 0; i < n; i++) { for(j = 0; j < m; j++) printf("%4 d", matr[i][j]); printf("n"); } printf("[1, 1]->%4 dn", *(*(matr+1)+1)); printf("[3, 2]->%4 dn", *(*(matr+3)+2)); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 34

Многомерные массивы нерегулярной структуры С помощью указателей можно сконструировать необычные, «зубчатые» массивы, в которых Многомерные массивы нерегулярной структуры С помощью указателей можно сконструировать необычные, «зубчатые» массивы, в которых каждая строка может иметь свой размер, например так: 1 2 5 3 4 3 8 5 7 9 //Ex 047. с – зубчатый массив #include #include main() { int m 1[] = {1, 2}; int m 2[] = {5, 3, 4, 7, 9, 2}; int m 3[] = {3, 8, 5}; int *matr[] = {m 1, m 2, m 3}; printf("[1, 1]->%4 dn", *(*(matr+1)+1)); printf("[1, 5]->%4 dn", *(*(matr+1)+5)); printf("[2, 2]->%4 dn", *(*(matr+2)+2)); printf("[1, 1]->%4 dn", matr[1][1]); printf("[1, 5]->%4 dn", matr[1][5]); printf("[2, 2]->%4 dn", matr[2][2]); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 35 2

Динамические многомерные массивы Используя указатели и динамическое выделение памяти сконструируем зубчатый массив специального вида Динамические многомерные массивы Используя указатели и динамическое выделение памяти сконструируем зубчатый массив специального вида - нижнюю треугольную матрицу размера n (n вводится с клавиатуры), заполненную числовыми значениями по следующему правилу: 1 2 1 3 2 1 4 3 2 1 5 4 3 2 1 6 5 4 3 2 1 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 9 8 7 6 5 4 3 2 Лекция 30. 09. 13 г. 1 36

Динамические многомерные массивы //Ex 048. с – треугольная матрица #include <stdio. h> #include <stdlib. Динамические многомерные массивы //Ex 048. с – треугольная матрица #include #include main() { int i, j, n; int **t; printf("n: "); scanf("%d", &n); t = (int **)calloc(n, sizeof(**t)); for(i = 0; i < n; i++) { t[i] = (int *)calloc(i+1, sizeof(*t)); for(j = 0; j <= i; j++) t[i][j] = i-j+1; } for(i = 0; i < n; i++) { for(j = 0; j <= i; j++) printf("%4 d", t[i][j]); printf("n"); } system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 37

Константные указатели Свойство константности объекта задаётся описателем const и символизирует запрет на выполнение операций Константные указатели Свойство константности объекта задаётся описателем const и символизирует запрет на выполнение операций записи в память. Это свойство можно задать для разных объектов программы: - переменных, - параметров функций, - возвращаемых из функций значений. Контроль за корректным использованием свойства const выполняет компилятор, поэтому ошибки обнаруживаются на ранних стадиях создания программы, что повышает ее надежность. Лекция 30. 09. 13 г. 38

Константные указатели С помощью описателя const можно сделать неизменяемым как указатель, так и область Константные указатели С помощью описателя const можно сделать неизменяемым как указатель, так и область памяти, на которую он ссылается. //Ex 049. с – константные указатели #include main() { int a = 5, b = 7; int *p 1 = &a; *p 1 = 3; // ok! p 1 = &b; // ok! const int *p 2 = &a; //констант. область памяти *p 2 = 9; // err! p 2 = &b; // ok! int *const p 3 = &a; //констант. указатель *p 3 = 9; // ok! p 3 = &b; // err! const int *const p 4 = &a; //и указ. , и обл. пам. конст. *p 4 = 6; // err! p 4 = &b; // err! system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 39

Аргументы командной строки argv В операционной среде, обеспечивающей поддержку С, имеется возможность передать аргументы Аргументы командной строки argv В операционной среде, обеспечивающей поддержку С, имеется возможность передать аргументы (параметры) ● запускаемой программе с помощью командной строки. В момент вызова ex 050 ● функции main она получает два аргумента. В первом, one ● обычно называемом argc (от argument count – счетчик аргументов), содержится число, равное количеству ● two аргументов, заданных в командной строке. Второй, argv ● three (от argument vector – вектор аргументов), является ● указателем на массив символьных строк, содержащих four сами аргументы, - по одному в строке. 0 //Ex 050. с – арг-ты командной строки #include char *argv[] = char **argv #include main(int argc, char *argv[]){ "%devcpp%gcc" Ex 050. c -std=c 99 -o Ex 050 for(int i = 0; i < argc; i++) printf("%d: %s ", i, *argv++); printf("n"); system("PAUSE"); return 0; Лекция 30. 09. 13 г. 40 }

Аргументы командной строки Следующая программа, которая названа factorial, позволяет вычислить факториалы нескольких чисел, которые Аргументы командной строки Следующая программа, которая названа factorial, позволяет вычислить факториалы нескольких чисел, которые задаются как параметры вызова данной программы. //Ex 051. с – арг-ты командной строки #include #include int fact(int n) { return n ? n * fact(n-1) : 1; } int main(int argc, char **argv) { int n; while(--argc > 0) { n = atoi(*++argv); printf("%2 d! = %10 dn", n, fact(n)); } system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 41

Дополнительные сведения о функциях: rvalue и lvalue Если тип возвращаемого значения функции отличен от Дополнительные сведения о функциях: rvalue и lvalue Если тип возвращаемого значения функции отличен от void, то в результате вызова функции получается, как правило, некоторое значение, которое может быть использовано как операнд в выражении. Например, для подсчета среднего значения массива может быть использован следующий фрагмент: double sum(double *x, int n); // прототип функции sum. . . sr = sum(m, 100)/100; В данном примере функция возвращает так называемое rvalue, иначе говоря, значение. Термин rvalue происходит от read value – значение для чтения, или от right value – правое значение – по его месту относительно операции присваивания. Лекция 30. 09. 13 г. 42

Дополнительные сведения о функциях: rvalue и lvalue В некоторых случаях типом возвращаемого значения функции Дополнительные сведения о функциях: rvalue и lvalue В некоторых случаях типом возвращаемого значения функции может быть тип, дающий lvalue, иначе говоря, адрес. Термин lvalue происходит от location value – значение местоположения, или от left value – левое значение – также по его месту относительно оператора присваивания. Следовательно, вызов такой функции может располагаться слева от оператора присваивания. В представленном на следующем слайде примере функция max выдает адрес максимального элемента массива, а основная программа main заменяет значение найденного максимального элемента на 999, используя вызов функции в левой части операции присваивания. Лекция 30. 09. 13 г. 43

Дополнительные сведения о функциях: rvalue и lvalue //Ex 052. с – функция возвращает lvalue Дополнительные сведения о функциях: rvalue и lvalue //Ex 052. с – функция возвращает lvalue #include #include int *max(int n, int x[]) { int k = 0, m = x[0]; for(int i = 0; i < n; i++) if(x[i] > m) k = i, m = x[i]; return x + k; } int main(int argc, char **argv) { int z[] = {-1, 7, 2, -3, 4, 0, 3}; int n = sizeof(z) / sizeof(z[0]); for(int i = 0; i < n; i++) printf("%4 d, ", z[i]); printf("n"); *max(n, z) = 999; for(int i = 0; i < n; i++) printf("%4 d, ", z[i]); printf("n"); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 44

Дополнительные сведения о функциях: rvalue и lvalue Внимание! Необходимо с осторожностью обращаться с lvalue, Дополнительные сведения о функциях: rvalue и lvalue Внимание! Необходимо с осторожностью обращаться с lvalue, возвращаемыми из функций и не допускать, чтобы из функций возвращались адреса несуществующих объектов, например, локальных переменных, объявляемых внутри функции: //Ex 053. с – функция возвращает lvalue #include #include int *fe() { int z; return &z; } int main(int argc, char **argv) { *fe() = 11; printf("*fe() = %dn", *fe()); printf("n"); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 45

Указатели на функции В программировании для Windows широко применяется техника функций обратного вызова (callback Указатели на функции В программировании для Windows широко применяется техника функций обратного вызова (callback functions). Функции обратного вызова – это указатели на функции, через которые могут быть вызваны функции. Пример указателя на функцию: //Ex 054. с #include #include int f(int k) { printf("Call f : k = %6 dn", k); return 100*k; } int f 1(int k) { printf("Call f 1: k = %6 dn", k); return 1000*k; pf – это указатель на функцию } int main(int argc, char **argv) { int i = 2; int (*pf)(int) = f; printf("1: %6 dn", pf(i++)); pf = f 1; printf("2: %6 dn", pf(i++)); system("PAUSE"); return 0; Лекция 30. 09. 13 г. } 46

Указатели на функции В предыдущем примере был использован указатель на функцию pf как самостоятельная Указатели на функции В предыдущем примере был использован указатель на функцию pf как самостоятельная переменная. Синтаксис языка С допускает объявить указатель на функцию как тип данных, что позволяет использовать динамические переменные и массивы указателей. //Ex 055. с #include #include int f(int k) { printf("Call f : k = %6 dn", k); return 100*k; } D – это тип, т. е. множество int f 1(int k) { указателей на функции printf("Call f 1: k = %6 dn", k); return 1000*k; } typedef int (*D)(int); int main(int argc, char **argv) { int i = 2; D p[] = {f, f 1}; D p 1 = f; printf("%d n", (*p)(i++)); printf("%d n", p[1](i++)); printf("%d n", p 1(i++)); system("PAUSE"); return 0; } Лекция 30. 09. 13 г. 47

Сложные объявления Язык С часто обвиняют за чрезмерно сложный синтаксис объявлений, особенно тех, которые Сложные объявления Язык С часто обвиняют за чрезмерно сложный синтаксис объявлений, особенно тех, которые содержат в себе указатели на функции. Например: int *f(); функция f, возвращающая указатель на int (*f)(); указатель на функцию f, возвращающую int Приоритет оператора * ниже, чем приоритет (), поэтому во втором случае скобки необходимы. Ещё пример: int *daytab[13]; массив из 13 указателей на int (*daytab)[13]; указатель на массив из 13 чисел типа int И ещё одно (очень запутанное!) объявление: int (*(*(*fun)())[])(); Его следует понимать так: «fun – это указатель на функцию, возвращающую указатель на массив указателей на функции, возвращающие int» . Рассмотрим общее правило (которое называется правило «право-лево» ), позволяющее разбирать любые «запутанные» объявления. Лекция 30. 09. 13 г. 48

Сложные объявления (правило «право-лево» ) При описании этого правила необходимо помнить следующие 3 операции: Сложные объявления (правило «право-лево» ) При описании этого правила необходимо помнить следующие 3 операции: () - функция, возвращающая. . . [] - массив из. . . * - указатель на. . . Первые две имеют наивысший приоритет, а третья – более низкий. Процесс разбора объявления итеративный: Шаг 1. Находим имя, с которого начинается разбор, и записываем начало предложения «Имя есть. . . » . Шаг 2. Начинаем движение вправо для поиска () или []. Если справа оказывается (), то в конструируемое предложение вместо многоточия подставляем: «функция, возвращающая. . . » и в итоге получаем: «Имя есть функция, возвращающая. . . » . Если справа [], то подставляем «массив из…» и получаем «Имя есть массив из. . . » . Таким образом мы идём вправо до тех пор, пока не дойдём до конца объявления или правой ) скобки. Шаг 3. После этого начинаем перемещаться влево. Если слева обнаруживается что-то отличное от упомянутых выше операций (то есть не (), [], *), то просто добавляем к уже имеющемуся предложению. Если же там что-то из этих трёх операций, то к предложению добавляем соответствующий текст. И так перемещаемся до начала объявления или левой ( скобки. Если обнаружена (, то переходим к шагу 2. Если дошли до начала объявления, разбор закончен. Лекция 30. 09. 13 г. 49

Сложные объявления (правило «право-лево» ) Рассмотрим сложный пример: int (*(*(*fun)())[])(); ^^^ Hаходим имя и Сложные объявления (правило «право-лево» ) Рассмотрим сложный пример: int (*(*(*fun)())[])(); ^^^ Hаходим имя и записываем: «fun есть. . . » . Шаг вправо, но там ), и потому идём влево int (*(*(*fun)())[])(); ^ и получаем «fun есть указатель на. . . » . Продолжаем двигаться влево, но тут (. Идём вправо int (*(*(*fun)())[])(); ^^ получаем «fun есть указатель на функцию, возвращающую. . . » . Шаг вправо, но там ), и потому идём влево. Получаем int (*(*(*fun)())[3])(); ^ «fun есть указатель на функцию, возвращающую указатель на. . . » . Слева опять (, поэтому идём вправо. Получаем int (*(*(*fun)())[])(); ^^ «fun есть указатель на функцию, возвращающую указатель на массив. . . » . Лекция 30. 09. 13 г. 50

Сложные объявления (правило «право-лево» ) И снова справа ), поэтому перемещаемся влево и получаем Сложные объявления (правило «право-лево» ) И снова справа ), поэтому перемещаемся влево и получаем int (*(*(*fun)())[])(); ^ «fun есть указатель на функцию, возвращающую указатель на массив указателей на. . . » . Снова разворот вправо, т. к. обнаруживается ( , и получаем int (*(*(*fun)())[])(); ^^ «fun есть указатель на функцию, возвращающую указатель на массив указателей на функции, возвращающие. . . » . Обнаруживаем конец описания, переключаемся на движение влево и получаем окончательную расшифровку этого очень сложного и запутанного описания: int (*(*(*fun)())[])(); ^^^ «fun есть указатель на функцию, возвращающую указатель на массив указателей на функции, возвращающие int» . Лекция 30. 09. 13 г. 51