2010-07-01 8 views
13

Lo siento, mi C/C++ no es tan bueno, pero el siguiente código parece basura incluso para mí. También tiene un error: falla cuando str = "07/02/2010" termina por '\ 0' -. Creo que, en lugar de corregir un error, podría ser reescrito. En Python es solo 'kas\nhjkfh kjsdjkasf'.split(). Sé que este es el código C-ish, ¡pero no puede ser tan complicado dividir una cadena! Manteniéndome en la misma firma, y ​​sin utilizar bibliotecas adicionales, ¿cómo puedo mejorarlo, hacerlo corto y dulce? Puedo decir que este código huele, por ejemplo debido a la cláusula else todo el camino al final.Una mejor manera de dividir una cadena en una matriz de cadenas en C/C++ utilizando espacios en blanco como un delimitador

línea que falla:

_tcsncpy_s(
    s.GetBuffer((int) (nIndex-nLast)), 
    nIndex-nLast, 
    psz+nLast, 
    (size_t) (nIndex-nLast) 
); 

con la cadena "07/02/2010" terminado por '\ 0' que tratará de escribir 11 caracteres en una memoria intermedia que está a sólo 10 caracteres de longitud.

FUNCIÓN COMPLETA:

#define 

// This will return the text string as a string array 
// This function is called from SetControlText to parse the 
// text string into an array of CStrings that the control 
// Gadgets will attempt to interpret 

BOOL CLVGridDateTimeCtrl::ParseTextWithCurrentFormat(const CString& str, const CGXStyle* pOldStyle, CStringArray& strArray) 
{ 
    // Unused: 
    pOldStyle; 

    // we assume that the significant segments are seperated by space 

    // Please change m_strDelim to add other delimiters 

    CString s; 

    LPCTSTR psz = (LPCTSTR) str; 

    BOOL bLastCharSpace = FALSE; 
    DWORD size = str.GetLength()+1; 

    // (newline will start a new row, tab delimiter will 
    // move to the next column). 
    // parse buffer (DBCS aware) 
    for (DWORD nIndex = 0, nLast = 0; nIndex < size; nIndex += _tclen(psz+nIndex)) 
    { 
     // check for a delimiter 
     if (psz[nIndex] == _T('\0') || _tcschr(_T("\r\n"), psz[nIndex]) || _tcschr(_T(" "), psz[nIndex]) 
      ||!_tcscspn(&psz[nIndex], (LPCTSTR)m_strDelim)) 
     { 
      s.ReleaseBuffer(); 
      s.Empty(); 
      // abort parsing the string if next char 
      // is an end-of-string 
      if (psz[nIndex] == _T('\0')) 
      { 
       if (psz[nIndex] == _T('\r') && psz[nIndex+1] == _T('\n')) 
        nIndex++; 

       _tcsncpy_s(s.GetBuffer((int) (nIndex-nLast)), 
        nIndex-nLast, 
          psz+nLast, 
          (size_t) (nIndex-nLast)); 
       CString temStr = s; 
       strArray.Add(temStr); 
       temStr.Empty(); 
       break; 
      } 

      else if (_tcscspn(&psz[nIndex], (LPCTSTR)m_strDelim) == 0 && !bLastCharSpace) 
      { 
       if (psz[nIndex] == _T('\r') && psz[nIndex+1] == _T('\n')) 
        nIndex++; 

       _tcsncpy_s(s.GetBuffer((int) (nIndex-nLast)), 
        nIndex-nLast, 
          psz+nLast, 
          (size_t) (nIndex-nLast)); 
       CString temStr = s; 
       strArray.Add(temStr); 
       temStr.Empty(); 
       bLastCharSpace = TRUE; 
       // abort parsing the string if next char 
       // is an end-of-string 
       if (psz[nIndex+1] == _T('\0')) 
        break; 

      } 
      // Now, that the value has been copied to the cell, 
      // let's check if we should jump to a new row. 
      else if (_tcschr(_T(" "), psz[nIndex]) && !bLastCharSpace) 
      { 
       if (psz[nIndex] == _T('\r') && psz[nIndex+1] == _T('\n')) 
        nIndex++; 

       _tcsncpy_s(s.GetBuffer((int) (nIndex-nLast)), 
        nIndex-nLast, 
          psz+nLast, 
          (size_t) (nIndex-nLast)); 
       CString temStr = s; 
       strArray.Add(temStr); 
       temStr.Empty(); 
       bLastCharSpace = TRUE; 
       // abort parsing the string if next char 
       // is an end-of-string 
       if (psz[nIndex+1] == _T('\0')) 
        break; 
      } 

      nLast = nIndex + _tclen(psz+nIndex); 


     } 
     else 
     { 
      // nLast = nIndex + _tclen(psz+nIndex); 
      bLastCharSpace = FALSE; 
     } 
    } 
    if (strArray.GetSize()) 
     return TRUE; 
    else 
     return FALSE; 
} 

