Лекции.Орг


Поиск:




Категории:

Астрономия
Биология
География
Другие языки
Интернет
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Механика
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Транспорт
Физика
Философия
Финансы
Химия
Экология
Экономика
Электроника

 

 

 

 


Тема 7. Указатели и массивы




 

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

Рассмотрим следующие операторы:

int mas[N];

int *p;

Первый из них описывает целочисленный массив размерностью N, а второй – указатель на целочисленную переменную, который пока ни на что не указывает. Если выполнить присваивание

p = &mas[0];

то теперь указатель p будет содержать адрес начального элемента массива mas, или, другими словами, будет указывать на элемент mas[0] (точнее, на первый байта элемента mas[0], т.к. значение типа int хранится в 4-х байтах).

Как только такое присваивание выполнено, можно применять правила адресной арифметики над указателями, суть которых заключается в том, что, если указатель p указывает на некоторый элемент массива, то p, увеличенный на единицу, будет указывать на следующий элемент, а p, увеличенный на i, будет указывать на i -й элемент по отношению к элементу, на который он указывал вначале.

 

p ® mas[0]
p + 1 ® mas[1]
p + 2 ® mas[2]
... ...
p+N-1 ® mas[N-1]

 

Так как операция разадресации (*) позволяет получить значение элемента массива, на который указывает указатель, то можно записать следующее:

*p = mas[0];

*(p+1) = mas[1];

*(p+2) = mas[2];

...

*(p+N-1) = mas[N-1];

С другой стороны, имя массива в языке С есть не что иное, как адрес его начального элемента, поэтому присваивание

p = &mas[0];

можно заменить на

p = mas;

С учетом этого можно записать, что:

*mas = mas[0];

*(mas +1) = mas[1];

*(mas +2) = mas[2];

...

*(mas +N-1) = mas[N-1];

Между указателем p и именем массива mas существует одно существенное различие. Указатель p – это переменная, предназначенная для хранения любых адресов, поэтому можно, например, написать:

p++;

p += i;

Но имя массива mas не является переменной, это указатель-константа на начальный элемент массива, и записи типа:

mas++;

mas += i;

недопустимы.

Пример 1: вывести на экран значения всех элементов массива, введенных с клавиатуры.

#include <stdio.h>

void main()

{

const int N = 10;

int i, mas[N];

int *p;

p = mas;

//Ввод (один из вариантов)

for (i = 0; i < N; i++)

scanf(“%d”, p+i);

//Вывод: вариант 1

for (i = 0; i < N; i++)

printf(“%d ”, *(p+i));

//Вывод: вариант 2

for (i = 0; i < N; i++)

{

printf(“%d ”, *p);

p++;

}

//Вывод: вариант 3

p = mas;

for (i = 0; i < N; i++)

printf(“%d ”, *p++);

//Вывод: вариант 4

p = mas;

for (i = 0; i < N; p++, i++)

printf(“%d ”, *p);

//Вывод: вариант 5

for (i = 0; i < N; i++)

printf(“%d ”, *(mas+i));

//Вывод элементов массива в обратном порядке (один из вариантов)

p = &mas[N-1];

for (i = 0; i < N; i++)

printf(“%d ”, *(p-i));

}

 

Пример 2: вывести на экран все значения элементов двумерного массива.

#include <stdio.h>

void main()

{

const int N = 2, M = 4;

int mas[N ][M], *p, i, j;

p = &mas[0][0]; //можно p = mas[0] или p = *mas;

//Ввод (один из вариантов)

for (i = 0; i < N*M; i++)

scanf(“%d”, p+i);

//Вывод: вариант 1

for (i = 0; i < N*M; i++)

printf("%d%c", *p++, (i+1)%M? ' ': '\n');

//Вывод: вариант 2

p = mas[0];

for (i = 0; i < N; i++)

{

for (j = 0; j < M; j++)

printf("%d ", *(p+i*M+j));

printf("\n");

}

//Вывод: вариант 3

for (i = 0; i < N; i++)

{

for (j = 0; j < M; j++)

printf("%d ", *(mas[i]+j));

printf("\n");

}

//Вывод: вариант 4

for (i = 0; i < N; i++)

{

for (j = 0; j < M; j++)

printf("%d ", *(*(mas+i)+j));

printf("\n");

}

}

