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]="";
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); }
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 " );
Составьте макроопределение copystr(s1, s2) для копирования строки s2 в строку s1.
Многие современные компиляторы сами обращаются с подобными короткими (1-3 оператора) стандартными функциями как с макросами, то есть при обращении к ним генерят не вызов функции, а подставляют текст ее тела в место обращения. Это делает объектный код несколько "толще", но зато быстрее. В расширенных диалектах Си и в Си++ компилятору можно предложить обращаться так и с вашей функцией - для этого функцию следует объявить как inline (такие функции называются еще "intrinsic").
abcdef --> fedcba.
Составьте функцию index(s, t), возвращающую номер первого вхождения символа t в строку s; если символ t в строку не входит, функция возвращает -1.
Перепишите эту функцию с указателями, чтобы она возвращала указатель на первое вхождение символа. Если символ в строке отсутствует - выдавать NULL. В UNIX System-V такая функция называется strchr. Вот возможный ответ:
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 );
Напишите функцию 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); }
Напишите макрос 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; }
Напишите функцию, проверяющую, оканчивается ли строка на ".abc", и если нет приписывающую ".abc" к концу. Если же строка уже имеет такое окончание - ничего не делать. Эта функция полезна для генерации имен файлов с заданным расширением. Сделайте расширение аргументом функции.
Для сравнения конца строки s со строкой p следует использовать:
int ls = strlen(s), lp = strlen(p); if(ls >= lp && !strcmp(s+ls-lp, p)) ...совпали...;
Напишите функции вставки символа 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; }
Составьте программу удаления символа 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'; /* не забывайте закрывать строку ! */ }
Составьте программу удаления из строки S1 каждого символа, совпадающего с каким-либо символом строки S2.
Составьте функцию scopy(s,t), которая копирует строку s в t, при этом символы табуляции и перевода строки должны заменяться на специальные двухсимвольные последовательности "\n" и "\t". Используйте switch.
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; p = strchr(from, c); *d++ = p ? to[p - from] : c; }else *d++ = c; *d = '\0'; }
Напишите программу, заменяющую в строке S все вхождения подстроки P на строку Q, например:
P = "ура"; Q = "ой"; S = "ура-ура-ура!"; Результат: "ой-ой-ой!"
Кроме функций работы со строками (где предполагается, что массив байт завершается признаком конца '\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 ); }Есть такие стандартные функции.
Почему лучше пользоваться стандартными функциями работы со строками и памятью (strcpy, strlen, strchr, memcpy, ...)?
Ответ: потому, что они обычно реализованы поставщиками системы ЭФФЕКТИВНО, то есть написаны не на Си, а на ассемблере с использованием специализированных машинных команд и регистров. Это делает их более быстрыми. Написанный Вами эквивалент на Си может использоваться для повышения мобильности программы, либо для внесения поправок в стандартные функции.
#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) перекрываются, то в некий момент *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); }Она печатает:
#00 перед S ...abcdefghijklmn... D #00 после S ...abAdefghijklmn... D #01 перед S ...abAdefghijklmn... D #01 после S ...abABefghijklmn... D #02 перед S ...abABefghijklmn... D #02 после S ...abABAfghijklmn... D #03 перед S ...abABAfghijklmn... D #03 после S ...abABABghijklmn... D #04 перед S ...abABABghijklmn... D #04 после S ...abABABAhijklmn... D
Отрезки НЕ перекрываются, если один из них лежит либо целиком левее, либо целиком правее другого (n - длина обоих отрезков).
dst src src dst ######## @@@@@@@@ @@@@@@@@ ######## dst+n <= src или src+n <= dst dst <= src-n или dst >= src+nОтрезки перекрываются в случае
! (dst <= src - n || dst >= src + n) = (dst > src - n && dst < src + n)При этом опасен только случай dst > src. Таким образом опасная ситуация описывается условием
src < dst && dst < src + n(если dst==src, то вообще ничего не надо делать). Решением является копирование "от хвоста к голове":
void bcopy(register char *src, register char *dst, register int n){ if(dst >= src){ dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else{ while(n-- > 0) *dst++ = *src++; } }Или, ограничиваясь только опасным случаем:
void bcopy(register char *src, register char *dst, register int n){ if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); }Программа
#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; if(dst==src || n <= 0){ printf("Ничего не надо делать\n"); return; } if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0){ show(iter, "перед"); *dst-- = toupper(*src--); show(iter++, "после"); } }else while(n-- > 0){ show(iter, "перед"); *dst++ = toupper(*src++); show(iter++, "после"); } exit(0); }Печатает
#00 перед S ...abcdefghijklmn... D #00 после S ...abcdefEhijklmn... D #01 перед S ...abcdefEhijklmn... D #01 после S ...abcdeDEhijklmn... D #02 перед S ...abcdeDEhijklmn... D #02 после S ...abcdCDEhijklmn... D #03 перед S ...abcdCDEhijklmn... D #03 после S ...abcBCDEhijklmn... D #04 перед S ...abcBCDEhijklmn... D #04 после S ...abABCDEhijklmn... D
Теперь bcopy() - удобная функция для копирования и сдвига массивов, в частности массивов указателей. Пусть у нас есть массив строк (выделенных malloc-ом):
char *lines[NLINES];Тогда циклическая перестановка строк выглядит так:
void scrollUp(){ char *save = lines[0]; bcopy((char *) lines+1, /* from */ (char *) lines, /* to */ sizeof(char *) * (NLINES-1)); lines[NLINES-1] = save; } void scrollDown(){ char *save = lines[NLINES-1]; bcopy((char *) &lines[0], /* from */ (char *) &lines[1], /* to */ sizeof(char *) * (NLINES-1)); lines[0] = save; }
Возможно, что написание по аналогии функции для копирования массивов элементов типа
(void *) - обобщенных указателей - может оказаться еще понятнее и эффективнее. Такая функция - memmove - стандартно существует в UNIX SVR4. Заметьте, что порядок аргументов в ней обратный по отношению к bcopy. Следует отметить, что в SVR4 все функции
mem... имеют указатели типа (void *) и счетчик типа size_t - тип для количества байт
(вместо unsigned long); в частности длина файла имеет именно этот тип (смотри системные вызовы lseek и stat).
#include <sys/types.h>
void memmove(void *Dst, const void *Src,
register size_t n){
register caddr_t src = (caddr_t) Src,
dst = (caddr_t) Dst;
if(dst==src || n <= 0) return;
if(src < dst && dst < src + n) {
dst += n-1;
src += n-1;
while(--n >= 0)
*dst-- = *src--;
}else memcpy(dst, src, n);
}
caddr_t - это тип для указателей на БАЙТ, фактически это (unsigned char *). Зачем
вообще понадобилось использовать caddr_t? Затем, что для
void *pointer;
int n;
значение
pointer + n
не определено и невычислимо, ибо sizeof(void) не имеет смысла - это не 0, а просто ошибка, диагностируемая компилятором!
Еще об опечатках: вот что бывает, когда вместо знака `=' печатается `-' (на клавиатуре они находятся рядом...).
#include <stdio.h> #include <strings.h> char *strdup(const char *s){ extern void *malloc(); return strcpy((char *)malloc(strlen(s)+1), s); } char *ptr; void main(int ac, char *av[]){ ptr - strdup("hello"); /* подразумевалось ptr = ... */ *ptr = 'H'; printf("%s\n", ptr); free(ptr); exit(0); }
Дело в том, что запись (а часто и чтение) по *pointer, где pointer==NULL, приводит к аварийному прекращению программы. В нашей программе ptr осталось равным NULL - указателем в никуда. В операционной системе UNIX на машинах с аппаратной защитой памяти, страница памяти, содержащая адрес NULL (0) бывает закрыта на запись, поэтому любое обращение по записи в эту страницу вызывает прерывание от диспетчера памяти и аварийное прекращение процесса. Система сама помогает ловить ваши ошибки (но уже во время выполнения программы). Это ОЧЕНЬ частая ошибка - запись по адресу NULL. MS DOS в таких случаях предпочитает просто зависнуть, и вы бываете вынуждены играть аккорд из трех клавиш - Ctrl/Alt/Del, так и не поняв в чем дело.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед