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, так и не поняв в чем дело.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед