Использование строки в библиотеке CS50 - PullRequest
0 голосов
/ 02 сентября 2018

Привет всем, у меня есть вопрос, касающийся передачи строки в функцию в C. Я использую библиотеку CS50, и я знаю, что они передают строку как массив символов (указатель на начало массива), поэтому передача выполняется ссылка. Моя функция получает массив в качестве аргумента и возвращает массив. Когда я изменяю, например, один из элементов массива в функции, это изменение отражается в исходной строке, как я и ожидал. Но если я назначу новую строку аргументу, функция вернет другую строку, а исходная строка не изменится. Можете ли вы объяснить механику этого поведения?

#include <stdlib.h>
#include <cs50.h>
#include <stdio.h>


string test(string s);

int main(void)
{
    string text = get_string("Text: ");
    string new_text = test(text);
    printf("newtext: %s\n %s\n", text, new_text);
    printf("\n");
    return 0;
}

string test(string s)
{
    //s[0] = 'A';
    s = "Bla";
    return s;
}

Первый пример отражает изменение первой буквы как в текстовых строках, так и в строках newtext, но во втором примере печатается текст без изменений, а newtext выводится как «Bla» Спасибо!

Ответы [ 2 ]

0 голосов
/ 03 сентября 2018

Это займет некоторое время.

Давайте начнем с основ. В C string является последовательностью символьных значений, включая терминатор с 0 значениями. IOW, строка "hello" представляется как последовательность {'h', 'e', 'l', 'l', 'o', 0}. Строки хранятся в массивах char (или wchar_t для "широких" строк, о которых мы не будем здесь говорить). Сюда входят строковые литералы , такие как "Bla" - они хранятся в массивах char, так что они доступны в течение всего жизненного цикла программы.

В большинстве случаев выражение типа "массив N-элементов из T" будет преобразовано ("распад") в выражение типа "указатель на T", поэтому большинство времени, когда мы имеем дело со строками, мы на самом деле имеем дело с выражениями типа char *. Однако это не означает, что выражение типа char * представляет собой строку - char * может указывать на первый символ строки или на первый символ в последовательности, которая не является строкой (без терминатора) или может указывать на один символ, который не является частью большой последовательности.

A char * также может указывать на начало динамически распределяемого буфера , который был выделен malloc, calloc или realloc.

Следует также отметить, что оператор индекса [] определен в терминах арифметики указателей - выражение a[i] определено как *(a + i) - с учетом значения адреса a (преобразованного из типа массива, как описано выше), смещение i элементов ( не байтов ) от этого адреса и разыменование результата.

Еще одна важная вещь, которую следует отметить, это то, что = не определен для копирования содержимого одного массива в другой. Фактически, выражение массива не может быть целью оператора =.

Тип CS50 string на самом деле является typedef (псевдоним) для типа char *. Функция get_string() выполняет много магии за кулисами, чтобы динамически распределять и управлять памятью для содержимого строки, и делает обработку строки в C на гораздо более высоком уровне, чем на самом деле. Я и несколько других людей считаем, что это плохой способ учить С, по крайней мере, в отношении струн. Не поймите меня неправильно, это чрезвычайно полезная утилита, просто если у вас нет cs50.h и вам нужно начать выполнять свою собственную обработку строк, вы окажетесь в море какое-то время.

Итак, что все эти глупости связаны с вашим кодом? В частности, линия

s = "Bla";

Происходит следующее: вместо копирования содержимого строкового литерала "Bla" в память, на которую указывает s, адрес строкового литерала записывается в s, перезаписывая предыдущее значение указателя. Вы не можете использовать оператор = для копирования содержимого одной строки в другую; вместо этого вам придется использовать библиотечную функцию, такую ​​как strcpy:

strcpy( s, "Bla" );

Причина, по которой s[0] = A работал так, как вы ожидали, заключается в том, что оператор индекса [] определен в терминах арифметики указателей. Выражение a[i] оценивается как *(a + i) - с учетом адреса a (либо указатель, либо выражение массива, которое "распалось" на указатель, как описано выше), смещение i элементов ( не байт! ) с этого адреса и разыменовывает результат. Таким образом, s[0] указывает на первый элемент строки, которую вы прочитали.

0 голосов
/ 02 сентября 2018

Сложно ответить правильно без примера кода. Я сделаю один, но он может не соответствовать тому, что вы делаете.

Давайте возьмем эту функцию C:

char* edit_string(char *s) {
    if(s) {
        size_t len = strlen(s);
        if(len > 4) {
            s[4] = 'X';
        }
    }
    return s;
}

Эта функция будет принимать указатель на массив символов , и если указатель не равен NULL и массив с нулевым символом в конце длиннее 4 символов, он заменит пятый символ в индексе 4 с «X». В C. нет 1010 * ссылок . Их всегда называют указателями . Это одно и то же, и вы получаете доступ к указанному значению с помощью оператора разыменования *p или с синтаксисом массива, например p[0].

Теперь эта функция:

char* edit_string(char *s) {
    if(s) {
        size_t len = strlen(s);
        if(len > 4) {
            char *new_s = malloc(len+1);
            strcpy(new_s, s);
            new_s[4] = 'X';
            return new_s;
        }
    }
    s = malloc(1);
    s[0] = '\0';
    return s;
}

Эта функция возвращает указатель на вновь выделенную копию исходного массива символов или вновь выделенную пустую строку. (При этом вызывающий абонент всегда может распечатать его и вызвать free для результата.)

Не изменяет исходный массив символов, поскольку new_s не указывает на исходный массив символов.

Теперь вы также можете сделать это:

const char* edit_string(char *s) {
    if(s) {
        size_t len = strlen(s);
        if(len > 4) {
            return "string was longer than 4";
        }
    }
    s = "string was not longer than 4";
    return s;
}

Обратите внимание, что я изменил тип возвращаемого значения на const char*, потому что строковый литерал, такой как "string was longer than 4", является константой. Попытка изменить его приведет к сбою программы.

Выполнение присваивания s внутри функции не приводит к изменению массива символов, который s использовал для указания на . Указатель s указывает на исходный массив символов или ссылается на него, а затем после s = "string" указывает на массив символов "string".

...