Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 1992-96 |
- 81 - |
Си в UNIX™ |
иметь на конце символа '\0', то есть будет находиться в некорректном (не каноническом) состоянии.
Ответ:
int strncmp(register char *s1, register char *s2, register int n)
{
if(s1 == s2) return(0);
while(--n >= 0 && *s1 == *s2++) if(*s1++ == '\0')
return(0); return((n < 0)? 0: (*s1 - *--s2));
}
2.25. В чем ошибка?
#include <stdio.h> /* для putchar */ char s[] = "We don't need no education"; main(){ while(*s) putchar(*s++); }
Ответ: здесь s - константа, к ней неприменима операция ++. Надо написать
char *s = "We don't need no education";
сделав s указателем на безымянный маccив. Указатель уже можно изменять.
2.26. Какие из приведенных конструкций обозначают одно и то же?
char a[] |
= |
""; |
/* пустая строка */ |
char b[] |
= |
"\0"; |
|
char c |
= '\0'; |
|
|
char z[] |
= |
"ab"; |
|
char aa[] = |
{ '\0' }; |
||
char bb[] = |
{ '\0', '\0' }; |
||
char xx[] = |
{ 'a', |
'b' }; |
|
char zz[] = |
{ 'a', |
'b', '\0' }; |
|
char *ptr = "ab";
2.27. Найдите ошибки в описании символьной строки:
main() {
char mas[] = {'s', 'o', 'r', 't'}; /* "sort" ? */ printf("%s\n", mas);
}
Ответ: строка должна кончаться '\0' (в нашем случае printf не обнаружив символа конца строки будет выдавать и байты, находящиеся в памяти после массива mas, т.е. мусор); инициализированный массив не может быть автоматическим - требуется static:
main() {
static char mas[] = {'s', 'o', 'r', 't', '\0'};
} |
|
Заметим, что |
|
main(){ |
char *mas = "sort"; } |
законно, т.к. сама строка здесь хранится в статической памяти, а инициализируется лишь указатель на этот массив байт.
2.28. В чем ошибка? Программа собирается из двух файлов: a.c и b.c командой
|
cc a.c b.c -o ab |
a.c |
b.c |
---------------------------------------------------
int n = 2; |
extern int n; |
char s[] = "012345678"; |
extern char *s; |
main(){ |
f(){ |
f(); |
s[n] = '+'; |
printf("%s\n", s ); |
} |
} |
|
Ответ: дело в том, что типы (char *) - указатель, и char[] - массив, означают одно и то же только при
А. Богатырёв, 1992-96 |
- 82 - |
Си в UNIX™ |
объявлении формального параметра функции: |
|
|
f(char *arg){...} |
f(char arg[]){...} |
|
это будет локальная переменная, содержащая указатель на char (т.е. адрес некоторого байта в памяти). Внутри функции мы можем изменять эту переменную, например arg++. Далее, и (char *) и char[] одинаково используются, например, оба эти типа можно индексировать: arg[i]. Но вне функций они объявляют разные объекты! Так char *p; это скалярная переменная, хранящая адрес (указатель):
-------- |
------- |
p:| *--|----- |
>| '0' | char |
-------- |
| '1' | char |
|
... |
тогда как char a[20]; это адрес начала массива (а вовсе не переменная):
-------
a:| '0' | char
|'1' | char
...
Внашем примере в файле b.c мы объявили внешний массив s как переменную. В результате компилятор будет интерпретировать начало массива s как переменную, содержащую указатель на char.
------- |
|
|
|
s:| '0' |
| |
\ |
это будет воспринято как |
| '1' |
| |
/ |
адрес других данных. |
|'2' |
...
Ииндексироваться будет уже ЭТОТ адрес! Результат - обращение по несуществующему адресу. То, что написано у нас, эквивалентно
char s[] |
= |
"012345678"; |
||
char |
**ss |
= s; |
/* s - как бы "массив указателей" */ |
|
|
/* первые |
байты s интерпретируются как указатель: */ |
||
char |
*p |
= ss[0]; |
||
|
p[2] |
= |
'+'; |
|
Мы же должны были объявить в b.c
extern char s[]; /* размер указывать не требуется */
Вот еще один аналогичный пример, который пояснит вам, что происходит (а заодно покажет порядок байтов в long). Пример выполнялся на IBM PC 80386, на которой
sizeof(char *) = sizeof(long) = 4
a.c |
b.c |
--------------------------------------------------- |
|
char s[20] = {1,2,3,4}; |
extern char *s; |
main(){ |
f(){ |
|
/*печать указателя как long */ |
f(); |
printf( "%08lX\n", s ); |
} |
} |
печатается 04030201. |
|
2.29. Что напечатает программа? |
|
static char str1[ ] |
= "abc"; |
static char str2[4]; |
|
strcpy( str2, str1 );
/* можно ли написать str2 = str1; ? */
printf( str1 == str2 ? "равно":"не равно" );
Как надо правильно сравнивать строки? Что на самом деле сравнивается в данном примере? Ответ: сравниваются адреса массивов, хранящих строки. Так
А. Богатырёв, 1992-96 |
- 83 - |
Си в UNIX™ |
char str1[2]; char str2[2]; main(){
printf( str1 < str2 ? "<":">");
}
печатает <, а если написать
char str2[2]; char str1[2];
то напечатается >.
2.30.Напишите программу, спрашивающую ваше имя до тех пор, пока вы его правильно не введете. Для сравнения строк используйте функцию strcmp() (ее реализация есть в главе "Мобильность").
2.31.Какие значения возвращает функция strcmp() в следующей программе?
#include <stdio.h> main() {
printf("%d\n", strcmp("abc", "abc")); /* 0 */
printf("%d\n", strcmp("ab" , "abc")); |
/* -99 |
*/ |
|||
printf("%d\n", strcmp("abd", "abc")); |
/* |
1 |
*/ |
||
printf("%d\n", |
strcmp("abc", |
"abd")); |
/* |
-1 |
*/ |
printf("%d\n", |
strcmp("abc", |
"abe")); |
/* |
-2 |
*/ |
}
2.32. В качестве итога предыдущих задач: помните, что в Си строки (а не адреса) надо сравнивать как
if( strcmp("abc", "bcd") < 0) ... ; if( strcmp("abc", "bcd") == 0) ... ;
вместо
if( "abc" < "bcd" ) ... ; if( "abc" == "bcd" ) ... ;
и присваивать как |
|
|
char d[80], s[80]; |
|
|
strcpy( d, s ); |
вместо |
d = s; |
2.33. Напишите программу, которая сортирует по алфавиту и печатает следующие ключевые слова языка Си:
int char double long for while if
2.34. Вопрос не совсем про строки, скорее про цикл: чем плоха конструкция?
char s[] = "You're a smart boy, now shut up."; int i, len;
for(i=0; i < strlen(s); i++) putchar(s[i]);
Ответ: в соответствии с семантикой Си цикл развернется примерно в
i=0;
LOOP: if( !(i < strlen(s))) goto ENDLOOP; putchar(s[i]);
i++;
goto LOOP; ENDLOOP: ;
Заметьте, что хотя длина строки s не меняется, strlen(s) вычисляется на КАЖДОЙ итерации цикла, совершая лишнюю работу! Борьба с этим такова:
for(i=0, len=strlen(s); i < len; i++ ) putchar(s[i]);
или
for(i=0, len=strlen(s); len > 0; i++, --len ) putchar(s[i]);
Аналогично, в цикле
А. Богатырёв, 1992-96 |
- 84 - |
Си в UNIX™ |
while( i < strlen(s))...;
функция тоже будет вычисляться при каждой проверке условия! Это, конечно, относится к любой функции, используемой в условии, а не только к strlen. (Но, разумеется, случай когда функция возвращает признак "надо ли продолжать цикл" - совсем другое дело: такая функция обязана вычисляться каждый раз).
2.35. Что напечатает следующая программа?
#include <stdio.h> main(){
static char str[] = "До встречи в буфете"; char *pt;
pt = str; puts(pt); puts(++pt); str[7] = '\0'; puts(str); puts(pt); puts(++pt);
}
2.36. Что напечатает следующая программа?
main() {
static char name[] = "Константин"; char *pt;
pt = name + strlen(name); while(--pt >= name)
puts(pt);
}
2.37. Что напечатает следующая программа?
char str1[] = "abcdef"; char str2[] = "xyz"; main(){
register char *a, *b; a = str1; b = str2; while( *b )
*a++ = *b++;
printf( "str=%s a=%s\n", str1, a );
a = str1; b = str2; while( *b )
*++a = *b++;
printf( "str=%s a=%s\n", str1, a );
}
Ответ:
str=xyzdef a=def str=xxyzef a=zef
2.38. Что печатает программа?
char *s;
for(s = "Ситроен"; *s; s+= 2){ putchar(s[0]); if(!s[1]) break;
}
putchar('\n');
2.39. Что напечатает программа? Рассмотрите продвижение указателя s, указателей - элементов массива strs[]. Разберитесь с порядком выполнения операций. В каких случаях ++ изменяет указатель, а в каких - букву в строке? Нарисуйте себе картинку, изображающую состояние указателей - она поможет вам распутать эти спагетти. Уделите разбору этого примера достаточное время!
#include <stdio.h> /* определение NULL */
/* Латинский алфавит: abcdefghijklmnopqrstuvwxyz */ char *strs[] = {
"abcd","ABCD","0fpx","159", "hello","-gop","A1479",NULL
А. Богатырёв, 1992-96 |
|
- 85 - |
Си в UNIX™ |
||
}; |
|
|
|
|
|
main(){ |
|
|
|
|
|
char c, |
|
**s = strs, |
*p; |
|
|
c = *++*s; |
printf("#1 %d %c %s\n", s-strs, c, *s); |
|
|||
c = **++s; |
printf("#2 %d %c %s\n", s-strs, c, *s); |
|
|||
c = **s++; |
printf("#3 %d %c %s\n", s-strs, c, *s); |
|
|||
c = ++**s; |
printf("#4 %d %c %s\n", s-strs, c, *s); |
|
|||
c = (**s)++; printf("#5 %d %c %s\n", s-strs, c, *s); |
|
||||
c = ++*++*s; printf("#6 %d %c %s\n", s-strs, c, *s); |
|
||||
c = *++*s++; printf("#7 %d %c %s %s\n", |
|
||||
|
|
|
|
s-strs, c, *s, strs[2]); |
|
c = ++*++*s++; printf("#8 %d %c %s %s\n", |
|
||||
|
|
|
|
s-strs, c, *s, strs[3]); |
|
c = ++*++*++s; printf("#9 %d %c %s\n", s-strs,c,*s); |
|
||||
c = ++**s++; |
printf("#10 %d %c %s\n",s-strs,c,*s); |
|
|||
p = *s; c = ++*(*s)++; |
|
|
|||
printf("#11 %d %c %s %s %s\n",s-strs,c,*s,strs[6],p); |
|
||||
c = ++*((*s)++); printf("#12 %d %c %s %s\n", |
|
||||
|
|
|
|
s-strs, c, *s, strs[6]); |
|
c = (*++(*s))++; printf("#13 %d %c %s %s\n", |
|
||||
|
|
|
|
s-strs, c, *s, strs[6]); |
|
for(s=strs; *s; s++) |
|
|
|||
|
printf("strs[%d]=\"%s\"\n", s-strs, *s); |
|
|||
putchar('\n'); |
|
|
|||
} |
|
|
|
|
|
Печатается: |
|
|
|
|
|
#1 0 b bcd |
|
|
strs[0]="bcd" |
|
|
#2 1 A ABCD |
|
|
strs[1]="ABCD" |
|
|
#3 2 A 0fpx |
|
|
strs[2]="px" |
|
|
#4 2 1 1fpx |
|
|
strs[3]="69" |
|
|
#5 2 1 2fpx |
|
|
strs[4]="hello" |
|
|
#6 2 g gpx |
|
|
strs[5]="iop" |
|
|
#7 3 p 159 px |
|
strs[6]="89" |
|
||
#8 4 6 hello 69 |
|
|
|||
#9 5 h hop |
|
|
|
|
|
#10 6 i A1479 |
|
|
|
||
#11 6 B 1479 1479 B1479 |
|
|
|||
#12 6 |
2 479 |
479 |
|
|
|
#13 6 |
7 89 |
89 |
|
|
|
Учтите, что конструкция
char *strs[1] = { "hello" };
означает, что в strs[0] содержится указатель на начальный байт безымянного массива, содержащего строку "hello". Этот указатель можно изменять! Попробуйте составить еще подобные примеры из *, ++, ().
2.40. Что печатает программа?
char str[25] = "Hi, ";
char *f(char **s){ int cnt;
for(cnt=0; **s != '\0'; (*s)++, ++cnt); return("ny" + (cnt && (*s)[-1] == ' ') + (!cnt));
}
void main(void){ char *s = str;
if( *f(&s) == 'y') strcat(s, "dude"); else strcat(s, " dude"); printf("%s\n", str);
}
Что она напечатает, если задать |
|
char str[25]="Hi,"; или |
char str[25]=""; |
2.41. В чем состоит ошибка? (Любимая ошибка начинающих)
А. Богатырёв, 1992-96 |
- 86 - |
Си в UNIX™ |
main(){ |
|
|
char *buf; |
/* или char buf[]; */ |
|
gets( buf ); |
|
|
printf( "%s\n", buf );
}
Ответ: память под строку buf не выделена, указатель buf не проинициализирован и смотрит неизвестно куда. Надо было писать например так:
char buf[80]; или
char mem[80], *buf = mem;
Обратите на этот пример особое внимание, поскольку, описав указатель (но никуда его не направив), новички успокаиваются, не заботясь о выделении памяти для хранения данных. Указатель должен указывать на ЧТО-ТО, в чем можно хранить данные, а не "висеть", указывая "пальцем в небо"! Запись информации по "висячему" указателю разрушает память программы и приводит к скорому (но часто не немедленному и потому таинственному) краху.
Вот программа, которая также использует неинициализированный указатель. На машине SPARCstation 20 эта программа убивается операционной системой с диагностикой "Segmentation fault" (SIGSEGV). Это как раз и значит обращение по указателю, указывающему "пальцем в небо".
main(){
int *iptr;
int ival = *iptr;
printf("%d\n", ival);
}
2.42. Для получения строки "Life is life" написана программа:
main(){
char buf[ 60 ]; strcat( buf, "Life " ); strcat( buf, "is " ); strcat( buf, "life" ); printf( "%s\n", buf );
}
Что окажется в массиве buf?
Ответ: в начале массива окажется мусор, поскольку автоматический массив не инициализируется байтами '\0', а функция strcat() приписывает строки к концу строки. Для исправления можно написать
*buf = '\0';
перед первым strcat()-ом, либо вместо первого strcat()-а написать strcpy( buf, "Life " );
2.43.Составьте макроопределение copystr(s1, s2) для копирования строки s2 в строку s1.
2.44.Составьте макроопределение lenstr(s) для вычисления длины строки.
Многие современные компиляторы сами обращаются с подобными короткими (1-3 оператора) стандартными функциями как с макросами, то есть при обращении к ним генерят не вызов функции, а подставляют текст ее тела в место обращения. Это делает объектный код несколько "толще", но зато быстрее. В расширенных диалектах Си и в Си++ компилятору можно предложить обращаться так и с вашей функцией - для этого функцию следует объявить как inline (такие функции называются еще "intrinsic").
2.45. Составьте рекурсивную и нерекурсивную версии программы инвертирования (зеркального отображения) строки:
abcdef --> fedcba.
2.46. Составьте функцию index(s, t), возвращающую номер первого вхождения символа t в строку s; если символ t в строку не входит, функция возвращает -1.
Перепишите эту функцию с указателями, чтобы она возвращала указатель на первое вхождение символа. Если символ в строке отсутствует - выдавать NULL. В UNIX System-V такая функция называется strchr. Вот возможный ответ:
А. Богатырёв, 1992-96 |
- 87 - |
Си в UNIX™ |
char *strchr(s, c) register char *s, c;
{while(*s && *s != c) s++; return *s == c ? s : NULL;
}
Заметьте, что p=strchr(s,'\0'); выдает указатель на конец строки. Вот пример использования:
extern char *strchr();
char *s = "abcd/efgh/ijklm"; char *p = strchr(s, '/');
printf("%s\n", p==NULL ? "буквы / нет" : p);
if(p) printf("Индекс вхождения = s[%d]\n", p - s );
2.47. Напишите функцию strrchr(), указывающую на последнее вхождение символа. Ответ:
char *strrchr(s, c) register char *s, c;
{char *last = NULL;
do if(*s == c) last = s; while(*s++); return last;
} |
|
Вот пример ее использования: |
|
extern char *strrchr(); |
|
char p[] = "wsh"; |
/* эталон */ |
main(argc, argv) char *argv[];{ |
|
char *s = argv[1]; |
/* проверяемое имя */ |
/* попробуйте вызывать
*a.out csh
*a.out /bin/csh
*a.out wsh
*a.out /usr/local/bin/wsh
*/
char *base =
(base = strrchr(s, '/')) ? base+1 : s; if( !strcmp(p, base))
printf("Да, это %s\n" , p); else printf("Нет, это %s\n", base);
/* еще более изощренный вариант: */
if( !strcmp(p,(base=strrchr(s,'/')) ? ++base :
|
(base=s)) |
) printf("Yes %s\n", |
p); |
else printf("No %s\n", |
base); |
} |
|
2.48. Напишите макрос substr(to,from,n,len) который записывает в to кусок строки from начиная с n-ой позиции и длиной len. Используйте стандартную функцию strncpy.
Ответ:
#define substr(to, from, n, len) strncpy(to, from+n, len)
или более корректная функция:
char *substr(to, from, n, len) char *to, *from;
{
int lfrom = strlen(from); if(n < 0 ){ len += n; n = 0; } if(n >= lfrom || len <= 0)
*to = '\0'; /* пустая строка */ else{
/* длина остатка строки: */
if(len > lfrom-n) len = lfrom - n; strncpy(to, from+n, len);
to[len] = '\0';
}
return to;
}
А. Богатырёв, 1992-96 |
- 88 - |
Си в UNIX™ |
2.49. Напишите функцию, проверяющую, оканчивается ли строка на ".abc", и если нет - приписывающую ".abc" к концу. Если же строка уже имеет такое окончание - ничего не делать. Эта функция полезна для генерации имен файлов с заданным расширением. Сделайте расширение аргументом функции.
Для сравнения конца строки s со строкой p следует использовать:
int ls = |
strlen(s), lp = strlen(p); |
|
if(ls >= |
lp && !strcmp(s+ls-lp, p)) ... |
совпали...; |
2.50. Напишите функции вставки символа c в указанную позицию строки (с раздвижкой строки) и удаления символа в заданной позиции (со сдвижкой строки). Строка должна изменяться "на месте", т.е. никуда не копируясь. Ответ:
/* удаление */
char delete(s, at) register char *s;
{
char c;
s += at; if((c = *s) == '\0') return c; while( s[0] = s[1] ) s++;
return c;
}
/* либо просто strcpy(s+at, s+at+1); */
/* вставка */
insert(s, at, c) char s[], c;
{
register char *p;
s += at; p = s; |
|
while(*p) p++; |
/* на конец строки */ |
p[1] = '\0'; |
/* закрыть строку */ |
for( ; p != s; p-- ) p[0] = p[-1];
*s = c;
}
2.51. Составьте программу удаления символа c из строки s в каждом случае, когда он встречается.
Ответ:
delc(s, c) register char *s; char c;
{
register char *p = s; while( *s )
if( *s != c |
) *p++ = *s++; |
else |
s++; |
*p = '\0'; /* |
не забывайте закрывать строку ! */ |
} |
|
2.52.Составьте программу удаления из строки S1 каждого символа, совпадающего с каким-либо символом строки S2.
2.53.Составьте функцию scopy(s,t), которая копирует строку s в t, при этом символы табуляции и перевода строки должны заменяться на специальные двухсимвольные последовательности "\n" и "\t". Используйте switch.
2.54.Составьте функцию, которая "укорачивает" строку, заменяя изображения спецсимволов (вроде "\n") на сами эти символы ('\n'). Ответ:
extern char *strchr(); void unquote(s) char *s;
{static char from[] = "nrtfbae",
to [] = "\n\r\t\f\b\7\33"; char c, *p, *d;
for(d=s; c = *s; s++) if( c == '\\'){
if( !(c = *++s)) break;
А. Богатырёв, 1992-96 |
- 89 - |
Си в UNIX™ |
|
p = strchr(from, c); |
|
|
*d++ = p ? to[p - from] : c; |
|
}else |
*d++ = c; |
|
*d = '\0'; |
|
|
} |
|
|
2.55. Напишите программу, заменяющую в строке S все вхождения подстроки P на строку Q, например:
P = "ура"; Q = "ой"; S = "ура-ура-ура!";
Результат: "ой-ой-ой!"
2.56. Кроме функций работы со строками (где предполагается, что массив байт завершается признаком конца '\0'), в Си предусмотрены также функции для работы с массивами байт без ограничителя. Для таких функций необходимо явно указывать длину обрабатываемого массива. Напишите функции: пересылки массива длиной n байт memcpy(dst,src,n); заполнения массива символом c memset(s,c,n); поиска вхождения символа в массив memchr(s,c,n); сравнения двух массивов memcmp(s1,s2,n); Ответ:
#define REG register
char *memset(s, c, n) REG char *s, c;
{REG char *p = s;
while( --n >= 0 ) *p++ = c;
return s;
}
char *memcpy(dst, src, n) REG char *dst, *src; REG int n;
{REG char *d = dst;
while( n-- > 0 ) *d++ = *src++; return dst;
}
char *memchr(s, c, n) REG char *s, c;
{
while(n-- && *s++ != c); return( n < 0 ? NULL : s-1 );
}
int memcmp(s1, s2, n)
REG char *s1, *s2; REG n;
{
while(n-- > 0 && *s1 == *s2) s1++, s2++;
return( n < 0 ? 0 : *s1 - *s2 );
}
Есть такие стандартные функции.
2.57. Почему лучше пользоваться стандартными функциями работы со строками и памятью (strcpy, strlen, strchr, memcpy, ...)?
Ответ: потому, что они обычно реализованы поставщиками системы ЭФФЕКТИВНО, то есть написаны не на Си, а на ассемблере с использованием специализированных машинных команд и регистров. Это делает их более быстрыми. Написанный Вами эквивалент на Си может использоваться для повышения мобильности программы, либо для внесения поправок в стандартные функции.
2.58. Рассмотрим программу, копирующую строку саму в себя:
#include <stdio.h> #include <string.h>
char string[] = "abcdefghijklmn"; void main(void){
memcpy(string+2, string, 5); printf("%s\n", string); exit(0);
Она печатает abababahijklmn. Мы могли бы ожидать, что кусок длины 5 символов "abcde" будет скопирован как есть: ab[abcde]hijklmn, а получили ab[ababa]hijklmn - циклическое повторение первых двух символов строки... В чем дело? Дело в том, что когда области источника (src) и получателя (dst) перекрываются, то в
А. Богатырёв, 1992-96 |
- 90 - |
Си в UNIX™ |
некий момент *src берется из УЖЕ перезаписанной ранее области, то есть испорченной! Вот программа, иллюстрирующая эту проблему:
#include <stdio.h> #include <string.h> #include <ctype.h>
char string[] = "abcdefghijklmn"; char *src = &string[0];
char *dst = &string[2]; int n = 5;
void show(int niter, char *msg){ register length, i;
printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t');
for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n');
printf("\t...%s...\n", string);
length = dst-string; putchar('\t');
for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n');
}
void main(void){
int iter = 0;
while(n-- > 0){
show(iter, "перед"); *dst++ = toupper(*src++);
show(iter++, "после");
}
exit(0);
}
Она печатает:
