Иницализация массивов указателей
Напишем функцию month_name(n), которая возвращает указатель на строку символов, содержащий название n-го месяца. Эта функция идеальна для демонстрации использования статического массива. Функция month_name имеет в своем личном распоряжении массив строк, на одну из которых она и возвращает указатель. Ниже покажем, как инициализируется этот массив имен.
Синтаксис задания начальных значений аналогичен синтаксису предыдущих инициализаций:
/* month_name: возвращает имя n-го месяца */char *month_name(int n){ static char *name[] = { "Неверный месяц", "Январь","Февраль","Март","Апрель","Май","Июнь", "Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь" }; return (n < 1 || n > 12) ? name[0] : name[n];}Объявление name массивом указателей на символы такое же, как и объявление lineptr в программе сортировки. Инициализатором служит список строк, каждой из которых соответствует определенное место в массиве. Символы i-й строки где-то размещены, и указатель на них запоминается в name[i]. Так как размер массива name не специфицирован, компилятор вычислит его по количеству заданных начальных значений.
УКАЗАТЕЛИ И МНОГОМЕРНЫЕ МАССИВЫ
Начинающие программировать на Си иногда не понимают, в чем разница между двумерным массивом и массивом указателей вроде name из приведенного примера. Для двух следующих определений:
int a[10][20];int *b[10];записи a[3][4] и b[3][4] будут синтаксически правильным обращением к некоторому значению типа int. Однако только a является истинно двумерным массивом: для двухсот элементов типа int будет выделена память, а вычисление смещения элементаa[строка][столбец] от начала массива будет вестись по формуле 20 * строка + столбец, учитывающей его прямоугольную природу. Для b же определено только 10 указателей, причем без инициализации. Инициализация должна задаваться явно -либо статически, либо в программе. Предположим, что каждый элемент b указывает на двадцатиэлементный массив, в результате где-то будут выделены пространство, в котором разместятся 200 значений типа int, и еще 10 ячеек для указателей. Важное преимущество массива указателей в том, что строки такого массива могут иметь разные длины. Таким образом, каждый элемент массива b не обязательно указывает на двадцатиэлементный вектор; один может указывать на два элемента, другой - на пятьдесят, а некоторые и вовсе могут ни на что не указывать.
Наши рассуждения здесь касались целых значений, однако чаще массивы указателей используются для работы со строками символов, различающимися по длине, как это было в функции month_name. Сравните определение массива указателей и соответствующий ему рисунок:
char *name[] = {"Неправильный месяц", "Янв", "Февр", "Март"};
с объявлением и рисунком для двумерного массива:
char aname[][15] = {"Неправ. месяц", "Янв", "Февр", "Март"};АРГУМЕНТЫ КОМАНДНОЙ СТРОКИ
В операционной среде, обеспечивающей поддержку Си, имеется возможность передать аргументы или параметры запускаемой программе с помощью командной строки. В момент вызова main получает два аргумента. В первом, обычно называемом argc (сокращение от argument count), стоит количество аргументов, задаваемых в командной строке. Второй, argv (от argument vector), является указателем на массив символьных строк, содержащих сами аргументы. Для работы с этими строками обычно используются указатели нескольких уровней.
Аргумент командной строки — это информация, которая вводится в командной строке операционной системы вслед за именем программы. Например, чтобы запустить компиляцию программы, необходимо в командной строке после подсказки набрать примерно следующее:
cc имя_программы
имя_программы представляет собой аргумент командной строки, он указывает имя той программы, которую вы собираетесь компилировать.
Чтобы принять аргументы командной строки, используются два специальных встроенных аргумента:argc и argv. Параметр argc содержит количество аргументов в командной строке и является целым числом, причем он всегда не меньше 1, потому что первым аргументом считается имя программы. А параметр argv является указателем на массив указателей на строки. В этом массиве каждый элемент указывает на какой-либо аргумент командной строки. Все аргументы командной строки являются строковыми, поэтому преобразование каких бы то ни было чисел в нужный двоичный формат должно быть предусмотрено в программе при ее разработке.
Вот простой пример использования аргумента командной строки. На экран выводятся слово Привети ваше имя, которое надо указать в виде аргумента командной строки.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc!=2) {
printf("Вы забыли ввести свое имя.\n");
exit(1);
}
printf("Привет %s", argv[1]);
return 0;
}
Если вы назвали эту программу name (имя) и ваше имя Том, то для запуска программы следует в командную строку ввести name Том. В результате выполнения программы на экране появится сообщениеПривет, Том.
Во многих средах все аргументы командной строки необходимо отделять друг от друга пробелом или табуляцией. Запятые, точки с запятой и тому подобные символы разделителями не считаются. Например,
run Spot, run
состоит из трех символьных строк, в то время как
Эрик, Рик, Фред
представляет собой одну символьную строку — запятые, как правило, разделителями не считаются.
Если в строке имеются пробелы, то, чтобы из нее не получилось несколько аргументов, в некоторых средах эту строку можно заключать в двойные кавычки. В результате вся строка будет считаться одним аргументом. Чтобы подробнее узнать, как в вашей операционной системе задаются параметры командной строки, изучите документацию этой системы.
Очень важно правильно объявлять argv. Вот как это делают чаще всего:
char *argv[];
Пустые квадратные скобки указывают на то, что у массива неопределенная длина. Теперь получить доступ к отдельным аргументам можно с помощью индексации массива argv. Например, argv[0]указывает на первую символьную строку, которой всегда является имя программы; argv[1] указывает на первый аргумент и так далее.
УКАЗАТЕЛИ НА ФУНКЦИИ
В Си сама функция не является переменной, но можно определить указатель на функцию и работать с ним, как с обычной переменной: присваивать, размещать в массиве, передавать в качестве параметра функции, возвращать как результат из функции и т.д. Для иллюстрации этих возможностей воспользуемся программой сортировки, которая уже встречалась в настоящей главе. Изменим ее так, чтобы при задании необязательного аргумента -n вводимые строки упорядочивались по их числовому значению, а не в лексикографическом порядке
В обращениях к функциям qsort, strcmp и numcmp их имена трактуются как адреса этих функций, поэтому оператор & перед ними не нужен, как он не был нужен и перед именем массива.
Функция swap, меняющая местами два указателя, идентична той, что мы привели ранее в этой главе за исключением того, что объявления указателей заменены на void*.
void swap(void *v[], int i, int j){ void *temp; temp = v[i]; v[i] = v[j]; v[j] = temp;}Программу сортировки можно дополнить и множеством других возможностей; реализовать некоторые из них предлагается в качестве упражнений.
double func1(double x, double grade)
{
double result = 3 * pow(x, grade) + 1;
return result;
}
double func2(double x)
{
double result = sin(x) + cos(x);
return result;
}
double func3(double x)
{
double result = exp(x);
return result;
}
int main()
{
double f;
int type;
double(*ptrFunc1)(double x, double grade) = &func1;
double(*ptrFunc2)(double x) = &func2;
double(*ptrFunc3)(double x) = &func3;
switch (type)
{
case 1:
f = ptrFunc1(x, grade);
break;
case 2:
f = ptrFunc2(x);
break;
case 3:
f = ptrFunc3(x);
break;
default:
break;
}
return 0;
}
СЛОЖНЫЕ ОБЪЯВЛЕНИЯ
Иногда Си ругают за синтаксис объявлений, особенно тех, которые содержат в себе указатели на функции. Таким синтаксис получился в результате нашей попытки сделать похожими объявления объектов и их использование. В простых случаях этот синтаксис хорош, однако в сложных ситуациях он вызывает затруднения, поскольку объявления перенасыщены скобками и их невозможно читать слева направо. Проблему иллюстрирует различие следующих двух объявлений:
int *f(); /* f: функция, возвращающая ук-ль на int */int (*pf)(); /* pf: ук-ль на ф-цию, возвращающую int */Приоритет префиксного оператора * ниже, чем приоритет (), поэтому во втором случае скобки необходимы.
Хотя на практике по-настоящему сложные объявления встречаются редко, все же важно знать, как их понимать, а если потребуется, и как их конструировать. Укажем хороший способ: объявления можно синтезировать, двигаясь небольшими шагами с помощью typedef, этот способ рассмотрен в параграфе 6.7. В настоящем параграфе на примере двух программ, осуществляющих преобразования правильных Си-объявлений в соответствующие им словесные описания и обратно, мы демонстрируем иной способ конструирования объявлений. Словесное описание читается слева направо.
char **argv argv: указ. на указ. на charint (*daytab)[13] daytab: указ. на массив[13] из intint (*daytab)[13] daytab: массив[13] из указ. на intvoid *comp() comp: функц. возвр. указ. на voidvoid (*comp)() comp: указ. на функц. возвр. voidchar (*(*x())[])() x: функц. возвр. указ. на массив[] из указ. на функц. возвр. charchar(*(*x[3])())[5] x: массив[3] из указ. на функц. возвр. указ. на массив[5] из char