EDIT: m_strDelim = _T(","); y esta variable miembro se utiliza sólo en esta función. Supongo que ahora veo el punto de la tokenización: trata de analizar una fecha y una hora ... ¡Espera, hay más! Aquí está el código que llama a esta función a continuación. Por favor, ayúdame a mejorar esto también. Algunos de mis colegas afirman que C# los hace no más productivos que C++. Solía ​​sentirme como un idiota por no poder decir lo mismo de mí.

// SetControlText will attempt to convert the text to a valid date first with 
// the help of COleDateTime and then with the help of the Date control and the 
// current format 

BOOL CLVGridDateTimeCtrl::ConvertControlTextToValue(CString& str, ROWCOL nRow, ROWCOL nCol, const CGXStyle* pOldStyle) 
{ 
    CGXStyle* pStyle = NULL; 
    BOOL bSuccess = FALSE; 

    if (pOldStyle == NULL) 
    { 
     pStyle = Grid()->CreateStyle(); 
     Grid()->ComposeStyleRowCol(nRow, nCol, pStyle); 
     pOldStyle = pStyle; 
    } 

    // allow only valid input 
    { 
     // First do this 
     CLVDateTime dt; 

     if (str.IsEmpty()) 
     { 
      ; 
      // if (Grid()->IsCurrentCell(nRow, nCol)) 
      // Reset(); 
      bSuccess = TRUE; 
     } 
     else if (dt.ParseDateTime(str,CLVGlobals::IsUSDateFormat()) && (DATE) dt != 0) 
     { 
      SetDateTime(dt); 
      if (m_bDateValueAsNumber) 
       str.Format(_T("%g"), (DATE) dt); 
      else 
       str = dt.Format(); 
      bSuccess = TRUE; 
     } 
     else 
     { 
      // parse the string using the current format 
      CStringArray strArray; 
      if (!ParseTextWithCurrentFormat(str, pOldStyle, strArray)) 
       return FALSE; 

      UpdateNullStatus(m_TextCtrlWnd); 

      SetFormat(m_TextCtrlWnd, *pOldStyle); 

      int nArrIndex = 0; 
      for(int i=0; i<m_TextCtrlWnd.m_gadgets.GetSize(); i++) 
      { 
       int val = m_TextCtrlWnd.m_gadgets[i]->GetValue(); 
       // s.Empty(); 
       if(m_TextCtrlWnd.m_gadgets[i]->IsKindOf(RUNTIME_CLASS(SECDTNumericGadget))) 
       { 
        // TRACE(_T("The value %s\n"), strArray[nArrIndex]); 
        ((CLVDTNumericGadget*)m_TextCtrlWnd.m_gadgets[i])->m_nNewValue = _ttoi(strArray[nArrIndex]);  
        nArrIndex++; 
        if (nArrIndex>strArray.GetUpperBound()) 
          break; 
       } 
       else if(m_TextCtrlWnd.m_gadgets[i]->IsKindOf(RUNTIME_CLASS(SECDTListGadget)) && val!=-1) 
       { 
        int nIndex = ((CLVDTListGadget*)m_TextCtrlWnd.m_gadgets[i])->FindMatch(strArray[nArrIndex], ((CLVDTListGadget*)m_TextCtrlWnd.m_gadgets[i])->GetValue()+1); 
        if (nIndex!=-1) 
        { 
         // TRACE(_T("The value %s\n"), strArray[nArrIndex]); 
         ((CLVDTListGadget*)m_TextCtrlWnd.m_gadgets[i])->SetValue(nIndex); 
         nArrIndex++; 
         if (nArrIndex>strArray.GetUpperBound()) 
          break; 
        } 

       } 

       CLVDBValue dbDate = m_TextCtrlWnd.GetDateTime(); 
       if (dbDate.IsNull()) 
        str = _T(""); 
       else 
       { 
        CLVDateTime dt = (CLVDateTime)dbDate; 
        if (m_bDateValueAsNumber) 
         str.Format(_T("%g"), (DATE) dt); 
        else 
         str = dt.Format(); 
       } 
      } 
      bSuccess = TRUE; 
     } 
    } 

    if (pStyle) 
     Grid()->RecycleStyle(pStyle); 

    return bSuccess; 
} 

Respuesta

4

La mejor manera de hacerlo sería utilizar strtok. Ese enlace debe explicarse por sí mismo sobre cómo usarlo, y también puede usar múltiples delimitadores. Muy útil función C

+1

+1, pero estoy seguro de que alguien tendrá una loca solución de C++ que incluye una sintaxis bizarra para quitarte tus votos ganadores. –

+1

Esta es una buena solución para C. Si desea usar C++, no hay nada "loco" en codificar las fortalezas del idioma. – Cogwheel

+11

Si 'strtok' es la respuesta correcta, generalmente ha hecho una pregunta incorrecta. –

6

En C++, es probablemente más fácil de utilizar un stsringstream:

std::istringstream buffer("kas\nhjkfh kjsdjkasf"); 

std::vector<std::string> strings; 

std::copy(std::istream_iterator<std::string>(buffer), 
      std::istream_iterator<std::string>(), 
      std::back_inserter(strings)); 

no han tratado de atenerse a exactamente la misma firma, sobre todo porque la mayor parte es no estándar, por lo que no hace aplicar a C++ en general.

Otra posibilidad sería usar Boost::tokenizer, aunque obviamente implica otra biblioteca, por lo que no intentaré cubrirla con más detalle.

No estoy seguro de si eso califica como "sintaxis bizarra" o no. Voy a tener que trabajar un poco en esa parte ...

Editar: lo tengo - inicializar el vector en su lugar:

La parte "bizarro" es que sin los paréntesis adicionales alrededor el primer argumento, esto invocaría el "análisis más irritante", por lo que declararía una función en lugar de definir un vector. :-)

Edit2: en lo que respecta a la edición de la pregunta, parece casi imposible responder directamente: depende de demasiados tipos (por ejemplo, CGXStyle, CLVDateTime) que no son ni estándar ni explicados. Yo, por mi parte, no puedo seguirlo con ningún detalle. A simple vista, esto parece un diseño bastante pobre, permitiendo al usuario ingresar cosas que son más o menos ambiguas, y luego tratar de resolver el problema. Es mejor usar un control que solo permita entradas inequívocas para comenzar, y usted puede simplemente leer algunos campos que contienen una fecha y hora directamente.

Edit3: código para hacer la división que también trata comas como separadores podrían hacerse así:

#include <iostream> 
#include <locale> 
#include <algorithm> 
#include <vector> 
#include <sstream> 

class my_ctype : public std::ctype<char> { 
public: 
    mask const *get_table() { 
     // this copies the "classic" table used by <ctype.h>: 
     static std::vector<std::ctype<char>::mask> 
      table(classic_table(), classic_table()+table_size); 

     // Anything we want to separate tokens, we mark its spot in the table as 'space'. 
     table[','] = (mask)space; 

     // and return a pointer to the table: 
     return &table[0]; 
    } 
    my_ctype(size_t refs=0) : std::ctype<char>(get_table(), false, refs) { } 
}; 

int main() { 
    // put our data in a strea: 
    std::istringstream buffer("first kas\nhjkfh kjsdjk,asf\tlast"); 

    // Create a ctype object and tell the stream to use it for parsing tokens: 
    my_ctype parser; 
    buffer.imbue(std::locale(std::locale(), &parser)); 

    // separate the stream into tokens: 
    std::vector<std::string> strings(
     (std::istream_iterator<std::string>(buffer)), 
     std::istream_iterator<std::string>()); 

    // copy the tokes to cout so we can see what we got: 
    std::copy(strings.begin(), strings.end(), 
     std::ostream_iterator<std::string>(std::cout, "\n")); 
    return 0; 
} 
+0

