Скачать презентацию Язык СИ Часть 3 ПАМЯТЬ УКАЗАТЕЛИ МАССИВЫ Скачать презентацию Язык СИ Часть 3 ПАМЯТЬ УКАЗАТЕЛИ МАССИВЫ

SI_3_chast.pptx

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

Язык СИ. Часть 3. ПАМЯТЬ. УКАЗАТЕЛИ. МАССИВЫ Язык СИ. Часть 3. ПАМЯТЬ. УКАЗАТЕЛИ. МАССИВЫ

Классы памяти переменных КЛЮЧЕВЫЕ СЛОВА AUTO. STATIC. EXTERN. REGISTER Классы памяти переменных КЛЮЧЕВЫЕ СЛОВА AUTO. STATIC. EXTERN. REGISTER

Локальные (автоматические) переменные – объявляются внутри какого-либо блока { }. Ключевое слово auto обычно Локальные (автоматические) переменные – объявляются внутри какого-либо блока { }. Ключевое слово auto обычно опускают (подразумевается по умолчанию) main() { int i = 0; …. }. Область видимости – блок, т. е. такие переменные доступны только внутри того блока {}, где они объявлены! int main(){ { int j = 0; } j = 9; ОШИБКА! Undefined symbol ‘j’ }

Глобальные (внешние) переменные – объявляются вне какого-либо блока. Область видимости – любая функция, следующая Глобальные (внешние) переменные – объявляются вне какого-либо блока. Область видимости – любая функция, следующая за их объявлением. Не рекомендуется использовать! (такие программы очень сложно отлаживать) int j = 10; //Глобальная переменная int return. J(); //Прототип функции int main() { printf("j = %d, from function j = %d", j, return. J()); } int return. J() { return j; }

Ключевое слово extern Во всех файлах, образующих исходный текст программы, должно быть не больше Ключевое слово extern Во всех файлах, образующих исходный текст программы, должно быть не больше одного определения одной и той же внешней переменной! В случае, когда переменная определена в одном файле, а использовать ее нужно в другом, необходимо в одном файле объявить ее как обычно, а во всех остальных – с ключевым словом extern. Файл 1. с: int Global. Var; Файл 2. с: extern int Global. Var;

Ключевое слово static Ограничивает область видимости внешней переменной или функции тем файлом, где она Ключевое слово static Ограничивает область видимости внешней переменной или функции тем файлом, где она объявлена. Если локальную переменную объявить как static, то ее значение будет сохраняться между вызовами функции, в которой она объявлена. int return. J(); int main(){ printf("%d ", return. J()); printf("%d", return. J()); } int return. J(){ Присваивание 0 static j = 0; произойдет 1 раз! j++; return j; } Выведется: 1 2

Ключевое слово register сообщает компилятору, что соответствующая переменная будет интенсивно использоваться программой. Идея заключается Ключевое слово register сообщает компилятору, что соответствующая переменная будет интенсивно использоваться программой. Идея заключается в том, чтобы поместить такие регистровые переменные в регистры процессора и добиться повышения быстродействия и уменьшения объема кода. Впрочем, компилятор имеет право игнорировать эту информацию. register int x; register char с;

Построение проекта С/С++ Преобразование исходного кода в исполняемый exe-файл происходит в несколько этапов. Если Построение проекта С/С++ Преобразование исходного кода в исполняемый exe-файл происходит в несколько этапов. Если вы пользуетесь IDE, то все эти этапы выполняет за вас она по нажатию на зеленую кнопку F 9 (для Builder) или F 5 (для VS). 0. Препроцессинг Препроцессор обрабатывает директивы, которые начинаются с символа #. Директива include заставляет препроцессор вставить в требуемое место все содержимое указанного файла. <имя_файла> - поиск файла в системных директориях. “имя_файла” – поиск файла рядом с файлом кода. С помощью директивы define можно объявлять макросы: #define идентификатор имя_для_замены В этом случае перед компиляцией препроцессор заменит все идентификаторы на имя_для_замены.

1. Компиляция: Файлы с исходным кодом (*. с, *. срр) Команды на языке высокого 1. Компиляция: Файлы с исходным кодом (*. с, *. срр) Команды на языке высокого уровня (x += 8) объектные файлы (*. o, *. obj) машинные команды add eax, 8 2. Компоновка: объектные файлы + файлы библиотек (*. o, *. obj + *. lib) lib – статические библиотеки dll – динамические библиотеки исполняемые файлы (*. exe, *. dll, *. lib)

Организация памяти Организация памяти

Программы и процессы Программа – определенный набор инструкций. При каждом запуске программы для нее Программы и процессы Программа – определенный набор инструкций. При каждом запуске программы для нее создается свой процесс. Процесс – экземпляр выполняемой программы. Это набор ресурсов и данных, использующихся при выполнении программы. Каждый процесс включает: 1) структуру данных, описывающую сам процесс (идентификатор процесса, список используемых ресурсов, статистическая информация); 2) адресное пространство – список доступных процессу адресов памяти; 3) исполняемая программа и данные.

Потоки Поток (thread) – некая сущность внутри процесса, получающая процессорное время для выполнения кода Потоки Поток (thread) – некая сущность внутри процесса, получающая процессорное время для выполнения кода программы. В каждом процессе есть минимум один поток. Этот первичный поток создается системой автоматически при создании процесса. Далее этот поток может породить другие потоки, те в свою очередь новые и т. д.

Чтобы все потоки работали, операционная система отводит каждому из них определенное процессорное время. Тем Чтобы все потоки работали, операционная система отводит каждому из них определенное процессорное время. Тем самым создается иллюзия одновременного выполнения потоков (разумеется, для многопроцессорных компьютеров возможен истинный параллелизм). Планирование в Windows осуществляется на уровне потоков, а не процессов. Это кажется понятным, так как сами процессы не выполняются, а лишь предоставляют ресурсы и контекст для выполнения потоков.

Память Каждому процессу в 32 -разрядной системе доступно 4 Гб линейной виртуальной памяти. Каждый Память Каждому процессу в 32 -разрядной системе доступно 4 Гб линейной виртуальной памяти. Каждый адрес указывает на конкретный байт памяти, поэтому с помощью 232 возможных комбинаций (не забываем, что адрес записывается в двоичном виде) можно адресовать 4 Гб памяти). В 64 -разрядных системах возможна адресация до 2 Тб памяти. За отображение виртуальной памяти на физическую и обратно отвечает компонент ОС под названием диспетчер памяти.

Описание областей Код – все операции (main(), printf()), написанные вами, + функции стандартных библиотек Описание областей Код – все операции (main(), printf()), написанные вами, + функции стандартных библиотек преобразуются компилятором в машинные команды, которые загружаются в память для последующего выполнения потоком (потоками). Пример: вычисление sin(x) call _sin add esp, 8 fstp [ebp+var_C] fld [ebp+var_4] fadd ds: dbl_40124 C add esp, 0 FFFFFFF 8 h ; x fstp [esp+38 h+var_38] Глобальные и статические переменные – статические данные, память для которых выделяется при запуске программы автоматически. Размер этой области можно вычислить при запуске и во время выполнения он не меняется!

Куча (heap) – область динамической памяти. Здесь хранятся данные, размер которых на момент запуска Куча (heap) – область динамической памяти. Здесь хранятся данные, размер которых на момент запуска программы неизвестен. Эта память выделяется специальными функциями языка (пример – malloc, realloc). Ее размер во время выполнения программы может меняться. Пример: выделение памяти под массив из 1000 элементов типа int * i = (int)malloc(1000); Использование динамической памяти позволяет эффективно использовать память и управлять ею вручную (особенности языков С/С++).

Стек (stack) – используется для хранения локальных переменных, передачи параметров в функцию и для Стек (stack) – используется для хранения локальных переменных, передачи параметров в функцию и для хранения адресов возврата из функции. Стек представляет из себя динамическую структуру данных, работающую по принципу LIFO – Last In First Out – последний вошел – первый вышел. Добавление (push) и извлечение (pop) данных всегда происходит по адресу вершины стека. Сохранение данных в стек Удаление данных из стека Ячейки памяти

Указатели Указатели

Указатель (pointer) * - это специальный тип данных, предназначенный для хранения адресов памяти, по Указатель (pointer) * - это специальный тип данных, предназначенный для хранения адресов памяти, по которым находятся какие-либо данные. Пример: int number = 5; // переменная для хранения адреса переменной типа int * number. Addr = &number; printf("number = %d, number Address = %#x", number. Addr);

Операции * и & & - операция получения адреса объекта. * - операция ссылки Операции * и & & - операция получения адреса объекта. * - операция ссылки по указателю (разыменование указателя). Применяя ее к указателю получаем объект, на который он указывает. Пример: int х = 1, у = 2 ; int *ip; /* ip - указатель на int */ ip = &х; /* ip теперь указывает на х */ y = *ip; /* у теперь равно 1 */ *ip = 0; /* х теперь равно 0 */

Указатели могут быть: ▪Указатель на объект (переменную). ▪Указатель на функцию. ▪void-указатель (неизвестно на что). Указатели могут быть: ▪Указатель на объект (переменную). ▪Указатель на функцию. ▪void-указатель (неизвестно на что).

Операции с указателями - 6 Оп сравнения: == != > >= < <= - Операции с указателями - 6 Оп сравнения: == != > >= < <= - ++ (инкремент), -- (декремент), - вычитание, сложение, - присваивания: = для Ук одного типа !!! Пример: int * pk; // Значение pk увел-ся на размер типа (для int на 4 байта) pk++ ; //Сложение с константой pk = pk + 4; // то же самое, что pk = pk + 4 * ( размер типа )

Массивы Массивы

Массивы (arrays) Массив (array) – это набор данных одного типа, имеющий определенное имя. Нумерация Массивы (arrays) Массив (array) – это набор данных одного типа, имеющий определенное имя. Нумерация элементов – с 0 до N-1, где N – длина массива. Объявление статического массива (Стат. М) : int. Array [10] ; Тип элементов массива Имя массива sizeof(int. Array) = ? Длина массива (количество элементов)

Способы указания размера Стат. М ▪Как сonst при объявлении: int arr[ 100 ] ; Способы указания размера Стат. М ▪Как сonst при объявлении: int arr[ 100 ] ; ▪именованная сonst в директиве Препроц: # define Narr 100. . . int arr[ Narr ]; ▪именованная сonst: const int M = 50; ▪ int arr[M]; Работает не во всех компиляторах!

Массивы в памяти Массив из 10 элементов типа short int (2 байта) Память Адрес Массивы в памяти Массив из 10 элементов типа short int (2 байта) Память Адрес 1000 1001 1002 1003 1004 располагаются непрерывно 1 Эл-т М a[0] a[1]. . . a[9] N – размер М a[N-1]

Обращение к элементам массива [ ] – операция индексации. int i, array [10] = Обращение к элементам массива [ ] – операция индексации. int i, array [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; //вывод всех элементов массива for( i = 0; i<10; i++) printf(“a[%d] = %d”, i, array[i]); При попытке доступа к array[10] получим ошибку!

Массивы и указатели int * pa; int a[10]; pa = a; // то же Массивы и указатели int * pa; int a[10]; pa = a; // то же самое что pa = &a[0]; int x = *pa; // что будет в х? int y = *(pa + 1); // что будет в y? Имя массива – это указатель на 0 -ой элемент!

Инициализация элементов массива int number[10] = {0, 1, 2, 3, 1}; //инициализация первых 5 Инициализация элементов массива int number[10] = {0, 1, 2, 3, 1}; //инициализация первых 5 эл-ов int number[10] = {0}; //инициализация всех эл-ов нулями int num [] = {0, 1, 2, 3, 1}; //определение длины массива printf("length(arr)=%d", sizeof(num)/sizeof(num[0])); //5 int i, number[10]; //инициализация в цикле for(i = 0; i<10; i++) { number[i] = i; }

const int N = 100; int m[ N ]; int *pm = m; /1/ const int N = 100; int m[ N ]; int *pm = m; /1/ Ввод массива – в цикле по индексу эл-ов: for( int i = 0; i < N; i++) scanf(“%d”, &m[ i ]); /2/ Ввод массива – в цикле по Ук for( int i = 0; i < N; i++) scanf(“%d”, pm++) ; // Почему не указываем Оп & ?

/3/ Ввод массива – в цикле по Ук for( int i = 0; i /3/ Ввод массива – в цикле по Ук for( int i = 0; i < N; i++) scanf(“%d”, pm+i) ; // В этом случае значение Ук pm не изм-ся // Можно ли вместо pm исп-ть Ук m ? ! for( int i = 0; i < N; i++) scanf(“%d”, m+i) ;

/1/ Вывод Массива – в цикле: for( int i = 0; i < N; /1/ Вывод Массива – в цикле: for( int i = 0; i < N; i++) printf(“%d ”, m[ i ]); /2/ Вывод Массива по Ук: for( int i = 0; i < N; i++) printf(“%d ”, *pm++) ; // Вывод значения, расположенного // по Ук рm и увеличение Ук на sizeof(int) байт

/3/ Вывод Массива по Ук: // Увеличение Ук рm на sizeof(int)*i байта // и /3/ Вывод Массива по Ук: // Увеличение Ук рm на sizeof(int)*i байта // и вывод значения, расположенного по Ук (pm + i) for( int i = 0; i < N; i++) printf(“%d ”, *(pm + i)) ; // Можно ли вместо pm использовать Ук m ? ! // Да, можно: printf(“%d ”, *(m + i)) ;

Двумерные массивы – матрицы (Матр) int А[ 3 ] [ 5 ] ; // Двумерные массивы – матрицы (Матр) int А[ 3 ] [ 5 ] ; // Матрица 3 х 5, А={ ai, j } // 3 строки (m) и 5 столбцов (n) 0 1 2 3 4 0 1 2 3 4 5 А = 1 4 5 6 7 8 2 7 8 9 8 8 А[ i ] [ j ] – обращение к элементу матрицы по индексам Размерность Матр – 2 Размер памяти Матр: (размер типа) x m x n в нашем примере = 30

Инициализация Матр: int А[3] = { { 1, 2, 3 }, { 4, 5, Инициализация Матр: int А[3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; int N = 3; Ввод Матр – в Ц: for( int i = 0; i < N; i++) // Ввод строками for( int j = 0; j < N; j++) scanf(“%d”, &A[ i ] [ j ] ); Вывод Матр – аналогично Инициализация Матр – все эл-ты 0: int А[3] = { 0 } ; Удобно при отладке П

Строка Матрицы – одномерный М, int А[3] [3], *ptr ; ptr = &A[ 1 Строка Матрицы – одномерный М, int А[3] [3], *ptr ; ptr = &A[ 1 ] [ 0 ] ; // Адрес начала 1 -ой строки в Ук ptr, // с ptr можно работать как с М: // ptr[ i ] - i-ый эл-т 1 -ой строки Обращение к элементам матрицы: A – это Ук на Ук, Массив Ук на строки Матрицы A[ i ] – Массив элементов i-ой строки, A[ i ] [ j ] – j-й элемент Массива A[ i ] [ j ] = *( *(A + i ) + j ) = *(A[ i ] + j )