Сборка strcat без библиотеки и без указателей

Меня попросили создать strcat из string.h без использования библиотеки и указателей .

У меня есть это до сих пор, но почему-то это не работает:

void strcatO(char a[], char b[])
{
    int i = 0;

    for(i = 0; i < strlen(b); ++i)
    {
        a[strlen(a) + i + 1] = b[i];
    }

    printf("%s", a);
}

Выход:

a


person Lidor Cohen    schedule 26.01.2019    source источник
comment
Обратите внимание, что когда вы объявляете такой аргумент, как char a[], компилятор действительно обрабатывает его как char *a. Так что нет, вы не делаете это без указателей.   -  person Some programmer dude    schedule 26.01.2019
comment
Думаю, ты прав   -  person Lidor Cohen    schedule 26.01.2019
comment
strlen() это из библиотеки. И a, и b являются действительно указателями, даже если их объявление выглядит как массивы. На самом деле, если ваш учитель говорит вам, что это не так, попросите его объяснить, почему sizeof( a ) в вашем strcat() всегда будет равно sizeof( char * ), независимо от того, насколько велик массив в вызове Функция есть. В итоге, поскольку любой массив, который вы используете в качестве аргумента для вызова функции, выродится в указатель, ваш учитель оказал своим ученикам медвежью услугу, сформулировав задачу таким образом. Ответ в том, что это невозможно сделать, все, что я могу сделать, это написать это без *.   -  person DevSolar    schedule 26.01.2019
comment
Я заранее построил strlen в другой функции, и теперь я получил указатели, спасибо!   -  person Lidor Cohen    schedule 26.01.2019
comment
@DevSolar прав. Массивы, передаваемые функциям в качестве аргументов, всегда настраиваются, как технический термин, на указатели на их первые элементы. Объявления void f(int arr[]); и void f(int *arr); — это одно и то же; они взаимозаменяемы. Даже void f(int arr[5]); не имеет значения; индекс игнорируется. Если вас смущают семантические и условные особенности C, вы не одиноки.   -  person Peter - Reinstate Monica    schedule 26.01.2019
comment
Я заранее построил strlen в другой функции... может иметь плохой эффект с этим кодом. Скажем, длина строки b равна N. затем for(i = 0; i < strlen(b); ++i) { ... } звонит оператору strlen(N) N раз. Каждый вызов работает вниз b, чтобы найти длину или N операций. Будучи кодом OP, компилятор, вероятно, не распознает эту неэффективность, а затем сделает этот код O(N*N) - очень медленным. Вместо этого вызовите strlen(b) перед циклом или просто используйте for(i = 0; b[i]; ++i).   -  person chux - Reinstate Monica    schedule 26.01.2019
comment
@PeterA.Schneider: В void f(int arr[5]); неясно, игнорируется ли длина массива. Некоторые компиляторы оценивают выражение размера массива (после чего значение отбрасывается). Например, с Apple LLVM 10.0.0 clang-1000.11.45.5 void f(int arr[printf("Hello, world.\n")]) {} int main(void) { f(0); } печатает «Привет, мир».   -  person Eric Postpischil    schedule 26.01.2019


Ответы (4)


как-то не работает

a[strlen(a) + i + 1] = b[i]; добавляет символы после a нулевого символа.

void strcatO(char a[], char b[]) {
    int i = 0;
    for(i = 0; i < strlen(b); ++i) {
      a[strlen(a) + i + 1] = b[i];  // Oops: appending position is off-by-one
    }
    printf("%s", a);
}

strcatO("ab", "cd") заполнит a как 'a', 'b', '\0', 'c', 'd'.

Печать этого с printf("%s", a); печатает только 'a', 'b'.


Чтобы исправить это, код должен добавляться в правильном месте, но это перезаписывает исходный a нулевой символ. Таким образом, вызовы strlen(a) плохи.

Вместо этого и для повышения эффективности выполните не звонить strlen() повторно.

