Русификация под Visual
При работе в консольном приложении Visual ввод-вывод выполняется в кодировке ASCII, которая является международной только в первой половине кодов (от 0 до 127, см. Приложение 1). Символы национального (русского) алфавита - вторая половина кодов. Для выполнения этого можно использовать функцию CharToOem() для преобразования символов из кодировки ANSI в кодировку ASCII и функцию OemToChar() для обратного преобразования, находящиеся в библиотеке windows.h. Приведем пример их использования.
...
#include<windows.h>
char bufRus[256];
char* Rus(const char*); // Описание прототипа
void main(void)
{ int a=2;
float r=5.5;
char s[]="Минск !", s1[256];
printf("\n %s ",Rus(s));
printf("\n Vvedi string ");
gets(s1);
printf("\n %s ",s1);
printf(Rus("\n Значение а = %d r = %f\n"), a, r);
}
char* Rus(const char *text) // Функция преобразования символов
{
CharToOem(text, bufRus);
return bufRus;
}
Функции пользователя
В отличие от других языков программирования высокого уровня в языке «С» нет разделения на подпрограммы-процедуры, подпрограммы-функции, здесь вся программа строится только из функций.
В языке «С» любая подпрограмма – функция, представляющая собой отдельный программный модуль, к которому можно обратиться в любой момент и (в случае необходимости) передавать через параметры некоторые исходные данные и который (в случае необходимости) способен возвращать один или несколько результатов своей работы.
Декларация функции
Как объект языка Си, функцию необходимо объявить. Объявление функции пользователя, т.е. ее декларация, выполняется в двух формах – в форме описания и в форме определения.
Описание функции заключается в приведении вначале программного файла ее прототипа. Прототип функции сообщает компилятору о том, что далее в тексте программы будет приведено ее полное определение (полный ее текст): в текущем или другом файле исходного текста либо находится в библиотеке.
В стандарте языка используется следующий способ декларации функций:
тип_результата ID_функции(тип переменной1, …, тип переменной N);
Заметим, что идентификаторы переменных в круглых скобках прототипа указывать не обязательно, так как компилятор языка их не обрабатывает.
Описание прототипа дает возможность компилятору проверить соответствие типов и количества параметров при фактическом вызове этой функции.
Пример описания функции fun со списком параметров:
float fun(int, float, int, int);
Полное определение функции имеет следующий вид:
тип_результата ID_функции(список параметров)
{
код функции
}
Тип результата определяет тип выражения, значение которого возвращается в точку ее вызова при помощи оператора return <выражение>.
Если тип функции не указан, то по умолчанию предполагается тип int.
Список параметров состоит из перечня типов и идентификаторов параметров, разделенных запятыми.
Функция может не иметь параметров, но круглые скобки необходимы в любом случае.
Если функция не возвращает никакого значения, она должна быть описана как функция типа void (пустая).
В данном случае оператор return можно не ставить.
В функции может быть несколько операторов return, но может и не быть ни одного. В таких случаях возврат в вызывающую программу происходит после выполнения последнего оператора в функции.
Пример функции, определяющей наименьшее значение из двух целочисленных переменных:
int min (int x, int y)
{
return (x<y)? x : y;
}
Все функции, возвращающие значение, должны использоваться в правой части выражений языка С, иначе возвращаемый результат будет утерян.
Если у функции отсутствует список параметров, то при декларации такой функции желательно в круглых скобках также указать ключевое слово void. Например, void main(void).
В языке С каждая функция – это отдельный блок программы, вход в который возможен только через вызов данной функции.
Наличие определения функции делает излишним запись ее описания в остатке файла исходного текста.
Вызов функции
Вызов функции имеет следующий формат:
ID _функции (список_аргументов);
где в качестве аргументов можно использовать константы, переменные, выражения (их значения перед вызовом функции будут определены компилятором).
Аргументы списка вызова должны полностью совпадать со списком параметров вызываемой функции по количеству, по порядку следования и по типам соответствующих им параметров.
Связь между функциями осуществляется через аргументы и возвращаемые функциями значения. Ее можно осуществить также через внешние, глобальные переменные.
Функции могут располагаться в исходном файле в любом порядке. А сама исходная программа может размещаться в нескольких файлах.
В языке С аргументы при стандартном вызове функции передаются по значению, т.е. в стеке выделяется место для формальных параметров функции и в это выделенное место при ее вызове заносятся значения фактических аргументов. Затем функция использует и может изменять эти значения в стеке.
При выходе из функции измененные значения теряются. Вызванная функция не может изменить значения переменных, указанных как фактические аргументы при обращении к данной функции.
В случае необходимости функцию можно использовать для изменения передаваемых ей аргументов. В этом случае в качестве аргумента необходимо в вызываемую функцию передавать не значение переменной, а ее адрес. А для обращения к значению аргумента-оригинала использовать операцию «*».
Пример функции, в которой меняются местами значения аргументов x и y:
void zam (int *x, int*y) {
int t;
t = *x;
*x = *y;
*y = t;
}
Участок программы с обращением к данной функции:
void zam (int*, int*);
void main (void) {
int a=2, b=3;
…
printf(" a = %d , b = %d\n", a, b);
zam (&a, &b);
printf(" a = %d , b = %d\n", a, b);
…
}
При таком способе передачи аргументов в вызываемую функцию, их значения будут изменены, т.е. на экран монитора будет выведено:
a = 2 , b=3
a = 3 , b=2
Операция typedef
Любому типу данных, как стандартному, так и определенному пользователем, можно задать новое имя с помощью операции typedef:
typedef <тип> <новое_имя>;
Введенный таким образом новый тип используется аналогично стандартным типам, например, введя пользовательские типы:
typedef unsigned int UINT;
typedef char M_s[100];
декларации идентификаторов введенных типов имеют вид:
UINT i, j; ® две переменные типа unsigned int
M_s str[10]; ® массив из 10 строк по 100 символов
15.4. Указатели на функции
В языке С идентификатор функции является константным указателем на начало функции в оперативной памяти и не может быть значением переменной. Но имеется возможность декларировать указатели на функции, с которыми можно обращаться как с переменными (например, можно создать массив, элементами которого будут указатели на функции).
Рассмотрим методику работы с указателями на функции.
1. Как и любой объект языка С, указатель на функции необходимо декларировать. Формат объявления указателя на функции следующий:
тип (*переменная-указатель)(список параметров);
т.е. декларируется указатель, который можно устанавливать на функции, возвращающие результат указанного типа и которые имеют указанный список параметров. Наличие первых круглых скобок обязательно, так как без них – это декларация функции, которая возвращает указатель на результат своей работы указанного типа и имеет указанный список параметров.
Например, объявление вида: float (* p_f)(char, float);говорит о том, что декларируется указатель p_f, который можно устанавливать на функции, возвращающие вещественный результат и имеющие два параметра: первый – символьного типа, а второй – вещественного типа.
2. Идентификатор функции является константным указателем, поэтому для того, чтобы установить переменную-указатель на конкретную функцию, достаточно ей присвоить ее идентификатор:
переменная-указатель = ID_функции;
Например, имеется функция с прототипом: float f1(char, float);тогда операция p_f = f1; установит указатель p_1 на данную функцию.
3. Вызов функции после установки на нее указателя выглядит так:
(*переменная-указатель)(список аргументов);
или
переменная-указатель (список аргументов);
После таких действий кроме стандартного обращения к функции:
ID_функции(список аргументов);
появляется еще два способа вызова функции:
(*переменная-указатель)(список аргументов);
или
переменная-указатель (список аргументов);
Последнее справедливо, так как p_f также является адресом начала функции в оперативной памяти.
Для нашего примера к функции f1 можно обратиться следующими способами:
f1(‘z’, 1.5); // Обращение к функции по ID
(* p_f)(‘z’, 1.5); // Обращение к функции по указателю
p_f(‘z’, 1.5); // Обращение к функции по ID указателя
4. Пусть имеется вторая функция с прототипом: float f2(char, float);
тогда переустановив указатель p_f на эту функцию: p_f = f2; имеем опять три способа ее вызова:
f2(‘z’, 1.5); // по ID функции
(* p_f)(‘z’, 1.5); // по указателю на функцию
p_f(‘z’, 1.5); // по ID указателя на функцию
Основное назначение указателей на функции – это обеспечение возможности передачи идентификаторов функций в качестве параметров в функцию, которая реализует некоторый вычислительный процесс, используя формальное имя вызываемой функции.
Пример: написать функцию вычисления суммы sum,обозначив слагаемое формальной функцией fun(x), а при вызове функции суммирования передавать через параметр реальное имя функции, в которой запрограммирован явный вид этого слагаемого. Например, пусть требуется вычислить две суммы:
и .
Поместим слагаемые этих сумм в пользовательские функции f1 и f2, соответственно.
При этом для более удобной работы воспользуемся операцией typedef,введем пользовательский тип данных: указатель на функции, который можно устанавливать на функции, возвращающие результат, указанного типа, и имеющие указанный список параметров:
typedef тип_результата (* переменная-указатель)(параметры);
Тогда в списке параметров функции суммирования достаточно указывать фактические ID функций данного типа.
Текст программы для решения данной задачи может быть следующим:
. . .
// Декларация пользовательского типа: указатель на функции,
// возвращающие float результат и имеющие один float параметр
typedef float (*p_f)(float);
float sum(p_f fun, int, float); // Декларации прототипов функций
float f1(float);
float f2(float);
void main(void) {
float x, s1, s2;
int n;
puts(" Введите кол-во слагаемых n и значение x: ");
scanf(“%d%f”, &n, %x);
s1=sum(f1, 2*n, x);
s2=sum(f2, n, x);
printf("\n\t N = %d , X = %f", n, x);
printf(“\n\t Сумма 1 = %f\n\t Сумма 2 = %f", s1, s2);
}
// Функция вычисления суммы, первый параметр которой – формальное имя
// функции, введенного с помощью typedef типа
float sum(p_f fun, int n, float x) {
float s=0;
for(int i=1; i<=n; i++) s+=fun(x);
return s;
}
// Первое слагаемое
float f1(float r) {
return (r/5.);
}
// Второе слагаемое
float f2(float r) {
return (r/2.);
}
В заключение рассмотрим оптимальную передачу в функции одномерных и двухмерных массивов.
Передача в функцию одномерного массива:
void main (void)
{
int vect [20];
…
fun(vect);
…
}
void fun( int v [ ])
{ … }
Передача в функцию двухмерного массива:
void main(void)
{
int mass [ 2 ][ 3 ]={{1,2,3}, {4,5,6}};
…
fun (mas);
…
}
void fun( int m [ ][3])
{ … }