goto,
характеризуя их как чудесный способ написания
Так как х - int, а рх -
выдает
рх = х;
присваивает х рх ; говорят, что рх "указывает" на х.
Операция применима только к (х-1) и 3 являются незаконными.
Нельзя также получить
Унарная операция * рассматривает свой операнд как y тоже
имеет int, то
y = *рх;
присваивает y содержимое того, на что указывает рх.
Так
рх = х; y = *рх;
присваивает y то же самое значение, что и
y = x;
int x, y; int *px;
с x и y мы уже неоднократно
встречались.
int *px;
является новым и должно рассматриваться как мнемоническое; оно говорит, что
комбинация *px имеет int. Это означает, что если px появляется в контексте *px, то это эквивалентно int. Фактически синтаксис
double atof(), *dp;
говорит, что atof() и *dp имеют в выражениях
значения double.
Вы должны также заметить, что из этого
px
указывает на целое x, то *px может появляться в
любом контексте, где может встретиться x. Так
y = *px + 1
присваивает y значение, на 1 большее
значения x ;
printf("%d\n", *px)
печатает текущее значение x ;
d = sqrt((double) *px)
получает в d квадратный корень из x, причем
до передачи sqrt значение x преобразуется
к double. (Смотри лекцию №2).
В выражениях вида
y = *px + 1
унарные операции * и связаны со
своим операндом более крепко, чем
арифметические операции, так что такое выражение берет то значение, на которое
указывает px, прибавляет 1 и присваивает
результат y. Мы вскоре вернемся к тому, что может
означать выражение
y = *(px + 1)
Ссылки на px указывает на x, то
*px = 0
полагает x равным нулю, а
*px += 1
увеличивает его на единицу, как и выражение
(*px)++
* и ++, выполняются
справа налево, это выражение увеличит px, а не ту
И наконец, так как py - другой int, то
py = px
копирует содержимое px в py, в результате чего py указывает на то же, что и px.
Так как в "с" передача swap. Для этого недостаточно написать
swap(a, b);
определив swap при этом следующим образом:
swap(x, y) /* wrong */
int x, y;
{
int temp;
temp = x;
x = y;
y = temp;
}
из-за swap не может воздействовать
на a и b
в вызывающей
К счастью, все же имеется возможность получить желаемый эффект. swap(a, b) ; так как операция выдает a является a. В самой swap
swap(px, py) /* interchange *px and *py */
int *px, *py;
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
swap
возвращает два значения, новые значения ее getint, которая
осуществляет преобразование поступающих в свободном формате getint должна возвращать либо найденное значение, либо EOF, даже если это значение
Одно из решений, основывающееся на описываемой в лекции №7 scanf, состоит в том, чтобы при выходе на
конец файла getint возвращала EOF в
качестве значения
Следующий цикл заполняет
getint:
int n, v, array[size];
for (n = 0; n < size getint(v) != EOF; n++)
array[n] = v;
В результате каждого обращения v становится равным следующему целому значению,
найденному во getint
необходимо указать v а не v. Использование
просто v скорее всего приведет к
ошибке getint полагает, что она работает именно с
Сама getint является очевидной модификацией
написанной нами ранее atoi:
getint(pn) /* get next integer from input */
int *pn;
{
int c,sign;
while ((c = getch()) == ' ' || c == '\n'
|| c == '\t'); /* skip white space */
sign = 1;
if (c == '+' || c == '-') { /* record
sign */
sign = (c == '+') ? 1 : -1;
c = getch();
}
for (*pn = 0; c >= '0' c <= '9'; c = getch())
*pn = 10 * *pn + c - '0';
*pn *= sign;
if (c != EOF)
ungetch(c);
return(c);
}
Выражение *pn используется всюду в getint
как обычная int. Мы также
использовали getch и ungetch
(
Упражнение 5-1
Напишите getfloat, аналог getint для чисел с плавающей точкой. Какой getfloat в качестве значения
В языке "C" существует сильная взаимосвязь между
int a[10]
определяет a[0], a[1], ..., a[9]. Запись a[i]
соответствует элементу i позиций от начала.
Если pa -
int *pa
то присваивание
pa = a[0]
приводит к тому, что pa указывает на нулевой элемент a ; это означает, что pa содержит a[0].
Теперь присваивание
x = *pa
будет копировать содержимое a[0] в x.
Если pa указывает на некоторый определенный
элемент a, то по pa+1
указывает на следующий элемент, и вообще pa-i указывает на
элемент, стоящий на i позиций до элемента, указываемого pa, а pa+i на элемент, стоящий на i
позиций после. Таким образом, если pa указывает на a[0], то
*(pa+1)
ссылается на содержимое a[1], pa+i - a[i], а *(pa+i) - содержимое a[i].
Эти замечания справедливы независимо от a. Суть i в pa+i
перед прибавлением умножается на размер объектов, на которые указывает pa.
Очевидно существует очень тесное соответствие между pa=a[0] можно записать как
pa = a
Еще более удивительным, по крайней мере на первый взгляд, кажется тот
факт, что ссылку на a[i] можно записать в виде *(a+i).
При анализировании выражения a[i] в языке "C" оно немедленно
преобразуется к виду *(a+i) ; эти две
формы совершенно эквивалентны. Если применить операцию
к обеим частям такого соотношения эквивалентности, то мы получим, что a[i] и a+i тоже идентичны: a+i
- i -го элемента от
начала a. С другой стороны, если pa является pa[i] идентично *(pa+i). Короче, любое
выражение, включающее
Имеется одно различие между именем pa=a и pa++ имеют смысл. Но имя a=pa или a++,или p=a будут незаконными.
Когда имя strlen, вычисляющей длину строки.
strlen(s) /* return length of string s */
char *s;
{
int n;
for (n = 0; *s != '\0'; s++)
n++;
return(n);
}
Операция увеличения s совершенно законна, поскольку эта s++ никак не влияет на strlen strlen копию
char s[]; char *s;
совершенно эквивалентны; какой вид
Можно передать a -
f(a[2])
как и
f(a+2)
передают f a[2], потому что и a[2], и a+2
являются a. Внутри f
f(arr) int arr[];
{
...
}
или
f(arr) int *arr;
{
...
}
Что касается f, то тот факт, что ее
Если p является p++ увеличивает p так,
что он указывает на следующий элемент
набора этих объектов, а операция p +=i увеличивает p так, чтобы он указывал на
элемент, отстоящий на i элементов от текущего элемента. Эти и аналогичные
Язык "C" последователен и постоянен в своем подходе к alloc(n) возвращает в качестве своего
значения p,
который указывает на первую из n последовательных символьных позиций, которые
могут быть использованы вызывающей alloc free(p) освобождает приобретенную таким образом память, так
что ее в дальнейшем можно снова использовать. free должны производиться в порядке, обратном тому, в
котором производились обращения к alloc. Таким образом, управляемая alloc и free память является alloc для распределения небольших участков памяти
неизвестных заранее размеров в непредсказуемые моменты времени.
Простейшая реализация состоит в том, чтобы allocbuf. Этот alloc и free.
Так как они работают с alloc и free, и
невидимым за его пределами. При практической реализации этот
Другой необходимой информацией является то, какая часть allocbuf
уже использована. Мы пользуемся allocp. Когда к alloc обращаются
за выделением n символов,
то она проверяет, достаточно ли осталось для этого места в allocbuf. Если
достаточно, то alloc возвращает текущее значение allocp
(т.е. начало свободного блока), затем увеличивает его на n,
с тем чтобы он указывал на следующую
свободную область. free(p) просто полагает allocp равным p при условии, что p
указывает на позицию внутри allocbuf.
#define null 0 /* pointer value for error report */
#define allocsize 1000 /* size of available space */
static char allocbuf[allocsize];/* storage for alloc */
static char *allocp = allocbuf; /* next free position */
char *alloc(n) /* return pointer to n characters */
int n;
(
if (allocp + n <= allocbuf + allocsize) {
allocp += n;
return(allocp - n); /* old p */
} else /* not enough room */
return(null);
)
free(p) /* free storage pointed by p */
char *p;
(
if (p >= allocbuf p < allocbuf + allocsize)
allocp = p;
)
Дадим некоторые пояснения. Вообще говоря, null (это обсуждается ниже) или
выражение, включающее
static char *allocp = allocbuf;
определяет allocp как allocbuf, т.е. на первую свободную позицию при начале работы
static char *allocp = allocbuf[0];
используйте ту запись, которая вам кажется более естественной. С помощью проверки
if (allocp + n <= allocbuf + allocsize)
выясняется, осталось ли достаточно места, чтобы удовлетворить запрос на n
символов. Если достаточно, то новое значение allocp не будет указывать дальше,
чем на последнюю позицию allocbuf. Если запрос может быть удовлетворен, то alloc возвращает обычный alloc должна вернуть некоторый признак, говорящий о
том, что больше места не осталось. В языке "C" гарантируется, что ни один
правильный null, с тем чтобы более ясно
показать, что это специальное значение
Проверки вида
if (allocp + n <= allocbuf + aloocsize) и if (p >= allocbuf p < allocbuf + allocsize)
демонстрируют несколько важных аспектов арифметики p и q указывают на
элементы одного и того же <, >= и т.д., работают надлежащим образом. Например,
p < q
истинно, если p указывает на более ранний элемент q. Отношения == и != тоже работают. Любой null. Но ни за что нельзя ручаться, если вы
используете сравнения при работе с
Во-вторых, как мы уже видели,
p + n
подразумевает n -ый объект за тем, на который p
указывает в настоящий момент. Это
справедливо независимо от того, на какой вид объектов p должен указывать; n в соответствии с определяемым из p
размером объектов, указываемых с помощью p. Например, на char, 2 для int
и short, 4 для long и float и 8 для double.
Вычитание p и q
указывают на элементы одного и того же p-q - количество элементов между p и q. Этот факт можно использовать для написания еще одного
варианта
strlen:
strlen(s) /* return length of string s */
char *s;
{
char *p = s;
while (*p != '\0')
p++;
return(p-s);
}
При p в этой s, в результате чего он указывает на первый символ строки.
В цикле while по
очереди проверяется каждый символ до тех пор, пока не появится
символ конца строки \0. Так как значение \0 равно нулю, а while только выясняет, имеет ли
выражение в нем значение 0, то в данном случае явную проверку можно опустить.
Такие циклы часто записывают в виде
while (*p)
p++;
Так как p указывает на символы, то p++
передвигает p каждый раз так, чтобы он указывал на следующий символ.
В результате p-s дает число просмотренных символов,
т.е. длину строки. Арифметика float, которые занимают больше памяти, чем char, и если бы p был float,
то p++ передвинул бы p на
следующее float. Таким образом, мы могли бы написать другой вариант alloc, распределяющей память для float, вместо char, просто заменив всюду в alloc и free описатель char на float. Все действия с
За исключением упомянутых выше операций (сложение и вычитание float или double.
"i am a string"
является \0, так что
По-видимому чаще всего
printf ("hello, world\n");
когда printf фактически
получает
Конечно, message как
char *message;
то в результате
message = "now is the time";
message станет
Мы проиллюстрируем другие аспекты
Первая strcpy(s,t), которая копирует строку t в строку s. t к s обычно пишут
s = t
сначала приведем версию с
strcpy(s, t) /* copy t to s */
char s[], t[];
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
Для сопоставления ниже дается вариант strcpy с
strcpy(s, t) /* copy t to s; pointer version 1 */
char *s, *t;
{
while ((*s = *t) != '\0') {
s++;
t++;
}
}
Так как strcpy может
использовать s и t так, как она пожелает. Здесь они
с удобством полагаются s
завершающий в t символ \0.
На практике strcpy была бы записана не так,
как мы показали выше. Вот вторая возможность:
strcpy(s, t) /* copy t to s; pointer version 2 */
char *s, *t;
{
while ((*s++ = *t++) != '\0')
;
}
Здесь увеличение s и t внесено в проверочную часть.
Значением *t++ является символ, на который указывал t
до увеличения; постфиксная операция ++
не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ
помещается в старую позицию s, до того как s будет увеличено.
Конечный результат заключается в том, что все символы, включая завершающий \0, копируются из t в s.
И как последнее сокращение мы опять отметим, что сравнение с \0 является
излишним, так что
strcpy(s, t) /* copy t to s; pointer version 3 */
char *s, *t;
{
while (*s++ = *t++)
;
}
хотя с первого взгляда эта запись может показаться загадочной, она дает
значительное удобство. Этой идиомой следует овладеть уже хотя бы потому, что вы
с ней будете часто встречаться в "C"-
Вторая , которая сравнивает s и t, возвращая отрицательное, нулевое или положительное значение в соответствии с
тем, меньше, равно или больше лексикографически s, чем t.
Возвращаемое значение получается в результате вычитания символов из первой позиции,
в которой s и t не совпадают.
strcmp(s, t) /* return <0 if s<t, 0 if s==t, >0 if s>t */
char s[], t[];
{
int i;
i = 0;
while (s[i] == t[i])
if (s[i++] == '\0')
return(0);
return(s[i]-t[i]);
}
Вот версия с
strcmp(s, t) /* return <0 if s<t, 0 if s==t, >0 if s>t */
char *s, *t;
{
for ( ; *s == *t; s++, t++)
if (*s == '\0')
return(0);
return(*s-*t);
}
так как ++ и -- могут быть как постфиксными,
так и * и ++ и --, хотя и менее часто.
Например
*++p
увеличивает p до извлечения символа, на который указывает p, а
*--p
сначала уменьшает p.
Упражнение 5-2
Напишите вариант с из лекции №2:
копирует строку t в конец s.
Упражнение 5-3
Напишите макрос для strcpy.
Упражнение 5-4
Перепишите подходящие getline /лекции 1 и №4/, atoi, itoa и их варианты /лекция №2, №3 и №4/, reverse /лекция №3/, index и getop /лекция №4/.
Вы, возможно, обратили внимание в предыдущих
"с"- strsave(s), которая копирует строку s
в некоторое место для хранения, выделяемое посредством alloc,
и возвращает
char *strsave(s) /* save string s somewhere */
char *s;
{
char *p, *alloc();
if ((p = alloc(strlen(s)+1)) != null)
strcpy(p, s);
return(p);
}
на практике существует сильное стремление опускать
*strsave(s) /* save string s somewhere */
{
char *p;
if ((p = alloc(strlen(s)+1)) != null)
strcpy(p, s);
return(p);
}
Эта int, а lint предупредит о таких
В языке "C" предусмотрены прямоугольные многомерные
Рассмотрим задачу преобразования дня месяца в день года и наоборот.
Например, 1-ое марта является 60-м днем не високосного года и 61-м днем
високосного года. Давайте введем две day_of_year преобразует месяц и день в день года, а month_day преобразует день
года в месяц и день. Так как эта последняя
month_day(1977, 60, m, d)
Полагает m равным 3 и d равным 1 (1-ое марта).
Обе эти
static int day_tab[2][13] = {
(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
(1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
};
day_of_year(year, month, day) /* set day of year */
int year, month, day; /* from month day */
{
int i, leap;
leap = year%4 == 0 year%100 != 0 || year%400 == 0;
for (i = 1; i < month; i++)
day += day_tab[leap][i];
return(day);
{
month_day(year, yearday, pmonth, pday) /*set month,day */
int year, yearday, *pmonth, *pday; /* from day of year */
{
leap = year%4 == 0 year%100 != 0 || year%400 == 0;
for (i = 1; yearday > day_tab[leap][i]; i++)
yearday -= day_tab[leap][i];
*pmonth = i;
*pday = yearday;
}
day_tab должен быть внешним как для day_of_year, так и для month_day,
поскольку он используется обеими этими
day_tab является первым двумерным
day_tab[i][j] а не day_tab [i, j]
как в большинстве языков. В остальном с двумерными
day_tab столбец из
нулей для того, чтобы номера месяцев изменялись естественным образом от 1 до
12, а не от 0 до 11. Так как за экономию памяти у нас пока не награждают, такой
способ проще, чем подгонка индексов.
Если двумерный int. Таким образом, если бы требовалось передать day_tab f, то f имело бы вид:
f(day_tab) int day_tab[2][13];
{
...
}
Так как количество строк является несущественным, то f могло бы быть таким:
int day_tab[][13];
или таким
int (*day_tab)[13];
в котором говорится, что [] имеют более
высокий уровень старшинства, чем * ; как мы увидим в следующем разделе, без
int *day_tab[13];
является
Так как sort
В лекции №3 мы привели
Здесь и возникают alloc ), то к каждой строке можно обратиться с
помощью .
Если две расположенные в неправильном порядке строки должны быть переставлены,
то фактически переставляются
Процесс сортировки включает три шага:
Как обычно, лучше разделить
#define null 0
#define lines 100 /* max lines to be sorted */
main() /* sort input lines */
{
char *lineptr[lines]; /*pointers to text lines */
int nlines; /* number of input lines read */
if ((nlines = readlines(lineptr, lines)) >= 0) {
sort(lineptr, nlines);
writelines(lineptr, nlines);
}
else
printf("input too big to sort\n");
}
#define maxlen 1000
readlines(lineptr, maxlines) /* read input lines */
char *lineptr[]; /* for sorting */
int maxlines;
{
int len, nlines;
char *p, *alloc(), line[maxlen];
nlines = 0;
while ((len = getline(line, maxlen)) > 0)
if (nlines >= maxlines)
return(-1);
else if ((p = alloc(len)) == null)
return (-1);
else {
line[len-1] = '\0'; /* zap newline */
strcpy(p,line);
lineptr[nlines++] = p;
}
return(nlines);
}
writelines(lineptr, nlines) /* write output lines */
char *lineptr[];
int nlines;
{
int i;
for (i = 0; i < nlines; i++)
printf("%s\n", lineptr[i]);
}
Существенно новым в этой
char *lineptr[lines];
которое сообщает, что lineptr является lines элементов, каждый из которых - char. Это означает, что lineptr[i] - *lineptr[i] извлекает символ.
Так как сам lineptr является writelines, с ним можно
обращаться как с
writelines(lineptr, nlines) /* write output lines */
char *lineptr[];
int nlines;
{
int i;
while (--nlines >= 0)
printf("%s\n", *lineptr++);
}
здесь *lineptr сначала указывает на первую строку;
каждое увеличение передвигает nlines убывает до нуля.
Справившись с
sort(v, n) /* sort strings v[0] ... v[n-1] */
char *v[]; /* into increasing order */
int n;
{
int gap, i, j;
char *temp;
for (gap = n/2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
for (j = i - gap; j >= 0; j -= gap) {
if (strcmp(v[j], v[j+gap]) <= 0)
break;
temp = v[j];
v[j] = v[j+gap];
v[j+gap] = temp;
}
}
Так как каждый отдельный элемент v (имя формального параметра,
соответствующего lineptr ) является temp должен быть
Мы написали эту readlines, а не копировать их в line,
а затем в скрытое место с помощью alloc. Но мы считаем, что будет разумнее первоначальный вариант сделать
более простым для понимания, а об "эффективности" позаботиться позднее. Все
же, по-видимому, способ, позволяющий добиться заметного ускорения работы
В лекции №1 мы отмечали, что поскольку в циклах while и for проверка осуществляется до того, как
Упражнение 5-5
Перепишите readlines таким образом, чтобы она помещала строки в main, а не в память, управляемую alloc.
Насколько быстрее стала
Рассмотрим задачу написания month_name(n), которая возвращает n -го месяца. Это идеальная задача
для применения внутреннего статического month_name содержит
локальный
char *month_name(n) /* return name of n-th month */
int n;
{
static char *name[] = {
"illegal month",
"january",
"february",
"march",
"april",
"may",
"jun",
"july",
"august",
"september",
"october",
"november",
"december"
};
return ((n < 1 || n > 12) ? name[0] : name[n]);
}
name точно такое же, как аналогичное lineptr в примере с сортировкой. Инициализатором является просто список i -ой строки помещаются в какое-то иное место, а ее name[i]. Поскольку
размер name не указан,
Начинающие изучать язык "с" иногда становятся в тупик перед вопросом о
различии между двумерным name в
приведенном выше примере. Если имеются
int a[10][10]; int *b[10];
то a и b можно использовать сходным образом
в том смысле, что как a[5][5], так и b[5][5]
являются законными ссылками на отдельное число int.
Но a - настоящий b, однако, b не должен обязательно указывать на
вектор из 10 элементов; некоторые могут указывать на вектор из двух элементов,
другие - из двадцати, а третьи могут вообще ни на что не указывать.
Хотя мы вели это обсуждение в терминах целых, несомненно, чаще всего month_name, - для хранения
Упражнение 5-6
Перепишите day_of_year и month_day,
используя вместо
Системные средства, на которые опирается реализация языка "с", позволяют
передавать командную строку main
вызывается к исполнению, она вызывается с двумя argc )
указывает число argv )
является
Самую простую иллюстрацию этой возможности и необходимых при этом echo, которая просто печатает в одну строку
echo hello, world
то выходом будет
hello, world
по соглашению argv[0] является именем,
по которому argc по меньшей мере равен 1.
В приведенном выше примере argc равен 3, а argv[0], argv[1] и argv[2] равны
соответственно "echo", "hello," и "world".
Первым фактическим argv[1], а последним - argv[argc-1]. Если argc равен 1, то за
именем echo:
main(argc, argv) /* echo arguments; 1st version */
int argc;
char *argv[];
{
int i;
for (i = 1; i < argc; i++)
printf("%s%c", argv[i], (i<argc-1) ? ' ' : '\n');
}
Поскольку argv является
main(argc, argv) /* echo arguments; 2nd version */
int argc;
char *argv[];
{
while (--argc > 0)
printf("%s%c",*++argv, (argc > 1) ? ' ' : '\n');
}
Так как argv является 1 ( ++argv ), мы вынуждаем
его указывать на подлинный argv[1], а не на argv[0]. Каждое последующее
увеличение передвигает его на следующий *argv
становится argc уменьшается; когда она обратится в нуль, все
Другой вариант:
main(argc, argv) /* echo arguments; 3rd version */
int argc;
char *argv[];
{
while (--argc > 0)
printf((argc > 1) ? "%s" : "%s\n", *++argv);
}
Эта версия показывает, что printf может быть
выражением, точно так же, как и любой другой. Такое использование встречается
не очень часто, но его все же стоит запомнить.
Как второй пример, давайте внесем некоторые усовершенствования в grep системы
#define maxline 1000
main(argc, argv) /* find pattern from first argument */
int argc;
char *argv[];
{
char line[maxline];
if (argc != 2)
printf ("usage: find pattern\n");
else
while (getline(line, maxline) > 0)
if (index(line, argv[1] >= 0)
printf("%s", line);
}
Теперь может быть развита основная модель, иллюстрирующая дальнейшее
использование
Общепринятым соглашением в "с"- -x, а для указания о
нумерации нужных строк выберем -n("номер"), то команда
find -x -n the
при
now is the time for all good men to come to the aid of their party.
Должна выдать
2:for all good men
Нужно, чтобы необязательные index не должен
содержать ссылку на argv[2], когда присутствует один необязательный argv[1], когда его нет.
Более того, для пользователей удобно, чтобы необязательные
find -nx the
вот сама
#define maxline 1000
main(argc, argv) /* find pattern from first argument */
int argc;
char *argv[];
{
char line[maxline], *s;
long lineno = 0;
int except = 0, number = 0;
while (--argc > 0 (*++argv)[0] == '-')
for (s = argv[0]+1; *s != '\0'; s++)
switch (*s) {
case 'x':
except = 1;
break;
case 'n':
number = 1;
break;
default:
printf("find: illegal option %c\n", *s);
argc = 0;
break;
}
if (argc != 1)
printf("usage: find -x -n pattern\n");
else
while (getlinе(line, maxline) > 0) {
lineno++;
if ((index(line, *argv) >= 0) != except) \
if (number)
printf("%ld: ", lineno);
printf("%s", line);
}
}
}
argv увеличивается
перед каждым необязательным argc уменьшается.
Если нет ошибок, то в конце цикла величина argc должна равняться 1, а *argv должно указывать на заданную комбинацию.
Обратите внимание на то, что *++argv является (*++argv)[0] - ее первый символ. *++(argv[0]). Другой правильной формой была бы **++argv.
Упражнение 5-7
Напишите add, вычисляющую обратное польское выражение из
командной строки. Например,
add 2 3 4 + * вычисляет 2*(3+4).
Упражнение 5-8
Модифицируйте entab и detab
(указанные в качестве упражнений в лекции №1) так, чтобы они получали
список табуляционных остановок в качестве
Упражнение 5-9
Расширьте entab и detab таким образом,
чтобы они воспринимали сокращенную нотацию
entab m +n
означающую табуляционные остановки через каждые n столбцов,
начиная со столбца m. Выберите удобное (для пользователя)
поведение
Упражнение 5-10
Напишите tail, печатающей
последние n строк из своего n равно 10, но это число может быть
изменено с помощью необязательного
tail -n
печатает последние n строк. n.
Составьте sort,
а не в двумерном
В языке "с" сами -n она бы
сортировала строки
Сортировка часто состоит из трех частей - сравнения, которое определяет
упорядочивание любой пары объектов, перестановки, изменяющей их порядок, и
алгоритма сортировки, осуществляющего сравнения и перестановки до тех пор,
пока объекты не расположатся в нужном порядке. Алгоритм сортировки не зависит
от операций сравнения и перестановки, так что, передавая в него различные
Как и прежде, лексикографическое сравнение двух строк осуществляется , а перестановка swap ;
нам нужна еще numcmp, сравнивающая две строки на
основе численного значения и возвращающая условное
указание того же вида, что и . Эти три main и sort.
В свою очередь sort обращается к
этим
#define lines 100 /* max number of lines
to be sorted */
main(argc, argv) /* sort input lines */
int argc;
char *argv[];
{
char *lineptr[lines]; /* pointers to text lines */
int nlines; /* number of input lines read */
int strcmp(), numcmp(); /* comparsion functions */
int swap(); /* exchange function */
int numeric = 0; /* 1 if numeric sort */
if(argc>1 argv[1][0] == '-' argv[1][1]=='n')
numeric = 1;
if(nlines = readlines(lineptr, lines)) >= 0) {
if (numeric)
sort(lineptr, nlines, numcmp, swap);
else
sort(lineptr, nlines, strcmp, swap);
writelines(lineptr, nlines);
} else
printf("input too big to sort\n");
}
Здесь , numcmp и swap - здесь не нужна
совершенно аналогично тому, как она не нужна и перед именем
Второй шаг состоит в модификации sort:
sort(v, n, comp, exch) /* sort strings v[0] ... v[n-1] */
char *v[]; /* into increasing order */
int n;
int (*comp)(), (*exch)();
{
int gap, i, j;
for(gap = n/2; gap > 0; gap /= 2)
for(i = gap; i < n; i++)
for(j = i-gap; j >= 0; j -= gap) {
if((*comp)(v[j], v[j+gap]) <= 0)
break;
(*exch)(v[j], v[j+gap]);
}
}
Здесь следует обратить определенное внимание на
int (*comp)()
говорит, что comp является int. Первые
int *comp()
говорило бы, что comp является
Использование comp в строке
if ((*comp)(v[j], v[j+gap]) <= 0)
полностью согласуется с comp - *comp - сама
(*comp)(v[j], v[j+gap])
- обращение к ней.
Мы уже приводили , сравнивающую две строки по первому
численному значению:
numcmp(s1, s2) /* compare s1 and s2 numerically */
char *s1, *s2;
{
double atof(), v1, v2;
v1 = atof(s1);
v2 = atof(s2);
if(v1 < v2)
return(-1);
else if(v1 > v2)
return(1);
else
return (0);
}
Заключительный шаг состоит в добавлении swap,
переставляющей два
swap(px, py) /* interchange *px and *py */
char *px[], *py[];
{
char *temp;
temp = *px;
*px = *py;
*py = temp;
}
Имеется множество других необязательных
Упражнение 5-11
Модифицируйте sort таким образом, чтобы она работала
с меткой -r, указывающей на сортировку в обратном (убывающем) порядке.
Конечно, -r должна работать с -n.
Упражнение 5-12
Добавьте необязательный -f, объединяющий вместе прописные и
строчные буквы, так чтобы различие регистров не учитывалось во время сортировки:
Упражнение 5-13
Добавьте необязательный -d ("словарное упорядочивание"), при
наличии которого сравниваются только буквы, числа и пробелы. Позаботьтесь о том,
чтобы эта -f.
Упражнение 5-14
Добавьте возможность обработки полей, так чтобы можно было сортировать
поля внутри строк. Каждое поле должно сортироваться в соответствии с
независимым набором необязательных -df для категории -n для номеров страниц).
goto,
характеризуя их как чудесный способ написания
Так как х - int, а рх -
выдает
рх = х;
присваивает х рх ; говорят, что рх "указывает" на х.
Операция применима только к (х-1) и 3 являются незаконными.
Нельзя также получить
Унарная операция * рассматривает свой операнд как y тоже
имеет int, то
y = *рх;
присваивает y содержимое того, на что указывает рх.
Так
рх = х; y = *рх;
присваивает y то же самое значение, что и
y = x;
int x, y; int *px;
с x и y мы уже неоднократно
встречались.
int *px;
является новым и должно рассматриваться как мнемоническое; оно говорит, что
комбинация *px имеет int. Это означает, что если px появляется в контексте *px, то это эквивалентно int. Фактически синтаксис
double atof(), *dp;
говорит, что atof() и *dp имеют в выражениях
значения double.
Вы должны также заметить, что из этого
px
указывает на целое x, то *px может появляться в
любом контексте, где может встретиться x. Так
y = *px + 1
присваивает y значение, на 1 большее
значения x ;
printf("%d\n", *px)
печатает текущее значение x ;
d = sqrt((double) *px)
получает в d квадратный корень из x, причем
до передачи sqrt значение x преобразуется
к double. (Смотри лекцию №2).
В выражениях вида
y = *px + 1
унарные операции * и связаны со
своим операндом более крепко, чем
арифметические операции, так что такое выражение берет то значение, на которое
указывает px, прибавляет 1 и присваивает
результат y. Мы вскоре вернемся к тому, что может
означать выражение
y = *(px + 1)
Ссылки на px указывает на x, то
*px = 0
полагает x равным нулю, а
*px += 1
увеличивает его на единицу, как и выражение
(*px)++
* и ++, выполняются
справа налево, это выражение увеличит px, а не ту
И наконец, так как py - другой int, то
py = px
копирует содержимое px в py, в результате чего py указывает на то же, что и px.
Так как в "с" передача swap. Для этого недостаточно написать
swap(a, b);
определив swap при этом следующим образом:
swap(x, y) /* wrong */
int x, y;
{
int temp;
temp = x;
x = y;
y = temp;
}
из-за swap не может воздействовать
на a и b
в вызывающей
К счастью, все же имеется возможность получить желаемый эффект. swap(a, b) ; так как операция выдает a является a. В самой swap
swap(px, py) /* interchange *px and *py */
int *px, *py;
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
swap
возвращает два значения, новые значения ее getint, которая
осуществляет преобразование поступающих в свободном формате getint должна возвращать либо найденное значение, либо EOF, даже если это значение
Одно из решений, основывающееся на описываемой в лекции №7 scanf, состоит в том, чтобы при выходе на
конец файла getint возвращала EOF в
качестве значения
Следующий цикл заполняет
getint:
int n, v, array[size];
for (n = 0; n < size getint(v) != EOF; n++)
array[n] = v;
В результате каждого обращения v становится равным следующему целому значению,
найденному во getint
необходимо указать v а не v. Использование
просто v скорее всего приведет к
ошибке getint полагает, что она работает именно с
Сама getint является очевидной модификацией
написанной нами ранее atoi:
getint(pn) /* get next integer from input */
int *pn;
{
int c,sign;
while ((c = getch()) == ' ' || c == '\n'
|| c == '\t'); /* skip white space */
sign = 1;
if (c == '+' || c == '-') { /* record
sign */
sign = (c == '+') ? 1 : -1;
c = getch();
}
for (*pn = 0; c >= '0' c <= '9'; c = getch())
*pn = 10 * *pn + c - '0';
*pn *= sign;
if (c != EOF)
ungetch(c);
return(c);
}
Выражение *pn используется всюду в getint
как обычная int. Мы также
использовали getch и ungetch
(
Упражнение 5-1
Напишите getfloat, аналог getint для чисел с плавающей точкой. Какой getfloat в качестве значения
В языке "C" существует сильная взаимосвязь между
int a[10]
определяет a[0], a[1], ..., a[9]. Запись a[i]
соответствует элементу i позиций от начала.
Если pa -
int *pa
то присваивание
pa = a[0]
приводит к тому, что pa указывает на нулевой элемент a ; это означает, что pa содержит a[0].
Теперь присваивание
x = *pa
будет копировать содержимое a[0] в x.
Если pa указывает на некоторый определенный
элемент a, то по pa+1
указывает на следующий элемент, и вообще pa-i указывает на
элемент, стоящий на i позиций до элемента, указываемого pa, а pa+i на элемент, стоящий на i
позиций после. Таким образом, если pa указывает на a[0], то
*(pa+1)
ссылается на содержимое a[1], pa+i - a[i], а *(pa+i) - содержимое a[i].
Эти замечания справедливы независимо от a. Суть i в pa+i
перед прибавлением умножается на размер объектов, на которые указывает pa.
Очевидно существует очень тесное соответствие между pa=a[0] можно записать как
pa = a
Еще более удивительным, по крайней мере на первый взгляд, кажется тот
факт, что ссылку на a[i] можно записать в виде *(a+i).
При анализировании выражения a[i] в языке "C" оно немедленно
преобразуется к виду *(a+i) ; эти две
формы совершенно эквивалентны. Если применить операцию
к обеим частям такого соотношения эквивалентности, то мы получим, что a[i] и a+i тоже идентичны: a+i
- i -го элемента от
начала a. С другой стороны, если pa является pa[i] идентично *(pa+i). Короче, любое
выражение, включающее
Имеется одно различие между именем pa=a и pa++ имеют смысл. Но имя a=pa или a++,или p=a будут незаконными.
Когда имя strlen, вычисляющей длину строки.
strlen(s) /* return length of string s */
char *s;
{
int n;
for (n = 0; *s != '\0'; s++)
n++;
return(n);
}
Операция увеличения s совершенно законна, поскольку эта s++ никак не влияет на strlen strlen копию
char s[]; char *s;
совершенно эквивалентны; какой вид
Можно передать a -
f(a[2])
как и
f(a+2)
передают f a[2], потому что и a[2], и a+2
являются a. Внутри f
f(arr) int arr[];
{
...
}
или
f(arr) int *arr;
{
...
}
Что касается f, то тот факт, что ее
Если p является p++ увеличивает p так,
что он указывает на следующий элемент
набора этих объектов, а операция p +=i увеличивает p так, чтобы он указывал на
элемент, отстоящий на i элементов от текущего элемента. Эти и аналогичные
Язык "C" последователен и постоянен в своем подходе к alloc(n) возвращает в качестве своего
значения p,
который указывает на первую из n последовательных символьных позиций, которые
могут быть использованы вызывающей alloc free(p) освобождает приобретенную таким образом память, так
что ее в дальнейшем можно снова использовать. free должны производиться в порядке, обратном тому, в
котором производились обращения к alloc. Таким образом, управляемая alloc и free память является alloc для распределения небольших участков памяти
неизвестных заранее размеров в непредсказуемые моменты времени.
Простейшая реализация состоит в том, чтобы allocbuf. Этот alloc и free.
Так как они работают с alloc и free, и
невидимым за его пределами. При практической реализации этот
Другой необходимой информацией является то, какая часть allocbuf
уже использована. Мы пользуемся allocp. Когда к alloc обращаются
за выделением n символов,
то она проверяет, достаточно ли осталось для этого места в allocbuf. Если
достаточно, то alloc возвращает текущее значение allocp
(т.е. начало свободного блока), затем увеличивает его на n,
с тем чтобы он указывал на следующую
свободную область. free(p) просто полагает allocp равным p при условии, что p
указывает на позицию внутри allocbuf.
#define null 0 /* pointer value for error report */
#define allocsize 1000 /* size of available space */
static char allocbuf[allocsize];/* storage for alloc */
static char *allocp = allocbuf; /* next free position */
char *alloc(n) /* return pointer to n characters */
int n;
(
if (allocp + n <= allocbuf + allocsize) {
allocp += n;
return(allocp - n); /* old p */
} else /* not enough room */
return(null);
)
free(p) /* free storage pointed by p */
char *p;
(
if (p >= allocbuf p < allocbuf + allocsize)
allocp = p;
)
Дадим некоторые пояснения. Вообще говоря, null (это обсуждается ниже) или
выражение, включающее
static char *allocp = allocbuf;
определяет allocp как allocbuf, т.е. на первую свободную позицию при начале работы
static char *allocp = allocbuf[0];
используйте ту запись, которая вам кажется более естественной. С помощью проверки
if (allocp + n <= allocbuf + allocsize)
выясняется, осталось ли достаточно места, чтобы удовлетворить запрос на n
символов. Если достаточно, то новое значение allocp не будет указывать дальше,
чем на последнюю позицию allocbuf. Если запрос может быть удовлетворен, то alloc возвращает обычный alloc должна вернуть некоторый признак, говорящий о
том, что больше места не осталось. В языке "C" гарантируется, что ни один
правильный null, с тем чтобы более ясно
показать, что это специальное значение
Проверки вида
if (allocp + n <= allocbuf + aloocsize) и if (p >= allocbuf p < allocbuf + allocsize)
демонстрируют несколько важных аспектов арифметики p и q указывают на
элементы одного и того же <, >= и т.д., работают надлежащим образом. Например,
p < q
истинно, если p указывает на более ранний элемент q. Отношения == и != тоже работают. Любой null. Но ни за что нельзя ручаться, если вы
используете сравнения при работе с
Во-вторых, как мы уже видели,
p + n
подразумевает n -ый объект за тем, на который p
указывает в настоящий момент. Это
справедливо независимо от того, на какой вид объектов p должен указывать; n в соответствии с определяемым из p
размером объектов, указываемых с помощью p. Например, на char, 2 для int
и short, 4 для long и float и 8 для double.
Вычитание p и q
указывают на элементы одного и того же p-q - количество элементов между p и q. Этот факт можно использовать для написания еще одного
варианта
strlen:
strlen(s) /* return length of string s */
char *s;
{
char *p = s;
while (*p != '\0')
p++;
return(p-s);
}
При p в этой s, в результате чего он указывает на первый символ строки.
В цикле while по
очереди проверяется каждый символ до тех пор, пока не появится
символ конца строки \0. Так как значение \0 равно нулю, а while только выясняет, имеет ли
выражение в нем значение 0, то в данном случае явную проверку можно опустить.
Такие циклы часто записывают в виде
while (*p)
p++;
Так как p указывает на символы, то p++
передвигает p каждый раз так, чтобы он указывал на следующий символ.
В результате p-s дает число просмотренных символов,
т.е. длину строки. Арифметика float, которые занимают больше памяти, чем char, и если бы p был float,
то p++ передвинул бы p на
следующее float. Таким образом, мы могли бы написать другой вариант alloc, распределяющей память для float, вместо char, просто заменив всюду в alloc и free описатель char на float. Все действия с
За исключением упомянутых выше операций (сложение и вычитание float или double.
"i am a string"
является \0, так что
По-видимому чаще всего
printf ("hello, world\n");
когда printf фактически
получает
Конечно, message как
char *message;
то в результате
message = "now is the time";
message станет
Мы проиллюстрируем другие аспекты
Первая strcpy(s,t), которая копирует строку t в строку s. t к s обычно пишут
s = t
сначала приведем версию с
strcpy(s, t) /* copy t to s */
char s[], t[];
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
Для сопоставления ниже дается вариант strcpy с
strcpy(s, t) /* copy t to s; pointer version 1 */
char *s, *t;
{
while ((*s = *t) != '\0') {
s++;
t++;
}
}
Так как strcpy может
использовать s и t так, как она пожелает. Здесь они
с удобством полагаются s
завершающий в t символ \0.
На практике strcpy была бы записана не так,
как мы показали выше. Вот вторая возможность:
strcpy(s, t) /* copy t to s; pointer version 2 */
char *s, *t;
{
while ((*s++ = *t++) != '\0')
;
}
Здесь увеличение s и t внесено в проверочную часть.
Значением *t++ является символ, на который указывал t
до увеличения; постфиксная операция ++
не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ
помещается в старую позицию s, до того как s будет увеличено.
Конечный результат заключается в том, что все символы, включая завершающий \0, копируются из t в s.
И как последнее сокращение мы опять отметим, что сравнение с \0 является
излишним, так что
strcpy(s, t) /* copy t to s; pointer version 3 */
char *s, *t;
{
while (*s++ = *t++)
;
}
хотя с первого взгляда эта запись может показаться загадочной, она дает
значительное удобство. Этой идиомой следует овладеть уже хотя бы потому, что вы
с ней будете часто встречаться в "C"-
Вторая , которая сравнивает s и t, возвращая отрицательное, нулевое или положительное значение в соответствии с
тем, меньше, равно или больше лексикографически s, чем t.
Возвращаемое значение получается в результате вычитания символов из первой позиции,
в которой s и t не совпадают.
strcmp(s, t) /* return <0 if s<t, 0 if s==t, >0 if s>t */
char s[], t[];
{
int i;
i = 0;
while (s[i] == t[i])
if (s[i++] == '\0')
return(0);
return(s[i]-t[i]);
}
Вот версия с
strcmp(s, t) /* return <0 if s<t, 0 if s==t, >0 if s>t */
char *s, *t;
{
for ( ; *s == *t; s++, t++)
if (*s == '\0')
return(0);
return(*s-*t);
}
так как ++ и -- могут быть как постфиксными,
так и * и ++ и --, хотя и менее часто.
Например
*++p
увеличивает p до извлечения символа, на который указывает p, а
*--p
сначала уменьшает p.
Упражнение 5-2
Напишите вариант с из лекции №2:
копирует строку t в конец s.
Упражнение 5-3
Напишите макрос для strcpy.
Упражнение 5-4
Перепишите подходящие getline /лекции 1 и №4/, atoi, itoa и их варианты /лекция №2, №3 и №4/, reverse /лекция №3/, index и getop /лекция №4/.
Вы, возможно, обратили внимание в предыдущих
"с"- strsave(s), которая копирует строку s
в некоторое место для хранения, выделяемое посредством alloc,
и возвращает
char *strsave(s) /* save string s somewhere */
char *s;
{
char *p, *alloc();
if ((p = alloc(strlen(s)+1)) != null)
strcpy(p, s);
return(p);
}
на практике существует сильное стремление опускать
*strsave(s) /* save string s somewhere */
{
char *p;
if ((p = alloc(strlen(s)+1)) != null)
strcpy(p, s);
return(p);
}
Эта int, а lint предупредит о таких
В языке "C" предусмотрены прямоугольные многомерные
Рассмотрим задачу преобразования дня месяца в день года и наоборот.
Например, 1-ое марта является 60-м днем не високосного года и 61-м днем
високосного года. Давайте введем две day_of_year преобразует месяц и день в день года, а month_day преобразует день
года в месяц и день. Так как эта последняя
month_day(1977, 60, m, d)
Полагает m равным 3 и d равным 1 (1-ое марта).
Обе эти
static int day_tab[2][13] = {
(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
(1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
};
day_of_year(year, month, day) /* set day of year */
int year, month, day; /* from month day */
{
int i, leap;
leap = year%4 == 0 year%100 != 0 || year%400 == 0;
for (i = 1; i < month; i++)
day += day_tab[leap][i];
return(day);
{
month_day(year, yearday, pmonth, pday) /*set month,day */
int year, yearday, *pmonth, *pday; /* from day of year */
{
leap = year%4 == 0 year%100 != 0 || year%400 == 0;
for (i = 1; yearday > day_tab[leap][i]; i++)
yearday -= day_tab[leap][i];
*pmonth = i;
*pday = yearday;
}
day_tab должен быть внешним как для day_of_year, так и для month_day,
поскольку он используется обеими этими
day_tab является первым двумерным
day_tab[i][j] а не day_tab [i, j]
как в большинстве языков. В остальном с двумерными
day_tab столбец из
нулей для того, чтобы номера месяцев изменялись естественным образом от 1 до
12, а не от 0 до 11. Так как за экономию памяти у нас пока не награждают, такой
способ проще, чем подгонка индексов.
Если двумерный int. Таким образом, если бы требовалось передать day_tab f, то f имело бы вид:
f(day_tab) int day_tab[2][13];
{
...
}
Так как количество строк является несущественным, то f могло бы быть таким:
int day_tab[][13];
или таким
int (*day_tab)[13];
в котором говорится, что [] имеют более
высокий уровень старшинства, чем * ; как мы увидим в следующем разделе, без
int *day_tab[13];
является
Так как sort
В лекции №3 мы привели
Здесь и возникают alloc ), то к каждой строке можно обратиться с
помощью .
Если две расположенные в неправильном порядке строки должны быть переставлены,
то фактически переставляются
Процесс сортировки включает три шага:
Как обычно, лучше разделить
#define null 0
#define lines 100 /* max lines to be sorted */
main() /* sort input lines */
{
char *lineptr[lines]; /*pointers to text lines */
int nlines; /* number of input lines read */
if ((nlines = readlines(lineptr, lines)) >= 0) {
sort(lineptr, nlines);
writelines(lineptr, nlines);
}
else
printf("input too big to sort\n");
}
#define maxlen 1000
readlines(lineptr, maxlines) /* read input lines */
char *lineptr[]; /* for sorting */
int maxlines;
{
int len, nlines;
char *p, *alloc(), line[maxlen];
nlines = 0;
while ((len = getline(line, maxlen)) > 0)
if (nlines >= maxlines)
return(-1);
else if ((p = alloc(len)) == null)
return (-1);
else {
line[len-1] = '\0'; /* zap newline */
strcpy(p,line);
lineptr[nlines++] = p;
}
return(nlines);
}
writelines(lineptr, nlines) /* write output lines */
char *lineptr[];
int nlines;
{
int i;
for (i = 0; i < nlines; i++)
printf("%s\n", lineptr[i]);
}
Существенно новым в этой
char *lineptr[lines];
которое сообщает, что lineptr является lines элементов, каждый из которых - char. Это означает, что lineptr[i] - *lineptr[i] извлекает символ.
Так как сам lineptr является writelines, с ним можно
обращаться как с
writelines(lineptr, nlines) /* write output lines */
char *lineptr[];
int nlines;
{
int i;
while (--nlines >= 0)
printf("%s\n", *lineptr++);
}
здесь *lineptr сначала указывает на первую строку;
каждое увеличение передвигает nlines убывает до нуля.
Справившись с
sort(v, n) /* sort strings v[0] ... v[n-1] */
char *v[]; /* into increasing order */
int n;
{
int gap, i, j;
char *temp;
for (gap = n/2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
for (j = i - gap; j >= 0; j -= gap) {
if (strcmp(v[j], v[j+gap]) <= 0)
break;
temp = v[j];
v[j] = v[j+gap];
v[j+gap] = temp;
}
}
Так как каждый отдельный элемент v (имя формального параметра,
соответствующего lineptr ) является temp должен быть
Мы написали эту readlines, а не копировать их в line,
а затем в скрытое место с помощью alloc. Но мы считаем, что будет разумнее первоначальный вариант сделать
более простым для понимания, а об "эффективности" позаботиться позднее. Все
же, по-видимому, способ, позволяющий добиться заметного ускорения работы
В лекции №1 мы отмечали, что поскольку в циклах while и for проверка осуществляется до того, как
Упражнение 5-5
Перепишите readlines таким образом, чтобы она помещала строки в main, а не в память, управляемую alloc.
Насколько быстрее стала
Рассмотрим задачу написания month_name(n), которая возвращает n -го месяца. Это идеальная задача
для применения внутреннего статического month_name содержит
локальный
char *month_name(n) /* return name of n-th month */
int n;
{
static char *name[] = {
"illegal month",
"january",
"february",
"march",
"april",
"may",
"jun",
"july",
"august",
"september",
"october",
"november",
"december"
};
return ((n < 1 || n > 12) ? name[0] : name[n]);
}
name точно такое же, как аналогичное lineptr в примере с сортировкой. Инициализатором является просто список i -ой строки помещаются в какое-то иное место, а ее name[i]. Поскольку
размер name не указан,
Начинающие изучать язык "с" иногда становятся в тупик перед вопросом о
различии между двумерным name в
приведенном выше примере. Если имеются
int a[10][10]; int *b[10];
то a и b можно использовать сходным образом
в том смысле, что как a[5][5], так и b[5][5]
являются законными ссылками на отдельное число int.
Но a - настоящий b, однако, b не должен обязательно указывать на
вектор из 10 элементов; некоторые могут указывать на вектор из двух элементов,
другие - из двадцати, а третьи могут вообще ни на что не указывать.
Хотя мы вели это обсуждение в терминах целых, несомненно, чаще всего month_name, - для хранения
Упражнение 5-6
Перепишите day_of_year и month_day,
используя вместо
Системные средства, на которые опирается реализация языка "с", позволяют
передавать командную строку main
вызывается к исполнению, она вызывается с двумя argc )
указывает число argv )
является
Самую простую иллюстрацию этой возможности и необходимых при этом echo, которая просто печатает в одну строку
echo hello, world
то выходом будет
hello, world
по соглашению argv[0] является именем,
по которому argc по меньшей мере равен 1.
В приведенном выше примере argc равен 3, а argv[0], argv[1] и argv[2] равны
соответственно "echo", "hello," и "world".
Первым фактическим argv[1], а последним - argv[argc-1]. Если argc равен 1, то за
именем echo:
main(argc, argv) /* echo arguments; 1st version */
int argc;
char *argv[];
{
int i;
for (i = 1; i < argc; i++)
printf("%s%c", argv[i], (i<argc-1) ? ' ' : '\n');
}
Поскольку argv является
main(argc, argv) /* echo arguments; 2nd version */
int argc;
char *argv[];
{
while (--argc > 0)
printf("%s%c",*++argv, (argc > 1) ? ' ' : '\n');
}
Так как argv является 1 ( ++argv ), мы вынуждаем
его указывать на подлинный argv[1], а не на argv[0]. Каждое последующее
увеличение передвигает его на следующий *argv
становится argc уменьшается; когда она обратится в нуль, все
Другой вариант:
main(argc, argv) /* echo arguments; 3rd version */
int argc;
char *argv[];
{
while (--argc > 0)
printf((argc > 1) ? "%s" : "%s\n", *++argv);
}
Эта версия показывает, что printf может быть
выражением, точно так же, как и любой другой. Такое использование встречается
не очень часто, но его все же стоит запомнить.
Как второй пример, давайте внесем некоторые усовершенствования в grep системы
#define maxline 1000
main(argc, argv) /* find pattern from first argument */
int argc;
char *argv[];
{
char line[maxline];
if (argc != 2)
printf ("usage: find pattern\n");
else
while (getline(line, maxline) > 0)
if (index(line, argv[1] >= 0)
printf("%s", line);
}
Теперь может быть развита основная модель, иллюстрирующая дальнейшее
использование
Общепринятым соглашением в "с"- -x, а для указания о
нумерации нужных строк выберем -n("номер"), то команда
find -x -n the
при
now is the time for all good men to come to the aid of their party.
Должна выдать
2:for all good men
Нужно, чтобы необязательные index не должен
содержать ссылку на argv[2], когда присутствует один необязательный argv[1], когда его нет.
Более того, для пользователей удобно, чтобы необязательные
find -nx the
вот сама
#define maxline 1000
main(argc, argv) /* find pattern from first argument */
int argc;
char *argv[];
{
char line[maxline], *s;
long lineno = 0;
int except = 0, number = 0;
while (--argc > 0 (*++argv)[0] == '-')
for (s = argv[0]+1; *s != '\0'; s++)
switch (*s) {
case 'x':
except = 1;
break;
case 'n':
number = 1;
break;
default:
printf("find: illegal option %c\n", *s);
argc = 0;
break;
}
if (argc != 1)
printf("usage: find -x -n pattern\n");
else
while (getlinе(line, maxline) > 0) {
lineno++;
if ((index(line, *argv) >= 0) != except) \
if (number)
printf("%ld: ", lineno);
printf("%s", line);
}
}
}
argv увеличивается
перед каждым необязательным argc уменьшается.
Если нет ошибок, то в конце цикла величина argc должна равняться 1, а *argv должно указывать на заданную комбинацию.
Обратите внимание на то, что *++argv является (*++argv)[0] - ее первый символ. *++(argv[0]). Другой правильной формой была бы **++argv.
Упражнение 5-7
Напишите add, вычисляющую обратное польское выражение из
командной строки. Например,
add 2 3 4 + * вычисляет 2*(3+4).
Упражнение 5-8
Модифицируйте entab и detab
(указанные в качестве упражнений в лекции №1) так, чтобы они получали
список табуляционных остановок в качестве
Упражнение 5-9
Расширьте entab и detab таким образом,
чтобы они воспринимали сокращенную нотацию
entab m +n
означающую табуляционные остановки через каждые n столбцов,
начиная со столбца m. Выберите удобное (для пользователя)
поведение
Упражнение 5-10
Напишите tail, печатающей
последние n строк из своего n равно 10, но это число может быть
изменено с помощью необязательного
tail -n
печатает последние n строк. n.
Составьте sort,
а не в двумерном
В языке "с" сами -n она бы
сортировала строки
Сортировка часто состоит из трех частей - сравнения, которое определяет
упорядочивание любой пары объектов, перестановки, изменяющей их порядок, и
алгоритма сортировки, осуществляющего сравнения и перестановки до тех пор,
пока объекты не расположатся в нужном порядке. Алгоритм сортировки не зависит
от операций сравнения и перестановки, так что, передавая в него различные
Как и прежде, лексикографическое сравнение двух строк осуществляется , а перестановка swap ;
нам нужна еще numcmp, сравнивающая две строки на
основе численного значения и возвращающая условное
указание того же вида, что и . Эти три main и sort.
В свою очередь sort обращается к
этим
#define lines 100 /* max number of lines
to be sorted */
main(argc, argv) /* sort input lines */
int argc;
char *argv[];
{
char *lineptr[lines]; /* pointers to text lines */
int nlines; /* number of input lines read */
int strcmp(), numcmp(); /* comparsion functions */
int swap(); /* exchange function */
int numeric = 0; /* 1 if numeric sort */
if(argc>1 argv[1][0] == '-' argv[1][1]=='n')
numeric = 1;
if(nlines = readlines(lineptr, lines)) >= 0) {
if (numeric)
sort(lineptr, nlines, numcmp, swap);
else
sort(lineptr, nlines, strcmp, swap);
writelines(lineptr, nlines);
} else
printf("input too big to sort\n");
}
Здесь , numcmp и swap - здесь не нужна
совершенно аналогично тому, как она не нужна и перед именем
Второй шаг состоит в модификации sort:
sort(v, n, comp, exch) /* sort strings v[0] ... v[n-1] */
char *v[]; /* into increasing order */
int n;
int (*comp)(), (*exch)();
{
int gap, i, j;
for(gap = n/2; gap > 0; gap /= 2)
for(i = gap; i < n; i++)
for(j = i-gap; j >= 0; j -= gap) {
if((*comp)(v[j], v[j+gap]) <= 0)
break;
(*exch)(v[j], v[j+gap]);
}
}
Здесь следует обратить определенное внимание на
int (*comp)()
говорит, что comp является int. Первые
int *comp()
говорило бы, что comp является
Использование comp в строке
if ((*comp)(v[j], v[j+gap]) <= 0)
полностью согласуется с comp - *comp - сама
(*comp)(v[j], v[j+gap])
- обращение к ней.
Мы уже приводили , сравнивающую две строки по первому
численному значению:
numcmp(s1, s2) /* compare s1 and s2 numerically */
char *s1, *s2;
{
double atof(), v1, v2;
v1 = atof(s1);
v2 = atof(s2);
if(v1 < v2)
return(-1);
else if(v1 > v2)
return(1);
else
return (0);
}
Заключительный шаг состоит в добавлении swap,
переставляющей два
swap(px, py) /* interchange *px and *py */
char *px[], *py[];
{
char *temp;
temp = *px;
*px = *py;
*py = temp;
}
Имеется множество других необязательных
Упражнение 5-11
Модифицируйте sort таким образом, чтобы она работала
с меткой -r, указывающей на сортировку в обратном (убывающем) порядке.
Конечно, -r должна работать с -n.
Упражнение 5-12
Добавьте необязательный -f, объединяющий вместе прописные и
строчные буквы, так чтобы различие регистров не учитывалось во время сортировки:
Упражнение 5-13
Добавьте необязательный -d ("словарное упорядочивание"), при
наличии которого сравниваются только буквы, числа и пробелы. Позаботьтесь о том,
чтобы эта -f.
Упражнение 5-14
Добавьте возможность обработки полей, так чтобы можно было сортировать
поля внутри строк. Каждое поле должно сортироваться в соответствии с
независимым набором необязательных -df для категории -n для номеров страниц).
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.