Почему я не могу присвоить значения указателям? - PullRequest
2 голосов
/ 15 марта 2009

После прочтения часто задаваемых вопросов и всего остального, что я могу найти, я все еще в замешательстве. Если у меня есть указатель на символ, который инициализируется таким образом:

char *s = "Hello world!"

Строка находится в постоянной памяти, и я не могу изменить ее следующим образом:

*s = 'W';

сделать "Мир Wello!" Это я понимаю, но я не могу, на всю жизнь, понять, как сделать это НЕ доступным только для чтения. Должен ли я использовать массив вместо указателя? Как здесь ?

Это мой код:

char *s = str;
char *e = s;
while (*e != '\0')
e++;
e--;
char *temp;
//Swop the string around
while (s <= e) {
    *temp = *s;
    *s = *e;
    *e = *temp;
    e--;
    s++;
}

Сообщение об ошибке является просто ошибкой сегментации. Заранее извиняюсь, если это действительно глупый вопрос.

Большое спасибо за помощь. Приняв все ваши советы, я получаю следующее:

void something(char * str) {
    char *store = str;
    char *s = new char[strlen(str) + 1]; //Allocate memory. Nice one.
    strcpy(s, str);
    char *e = new char[strlen(str) + 1];
    strcpy(e, str);
    while (*e != '\0')
        e++;
    e--;
    char temp; //no longer a pointer
    while (s <= e) {
        cout << *e;
        temp = *s;
        *s = *e;
        *e = temp;
        e--;
        s++;

    }
    delete [] e;
    delete [] s;        
}

однако удаление в конце функции, похоже, вызывает собственные ошибки сегментации. Зачем?

Ради интереса: ошибки возникли из-за доступа к указателям e и s после их увеличения. Из этого вытекает гораздо более простое решение:

void something(char * str) {
    char *s = new char[strlen(str) + 1];
    strcpy(s, str);
    char temp;
    int j = strlen(str) - 1;
    for (int i = 0; i <= strlen(str)/2; i++) {
        cout << s << endl;
        temp = s[i];
        s[i] = s[j];
        s[j] = temp;
        j--;
    }
    delete [] s;
}

Ответы [ 6 ]

7 голосов
/ 15 марта 2009

Попробуйте:

char src[] = "Hello world";
src[6]     = 'W';

-- // or

char   buffer[] = "Hello world";
char*  src      = buffer;
src[6]          = 'W';

Если вы хотите скопировать строку в буфер, используйте strcpy () или strncpy ()

char   buffer[20];
char const* s = "Hello World"

strcpy(s,buffer);

Если вы должны написать свою собственную строковую копию, она должна выглядеть следующим образом:

char   buffer[20];
char const* s = "Hello World";

// OK this is not the perfect solution but it is easy to read.
for(int loop = 0;s[loop] != '\0';++loop)
{
    buffer[loop] = s[loop];
}
buffer[loop] = '\0';
6 голосов
/ 15 марта 2009

Самый простой способ изменить его - создать массив для вашего хранилища, а затем скопировать в него строку.

Например:

char buf[128];
const char *src = "Hello World";
strncpy(buf, src, 127); // one less - we always 0-terminate
buf[127] = '\0';

// you can now modify buf
buf[0] = 'W';

Причина, по которой ваш код не работает, заключается в том, что вы не выделили память для копии строки - вы только что сделали второй указатель на ту же постоянную память. (А потом попытался скопировать его? Я не совсем уверен, что делает остальная часть кода.) Вам нужно где-то получить не-постоянную память, и гораздо проще использовать стандартную библиотеку, чтобы скопировать ее в эта новая память, а не писать цикл самостоятельно.

В случае, когда вы заранее не знаете длину строки, вы также можете использовать malloc (или, что еще лучше, сделать то, что говорит ответ drschnz, и использовать new char [] ):

const char *src = "Hello world";
char *buf = malloc(strlen(src) + 1);   // or = new char[strlen(src) + 1];
strcpy(buf, src);
// you can now modify buf
// later, you need to free it
free(buf);                             // or delete [] buf;

Кроме того, если вы используете C ++, вы можете просто использовать std :: string:

std::string myString("Hello world");
myString[0] = "W";

Надеюсь, это поможет.

2 голосов
/ 15 марта 2009

Указатель не только для чтения. (сами строковые данные есть, но указатель, указывающий на них, может быть свободно изменен). Однако назначение символа указателю не дает ожидаемого результата.

В общем, единственное, что вы можете назначить точке - это адрес. Вы не можете назначать значения, только адрес значений.