void strcatO(char a[], const char b[]) {
  size_t ai = 0;
  while (a[ai]) {       // go to end of a
    ai++;
  }

  size_t bi = 0;
  while (b[bi]) {        // while not at the end of b ...
    a[ai++] = b[bi++];
  }

  a[ai] = '\0';
  printf("<%s>", a);
}

Детали тонких улучшений:

const в const char b[] означает, что b ссылается на данные, которые эта функция не должна пытаться изменить. Это 1) позволяет этой функции конкатенировать b, если это будет const char [] 2) Позволяет оптимизацию, которую слабый компилятор может не увидеть.

size_t лучше, чем int, для длинных строк, которые могут быть длиннее INT_MAX. size_t — это тип «правильного размера» для длин строк и размеров массивов. OP (исходный постер) действительно имел «без использования библиотеки», а size_t взят из библиотеки, поэтому код мог использовать unsigned или лучше unsigned long в качестве альтернативы.

person chux - Reinstate Monica    schedule 26.01.2019
comment
Я думаю, что ОП написал функцию strlen ;-). - person Peter - Reinstate Monica; 26.01.2019
comment
@PeterA.Schneider да OP вычисляет длину b, но потерял ее ^^ Кстати, что означает OP? Я использую его, потому что некоторые так и делают, но не зная точно, что означают эти аббревиатуры ^^ - person bruno; 26.01.2019
comment
@bruno: Оригинальный постер. - person Nominal Animal; 26.01.2019
comment
@bruno Либо оригинальный пост, либо оригинальный постер, насколько мне известно, по крайней мере, я так его использую. - person Peter - Reinstate Monica; 26.01.2019
comment
Кстати, мне нравится этот ответ, потому что он очень четко объясняет проблему, а решение является кратким и правильным. - person Peter - Reinstate Monica; 26.01.2019
comment
@PeterA.Schneider, как ни странно, я предпочитаю свой старый ^^ - person bruno; 26.01.2019
comment
Хорошо сделано. Если бы этот ответ был здесь раньше, я бы не написал свой. - person Eric Postpischil; 26.01.2019

из ваших проблем вы постоянно вычисляете strlen даром, надеясь, что компилятор оптимизируется, вы можете сделать это:

#include <stdio.h>
#include <string.h>

void strcatO(char a[], char b[])
{
   size_t i = strlen(a);
   size_t j;

    for (j = 0; b[j] != 0; ++j)
    {
        a[i++] = b[j];
    }

    a[i] = 0;

    printf("%s\n", a);
}

int main()
{
  char a[20] = "aze";
  char b[] = "rtyu";
  strcatO(a,b);
  return 0;
}

Исполнение :

azertyu

Обратите внимание, что char a[] для параметра равно char *, без указателей равно false ;-)


и указать на проблемы в вашем коде по просьбе Эрика Постпишила:

  • a[strlen(a) + i + 1] пишет 1 символ после нужной позиции, должно быть a[strlen(a) + 1] = 0; a[strlen(a)] = b[j];. В некотором смысле это шанс, что вы напишете больше после конца, потому что strlen вернет не начальную длину a, а неопределенное значение из-за вероятного отсутствия нуля символ в остальной части a
  • после копии, которую вы пропустите, чтобы добавить нулевой символ
person bruno    schedule 26.01.2019
comment
Ваш ответ великолепен, спасибо!! еще не узнал о size_t, поэтому я не совсем понимаю, но я понял, что вы сделали, спасибо - person Lidor Cohen; 26.01.2019
comment
@LidorCohen size_t — это тип значения, возвращаемого strlen и другими функциями, см. stackoverflow.com/questions/2550774/what-is-size-t-in-c - person bruno; 26.01.2019
comment
В этом ответе не указано, что было не так с исходным кодом или почему новый код исправляет это. - person Eric Postpischil; 26.01.2019
comment
@EricPostpischil Да, но первоначальный код был настолько плохим, что я думал, что он бесполезен ^^ - person bruno; 26.01.2019
comment
@EricPostpischil, вы правы, я склонен думать, что скрыть этот код, который я не вижу из-за определенного уровня проблемы, давая другой код. - person bruno; 26.01.2019
comment
Давайте продолжим обсуждение в чате. - person chux - Reinstate Monica; 26.01.2019