Эти варианты программы основаны на следующей схеме взаимосвязей между указателями и элементами двумерного массива:

p = mas[0] = *mas ® mas[0][0]
p+1 = mas[0]+1 = *mas+1 ® mas[0][1]
p+2 = mas[0]+2 = *mas+2 ® mas[0][2]
p+3 = mas[0]+3 = *mas+3 ® mas[0][3]
p+4 = mas[1] = *(mas+1) ® mas[1][0]
p+5 = mas[1]+1 = *(mas+1)+1 ® mas[1][1]
p+6 = mas[1]+2 = *(mas+1)+2 ® mas[1][2]
p+7 = mas[1]+3 = *(mas+1)+3 ® mas[1][3]

 

 

Тема 8. Строки символов

 

Строка представляет собой массив символов, заканчивающийся нуль-символом. Нуль-символ – это символ с кодом, равным нулю, что записывается в виде управляющей последовательности ‘\0’. По положению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковой константой:

char str[50] = “Vasia”; //первые элементы массива str: ‘V’, ‘a’, ‘s’, ‘i’, ‘a’, ‘\0’

Можно не указывать размерность массива:

char str[ ] = “Vasia”; //выделяется память под 6 элементов: 5 букв и нуль-символ

Возможна инициализация с помощью набора символьных констант:

char str[50] = {‘V’, ‘a’, ‘s’, ‘i’, ‘a’}; // первые элементы: 5 букв и нуль-символ

Если размерность не указывать, то к инициализаторам нужно обязательно добавить нуль-символ:

char str[ ] = {‘V’, ‘a’, ‘s’, ‘i’, ‘a’, ‘\0’}; //выделяется память под 6 элементов

 

Для ввода данных с клавиатуры в строку символов используются функции gets, scanf, getchar. Для вывода содержимого строки на экран используются функции puts, printf, putchar. Все эти функции описаны в заголовочном файле stdio.h:

#include <stdio.h>

void main()

{

const int N = 50;

char str1[N], str2[N], str3[N], str4[N];

int i;

//ввод: вариант 1

gets(str1); //в str1 вводится строка символов. Окончание ввода – по нажатию //клавиши ВВОД (Enter)

//ввод: вариант 2

scanf("%s", str2); //в str2 вводится строка в которой недопустимы пробельные

//символы. Окончание ввода – по нажатию клавиши ВВОД или клавиши ПРОБЕЛ.

fflush(stdin); //очистка потока ввода stdin

 

//ввод: вариант 3

scanf("%10c", str3); //в str3 вводится 10 символов (завершение ввода клавишей ВВОД)

str3[10] = ‘\0’; //с помощью операции присваивания добавляется нуль-символ

fflush(stdin); //очистка stdin, если с помощью scanf было введено > 10 символов

 

//ввод: вариант 4

i = 0;

while(1)

{

str4[i] = getchar();

if (str4[i] == ‘\n’) break;

i++;

}

str4[i] = ‘\0’; //последний введенный символ (‘\n’) заменяется на ‘\0’

// вывод: вариант 1

puts(str1); //вывод str1 и переход на начало новой строки

// вывод: вариант 2

printf("%s\n", str2); //вывод str2 и переход на начало новой строки

// вывод: вариант 3

i = 0;

while(str3[i]!= ‘\0’) //можно записать короче: while(str3[i])

{

printf("%c", str3[i]); //можно совместить 2 строки: printf("%c", str3[i++]);

i++;

}

printf("\n");

 

// вывод: вариант 4

i = 0;

while(str4[i]!= ‘\0’) //можно записать короче: while(str4[i])

{

putchar(str4[i]); //можно совместить 2 строки: putchar(str4[i++]);

i++;

}

}

 

Пример программы копирования одной строки в другую:

#include <stdio.h>

void main()

{

const int N = 50;

char str1[N], str2[N] = “\”Zubilo\” – chempion”;

char *p1 = str1, * p2 = str2;

 

//вариант 1

int i = 0;

do

str1[i] = str2[i];

while(str1[i++]!= ‘\0’); //можно записать короче: while(str1[i++]);

 

//вариант 2

int i = 0;

while(1);

{

str1[i] = str2[i];

if (str1[i++] == ‘\0’) break; //можно записать короче: if (!str1[i++]) break;

}

 

//вариант 3

int i = 0;

while((str1[i] = str2[i])!= ‘\0’) //можно записать короче: while(str1[i] = str2[i])

i++;

 

//вариант 4

while((*p1 = *p2)!= ‘\0’)

{

p1++;

p2++;

}

 

//вариант 5

while((*p1++ = *p2++)!= ‘\0’);

 

//вариант 6

while(*p1++ = *p2++);

//вывод результата копирования на экран

puts(str1); //можно и так: p1 = str1; puts( p1 );

}

Тема 9. Структуры

 

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

Сначала структуру нужно объявить (декларировать). Объявление структуры начинается с ключевого слова struct и содержит список описаний, заключенный в фигурные скобки, например:

struct point

{

int x, y;

char color[20];

};

где point – это имя структуры.

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

Объявив структуру, мы, тем самым, создаем новый, составной тип данных, который далее можно использовать обычным образом, например, запись:

struct point A, B, C;

с точки зрения синтаксиса аналогична записи:

int a, b, c;

и описывает 3 переменные структурного типа с именами A, B, C. Одновременно можно выполнить инициализацию полей структур:

struct point A = {50, 50, “Red”}, B, C = {100, 100, “Green”};

Можно совместить объявление структуры с описанием переменных структурного типа:

struct point

{

int x, y;

chat color[20];

} A = {50, 50, “Red”}, B, C;

Более того, если кроме A, B и C нам больше не нужны переменные этого типа, то имя point можно опустить.

Доступ к отдельному полю структуры выполняется посредством конструкции вида:

ИмяСтруктуры.ИмяПоля

Например, введем с клавиатуры данные в поля структуры B, а затем выведем эти данные на экран:

scanf("%d%d%s", &B.x, &B.y, &B.color);

printf("(%d, %d) %s", B.x, B.y, B.color);

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

УказательНаСтруктуру->ИмяПоля

Например:

struct point *p;

p = &B;

scanf("%d%d%s", &p->x, &p->y, &p->color);

printf("(%d, %d) %s\n", p->x, p->y, p->color);

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

struct rect //задает прямоугольник, стороны которого ||-ы осям координат

{ // pt1 и pt2 – угловые точки, лежащие на диагонали прямоугольника

struct point pt1;

struct point pt2;

} R;

R.pt1.x = R.pt1.y = 50;

R.pt1.color = “Red”;

R.pt2.x = R.pt2.y = 100;

R.pt2.color = “Green”;

Массивы структур

В практике программирования большое распространение получили массивы структур, т.е. массивы, элементами которых являются переменные структурного типа. Например, оператор

struct point mas[3];

описывает массив, состоящий из трех элементов типа struct point. Описание можно дополнить инициализацией:

struct point mas[ ] = {{10, 10, “Red”}, {20, 20, “Green”}, {30, 30, “Blue”}};

Если инициализаторы – простые константы или строки символов, и все они имеются в наличии, во внутренних скобках нет необходимости:

struct point mas[ ] = {10, 10, “Red”, 20, 20, “Green”, 30, 30, “Blue”};

Ввод данных в массив структур с клавиатуры и вывод их на экран можно выполнить следующим образом:

int i;

for(i=0; i<3; i++)