Genial, utilizamos VS2010 para compilar esto, por lo que 'Boost' es un poco exagerado, pero estoy seguro de que hay muchas bibliotecas disponibles. –

+8

Espera, Jerry, ¿dónde especifico la lista de caracteres para tokenizar? Ver respuesta de Beh Tou Cheh arriba, por ejemplo. él tiene: 'strtk :: parse (data,", \ r \ n ", str_list);'. –

+1

Están especificados en la 'locale' utilizada por la transmisión. De manera predeterminada, solo será un espacio en blanco, pero puede crear una configuración regional con una 'faceta ctype' que use lo que desee. http://stackoverflow.com/questions/1894886/parsing-a-comma-delimited-stdstring/1895584#1895584 –

0

cadenas de análisis sintáctico en C/C++ rara vez resulta ser un asunto sencillo. El método que publica parece que tiene un poco de "historia" involucrada en él. Por ejemplo, declara que desea dividir la cadena en espacios en blanco. Pero el método en sí parece estar usando una variable miembro m_strDelim como parte de la decisión de división. Simplemente reemplazar el método podría conducir a otros problemas inesperados.

Usar una clase de tokenización existente such as this Boost library podría simplificar bastante las cosas.

+3

"El análisis de cadenas en C/C++ rara vez resulta ser una cuestión simple". Es una cuestión sencilla, pero las soluciones como el análisis sintáctico suelen implicar el procesamiento de cadenas carácter por carácter y bucles. Es decir. No puedo pensar en algo (estándar) como la función split() de Python en C++ .... – SigTerm

1

Una manera muy superior para resolver este problema es utilizar las bibliotecas Qt. Si está usando KDE, ya están instalados. La clase QString tiene una función de división de miembros que funciona como la versión de Python. Por ejemplo

QString("This is a string").split(" ", QString::SkipEmptyParts) 

devuelve un QStringList de QString s:

["This", "is", "a", "string"] 

(en la sintaxis Pythonic). Tenga en cuenta que se requiere el segundo argumento o si las palabras se dividen por espacios múltiples, cada uno individual sería devuelto.

En general encuentro con la ayuda de las bibliotecas de Qt, la mayor parte de la simplicidad de python, por ejemplo. el análisis simple de cadenas y la iteración de listas se pueden manejar con facilidad y con la potencia de C++.

+4

Somos una tienda de MSFT, así que puedo usar cualquier libs estándar que venga con VS2010. Me gusta Linux, código abierto, etc., pero no puedo simplemente instalar bibliotecas arbitrarias. Podría robar un par de archivos .h y .cpp aunque si una licencia lo permite. –

13

El String Toolkit Library (Strtk) tiene la siguiente solución a su problema:

#include <string> 
#include <deque> 
#include "strtk.hpp" 
int main() 
{ 
    std::string data("kas\nhjkfh kjsdjkasf"); 
    std::deque<std::string> str_list; 
    strtk::parse(data, ", \r\n", str_list); 
    return 0; 
} 

Más ejemplos se pueden encontrar Here

+0

Hm ... Me pregunto si puedo robar solo un par de encabezados y archivos cpp sin instalar todo. –

+18

@Hamish: Siéntase libre de tomar lo que quiera, todo bajo CPL. Si no tiene Boost, o simplemente usa C++ vano con STL, puede comentar #define ENABLE_LEXICAL_CAST #define ENABLE_RANDOM #define ENABLE_REGEX, y todo debería funcionar, todo se explica en el archivo readme.txt. –

0

Puede utilizar boost::algorithm::split. Es decir .:

std::string myString; 
std::vector<std::string> splitStrings; 
boost::algorithm::split(splitStrings, myString, boost::is_any_of(" \r\n")); 
+0

+1 ¡La biblioteca de Algoritmos Boost String es esencial para las operaciones de cadena! –

+0

Realmente me gustaría saber por qué las personas estaban bajando esto. –

+0

No he votado negativamente. Supongo que es por mi comentario (publicación no original) que prefiero usar libs estándar solamente ... –

0

Un mejor método que mi otra respuesta: TR1's característica regex. Here's un pequeño tutorial para comenzar. Esta respuesta es C++, usa expresiones regulares (que es quizás la mejor/más fácil manera de dividir una cadena), y la usé yo mismo recientemente, así que sé que es una buena herramienta.

Cuestiones relacionadas