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.

В чем состоит ошибка? (Любимая ошибка начинающих)

      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. Вот возможный ответ:


    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;

    }

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;

                            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) перекрываются, то в некий момент *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, а просто ошибка, диагностируемая компилятором!

2.59.

Еще об опечатках: вот что бывает, когда вместо знака `=' печатается `-' (на клавиатуре они находятся рядом...).


    #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, так и не поняв в чем дело.

[Назад][Содержание][Вперед]

Используются технологии uCoz