Файл: Лабораторная работа 1 Изучение среды разработки программ 3 Лабораторная работа 2 Исследование базовых типов данных языка Си 18.doc
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 263
Скачиваний: 3
СОДЕРЖАНИЕ
Лабораторная работа № 1Изучение среды разработки программ
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Лабораторная работа № 2Исследование базовых типов данных языка Си
Лабораторная работа № 4Применение управляющих инструкций языка для организации ветвлений в программе
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
ЗАДАНИЕ ДЛЯ САМОСТОЯТЕЛЬНОЙ РАБОТЫ
Лабораторная работа № 5Исследование циклов
Лабораторная работа № 6Применение массивов и указателей для решения прикладных задач
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Лабораторная работа № 7Исследование массивов и указателей
Лабораторная работа № 8Применение функций работы со строками для решения прикладных задач
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Практическое занятие № 6Использование функций для работы с массивами
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Практическое занятие № 7Программирование рекурсивных алгоритмов
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Практическое занятие № 8Применение производных типов данных для решения прикладных задач
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Лабораторная работа № 5Исследование методов доступа к файлам данных
Лабораторная работа № 6Исследование связанных списков данных
Если в списке инициализации в какой-то из размерностей не хватает данных, то все дальнейшие не перечисленные элементы считаются равными нулям.
Указатель - это переменная, значение которой равно значению адреса памяти, по которому лежит значение некоторой другой переменной. В этом смысле имя этой другой переменной отсылает к ее значению прямо, а указатель - косвенно. Ссылка на значение посредством указателя называется косвенной адресацией.
Указатели, подобно любым другим переменным, перед своим использованием должны быть объявлены. Объявление указателя имеет вид:
type *ptr;
где type - один из предопределенных или определенных пользователем типов, а ptr - указатель. Например,
int *countPtr, count;
объявляет переменную countPtr типа int * (т.е. указатель на целое число) и переменную count целого типа. Символ * в объявлении относится только к countPtr. Каждая переменная, объявляемая как указатель, должна иметь перед собой знак звездочки (*). Если в приведенном примере желательно, чтобы и переменная count была указателем, надо записать:
int *countPtr, *count;
Символ * в этих записях обозначает операцию косвенной адресации.
Может быть объявлен и указатель на void:
void *Pv;
Это универсальный указатель на любой тип данных. Но прежде, чем его использовать, ему надо в процессе работы присвоить значение указателя на какой-то конкретный тип данных. Например:
Pv = countPtr;
В Cи указатели используются очень широко. Указатели должны инициализироваться либо при своем объявлении, либо с помощью оператора присваивания. Указатель может получить в качестве начального значения 0, NULL или адрес. Указатель с начальным значением 0 или NULL ни на что не указывает. NULL - это символическая константа, определенная специально для цели показать, что данный указатель ни на что не указывает. Пример объявления указателя с его инициализацией:
int *countPtr = NULL;
Для присваивания указателю адреса некоторой переменной используется операция адресации &, которая возвращает адрес своего операнда. Например, если имеются объявления
int Step = 5;
int *ptr, Num
;
то оператор
ptr = &Step;
присваивает адрес переменной Step указателю ptr.
Операцию разыменования нельзя применять к указателю на void, поскольку для него неизвестно, какой размер памяти надо разыменовывать.
Для того, чтобы получить значение, на которое указывает указатель, используется операция *, обычно называемая операцией косвенной адресации или операцией разыменования. Она возвращает значение объекта, на который указывает ее операнд (т.е. указатель). Например, если продолжить приведенный выше пример, то оператор
Num = *ptr;
присвоит переменной Num значение 5, т.е. значение переменной Step, на которую указывает ptr.
Здесь необходимо обратить внимание на разночтение символа «*». Если символ «*» стоит перед идентификатором переменной при ее объявлении, то смысл этой «звездочки» - сказать, что объявляется не обычная переменная, а переменная-указатель. Во всех остальных случаях символ «*» читается и воспринимается программой как «взятие значения по адресу», то есть является операцией косвенной адресации.
ПРОГРАММА РАБОТЫ
-
Использование числовых массивов
-
набрать и отладить программу нахождения суммы элементов массива, стоящих в нечетных позициях:
#include
#include
int main(int argc, char* argv[])
{
srand(time(0)); // Оператор 0
const int N=10;
int Arr[N];
int sum=0; // Оператор 1
for (int i=0;i
{
Arr[i]= rand()%100; // Оператор 2
cout<
}
for (int i=1;i
sum+=Arr[i]; // Оператор 4
cout<<"\nSumma="<
return 0;
}
-
объяснить назначение операторов 0-4; -
набрать и отладить программу нахождения суммы элементов массива, кратных 3:
#include
#include
int main(int argc, char* argv[])
{
time_t t;
srand((unsigned)time(&t));
const int N=10;
int Arr[N];
int sum=0; // Оператор 1
for (int i=0;i
{
Arr[i]=rand()%50; // Оператор 2
cout<
}
for (int i=1;i
if (Arr[i]%3==0) sum+=Arr[i]; // Оператор 3
cout<<"\nSumma="<
return 0;
}
-
объяснить назначение операторов 1-3; -
набрать и отладить программу нахождения наибольшего элемента массива из N целых чисел с выводом номеров наибольших элементов:
#include
#include
int main(int argc, char* argv[])
{
srand(time(0));
const int N=10;
int Arr[N];
int index[N]={0}; // Оператор 1
int max, k=0;
for (int i=0;i
{
Arr[i]=rand()%100-50; // Оператор 2
cout<
}
max=Arr[0]; // Оператор 3
for (int i=1;i
if (Arr[i]>=max) // Оператор 4
{
max=Arr[i];
index[k]=i+1; // Оператор 5
k++;
}
k=0;
cout<<"\nMax="<
cout<<"\nИндексы: ";
while(index[k]>0) // Оператор 6
{
cout<
k++;
}
cout<
return 0;
}
-
объяснить назначение операторов 1-6.
Использование указателей
-
набрать и отладить следующую программу:
int main(int argc, char* argv[])
{
int a=7, b=2;
int *ptr=NULL;
cout<<"\na="<
cout<<", b="<
ptr=&a;
cout<<"\nДействие: ptr=&a. Результат: ptr="
<
ptr=&b;
cout<<"\nДействие: ptr=&b. Результат: ptr="
<<ptr<<", *ptr="<<*ptr;
*ptr=137;
cout<<"\nДействие: *ptr=137. Результат: b="<<b;
ptr++;
*ptr=-105;
cout<<"\nДействие: ptr++, *ptr=-105. Результат: ptr="
<
return 0;
}
-
объяснить работу программы.
Занятие 7
Лабораторная работа № 7
Исследование массивов и указателей
Цель занятия:
-
Совершенствование навыков разработки программ в среде программирования MS Visual C++ -
Совершенствование навыков в использовании циклов для работы с массивами -
Совершенствование навыков в программировании с использованием указателей -
Исследование взаимосвязи между массивами и указателями -
Исследование способов создания динамических массивов
Время на выполнение работы: 2 часа
Программа исследований:
-
Исследование взаимосвязи между массивами и указателями -
Исследование способов создания динамических массивов
Подготовка к выполнению работы:
-
Изучить материал настоящего руководства и рекомендованную литературу (структура программы на языке высокого уровня, алфавит и элементарные конструкции языка Си, переменные и константы, стандартные типы данных, выражения и операции в языке Си, циклы, массивы и указатели). -
Практически освоить порядок работы на ПЭВМ в интегрированной среде разработки MS Visual C++.
Материалы для подготовки к занятию:
-
Конспект лекций -
[1] стр. 51-63
Содержание отчета:
-
Цели исследования. -
Программу работы. -
Листинги программ -
Результаты исследований -
Выводы по каждому пункту и общий вывод.
МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ
Массивы и указатели в Си тесно связаны и могут быть использованы почти эквивалентно. Имя массива можно понимать как константный указатель на первый элемент массива. Его отличие от обычного указателя только в том, что его нельзя модифицировать. Например, задан целочисленный массив A на 5 элементов:
int A[5]={-2,5,4,-17,3};
Посмотрим, как этот массив разместится в оперативной памяти (рисунок 7.1).
| . . . | |
A[4] | | |
| 0x0012FF90 | |
| ||
| ||
| | |
A[3] | | 0x0012FF8C |
| ||
| ||
| | |
A[2] | | 0x0012FF88 |
| ||
| ||
| | |
A[1] | | 0x0012FF84 |
| ||
| ||
| | |
A[0] | | 0x0012FF80 |
| ||
| ||
| . . . | |
const int *A | | 0х0012FF70 |
| ||
| ||
| ||
| . . . |
Рисунок 7.1 – Представление массива элементов в памяти ЭВМ
Обратите внимание, что под хранение значения каждого элемента массива выделяется четыре ячейки памяти, что видно из чередования адресов ячеек памяти через четыре байта. Это связано с тем, что тип этого массива – int. Причем, под хранение адреса начала массива выделяется тоже четыре байта, так как все указатели для 32-разрядных процессоров 4-х байтные.
Указатели можно использовать для выполнения любой операции, включая индексирование массива. Пусть вы сделали следующее объявление:
int b[5] = {1,2,3,4,5}, *Pt;
Тем самым вы объявили массив целых чисел b[5] и указатель на целое Pt. Поскольку имя массива является указателем на первый элемент массива, вы можете задать указателю Pt адрес первого элемента массива b с помощью оператора Pt = b;
Это эквивалентно присваиванию адреса первого элемента массива следующим образом: Pt = &b[0];
Теперь можно сослаться на элемент массива b[3] с помощью выражения *(Pt + 3).
Указатели можно индексировать точно так же, как и массивы. Например, выражение Pt[3] ссылается на элемент массива b[3].
Или, например, определение int a[5]; задает массив из пяти элементов а[0], a[1], a[2], a[3], a[4]. Если объект *у определен как
int *у;
то оператор
у = &a[0];
присваивает переменной у адрес элемента а[0]. Если переменная у указывает на очередной элемент массива а, то y+1 указывает на следующий элемент, причем здесь выполняется соответствующее масштабирование для приращения адреса с учетом длины объекта (для типа int – 4 байта, long - 4 байта, (double - 8 байт и т.д.).
Так как само имя массива есть адрес его нулевого элемента, то оператор у = &a[0]; можно записать и в другом виде: у = а. Тогда элемент а[1] можно представить как *(а+1). С другой стороны, если у - указатель на массив a, то следующие две записи: a[i] и *(у+i) эквивалентны. Рассмотрим пример:
int main(int argc, char* argv[])
{
int a[5]={-5,0,34,12,-17};
cout<
cout<
cout<
cout<
cout<<endl;
return 0;
}
Так, операторы 1 и 2 выведут одно и то же значение – адрес элемента массива с индексом 2. А операторы 3 и 4 – одно и то же значение 34.
Между именем массива и соответствующим указателем есть одно важное различие. Указатель - это переменная и у = а или y++ - допустимые операции. Имя же массива - константа, поэтому конструкции вида a = y, a++ использовать нельзя, так как значение константы постоянно и не может быть изменено.
Указатели могут применяться как операнды в арифметических выражениях, выражениях присваивания и выражениях сравнения. Однако, не все операции, обычно используемые в этих выражениях, разрешены применительно к переменным указателям.
С указателями может выполняться ограниченное количество арифметических операций. Указатель можно увеличивать (++), уменьшать (--), складывать с указателем целые числа (+ или +=), вычитать из него целые числа (- или -=) или вычитать один указатель из другого.
Сложение указателей с целыми числами отличается от обычной арифметики. Прибавить к указателю 1 означает сдвинуть его на число байтов, содержащихся в переменной, на которую он указывал. Обычно подобные операции применяются к указателям на массивы. Если продолжить приведенный выше пример, в котором указателю Pt было присвоено значение b - указателя на первый элемент массива, то после выполнения оператора
Pt += 2;
Pt будет указывать на третий элемент массива b. Истинное же значение указателя Pt изменится на число байтов, занимаемых одним элементом массива, умноженное на 2. Например, если каждый элемент массива b занимает 2 байта, то значение Pt (т.е. адрес в памяти, на который указывает Pt) увеличится на 4.
Аналогичные правила действуют и при вычитании из указателя целого значения.
Переменные указатели можно вычитать один из другого. Например, если Pt указывает на первый элемент массива b, а указатель Pt1 - на третий, то результат выражения Pt1 - Pt будет равен 2 - разности индексов элементов, на которые указывают эти указатели. И так будет, несмотря на то, что адреса, содержащиеся в этих указателях, различаются на 4 (если элемент массива занимает 2 байта).
Арифметика указателей теряет всякий смысл, если она выполняется не над указателями на массив. Сравнение указателей операциями >, <, >=, <= также имеют смысл только для указателей на один и тот же массив. Однако, операции отношения == и != имеют смысл для любых указателей. При этом указатели равны, если они указывают на один и тот же адрес в памяти.
Динамические массивы создают с использованием операции new, при этом необходимо указать тип и размерность, например:
int a=10;
int *ptr=new int [a];
В этой строке создается переменная-указатель на int, в динамической памяти отводится непрерывная область, достаточная для размещения 10 элементов целого типа, и адрес ее начала записывается в указатель ptr. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.
Доступ к элементам динамического массива осуществляется точно так же, как к статическим, например, к элементу номер 5 можно обратиться как ptr[5] или *(ptr+5).
Альтернативный способ создания динамического массива – использование функции malloc библиотеки Си:
int a=10;
int *ptr=(int*)malloc(a*sizeof(int));
Операция преобразования типа, записанная перед обращением к функции malloc, требуется потому, что функция возвращает значение указателя типа void*.
Память, зарезервированная под динамический массив с помощью new[], должна освобождаться оператором delete [], а память, выделенная функцией malloc – посредством функции free, например:
delete [] ptr;
free(ptr);
Размерность массива в операции delete не указывается, но квадратные скобки обязательны.