Эта строка:

a[strlen(a) + i + 1] = b[i];

пишет символы на одну позицию дальше, чем вы хотите.

При вызове в вашем примере ваша процедура передается a и b со следующим содержимым:

a[0] = 'e'
a[1] = 'g'
a[2] = 'g'
a[3] = 0

b[0] = 's'
b[1] = 'a'
b[2] = 'm'
b[3] = 'p'
b[4] = 'l'
b[5] = 'e'
b[6] = 0

Вы хотите получить этот результат:

a[0] = 'e'
a[1] = 'g'
a[2] = 'g'
a[3] = 's'
a[4] = 'a'
a[5] = 'm'
a[6] = 'p'
a[7] = 'l'
a[8] = 'e'
a[9] = 0

Однако, поскольку ваш код записывает в a[strlen(a) + i + 1], он записывает первый символ в a[strlen(a) + 0 + 1], то есть a[4]. Вы хотите это в a[3]. Вы можете изменить strlen(a) + i + 1 на strlen(a) + i, но тогда, когда вы напишете первый символ, вы перезапишете завершающий нуль, и strlen больше не будет работать для определения длины. Чтобы исправить это, вы можете запомнить длину a перед входом в цикл. Рассмотрим этот код:

int i = 0;
int LengthOfA = strlen(a);
for (i = 0; i < strlen(b); ++i)
{
    a[LengthOfA + i] = b[i];
}

Это запишет символы в правильное место.

Однако в конце a не ставится нулевой завершающий символ. Для этого мы можем поместить еще один оператор после цикла:

a[LengthOfA + i] = 0;

В этот момент ваша рутина будет работать для обычных ситуаций. Однако мы можем сделать еще два улучшения.

Во-первых, вместо использования int для длин и индексов мы можем использовать size_t. В C ширина int является гибкой, а size_t предоставляется как хороший тип для использования при работе с размерами объектов. Чтобы использовать его, сначала используйте #include <stddef.h>, чтобы получить его определение. Тогда ваш код может быть:

size_t i = 0;
size_t LengthOfA = strlen(a);
for (i = 0; i < strlen(b); ++i)
{
    a[LengthOfA + i] = b[i];
}
a[LengthOfA + i] = 0;

Во-вторых, ваш код номинально вычисляет strlen(b) на каждой итерации. Это расточительно. Длину желательно вычислить один раз и запомнить:

size_t i = 0;
size_t LengthOfA = strlen(a);
size_t LengthOfB = strlen(b);
for (i = 0; i < LengthOfB; ++i)
{
    a[LengthOfA + i] = b[i];
}
a[LengthOfA + i] = 0;
person Eric Postpischil    schedule 26.01.2019

Вы не перезаписываете первый разделитель строки null(\0)

    a[strlen(a) + i + 1] = b[i];

должно быть

int len = strlen(a);

for(i = 0; i < strlen(b); ++i)
{
    a[len + i] = b[i];
}
a[len+i] = '\0'; //Finally null terminate the new string.
person kiran Biradar    schedule 26.01.2019
comment
!a Это новый вывод, общий размер массива 'a' равен 25 - person Lidor Cohen; 26.01.2019
comment
Учитывая, что OP написал свой собственный strlen(), этот код страдает тем же самым O(N*N) неэффективность. Нет необходимости повторно вызывать strlen(b). - person chux - Reinstate Monica; 26.01.2019
comment
В этом ответе не указано, что было не так с исходным кодом или почему новый код исправляет это. - person Eric Postpischil; 26.01.2019
comment
@EricPostpischil Это действительно указывает на ключевую проблему: вы не перезаписываете первую строку null, что, по крайней мере, является частичным объяснением. - person chux - Reinstate Monica; 26.01.2019