Арифметика указателей на массивах строкового типа, как C ++ справляется с этим? - PullRequest
1 голос
/ 17 июня 2009

Я изучаю указатели, и меня беспокоит одна концепция. Я понимаю, что если у вас есть указатель (например, «указатель1») типа INT, который указывает на массив, вы можете заполнить этот массив INTS. Если вы хотите обратиться к члену массива, вы можете использовать указатель, и вы можете сделать pointer1 ++; пройти через массив. Программа знает, что это массив INT, поэтому она знает, как выполнить шаги размера INT. Но что, если массив состоит из строк, длина которых может варьироваться. Откуда он знает, что делать, когда вы пытаетесь увеличить с ++, поскольку каждый элемент имеет разную длину?

Точно так же, когда вы создаете вектор строк и используете ключевое слово Reserve, как он узнает, сколько резервировать, если строки могут быть разной длины? Это, вероятно, действительно очевидно, но я не могу понять это, и это не вписывается в мои нынешние (вероятно, неправильные) взгляды на указатели. Спасибо

Ответы [ 5 ]

10 голосов
/ 17 июня 2009

Довольно просто.

Массив строк отличается от вектора строк.

Массив строк (указатели в стиле C) - это массив указателей на массив символов "char **". Таким образом, каждый элемент в массиве строк имеет размер «Pointer-to-char-array», поэтому он может без проблем проходить по элементам в массиве строк. Указатели в массиве могут указывать на куски памяти разного размера.

С вектором строк это массив строковых объектов (стиль C ++). Каждый строковый объект имеет одинаковый размер object , но где-то содержит указатель на фрагмент памяти, где на самом деле хранится содержимое строки. Таким образом, в этом случае элементы вектора также идентичны по размеру, хотя и отличаются от «просто указателя на массив символов», что позволяет выполнять простое вычисление адреса элемента.

6 голосов
/ 17 июня 2009

Это потому, что строка (по крайней мере, в C / C ++) не совсем то же самое, что и целое число. Если мы говорим о строках в стиле C, то их массив типа

char* test[3] = { "foo", "bar", "baz" };

что на самом деле происходит под капотом, так это то, что «тест» - это массив указателей, каждый из которых указывает на фактические данные, где находятся символы. Допустим, случайным образом, что «тестовый» массив начинается с адреса памяти 0x10000, и что указатели имеют длину четыре байта, тогда у нас может быть

test[0] (memory location 0x10000) contains 0x10020
test[1] (memory location 0x10004) contains 0x10074
test[2] (memory location 0x10008) contains 0x10320

Тогда мы могли бы взглянуть на области памяти вокруг 0x10020, мы бы нашли фактические данные о символах:

test[0][0] (memory location 0x10020) contains 'f'
test[0][1] (memory location 0x10021) contains 'o'
test[0][2] (memory location 0x10022) contains 'o'
test[0][3] (memory location 0x10023) contains '\0'

и около области памяти 0x10074

test[1][0] (memory location 0x10074) contains 'b'
test[1][1] (memory location 0x10075) contains 'a'
test[1][2] (memory location 0x10076) contains 'r'
test[1][3] (memory location 0x10077) contains '\0'

С объектами C ++ std :: string происходит то же самое: настоящий строковый объект C ++ не «содержит» символы, потому что, как вы говорите, строки имеют переменную длину. На самом деле он содержит указатель на символы. (По крайней мере, в простой реализации std :: string это имеет более сложную структуру, обеспечивающую лучшее использование памяти и производительность).

0 голосов
/ 17 июня 2009

В C ++ массивы и векторы всегда содержат элементы фиксированного размера. Строки соответствуют этому условию, потому что ваши строковые элементы являются либо указателями на завершенные нулем c-строки (char *), хранящиеся где-то еще, либо обычными объектами std :: string.

Объект std :: string имеет постоянный размер, фактические строковые данные размещаются где-то еще (кроме небольшой строковой оптимизации, но это уже другая история).

vector<string> a;
a.resize( 2 ); // allocate memory for 2 strings of any length.

vector<char *> b;
b.resize( 2 ); // allocate memory for 2 string pointers.

vector<char> c; // one string. Should use std::string instead.
c.resize( 2 ); // allocate memory for 2 characters (including or not the terminator).

Обратите внимание, что функция reserve () в std :: vector просто подготавливает вектор к росту. Он используется в основном для оптимизации. Вы, вероятно, хотите использовать resize ().

0 голосов
/ 17 июня 2009

Это может показаться педантизмом, но на языке стреляющих ног, таких как C ++, это важно: в исходном вопросе вы говорите:

вы можете сделать pointer1 ++; пройти через массив.

Постинкремент (pointer1 ++) обычно семантически неправильный здесь, потому что это означает «увеличить указатель1, но сохранить значение выражения в исходном значении указателя1». Если вам не нужно исходное значение указателя 1, используйте вместо него прединкремент (++ pointer1), который семантически точно означает значение «увеличить указатель на единицу».

По какой-то причине большинство учебников по С ++ повсеместно занимаются постинкрементами, обучая новым С ++ - вредным привычкам; -)

0 голосов
/ 17 июня 2009

Массив строк - это массив указателей на первый символ некоторых строк. Размер указателя на символ, вероятно, совпадает с размером указателя на int.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...