Новые книги

Windows XP – это одна из самых популярных операционных систем. Дома, на работе, в Интернет-кафе вы не сможете работать на компьютере, не умея работать с этой операционной системой.

С помощью этой книги вы быстро освоите все необходимые навыки для работы с Windows XP. Прочитав ее, вы узнаете, как установить, настроить и управлять этой операционной системой.
Фундаментальный учебник по основам объектно-ориентированного программирования и инженерии программ. В книге подробно излагаются основные понятия объектной технологии – классы, объекты, управление памятью, типизация, наследование, универсализация. Большое внимание уделяется проектированию по контракту и обработке исключений, как механизмам, обеспечивающим корректность и устойчивость программных систем.

В книге Бертрана Мейера рассматриваются основы объектно-ориентированного программирования. Изложение начинается с рассмотрения критериев качества программных систем и обоснования того, как объектная технология разработки может обеспечить требуемое качество. Основные понятия объектной технологии и соответствующая нотация появляются как результат тщательного анализа и обсуждений. Подробно рассматривается понятие класса - центральное понятие объектной технологии. Рассматривается абстрактный тип данных, лежащий в основе класса, совмещение классом роли типа данных и модуля и другие аспекты построения класса. Столь же подробно рассматриваются объекты и проблемы управления памятью. Большая часть книги уделена отношениям между классами – наследованию, универсализации и их роли в построении программных систем. Важную часть книги составляет введение понятия контракта, описание технологии проектирования по контракту, как механизма, обеспечивающего корректность создаваемых программ. Не обойдены вниманием и другие важные темы объектного программирования – скрытие информации, статическая типизация, динамическое связывание и обработка исключений. Глубина охвата рассматриваемых тем делает книгу Бертрана Мейера незаменимой для понимания основ объектного программирования.

Массивы, строки, указатели.



2.20.

Напишите программу, которая объединяет и распечатывает две строки, введенные с терминала. Для ввода строк используйте функцию gets(), а для их объединения strcat(). В другом варианте используйте sprintf(result,"%s%s",s1,s2);

2.21.

Модифицируйте предыдущую программу таким образом, чтобы она выдавала длину (число символов) объединенной строки. Используйте функцию strlen(). Приведем несколько версий реализации strlen:

    /* При помощи индексации массива */
    int strlen(s) char s[];
    {   int length = 0;
        for(; s[length] != '\0'; length++);
        return (length);
    }
    /* При помощи продвижения указателя */
    int strlen(s) char *s;
    {   int length;
        for(length=0; *s; length++, s++);
        return length;
    }
    /* При помощи разности указателей */
    int strlen(register char *s)
    {   register char *p = s;
        while(*p) p++;   /* ищет конец строки */
        return (p - s);
    }
Разность двух указателей на один и тот же тип - целое число:
    если TYPE *p1, *p2;
    то  p2 - p1 = целое число штук TYPE
                  лежащих между p2 и p1
    если p2 = p1 + n
    то   p2 - p1 = n
Эта разность может быть и отрицательной если p2 < p1, то есть p2 указывает на более левый элемент массива.

2.22.

Напишите оператор Си, который обрубает строку s до длины n букв.

Ответ:

    if( strlen(s) > n )
         s[n] = '\0';

Первое сравнение вообще говоря излишне. Оно написано лишь на тот случай, если строка s короче, чем n букв и хранится в массиве, который также короче n, т.е. не имеет nого элемента (поэтому в него нельзя производить запись признака конца).

2.23.

Напишите функции преобразования строки, содержащей изображение целого числа, в само это число. В двух разных вариантах аргумент-адрес должен указывать на первый байт строки; на последний байт. Ответ:

    #define isdigit(c) ('0' <= (c) && (c) <= '9')
    int atoi(s) register char *s;
    {   register int res=0, neg=0;
        for(;;s++){
            switch(*s){
            case ' ': case '\t': continue;
            case '-':            neg++;
            case '+':            s++;
            } break;
        }
        while(isdigit(*s))
            res = res * 10  +  *s++ - '0';
        return( neg ? -res : res );
    }
    int backatoi(s) register char *s;
    {   int res=0, pow=1;
        while(isdigit(*s)){
            res += (*s-- - '0') * pow;
            pow *= 10;
        }
        if(*s == '-') res = -res;
        return res;
    }

2.24.

Можно ли для занесения в массив s строки "hello" написать
            char s[6]; s = "hello";
                 или
            char s[6], d[] = "hello"; s = d;

Ответ: нет. Массивы в Си нельзя присваивать целиком. Для пересылки массива байт надо использовать функцию strcpy(s,d). Здесь же мы пытаемся изменить адрес s (имя массива - это адрес начала памяти, выделенной для хранения массива), сделав его равным адресу безымянной строки "hello" (или массива d во втором случае). Этот адрес является константой и не может быть изменен!

Заметим однако, что описание массива с инициализацией вполне допустимо:

            char s[6] = "hello";
или
            char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };
или
            char s[] = "hello";
или
            char s[] = { "hello" };

В этом случае компилятор резервирует память для хранения массива и расписывает ее байтами начального значения. Обратите внимание, что строка в двойных кавычках (если ее рассматривать как массив букв) имеет длину на единицу больше, чем написано букв в строке, поскольку в конце массива находится символ '\0' - признак конца, добавленный компилятором. Если бы мы написали

            char s[5] = "hello";

то компилятор сообщил бы об ошибке, поскольку длины массива (5) недостаточно, чтобы разместить 6 байт. В третьей строке примера написано s[], чтобы компилятор сам посчитал необходимую длину массива.

Наконец, возможна ситуация, когда массив больше, чем хранящаяся в нем строка. Тогда "лишнее" место содержит какой-то мусор (в static-памяти изначально - байты \0).

      char s[12] = "hello";
      содержит:     h e l l o \0 ? ? ? ? ? ?

В программах текстовой обработки под "длиной строки" обычно понимают количество букв в строке НЕ считая закрывающий байт '\0'. Именно такую длину считает стандартная функция strlen(s). Поэтому следует различать такие понятия как "(текущая) длина строки" и "длина массива, в котором хранится строка": sizeof(s). Для написанного выше примера эти значения равны соответственно 5 и 12.

Следует также отличать массивы от указателей:

            char *sp = "bye bye";
            sp = "hello";

будет вполне законно, поскольку в данном случае sp - не имя массива (т.е. константа, равная адресу начала массива), а указатель (переменная, хранящая адрес некоторой области памяти). Поскольку указатель - это переменная, то ее значение изменять можно: в данном случае sp сначала содержала адрес безымянного массива, в котором находится "bye bye"; затем мы занесли в sp адрес безымянного массива, хранящего строку "hello". Здесь не происходит копирования массива, а происходит просто присваивание переменной sp нового значения адреса.

Предостережем от возможной неприятности:

            char d[5]; char s[] = "abcdefgh";
            strcpy(d, s);

Длины массива d просто не хватит для хранения такой длинной строки. Поскольку это ничем не контролируется (ни компилятором, ни самой strcpy, ни вами явным образом), то при копировании строки "избыточные" байты запишутся после массива d поверх других данных, которые будут испорчены. Это приведет к непредсказуемым эффектам.

Некоторые возможности для контроля за длиной строк-аргументов вам дают функции

	strncpy(d,s,len);  	strncat(d,s,len); 	strncmp(s1,s2,len).

Они пересылают (сравнивают) не более, чем len первых символов строки s (строк s1, s2). Посмотрите в документацию! Напишите функцию strncmp (сравнение строк по первым len символам), посмотрев на функцию strncpy:

    char *strncpy(dst, src, n)
         register char *dst, *src;
         register int n;
    {    char *save;
         for(save=dst; --n >= 0; )
             if( !(*dst++ = *src++)){
                 while(--n >= 0)
                    *dst++ = '\0';
                 return save;
             }
         return save;
    }

Отметьте, что strncpy обладает одним неприятным свойством: если n <= strlen(src), то строка dst не будет иметь на конце символа '\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[] - массив, означают одно и то же только при объявлении формального параметра функции:

    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 ? "равно":"не равно" );
Как надо правильно сравнивать строки? Что на самом деле сравнивается в данном примере?

Ответ: сравниваются адреса массивов, хранящих строки. Так

    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]);
Аналогично, в цикле
    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
    };
    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". Этот указатель можно изменять! Попробуйте составить еще подобные примеры из *, ++, ().

© Copyright А. Богатырев, 1992-95
Си в UNIX

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