В реферате рассмотрены базовые элементы языка СИ, в том числе, типы данных, операции и операторы СИ, средства ввода и вывода данных, способы организации структур данных, встроенные и пользовательские функции СИ. Все рассматриваемые элементы языка иллюстрируются примерами, которые запускались как СИ++ консоль-приложение Microsoft Visual Studio. Все примеры снабжены заголовочным файлом
#include "stdafx.h"
По умолчанию он содержит следующие определения:
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
В прил. П1 приведен пример решения типовой для первого курса задачи программирования, а в прил. П2 - перечень задач для самостоятельной проработки. Все эти задачи можно решить, опираясь на имеющиеся в реферате сведения.
СИ имеет 4 базовых типа. Остальные типы данных в СИ образуются из базовых.
char - единичный байт; может содержать один символ (литеру).
int - целое число.
float - число с плавающей точкой одинарной точности.
double - число с плавающей точкой двойной точности.
В базовые типы СИ не входит строковый тип. В СИ отсутствует логический тип данных.
Примеры объявления типов данных рассмотрены ниже.
Замечание. Число байт, отводимых под переменную заданного типа, можно определить при помощи унарной операции sizeof(<тип>). Например, sizeof(int) определяет число байт, необходимое для представления int. Значение sizeof(char) всегда равно 1.
Квалификаторы служат для определения производных типов данных СИ. (Применение квалификаторов не единственный способ формирования типов данных.)
short, long - используются с int, например:
short int v1;
long int v2;
signed, unsigned (со знаком, без знака) применимы к int и char и любому целому типу, например:
signed int; // -32768 - 32767
unsigned int; // 0 - 65535
long double - предназначен для арифметики с плавающей точкой повышенной точности.
В СИ, как и в других языках, выполняются автоматические и явные преобразования типов данных. Преобразования выполняются автоматически, если в выражении присутствуют данные разных типов. Рассмотрение таких преобразований предоставляется для самостоятельной проработки. Для явного преобразования типов используется унарный оператор, называемый приведением. Конструкция вида
(имя-типа) выражение
приводит результат выражения к указанному в скобках типу данных.
Например, в СИ аргумент библиотечной функции sqrt (извлечение квадратного корня) должен иметь аргумент типа double. Поэтому, если n есть целое число, то при извлечении квадратного корня следует написать
sqrt((double) n).
При этом вырабатывается значение n типа double. Сама же переменная n тип не изменяет.
Замечание. Функции преобразования строка в int, строка в double и ряд других функций преобразования типа данных описаны в файле stdlib.h.
В СИ нет логического типа данных. Выражение в СИ считается истинным, если его значение отлично от 0. В противном случае значение выражения есть ложь.
Пример. Вывести на печать значения переменных a и b, если а делится на b без остатка.
#include "stdafx.h"
// viod означает, что функция не возвращает результата, поэтому оператор return не нужен
void main() {
int a = 10, b = 5, c; // Объявление и инициализация переменных
c = a % b; // Находим остаток от деления 'a' на 'b'
if(c)
printf_s("'a' is divided by 'b' with a remainder\n");
else
printf_s("a = %2d b = %2d\n", a, b);
}
Замечания:
В приведенном примере использованы следующие операции СИ:
= - операция присваивания;
% - операция определения остатка от деления.
Стандартная функция printf, выполняющая форматный вывод на экран. Последовательность '\n'в функции printf обеспечивает переход на новую строку экрана.
Найти суммарную площадь n равнобедренных треугольников, имеющих равное основание a. Длина боковой стороны i-го треугольника равна a + i.
#include "stdafx.h"
#include <math.h> // Математические функции sqrt, pow, ...
void main() {
int n, i;
float a, h, s = 0; // Инициализация при объявлении
n = 20; a = 2.2f; // На одной строке может быть более одного оператора
for (i = 1; i <= n; i++) {
h = sqrt((a + i) * (a + i) - a * a / 4); // или h = sqrt(pow(a + i, 2) - pow(a / 2, 2));
s += a * h / 2; // или s = s + a * h / 2;
}
printf_s("Total area s = %7.2f\n", s);
}
В примере наряду с прочими использованы стандартные функции СИ:
sqrt(x) - корень квадратный из x;
pow(a, b) - a в степени b - ab. Ошибка области определения: a = 0 и b <= 0 или a < 0 и b не целое.
+ - сложение;
- - вычитание;
* - умножение;
/ - деление;
% - остаток от деления.
Пример.
int a = 10, b = 4, c;
c = a % b; // Результат c = 2
Замечание. Для возведения a в степень b можно использовать функцию pow(a, b) библиотеки math.h.
Рассмотрим три логические операции СИ: отрицание, логическое или и логическое и. Для их обозначения в СИ используются знаки:
! - отрицание;
|| - логическое ИЛИ;
&& - логическое И.
Пример. Вывести на экран значения переменных a, b и с, если, во-первых, a < b или c < b и если, во-вторых, b = 0.
#include "stdafx.h"
void main(){
int a = -3, b = 0, c = 2; // Объявление и инициализация переменных
if((a < b || c < b) && b == 0) // Для приведенных данных условие выполнено
printf_s("a = %2d b = %2d c = %2d\n", a, b, c);
else
printf_s("Condition is not fulfilled\n");
}
Приоритет операции и (&&) выше приоритета операции или (||), поэтому выражение (a < b || c < b) необходимо заключить в скобки. Запись
(a < b || c < b && b == 0)
эквивалентна следующей:
(a < b || (c < b && b == 0)).
== - равно;
!= - не равно;
< - меньше;
<= - меньше равно;
> - больше;
>= - больше равно.
= - операция присваивания СИ (например, a = b;).
Поскольку присваивание является операцией, то допустимо множественное присваивание:
a = b = c = 2;
Пусть v - простая переменная; en - выражение, результатом которого является величина, тип которой совпадает с типом переменной v. Если операция присваивания имеет вид
v = v <арифметическая операция> en
то можно использовать более компактную форму для записи такой операции:
v <арифметическая операция>= en
Пример.
i = i + 2;
// или
i += 2;
k = k * a / c;
// или
k *= a / c;
Вызывают соответственно увеличение и уменьшение значения выражения на 1.
++ - операция инкремента;
-- - операция декремента.
Например, вместо i = i +1 можно записать i++ (суффикс) или ++i (префикс), а вместо i = i - 1 можно записать i-- или --i.
Пример.
int i = 3, j, k;
j = i++; // Результат i = 4; j = 3
k = --j; // Результат j = 2; k = 2
Пусть EXPL - некоторое выражение, результатом которого является истина или ложь; v - переменная; expl, exp2 - выражения, типы результатов которых совпадают с типом переменной v.
v = EXPL ? exp1 : exp2; // если EXPL есть истина, то v = exp1, иначе v = exp2.
Пример.
z = (a > b) ? a : b; // если a > b, то z = a, иначе z = b
y = (x < 0)? -x : x; // Выражение для y = |x|
При вычислении значения выражения операции выполняются слева направо порядке, определяемом их приоритетом. Приведем, разделяя пробелом, некоторые операции СИ, расположив их в порядке убывания приоритета:
Введем предварительно понятия идентификатора и выражения.
Идентификатор - это последовательность букв, цифр или подчеркивания, начинающаяся с буквы или подчеркивания, например, st, _step.
В СИ имеет значение регистр букв идентификатора: идентификаторы st, ST, St и sT есть не одно и то же.
Выражение - это сочетание одной или более констант, переменных или вызовов функций и нуля и более операций.
Оператор выражения представляет собой выражение, заканчивающееся точкой с запятой (;), например:
a = b + c;
Выражение рассматривается как истина, если результат его вычисления не равен нулю, или как ложь - в противном случае.
Оператор с меткой - это оператор выражения, перед которым стоит идентификатор метки и двоеточие (:), например:
label1: a = b + c;
Метка используется в операторе goto. Метка должна находиться в той же функции, где употреблен оператор goto, эту метку адресующий.
Пустой оператор - это просто точка с запятой (;). Используется, когда после метки или операторов while, do или for не должно следовать никакого оператора.
Следование - это нуль или более операторов, заключенных в фигурные скобки. Фигурные скобки могут быть опущены, если следование содержит нуль или один оператор.
Пример. В цикле ищется сумма отрицательных элементов целочисленного массива a. Цикл прерывается при обнаружении в массиве отрицательного элемента.
s = 0;
for(i = 0; i < n; i++)
if(a[i] == 0)
{ fl = 1; break; } // Следование двух операторов
else if(a[i] < 0)
s += a[i];
Оператор объявления - содержит после имени типа идентификаторы переменных и констант. При объявлении возможна инициализация переменных.
int global, step;
char c;
char esc = '\\'; // Объявление и инициализация переменной
char line[80];
Для объявления константы используется квалификатор const:
const double e = 2.718281828845905; // Объявление костанты
Иной способ задание констансты - это употребление директивы #define.
Оператор break - прерывает выполнение оператора while, do, for или switch. Выполнение передается на оператор, следующий за прерванным циклом или переключателем switch.
Пример. Найти первое отрицательное число в массиве a[9].
int i;
float a[] = {1.1, -2.2, 3.3, -4.4, 5.5, -6.6, 7.7, -8.8, 9.9};
for(i = 0; i < 9; i++) if(a[i] < 0) break;
if(i < 9) // Сюда передается управление после выполнения оператора break
printf_s("First negative number in array: %7.3f\n", a[i]);
else
printf_s("No negative numbers found\n");
Оператор continue - передает управление на начало оператора (цикла) while, do или for.
Пример. Найти сумму положительных чисел массива a[9].
int s = 0, i, a[] = {1, -2, 3, -4, 5, -6, 7, -8, 9};
for(i = 0; i < 9; i++) {
if(a[i] <= 0) continue; // Переход к следующему элементу массива a
s += a[i];
}
printf_s("Array positive elements sum: s = %6d\n", s);
Возможны и иные варианты программирования последней задачи, например:
int s, i, a[] = {1, -2, 3, -4, 5, -6, 7, -8, 9};
for(s = i = 0; i < 9; i++) s += a[i ]> 0 ? a[i] : 0;
printf_s("Array positive elements sum: s = %6d\n", s);
Оператор return <выражение> прерывает выполнение текущей функции; результат функции равен значению выражения оператора return.
Выражение опускается, если функция имеет тип void.
Оператор goto <метка> передает управление оператору, помеченному меткой.
Пример. Найти первое отрицательное число в массиве a[9].
int i, a[] = {1, -2, 3, -4, 5, -6, 7, -8, 9};
for(i = 0; i < 9; i++) if(a[i] < 0) goto L1;
L1: // управление передается сюда
if(i < 9)
printf_s("First negative number in array: %5d\n", a[i]);
else
printf_s("No negative numbers found\n");
Условный оператор, или "ветвление"
if(выражение) {
следование_1
}
else {
следование_2
}
Оператор вычисляет выражение и, если оно истинно (не равно нулю), то выполняется следование_1, в противном случае выполняется следование_2. Конструкция else и следование_2 могут быть опущены.
Оператор switch - "переключатель"
switch(выражение) {
case константное выражение_1:
следование_1
case константное выражение_2:
следование_2
...
case константное выражение_N:
следование_N
default:
следование_N+1
}
Определяется результат выражения и выполняются операторы, которые следуют за константным выражением, имеющим то же значение, что и результат выражения. Если такое константное выражение отсутствует, то выполняется стоящее за default следование_N+1.
Элементами константного выражения могут быть только константы, определенные директивой #define. Значение константного выражения вычисляется при компиляции программы. Для прерывания оператора switch используется, как правило, оператор break (можно использовать, например, goto или return).
Допустимые типы выражения и константного выражения: int и char. Типы выражения и константного выражения должны совпадать. Совпадений константных выражений не допускается.
Пример. Имитация работы калькулятора.
switch(c) {
case '+': n = a + b; break;
case '-': n = a - b; break;
case '*': n = a * b; break;
case '/': n = a / b; break;
default: printf_s("Invalid operation\n");
}
// Сюда передается управление после выполнения любого оператора break
Программа имитации работы калькулятора с определеним констант посредством директивы #define:
#include "stdafx.h"
#include <conio.h> // Для >_getch
#define pls '+' // Объявляем define-константу
#define mns '-'
#define mltpl '*'
#define dvsn '/'
void main() {
float n, a = 2.3, b = 4.6;
char c;
// Цикл прерывается после ввода символа, отличного от символа арифметической операции
while(1) {
puts("\n Enter operation (+, -, *, / or any to quit): ");
c = _getch(); // Ввод символа арифметической операции
switch(c) {
case pls: n = a + b; break;
case mns: n = a - b; break;
case mltpl: n = a * b; break;
case dvsn: n = a / b; break;
default:
printf_s("Invalid operation\n");
return;
}
printf_s("n = %6.3f\n", n);
}
}
Оператор цикла "пока"
while(выражение) {
следование
}
Следование выполняется до тех пор, пока истинно выражение. Прерывание оператора цикла while может быть выполнено операторами break или return (последний обеспечивает также и выход из текущей функции). Также цикл while может быть прерван функцией exit, которая одновременно прерывает и выполнение программы. Пример цикла while рассмотрен ниже.
Оператор цикла "до"
do {
следование
}
while(выражение)
Следование выполняется до тех пор, пока истинно выражение. Прерывание оператора цикла do может быть выполнено теми же операторами, что и цикла while. Пример цикла do рассмотрен ниже.
Оператор цикла for (цикл "с параметром")
for (следование_1; выражение_1; выражение_2) {
следование_2
}
Следование_2 в цикле for выполняется до тех пор, пока истинно выражение_1.
Следование_1, как правило, выполняет инициализацию параметра цикла.
Выражение_1, как правило, контролирует верхнюю или нижнюю границы параметра цикла.
Выражение_2, как правило, определяет закон изменения параметра цикла.
Оператор_1 и оба выражения цикла for могут быть опущенными. Когда выражение_1 опущено, его значение считается истинным.
Прерывание оператора цикла for может быть выполнено теми же операторами, что и цикла while.
Пример. Выполнить табуляцию функции y = x * sinx на отрезке [0, π] с шагом π / 20.
#include "stdafx.h"
#include <math.h> // Для sin(x)
void main() {
double pi, x, dx;
pi = 2.0 * asin(1.0);
dx = pi / 20;
printf_s("y = x * sinx\n x y\n");
for(x = 0; x <= pi; x += dx)
printf_s("%7.3f %7.3f\n", x, x*sin(x));
}
Пример. Если в одномерном массиве нет нулевых элементов, то найти сумму его отрицательных элементов. Ввод элементов массива выполнить с клавиатуры.
Замечание. При вводе с клавиатуры разделителями являются символы пробела, табуляции или конца строки, например, при вводе 5-и элементов можно задать строку -2 -2 1 3 5. Допустим также ввод данных в столбик. В общем случае расположение данных произвольно: часть данных может быть расположена в столбик, часть - в строчку.
#include "stdafx.h"
#include <conio.h>
void main() {
int n, i, fl = 1; // Инициализируем fl значением истина
float a[120], s = 0; // Объявление массива и инициализация переменной s
printf_s("Array length: ");
scanf_s("%d", &n);
printf_s("\nArray elements:\n");
for(i = 0; i < n; i++) scanf_s("%f", &a[i]);
// Контрольный вывод
for(i = 0; i < n; i++) printf_s("%7.2f", a[i]);
//
// Цикл "с параметром"
for(i = 0; i < n; i++)
if(a[i] == 0) // Конструкция if - else - if
{fl = 0; break;}
else if(a[i] < 0)
s += a[i]; // или s = s + a[i];
if(!fl)
printf_s("\nZero is found");
else
printf_s("\ns = %7.2f\n", s);
//
// Тот же результат, но с циклом "пока"
s = 0.0;
i = 0;
while(i < n && fl) {
if(a[i] == 0)
fl = 0;
else if(a[i] < 0)
s += a[i];
i++;
}
if(!fl)
printf_s("\nZero is found");
else
printf_s("s = %7.2f\n",s);
//
// Тот же результат, но с циклом "до"
s = 0.0;
i = 0;
do {
if(a[i] == 0)
fl = 0;
else if(a[i] < 0)
s += a[i];
i++;
}
while(i < n && fl);
if(!fl)
printf_s("\nZero is found\n");
else
printf_s("s = %7.2f\n",s);
}
В примере использована функция scanf_s, обеспечивающая форматный ввод данных с клавиатуры. Второй и последующие аргументы этой функции - это адреса переменных.
Указатель - это переменная, которую можно ассоциировать с другой переменной. При этом в указатель устанавливается значение адреса ассоциируемой переменной.
Операторы, применяемые при работе с указателями:
& - унарный оператор определение адреса переменной;
* - унарный оператор раскрытия ссылки (позволяет получить значение по адресу, хранимому указателем).
Указатели объявляются при помощи символа *:
int j,*pj = &j; // Объявление указателя pj на int и его инициализация
int *ppj = &pj; // Объявление указателя на указатель на int
float (*pf)(int); // Указатель на функцию, принимающую параметр типа int и возвращающую значение типа float
Пример.
#include "stdafx.h"
void main() {
int *p; // Объявляем указатель на int
int a = 1, y, z[5] = {1, 2, 5, 8, 3};
p = &a; // p содержит адрес переменной a: p указывает на a
y = *p; // То же, что и y = a; тТеперь y = 1
*p = 0; // Меняем значение по адресу p; в результате получим, что с = 0, однако y = 1
printf_s("a = %3d, y = %3d\n", a, y); // Результат: с = 0, y = 1
p = &z[2]; // Теперь p содержит адрес элемента z[2]: p указывает на z[2]
y = *p; // В результате y = z[2] = 5; значение a не меняется (p теряет связь с a)
printf_s("a = %3d, y = %3d\n", a, y); // Результат: с = 0, y = 5
}
При работе с функциями в СИ следует помнить, что аргументы функции, кроме указателей, передаются в функцию по значению. Передача аргумента по значению означает, что в функции образуется копия аргумента и преобразование копии не вызовет изменение самого аргумента. Чтобы передать изменения, необходимо в качестве аргумента функции в СИ использовать указатель.
Указателями в СИ являются имена массивов и строк. Последние в СИ являются одномерными массивами. Имя массива (строки) указывает на первый элемент массива (строки). Индекс первого элемента массива (строки) СИ равен 0.
Чтение введенного с клавиатуры символа можно выполнить функцией _getch или getchar. Первая функция объявлена в заголовочном файле conio.h, а вторая - в файле stdio.h.
Функция _getch возвращает код символа сразу после его ввода; пример употребления _getch см. выше.
Функция getchar после нажатия на Enter читает набранную последовательность символов.
Вывод символа на экран можно выполнить функциями _putch и putchar, принимающими код символа - переменную типа int. Первая функция объявлена в заголовочном файле conio.h, а вторая - в файле stdio.h.
Пример. Обеспечить ввод символов с клавиатуры и их последующий вывод на экран.
#include "stdafx.h"
void main() {
int c;
// Ввод с клавиатуры; в условии вместо '\n' можно записать (int) '\n'
while((c = getchar()) != '\n') putchar(c);
// Введем 1a2b3c далее нажмем на Enter ('\n' - символ конца строки)
// В цикле будет напечатано: 1a2b3c
printf_s("\nDone\n");
}
Цикл будет прерван, если нажать Ctrl C.
В приведенном примере следует обратить внимание на запись логического выражения оператора цикла
while((c = getchar()) != '\n')
Такая запись возможна, поскольку присваивание является операцией и, следовательно, выражение
c = getchar()
вычисляется со значением, равным с.
Функция printf_s отображает выводимые данные на консоли. Определена следующим образом:
int printf_s(const char *_Formart, ...),
где char *_Formart - формат вывода (управляющая строка), последующие параметры - это выводимые на консоль значения.
Формат вывода, заключенная в двойные кавычки строка, в общем случае содержит:
Текст (обычные символы) формата вывода выводится без преобразований.
СП задает размер поля вывода, точность и способ вывода в заданном поле. Каждая СП вызывает преобразование и печать очередного аргумента функции printf_s. СП начинается символом % и заканчивается символом спецификатором. Между символом % и символом спецификатором располагается описание поля вывода и описание способа вывода.
Ниже приведены некоторые преобразования функции printf_s (приняты обозначения: w - ширина поля; n - точность; необязательные элементы СП заключены в таблице в квадратные скобки).
Спецификатор | Тип | СП аргумента | Вид печати |
---|---|---|---|
d, i | int | %[w]d(i) | Целое число |
c | int | %[w]c | Одиночный символ |
s | char * | %[w]s | Строка символов |
f | double | %[w].[n]f | Вещественное число |
e | double | %[w].[n]e | Экспоненциальное представление вещественного числа |
Пример. Вывести на разных строках значения вещественных переменных x = -2.43 и y = 9.657.
float x = -2.43f, y = 9.567f;
printf_s(" x = %8.3f; \n y = %7.3f \n", x, y); // Используем \n для перехода на следующую строку
/* Результат:
x = -2.430;
y = 9.657
*/
printf_s(" x = %9.3e; \n y = %11.3e \n ",x,y);
/* Результат:
x = -2.430e+000;
y = 9.567e+000
*/
Форматный ввод обеспечивается в СИ функцией scanf_s которая выполняет те же преобразования, что и функция printf_s, но в обратном порядке. Описание функции приведено в файле stdio.h:
int scanf_s(const char *_Formart, ...)
Поскольку введенные данные должны возвращаться в программу, то аргументами функции должны быть указатели.
Разделителем между вводимыми данными являются символы пробела, табуляции или конца строки. Поэтому в общем случае нет необходимости описывать ширину поля и точность вводимых данных.
Некоторые преобразования scanf_s:
Спецификатор | Тип | СП аргумента | Вид печати |
---|---|---|---|
d, i | int | %d | Целое число |
e или f | float | %e или f | Вещественное число; обязательно присутствие десятичной точки и/или экспоненциальной части |
Пример.
#include "stdafx.h"
void main() {
int a;
float x, y;
// Получить в результате ввода следующие значения:
// a = 7
// x = -5.43
// y = 28.35e12
//
// Вводим с клавиатуры:
// 7 -5.43 28.35e12
scanf_s("%d%f%e", &a, &x, &y);
// Печатаем введенные данные:
printf_s("a = %2d, x = %6.2f, y = %10.2e\n", a, x, y);
// Напечатает:
// a = 7, x = -5.43, y = 2.83e+013
}
Примеры ввода-вывода символа, строки, одномерного и двумерного массива рассмотрены в других разделах.
При работе с файлом можно придерживаться следующей схемы:
Указатель файла должен быть объявлен декларацией
FILE *<имя файлового указателя>; (например, *fp - сокращение от file pointer).
Указатель на файл получаем в результате вызова функции fopen_s, например:
fopen_s(&fp, name, mode);
name - строка, содержащая имя файла;
mode - строка, определяющая режим использования файла, например:
"r" - чтение файла (read); файл должен существовать;
"w" - запись в файл (write); файл создается при выполнении функции записи в файл;
"a" - добавление (append) в конец уже существующего файла.
Например:
fopen_s(&fp, "c:\\a.txt", "r"); // Обратите внимание на число слэшей (символов '\') в определении имени файла
или
char fn[30], md;
fn = "c:\\a.txt";
md = "r";
fopen_s(&fp, fn, md);
При наличии ошибки при открытии файла функция fopen_s заполняет fp значением NULL. Возможна более точная идентификация ошибки.
Файл закрывается функцией fclose, например:
fclose(fp);
Форматный ввод данных выполняется функцией fscanf_s:
int fscanf_s(FILE *_File, const char *_Format, ...)
Форматный вывод данных в файл функцией fprintf_s:
int fprintf_s(FILE *_File, const char *_Format, ...).
Напомним, что аргументами функции fscanf_s должны быть адреса переменных.
Пример. Обеспечить ввод целочисленного массива из файла a.txt.
#include "stdafx.h"
void main() {
int n, nf = 0, i, a[120]; // nf - число введенных из файла элементов
FILE *fp;
fopen_s(&fp, "a.txt", "r"); // Открываем файл a.txt для чтения
// Пусть файл a.txt содержит следующие данные:
// 1 3 -7 4 9 78 22
if(fp == NULL) {
printf_s("Open failed\n");
return;
}
printf_s("Enter number elements to scan: ");
scanf_s("%d", &n);
// В общем случае следует при вводе следует проверять два условия: i < n && !feof(fp)
for(i = 0; i < n && !feof(fp); nf++, i++)
fscanf_s(fp, "%d", &a[i]); // Передаем адрес элемента массива
fclose(fp);
// Контрольный вывод введенного массива
printf_s("nf = %3d\n", nf);
for(i = 0; i < nf; i++) printf_s("%6d", a[i]); // Размер поля вывода - 6 символов
printf_s("\n");
}
При вводе разделителями являются символы пробела, табуляции или конца строки. Следовательно, данные при вводе можно набивать и в строчку, и в столбик; часть данных может находиться в одной строке, а часть в другой.
В примере вместо оператора return для прекращения вычислений можно употребить функцию exit (описана в stdlib.h). Эта функция возвращает в среду, из которой была запущена программа, код завершения, передаваемый в качестве параметра функции. Переданный код может быть обработан средствами среды.
В СИ определяются только функции (в Паскале, например, могут быть определены и процедуры). Функции разделяются на стандартные и пользовательские. Стандартные функции хранятся в в стандартных библиотеках СИ и описываются в заголовочных файлах stdio.h, string.h, math.h, stdlib.h и других. Пользовательские функции создаются программистом. Программа может состоять из отдельных функций, которые могут располагаться в одном или нескольких файлах.
Функция может иметь следующий вид:
[тип результата или void] имя_функции([<декларация аргументов>]) {
раздел описаний переменных
тело функции
[return выражение;]
}
Если "тип результата" и void опущены, то по умолчанию функция имеет тип int. Если функция не возвращает результата, то оператор return выражение можно опустить, а функцию определить как void (тип функции не определен).
В СИ запрещается определять функции внутри других функций.
Связь между функциями осуществляется через аргументы, возвращаемые значения и внешние переменные.
Вызов функции осуществляется по имени. Для получения доступа к функции необходимо описать ее прототип.
Описание прототипа имеет слендующий вид:
[тип результата или void] имя функции([<описание типов аргументов>]).
Если текст функции находится в том же файле, где и программа, вызывающая функцию, причем текст функции расположен в файле раньше, чем текст вызывающей программы, то описание прототипа функции необязательно.
Пример. Выполнить копирование строки sta в строку stb.
Вариант, когда нужно описание прототипа.
#include "stdafx.h"
void copy(char *, char *); // Описание прототипа функции copy
void main() {
char sta[]="abcd", stb[5];
copy(sta, stb); // Копируем строку sta в строку stb
printf_s("stb = %s\n",stb);
}
// Копирует строку sta в строку stb
void copy(char sta[], char stb[]) {
int i = 0;
while((stb[i] = sta[i]) != '\0') i++;
}
Вариант, когда описание прототипа не нужно.
#include "stdafx.h"
void copy(char sta[], char stb[]) {
int i = 0;
while((stb[i] = sta[i]) != '\0') i++;
}
void main() {
char sta[]="abcd", stb[5];
copy(sta,stb);
printf_s("stb = %s\n", stb);
}
Пример. В одномерном целочисленном массиве a найти минимальное значение, принадлежащее отрезку [c, d].
#include "stdafx.h"
int fmin(int *, int, int, int); // Описание прототипа функции fmin
void main() {
int a[120] = {5, 22, 33, 4, 11, 6, 7, 8, 9, 10, -11, 12};
int c = 2, d = -9, n = 12, imin;
// Ищем индекс минимального элемента массива на отрезке [c, d]
// Если на заданном отрезке нет элементов массива, функция fmin возвращает -1
imin = fmin(a, c, d, n);
if(imin == -1)
printf_s("No elements of array a are found in [c, d]\n");
else
printf_s("min = %3d\n", a[imin]);
}
int fmin(int a[], int c, int d, int n) {
int i, ai, imin, min;
min = d + 1;
imin = -1;
for(i = 0; i < n; i++) {
ai = a[i];
if(ai < c || ai > d) continue;
if(ai < min) {
min = ai;
imin = i;
}
}
return imin;
}
В СИ функции не являются переменными можно, однако, определить указатель на функцию и менять его значение, размещать в массиве, передавать в качестве параметра функции, возвращать как результат функции. Описание указателя на функцию должно соответствовать описанию самой функции: число и типы аргументов указателя должны совпадать с числом и типами аргументов функции. Порядок определения и использования указателей на функции рассмотрим в примере.
Пример. Написать функцию root поиска методом простых итераций корня уравнения
x = f(x)
на отрезке [a, b] с заданной точностью eps.
Далее, используя функцию root, найти на отрезке [0, 3] с точностью eps = 0.0001 корни следующих уравнений:
Алгоритм метода простых итераций.
Метод простых итераций иллюстрирует рис. 5.1.
Рис. 5.1. Графическая иллюстрация метода простых итераций
Условия сходимости метода простых итераций: |f '(x)| < 1 и f '(x) < 0.
Текст программы нахождения корней заданных функций (число итераций ограничено).
#include "stdafx.h"
#include <math.h> // Для математических функций
#include <stdlib.h> // Для функции exit
// Функции, возвращающие правые части заданных уравнений
double fx1(double x) { return 1.0 / (1.2 * tan(x) + sqrt(x + 1.0)); }
double fx2(double x) { return (exp(-x) - sqrt(exp(x)) + 3.7) / 3.0; }
// Ищет корень функции fx(x)
// double (*fx)(double) описывает указатель на функцию типа double с одним аргументом типа double
double root(double (*fx)(double), double a, double b, double eps) {
int i, n = 100; // Предельное число итераций
double x, x0 = (a + b) / 2.0;
x = (*fx)(x0);
i = 0; // Текущее число итераций
while(fabs(x - x0) > eps && i <= n) {
i++;
x0 = x;
x = (*fx)(x0);
}
if(i > n) {
printf_s("To many iterations done\n");
exit(1); // Прекращаем вычисления
}
else
return x;
}
void main() {
// p - указатель на функции, принимающую double и возвращающую double
double (*p)(double) = fx1; // Установим указатель p на функцию fx1
double r1, r2;
r1 = root(*p, 0.0, 3.0, 1.0e-4);
p = fx2; // Установим теперь указатель p на функцию fx2
r2 = root(*p, 0.0, 3.0, 1.0e-4);
printf_s("Function fx1 root: r1 = %7.4f\n", r1);
printf_s("Function fx2 root: r2 = %7.4f\n", r2);
}
Иной вариант обращения к функции root:
void main() {
double r1, r2;
r1=root(*fx1, 0.0, 3.0, 1.0e-4);
r2 = root(*fx2, 0.0, 3.0, 1.0e-4);
printf_s("Function fx1 root: r1 = %7.4f\n", r1);
printf_s("Function fx2 root: r2 = %7.4f\n", r2);
}
В примере использованы математические функции tan(x) - tgx; exp(x) - x; fabs(x) - модуль x (|x|). Функции описаны в math.h.
Массив в СИ это именованный набор из конечного числа объектов одного типа, последовательно расположенных в памяти ЭВМ. Элементами массива могут быть числа, символы, структуры и указатели.
Декларация целочисленного одномерного массива a из 10 элементов
int a[10];
Декларация и инициализация одномерного массива
int a[10] = {1, 2, 3, 4, 5}; // Остальные элементы массива равны нулю
или
int a[] = {1, 2, -5, 9, 14, 6, 7, 8, 9, 10}; // Эта декларация определяет массив из десяти элементов
Приведенные декларации определяют массив из 10 объектов (элементов) с именами a[0], a[1], ..., a[9].
Запись a[i] отсылает нас к i-му элементу массива a.
Доступ к элементам массива можно обеспечить при помощи указателей.
Пусть pa есть указатель на int и определен так:
int *pa;
тогда в результате присваивания
pa = &a[0];
pa будет указывать на нулевой элемент массива a, иначе говоря, pa будет содержать адрес элемента a[0].
В СИ имя одномерного массива является указателем на начальный элемент массива. Поэтому разместить указатель на начальный элемент массива можно и в результате присваивания
pa = a;
Присваивание
pa = &a[i];
размещает указатель на i-й элемент массива a. Тот же эффект имеет выполнение оператора
pa = a + i;
Это обусловлено тем, что имя массива в СИ есть указатель на начальный элемент массива.
Пусть pa указывает на i-й элемент массива. Тогда операторы
x = *pa;
и
x = a[i];
будут эквивалентны: в х будет установлено значение элемента а[i].
При этом pa + 1 будет указывать на элемент а[i + 1], pa + 2 будет указывать на элемент а[i + 2] и так далее.
Таким образом, если pa указывает на a[0], то *(pa + i) - это значение элемента a[i].
Ввод и вывод одномерного массива может быть выполнен при помощи указателей или в результате непосредственного обращения к i-му элементу массива.
#include "stdafx.h"
void main() {
int i, a[5], *pa = a; // pa указывает на a[0] - начальный элемент массива а
// Ввод массива
// Нужно задать 5 целых чисел на одной или нескольких строках и затем нажать Enter
for(i = 0; i < 5; i++) scanf_s("%d", pa + i);
// Вывод массива
printf_s("\nArray a: ");
pa = a;
for(i = 0; i < 5; i++) printf_s("%3d", *(pa+i));
printf_s("\n");
}
или
#include "stdafx.h"
void main() {
int i, a[5];
// Ввод массива
// Нужно задать 5 целых чисел на одной или нескольких строках и затем нажать Enter
for(i = 0; i < 5; i++) scanf_s("%d", &a[i]);
// Вывод массива
printf_s("\nМассив a: ");
for(i = 0; i < 5; i++) printf_s("%3d", a[i]);
printf_s("\n");
}
Описание одномерного массива a в списке параметров функции может быть задано так:
int fun(int a[]);
int fun(int *a);
Описание прототипа функции, принимающей одномерный массив, может быть таким:
int fun(int a[]);
int fun(int *a);
int fun(int *);
Обращение к функции fun может быть таким:
y = fun(a);
y = fun(&a[0]);
y = fun(&a[2]);
В первых двух случаях функции передается адрес начала массива a. В последнем случае функции передается адрес третьего элемента массива, поэтому присваивание
x = a[0];
выполняемое в теле такой функции аналогично присваиванию
x = a[2];
выполненному в теле функции, вызвавшей функцию fun.
Пример. Иллюстрируются различные варианты обращения к элементам массива.
#include "stdafx.h"
void main() {
int a[120] = {1, 2, -8}; // Остальные элементы массива есть 0
int *pa = a, *pi;
int i, x1, x2, x3, x4, x5;
int fun(int *); // или fun(int a[])
// pa указатель на a, поэтому, выполнив *pa = a, мы установим в pa адрес a[0]
// pa = &a[0]; то же, что и *pa = a
i = 2;
pi = &a[i]; // pi указывает на элемент a[i]
// Далее следует эквивалентные с точки зрения результата операторы;
// в результате их выполнения получим: x1 = x2 = x3 = x4 = x5 = -8
x1 = a[i];
x2 = *(pa + i);
x3 = *(a + i);
x4 = *pi;
x5 = pa[i];
// x1 = x2 = x3 = x4 = x5 = -8
printf_s("x1 = %3d; x2 = %3d; x3 = %3d; x4 = %3d; x5 = %3d\n", x1, x2, x3, x4, x5);
printf_s("x0 = %3d\n", fun(a)); // Напечатает: 1
for(i = 0; i < 4; i++)
printf_s("x = %3d\n", fun(&a[i])); // Напечатает: 1 2 -8 0
printf_s("\n");
}
int fun(int *a) // или fun(int a[])
{ return a[0]; }
Декларация целочисленного двумерного массива a из 8 элементов, содержащего 2 стрки и 4 столбца
int a[2][4];
Декларация и инициализация двумерного массива
int a[2][4]={{1, -2, 3, -4}, {5, -6, 7, -8}};
или
int a[][4]={{1, -2, 3, -4}, {5, -6, 7, -8}};
Замечание. Наличие второй размерности обязательно.
Приведенные декларации определяют массив из 8 элементов с именами 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[i][j] - отсылает нас к элементу j массива в строке i.
Двумерный массив в СИ - это одномерный массив, каждый элемент которого так же массив - строка двумерного массива.
Доступ к элементам двумерного массива при помощи указателей.
Пусть pa есть указатель на int и определен так:
int *pa;
тогда в результате присваивания
pa = &a[0][0];
или
pa = *a;
pa будет указывать на начальный элемент массива a, иначе говоря, pa будет содержать адрес элемента a[0][0].
Присваивание
pa = &a[i][j];
размещает указатель на элемент в строке i массива a. Тот же эффект мы имеет после выполнения оператора
pa = *a + i * n + j;
где n - число элементов в строке массива (в нашем примере n = 4).
Пусть pa указывает на j-й элемент в строке i массива a. Тогда операторы
x = *pa;
и
x=a[i][j];
будут эквивалентны. При этом pa + 1 будет указывать на следующий элемент массива а.
Ввод двумерного массива в СИ может быть выполнен при помощи указателя на массив или при помощи использования промежуточной переменной. В СИ++ ввод может быть выполнен, так же как и для одномерного массива, в результате непосредственного обращения к элементу массива, например:
scanf_s("%d", &a[i][j]);
Примеры форматного ввода вывода двумерного массива.
#include "stdafx.h"
void main() {
int i, j, a[2][4], *pa = *a; // pa указывает на начальный элемент массива a
for(i = 0; i < 8; i++)
scanf_s("%d", pa + i); // или scanf_s("%d", pa++);
printf_s("\nArray a\n");
pa = &a[0][0];
// Вывод массива по строчкам
for(i = 0; i < 8; i++) {
printf_s("%3d", *(pa + i));
if((i + 1) % 4 == 0) printf_s("\n"); // Переход на новую строку
}
}
или
#include "stdafx.h"
void main() {
int i, j, x, a[2][4];
for(i = 0; i < 2; i++)
for(j = 0; j < 4; j++) {
scanf_s("%d", &x); // В СИ++ можно записать scanf_s("%d", &a[i][j]);
a[i][j] = x;
}
printf_s("\nArray a\n");
// Вывод массива по строчкам
for(i = 0; i < 2; i++) {
for(j = 0; j < 4; j++) printf_s("%3d", a[i][j]);
printf_s("\n"); // Переход на новую строку
}
}
Описание двумерного массива a из 2-х строк и 4-х столбцов в списке параметров функции может быть задано так:
int fun(int a[2][4]);
int fun(int a[][4]);
int fun(int (*a)[4]);
Последняя запись декларирует, что параметр функции есть указатель на массив из четырех значений типа int. Скобки (*a) необходимы, так как квадратные скобки имеют более высокий приоритет, чем * - операция раскрытия ссылки.
Замечание. Наличие второй размерности обязательно. В более общем случае (трех и более мерные массивы) можно при описании функции не указывать только первое измерение.
Описание прототипа функции, принимающей двумерный массив, может быть таким:
int fun(int a[2][4]);
int fun(int a[][4]);
int fun(int (*a)[4]);
Обращение к функции fun может быть следующим:
y = fun(a);
Пример. Иллюстрируются различные варианты обращения к элементам двумерного массива и использование двумерного массива в качестве параметра функции.
#include "stdafx.h"
void main() {
int a[][4] = {{1, 2, - 8, -7}, {-1, -2, 8, 7}};
int *pa = *a; // или *pa = &a[0][0];
int i, j, x1, x2, x3, x4, x5, *pi;
// Прототип функции fun
int fun(int a[][4]); //или int fun(int (*)[4]);
i = 1;
j = 2;
pi = &a[i][j]; // Адрес второго элемента второй строки массива
// Далее следуют эквивалентные с точки зрения результата операторы;
// в результате их выполнения получим: x1 = x2 = x3 = x4 = x5 = 8
x1 = a[i][j];
x2 = *pi;
x3 = *(pa + i * 4 + j);
x4 = *(*a + i * 4 + j);
x5 = pa[i * 4 + j];
// x1 = x2 = x3 = x4 = x5 = 8
printf_s("x1 = %3d; x2 = %3d; x3 = %3d; x4 = %3d; x5 = %3d\n", x1, x2, x3, x4, x5);
// Обращение к функции fun
printf_s("x0 = %3d\n", fun(a)); // x0 = a[1][0] = -1
for(j = 0; j < 4; j++)
printf_s("%3d", a[i][j]); // Будет выведена вторая строка : -1 -2 8 7
printf_s("\n");
pa = *a + 4;
for(j = 0; j < 4; j++)
printf_s("%3d", *(pa + j)); // Будет выведена вторая строка : -1 -2 8 7
printf_s("\n");
for(j = 0; j < 4; j++)
printf_s("%3d", pa[j]); // Будет выведена вторая строка : -1 -2 8 7
printf_s("\n");
}
int fun(int a[][4]) // или int fun(int (*a)[4])
{ return a[1][0]; }
Строка в СИ - это последовательность символов, завершаемая пустым символом '\0'. Строка в СИ есть одномерный массив символов, доступ к элементам которого осуществляется так же, как и к элементам обычного одномерного массива.
Декларация строки из 11 символов:
char st[11];
Декларация и инициализация строки из 11 символов:
char st[11] = "Это строка";
или
char st[] = "Это строка";
Приведенные декларации определяют массив из 11 символов с именами st[0], st[1], ..., st[10].
В этом массиве st[10] = '\0'. Запись st[i] - отсылает нас к i-му символу строки st.
Строки можно вводить, используя функции getchar и _getch чтения символов и функций gets_s и fgets ввода строк.
Вывод строки можно выполнить посимвольно (функции putchar и _putch), употребив функции puts и fputs или функции форматного вывода на консоль или в файл.
Пример посимвольного ввода-вывода строки.
#include "stdafx.h"
#include <conio.h> // Для _getch и _putch
void main() {
int i = 0;
char st[11];
// Завершаем ввод нажатием на Enter ('\n' - символ конца строки)
while((st[i] = getchar()) != '\n' && i < 10) i++;
// В случае _getch вводим всю строку (10 символов)
//while(i < 10) {st[i] = _getch(); i++;}
st[i]='\0';
printf_s("\nString st:\n");
// Вывод putchar
i = 0;
while(st[i]) {putchar(st[i]); i++;}
printf_s("\n");
// Вывод printf_s
printf_s("%s\n", st);
// Вывод puts
puts(st);
// Вывод _putch
i = 0;
while(st[i]) {_putch(st[i]); i++;}
printf_s("\n");
}
Вариант с использованием указателей.
#include "stdafx.h"
void main() {
char st[11], *ps0, *ps = st; // ps указывает на начало строки, то есть на st[0]
ps0 = ps; // (ps - ps0) - длина введенной строки
// Завершаем ввод нажатием на Enter ('\n' - символ конца строки)
while((*ps++ = getchar()) != '\n' && ps - ps0 < 11) ; // ; - пустой оператор
*(ps - 1) = 0; // Символ конца строки (или *(ps - 1) ='\0';)
printf_s("\nString st:\n");
// Вывод начинаем с ps0
while(*ps0++) putchar(*(ps0 - 1));
printf_s("\n");
}
Пример форматного вывода строки.
#include "stdafx.h"
void main() {
char st[11], *ps = st;
// Ввод строки после нажатия на Enter
gets_s(st);
// Два варианта форматного вывода
printf_s("\nString st: %12s\n", st);
printf_s("\nString st: %12s\n", ps);
}
Замечание. Форматный ввод-вывод строк может быть выполнен и при работе с файлами.
Функция ввода строк из файла fgets в СИ описана следующим образом
char *fgets(char *_Buf, int _MaxCount, FILE *_File).
Функция читает строку ввода в массив символов _Buf. Число прочитанных символов не может превышать _MaxCount - 1. Считанная строка добавляется символом '\0'. В случае ошибки функция fgets возвращает NULL.
Функция fputs добавляет в файл строку (которая в общем случае может не заканчиваться символом '\n' - "новая строка").
int fputs(char *line, FILE *fp).
Эта функция возвращает EOF, если возникла ошибка, и нуль в противном случае.
Библиотечные функции fgets и fputs подобны функциям gets_s и puts. Отличаются они тем, что первые оперируют со стандартными устройствами ввода-вывода: клавиатура и монитор. Кроме того, gets_s исключает из строки последнюю литеру '\n', а puts ее добавляет.
Пример. Вывести на экран все строки текстового файла b.txt.
#include "stdafx.h"
#include <stdlib.h> // для вызова функции exit
void main() {
char st[80];
FILE *fp;
fopen_s(&fp, "b.txt", "r"); // Открываем файл b.txt для чтения
if(fp == NULL) {
printf_s("\nOpen failed");
exit(1); // Прекращаем вычисления
}
// Ввод (fgets) из файла и вывод (puts) на консоль
while(fgets(st, 80, fp) != NULL) puts(st);
// Если в файле 3 следующие строки:
// Line one
// Second line
// Line 3
// то на консоли получим:
// Line one
//
// Second line
//
// Line 3
}
Пример. Различные варианты копирования строки st1 в st2.
#include "stdafx.h"
void main() {
char st1[] = "abcd", st2[8];
void copy(char *, char *); // или void copy(char st2[], char st1[]);
copy(st2, st1);
printf_s("st2 = %s\n", st2);
}
// Копирует содержимое st1 в st2
void copy(char *st2, char *st1) { // или void copy(char st2[], char st1[]) {
//
// Вариант 1
int i = 0;
while(st1[i] != '\0') { // или просто: != 0
st2[i] = st1[i]; i++;
}
st2[i] = '\0'; // Добавляем символ конца строки '\0'
puts(st2);
//
// Вариант 2
for(i = 0; (st2[i] = st1[i]) != '\0'; i++);
puts(st2);
//
// Вариант 3
//while((*st2 = *st1) != '\0') {
// st2++;
// st1++;
//}
//
// Вариант 4
//while((*st2++ = *st1++) != '\0');
//
// Вариант 5
while(*st2++ = *st1++);
}
Замечание. При работе со строками употребляются стандартные библиотечные функции, описанные в string.h и stdlib.h.
В некоторых режимах работы, например, при организации прямого ввода-вывода может возникнуть необходимость вычисления размера переменной или размера, отводимого под одну переменную базового или производного типа данных. Такую возможность в СИ предоставляет унарная операция sizeof.
Унарная операция sizeof возвращает целое значение типа size_t, равное размеру указанного объекта или типа в байтах. Операция выполняется на этапе компиляции программы. size_t - тип данных "беззнаковое целое", определенный в файле stddef.h. Использование sizeof:
sizeof <объект>;
или
sizeof(<имя типа>);
В качестве объектов могут выступать имена переменных.
В качестве имени типа может использоваться как имя базового типа (int, float, ...), так и с квалификатором или имя производного типа (short int, long double, struct exam, ...).
Пример. Определить число байт, отводимых компилятором под тип long double и целочисленный массив a из 120 элементов.
#include "stdafx.h"
void main() {
size_t c, d;
int a[120];
c = sizeof(long double); // Ответ: с = 8
d = sizeof a; // Ответ d: = 480
printf_s("\nSize of long double: %2d", c);
printf_s("\nSize of int array a[120]: %3d\n", d);
}
До сих пор мы имели дело с форматным вводом-выводом, позволяющим напрямую работать либо с простыми имеющими базовый тип данных переменными, либо со строками. Вывод (чтение), например, массива за одно обращение к оператору вывода (ввода) при форматном вводе-выводе невозможно: для решения этой задачи нужно организовать цикл, в котором поочередно выводились (читались) элементы массива.
Пример. Вывести n элементов целочисленного массива в файл c.txt, располагая в каждой строке по 10 чисел.
#include "stdafx.h"
void main() {
int i, n = 34, a[120] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
FILE *fp;
fopen_s(&fp, "c.txt", "w");
if(fp == NULL) {
printf_s("Open failed\n");
return;
}
for(i = 0; i < n; i++) {
fprintf_s(fp, "%4d", a[i]);
// Если (i + 1) % 10 == 0, тогда переходим на новую строку файла
if((i + 1) % 10 == 0) fprintf_s(fp, "\n"); // % - операция определения остатка о деления
}
}
Результат:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0
Прямой вывод в файл, то есть вывод без преобразования данных, обеспечивает функция fwrite, а ввод из файла, сформированного этой функцией, выполняется функцией fread или fread_s. Описание функций находится в stdio.h:
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp).
size_t __cdecl fwrite(const void *_Str, size_t _Size, size_t _Count, FILE *_File);
size_t __cdecl fread(void *_DstBuf, size_t _ElementSize, size_t _Count, FILE *_File);
size_t __cdecl fread_s(void *_DstBuf, size_t _DstSize, size_t _ElementSize, size_t _Count, FILE *_File);
Функция fwrite пишет из _Str в _File _Count объектов размера _Size и возвращает число записанных объектов, которое в случае ошибки меньше _Count. Для индикации состояния чтения следует использовать функцию ferror.
Функция fread_s читает из _File в _DstBuf размера _DstSize не более _Count объектов размера _ElementSize. Она возвращает количество прочитанных объектов, которое в случае ошибки может быть меньше заявленного. Для индикации состояния чтения можно использовать функции feof и ferror.
Функция fread отличается от fread_s отсутствием параметра _DstBuf.
Напомним, что size_t - тип данных беззнаковое целое, определенный в файле stddef.h. Значение типа size_t возвращается унарной операцией sizeof, которая возвращает целое значение, равное размеру указанного объекта или типа в байтах.
Замечание. При работе с функциями прямого ввода-вывода употребляются двоичные файлы.
Пример. Записать в двоичный файл a.dat 8 раз заданный целочисленный массив из 10 элементов. Из созданного файла ввести данные в целочисленный массив из 80 элементов.
#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
void main() {
int i, b[80], a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
size_t sta = sizeof(a); // Размер в байтах порции вывода и ввода
FILE *fp;
fopen_s(&fp, "a.dat", "wb"); // Открываем двоичный файл для записи
if(fp == NULL) {
printf_s("Open failed\n");
exit(1);
}
for(i = 1; i <= 8; i++)
fwrite(a, sta, 1, fp); // sta - число выводимых в файл за один раз байт
fclose(fp);
// Откроем созданный файл для чтения и выполним ввод данных
fopen_s(&fp, "a.dat", "rb"); // Помним, что работаем с двоичным файлом
fread_s(b, sizeof b, sta, 8, fp); // Читаем в массив b порцию размером в sta байт 8 раз
// или
//fread(b, sta, 8, fp); // Читаем в массив b порцию размером в sta байт 8 раз
fclose(fp); // Выведем массив на консоль, располагая на строке 10 чисел
printf_s("\nArray b\n");
for(i = 0; i < 80; i++) {
printf_s("%5d", b[i]);
if((i + 1) % 10 == 0) printf_s("\n");
}
}
Результат:
Array b
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
...
1 2 3 4 5 6 7 8 9 10
Многие функции при работе с файлами в случае ошибки или конца файла устанавливают индикаторы состояния файла. Примером такой функции является функция fread. В Эти индикаторы можно проверять и изменять, например, посредством функций feof, ferror и clearerr:
int feof(FILE *_File)
feof - возвращает не нулевое значение, если достигнут конец файла _File.
int ferror(FILE *_File)
ferror - возвращает не нулевое значение, если возникла ошибка при работе с файлом _File.
int clearerr(FILE *_File)
clearerr - очищает индикаторы конца файла и ошибки файла _File.
Пример. Если выполняется попытка чтения данных из файла, открытого для записи, то функция ferror возвращает отличное от нуля значение, то есть сигнализирует об ошибке.
#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
void main() {
int b[120], a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
size_t sta = sizeof(a); // Размер в байтах порции вывода и ввода
FILE *fp;
fopen_s(&fp, "a.dat", "wb");
if(fp == NULL) {
printf_s("Open failed\n");
exit(1);
}
// Ошибка: попытка чтения из пустого файла
fread(b, sta, 1, fp);
// Пример применения ferror
printf_s("%5d\n", ferror(fp));
if(ferror(fp)) {
printf ("Read failed\n");
exit(1);
}
fclose(fp);
}
Замечание. Пример использования функции feof можно посмотреть, например, в разд. 4.5.
Структура - это одна или несколько переменных, сгруппированных под одним именем.
Структура является производным типом данных и может включать как базовые типы данных, так и иные небазовые типы.
Структура объявляется в разделе описаний. Объявление структуры имеет следующий вид:
struct <имя структуры> {
<тип1> <имя компоненты1 типа1>, <имя компоненты2 типа1>, ...;
<тип2> <имя компоненты1 типа2>, <имя компоненты2 типа2>, ...;
...
<типn> <имя компоненты1 типаn>, <имя компоненты2 типаn>,...;
} [<список переменных типа struct <имя структуры>];
В качестве примера рассмотрим формирование структуры struct exam, отражающей результаты экзаменационной сессии студента. Объявим также и переменные с12 и с6 типа struct exam, выполнив инициализацию переменной с6.
struct exam {
char name[50]; // Фамилия, имя и отчество
int m1, m2, m3, m4; // Оценки студента соответственно на 1-м, 2-м, 3-м и 4-м экзаменах
} c12, c6 = {"Алешин С. А.", 4, 4, 5, 5};
Возможен и иной способ объявления структуры и переменных: первоначально объявляется структура как тип, а затем уже объявляются переменные.
// Объявление структуры как типа
struct exam {
char name[50];
int m1, m2, m3, m4;
};
struct exam c12, c6 = {"Алешин С. А.", 4, 4, 5, 5};
Доступ к отдельному полю структуры выполняется одним из следующих способов:
где "." и -> - это операции доступа к полю структуры.
Продолжая начатый пример, запишем в переменную v1 оценку студента Алешина С. А. за первый экзамен, используя поочередно каждый из приведенных способов.
struct exam {
char name[50];
int m1, m2, m3, m4;
}
// Переменные типа struct exam
struct exam c12, c6 = {"Алешин С.А.". 4, 4, 5, 5}, *p = &c6;
// Переменная p является указателем на переменную c6
int v1;
v1 = c6.m1;
v1 = p->m1;
v1 = (*p).m1;
v1 = (&c6)->m1;
Пример. Сформировать двоичный файл, содержащий результаты сдачи студентами группы С6 зимней сессии. Вывести фамилии студентов, средняя оценка которых превышает среднюю оценку группы. Запись файла должна содержать поля, определенные в следующей структуре
struct exam {
char name[50];
int m1, m2, m3, m4;
float am;
};
где am - средняя оценка студента:
am = (m1+m2+m3+m4) / 4
Создадим, во-первых, программу, обеспечивающую запись данных в файл. Чтение данных из файла и их обработку выполним в другой программе.
В коде предполагается, что оценки разделяются одним пробелом.
Ввод данных будет продолжаться до тех пор, пока после сообщения Press Y to continue будет вводиться символ 'Y' или 'y'.
#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
#include <conio.h> // Для вызова _getche
#include <ctype.h> // Для вызова toupper
void main() {
size_t st;
char fl = 'Y';
char m[20], marks[20]; // Строка для ввода оценок
struct exam {
char name[50];
int m1, m2, m3, m4;
float am;
};
struct exam tmp, *t = &tmp;
FILE *fp;
// Открываем для записи двоичный файл
fopen_s(&fp, "exam.dat", "wb");
if(fp == NULL) {
printf_s("Open failed\n");
exit(1);
}
st = sizeof(struct exam);
while(fl == 'Y') {
printf_s("\nName: ");
gets_s(t->name);
printf_s("Marks: ");
gets_s(marks);
// Получаем из строки marks оценки, полагая, что они разделены одним пробелом
m[0] = marks[0]; m[1] = '\0';
t->m1 = atoi(m); // Преобразование строки в число
m[0] = marks[2]; m[1] = '\0';
t->m2 = atoi(m);
m[0] = marks[4]; m[1] = '\0';
t->m3 = atoi(m);
m[0] = marks[6]; m[1] = '\0';
t->m4 = atoi(m);
// Контрольный вывод
printf_s("Student: %s\n", t->name);
printf_s("Marks: %3d%3d%3d%3d\n", t->m1, t->m2, t->m3, t->m4);
t->am = (float)(t->m1 + t->m2 + t->m3 + t->m4) / 4.0f;
fwrite(&tmp, st, 1 ,fp); // Добавляем запись в файл
printf_s("Press Y to continue ");
fl = toupper(_getche()); // Функция toupper переводит символ в верхний регистр
}
fclose(fp);
}
Введем следующие данные (показан также и контрольный вывод):
Name: Popov A. C.
Marks: 4 5 5 4
Student: Popov A. C.
Marks: 4 5 5 4
Press Y to continue y
Name: Pasick V. D.
Marks: 4 3 4 3
Student: Pasick V. D.
Marks: 4 3 4 3
Press Y to continue y
Name: Fedorov A. K.
Marks: 4 4 5 5
Student: Fedorov A. K.
Marks: 4 4 5 5
Press Y to continue y
Name: Sarnov D. E.
Marks: 2 3 3 3
Student: Sarnov D. E.
Marks: 2 3 3 3
Программа чтения и обработки двоичного файла exam.dat.
#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
void main() {
size_t st;
float s = 0; // Средняя оценка группы
int i, n = 0; // n - число студентов в группе
struct exam {
char name[50];
int m1, m2, m3, m4;
float am;
};
struct exam group[30], *t = group; // В массив group будут помещены записи файла exam.dat
FILE *fp;
// Открываем для записи двоичный файл
fopen_s(&fp, "exam.dat", "rb");
if(fp == NULL) {
printf_s("Open failed");
exit(1);
}
st = sizeof(struct exam);
// Заполняем массив group и вычисляем среднюю оценку группы
// Цикл завершается при обнаружении конца файла exam.dat
while(1) {
fread(t, st, 1, fp);
if(feof(fp)) break; // Покидаем цикл, если достигнут конец файла
s += t->m1 + t->m2 + t->m3 + t->m4;
// Контрольный вывод
printf_s("\nStudent: %s\nMarks: %3d%3d%3d%3d%5.2f", t->name, t->m1, t->m2, t->m3, t->m4, t->am);
t++; // Устанавливаем указатель на следующий элемент массива group
n++;
}
fclose(fp);
s = s / (4 * n); // Средняя оценка группы
printf_s("\nGroup average mark: %5.2f", s);
t = group; // Перемещаем указатель на начало массива group
printf_s("\nStudents list which average mark > group average mark");
for(i = 0; i < n; i++, t++)
if(t->am > s) printf_s("\n%-35s%5.2f", t->name, t->am);
printf_s("\n");
}
Результат (показан и контрольный вывод):
Student: Popov A. C.
Marks: 4 5 5 4 4.50
Student: Pasick V. D.
Marks: 4 3 4 3 3.50
Student: Fedorov A. K.
Marks: 4 4 5 5 4.50
Student: Sarnov D. E.
Marks: 2 3 3 3 2.75
Group average mark: 3.81
Students list which average mark grater then group average mark
Popov A. C. 4.50
Fedorov A. K. 4.50
Даны три массива a, b и с. Найти массив, содержащий наименьшее число нулевых элементов.
#include "stdafx.h"
#include <stdlib.h>
int inar(FILE *, int *); // Заполняет массив данными из указанного файла
void outar(int *, int); // Печатает массив
int f0(int *, int); // Находит число нулевых элементов в массиве
void main() {
int a[80], b[80], c[80], na, nb, nc, ka, kb, kc, km;
FILE *fp;
fopen_s(&fp, "abc.txt", "r");
if(fp == NULL) {
printf_s("\nOpen failed\n");
exit(1);
}
// Ввод данных
na = inar(fp, a);
nb = inar(fp, b);
nc = inar(fp, c);
// Контрольный вывод
outar(a, na);
outar(b, nb);
outar(c, nc);
// Поиск числа нулевых элементов в массивах
ka = f0(a, na);
kb = f0(b, nb);
kc = f0(c, nc);
// km - минимальное из трех чисел ka, kb и kc
km = ka < kb ? ka : kb;
km = km < kc ? km : kc;
// Вывод результата
if(ka == 0 || kb == 0 || kc == 0)
printf_s("\nNot all arrays has elements equal 0");
else {
if(km == ka) printf_s("\nArray a");
if(km == kb) printf_s("\n Array b");
if(km == kc) printf_s("\n Array c");
}
printf_s("\n");
}
// Заполняет массив данными из указанного файла
/*
Файл abc.txt имеет следующую структуру:
число элементов в массиве a
перечень элементов массива a
число элементов в массиве b
перечень элементов массива b
число элементов в массиве c
перечень элементов массива c
*/
int inar(FILE *fp, int d[]) {
int i, nd;
fscanf_s(fp, "%d", &nd);
for(i = 0; i < nd; i++) fscanf_s(fp, "%d", &d[i]);
return nd;
}
// Печать массива
void outar(int d[],int nd) {
int i;
printf_s("\n n = %3d\n", nd);
for(i = 0; i < nd; i++) printf_s("%3d", d[i]);
}
// Подсчет число нулевых элементов в массиве
int f0(int d[], int nd) {
int i, kd = 0;
for(i = 0; i < nd; i++)
if(d[i] == 0) kd++;
return kd;
}
Состав файла abc.txt:
7
1 2 3 4 0 0 0
6
1 2 0 0 5 6
8
1 2 3 0 0 6 7 8
Результат:
Array b
Array c