С/С++
• Язык С был разработан Деннисом М. Ритчи в 1972 году. Реализация С, в соответствии с изложенными правилами, рассматривается как K&R стандарт (Брайн В. Керниган и Ритчи). K&R – минимальная стандартная реализация. В 1983 году Американский институт национальных стандартов (ANSI) разработал новый стандарт, названный стандартом ANSI языка С. • С++ - это надмножество языка С. Реально он включает все операторы и средства языка С, добавив только некоторые новые. Преимущество С++ в том, что он позволяет с большой легкостью разрабатывать большие сложные программы за счет модульного подхода и некоторых других усовершенствований. Кроме того, С++ является языком ООП.
Логические структуры языка. Программа на С состоит из лексических элементов. 1. Элементы Программа на С представляет собой строки, состоящие из лексических элементов пяти типов: зарезервированные (ключевые) слова, константы, операции, ограничители, идентификаторы. Смежные элементы отделяются друг от друга разделителями или комментариями. Разделители: пробелы, символы табуляции, возврата каретки, перевода строки. 2. Комментарии Они служат для документирования программы и ограничиваются символами /* и */. Формат /* текст комментария */ В С++ формат комментария // текст комментария Пример /* Программа выводит сообщение на экранили // */ Программа выводит сообщение на экран
3. Ограничители Символы – ограничители: ( ), [ ], { }, : , , , ; 4. Операции Перечень операций, используемых в С приведен ниже: Одно символьные операции: = ! ^ & * - : . < > / ? + % Двух символьные операции: = = != && : : -> ++ -- << >> <= >= += -= *= /= %= ^= : = &= Трехсимвольные операции: <<= >>= 5. Идентификаторы В С идентификаторы служат для именования типов, переменных, констант и функций. Идентификатор состоит из букв и цифр и может содержать символы подчеркивания. Значащие – первые 32 символа. Начинается идентификатор с буквы. В идентификаторах прописные и заглавные буквы различаются.
6. Зарезервированные слова В С используются зарезервированные слова, которые нельзя использовать в качестве идентификаторов. Они задаются прописными буквами. Фрагмент таблицы зарезервированных слов.
Константы В С имеется четыре типа констант: целые, вещественные, символьные и строковые. Константы целого типа могут задаваться в десятичной, 8 -ой или 16 -ой системах счисления. Десятичные целые константы образуются из цифр. Первой цифрой не должен быть 0. Восьмеричные константы начинаются с цифры 0, за которой следуют цифры 0 -7. Шестнадцатеричные константы начинаются с цифры 0 и символа , за которыми может стоять одна или более 16 -ых цифр 0 -9, A-F.
Пример #include <stdio. h> main() { int a=3478, b=06626, c=0 x. D 96; printf(“a=%d b=%d c=%dn”, a, b, c); } На экране a=3478 b=3478 c=3478
Константы вещественного типа Константы этого типа состоят из цифр, десятичной точки и знака десятичного порядка Примеры 1. 2 е 1. 1234. 1 е 3. 1 2 Е 1 1. 234 0. 0035 е-6 1. 0 2 е-1 2. 1 е-12 0. 234 Символьные константы заключаются в одиночные кавычки (апострофы) Например if (ch>=’a’&&ch<=’z’) Одни символьные константы соответствуют символам, которые можно вывести на экран, другие – управляющим символам, задаваемым с помощью escпоследовательности, третьи – форматирующим символам, также задаваемым с помощью esc – последовательности. Например Символ “апостроф” → ‘’’; Переход на новую строку → ‘n’; Обратный слэш → ‘\’.
Управляющие коды В следующей таблице приведены управляющие коды, используемые в С. Каждая esc – последовательность должна быть заключена в кавычки
Строковые константы состоят из нуля или более символов, заключенных в двойные кавычки. В строковых константах управляющие коды задаются с помощью esc-последовательности. Замечания к использованию констант Для задания констант можно использовать их непосредственное написание (обозначение). Кроме того, существуют следующие способы задания константы: a) Макроопределение Формат #define <имя константы> <значение константы> Например #define PI 3. 14 #define CHARACTER_B 'B' #define version_oct 020 // для восьмеричного числа #define version_dec 16 // для десятичного числа #define version_hex 0 x 10 // для 16 -го числа #define NAME “ALEKS”
б) типизированные константы Можно определить константу, описать тип данных и присвоить значение, использую ключевое слово const Например main() { const int CHILDREN=8; const char INIT=’C’; const float NUMBER=1. 65; }
Скалярные типы данных, операции, преобразование типов. В С можно использовать различные типы данных для представления хранимой и обрабатываемой информации. Типы данных и элементы памяти. Тип задается набором допустимых значений и набором действий, которые можно совершать над каждой переменной рассматриваемого типа. Переменные типизируются посредством их описаний. Выражения типизируются посредством содержащих в них операций. В С имеется множество предопределенных типов данных, включая несколько видов целых, вещественных, указателей, переменных, массивов, функций, объединений, структур и тип void (отсутствие типа). Скалярные типыуказатель, арифметический (целый, : вещественный), перечисляемый. Тип void.
Функции. Агрегатные типы: массив, структура, объединение. В следующей таблице представлены типы С, их размеры и диапазоны
2. Переменные целого типа Переменная описываются с помощью спецификатора типа (см. таблицу) и при описании ей может быть присвоено начальное значение. Пример int age=20, height=170; // возраст, рост unsigned weight=height/2; // вес long index; // индекс Замечание Если используются спецификаторы unsigned, short, longто int можно опускать. , Допустимые операции над целочисленными операндами указаны в таблице
3. Переменные вещественного типа Для описания таких переменных используются спецификаторы float, double, long double. Пример float force=12. 78, /* сила */ acceleration=1. 234; /* ускорение */ double height; /* высота */ Операции над вещественными операндами, аналогичны арифметическим и логическим операциям над целочисленными операндами (см. предыдущую таблицу). Исключение – операция % (остаток от деления).
4. Символьные переменные Для описания символьных переменных используются спецификаторы char, signed char, unsigned char. Можно задавать начальные значения. Пример char ch=’$’, ans=’n’, ascii_value=65; Замечание В выражениях переменные типа char могут смешиваться с переменными типа int, поскольку те и другие принадлежат к целому типу. Пример #include <stdio. h> main() { char ch=’a’, ans; printf(“значение ch+3=%c”, ch+3); ans=ch%3; printf((“nn значение ans=%dn”, ans); } Программа выводит на экран следующие строки: Значение ch+3=d Значение ans=1
5. Строковые переменные Для определения строковой переменной необходимо использовать тип char и указать максимальное число символов, которое может содержать строка. В С нет стандартного типа строкаи строка объявляется, как массив символов, но для работы с массивом символов, как со строкой имеется набор библиотечных функций. Описание в общем случае: char name[n]; name – имя массива; n – размер массива. Пример char str[80]; Работу со строковыми данными рассмотрим подробнее при изучении массивов и функций работы со строками. 6. Приоритет и порядок выполнения операций. Если в выражении не используются круглые скобки, задающие порядок выполнения операций, то группировка операндов для операций производится с учетом приоритета операций. В следующей таблице приведены операции в порядке убывания приоритета.
7. Операции отношения Логические выражения строятся из операций отношения и вырабатывают в качестве результата значение типа int. Если результат равен 0, то считается, что логическое выражение ложно, в противном случае – истинно. Если в логическом выражении не используются скобки, то оно вычисляется слева направо. Вычисление прекращается, как только результат становится определенным. Такой способ вычисления логических выражений называется усечением. Например if (a>b||c>d||e>f) Вычисление выражения прерывается, как только выясняется, что, либо a>b, либо c>d, либо e>f. Усечение с успехом может быть использовано для задания корректного порядка вычислений в логических выражениях. Например, логическое выражение if (b!=0. 0&&a/b>12. 4) Имеет больший смысл, чем логическое выражение if (a/b>12. 4&&b!=0. 0)
8. Побитовые операции Операнд или операнды побитовых операций должны быть целого типа. Побитовые операции обычно используются в приложениях, где требуется манипуляция с битами на нижнем уровне. Операции &, |, ^ определяются следующим образом
Операции << и >> служат для сдвига последовательности битов, соответственно, влево и вправо. Эти операции можно применять для деления или умножения на число, равное степени 2, в соответствии со следующими правилами:
9. Операции присваивания К этим операциям относятся: = , += , -= , *= , /= , %= , и префиксные и постфиксные операции ++ и --. Все операции присваивают переменной результат вычисления выражения. Если тип левой части присваивании отличается от типа правой части, то тип правой части приводится к типу левой. В одном операторе операция присваивания может встречаться несколько раз. Вычисление производится справа налево. Например a=(b=c)*d; Значению переменной b присваивается значение переменной c, затем выполняется операция умножения на d и результат присваивается переменной a. Типичный пример использования многократного присваивания a=b=c=d=e=f=0; Операции += , -= , *= , /= являются укороченной формой записи операции присваивания, а именно: a+=b; -> a=a+b; a-=b; -> a=a-b; a*=b; -> a=a*b; a/=b; -> a=a/b; Постфиксные и префиксные операции ++ и -- используются для увеличения (инкремент) и уменьшения (декремент) на 1 значения переменной. Семантика указанных операций следующая: ++a , а++– увеличение значения переменной a на 1 до (после) ее использования в выражении. --a , а-- – уменьшение значения переменной a на 1 до (после) ее использования в выражении. Пример #include <stdio. h> main() { int a, b, c=3, d=4, f=3, g, h=5, z=6, i; a=z+(b=c*d*f+(g=h+(i=3))); printf(“%dn”, a); } Программа выводит на экран значение – 50.
10. Операция sizeof Эту операцию можно применять к константе, типу или переменной. В результате будет получено число байтов, занимаемых операндом. Если операндом является тип, то такой операнд следует заключать в круглые скобки. Если операнд переменная, то скобки можно опускать. Пример #include <stdio. h> main() { float a; int b; char c; float d[500]; printf(“n Размер памяти под целое %d”, sizeof(int)); printf(“n Размер памяти под двойную точность %d”, sizeof(double)); printf(“n Размер памяти под переменную a %d”, sizeof a); printf(“n Размер памяти под массив d %d”, sizeof d); } Результат работы программы Размер памяти под целое 2 Размер памяти под двойную точность 8 Размер памяти под переменную a 4 Размер памяти под массив d 2000
11. Операция “запятая” для связывания между собой выражений. Список выражений, разделенный запятыми, трактуется как единое выражение и вычисляется слева направо. Пример if (c=getchar(), c>’a’) Читается символ в переменную с и сравнивается с символом ‘a’, результат целое число. 12. Приведение и преобразование типов Для выполнения однозначного преобразования (cast) объектов одного типа в другой в С имеется специальная конструкция вида: (имя_типа) выражение; Пример float r=3. 5; int i; i=(int) r; Преобразование одного типа в другой тип данных выполняется в соответствии со следующими условиями: преобразование используется для однозначного перевода данного значения в другой тип; операнд автоматически приводится к другому типу перед выполнением соответствующей арифметической или логической операции; если операнд одного типа присваивается лево допустимому объекту другого типа, то приведение типов выполняется автоматически; аргумент функции может автоматически приводится к требуемому типу прямо в вызове функции; результат выполнения функции может быть автоматически приведен к требуемому типу в момент возврата из функции.
Управляющие структуры – это конструкции языка, позволяющие управлять логикой выполнения программы. Управляющие структуры можно разделить на две группы: структуры выбора и циклы. 1. Блоки и составные операторы Любая последовательность операторов, заключенная в { } скобки, является составным оператором (блоком). Внутри блока каждый оператор должен заканчиваться “; ”. Составной оператор можно использовать везде, где синтаксис языка допускает обычный оператор. Пример if (x+y+z>w) { cout<<”n Happy End”; x+=3; y-=4; z*=6; }
2. Пустой оператор Он представляется символом “; ” перед которым нет выражения. Пустой оператор используется там, где синтаксис языка требует присутствия в данном месте программы оператора, однако по логике программы оператор должен отсутствовать. Необходимость в использовании пустого оператора часто возникает при программировании циклов, когда действия, которые могут быть выполнены в теле цикла, целиком помещаются в заголовке цикла. 3. Конструкции выбора К ним относятся операторы: условный оператор if; условный оператор if else; условная операция ? ; оператор switch. Оператор if Синтаксис if (выражение) оператор; Если выражение в заголовке условного оператора вырабатывает ненулевое значение, то операторв условном операторе выполняется, в противном случае, управление передается оператору, следующему за if. Пример int t; if (t<12) printf (“Доброе утро!”); if (t>=12) printf(“Добрый день!”);
Оператор if else Синтаксис if (выражение) оператор1; else оператор2; Если значение выраженияне равно 0, то выполняется оператор1 в противном случае – оператор2. , Можно допускать вложенные if else но следует , помнить, что else всегда относится к ближайшему if. Пример int t; if (t<12) printf (“Доброе утро!”); else printf(“Добрый день!”);
Условная операция ? Эта операция может с успехом использоваться вместо конструкции if else там, где входящие в нее операторы являются простыми выражениями. Синтаксис: Результат=выражение? выражение 1: выражение 2; Пример1 #include <stdio. h> main() Пример 3 { #include <stdio. h> int i=6, j=4; main() int result=(i<j)i: j; { printf(“%dn”, result); int i=3, j=5; } // В этом случае result инициализируется j printf(“Минимум из i и j – это Пример 2 %dn”, (i<j)? i: j); #include <stdio. h> } main() Минимум из i и j – это 3 { int i=6, j=4; (i<j)? printf(“i<jn”): printf(“i>=jn”); } // Выполняется второй оператор printf
Оператор switch Конструкция switch заменяет разветвленный многократный оператор if else Синтаксис: switch(выражение) { case константное_выражение 1: оператор1; case константное_выражение 2: оператор2; … case константное_выражение n: оператор n; default: оператор; }
После вычисления выражения в заголовке оператора его результат последовательно сравнивается с константными выражениями, начиная с самого верхнего, пока не будет установлено их соответствие. Тогда выполняется оператор (операторы) внутри соответствующего case, управление передается на следующее константное выражение и проверки продолжаются. Именно поэтому в конце каждой последовательности должен присутствовать оператор break. Осуществляется выход из конструкции switch. Ветка default (умолчание) может отсутствовать. Если она есть, то последовательность операторов, стоящая непосредственно за default и “: ” выполняется только тогда, когда сравнение ни с , одним из стоящих выше константных выражений не истинно.
Пример #include <stdio. h> void error_message(int error_code) { switch(error_code) { case 1: printf(“n Message 1”); break; case 2: printf(“n Message 2”); break; … case 5: printf(“n Message 5”); break; default: printf(“n Invalid code error”); } }
Оператор goto используется для передачи управления внутри программной единицы от одного оператора к другому. Синтаксис: goto идентификатор; Пример goto backend; … backend: x+=3; Рекомендации по применению go to. не входить внутрь блока извне; не входить внутрь оператора if или else, if else, switch; не входить внутрь оператора цикла.
Операторы цикла Циклы (итерационные структуры) позволяют повторять выполнение отдельных операторов или группы операторов. Число повторений может быть фиксировано, либо определяется в процессе счета на основе одной или нескольких проверок условия. Операторы цикла: while, do while, for. Оператор цикла while (пока) Синтаксис while (условное-выражение) оператор Проверка условия проводится перед выполнением тела цикла. Если результат вычисления условного выражения не равен 0, то выполняется оператор (или группа операторов). Операторы, составляющие тело цикла должны, как правило, должны изменять значение одной или нескольких переменных, входящих в условное выражение, с тем, чтобы выражение обратилось в 0 и цикл завершился. Цикл while завершается в следующих случаях: обратилось в 0 условное выражение в заголовке цикла; в теле цикла выполнился оператор break; в теле цикла выполнился оператор return. В случаях 1. , 2. управление передается на оператор, следующий за циклом. В случае 3. – возврат из программной единицы.
Пример 1. main() { int i=4; while(i>0) printf(“n C++эффективен ”); n } Ошибка. Не меняется значение переменной i. Пример 2. main() { int j=5; while(j=5) { printf(“n j=5n”); j++; } } //Ошибка. Используется “=”, а нужно “= =” Пример 3. printf(“n. Отвечайтеyes или no (y/n): ”); scanf(“%c”, &ch); while(ch!=’y’&&ch!=’n’) { printf(“n. Отвечайтеyes или no (y/n): ”); scanf(“%c”, &ch); } Пример 4 while (index<size&&data[index]!=key) index++; • return(data[index]= =key)? index: -1; Возвращается неотрицательный индекс, определяющий положение целочисленной переменной key в массиве data, и – 1, если в массиве такого значения нет.
2. Оператор цикла while do Синтаксис do оператор while (условное-выражение); Цикл do while прекращает выполняться, когда условное выражение обращается 0 (ложно). Выход из данного цикла аналогичен выходу из цикла while: Пример программы сравнения двух строк int length(char str[]) #include <stdio. h> { main() int index=0; { while (str[index++]!=0) ; extern int compare(char str 1[], char str 2[]); return - - index; } char str 1[80], str 2[80]; int compare(char str 1[], str 2[]) printf(“n Введите первую строку: ”); { scanf(“%s”, str 1); extern int length(char str[]); printf(“n Введите вторую строку: ”); int len 1=length(str 1); scanf(“%s”, str 2); int len 2=length(str 2); if (compare(str 1, str 2)<0) int index=-1; printf(“nn Первая строка меньше второй”); int minlength; else if (compare(str 1, str 2)= =0) minlength=(len 1<len 2)? len 1: len 2; printf(“nn Первая строка равна второй”); do else index++; printf(“nn Первая строка больше второй”); while (index<minlength&&str 1[index]= = str 2[index]); } if (len 1= = len 2&&str 1[index] = = str 2[index]) return 0; else if (len 1< len 2&&str 1[index] = = str 2[index]) return -1; else if (len 1> len 2&&str 1[index] = = str 2[index]) return 1; else if (str 1[index] < str 2[index]) return -1; else return 1; }
3. Оператор цикла for Это наиболее общая форма оператора цикла. Синтаксис: for ([выражение 1]; [выражение 2]; [выражение 3]) оператор; Каждое из трех выражений можно не указывать. Следует знать, что: выражений 1– служит для инициализации параметра цикла; выражение 2– служит для выполнения проверки на окончание цикла. (максимальное или минимальное значение параметра цикла); Выражение 3– служит для изменения значения параметра цикла. Цикл for выполняется следующим образом: Вычисляется выражение 1; Вычисляется выражение 2. Если результат равен 0, то цикл прекращается, иначе продолжается. Выполняются операторы тела цикла; Вычисляется выражение 3 ; Переход к пункту 2. Замечание Появление в любом месте тела цикла оператора continueприводит к. немедленному переходу к пункту 4. Эквивалентность цикла for и цикла while Пример for (выражение 1; выражение 2; выражение 3) оператор; #include <stdio. h> переводится в void main(void) выражение 1; { long i; while (выражение 2) for (i=1000000; i>=0; i-=2) { оператор; printf(“n%ld”, i); выражение 3; printf(“n”); } }
Оператор continue. Если оператор continue встречается в операторе цикла, то он передает управление на начало следующей итерации цикла. В циклах while и do while – на проверку условия, в цикле for – на приращение. Этот оператор необходим, если нужно закончить текущую итерацию цикла и не выполнять оставшиеся операторы, а сразу перейти к следующей итерации цикла. #include <stdio. h> void main(void) // Пример программы, выводящей натуральные числа кратные 7. { int i; for (i=1; i<1000; i++) { if(i%7) continue; printf(“%8 d”, i); } }
Функции в языке С Функция – это самостоятельная программная единица, спроектированная для решения конкретной задачи, обычно повторяющаяся несколько раз. Объявление функции Основная форма описания (definition) имеет вид: тип <имя функции>(список параметров) { тело функции } Тип определяет тип значения, которое возвращает функция с помощью оператора return. Если тип не указан, то по умолчанию предполагается, что функция возвращает целое значение (типа int). Список параметров состоит из перечня типов и имен параметров, разделенных запятыми. Функция может не иметь параметров, но круглые скобки необходимы в любом случае. В списке параметров для каждого параметра должен быть указан тип. Пример: f(int x, float z)
Оператор return Оператор имеет два варианта использования. Во-первых, это оператор вызывает немедленный выход из функции и возврат в вызывающую программу. Во-вторых, этот оператор может использоваться для возврата значения функции. Отметим, что в теле функции может быть несколько операторов return, но может и не быть ни одного. В этом случае возврат в вызывающую программу происходит после выполнения последнего оператора тела цикла. Примеры функций. float step(float a, int b) { float i; if (a<0) return -1; /* основание отрицательное */ for(i=1; i<=b; i++) a*=a; return a; } max(int a, int b) { int m; if (a>b) m=a; else m=b; return m; } max(int a, int b) { if (a>b) return a; return b; } max(int a, int b) { return (a>b)&a: b; }
В случае, когда оператора return нет или за ним нет значения, то возвращаемое значение неизвестно. Если функция должна возвращать значение, но не делает этого, компилятор выдает предупреждение. Все функции, возвращающие значения, могут использоваться в выражениях языка С, но они не могут использоваться в левой части оператора присваивания, за исключением тех случаев, когда возвращаемым значением является указатель. Когда функция не возвращает никакого значения, она должна быть описана как функция типа void (пустая). Пример вывода горизонтальной строки, состоящей из одного символа. void gorizontal_line(char ch) { int i; for(i=0; i<80; i++) printf(“%c”, ch); } Можно не объявлять функцию типа void тогда она по , умолчанию будет иметь тип int и не возвращать никакого значения. Это вызовет предупреждающее сообщение компилятора, но не будет препятствием для компиляции. Однако объявление типа возвращаемого значения является хорошим правилом.
Прототипы функций Особенность языка С является то, что для создания правильного машинного кода функции ему необходимо сообщить до первого вызова функции тип возвращаемого результата, а также количество и тип аргументов. Для этих целей в С используется понятие прототип функции. Прототип функции задается следующим образом: тип <имя функции>(список параметров); Использование прототипа функции является объявлением функции (declaration Чаще всего прототип функции ). совпадает с заголовком в описании функции, хотя это не всегда так. При объявлении функции компилятору важно знать имя функции, количество и тип параметров, и тип возвращаемого значения. При этом имена формальных параметров не играют никакой роли и игнорируются компилятором. Поэтому прототип функции может выглядеть так: int func(int a, float b, char *c); int func(int, float, char *); или Эти объявления равносильны.
Пример: #include <stdio. h> float sqr (float a /* это прототип функции */ ); void main(void) { float b; b=5. 2; printf(“ вадрат числа равен %f”, b, sqr(b)); К %f } float sqr (float a /* это описание функции */ ) { return a*a; }
Если функция не имеет аргументов, то при объявлении прототипа такой функции следует вместо аргументов писать ключевое слово void Это касается и функции. main(). Ее объявление должно иметь вид void main (void ) или main(void ). #include <stdio. h> void line(void); /* прототип функции */ void main(void) { line(); } void line(void) { int i; for(i=0; i<80; i++) printf(“-“); } Ранее говорили о стандартных заголовочных файла (header files Заголовочные файлы содержат два типа ). информации: первый – это отдельные определения, которые используются функциями. Второй – это прототипы функций, относящиеся к этому заголовочному файлу. Примеры таких заголовочных файлов: stdio. h, string conio iostream stdlib. h, math. h и т. д. . h,
Область действия и область видимости Область действия (scope rules переменной – это правила, ) которые устанавливают, какие данные доступны из данного места программы. В языке С каждая функция – это отдельный блок программы. Попасть в тело функции нельзя иначе, как через вызов данной функции. С точки зрения области действия переменных различают три типа переменных: глобальные, локальные и формальные параметры. Правила области действия определяют, где каждая из них может применяться. Локальные переменные – это переменные, объявленные внутри блока, в частности внутри функции. Язык С поддерживает простое правило: переменная может быть объявлена внутри любого блока программы. Локальная переменная доступна внутри блока, в котором она объявлена. Область действия локальной переменной – блок. Локальная переменная существует, пока выполняется блок, в котором она объявлена. При выходе из блока эта переменная (и ее значение) теряется.
Пример #include <stdio. h> void f(void); void main(void) { int i; i=1; f(); printf(“ функцииmain значениеi равно %dn”, i); В } void f(void) { int i; i=10; printf“В функцииf() значениеi равно % ( dn”, i); }
Пример показывает, что при вызове функции значение переменной i, объявленной в main() не изменилось. Формальные параметры – это переменные, объявленные при описании функции как ее аргументы. Формальные параметры могут использоваться в теле функции, также как локальные переменные. Область действия формальных параметров – блок, являющийся телом функции. Глобальные переменные – это переменные, объявленные вне какой-либо функции. В отличие от локальных переменных глобальные переменные могут быть использованы в любом месте программы, но перед их использованием они должны быть объявлены. Область действия глобальной переменной – вся программа.
Классы памяти В языке С есть инструмент, позволяющий управлять ключевыми механизмами использования памяти и создавать гибкие и мощные программы. Этот инструмент – классы памяти. Каждая переменная принадлежит к одному из классов памяти. Эти классы описываются следующими ключевыми словами: auto – автоматическая; extern – внешняя; static – статическая; register– регистровая. Примеры: static int sum; register int plus; Если ключевого слова перед спецификацией типа локальной переменной при ее объявлении нет, то по умолчанию она принадлежит классу auto. Автоматические переменные имеют локальную область действия. Они известны только внутри блока, в котором они определены. Автоматическая переменная создается (т. е. ей отводится место в памяти программы) при входе в блок функции. При выходе из блока автоматическая переменная пропадает, а область памяти, в которой находилась эта переменная, считается свободной и может использоваться для других целей. Автоматические переменные хранятся в оперативной памяти ЭВМ, точнее, в стеке. Регистровые (register переменные хранятся в регистрах ) процессора. Доступ к переменным, хранящимся в регистровой памяти, значительно быстрее, чем к тем, которые хранятся в оперативной памяти ЭВМ. В остальном, регистровые переменные аналогичны автоматическим переменным.
Пример: register int quick; Внешняя переменная (extern относится к ) глобальным переменным. Она может быть объявлена как вне, так и внутри тела функции: void f(void) { extern int ; j /* объявление внешней переменной внутри функции */ } Для глобальных переменных выделяется место в разделе глобальных переменных и констант. Объявлений переменной как внешней может быть несколько, описание же переменной должно быть только одно. Следующий пример демонстрирует разные способы описания внешних переменных:
int var ; /* описана внешняя переменная */ var void main (void ) { extern int var /* объявлена та же внешняя переменная */ ; … } func 1() { extern int var /* объявлена внешняя переменная */ 1; var 1 /* переменная также внешняя, хотя она не описана в этом блоке var */ … } func 2() { /* переменная var внешняя */ /* переменная var 1 невидима для этой функции в этом блоке */ } int var /* описание внешней переменной */ 1; fun 3() { /* для этой функции var 1 внешняя */ int var /* переменная ; var описана как локальная и не связана с глобальной переменной var */ /* по умолчанию эта переменная автоматическая */ } func 4() { /* здесь переменная является внешней глобальной переменной */ var auto int var ; /* переменная 1 var 1 локальная автоматическая переменная */ }
При описании статических переменных перед описанием типа ставится ключевое слово static. Область действия локальной переменной - вся программа. Место в памяти под локальные переменные выделяется в начале работы программы в разделе глобальных и статических переменных. Однако область видимости локальных статических переменных такая же, как и у автоматических. Значение статической переменной сохраняется от одного вызова функции до другого. Локальные статические переменные инициализируются нулем, если не указан другой инициализатор. При этом описание с инициализацией static int count = 10; локальной статической переменной count вызывает однократную инициализацию переменной count при выделении для нее памяти. При последующих вызовах функции, в которой описана эта переменная, инициализации не происходит. Это позволяет использовать такую переменную для счетчика вызова функций.
#include <stdio. h> /* использование статической переменной */ void trystat (void /* прототип функции */ ); void main(void) { int i; for(i=1; i<=3; i++) { printf(“Call # %dn”, i); trystat(); } } void trystat(void) { int auto_l=1; static int stat_l=1; printf(“auto_l=%d stat_l=%dn”, auto_l++, stat_l++); }
Можно также описать глобальную (внешнюю) статическую переменную, т. е. описать переменную типа static вне любой функции. Отличие внешней переменной от внешней статической переменной состоит в области их действия. Обычно внешняя переменная может использоваться функциями в любом файле, в то время как внешняя статическая переменная только функциями того файла, где она описана, причем только после ее определения. Все глобальные переменные – и статические, и нестатические – инициализируются нулем, если не предусмотрено другой инициализации. В следующей таблице приведены область действия и продолжительность существования переменных разных классов памяти:
Класс памяти Ключевое слово Автоматический Регистровый Статический локальный Статический глобальный Внешний auto register static Время существования временно постоянно Область действия блок static постоянно файл extern постоянно программа
Параметры и аргументы функции В языке С есть особенность, связанная с тем, что все аргументы функции передаются по значению. При вызове функции в стеке выделяется место для формальных параметров функции, и в это выделенное место заносится значение фактического параметра, т. е. значение параметра при вызове функции. Далее функция использует и меняет значения в стеке. При выходе из функции измененные значения параметров теряются. В языке С вызванная функция не может изменить переменные, указанные в качестве фактических параметров в функции при обращении к ней. Функция swap(), которая должна менять значения параметров местами, не будет фактически это делать. void swap(int a, int b) { int tmp=a; a=b; b=tmp; }
Если необходимо, функцию можно приспособить для изменения аргументов. Надо передавать в качестве параметра не значение, а адрес переменной, которую нужно изменять, т. е. передавать указатель на переменную. Такой подход называется передачей параметров по ссылке. Вызванная функция должна описывать аргумент как ссылку и обращаться к фактической переменной косвенно, через эту ссылку. Если в качестве аргумента берется имя массива, то передаваемое функции значение фактически есть адрес первого элемента массива. Функция swap 1() будет теперь выглядеть так: void swap 1(int *a, int *b) { int tmp=*a; *a=*b; *b=tmp; }
Иллюстрацию использования этих двух способов передачи параметров дает следующая программа: #include <stdio. h> void swap(int a, int b); void swap 1(int *a, int *b); void main(void) { int x=5, y=10; printf“Сначалаx = %d y = %dn”, x, y); ( swap(x, y); printf(“ осле вызова П swap x = %d y = %dn”, x, y); printf“Ничего не изменилось”); ( swap 1(&x, &y); /* теперь передаем адреса переменных */ printf (“После вызова swap 1 x = %d y = %dn”, x, y); printf (“Значения поменялись”); return 0; } // функции описаны выше
Результат работы программы следующее: Сначала x=5, y=10 После вызова swap x=5, y=10 Ничего не изменилось После вызова swap 1 x=10, y=5 Особенности вызова: swap(5, 10); /* можно */ swap 1(&5, &10); /* нельзя */ Если в качестве аргумента функции используется массив, есть лишь один способ – передача параметров по ссылке. В качестве аргумента функции надо указать адрес начала массива. Это можно сделать следующими способами: function(int ar[10]); function(int ar[]); function(int *ar);
Пример функции для работы с массивом: #include <stdio. h> void sort(int ar[], int n); void main(void) { int mass[10]={1, 3, -5, 7, 9, 0, 22, 4, 6, 8}; int size=10, i; printf(“Before sorting: ”); for(i=0; i<10; i++) printf(“%d”, mass[i]); printf(“n”); sort(mass, size); printf(“After sorting: ”); for(i=0; i<10; i++) printf(“%d”, mass[i]); printf(“n”); } void sort(int ar[], int n) /* можно заголовок заменить на void sort(int ar[10], int n) или void sort(int *ar, int n) */ { int i, j, tmp; for(i=0; i<n; i++) { for(j=i+1; j<n; j++) { if (ar[i]>ar[j]) { tmp=ar[i]; ar[i]=ar[j]; ar[j]=tmp; } }
Пример программы умножения двух матриц размера #include <stdio. h> void multiply(int U[3][3], int V[3][3], int W[3][3]); void main(void) { int A[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; int B[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; int i, j, С[3][3]; multiply(A, B, C); printf(“Matrix C: n”); for(i=0; i<3; i++) printf(“%d %d %dn”, C[i][0], C[i][1], C[i][2]); } void multiply(int U[3][3], int V[3][3], int W[3][3]) { int i, j, k; for(i=0; i<3; i++) { for(j=0; j<3; j++) { W[i][j]=0; For(k=0; k<3; k++) W[i][j]+=U[i][k]*V[k][j]; } } }
Массивы и указатели Массив – одна из наиболее простых и известных структур данных. Под массивом в языке С понимают набор данных одного и того же типа, собранных под одним именем. Каждый элемент массива определяется именем массива и порядковым номером элемента, который называется индексом. Индекс в языке С всегда целое число. Объявление массива в программе Основная форма объявления массива размерности N такова: тип <имя массива>[размер1][размер2]…[размер N]; Размер массива в языке С можно задавать константой или константным выражением. Нельзя задавать массив переменного размера. Для этого существует отдельный механизм, называемый динамической памятью. В языке С индекс всегда начинается с нуля. Когда говорим о первом элементе массива, то имеем в виду элемент с индексом 0. Для одномерного массива легко подсчитать, сколько байт в памяти будет занимать массив: Количество байт=<размер базового типа>*<количество элементов> Можно определить массив любого определенного ранее типа, например unsigned arr[40], long double b[10], char ch[80 ];
Массивы символов. Строки. Массивы типа char – символьные массивы – занимают в языке С особое место. Во многих языках есть специальный тип данных – строка символов (string В С отдельного типа строки символов ). нет, а реализована работа со строками путем использования одномерных массивов типа char. В С, символьная строка – это одномерный массив типа char, заканчивающийся нулевым байтом (каждый бит байта равен 0). Для нулевого байта определена специальная символьная константа ‘ ’ Это следует. учитывать при описании соответствующего массива символов. Так, если строка должна содержать N символов, то в описании массива следует указать N+1 элемент. Например, описание char str [11]; предполагает, что строка содержит 10 символов, а последний байт зарезервирован под нулевой байт. Язык С допускает строковые константы. Строковая константа – это список литер, заключенных в двойные кавычки. Например, “Borland C++” В памяти это будет выглядеть так B o r l a n d C + +
Есть два простых способа ввода строки с клавиатуры: Функция scanf() со спецификатором ввода %s обеспечивает ввод символов до первого пробела. Функция gets() (из файла stdio позволяет вводить. h) строки, содержащие пробелы. Ввод оканчивается нажатием клавиши Enter. Обе функции автоматически ставят в конец строки нулевой байт. В качестве параметра в этих функциях используется просто имя массива. Вывод строк производится функциями printf или () puts(). Обе функции выводят содержимое массива до первого нулевого байта. Функция puts() добавляет в конце выводимой строки символ новой строки. В функции printf переход на новую строку надо () предусматривать в строке формата самим.
Пример #include <stdio. h> /* ввод строки с клавиатуры */ void main (void ) { char str [80]; /* место под строку */ printf (“Bведите строку длиной менее 80 символов: “); gets(str); /* чтение строки с клавиатуры, пока не нажата клавиша Enter */ printf (“Вы ввели строку s n”, str); % printf (“Bведите еще одну строку длиной менее 80 символов: “); scanf(“%s”, &str); /* чтение строки с клавиатуры, пока не встретится пробел */ printf (“Вы ввели строку ”); puts(str); }
Функции работы со строками Для работы со строками существует специальная библиотека, описание которой находится в файле string. h. Наиболее часто используются функции strcpy(), strcat(), strlen(), strcmp() Вызов функции strcpy()имеет вид strcpy(s 1, s 2). Содержимое строки s 2 копируется в строку s 1. Вызов функции strcat имеет вид strcat () (s 1, s 2). Функция strcat присоединяет строку s 2 к () строке s 1 и помещает ее в массив, где находится строка s 1, при этом строка s 2 не изменяется. Нулевой байт, который завершал строку s 1, будет заменен первым символом строки s 2.
Пример использования данных функций #include <stdio. h> #include <string. h> void main(void) { char s 1[20], s 2[20]; strcpy(s 1, ”Hello, “); strcpy(s 2, ”World!”); puts(s 1); puts(s 2); strcat(s 1, s 2); puts(s 1); puts(s 2); }
Вызов функции strcmp() имеет вид strcmp(s 1, s 2). Функция strcmp сравнивает строки s 1 и s 2 и возвращает значение () 0, если строки равны, то есть содержат одно и то же число одинаковых символов. Сравнение в лексикографическом смысле. Если s 1 лексикографически (в смысле словаря) больше s 2, то функция возвращает положительное значение, если меньше, то отрицательное значение. Вызов функции strlen() имеет вид strlen(s). Функция возвращает длину строки s, при этом завершающий нулевой байт не учитывается. Пример #include <stdio. h> #include <string. h> void main(void) { char s[80]; printf”Введите строку: “); ( gets(s); printf(“ трока n %s n имеет длину символов С %d n”, s, strlen(s)); }
Функции из стандартной библиотеки для работы с строками 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. char *strcat(char *dest, char *source) конкатенация строк dest и source. – char *strncat(char *dest, char *source, unsigned maxlen) – присоединяет maxlen символов строки source к строке dest. char *strchr(char *source, char ch) поиск в строке source первого вхождения – символа ch. int strcmp(char *s 1, char *s 2) возвращает 0, если s 1 = = s 2, возвращает <0, если – s 1<s 2, и возвращает >0, если s 1>s 2. int strncmp(char *s 1, char *s 2, int maxlen)возвращает 0, если s 1 = = s 2, возвращает – <0, если s 1<s 2, и возвращает >0, если s 1>s 2. Сравниваются только первые maxlen символов. int stricmp (char *s 1, char *s 2) – возвращает 0, если s 1 = = s 2, возвращает <0, если s 1<s 2, и возвращает >0, если s 1>s 2. Не проверяются регистры букв. int strnicmp(char *s 1, char *s 2, int maxlen) – возвращает 0, если s 1 = = s 2, возвращает <0, если s 1<s 2, и возвращает >0, если s 1>s 2. Сравниваются только первые maxlen символов. Не проверяются регистры букв. char *strcpy(char *dest, char *source) копирование строки source в строку dest. – char *strncpy(char *dest, char *source, unsigned maxlen) – копирование maxlen символов строки source в строку dest. int strlen (char *s) – выдает число символов в строке без учета нулевого символа конца строки. char *strlwr(char *s) – переводит всю строку в нижний регистр (в строчные буквы)
12. char *strupr(char *s) – переводит всю строку в верхний регистр (в прописные буквы) 13. char *strdum(char *s) – вызывает функцию malloc и отводит место под копию s. 14. char *strset(char *s, char ch) – заполняет всю строку символами ch. 15. char *strnset(char *s, char ch, unsigned n) – заполняет первые n позиций строки s символами ch. 16. char *strrev(char *s) – инвертирует все буквы в строке s. 17. int strcspn(char *s 1, char *s 2) – возвращает длину начального сегмента строки s 1, которая состоит исключительно из символов, не содержащихся в строке s 2. 18. char *strpbrk(char *s 1, char *s 2) – просмотр строки s 1 до тех пор, пока в ней не встретится символ, содержащийся в s 2 19. char *strrchr(char *s, char ch) – просматривает строку s до последнего появления в ней символа ch. 20. int strspn(char *s 1, char *s 2) – возвращает длину начального сегмента строки s 1, который состоит исключительно из символов из строки s 2 21. char *strstr(char *s 1, char *s 2) – возвращает указатель на позицию вхождения строки s 2 в строку s 1. Если вхождения нет, то значение указателя NULL. 22. char *strtok(char *s 1, char *s 2) – предполагается что строка s 1 состоит из фрагментов, разделенных одно- или многосимвольными разделителями из строки s 2. При первом обращении к strtok выдается указатель на первый символ первого фрагмента строки s 1. последующие вызовы с заданием нуля вместо первого аргумента будут выдавать адреса дальнейших фрагментов из строки s 1 до тех пор, пока фрагментов не останется.
Двумерные массивы Язык С допускает многомерные массивы, простейшей формой которых является двумерный массив (two-dimensional array Двумерный массив a[3][4] можно представить ). в виде таблицы A[0][0] A[0][1] A[0][2] A[0][3] A[1][0] A[1][1] A[1][2] A[1][3] A[2][0] A[2][1] A[2][2] A[2][3] Количество байт = <размер типа данных>*<количество строк>*<количество столбцов> Следует помнить, что память для всех массивов, которые определены как глобальные, отводится в процессе компиляции и сохраняется все время, пока работает программа. Часто двумерные массивы используются для работы с таблицами, содержащими текстовую информацию. Очень часто используются массивы строк.
Пример: #include <stdio. h> #include <string. h> void main(void) { char text[5][20]; strcpy(text[0], ”Turbo Basic”); strcpy(text[1], ”Turbo Pascal”); strcpy(text[2], ”Borland C++”); strcpy(text[3], ”Turbo Prolog”); strcpy(text[4], ”Visual C++”); } T u r b o B a s i c T u r b o P a s c a l B o r l a C + + T u r b o r o l o g V i s u a C + + n d P l
Инициализация массивов В С имеется возможность инициализации данных, то есть присваивание начальных значений. Самый простой: в процессе объявления массива можно указать в фигурных скобках список инициализаторов. Например: float farr [6]={1. 1, 2. 2, 3. 4, 4. 0. -5. 5}; int a[3][5]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; Инициализации int a[3][5]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; int и a[3][5]={1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}; эквивалентны. Количество инициализаторов не обязательно совпадает с количеством элементов массива. Например, различные инициализации одного и того же массива. int a[3][5]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; int и a[3][5]={1, 2, 3}, {4, 5, 6, 7, 8}, {9, 10, 11}; Соответствующие массивы будут заполнены следующим образом
Символьные массивы могут инициализироваться как обычный массив или как строка символов. Например: char str [15]={‘ o’, ’ l’, ’ n’, ’ B’, ’ r’, ’ a’, ’ d’, ’ ‘, ’ C’, ’+’}; или char str[15]=” Borland C++”; Допускается объявление и инициализация массива без явного указания размера массива. Примеры: char str []=”Это объявление и инициализация массива символов”; int mass[]={1, 2, 3, 4, 5, 6}; int arr[][3]={1, 2, 3, 5, 6, 7, 8, 9, 0};
Пример сортировки элементов массива #include <stdio. h> void main(void) { int arr[10]={1, 23, 4, 7, 8, 0, 1, 9, 4, 7}; int i, j, tmp; printf“Неотсортированный массив: ”); ( for(i=0; i<10; i++) printf(“%d “, arr[i]); printf(“n”); for(i=0; i<9; i++) { for(j=i+1; j<10; j++) { if (a[i]>a[j]) { tmp=a[i]; a[i]=a[j]; a[j]=tmp; } } } printf (“Oтсортированный массив: ”); for(i=0; i<10; i++) printf(“%d “, arr[i]); printf(“n”); }
Указатели Важная конструкция в С, С++ - это указатели. Они необходимы для успешного использования функций и динамического распределения памяти. Кроме того многие конструкции языка С, С++, Visual C++ требуют применения указателей. Объявление указателей. Указатель – это переменная, которая содержит адрес некоторого объекта (адрес памяти, целое число). Указатель объявляется следующим образом: тип *<имя переменной>; В этом объявлении тип – некоторый тип языка С, определяющий тип объекта, на который указывает указатель (адрес которого содержит). Символ * означает, что следующая за ним переменная является указателем. Пример: char *ch; int *temp, i, j; float *pf, f;
Операции над указателями С указателями связаны две специальные операции & и *. Обе операции являются унарными (один операнд) и обладают наивысшим приоритетом. Операция & соответствует операции “взять адрес”. Операция * соответствует операции “взять значение по указанному адресу ”. Простейшие действия с указателями иллюстрируются следующей программой: #include <stdio. h> void main(void) { float x=10. 1, y; float *pf; pf=&x; y=*pf; printf(“x=%f y=%f”, x , y ); *pf++; printf(“x=%f y=%f”, x , y ); y=1+*pf*y; printf(“x=%f y=%f”, x , y ); }
К указателям можно применить операцию присваивания. Указатели одного и того же типа могут использоваться в операциях присваивания, как и любые другие переменные. Рассмотрим пример: #include <stdio. h> void main(void) { int x=10; int *p, *g; p=&x; g=p; printf“%p”, p); ( /* печать содержимого*/ p printf (“%p”, g); /* печать содержимого*/ g printf (“%d %d”, x, g); /* печать величины х и величины по адресуg */ }
В С допустимо присвоить указателю любой адрес памяти. Однако, если объявлен указатель на целое int *pi, а по адресу, который присвоен данному указателю, находится переменная типа float то при компиляции программы , будет выдано сообщение об ошибке в строке p=&x; Эту ошибку можно исправить, преобразовав указатель на int к указателю на float явным преобразованием типа: p=(int*)&x; Но при этом теряется информация о том, на какой тип указывает исходный указатель. Как и над другими типами переменных, над указателями можно производить арифметические операции: сложение и вычитание (операции ++ и – являются частными случаями операций сложения и вычитания). Пример: #include <stdio. h> void main(void) { int *p; int x; p=&x; printf(“%p %p”, p, ++p); }
После выполнения этой программы при выполнении операции ++p значение указателя увеличивается на 2 (указание на адрес следующего целого, т. е. учитывается базовый тип указателя). Операции над указателями не ограничиваются только операциями ++ и --. К указателям можно прибавлять и из них можно вычитать целые числа. Общая формула для вычисления значения указателя после выполнения операции p=p+n; будет иметь вид <p>=<p>+n*<количество байт памяти базового указателя типа> Указатели можно сравнивать. Применимы все 6 операций: <, >, <=, >=, ==, !=.
Связь указателей и массивов В С существует важная связь между массивами и указателями. В С принято, что имя массива – это адрес памяти, начиная с которого расположен массив, то есть адрес первого элемента массива. Таким образом, если был объявлен массив int plus [10]; то plus является указателем на массив, точнее, на первый элемент массива. Операторы p 1=plus; и p 1=&plus[0]; приведут к одному и тому же результату. Для того, чтобы получить значение 6 -го элемента массива можно написать plus[5] или *(p 1+5) и результат будет один и тот же. Преимущество использования второго варианта состоит в том, что арифметические операции над указателями выполняются быстрее, если мы работаем с подряд идущими элементами массива. Если же выбор элементов массива случаен, то быстрее и более наглядна работа с индексами. Примеры работы со строками:
#include <stdio. h> #include <ctype. h> void main(void) { char str[]=”String From Letters in Different Registers”; int i ; printf (“Строка Будет Напечатана Заглавными Буквами”); while(str[i]) printf(“%c”, toupper(str[i++])); } #include <stdio. h> #include <ctype. h> void main(void) { char str[]=”String From Letters in Different Registers”; int i ; printf (“Строка Будет Напечатана Строчными Буквами”); p=str; while(*p) printf(“%c”, tolower(*p++)); } // В первом примере вывод текста заглавными буквами, во втором, строчными.
Массивы указателей Указатели, как и переменные любого другого типа, могут объединяться в массивы. Объявление указателей на 10 целых чисел имеет вид int *x[10]; Каждому из элементов массива можно присвоить адрес: например, третьему элементу присвоим адрес целой переменной y: x[2]=&y; Чтобы найти значение переменой y, можно написать *x[2]. #include <stdio. h> // Пример использования массива указателей при обработке массива строк. #include <string. h> #include <stdlib. h> #include <conio. h> void main(void) { char *ext[]={“exe”, ”com”, ”dat”, ”c”, ”pas”, ”cpp”}; char ch, sl[80]; for(; ; ) { do { printf (“Файлы с расширением: n”); printf(“ 1, exen”); printf(“ 2, comn”); } printf(“ 3, datn”); printf(“ 4, cn”); printf(“ 5, pasn”); printf(“ 6, cppn”); printf(“ 7, guitn”); printf (“Ваш выбор: ”); n ch=getche(); printf(“n”); } while((ch<’ 1’||(ch>’ 7’)); if (ch==’ 7’) break; strcpy(sl, ”dir *. ”); strcat(sl, ext[ch-1]); system (sl); }
Очень часто массив указателей используется, если надо иметь ссылки на стандартный набор строк. Например, если мы хотим хранить сообщения о возможных ошибках, то это удобно сделать так: char *errors[]={“Cannot open file”, “Cannot close file”, “Allocation error”, “System error”}; При таком объявлении строчные константы будут занесены в раздел констант в памяти, массив указателей будет состоять из четырех элементов, под которые будет выделена память, и эти элементы будут инициализированы адресами, указывающими на начало этих строчных констант. Вообще строчная константа в языке С ассоциируется с адресом начала строки в памяти, тип строки получается char* (указатель на тип char). Поэтому возможно и активно используется присваивание: char *pc; pc=”hello, World!”; В языке С возможна также ситуация, когда указатель указывает на указатель. В этом случае описание будет иметь следующий вид: int **point ;
point имеет тип указатель на int. Соответственно, чтобы получить целочисленное значение переменной, на которую указывает point надо в , выражении использовать **point. Типы, определяемые пользователем Кроме известных типов данных язык С позволяет создавать еще 5 типов данных: - структуры (structure ); - объединения (union); - перечисляемый тип (enumeration ); - поля битов (bit fields ); - с помощью оператора typedef создается новое имя (псевдоним) для уже существующего типа.
Структура объединяет несколько переменных, возможно разного типа. Переменные, которые объединены структурой, называются элементами или полями структуры. Пример определения структуры: struct student { char name[30]; int kurs; char group [3]; int index ; } В данном случае под именем student задан шаблон структуры и определен новый тип struct student. Выделение памяти не происходит. Для того, чтобы объявить конкретные переменные типа struct student , надо написать struct student stud , stud 2; 1
Компилятор автоматически выделит память под данные переменные (непрерывный участок). Можно в одном операторе задать шаблон структуры и объявить переменные struct student { char name[30]; int kurs; char group[3]; int index ; } stud 1, stud 2; Доступ к конкретному элементу структуры осуществляется с помощью операции “точка” (dot). Например strcpy(stud. name, ” Иванов И. И. ”); или printf(“%s”, stud 2. group); Структуры, как и переменные другого типа, могут объединяться в массивы структур. Например: struct student stud kurs[200]; 1 Этот оператор создаст в памяти 200 переменных типа структуры с шаблоном student и именами stud 1 kurs[0], stud 1 kurs[1] и т. д.
Для доступа к полю kurs 25 -го элемента массива используем конструкцию stud 1 kurs[24]. kurs Если объявлены две переменные типа структуры с одним шаблоном, можно сделать присваивание stud 1=stud 2; При этом произойдет побитовое копирование каждого поля одной переменной в соответствующее поле другой переменной. В то же время нельзя использовать операцию присваивания переменных типа структуры, шаблоны которых описаны под разными именами, пусть даже совсем идентично. Переменная типа структуры может быть глобальной, локальной переменной и формальным параметром. Можно использовать структуру или элемент структуры как любую другую переменную в качестве параметра функции.
Пример: func 1(first. a); или func 2(&second. b); Можно в качестве формального параметра передать по значению всю структуру: #include <stdio. h> /* использование структуры в качестве параметра функции */ struct stru { int x; char y; }; void f(struct stru param); /* прототип функции */ main(void) { struct stru arg; arg. x=1; arg. y=’ 2’; f(arg); return 0; } void f(struct stru param) { printf(“%d %d n”, param. x, param. y); }
Можно также создать указатель на структуру и передавать аргумент типа структуры по ссылке. Объявить указатель на структуру можно следующим образом: trsuct stru*adr_pointer ; adr_pointer– переменная типа указатель на структуру struct stru. Если передавать структуру по значению, то все элементы структуры заносятся в стек. При использовании в качестве элемента структуры массива возможно переполнение стека. При передаче по ссылке в стек заносится только адрес структуры. При этом копирование структуры не происходит, а также появляется возможность изменять содержимое элементов структуры. struct complex { float x; float y; } c 1, c 2; struct complex /* объявление указателя */ *a; a=&c 1; /* указателю присваивается адрес переменнойс1 */ Получить значение элемента x переменной c 1 можно так: (*a). x; Использование указателей на структуру встречается часто. Поэтому, кроме способа получения значения элемента структуры, используя (*a). x, применяется специальная операция -> (стрелка, arrow). Вместо (*a). x можно использовать a -> x. В качестве элементов структуры можно использовать массивы, структуры и массивы структур.
Пример: struct addr { char city[30], street[30]; int house; }; struct fulladdr { struct address; int room; char name[30]; } f, g; f. address. house=101; /* присвоение значения элементу house структуры address переменной */ f
Доступ к отдельному биту В отличие от других языков программирования язык С обеспечивает доступ к одному или нескольким битам в байте или слове. Один из методов, встроенных в язык С и позволяющих иметь доступ к биту, - это поля битов (bit-fields В действительности поля битов – это ). специальный тип членов структуры, в котором определено, из скольких бит состоит каждый элемент. Основная форма объявления такой структуры следующая: struct имя_структуры { тип имя 1: длина_в_битах; тип имя 2: длина_в_битах; …. тип имя N: длина_в_битах; }; тип: { nt, unsigned, signed} i Имя. I может быть пропущено, тогда соответствующее количество бит не используется (пропускается). Длина структуры всегда кратна 8. Так, если указать: struct onebit { unsigned one _bit: 1; } obj; то для переменной obj будет выделено 8 бит, но использоваться будет только первый. В структуре могут быть смешаны обычные переменные и поля битов.
Объединения В языке С определен тип для размещения в памяти нескольких переменных разного типа. Это объединение. Объявляется объединение так же, как и структура, например: union u { int i; char ch; long int l; }; Таким образом задается шаблон объединения. Далее можно объявить переменные данного типа. Например: union u alfa beta; , Можно объявлять переменные одновременно с заданием шаблона. В отличие от структуры для переменной типа union места в памяти выделяется ровно столько, сколько надо элементу объединения, имеющему наибольший размер в байтах. В приведенном выше примере под переменную alfa будет выделено 4 байта памяти. Все элементы располагаются в одном и том же месте памяти. Синтаксис использования элементов объединения такой же, как для структуры: u. ch=’ 5’; Для объединений также разрешена операция ->, если обращение к объединению с помощью указателя.
Программа, приведенная ниже, выдает на экран двоичный код ASCII символа, вводимого с клавиатуры: #include <stdio. h> #include <conio. h> /* использование полей битов и объединений */ void decode(union bits struct byte { u) int b 1: 1; int b 2: 1; { int b 3: 1; if (u. b. b 8) printf(“ 1”); int b 4: 1; else printf(“ 0”); int b 5: 1; if (u. b. b 7) printf(“ 1”); int b 6: 1; int b 7: 1; else printf(“ 0”); int b 8: 1; if (u. b. b 6) printf(“ 1”); }; /* определена структура – битовое поле */ else printf(“ 0”); union bits { if (u. b. b 5) printf(“ 1”); char ch; struct byte b; else printf(“ 0”); } u; /* определение объединения */ if (u. b. b 4) printf(“ 1”); void decode(union bits u); /* прототип функции */ else printf(“ 0”); main(void) if (u. b. b 3) printf(“ 1”); { do { else printf(“ 0”); u. ch=getche(); if (u. b. b 2) printf(“ 1”); printf(“: “); else printf(“ 0”); decode(u); if (u. b. b 1) printf(“ 1”); } while(u. ch!=’q’); return 0; else printf(“ 0”); } } printf(“n”);
Перечисляемый тип (enumeration – это множество поименованных ) целых констант. Перечисляемый тип определяет все допустимые значения, которые могут иметь переменные этого типа. Основная форма объявления типа следующая: num имя_типа{список_названий} список переменных; Список переменных может быть пустым. Пример определения перечисляемого типа и переменной данного типа: enum seasons {win, spr, sum, aut }; enum seasons s ; Ключом к пониманию сущности перечисляемого типа является то, что каждое из имен win, spr, sum, aut представляют собой целую величину. Если эти величины не определены по другому, то по умолчанию они соответственно равны 0, 1, 2. 3. Во время объявления типа можно одному или нескольким символам присвоить другие значения, например: enum value {one=1, two, three, ten=10, thousand=1000, next); printf(“%d %d %dn”, one, two, ten, thousand, next); На экране 1 2 10 1001 С переменными перечисляемого типа можно производить следующие операции: - присвоить переменную типа enum другой переменной того же типа; - провести сравнение с целью выяснения равенства или неравенства; - арифметические операции с константами типа enum (i=win – aut).
Переименование типов typedef – Язык С позволяет давать новое название уже существующим типам данных. Для этого используется ключевое слово typedef. При этом не создается новый тип данных. Например: typedef char SYMBOL; typedet unsigned UNSIGN; typedef float real; Достаточно часто используется оператор typedefс применением структур: typedef struct st_tag{ char name[30]; int kurs; char group[3]; int index; } STUDENT; Теперь для определения переменной можно использовать struct st_tag avar или STUDENT ava; ; Динамическое распределение памяти Принятое распределение памяти в языке С следующее:
Для глобальных переменных отводится фиксированное место в памяти на все время работы программы. Локальные переменные хранятся в стеке. Между ними находится область свободной памяти для динамического распределения. Наиболее важными функциями для динамического распределения памяти являются malloc и () free(). Они используются для динамического распределения свободной памяти. Функция malloc выделяет память, () функция free() – освобождает память. Прототипы функций в stdlib. void *malloc(size_t size); void *free(void *p); Функция malloc возвращает указатель типа void для () , правильного использования значение этой функции надо преобразовать к указателю на соответствующий тип. При успешном выполнении malloc() возвращает указатель на первый байт свободной памяти требуемого размера. Если достаточного количества памяти нет, то возвращается значение 0 (нулевой указатель). Чтобы определить количество байт, необходимых для переменной, используют операцию sizeof.
Пример использования этих функций: #include <stdio. h> #include <stdlib. h> /* Динамическое выделение памяти */ void main(void) { int *p, i; p=malloc(100*sizeof(int)); /* 100 цедых*/ под if(!p) { printf (“Недостаточно памяти! n”); exit (1); } for(i=0; i<100; i++) *(p+i)=i; for(i=0; i<100; i++) printf(“%d”, *(p++)); free(p); /*освобождение памяти * / }
В C++ используются две операции – new и delete Их. форма использования: pointer_var = new var_type; delete pointer_var; где pointer_var– указатель - типа var_type; Операция new выделяет соответствующее место для переменной в соответствующей области памяти и возвращает адрес выделенного места. Неуспешная попытка – возвращение нулевого указателя NULL. Операция deleteосвобождает соответствующую память, на которую указывает pointer _var. Удобство операции new состоит в том, что операция сама определяет размер переменной var_type автоматически и возвращает указатель, уже преобразованный к этому типу. Пример выделения памяти под массив pointer _var = new var _type [SIZE]; delete [SIZE] pointer_var; // or delete pointer_var;
Пример использования этих операций: #include <iostream. h> void main (void ) { int *p=new int // выделение памяти под целое ; if(!p) { cout<<”n Недостаточно памяти! n”; return 1; } *p=20; cout<<p<<endl; delete p // освобождение памяти ; }
Пример работы с динамически выделенной памятью под массив: #include <iostream. h> void main(void) { unsigned int size; cout<<”n Введите размер массива: ”; cin>>size; int *p=new int[size]; if(!p) { cout<<”n Недостаточно памяти !n”; return 1; } for(int i=0; i<size; i++) p[i]=i*i; // использование *p++=i*i меняет значение указателя // получаем некорректность при освобождении памяти int *q=p; for( i=0; i<size; i++) cout<<*q++<<” “; // корректно cout<<endl; delete p; } Динамическое выделение памяти целесообразно для выделения памяти под большие объекты, массивы, особенно под массивы неизвестного заранее размера
Библиотеки ввода-вывода и работа с файлами в языке С Операции ввода/вывода в языке С организованы посредством библиотечных функций. Система ввода вывода следует стандарту ANSI, называемому также буферизованным (buffered или ) форматированным (formatted вводом/выводом. Кроме того, ) поддерживается и другой метод ввода/вывода, так называемый UNIX-подобный, или неформатированный (не буферизованный) ввод/вывод. Язык С++ также поддерживает собственный объектноориентрованный ввод/вывод. Рассмотрим понятия файл (file и поток (stream Система ) ). ввода/вывода языка С поддерживает интерфейс, не зависящий от того, какое в действительности используется устройство ввода/вывода, т. е. есть абстрактный уровень между программой и физическим устройством. Эта абстракция и называется потоком. Способ же хранения информации на физическом устройстве называется файлом. Несмотря на то что устройства очень разные (терминалы, диски, ленты , принтеры и т. д. ), стандарт ANSI языка С связывает каждое из устройств с логическим устройством, называемым потоком. Так как потоки не зависят от физических устройств, то одна и та же функция может записывать информацию на диск, на МЛ или выводить на экран. Различаются два потока: текстовый (text) и двоичный (binary).
Текстовый поток – это последовательность символов. Двоичный поток – это последовательность байтов, которые однозначно соответствуют тому, что находится на внешнем устройстве. Файл в языке С – это понятие, которое может быть приложено ко всему от файла на диске до терминала. Поток может быть связан с файлом с помощью оператора открытия файла. Как только файл открыт, информация может передаваться между ним и программой. Различаются файлы с последовательной и прямой организацией, соответственно различаются и способы работы с такими файлами. Если операция открытия файла связывает поток с определенным файлом, то операция закрытия разрывает эту связь. Каждый поток, связанный с файлом, имеет управляющую структуру, называемую FILE. Она описана в заголовочном файле stdio. h.
Ввод/вывод на консоль Функции printf и scanf() осуществляют форматированный () ввод и вывод на консоль. Форматированный ввод и вывод означает, что функции могут читать и выводить данные в разном формате, которым можно управлять. Функция printf имеет прототип в файле stdio (). h int printfchar *управляющая_строка, …); ( управляющая строка содержит два типа информации: символы, которые непосредственно выводятся на экран, и команды формата (спецификаторы формата), определяющие, как выводить аргументы. Команда формата начинается с символа %, за которым следует код формата. Команды формата следующие: %c – символ; %d – целое десятичное число; %i – целое десятичное число; %e – десятичное число в виде e xx x. xx ;
%E – десятичное число в виде E xx; x. xx %f – десятичное число с плавающей запятой xx. xxxx; %F – десятичное число с плавающей запятой xx. xxxx; %g - %f или % что короче; e, %G - %F или % что короче; E, %o – восьмеричное число; %u – беззнаковое десятичное число; %x – шестнадцатеричное число (например 5 a 5 f); %X – шестнадцатеричное число (например 5 A 5 F); %% - символ %; %p – указатель; %n – указатель; Другие спецификаторы: %ld – выводlong int; %hu – выводshort unsigned; %Lf – выводlong double;
Между знаком % и форматом команды может стоять целое число, которое указывает на наименьшее поле, отводимое для вывода. Если строка или число больше этого поля, то строка или число выводится полностью, игнорируя ширину поля. Нуль, поставленный перед целым числом, указывает на необходимость заполнить неиспользованные места поля нулями. Например: printf (“%05 d”, 15); дает результат 00015. Чтобы указать число десятичных знаков после целого числа, ставится точка и целое число, указывающее, на количество десятичных знаков. Когда такой формат применяется к целому числу или к строке, то число, стоящее после точки, указывает на максимальную ширину поля вывода.
Выравнивание производится по правому краю поля. Если необходимо выровнять по левому знаку поля, то сразу за знаком % следует поставить знак “-“. В прототипе функции многоточием обозначен список аргументов – переменных или констант, которые следуют через запятую и подлежат выводу в соответствующем формате, следующем по порядку. scanf() – основная функция ввода с консоли. Она предназначена для ввода данных любого встроенного типа и автоматически преобразует введенное число в заданный формат. Прототип функции из файла stdio имеет вид: int scanfchar. h ( *управляющая_строка, …); Управляющая строка содержит три вида символов: спецификаторы формата, пробелы и другие символы. Команды или спецификаторы формата начинаются с символа %. Они перечислены ниже: %c – чтение символа; %d – чтение десятичного целого; %i – чтение десятичного целого;
%e – чтение числа типа float ; %h – чтение числа типа short int ; ; %o – чтение восьмеричного числа; %s – чтение строки; %x – чтение шестнадцатеричного числа; %p – чтение указателя; %n – чтение указателя в увеличенном формате; Символ пробела в управляющей строке дает команду пропустить один или более символов в потоке ввода. Кроме пробела может восприниматься символ табуляции или новой строки. Нулевой символ указывает на чтение и отбрасывание (discard) этого символа. Все переменные, которые вводятся, должны указываться с помощью адресов, как положено в функциях языка С. Строка будет читаться как массив символов, и поэтому имя массива без индексов указывает адрес первого элемента.
Разделителями между двумя вводимыми числами являются символы пробела, табуляции или новой строки. Знак * после % и перед кодом формата дает команду прочитать данные указанного типа, но не присваивать это значение. Так, scanf(“%d%*c%d”, &i, &j); при вводе 50+20 присвоит переменной i значение 50, переменной j – значение 20, а символ + будет прочитан и проигнорирован. В команде формата может быть указана наибольшая ширина поля, которая подлежит считыванию. Например, scanf(“%5 s”, str); указывает необходимость прочитать из потока первые 5 символов. При вводе 123456789 массив str будет содержать только 12345, остальные символы будут проигнорированы. Если в управляющей строке встречаются какие-либо другие символы, то они предназначаются для того, чтобы определить и пропустить соответствующий символ. Поток символов 5 plus 10 оператором scanf(“%dplus%d”, &i, &j); присвоит переменной i значение 5, переменной j – значение 10, а символы plus пропустит, так как они встретились в управляющей строке.
Простейшая функция ввода getche читает символы с () клавиатуры. Функция ожидает, пока не будет нажата клавиша, и возвращает код, соответствующий символу. Одновременно происходит отображение введенного символа на экран. Прототип функции int getchevoid в ( ); conio. h. Простейшая функция вывода – putchar(). Она выводит символ, который является ее аргументом, на экран в текущую позицию. Прототип int putchar (int c в ); stdio. h. Двумя наиболее важными аналогами функции getche () являются функции getchar и getch Функция getchar () (). () производит буферизованный ввод, но требует нажатия клавиши Enter. Прототипы этих функций описаны в файле stdio Функция getch действует также, как. h. () getche но не выводит символ на экран, ее прототип в (), conio Функция getch часто используется для. h. () остановки действия программы до нажатия какой-либо клавиши именно потому, что она не выдает эхо на экран.
Функции gets() и puts() осуществляют соответственно ввод и вывод на консоль строки символов. Прототип gets() имеет вид char gets (chat *s); здесь s – указатель на массив символов, который заполняется вводимыми с клавиатуры символами. Окончание ввода осуществляется нажатием клавиши Enter. Заносится символ “ ”, завершающий строку. Функция puts() выводит на экран строку. Ее прототип int puts(char *s); Эта функция, так же как и printf распознает специальные (), символы. В отличие от printf функция puts() может выводить () только строку, зато работает быстрее и ее запись короче, чем у printf В результате действия функции puts() всегда происходит (). переход на новую строку. Если вывод успешно завершен, то функция возвращает ненулевое значение, в противном случае возвращает символ EOF. Прототипы функций puts() и gets() находятся в файле stdio. h. Указатель на файловую переменную Связующим звеном между файлом и потоком в системе ввода-вывода стандарта ANSI языка С, является указатель на файл (file pointer ). Указатель на файл – это указатель на информацию, которая определяет различные стороны файла: имя, статус, текущую позицию. Указатель файла определяет имя файла и его использование в потоке, ассоциированном с ним. Указатель файла – это указатель на структуру типа FILE в stdio В файле stdio. h. . h определены также следующие функции.
Чтобы объявить указатель на файл, используется оператор FILE *fput; Функция fopen выполняет два действия: во-первых, () открывает поток и связывает файл с этим потоком; вовторых, возвращает указатель, ассоциированный с этим файлом. Прототип функции FILE *fopen (char *filename char *mode); где mode – это строка, , содержащая режим открываемого файла. Возможные режимы открытия файлов перечислены ниже:
Если требуется открыть файл с именем test для записи, то достаточно написать FILE *fp; fp=fopen(“test”, ”w”); Рекомендуется использовать следующий способ открытия файла: FILE *fp; if(fp=fopen(“test”, ”w”))==NULL) { puts(“Не могу открыть файл n”); exit (1); } Этот метод определяет ошибку при открытии файла. Константа NULL определена в stdio Функция exit имеет. h. () прототип в void exitint val; Она прекращает выполнение ( ) программы, а величину val возвращает в вызывающую программу. При этом перед прекращением работы программы закрывает все открытые файлы, освобождает буферы, в частности выводя все необходимые сообщения на экран.
Запись символа производится функцией fputc() с прототипом int fputc(int ch FILE *fptr); Если операция была успешной, то , возвращается записанный символ, в случае ошибки возвращается EOF. Функция fgetc считывает символ из потока, открытого () функцией fopen Прототип функции (). int getc (FILE *fptr); В функциях fputc(), fgetc используется только () младший байт. Чтобы прочитать текстовый файл можно использовать конструкцию ch=getc(fptr); while(ch!=EOF) { ch=fgetc(fptr); } Запись строки символов в поток производится функцией fputs() с прототипом int fputs(char *, FILE *fptr); . Чтение строки символов из потока производится функцией fgets с () прототипом char *fgets(char *s, int n, FILE *fptr); n – количество символов. где
Функции fprintf) и fscanf() осуществляют форматированный ввод и ( вывод (из) в поток. Форматированный ввод и вывод означает, что функции могут читать и выводить данные в разном формате, которым можно управлять. Функции fprintf), fscanf() имеют прототипы в файле stdio (. h int fprintf (FILE *fptr, char *управляющая_строка, …); int fscanf. FILE *fptr, char *управляющая_строка, …); ( Для определения конца файла служит функция feof с () прототипом int feof. FILE *fptr); ( Функция возвращает значение “истинно если конец файла ”, достигнут, и “ложно в противном случае. Следующая ” конструкция читает файл до конца. while(!feof(fptr)) { ch=fgetc(fptr); } Функция fclose служит для закрытия файла. Ее прототип: int () fclose (FILE *fptr); При успешном закрытия файла соответствующие данные считываются из (в) файл, происходит освобождение блока управления файлом, ассоциированного с потоком, и файл становится не доступным.
Если произошла ошибка чтения или записи файла, то соответствующая функция возвращает EOF. Чтобы определить, что же в действительности произошло, служит функция ferror с прототипом: int ferror. FILE *fptr); , () ( которая возвращает значение “истинно при успешной ” выполнении последней операции с файлами и “ложно в ” противном случае. Функция ferror должна быть () выполнена непосредственно после каждой операции с файлами, иначе ее сообщение об ошибке будет потеряно. Функция rewind() устанавливает указатель позиции файла на начало файла, определенного как аргумент функции. Прототип функции имеет вид: void rewind(FILE *fptr); Функции fread(), fwrite используются для чтения и записи () блоков данных; unsigned fread(void *buf, int bytes, int c, FILE *fptr); unsigned fwrite(void *buf, int bytes, int c, FILE *fptr); где buf – указатель на область памяти, откуда происходит обмен информацией; с – сколько единиц записей, каждая длиной bytes байтов будет считано (записано); bytes –длина каждой единицы записи в байтах; fptr – указатель на соответствующий файл.
Чтение и запись в файл необязательно делать последовательно, можно это делать непосредственно доступом к нужному элементу файла посредством функции fseek которая устанавливает указатель позиции (), файла в нужное место. Прототип функции: int fseek(FILE *fptr, long numbytes, int origin); fptr – указатель на соответствующий файл; numbytes – количество байт от точки отсчета для установки текущей позиции указателя файла; origin – один из макросов, определенных в stdio. h.
Когда начинается выполнение программы, автоматически открывается 5 предопределенных потоков. Первые три из них – стандартный ввод (stdin), стандартный вывод (stdout и стандартный поток ошибок (stderr) В обычной ) ситуации они связаны с консолью, однако они могут быть перенаправлены на другой поток. Можно использовать stdin, stdout stderr как указатели файлов во всех , функциях, применяющих тип FILE. Кроме того, есть два потока stdprn и stdaux, ассоциированные соответственно с принтером и последовательным портом ПК. Эти потоки открываются и закрываются автоматически. Функци remove уничтожает указанный файл. Прототип () функции int remove(char *filename); Так как язык С связан с ОС UNIX, то существует вторая система ввода-вывода. Эта система соответствует стандарту UNIX. Прототипы функций в файле io. h. Этими функциями являются: read() – читает буфер данных; write – пишет в буфер данных; () open() – открывает файл; close – закрывает файл; () lseek() – поиск определенного байта в файле; unlink() – уничтожает файл.
Пример программы чтения строк из файла поиска в строках заданного слова и запись строк, содержащих это слово в другой файл. #include<iostream. h> #include<conio. h> #include<string. h> #include<stdio. h> int main() { char *tmp=new char[80]; char *fname=new char[20]; char *word=new char[80]; char *ptr; FILE *fw; clrscr(); fw=fopen("bbb. cpp", "w"); int a=0; cout<<"Give name file: "<<endl; do
{ // cin. getline(fname, 20); if ((fr=fopen(fname, "r"))==NULL) { a=0; cout<<"Invalid name file!! Give correct name: "<<endl; } else a=1; } while(a==0); cout<<"n. Give word: "; cin. getline(word, 80); while(!feof(fr)) { fscanf(fr, "%s", tmp); // read only to space fgets(tmp, 80, fr); if ((ptr=strstr(tmp, word))!=NULL) // search word { cout<<tmp<<endl; fprintf(fw, "%s", tmp); } } fclose(fr); fclose(fw); getch(); return 0; }
Работа с файлами в C++ Для организации обмена с файлами к программе нужно подключить файл fstream В С++ файл открывается. h. присоединением к потоку. Если поток объявлен объектом класса ifstream то он открыт для ввода, если он , присоединен к объекту класса ofstream то он открыт для , вывода. Поток, предназначенный и для ввода и для вывода должен быть объектом класса fstream. Пример ifstream input_from; ofstream out; fstream in_out_stream; Если поток уже создан, то он может быть ассоциирован с файлом с помощью функции open(). Ее прототип void open(char *filename, int mode, int access); filename– имя файла; mode – режим, определяющий как открыт файл; access – режим доступа файла. Режимы доступа приведены в таблице (режимы могут комбинироваться)
В случае объявления потока ifstreamили ofstreamавтоматически устанавливаются соответственно режимы ios: : in и ios: : out. При вызове функции open() указывать все параметры необязательно. Например, вызовы ofstream out ; out. open(“myfile”, ios: : out, 0); и вызовы ofstream out; out. open(“myfile”); эквивалентны.
При открытии файла для ввода и вывода указываются оба режима работы. fstream m_in_out; m_in_out. open(“myfile”, ios: : in|ios: : out); Для закрытия файла используется функция close (). Пример m_in_out. close(); Пример открытия файла для вывода: ofstream m_out; m_out. open(“myfile”, ios: : out, 0); if(!m_out) { cout<<”Не могу открыть файл”; return 1; } Обработка текстовых файлов Для ввода/вывода в текстовый файл можно непосредственно применять операции << и >>.
Пример вывода в файл: #include <iostream. h> #include <fstream. h> void main(void) { ofstream out(“myfile. txt”); if (!out) { cout<<”Не могу открыть файлn”; return 1; } out<<10<<” “<<2004; out<<”Строка выводится очень просто”; out. close(); } Для чтения из файла можно использовать следующую программу: #include <iostream. h> #include <fstream. h> void main(void) { char ch; int i; float f; char str[80]; ifstream in(“myfile. txt”); if (!in) { cout<<”Не могу открыть файлn”; return 1; } in>>i; in>>f; in>>ch; in>>str; cout<<i<<” “<<f<<” “<<ch<<endl; cout<<str<<endl; in. close(); }
Для чтения символьных данных можно использовать функцию getline с прототипом из iostream (). h char *getline(char *s, int n); s - строка (массив) для ввода; n - количество читаемых символов. Пример программы чтения строк из файла поиска в строках заданного слова и запись строк, содержащих это слово в другой файл. #include<iostream. h> #include<conio. h> #include<string. h> #include<fstream. h> #include<stdio. h> void main(void) { ofstream fcout("aaa. cpp"); ifstream fcin; char *tmp=new char[80]; char *fname=new char[20]; char *word=new char[80]; char *ptr; int a=0;
cout<<"Give name file: "<<endl; do { if(a) { cout<<"Invalid name file!! Give correct name: "<<endl; } cin. getline(fname, 20); fcin. open(fname); a=1; } while(!fcin); cout<<"give word: "; cin. getline(word, 80); while(!fcin. eof()) /* read line from file */ { fcin. getline(tmp, 80); if ((ptr=strstr(tmp, word))!=NULL) { cout<<tmp<<endl; fcout<<tmp<<endl; } } fcout. close(); fcin. close(); getch(); }
Двоичный вводвывод /. Есть следующие способы работы с двоичными файлами. Первый состоит в использовании для ввода-вывода байта функций get() и put(). Наиболее часто используемая форма этих функций: ifstream &get(char &ch); ofstream &put(char ch); Функция get() читает символ из потока и присваивает значение переменной ch. Функция put() записывает символ ch в поток. Пример программы копирования файла на экран. #include <iostream. h> #include <fstream. h> void main(void) { char ch; ifstream in; in. open(“myfile. bin”, ios: : binary, 1); if(!in) { cout<<”n не могу открыть файл”); return 1; } while(in. get(&ch)) cout<<ch; in. close(); }
Когда поток in достигает конца файла, значение переменной in становится нулевым Пример программы копирования строки в файл #include <iostream. h> #include <fstream. h> oid main(void) { char *p=”Hello, World!”; ofstream out(“test”); if(!out) { cout<<”Не могу открыть файл n”; return 1; } while (*p) out. put(*p++); out. close(); } Для быстрой обработки двоичных данных можно использовать функции read(), write с прототипами: () ifstream &read(unsigned char *buf, int num); ofstream &write(unsigned char *buf, int num); Функция read() читает из потока num байт и помещает их в buf. Функция write () записывает в поток num байт из буфера buf.
Пример #include <iostream. h> #include <fstream. h> void main(void) { float numbers[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; int i; ofstream out(“test”); if (!out) { cout<<”Не могу открыть файл n”; return 1; } out. write((unsigned char *) numbers, sizeof numbers); out. close(); for(i=0; i<10; i++) numbers[i]=0; ifstream in(“test”); if (!in) { cout<<”Не могу открыть файл n”; return 1; } in. read((unsigned char *) numbers, sizeof numbers); for(i=0; i<10; i++) cout<<numbers[i]<<” “; cout <<”n”; cout<<”Было прочитано”<< in. gcount ()<<” символов”; n in. close(); }
Функция gcount возвращает количество прочитанных байт () последним вызовом функции read(). Для обнаружения конца файла можно использовать функцию eof(), которая возвращает ненулевое значение, когда маркер файла достигает конца файла. Система ввода/вывода С++, так же как и в языке С, оперирует потоками (streams В начале выполнения ). программы автоматически открывается 4 предопределенных потока: cin, cout, cerr, clog. Поток cin связан со стандартным вводом, поток cout – со стандартным выводом. Потоки cerr и clog также связаны со стандартным выводом. Поток cerr –небуферизованный, т. е. вызывает немедленный вывод. Поток clog буферизован, и вывод происходит только тогда, когда наполнится буфер. Оба этих потока используются для вывода сообщений об ошибках. По умолчанию поток cin связан с клавиатурой, cout – с дисплеем, но они могут быть перенаправлены на другие устройства. При работе с потоками используются две операции. Операция << - inserting(вставка данных в поток), операция >> - extracting(извлечение данных из потока).
В следующем примере применяются данные операции. #include <iostream. h> void main(void) { char name[100]; cout<<”n. Пожалуйста введите ваше имя , : ”; cin>>name; cout>>”n. Привет , ”; cout<<name; } Библиотека потоков С++ предусматривает три способа управления форматом выходных данных: вызов форматирующих функций-элементов , использование флагов и применение манипуляторов.
Форматирующие функции-элементы. Данные функции принадлежат классу ios. К ним относятся функции: width(), fill(), precision(). Функцияwidth int width возвращает текущее значение внутренней () переменной ширины поля потока. int width (int ) устанавливает значение внутренней переменной ширины поля потока. Замечания к применению: применяемая при вводе функция может быть использована для задания максимального числа читаемых символов; применяемая при выводе функция задает минимальную ширину поля вывода; если ширина поля меньше заданной, то дополнение символами fill; если выходное поле больше указанного, то значение width игнорируется; по умолчание значение width равно 0; width обнуляется после каждого помещения данных в поток.
Пример #include <iostream. h> const int MAX_LEN = 10; const int FLD_WIDTH = 10; void main(void) { char name[MAX_LEN]; int x=2867; int y=20051; int z=017; cout<<”|n Give name “<<”(max “<<MAX_LEN-1<<” symbols): “; cin. width(MAX_LEN); cin>>name; cout<<”n Hello, “<<name; cout. width(FLD_WIDTH); cout<<x<<”n”; cout. width(FLD_WIDTH); cout<<y<<”n”; cout. width(FLD_WIDTH); cout<<z<<”n”; }
Функцияfill int fill) возвращает текущий символ заполнения; ( int fillchar) устанавливает внутренний заполняющий ( символ и возвращает его предыдущее значение. Символом заполнения по умолчанию является пробел. Пример #include <iostream. h> const int FLD_WIDTH = 10; const int FILL_CHAR = ‘ 0’; void main(void) { int x=2867, y=20051; cout. fill(FILL_CHAR); cout. width(FLD_WIDTH); cout<<x<<”n”; cout. width(FLD_WIDTH); cout<<y<<”n”; }
Функцияprecision int precision возвращает текущее символ значение точности; () int precision (int) устанавливает внутреннюю переменную точности вещественных чисел потока и возвращает прежнее значение. Замечания к применению: по умолчанию точность равна 6 цифрам; если установлен флаг scientificили fixed precisionзадает общее , число значащих цифр; если не установлен ни один из флагов scientificили fixed precision , задает общее число значащих цифр; Пример иллюстрирует применение precision #include <iostream. h> void main(void) { float f = 3456. 141592; double d = 50. 2345639101; cout. precision(4); cout<<d<<’n’; //выводит’ 50. 23’ cout<<f<<’n’; // выводит ’ 3456’ cout. precision(3); cout<<f<<’n’; //выводит’ 3. 46 e+3’ }
Флаги форматирования. В потоках С++ имеются флаги формата. Они задают, каким образом. форматируется ввод и вывод. Флаги являются битовыми полями , хранящимися в переменной типа long. В следующей таблице приведены функции, предусмотренные в классе ios для управления флагами формата. • В следующей таблице описаны флаги форматирования
Пример, демонстрирующий применение некоторых из флагов. #include <iostream. h> #include <limits. h> void main(void) { int i =0; cout<<”Введите целое с необязательными пробелами впереди: “; // при чтении пробелы будут пропущены cin>>i; // показать число… cout<<”Вы ввели “ << << endl; i // удалить оставшиеся в потоке символы cin. ignore( INT_MAX, ‘n’); cout <<”Введите целое с необязательными пробелами впереди: “; // Сбросить флаг skipws cin. unsetf(ios: : skipws); // Эта операция не будет игнорировать пробелы cin >> i; } #include <iostream. h>
void main(void) { int x = 1678; // показать значение cout<<”Значениеx= “<<x<<’n’; // сохранить значения флагов long saved. Flags= cout. flags; // установить основание 16 с индикацией cout. setf(ios: : showbase|ios: : hex); // вывести значение снова cout<<” Значениеx=”<< x <<’n’; } #include <iostream. h> void main(void) { float f = 2. 3456789 e 6; double d = 3. 0 e 9; cout << “Значение = “ << f <<’n’; f cout << “Значение = “ << d <<’n’; d // выводить знак + для положительных значений cout. setf(ios: : showpos); // вывести значения снова cout << “Значение = “ << f <<’n’; f cout << “Значение = “ << d <<’n’; d }
Манипуляторы являются функциями, которые можно включать в цепочку последовательных операций помещения и извлечения. Это удобный способ управления флагами потоков. Простые манипуляторы Манипуляторы, не требующие указания аргументов, называются простыми. Предопределенные простые манипуляторы описаны в следующей таблице.
Пример иллюстрирует использование простых манипуляторов #include <iostream. h> #include <iomanip. h> void main(void) { int i; cout << “Введите число: ”; >> i; cin if (!cin) cout << Ошибочный ввод “ …. ”<<endl; else { // применить манипуляторы oct, dec hex, cout<<”Hex: << hex << i << endl; cout<<”Oct: << oct << i << endl; cout<<”Dec: << dec << i << endl; } }