Строковые литералы (например, "hello world") являются единственным исключением, потому что строки являются специальными. Если вы назначите один из них указателю, вы получите указатель на эту строку. Но в целом вы назначаете адреса указателям.

Другой момент заключается в том, что символы в C ++ являются целочисленными типами данных. Они могут рассматриваться как целые числа без использования приведения. Я могу сделать int i = 'W', и компилятор не будет жаловаться.

Так что же произойдет, если вы назначите 'W' указателю? Он принимает «W» в качестве целочисленного значения и предполагает, что это адрес. «W» имеет значение ASCII 127, поэтому вы фактически устанавливаете указатель на указатель на адрес 127, что не имеет смысла.

Я не понимаю, как это связано с вашим кодом. Кажется, проблема в том, что temp не указывает на действительные данные. Вы объявляете указатель, который указывает на какой-то неопределенный адрес. И затем вы говорите: «Где бы это ни указывало, я хочу записать значение, на которое указывает s. Следующее должно работать несколько лучше:

char temp; // not a pointer. We want a character to store our temporary value in
while (s <= e) {
    temp = *s; // note, no * on temp.
    *s = *e;
    *e = temp; // note, no * on temp.
    e--;
    s++;
}

Однако, если str указывает на строковый литерал, например «hello world», то это будет недопустимо, поскольку сами строковые данные доступны только для чтения. Компилятор может не применять его, но тогда вы рискуете попасть в страну неопределенного поведения. Если вы хотите изменить строку, скопируйте ее в локальный буфер, как показал один из других ответов.

Вы, кажется, немного озадачены семантикой указателей. Присвоение адреса (или чего-то, что может быть преобразовано в адрес, например целое число) указателю делает указатель указателем на этот адрес. Он не изменяет указанные данные. Объявление указателя не означает, что он будет указывать на что-либо значимое. Если вы хотите сохранить char, объявите переменную char. Указатель не хранит данные, он просто указывает на данные, размещенные в другом месте.

редактировать Комментарии и исправления к вашему обновленному коду:

void something(const char * str) { // let the function take a pointer to a non-modifiable string, so add the const. Now it's clear that we're not allowed to modify the string itself, so we have to make a copy.
    char *s = new char[strlen(str) + 1]; // Since the original string is const, we have to allocate a copy if we want to modify it - in C, you'd use malloc(strlen(str)) instead
    strcpy(s, str);
    char *e = s; // make e point to the start of the copied string (don't allocate two copies, since e and s are supposed to work on the same string
    while (*e != '\0') { // add braces so it's clear where the loop starts and ends.
        e++;
    }
    e--;

    while (s <= e) { // the loop condition wouldn't work if s and e pointed to separate copies of the string
        cout << *e; // why? I thought you just wanted to reverse the string in memory. Alternatively, if you just want to print out the string reversed, you don't need to do most of the rest of the loop body. In C, you'd use printf instead of *e
        char temp = *s; // might as well declare the temp variable when you need it, and not before
        *s = *e;
        *e = temp;
        e--;
        s++;

    }
}

Просто для справки, и в ответ на комментарии о C против C ++, вот как я написал бы функцию для обратного преобразования строки в C ++:

std::string revert_string(const std::string& str) {
  return std::string(str.rbegin(), str.rend());
}

Или чтобы вернуть строку на место:

std::string revert_string(const std::string& str) {
  std::reverse(str.begin(), str.end());
}
1 голос
/ 15 марта 2009

Технически, то, что у вас есть, более правильно написано так:

const char *s = "Hello world!"

То, что вы действительно хотите иметь, это что-то вроде этого:

char s[] = "Hello world!"

Следующие несколько строк могут помочь вам понять больше:

const char *p = "Hello World";
char q[] = "Hello World";
printf("%d %d", sizeof(p), sizeof(q));
// p[0] = 'W' // INVALID
q[0] = 'W'; // valid
0 голосов
/ 15 марта 2009

Есть # функция "strdup ()" для создания копии строки ... она гарантирует, что вы не забудете "+1" в вашем malloc.

char* source = "Hello World";
char* dest = strdup(source);
0 голосов
/ 15 марта 2009

Ваши удаления генерируют ошибки, потому что вы изменили указатель. Вы должны сохранить исходное местоположение нового и удалить [] его. Вы пытаетесь удалить местоположение, которого нет в таблице размещения. Если вы хотите изменить значение указателя, сделайте еще один char * temp = t; и использовать его для перебора строки.

...