Как работать с нулевыми указателями в std :: vector - PullRequest
2 голосов
/ 26 сентября 2011

Скажем, у меня есть вектор нулевых завершающих строк, некоторые из которых могут быть нулевыми указателями.Я не знаю, даже если это законно.Это учебное упражнение.
Пример кода

std::vector<char*> c_strings1;
char* p1 = "Stack Over Flow";
c_strings1.push_back(p1);
p1 = NULL; // I am puzzled you can do this and what exactly is stored at this memory location
c_strings1.push_back(p1);
p1 = "Answer";
c_strings1.push_back(p1);
for(std::vector<char*>::size_type i = 0; i < c_strings1.size(); ++i)
{
  if( c_strings1[i] != 0 )
  {
    cout << c_strings1[i] << endl;
  }
}

Обратите внимание, что размер вектора равен 3, хотя у меня NULL в местоположении c_strings1[1]
Вопрос .Как вы можете переписать этот код, используя std::vector<char> Что именно хранится в векторе, когда вы нажимаете нулевое значение?

РЕДАКТИРОВАТЬ
На первую часть моего вопроса дан полный ответ, но не на вторую.По крайней мере, не в моей статистике.Я делаю хочу увидеть использование vector<char>;не какой-то вложенный вариант или std::vector<std::string> те знакомы.Итак, вот что я попробовал (подсказка: это не работает)

std::vector<char> c_strings2;
string s = "Stack Over Flow";
c_strings2.insert(c_strings2.end(), s.begin(), s.end() );
//  char* p = NULL; 
s = ""; // this is not really NULL, But would want a NULL here
c_strings2.insert(c_strings2.end(), s.begin(), s.end() );
s = "Answer";
c_strings2.insert(c_strings2.end(), s.begin(), s.end() );

const char *cs = &c_strings2[0];
while (cs <= &c_strings2[2]) 
{
  std::cout << cs << "\n";
  cs += std::strlen(cs) + 1;
}

Ответы [ 6 ]

4 голосов
/ 26 сентября 2011

У вас нет vector строк - у вас есть vector указателя на символ. NULL - это совершенно правильный указатель на символ, который ничего не указывает, поэтому он хранится в векторе.

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

Нет смысла смешивать стиль C ++ vector с указателями в стиле C. Это не противозаконно, но смешивание таких парадигм часто приводит к запутанному и испорченному коду.

Вместо использования vector<char*> или vector<char>, почему бы не использовать vector<string>?

EDIT

Исходя из ваших правок, кажется, что вы пытаетесь сгладить несколько строк в один vector<char>, с NULL-терминатором между каждой из сплющенных строк.

Вот простой способ сделать это:

#include <algorithm>
#include <vector>
#include <string>
#include <iterator>
using namespace std;

int main()
{
    // create a vector of strings...
    typedef vector<string> Strings;
    Strings c_strings;

    c_strings.push_back("Stack Over Flow");
    c_strings.push_back("");
    c_strings.push_back("Answer");

    /* Flatten the strings in to a vector of char, with 
        a NULL terminator between each string

        So the vector will end up looking like this:

        S t a c k _ O v e r _ F l o w \0 \0 A n s w e r \0

    ***********************************************************/

    vector<char> chars;
    for( Strings::const_iterator s = c_strings.begin(); s != c_strings.end(); ++s )
    {
        // append this string to the vector<char>
        copy( s->begin(), s->end(), back_inserter(chars) );
        // append a null-terminator
        chars.push_back('\0');
    }
}
2 голосов
/ 26 сентября 2011

Итак,

char *p1 = "Stack Over Flow";
char *p2 = NULL;
char *p3 = "Answer";

Если вы заметили, тип всех трех из них точно такой же.Все они char *.Из-за этого мы ожидаем, что все они имеют одинаковый размер в памяти.

Вы можете подумать, что для них не имеет смысла иметь одинаковый размер в памяти, потому что p3 короче, чемp1.На самом деле происходит то, что компилятор во время компиляции найдет все строки в программе.В этом случае он найдет "Stack Over Flow" и "Answer".Он бросит их в какое-то постоянное место в памяти, о котором он знает.Затем, когда вы пытаетесь сказать, что p3 = "Answer", компилятор фактически преобразует это в что-то вроде p3 = 0x123456A0.

Следовательно, с любой версией вызова push_back вы только толкаете в вектор указательне сама строка.

Сам вектор не знает или не заботится о том, что NULL char * - пустая строка.Таким образом, при подсчете он видит, что вы поместили в него три указателя, поэтому он сообщает о размере 3.

0 голосов
/ 26 сентября 2011

У меня странное чувство, что вам действительно нужно, чтобы вектор содержал что-то вроде "Stack Over Flow Answer" (возможно, без пробела до "Answer").

В этом случае вы можете использовать std::vector<char>, вам просто нужно передать целые массивы, а не только указатели на них.