{

scanf(“%d%d%s”, &mas[i].x, &mas[i].y, &mas[i].color);

printf("(%d, %d) %s", mas[i].x, mas[i].y, mas[i].color);

}

То же самое с использованием механизма указателей:

struct point *p;

p = mas;

for(i=0; i<3; i++)

{

scanf("%d%d%s", &(p+i)->x, &(p+i)->y, &(p+i)->color);

printf("(%d, %d) %s", (p+i)->x, (p+i)->y, (p+i)->color);

}

Битовые поля

Битовые поля – это особый вид полей структуры. Они используются для плотной упаковки данных, например, флажков типа «да/нет». Это объясняется тем, что минимальная адресуемая ячейка оперативной памяти имеет длину один байт (8 бит), в то время как для хранения флажка достаточно одного бита. При описании битового поля после имени через двоеточие указывается длина поля в битах. Битовые поля могут быть любого целого типа и обычно используется тип unsigned int (сокращенно unsigned). Например:

struct options

{

unsigned bold: 1;

unsigned italic: 1;

unsigned underline: 1;

unsigned background: 4;

} opt;

Под переменную opt структурного типа options будет отведена память размером 1 байт (если не использовать битовые поля, то структурная переменная будет занимать минимум 4 байта).

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

Объединения

Объединение представляет собой частный случай структуры, все поля которой располагаются по одному и тому же адресу. Формат описания такой же, как и у структуры, только вместо ключевого слова struct используется слово union. Длина объединения равна наибольшей из длин его полей. В каждый момент времени объединение хранит только одно значение, и ответственность за его правильное использование лежит на программисте.

Объединения применяют для экономии памяти в тех случаях, когда известно, что больше одного поля одновременно не требуется. Пример:

#include <stdio.h>

void main()

{

int payType; //тип платежа

union payment

{

char card[25]; //оплата по карте

int check; //оплата чеком

} info;

// ... присваивание значений переменным payType и info

switch (payType)

{

case 0: printf("Оплата по карте: %s", info.card); break;

case 1: printf("Оплата чеком: %d", info.check); break;

}

}

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

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

#include <stdio.h>

void main()

{

struct

{

int payType;

union

{

char card[25];

int check;

};

} info;

// ... присваивание значения переменной info

switch (info.payType)

{

case 0: printf("Оплата по карте: %s", info.card); break;

case 1: printf("Оплата чеком: %d", info.check); break;

}

}

Перечисления

Перечисление – это список именованных целых констант. Для описания перечисления используется следующий формат:

enum [имяПеречисления] {списокИменованныхКонстант};

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

enum boolean {NO, YES}; // NO = 0, YES = 1

boolean bool; //описали переменную bool типа boolean

bool = YES; // bool = 1

 

enum months {JAN=1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};

months mon; // FEB=2, MAR=3, APR=4,..., DEC=12

mon = DEC; // mon = 12

enum controls {BELL=’\a’, BACKSPACE=’\b’, NEWLINE=’\n’, TAB=’\t’, VTAB=’\v’};

char ch;

while(1)

{

if ((ch = getchar()) == NEWLINE) break;

...

}

 

Пример формирования на экране меню

#include <stdio.h>

void main()

{

enum menu {READ=1, WRITE, APPEND, EXIT};

int num;

do

{

printf("1. Читать\n");

printf("2. Писать\n");

printf("3. Дописать в конец\n");

printf("4. Выйти\n");

printf("\nВаш выбор?\n");

scanf("%d", &num);

if (num == EXIT) break;

switch (num)

{

case READ:...; break;

case WRITE:...; break;

case APPEND:...; break;

default: printf("Неверный ввод\n"); break;

}

} while(1);

}

 

Переименование типов

 

Для того, чтобы сделать программу более ясной, можно дать типу новое имя с помощью ключевого слова typedef:

typedef Тип НовоеИмяТипа[Размерность];

Размерность может отсутствовать. Примеры:

typedef unsigned int uint;

typedef char message[100];

typedef char * string;

