Лекции по О..

advertisement
1
Алфавит языка.
Алфавит (или множество литер) языка программирования C основывается на множестве
символов таблицы кодов ASCII. Алфавит C включает:

строчные и прописные буквы латинского алфавита (мы их будем называть буквами),

цифры от 0 до 9 (назовём их буквами-цифрами),

символ '_' (подчерк - также считается буквой),

набор
специальных
символов:
"{},|[]+-%/\;':?<>=!&#~^.*

прочие символы.
Алфавит C служит для построения слов, которые в C называются лексемами. Различают пять
типов лексем:

идентификаторы,

ключевые слова,

знаки (символы) операций,

литералы,

разделители.
Почти все типы лексем (кроме ключевых слов и идентификаторов) имеют собственные
правила словообразования, включая собственные подмножества алфавита.
Лексемы разделяются разделителями. Этой же цели служит множество пробельных символов,
к числу которых относятся пробел, символы горизонтальной и вертикальной табуляции, символ
новой строки, перевода формата и комментарии.
Идентификаторы.
Идентификаторы в языке программирования используются для обозначения имен переменных,
функций и меток, применяемых в программировании. Идентификатором может быть произвольная
последовательность латинских букв (прописных и строчных), цифр и символа подчеркивания,
которая начинается с буквы или символа подчеркивания. В языке С идентификатор может состоять
из произвольного числа символов, однако два идентификатора считаются различными, если у них
различаются первые 32 символа.
Идентификатором называется последовательность цифр и букв, а также специальных
символов, при условии, что первой стоит буква или специальный символ. Для образования
идентификаторов могут быть использованы строчные или прописные буквы латинского алфавита. В
качестве специального символа может использоваться символ подчеркивание (_). Два
идентификатора для образования которых используются совпадающие строчные и прописные буквы,
считаются различными. Например:
int _count;
int _COUNT;
float 123count;//не допустимый идентификатор
float _123count;
float *count12;
В языке С некоторые идентификаторы употребляются как служебные слова, которые имеет
специальное значение для компилятора. Их употребление строго определено, и эти слова не могут
использоваться иначе. Ключевыми словами являются:
auto
double
int
struct break
else
long
switch
register tupedef
char extern return void
case
float
unsigned default
for
signed union
do
if
sizeof
volatile continue
enum short
while
Примеры объявления данных:
int а, b;
unsigned i, j;
float k;
2
Здесь объявлены переменные: целые а и b , беззнаковые целые i и j , вещественное число
одинарной точности k.
Пример 1:
#include<stdio.h>
void main(void)
{
int year;
year=2010;
printf("Сейчас %d год\n",year);
}
Пример 2:
#include<stdio.h>
#include <math.h>
void main(void)
{//вычисление площади прямоугольника
int a;//сторона квадрата
double square;//площадь
printf("Введите значение стороны квадрата\n");
scanf("%d",&a);
square=pow((double)a,(double)2);
printf("Площадь равна %lf\n",square);
}
Типы данных: целый, вещественный, символьный; их представление в оперативной
памяти.
Важное отличие языка С от других языков (PL1, FORTRAN, и др.) является отсутствие
принципа умолчания, что приводит к необходимости объявления всех переменных используемых в
программе явно вместе с указанием соответствующих им типов.
Объявления переменной имеет следующий формат:
[спецафикатор-класа-памяти] спецификатор-типа
описатель [=инициатор] [,описатель [= инициатор] ]...
Описатель - идентификатор простой переменной либо более сложная конструкция с
квадратными скобками, круглыми скобками или звездочкой (набором звездочек).
Спецификатор типа - одно или несколько ключевых слов, определяющие тип объявляемой
переменной. В языке СИ имеется стандартный набор типов данных, используя который можно
сконструировать новые (уникальные) типы данных.
Инициатор - задает начальное значение или список начальных значений, которые (которое)
присваивается переменной при объявлении.
Спецификатор класса памяти - определяется одним из четырех ключевых слов языка С: auto,
extern, register, static, и указывает,каким образом будет распределяться память под объявляемую
переменную, с одной стороны, а с другой, область видимости этой переменной, т.е., из каких частей
программы можно к ней обратиться.
3
Пример
int day;
int year=2010;
register float square;
1.2.1 Категории типов данных
Ключевые слова для определения основных типов данных
Целые типы :
Плавающие типы:
char
float
int
double
short
long double
long
signed
unsigned
как немодифицируемая. Это
const к спецификатору-типа. Объекты с типом const
Переменная любого типа может быть объявлена
достигается добавлением ключевого слова
представляют собой данные используемые только для чтения, т.е. этой переменной не может быть
присвоено новое значение. Отметим, что если после слова const отсутствует спецификатор-типа, то
подразумевается спецификатор типа int. Если ключевое слово const стоит перед объявлением
составных типов (массив, структура, смесь, перечисление), то это приводит к тому, что каждый
элемент также должен являться немодифицируемым, т.е. значение ему может быть присвоено только
один раз.
Примеры:
const double A=2.128E-2;
const B=286; (подразумевается const int B=286)
1.2.2. Целый тип данных
Для определения данных целого типа используются различные ключевые слова, которые
определяют диапазон значений и размер области памяти, выделяемой под переменные.
Название
типа
Тип значения
переменной
Диапазон значений
Необходимая память, в
битах
Примечания
1
2
3
4
5
16
Задает значения, к
которым относятся все
целые числа, например 6, 0, 28 и т.д.
int
Целый
short
Короткий и
Целый
0..65535
16
Объекты short не могут
быть больше, чем int. В
Borland C int и short
равной длины
long
Длинный и
214748364…2147483647
32
Используется, когда
-32768 ...32767
4
Целый
Символьный
char
unsigned
диапазон значений
выходит за пределы
диапазона типа int
Символы кодовой таблицы ASCII
(0...255)
Задает значения, которые
представляют различные
символы, например w, у,
ф, 4, !, ., * и т. д. Этот тип
часто используется как
наименьшее беззнаковое
целое значение
8
Модификатор типов char,
short, int, long,
определяющий их
беззнаковыми.
Беззнаковый
Размеры данных (почти то же самое)
Тип
char
Размер памяти в байтах
1
Диапазон значений
от -128 до 127
int
2
short
2
от -32768 до 32767
long
4
от -2 147 483 648 до 2 147 483 647
unsigned shar
1
oт 0 до 255
unsigned int
2
unsigned short
2
от 0 до 65535
unsigned long
4
от 0 до 4 294 967 295
Отметим, что ключевые слова signed и unsigned необязательны. Они указывают, как
интерпретируется нулевой бит объявляемой переменной, т.е., если указано ключевое слово unsigned,
то нулевой бит интерпретируется как часть числа, в противном случае нулевой бит интерпретируется
как знаковый. В случае отсутствия ключевого слова unsigned целая переменная считается знаковой. В
том случае, если спецификатор типа состоит из ключевого типа signed или unsigned и далее следует
идентификатор переменной, то она будет рассматриваться как переменная типа int. Например:
unsigned int n;
unsigned int b;
int c;
(подразумевается
signed
int c
);
unsigned d;
(подразумевается
unsigned int d
);
signed f;
(подразумевается
signed
).
int f
Отметим, что модификатор-типа char используется для представления символа (из массива
представление символов) или для объявления строковых литералов. Значением объекта типа char
является код (размером 1 байт), соответствующий представляемому символу. Для представления
символов русского алфавита, модификатор типа идентификатора данных имеет вид unsigned char, так
как коды русских букв превышают величину 127.
Следует сделать следующее замечание: в языке СИ не определено представление в памяти и
диапазон значений для идентификаторов с модификаторами-типа int и unsigned int. Размер памяти
для переменной с модификатором типа signed int определяется длиной машинного слова, которое
5
имеет различный размер на разных машинах. Так, на 16-ти разрядных машинах размер слова равен 2м байтам, на 32-х разрядных машинах соответственно 4-м байтам, т.е. тип int эквивалентен типам
short int, или long int в зависимости от архитектуры используемой ПЭВМ. Таким образом, одна и та
же программа может правильно работать на одном компьютере и неправильно на другом. Для
определения длины памяти занимаемой переменной можно использовать операцию sizeof языка СИ,
возвращающую значение длины указанного модификатора-типа.
Например:
a = sizeof(int);
b = sizeof(long int);
c = sizeof(unsigned long);
d = sizeof(short);
Отметим также, что восьмеричные и шестнадцатеричные константы также могут иметь
модификатор unsigned. Это достигается указанием префикса u или U после константы, константа без
этого префикса считается знаковой.
Например:
0xA8C (int signed );
01786l (long signed );
0xF7u (int unsigned );
1.2.3. Данные плавающего типа
Для переменных, представляющих число с плавающей точкой используются следующие
модификаторы-типа : float, double, long double (в некоторых реализациях языка long double СИ
отсутствует).
Величина с модификатором-типа float занимает 4 байта. Из них 1 байт отводится для знака, 8
бит для избыточной экспоненты и 23 бита для мантиссы. Отметим, что старший бит мантиссы всегда
равен 1, поэтому он не заполняется, в связи с этим диапазон значений переменной с плавающей
точкой приблизительно равен от 3.14E-38 до 3.14E+38.
Величина типа double занимает 8 байт в памяти. Ее формат аналогичен формату float. Биты
памяти распределяются следующим образом: 1 бит для знака, 11 бит для экспоненты и 52 бита для
мантиссы. С учетом опущенного старшего бита мантиссы диапазон значений равен от 1.7E-308 до
1.7E+308.
Название
типа
Тип значения
переменной
Диапазон значений
Необходимая память, в
битах
Примечания
float
Вещественный
±3.4е-38… ±3.4е+38
32
Определяет
вещественные числа,
дробная часть которых
отделяется точкой
(например, -5.27, 0.0,
31.69 и т.д.).
Вещественные числа
могут записываться в
экспоненциальной
форме. Например: 1.58е+2 (что равно -1,58
* 102), 3.61е-4 (что равно
3,61 *10-4).
double
двойная точность
±1.7е-308... ±1.7е+308
64
Определяет
вещественные
6
переменные двойной
точности, занимающие в
два раза больше памяти,
чем переменная типа
float
Пример:
float a=3.4;
double square=12.6;
float b;
Константы
Константами называются перечисление величин в программе. В языке СИ разделяют четыре
типа констант: целые константы, константы с плавающей запятой, символьные константы и
строковыми литералы.
Целая константа: это десятичное, восьмеричное или шестнадцатеричное число, которое
представляет целую величину в одной из следующих форм: десятичной, восьмеричной или
шестнадцатеричной.
Десятичная константа состоит из одной или нескольких десятичных цифр, причем первая
цифра не должна быть нулем (в противном случае число будет воспринято как восьмеричное).
Восьмеричная константа состоит из обязательного нуля и одной или нескольких
восьмеричных цифр (среди цифр должны отсутствовать восьмерка и девятка, так как эти цифры не
входят в восьмеричную систему счисления).
Шестнадцатеричная константа начинается с обязательной последовательности 0х или 0Х и
содержит одну или несколько шестнадцатеричных цифр (цифры представляющие собой набор цифр
шеснадцатеричной системы счисления: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)
Примеры целых констант:
Десятичная
Восьмеричная
Шестнадцатеричная
константа
константа
константа
16
020
0x10
127
0117
0x2B
240
0360
0XF0
Если требуется сформировать отрицательную целую константу, то используют знак "-" перед
записью константы (который будет называться унарным минусом). Например: -0x2A, -088, -16 .
Каждой целой константе присваивается тип, определяющий преобразования, которые должны
быть выполнены, если константа используется в выражениях. Тип константы определяется
следующим образом:
- десятичные константы рассматриваются как величины со знаком, и им присваивается тип int
(целая) или long (длинная целая) в соответствии со значением константы. Если константа меньше
32768, то ей присваивается тип int в противном случае long.
- восьмеричным и шестнадцатеричным константам присваивается тип int, unsigned int
(беззнаковая целая), long или unsigned long в зависимости от значения константы согласно табл 5.
Приоритет и ассоциативность операций
Пример выражения:
Y=Z*A+pow(X, (double)2);
где
X, Y, Z, A, 2 – операнды;
=, +, pow, (double)<приведение типа> – операции над операндами
Каждая операция имеет вполне определенный приоритет и ассоциативность. Это
позволяет без лишних скобок однозначно понимать, к какой операции относится
7
операнд, стоящий между двумя соседними операциями. Например, в выражении
a+b*c
операнд b относится к операции умножения, так как у нее приоритет выше, чем у
сложения. Поэтому эквивалентное выражение со скобками будет таким:
a + (b * c)
Операции одинакового приоритета либо левоассоциативны, либо правоассоциативны.
Это значит, что операнд, находящийся между двумя такими операциями, относится
либо к левой операции, либо (в случае правой ассоциативности) к правой. В языке Си
операции сложения и вычитания имеют одинаковый приоритет и левую
ассоциативность. Поэтому в выражении
a−b+c
операнд b относится к левой операции, т.е. к «минусу», а не к «плюсу», и это
выражение эквивалентно следующему выражению со скобками:
((a − b) + c)
Таким образом, левоассоциативные операции группируются с помощью скобок слева
направо; правая ассоциативность задаёт способ группировки операций справа налево.
Примером правоассоциативной операции в языке Си является операция присваивания.
Выражение с операциями присваивания
a=b=c
эквивалентно выражению
a=(b=c)
Приоритет и ассоциативность позволяют установить границы подвыражений,
являющихся операндами, и заключить их в скобки. Например, если в выражении
a*(b − c) + 2 + a*4*5 − d
каждый нетривиальный операнд заключить в скобки, получим:
(((a*(b−c))+2)+((a*4)*5))−d
Каждая операция вместе со своими операндами выделена в этом примере отдельным
цветом.
Порядок выполнения операций зависит не только от расстановки скобок, но и от
порядка вычисления операндов (об этом ниже в п. 4).
В языке Си все постфиксные операции имеют одинаковый приоритет — самый
высокий по отношению к другим операциям, — и естественным образом группируются
слева направо. Приоритет префиксных операций ниже, чем у постфиксных, но выше,
чем у инфиксных. Префиксные операции естественным образом группируются справа
налево. Например, в выражении
&*p[i]++
операции & и * — префиксные, [] и ++ — постфиксные. Поэтому скобки будут
расставлены так:
&(*((p[i])++))
Инфиксные операции разбиты на несколько групп, имеющих разные приоритеты и
ассоциативность (см. таблицу в п. 6).
Логические операции
В Си используются следующие обозначения для логических операций:
|| логическое "или" (логическое сложение)
&& логическое "и" (логическое умножение)
! логическое "не" (логическое отрицание)
Логические константы "истина" и "ложь" обозначаются через true и false (это ключевые слова
языка). Примеры логических выражений:
bool a, b, c, d;
int x, y;
a = b || c;
d = b && c;
// логическое "или"
// логическое "и"
8
a = !b;
// логическое "не"
a = (x == y);
// сравнение в правой части
a = false;
// ложь
b = true;
// истина
c = (x > 0 && y != 1); // c истинно, когда
// оба сравнения истинны
Самый высокий приоритет у операции логического отрицания, затем следует логическое
умножение, самый низкий приоритет у логического сложения.
Унарные и бинарные операции
По количеству операндов, участвующих в операции, операции подразделяются на унарные,
бинарные и тернарные.
В языке Си имеются следующие унарные операции:
- арифметическое отрицание (отрицание и дополнение);
~ побитовое логическое отрицание (дополнение);
! логическое отрицание;
* разадресация (косвенная адресация);
& вычисление адреса;
+ унарный плюс;
++ увеличение (инкремент);
-- уменьшение (декремент);
sizeof размер .
Унарные операции выполняются справа налево.
Операции увеличения и уменьшения увеличивают или уменьшают значение операнда на
единицу и могут быть записаны как справа так и слева от операнда. Если знак операции записан
перед операндом (префиксная форма), то изменение операнда происходит до его использования в
выражении. Если знак операции записан после операнда (постфиксная форма), то операнд вначале
используется в выражении, а затем происходит его изменение.
Пример
++A
A++
префиксная форма
постфиксная форма
Пример
#include<stdio.h>
#include <math.h>
void main(void)
{//демонстрирует различия в выражениях
//a++;++a;
int a=20;
int b=40;
a++;
++b;
printf("a = %d b = %d \n",a,b);
printf("a = %d b = %d \n",a++,++b);
}
Результат
9
В отличие от унарных, бинарные операции, список которых приведен в табл., выполняются
слева направо.
Таблица
Знак
Операция
Группа операций
операции
*
Умножение
/
Деление
%
Остаток от деления
+
Сложение
-
Вычитание
<<
Сдвиг влево
>>
Сдвиг вправо
<
Меньше
<=
Меньше или равно
>=
Больше или равно
==
Равно
!=
Не равно
&
Поразрядное И
|
Поразрядное ИЛИ
^
Мультипликативные
Аддитивные
Операции сдвига
Поразрядное исключающее
Логическое И
||
Логическое ИЛИ
=
*=
/=
Поразрядные операции
ИЛИ
&&
,
Операции отношения
Логические операции
Последовательное
вычисление
Последовательного вычисления
Присваивание
Умножение
присваиванием
с
Деление с присваиванием
%=
Остаток
присваиванием
от
-=
Вычитание
присваиванием
деления
с
с
+=
Сложение с присваиванием
<<=
Сдвиг
влево
с
Операции присваивания
10
присваиванием
>>=
Сдвиг
присваиванием
вправо
&=
Поразрядное
присваиванием
И
с
|=
Поразрядное
присваиванием
ИЛИ
с
Поразрядное исключающее
ИЛИ с присваиванием
Левый операнд операции присваивания должен быть выражением, ссылающимся на область
памяти (но не объектом объявленным с ключевым словом const), такие выражения называются
леводопустимыми к ним относятся:
- идентификаторы данных целого и плавающего типов, типов указателя, структуры,
объединения;
- индексные выражения, исключая выражения имеющие тип массива или функции;
- выражения выбора элемента (->) и (.), если выбранный элемент является леводопустимым;
- выражения унарной операции разадресации (*), за исключением выражений, ссылающихся
на массив или функцию;
- выражение приведения типа если результирующий тип не превышает размера
первоначального типа.
При записи выражений следует помнить, что символы (*), (&), (!), (+) могут\ обозначать
унарную или бинарную операцию.
^=
Преобразование типов
При выполнении операций происходят неявные преобразования типов в следующих случаях:
- при выполнении операций осуществляются обычные арифметические преобразования
(которые были рассмотрены выше);
- при выполнении операций присваивания, если значение одного типа присваивается
переменной другого типа;
- при передаче аргументов функции.
Кроме того, в Си есть возможность явного приведения значения одного типа к другому.
В операциях присваивания тип значения, которое присваивается, преобразуется к типу
переменной, получающей это значение. Допускается преобразования целых и плавающих типов,
даже если такое преобразование ведет к потере информации.
Преобразование целых типов со знаком. Целое со знаком преобразуется к более короткому
целому со знаком, посредством усечения старших битов. Целая со знаком преобразуется к более
длинному целому со знаком, путем размножения знака. При преобразовании целого со знаком к
целому без знака, целое со знаком преобразуется к размеру целого без знака и результат
рассматривается как значение без знака.
Преобразование целого со знаком к плавающему типу происходит без потери] информации, за
исключением случая преобразования значения типа long int или unsigned long int к типу float, когда
точность часто может быть потеряна.
Преобразование целых типов без знака. Целое без знака преобразуется к более короткому
целому без знака или со знаком путем усечения старших битов. Целое без знака преобразуется к
более длинному целому без знака или со знаком путем дополнения нулей слева.
Когда целое без знака преобразуется к целому со знаком того же размера, битовое
представление не изменяется. Поэтому значение, которое оно представляет, изменяется, если
знаковый бит установлен (равен 1), т.е. когда исходное целое без знака больше чем максимальное
положительное целое со знаком, такой же длины.
Целые значения без знака преобразуются к плавающему типу, путем преобразования целого
без знака к значению типа signed long, а затем значение signed long преобразуется в плавающий тип.
Преобразования из unsigned long к типу float, double или long double производятся с потерей
11
информации, если преобразуемое значение больше, чем максимальное положительное значение,
которое может быть представлено для типа long.
Преобразования плавающих типов. Величины типа float преобразуются к типу double без
изменения значения. Величины double и long double преобразуются к float c некоторой потерей
точности. Если значение слишком велико для float, то происходит потеря значимости, о чем
сообщается во время выполнения.
При преобразовании величины с плавающей точкой к целым типам она сначала преобразуется
к типу long (дробная часть плавающей величины при этом отбрасывается), а затем величина типа
long преобразуется к требуемому целому типу. Если значение слишком велико для long, то результат
преобразования не определен.
Преобразования из float, double или long double к типу unsigned long производится с потерей
точности, если преобразуемое значение больше, чем максимально возможное положительное
значение, представленное типом long.
Преобразование типов указателя. Указатель на величину одного типа может быть
преобразован к указателю на величину другого типа. Однако результат может быть не определен изза отличий в требованиях к выравниванию и размерах для различных типов.
Указатель на тип void может быть преобразован к указателю на любой тип, и указатель на
любой тип может быть преобразован к указателю на тип void без ограничений. Значение указателя
может быть преобразовано к целой величине. Метод преобразования зависит от размера указателя и
размера целого типа следующим образом:
- если размер указателя меньше размера целого типа или равен ему, то указатель
преобразуется точно так же, как целое без знака;
- если указатель больше, чем размер целого типа, то указатель сначала преобразуется к
указателю с тем же размером, что и целый тип, и затем преобразуется к целому типу.
Целый тип может быть преобразован к адресному типу по следующим правилам:
- если целый тип того же размера, что и указатель, то целая величина просто рассматривается
как указатель (целое без знака);
- если размер целого типа отличен от размера указателя, то целый тип сначала преобразуется к
размеру указателя (используются способы преобразования, описанные выше), а затем полученное
значение трактуется как указатель.
Преобразования при вызове функции. Преобразования, выполняемые над аргументами при
вызове функции, зависят от того, был ли задан прототип функции (объявление "вперед") со списком
объявлений типов аргументов.
Если задан прототип функции и он включает объявление типов аргументов, то над
аргументами в вызове функции выполняются только обычные арифметические преобразования.
Эти преобразования выполняются независимо для каждого аргумента. Величины типа float
преобразуются к double, величины типа char и short преобразуются к int, величины типов unsigned
char и unsigned short преобразуются к unsigned int. Могут быть также выполнены неявные
преобразования переменных типа указатель. Задавая прототипы функций, можно переопределить эти
неявные преобразования и позволить компилятору выполнить контроль типов.
Преобразования при приведении типов. Явное преобразование типов может быть
осуществлено посредством операции приведения типов, которая имеет формат:
( имя-типа ) операнд.
В приведенной записи имя-типа задает тип, к которому должен быть преобразован операнд.
Пример:
int i=2; long l=2; double d; float f; d=(double)i * (double)l; f=(float)d; В данном примере величины
i,l,d будут явно преобразовываться к указанным в круглых скобках типам.
Одномерные и многомерные массивы.
В программах очень часто приходится обрабатывать большие объёмы однотипных данных.
Применять обычные переменные для этого очень неудобно: представьте себе 1000 переменных с
разными именами и одинакового типа, которые нельзя обработать в цикле. Эту проблему позволяет
решить использование массивов.
12
Массив – совокупность переменных (элементов) одинакового типа и собщим названием.
Доступ к элементам массива осуществляется простым указанием номера элемента – индекса.
В языке Си нумерация элементов массива начинается с нуля!
Примеры объявления массивов:
int a[10]; // Целочисленный массив a, размер – 10 элементов
double vect[20]; // Массив вещественных чисел vect, в нём 20 элементов
char str[1024]; // Символьный массив на 1024 элемента
При объявлении массива его элементам можно сразу присвоить нужные значения (т.е.
инициализировать массив):
int x[5] = {10, 20, 30, 40, 50};
char str[] = “Hello!”; // Автоматическое
int a[] = {1, 2, 3}; // определение размера
Примеры обращения к элементам массива:
a = x[0];
printf(“%d\n”,x[0]);
scanf(“%d”, &x[0]);
Примечания:
1. В памяти компьютера элементы массива располагаются в виде единого блока, друг за
другом:
x[0] x[1] x[2] x[3] x[4]
10 20 30 40 50
2. В языке Си нет проверки правильности индекса (т.е. номера элемента). Выход за границы
массива чреват непредсказуемыми последствиями.
Как известно, массив - это конечная совокупность данных одного типа. Можно говорить о
массивах целых чисел, массивов символов и.т.д. Мы можем даже определить масссив, элементы
которого - массивы( массив массивов), определяя, таким образм, многомерные массивы. Любой
массив в программе должен быть описан: после имени массива добаляют квадратные скобки [],
внутри которых обычно стоит число, показывающее количество элементов массива. Например,
запись int x[10]; определяет x как массив из 10 целых чисел. В случае многомерных массивов
показывают столько пар скобок , какова размерность массива, а число внутри скобок показывает
размер массива по данному измерению. Например, описание двумерного массива выглядит так: int
a[2][5];. Такое описание можно трактовать как матрицу из 2 строк и 5 столбцов. Для обрщения к
некоторому элементу массива указывают его имя и индекс, заключенный в квадратные скобки(для
многомерног массива - несколько индексов , заключенные в отдельные квадратные скобки): a[1][3],
x[i] a[0][k+2]. Индексы массива в Си всегда начинаются с 0, а не с 1, т.е. описание int x[5];
порождает элементы x[0], x[1], x[2], x[3], x[4], x[5]. Индекс может быть не только целой константой
или целой переменной, но и любым выражением целого типа. Переменная с индексами в программе
используется наравне с простой переменной (например, в операторе присваивания, в функциях
ввода- вывода). Начальные значения массивам в языке Си могут быть присвоены при компиляции
только в том случае, если они объявлены с классом памяти extern или static, например:
static int a[6]={5,0,4,-17,49,1};
обеспечивает присвоения a[0]=5; a[1]=0; a[2]=4 ... a[5]=1. Как видите, для начального
присвоения значений некоторому массиву надо в описании поместить справа от знака = список
инициирующих значений, заключенные в фигурные скобки и разделенные запятыми. Двумерный
массив можно инициировать так:
13
static int matr[2][5] = {{3,4,0,1,2},{6,5,1,4,9}};
Матрица хранится в памяти построчно, т.е. самый правый индекс в наборе индексов массива
меняется наиболее быстро.
Пример
#include<stdio.h>
void main(void)
{//демонстрирует работу с одномерным массивом
int mas[]={2,3,1,8,0,3,5,6,8,9};
int temp;
printf("ne otsortirovannij massiv\n");
for(int i=0;i<10;i++)
printf("%d ",mas[i]);
temp;
for(int i=0;i<10;i++)
for(int j=0;j<10;j++)
if (mas[i]>mas[j]){
temp=mas[i];
mas[i]=mas[j];
mas[j]=temp;
}
printf("\n\notsortirovannij massiv\n");
for(int i=0;i<10;i++)
printf("%d ",mas[i]);
}
Результат работы программы
Указатели.
Указаетль - это переменная, которая содержит адрес некоторого объекта в памяти
компьютера. Понятно, что адрес - целое число. Понимание и правильное использование указателей
очень важно для создания хороших программ.
Многие конструкции языка Си требуют применения указателей. Например, указатели
необходимы для успешного использования функций и динамического распределения памяти. С
указателями следует обращаться очень осторожно. Так использование в программе
неинициализированного указателя может привести к "зависанию" компьютера. При неправильном,
14
неаккуратном использовании указателей в программе могут возникнуть ошибки, которые очень
трудно бывает обнаружить. Обойтись же без указателей в программах на язке Си нельзя.
3. Объявление указателей в Си. Указатель объявляется следующим образом.
1.
Вначале указывается тип указателя. Это некоторый тип языка Си. В данном случае он
определяет тип объекта, на который указывает указатель.
2.
Вслед за этим через пробел ставится звездочка - *. Она обозначает, что следующая за
ней переменная является указателем.
Получается довольно простая формула:
тип *<простая переменная>
Например:
char *ch;
nt *temp, i, *j, *k;
loat *pt, fon;
Здесь указателями являются: ch, temp, j, k pt.
Операции над указателями в Си.
Простейшая операция над указателями - это операция &, что означает "взять адрес".
Существует еще одна операция над указателями. Она обозначается символом звездочка *. Смысл
этой операции таков: "значение, расположенное по указанному адресу".
Хотя знак звездочка * соответствует обычной операции умножения, но никак нельзя
перепутать эти две операции. Ведь арифметическая операция умножения имеет два операнда. Иначе
говоря, при умножении должны быть указаны, как данные, два числа, участвующие в умножении.
Поэтому и говорят, что умножение - это бинарная операция. Операция * над указателями, в отличие
от арифметического умножения, - это унарная операция. То есть, другими словами, она использует
всего один операнд (одно данное).
Рассмотрим простейшие программки, поясняющие использование указателей в языке Си.
#include<stdio.h>
void main(void)
{//демонстрирует работу с указателями
float a=12.5;
float y;
float *p;
p=&a;
y=*p;
printf("a = %f y = %f \n",a,y);
(*p)++;//увеличили на 1 значение, взятое по указателю
p
printf("a = %f y = %f \n",a,y);
y=y**p;//умножаем y на значение, взятое по указателю
p
printf("y = %f \n",y);
}
Результат
15
Директивы препроцессора
При каждом запуске компилятора сначала запускается препроцессор, который выполняет
директивы начинающиеся с символа #. При выполнении этих команд создается временный файл
исходного кода, с которым уже работает компилятор.
Директива #include
Строка
#include "имя файла"
Заменяет эту строку полным содержимым файла имя. файла. Если не указан путь то файл
сначала ищется в директории исходного файла , затем в директории заданной в опциях
компилятора(обычно директория Include).
Строка #include <имя файла>
Ищет файл только в директории заданной в опциях компилятора.
Директива #define
#define идентификатор строка символов
Заменяет все последующие вхождения идентификатора строкой символов. Пример:
#define A_NUMBER 100
int n=A_NUMBER;
n присвоится значение 100
#define можно применять также для определения макросов, например:
#define SWAP(a,b) temp=(a);(a)=(b);(b)=temp
Подробнее о #define (и в частности о макросах) будет отдельная статья.
Директива #undef
#undef идентификатор
Отменяет прероцессорное определение идентификатора.
Директивы #if #else #endif
#if выражение
.....
#endif
Проверяет истинно ли выражение и если истинно, то выполняет все последующие строки до
директивы #endif.
Конструкция типа:
#if выражение
....
#else
...
#endif Проверяет выражение, если оно истинно то выполняются строки между #if и #else а
если ложно то между #else и #endif.
Директивы #ifdef #ifndef
#ifdef идентификатор
.....
#endif
Проверяет определен ли идентификатор в препроцессоре в данный момент(директивой
#define) и если определен, то выполняет все последующие строки до директивы #endif.
16
#ifndef идентификатор
.....
#endif
Наоборот, выполняет все последующие строки до директивы #endif если идентификатор не
определен в препроцессоре в данный момент.
Директива #error
#error - сообщение об ошибке. Останавливает работу компилятора и выдает сообщение об
ошибке. Например:
#ifndef smth_important
#error smth important isn't defined
#endif
Компилятор выдаст что-то типа:
Fatal F1003 file.cpp 2: Error directive: smth important isn't defined
*** 1 errors in Compile ***
Директива #line
Директива
#line константа "имя файла"Заставляет компилятор считать, что константа задает номер следующей
строки исходного файла, и текущий входной файл именуется идентификатором. Если идентификатор
отсутствует, то запомненное имя файла не изменяется.
Директива #pragma
#pragma - это директива препроцессора, которая реализует возможности компилятора. Эти
особенности могут быть связанны с типом компилятора.Разные типы компиляторов могут
поддерживать разные директивы. Общий вид директивы:
#pragma команда компилятора
Например:
#pragma message("сообщение") - просто выдает сообщение при компиляции.
Области видимости
Функции и внешние переменные, из которых состоит Си-программа, каждый раз
компилировать все вместе нет никакой необходимости. Исходный текст можно хранить в нескольких
файлах. Ранее скомпилированные программы можно загружать из библиотек. В связи с этим
возникают следующие вопросы:
• Как писать объявления, чтобы на протяжении компиляции используемые переменные были
должным образом объявлены?
• В каком порядке располагать объявления, чтобы во время загрузки все части программы
оказались связаны нужным образом?
• Как организовать объявления, чтобы они имели лишь одну копию?
• Как инициализировать внешние переменные?
Начнем с того, что разобьем программу-калькулятор на несколько файлов. Конечно, эта
программа слишком мала, чтобы ее стоило разбивать на файлы, однако разбиение нашей программы
позволит продемонстрировать проблемы, возникающие в больших программах.
Областью видимости имени считается часть программы, в которой это имя можно
использовать. Для автоматических переменных, объявленных в начале функции, областью
видимости является функция, в которой они объявлены. Локальные переменные разных функций,
имеющие, однако, одинаковые имена, никак не связаны друг с другом. То же утверждение
справедливо и в отношении параметров функции, которые фактически являются локальными
переменными.
Область действия внешней переменной или функции простирается от точки программы, где
она объявлена, до конца файла, подлежащего компиляции. Например, если main, sp, val, push и pop
определены в одном файле в указанном порядке, т. е.
main() {...}
int sp = 0;
17
double val[MAXVAL];
void push(double f) {...}
double pop(void) {...}
то к переменным sp и val можно адресоваться из push и pop просто по их именам; никаких
дополнительных объявлений для этого не требуется. Заметим, что в main эти имена не видимы так
же, как и сами push и pop.
Однако, если на внешнюю переменную нужно сослаться до того, как она определена, или если
она определена в другом файле, то ее объявление должно быть помечено словом extern.
Важно отличать объявление внешней переменной от ее определения. Объявление объявляет
свойства переменной (прежде всего ее тип), а определение, кроме того, приводит к выделению для
нее памяти. Если строки
int sp;
double val[MAXVAL];
расположены вне всех функций, то они определяют внешние переменные sp и val, т. e.
отводят для них память, и, кроме того, служат объявлениями для остальной части исходного файла.
А вот строки
extern int sp;
extern double val[];
объявляют для оставшейся части файла, что sp - переменная типа int, а val - массив типа
double (размер которого определен где-то в другом месте); при этом ни переменная, ни массив не
создаются, и память им не отводится.
На всю совокупность файлов, из которых состоит исходная программа, для каждой внешней
переменной должно быть одно-единственное определение; другие файлы, чтобы получить доступ к
внешней переменной, должны иметь в себе объявление extern. (Впрочем, объявление extern можно
поместить и в файл, в котором содержится определение.) В определениях массивов необходимо
указывать их размеры, что в объявлениях extern не обязательно. Инициализировать внешнюю
переменную можно только в определении. Хотя вряд ли стоит организовывать нашу программу
таким образом, но мы определим push и pop в одном файле, а val и sp - в другом, где их и
инициализируем. При этом для установления связей понадобятся такие определения и объявления:
В файле 1:
extern int sp;
extern double val[];
void push(double f) {...}
double pop(void) {...}
В файле2:
int sp = 0;
double val[MAXVAL];
Поскольку объявления extern находятся в начале файла1 и вне определений функций, их
действие распространяется на все функции, причем одного набора объявлений достаточно для всего
файла1. Та же организация extern-объявлений необходима и в случае, когда программа состоит из
одного файла, но определения sp и val расположены после их использования.
Классы памяти и область действия
Автоматические, внешние, статические и регистровые переменные.
Каждая переменная имеет тип и принадлежит к некоторому классу памяти. Время жизни и
область действия идентификатора определяются ассоциированным с ним классом памяти.
Существуют четыре разновидности классов памяти:
auto - автоматический - локальные идентификаторы, память для которых выделяется при
входе в блок, т.е. составной оператор, и освобождается при выходе из блока. Слово auto является
сокращением слова automatic.
18
static - статический - локальные идентификаторы, существующие в процессе всех
выполнений блока. В отличие от идентификаторов типа auto, для идентификаторов типа static память
выделяется только один раз - в начале выполнения программы, и они существуют, пока программа
выполняется.
extern - внешний - идентификаторы, называемые внешними, external, используются для
связи между функциями, в том числе независимо скомпилированными функциями, которые могут
находиться в различных файлах. Память, ассоциированная с этими идентификаторами, является
постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.
register - регистровый - идентификаторы, подобные идентификаторам типа auto. Их
значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого
доступа к данным.
Если класс памяти идентификатора не указан явно, то его класс памяти задается положением
его определения в тексте программы. Если идентификатор определяется внутри функции, тогда его
класс памяти auto, в остальных случаях идентификатор имеет класс памяти extern.
Автоматические переменные
По умолчанию переменные, описанные внутри функции, являются автоматическими.
Можно, однако, это подчеркнуть явно с помощью ключевого слова auto:
main( )
{
auto int kat;
}
Так поступают, если хотят, например, показать, что определение переменной не нужно искать
вне функции.
Автоматические переменные имеют локальную область действия. Только функция, в которой
переменная определена, знает ее. Другие функции могут использовать переменные с тем же самым
именем, но это будут независимые переменные, находящиеся в разных ячейках памяти.
Автоматическая переменная начинает существовать при вызове функции, содержащей ее.
Когда функция завершает свою работу и возвращает управление туда, откуда ее вызвали,
автоматическая переменная исчезает. Область действия автоматической переменной ограничена
блоком, т.е. { }, в котором переменная описана.
! Мы всегда должны описывать наши переменные в начале тела функции (блока). Областью
действия их является вся функция. Можно описать переменную внутри подблока. Тогда
переменная будет известна только в этой части функции, однако этого лучше не делать. Это дурной
стиль программирования!
Внешние переменные
Переменная, описанная вне функции, является внешней.
Глобальные переменные определяются на том же уровне, что и функции, т.е. они не локальны
ни в каком блоке. Постоянные глобальные переменные инициализируются нулем, если явно не
задано другое начальное значение. Областью действия является вся программа. Они должны быть
описаны во всех файлах программы, в которых к ним есть обращения. Некоторые компиляторы
требуют, чтобы глобальные переменные были определены только в одном файле, и описаны как
внешние в других файлах, где они используются. Глобальные переменные должны быть описаны в
файле до первого использования.
Пример:
int global_flag;
Внешнюю переменную можно описать и в функции, которая использует ее, при помощи
ключевого слова extern. Группу extern-описаний можно совсем опустить, если исходные определения
переменных появляются в том же файле и перед функцией, которая их использует. Включение
ключевого слова extern позволяет функции использовать внешнюю переменную, даже если она
определяется позже в этом или другом файле. Оба файла должны быть скомпилированы, связаны
или собраны в одно и то же время.
19
Если слово extern не включено в описание внутри функции, то под этим именем создается
новая автоматическая переменная. Мы можем пометить вторую переменную как автоматическую с
помощью слова auto.
Статические переменные
Статические здесь означает, что переменные остаются в работе. Они имеют такую же
область действия, как автоматические переменные, но они не исчезают, когда содержащая их
функция закончит свою работу. Компилятор хранит их значения от одного вызова функции до
другого.
Пример:
/* Статическая переменная */
#include <stdio.h>
void man_woman( )
{
int man = 1;
static int woman = 1;
printf("man = %d and woman = %d\n",
man++, woman++);
}
void main(void)
{
int count;
for(count = 1;count <= 3; count ++)
{
printf("Count students %d:\n", count);
man_woman ( );
}
}
Результат работы программы
Функция man_woman увеличивает каждую переменную после печати ее значения. Работа этой
программы дает следующие результаты:
Подсчет студентов 1:
юношей = 1 и девушек = 1
Подсчет студентов 2:
юношей = 1 и девушек = 2
Подсчет студентов 3:
юношей = 1 и девушек = 3
Статическая переменная woman помнит, что ее значение было увеличено на 1, в то время как
для переменной man начальное значение устанавливается каждый раз заново. Это указывает на
20
разницу в инициализации: man инициализируется каждый раз, когда вызывается man_woman ( ), в то
время как woman инициализируется только один раз при компиляции функции man_woman ( ).
Внешние статические переменные
Можно описать статические переменные вне любой функции. Это создает внешнюю
статическую переменную. Разница между внешней переменной и внешней статической переменной
заключается в области их действия. Обычная внешняя переменная может использоваться функциями
в любом файле, а внешняя статическая переменная может использоваться только функциями того же
самого файла, причем после определения переменной. Статическую переменную мы описываем вне
любой функции.
Регистровые переменные
Обычно переменные хранятся в памяти машины. Регистровые переменные запоминаются в
регистрах центрального процессора, где доступ к ним и работа с ними выполняются гораздо быстрее,
чем в памяти. В остальном регистровые переменные аналогичны автоматическим переменным.
Пример:
main( )
{
register int pleat;
}
Компилятор сравнивает наши требования с количеством доступных регистров, поэтому мы
можем и не получить то, что хотим. В этом случае переменная становится простой автоматической
переменной.
Особенности работы с языком Си. Какой класс памяти применять? Ответ на вопрос автоматический. Этот класс памяти выбран по умолчанию. Использование внешних переменных
очень соблазнительно. Если описать все переменные как внешние, то не будет забот при
использовании аргументов и указателей для связи между функциями в прямом и обратном
направлениях. Но тогда возникает проблема с функцией С, изменяющей переменные в функции А, а
мы этого не хотели! Такая проблема значительно перевешивает кажущуюся привлекательность
широкого использования внешних переменных. Одно из золотых правил программирования
заключается в соблюдении принципа "необходимо знать только то, что нужно". Организуйте работу
каждой функции автономно, насколько это возможно, и используйте глобальные переменные только
тогда, когда это действительно необходимо!
Операция получения адреса & неприменима к регистровым переменным. Любые переменные
в блоке, кроме формальных параметров функции, могут быть определены как статические.
Подведем итог.
Классы памяти, которые описываются внутри функции:
1.
автоматический, продолжительность существования - временно, область действия локальная;
2.
регистровый, продолжительность существования - временно, область действия локальная;
3.
статический, продолжительность существования - постоянно, область действия локальная.
Классы памяти, которые определяются вне функции:
1.
внешний, продолжительность существования - постоянно, область действия глобальная
(все файлы);
2.
внешний статический, продолжительность существования - постоянно, область
действия - глобальная (один файл).
Определение и вызов функций
Функция - это совокупность объявлений и операторов, обычно предназначенная для решения
определенной задачи. Каждая функция должна иметь имя, которое используется для ее объявления,
определения и вызова. В любой программе на СИ должна быть функция с именем main (главная
функция), именно с этой функции, в каком бы месте программы она не находилась, начинается
выполнение программы.
21
При вызове функции ей при помощи аргументов (формальных параметров) могут быть
переданы некоторые значения (фактические параметры), используемые во время выполнения
функции. Функция может возвращать некоторое (одно !) значение. Это возвращаемое значение и есть
результат выполнения функции, который при выполнении программы подставляется в точку вызова
функции, где бы этот вызов ни встретился. Допускается также использовать функции не имеющие
аргументов и функции не возвращающие никаких значений. Действие таких функций может
состоять, например, в изменении значений некоторых переменных, выводе на печать некоторых
текстов и т.п..
С использованием функций в языке СИ связаны три понятия - определение функции
(описание действий, выполняемых функцией), объявление функции (задание формы обращения к
функции) и вызов функции.
Определение функции задает тип возвращаемого значения, имя функции, типы и
число формальных параметров, а также объявления переменных и операторы,
называемые телом функции, и определяющие действие функции. В определении функции
также может быть задан класс памяти.
Пример:
int rus (unsigned char r)
{
if (r>='А' && c<=' ')
return 1;
else
return 0;
}
В данном примере определена функция с именем rus, имеющая один параметр с именем r и
типом unsigned char. Функция возвращает целое значение, равное 1, если параметр функции является
буквой русского алфавита, или 0 в противном случае.
Передача аргументов, стек.
При вызове функции с переменным числом параметров в вызове этой функции задается любое
требуемое число аргументов. В объявлении и определении такой функции переменное число
аргументов задается многоточием в конце списка формальных параметров или списка типов
аргументов.
Все аргументы, заданные в вызове функции, размещаются в стеке. Количество формальных
параметров, объявленных для функции, определяется числом аргументов, которые берутся из стека и
присваиваются формальным параметрам. Программист отвечает за правильность выбора
дополнительных аргументов из стека и определение числа аргументов, находящихся в стеке.
Примерами функций с переменным числом параметров являются функции из библиотеки
функций языка СИ, осуществляющие операции ввода-вывода информации (printf,scanf и т.п.).
Подробно эти функции рассмотрены во третьей части книги.
Программист может разрабатывать свои функции с переменным числом параметров. Для
обеспечения удобного способа доступа к аргументам функции с переменным числом параметров
имеются три макроопределения (макросы) va_start, va_arg, va_end, находящиеся в заголовочном
файле stdarg.h. Эти макросы указывают на то, что функция, разработанная пользователем, имеет
некоторое число обязательных аргументов, за которыми следует переменное число необязательных
аргументов. Обязательные аргументы доступны через свои имена как при вызове обычной функции.
22
Функция форматированного вывода printf
Функция printf() является функцией стандартного вывода. С помощью этой функции можно
вывести на экран монитора строку символов, число, значение переменной...
Функция
printf()
имеет
прототип
в
файле
stdio.h
int printf(char *управляющая строка, ...);
В случае успеха функция printf() возвращает число выведенных символов.
Управляющая строка содержит два типа информации: символы, которые непосредственно
выводятся на экран, и спецификаторы формата, определяющие, как выводить аргументы.
Функция printf() это функция форматированного вывода. Это означает, что в параметрах
функции необходимо указать формат данных, которые будут выводиться. Формат данных
указывается спецификаторами формата. Спецификатор формата начинается с символа % за которым
следует код формата.
Спецификаторы формата:
%с
символ
%d
целое десятичное число
%i
целое десятичное число
%e
десятичное число в виде x.xx e+xx
%E
десятичное число в виде x.xx E+xx
%f
десятичное число с плавающей запятой xx.xxxx
%F
десятичное число с плавающей запятой xx.xxxx
%g
%f или %e, что короче
%G
%F или %E, что короче
%o
восьмеричное число
%s
строка символов
%u
беззнаковое десятичное число
%x
шестнадцатеричное число
%X
шестнадцатеричное число
%%
символ %
%p
указатель
%n
указатель
Кроме того, к командам формата могут быть применены модификаторы l и h.
%ld
печать long int
%hu
печать short unsigned
%Lf
печать long double
В спецификаторе формата, после символа % может быть указана точность (число цифр после
запятой). Точность задаётся следующим образом: %.n<код формата>. Где n - число цифр после
запятой, а <код формата> - один из кодов приведённых выше.
Например, если у нас есть переменная x=10.3563 типа float и мы хотим вывести её значение с
точностью до 3-х цифр после запятой, то мы должны написать:
printf("Переменная x = %.3f",x);
Результат:
Переменная x = 10.356
Вы также можете указать минимальную ширину поля отводимого для печати. Если строка или
число больше указанной ширины поля, то строка или число печатается полностью.
Например, если вы напишите:
printf("%5d",20);
то
результат
20
будет
следующим:
23
Обратите внимание на то, что число 20 напечаталось не с самого начала строки. Если вы
хотите чтобы неиспользованные места поля заполнялись нулями, то нужно поставить перед шириной
поля
символ
0.
Например:
printf("%05d",20);
Результат:
00020
Кроме спецификаторов формата данных в управляющей строке могут находиться
управляющие символы:
\b
BS, забой
\f
Новая страница, перевод страницы
\n
Новая строка, перевод строки
\r
Возврат каретки
\t
Горизонтальная табуляция
\v
Вертикальная табуляция
\"
Двойная кавычка
\'
Апостроф
\\
Обратная косая черта
\0
Нулевой символ, нулевой байт
\a
Сигнал
\N
Восьмеричная константа
\xN
Шестнадцатеричная константа
\?
Знак вопроса
Чаще всего вы будете использовать символ \n. С помощью этого управляющего символа вы
сможете переходить на новую строку. Посмотрите примеры программ и вы всё поймёте.
Примеры программ.
/* Пример 1 */
#include <stdio.h>
void main(void)
{
int a,b,c;
// Объявление переменных a,b,c
a=5;
b=6;
c=9;
printf("a=%d, b=%d, c=%d",a,b,c);
}
Результат работы программы:
a=5, b=6, c=9
/* Пример 2 */
#include <stdio.h>
void main(void)
{
float x,y,z;
x=10.5;
y=130.67;
z=54;
24
printf("Координаты объекта: x:%.2f, y:%.2f, z:%.2f", x, y, z);
}
Результат работы программы:
Координаты объекта: x:10.50, y:130.67, z:54.00
Функция стандартного ввода scanf()
Функция scanf() - функция форматированного ввода. С её помощью вы можете вводить
данные со стандартного устройства ввода (клавиатуры). Вводимыми данными могут быть целые
числа, числа с плавающей запятой, символы, строки и указатели.
Функция
scanf()
имеет
следующий
прототип
в
файле
stdio.h:
int scanf(char *управляющая строка);
Функция возвращает число переменных которым было присвоено значение.
Управляющая строка содержит три вида символов: спецификаторы формата, пробелы и
другие символы. Спецификаторы формата начинаются с символа %.
Спецификаторы формата:
%c
чтение символа
%d
чтение десятичного целого
%i
чтение десятичного целого
%e
чтение числа типа float (плавающая запятая)
%h
чтение short int
%o
чтение восьмеричного числа
%s
чтение строки
%x
чтение шестнадцатеричного числа
%p
чтение указателя
%n
чтение указателя в увеличенном формате
При вводе строки с помощью функции scanf() (спецификатор формата %s), строка вводиться
до первого пробела!! т.е. если вы вводите строку "Привет мир!" с использованием функции scanf()
char
str[80];
//
массив
на
80
символов
scanf("%s",str);
то после ввода результирующая строка, которая будет храниться в массиве str будет состоять
из одного слова "Привет". ФУНКЦИЯ ВВОДИТ СТРОКУ ДО ПЕРВОГО ПРОБЕЛА! Если вы хотите
вводить строки с пробелами, то используйте функцию
char *gets( char *buf );
С помощью функции gets() вы сможете вводить полноценные строки. Функция gets() читает
символы с клавиатуры до появления символа новой строки (\n). Сам символ новой строки
появляется, когда вы нажимаете клавишу enter. Функция возвращает указатель на buf. buf - буфер
(память) для вводимой строки.
Напишем пример программы, которая позволяет ввести целую строку с клавиатуры и вывести
её на экран с помощью gets().
#include
<stdio.h>
void
{
main(void)
char
//
buffer[100];
gets(buffer);
printf("%s",buffer);
//
//
массив
вводим
вывод
(буфер)
для
вводимой
строки
строку
введённой
и
нажимаем
строки
на
enter
экран
}
Ещё одно важное замечание! Для ввода данных с помощью функции scanf(), ей в качестве
параметров нужно передавать адреса переменных, а не сами переменные. Чтобы получить адрес
25
переменной, нужно поставить перед именем переменной знак &(амперсанд). Знак & означает взятие
адреса.
Что значит адрес? Попробую объяснить. В программе у нас есть переменная. Переменная
хранит своё значение в памяти компьютера. Так вот адрес, который мы получаем с помощью & это
адрес в памяти компьютера где храниться значение переменной.
Давайте рассмотрим пример программы, который показывает нам как использовать &
#include <stdio.h>
void main(void)
{
int x;
printf("Введите переменную x:");
scanf("%d",&x);
printf("Переменная x=%d",x);
}
Теперь давайте вернёмся к управляющей строке функции scanf(). Ещё раз:
int scanf(char *управляющая строка);
Символ пробела в управляющей строке дает команду пропустить один или более пробелов в
потоке ввода. Кроме пробела может восприниматься символ табуляции или новой строки. Ненулевой
символ указывает на чтение и отбрасывание этого символа.
Разделителями между двумя вводимыми числами являются символы пробела, табуляции или
новой строки. Знак * после % и перед кодом формата (спецификатором формата) дает команду
прочитать данные указанного типа, но не присваивать это значение.
Например:
scanf("%d%*c%d",&i,&j);
при вводе 50+20 присвоит переменной i значение 50, переменной j - значение 20, а символ +
будет прочитан и проигнорирован.
В команде формата может быть указана наибольшая ширина поля, которая подлежит
считыванию.
Например:
scanf("%5s",str);
прророотт необходимость прочитать из потока ввода первые 5 символов. При вводе 123т
и7890ABC массив str будет содержать только 12345, остальные символы будут ртоигнорированы.
Разделители: пробел, символ табуляции и символ новой строки - при вводе олвола воспринимаются,
как и все другие символы.
Указатели на функции
Указатели на функции– мощное средство языка С. Функция располагается в памяти по
определенному адресу. Адресом функции является ее точка входа. Именно этот адрес используется
при вызове функции. Так как указатель хранит адрес функции, то она может быть вызвана с
помощью этого указателя. Он позволяет также передавать ее другим функциям в качестве аргумента.
В программе на С адресом функции служит ее имя без скобок и аргументов (это похоже на
адрес массива, который равен имени массива без индексов).
Пример:
void error(char* p) { /* ... */ }
void (*efct)(char*); // указатель на функцию
void f()
{
efct = &error;
// efct настроен на функцию error
(*efct)("error"); // вызов error через указатель efct
}
26
о
Для вызова функции с помощью указателя (efct в нашем примере) надо вначале применить
операцию косвенности к указателю - *efct. Поскольку приоритет операции вызова () выше, чем
приоритет косвенности *, нельзя писать просто *efct("error"). Это будет означать *(efct("error")), что
является ошибкой. По той же причине скобки нужны и при описании указателя на функцию. Однако,
писать просто efct("error") можно, т.к. транслятор понимает, что efct является указателем на
функцию, и создает команды, делающие вызов нужной функции.
Отметим, что формальные параметры в указателях на функцию описываются так же, как и в
обычных функциях. При присваивании указателю на функцию требуется точное соответствие типа
функции и типа присваиваемого значения. Например:
void (*pf)(char*);
// указатель на void(char*)
void f1(char*);
// void(char*);
int f2(char*);
// int(char*);
бл
void f3(int*);
// void(int*);
void f()
{
pf = &f1;
pf = &f2;
pf = &f3;
(*pf)("asdf");
(*pf)(1);
int i = (*pf)("qwer");
}
// нормально
// ошибка: не тот тип возвращаемогожэхюж
// значения
// ошибка: не тот тип параметра
// нормально
// ошибка: не тот тип параметра
// ошибка: void присваивается int
Правила передачи параметров одинаковы и для обычного вызова, и для вызова с помощью
указателя.
Декларация структур.
Cтруктуры - это составной объект, в который входят элементы любых типов, за исключением
функций. В отличие от массива, который является однородным объектом, структура может быть
неоднородной. Тип структуры определяется записью вида:
struct { список определений }
В структуре обязательно должен быть указан хотя бы один компонент. Определение структур
имеет следующий вид:
тип-данных описатель;
где тип-данных указывает тип структуры для объектов, определяемых в описателях. В
простейшей форме описатели представляют собой идентификаторы или массивы.
Пример:
struct { double x,y; } s1, s2, sm[9];
struct { int year;
char moth, day; } date1, date2;
Переменные s1, s2 определяются как структуры, каждая из которых состоит из двух
компонент х и у. Переменная sm определяется как массив из девяти структур. Каждая из двух
переменных date1, date2 состоит из трех компонентов year, moth, day. >p>Существует и другой
27
способ ассоциирования имени с типом структуры, он основан на использовании тега структуры. Тег
структуры аналогичен тегу перечислимого типа. Тег структуры определяется следующим образом:
struct тег { список описаний; };
где тег является идентификатором.
В приведенном ниже примере идентификатор student описывается как тег структуры:
struct student { char name[25];
int id, age;
char prp;
};
Тег структуры используется для последующего объявления структур данного вида в форме:
struct тег список-идентификаторов;
Пример:
struct studeut st1,st2;
Использование тегов структуры необходимо для описания рекурсивных структур. Ниже
рассматривается использование рекурсивных тегов структуры.
struct node { int data;
struct node * next; } st1_node;
Тег структуры node действительно является рекурсивным, так как он используется в своем
собственном описании, т.е. в формализации указателя next. Структуры не могут быть прямо
рекурсивными, т.е. структура node не может содержать компоненту, являющуюся структурой node,
но любая структура может иметь компоненту, являющуюся указателем на свой тип, как и сделано в
приведенном примере.
Доступ к компонентам структуры осуществляется с помощью указания имени структуры и
следующего через точку имени выделенного компонента, например:
st1.name="Иванов";
st2.id=st1.id;
st1_node.data=st1.age;
Download