Этого нельзя достичь с помощью push_back, однако vector имеет метод insert, который принимает диапазоны.

/// Maintain the invariant that the vector shall be null terminated
/// p shall be either null or point to a null terminated string
void push_back(std::vector<char>& v, char const* p) {
  if (p) {
    v.insert(v.end(), p, p + strlen(p));
  }

  v.push_back('\0');
} // push_back

int main() {
  std::vector<char> v;

  push_back(v, "Stack Over Flow");
  push_back(v, 0);
  push_back(v, "Answer");

  for (size_t i = 0, max = v.size(); i < max; i += strlen(&v[i]) + 1) {
    std::cout << &v[i] << "\n";
  }
}

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

0 голосов
/ 26 сентября 2011

Вы должны быть осторожны при хранении указателей в контейнерах STL - копирование контейнеров приводит к мелкому копированию и тому подобному.

Что касается вашего конкретного вопроса, вектор будет хранить указатель типа char * независимо от того, указывает ли этот указатель на что-либо. Вполне возможно, что по какой-то причине вы захотите сохранить нулевой указатель типа char * внутри этого вектора - например, что если вы решите удалить эту строку символов в более поздней точке из вектора? Векторы поддерживают только амортизированное постоянное время для push_back и pop_back, поэтому есть хороший шанс, если вы удалите строку внутри этого вектора (но не в конце), что вы бы предпочли просто установить ее в ноль быстро и сэкономить время.

Двигаясь дальше - я бы предложил создать std :: vector>, если вам нужен динамический массив строк, который выглядит так, как вы собираетесь.

Std :: vector, как вы упомянули, будет бесполезен по сравнению с вашим исходным кодом, потому что ваш исходный код хранит динамический массив строк, а std :: vector будет содержать только одну динамически изменяемую строку (так как строка является массивом символы по существу).

0 голосов
/ 26 сентября 2011

NULL равно 0. Указатель со значением 0 имеет значение. Но символ со значением 0 имеет другое значение . Он используется в качестве разделителя, чтобы показать конец строки. Поэтому, если вы используете std::vector<char> и push_back 0, вектор будет содержать символ со значением 0. vector<char> - это вектор символов, а std::vector<char*> - это вектор строк в стиле C - очень разные вещи ,

Обновление. Как хочет ОП, я даю представление о том, как хранить (в векторе) строки с нулевым символом в конце, некоторые из которых являются нулевыми.

Вариант 1 : Предположим, у нас есть vector<char> c_strings;. Затем мы определяем функцию для хранения строки pi. Введена большая сложность, поскольку мы должны различать пустую строку и нулевой символ *. Мы выбираем символ-разделитель, который не встречается в нашем использовании. Предположим, это символ «~».

char delimiter = '~';
// push each character in pi into c_strings
void push_into_vec(vector<char>& c_strings, char* pi) {
  if(pi != 0) {
    for(char* p=pi; *p!='\0'; p++) 
      c_strings.push_back(*p);
    // also add a NUL character to denote end-of-string
    c_strings.push_back('\0');
  }
  c_strings.push_back(deimiter);
  // Note that a NULL pointer would be stored as a single '~' character
  // while an empty string would be stored as '\0~'.
}

// now a method to retrieve each of the stored strings. 
vector<char*> get_stored_strings(const vector<char>& c_strings) {
  vector<char*> r;
  char* end = &c_strings[0] + c_strings.size();
  char* current = 0;
  bool nullstring = true; 
  for(char* c = current = &c_strings[0]; c != end+1; c++) {
    if(*c == '\0') {
      int size = c - current - 1;
      char* nc = new char[size+1];
      strncpy(nc, current, size);  
      r.push_back(nc);     
      nullstring = false; 
    }
    if(*c == delimiter) {
      if(nullstring) r.push_back(0);     
      nullstring = true; // reset nullstring for the next string
      current = c+1; // set the next string
    }
  }
  return r;
}

Вам все еще нужно вызвать delete[] в памяти, выделенной на new[] выше. Все эти сложности решаются с помощью класса string. Я очень редко использую char* в C ++.

Вариант 2: Вы можете использовать vector<boost::optional<char> >. Тогда '~' может быть заменено пустым boost :: option, но другие другие части такие же, как и в варианте 1. Но использование памяти в этом случае будет выше.

0 голосов
/ 26 сентября 2011

Что именно сохраняется в векторе, когда вы нажимаете нулевое значение?

A NULL.Вы храните указатели, и NULL является возможным значением для указателя.Почему это неожиданно в любом случае?

Кроме того, используйте std::string в качестве типа значения (т. Е. std::vector<std::string>), char* не следует использовать, если это не требуется для взаимодействия с C.Чтобы скопировать код с помощью std::vector<char>, вам потребуется std::vector<std::vector<char>>.

...