Введенные таким образом имена можно использовать таким же образом, как и имена простых типов данных:

uint i, j; //две переменные типа unsigned int

message str[10]; //массив из 10 строк по 100 символов в каждой строке

string p = str; //инициализированный указатель на char

Кроме задания типам с длинными описаниями более коротких имен, typedef используется для облегчения переносимости программ: если машинно-зависимые типы объявить с помощью операторов typedef, при переносе программы потребуется внести изменения только в эти операторы.

 

Тема 10. Функции

 

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

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

Любая функция должна быть определена. Определение функции состоит из заголовка и тела:

тип имя ([список параметров]) //заголовок функции

{

тело функции

}

Заголовок функции задает ее имя, тип возвращаемого значения (результата) и список передаваемых параметров. Тип возвращаемого функцией значения может быть любым, кроме массива и функции (но может быть указателем на массив или функцию). Если функция не должна возвращать значение, указывается тип void. Список параметров определяет величины, которые требуется передать в функцию при ее вызове. Элементы списка параметров разделяются запятыми. Для каждого параметра, передаваемого в функцию, указывается его тип и имя.

Тело функции представляет собой последовательность описаний и операторов в фигурных скобках. Пример:

int sumFunc(int n, int m, int p)

{

int result; //эту и 2 последующие строки можно

result = n + m + p; //заменить одной строкой:

return result; // return = n + m + p;

}

Эта простейшая функция, имя которой sumFunc, предназначена для нахождения суммы трех целых чисел n, m и p, которые передаются ей в качестве параметров. Возврат из функции подсчитанной суммы выполняется с помощью оператора, имеющего следующий формат:

return [выражение];

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

void main()

{

int a = 10, b = 20, c = 30, res;

puts("a = 10, b = 20, c = 30");

res = sumFunc(a, b, c); //эту и следующую строки можно совместить:

printf("a + b + c = %d", res); // printf("a + b + c = %d", sumFunc(a, b, c));

}

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

res = sumFunc(a, b, c);

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

#include <stdio.h>

int sumFunc(int n, int m, int p); //прототип функции sumFunc

void main() //определение функции main

{

int a = 10, b = 20, c = 30;

puts("a = 10, b = 20, c = 30");

printf("a + b + c = %d", sumFunc(a, b, c)); //вызов функции sumFunc

}

int sumFunc(int n, int m, int p) //определение функции sumFunc

{

return n + m + p;

}

В прототипе функции имена параметров можно не указывать, достаточно лишь указать их тип:

int sumFunc(int, int, int);

Глобальные, локальные и статические переменные

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

#include <stdio.h>

void func(int n)

{

static int a = 0;

a += n;

printf("%d ", a);

}

void main()

{

func(5);

func(5);

func(5);

}

Результат работы программы:

5 10 15

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

Параметры функции

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

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

Существует два способа передачи параметров в функцию: по значению и по адресу.

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

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

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

Пример функции swap, выполняющей обмен значениями между двумя переменными a и b. Если использовать вызов

swap(a, b);

функции, имеющей прототип

void swap(int n, int m);

то переменные a и b сохранят свои первоначальные значения, поскольку swap получает лишь копии значений этих переменных.

Чтобы получить желаемый эффект, надо вызывающей программе передать указатели на те значения, которые должны быть изменены:

swap(&a, & b);

В самой функции swap параметры должны быть описаны как указатели, при этом доступ к значениям параметров будет осуществляться через них косвенно:

void swap(int *pn, int *pm)

{

int temp;

temp = *pn;

*pn = *pm;

*pm = temp;

}





Поделиться с друзьями:


Дата добавления: 2016-04-03; Мы поможем в написании ваших работ!; просмотров: 454 | Нарушение авторских прав


Поиск на сайте:

Лучшие изречения:

Самообман может довести до саморазрушения. © Неизвестно
==> читать все изречения...

2538 - | 2391 -


© 2015-2025 lektsii.org - Контакты - Последнее добавление

Ген: 0.016 с.