How to convert wchar_t* to char*

本文介绍了一种在C++中实现宽字符(wchar_t)与窄字符(char)相互转换的方法。通过使用标准库中的ctype facet,实现了字符串的widening(窄到宽)和narrowing(宽到窄)转换。示例展示了如何将ASCII字符串转换为宽字符,并将宽字符字符串转换回窄字符。

根据上面代码再结合下面代码写出完整的文本文档管理 // // MODULE: TextDocument.cpp // // PURPOSE: Basic implementation of a text data-sequence class // // NOTES: www.catch22.net // #define STRICT #define WIN32_LEAN_AND_MEAN #include <windows.h> #include "TextDocument.h" #include "TextView.h" #include "Unicode.h" struct _BOM_LOOKUP BOMLOOK[] = { // define longest headers first { 0x0000FEFF, 4, NCP_UTF32 }, { 0xFFFE0000, 4, NCP_UTF32BE }, { 0xBFBBEF, 3, NCP_UTF8 }, { 0xFFFE, 2, NCP_UTF16BE }, { 0xFEFF, 2, NCP_UTF16 }, { 0, 0, NCP_ASCII }, }; // // TextDocument constructor // TextDocument::TextDocument() { // buffer = 0; m_nDocLength_bytes = 0; m_nDocLength_chars = 0; m_pLineBuf_byte = 0; m_pLineBuf_char = 0; m_nNumLines = 0; m_nFileFormat = NCP_ASCII; m_nHeaderSize = 0; } // // TextDocument destructor // TextDocument::~TextDocument() { clear(); } // // Initialize the TextDocument with the specified file // bool TextDocument::init(TCHAR *filename) { HANDLE hFile; hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) return false; return init(hFile); } // // Initialize using a file-handle // bool TextDocument::init(HANDLE hFile) { ULONG numread; char *buffer; if((m_nDocLength_bytes = GetFileSize(hFile, 0)) == 0) return false; // allocate new file-buffer if((buffer = new char[m_nDocLength_bytes]) == 0) return false; // read entire file into memory ReadFile(hFile, buffer, m_nDocLength_bytes, &numread, 0); m_seq.init((BYTE *)buffer, m_nDocLength_bytes); // try to detect if this is an ascii/unicode/utf8 file m_nFileFormat = detect_file_format(&m_nHeaderSize); // work out where each line of text starts if(!init_linebuffer()) clear(); CloseHandle(hFile); delete[] buffer; return true; } // Initialize the TextDocument with the specified file // /*bool TextDocument::save(TCHAR *filename) { HANDLE hFile; hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) return false; CloseHandle(hFile); return true; }*/ // // Parse the file lo // // // From the unicode.org FAQ: // // 00 00 FE FF UTF-32, big-endian // FF FE 00 00 UTF-32, little-endian // FE FF UTF-16, big-endian // FF FE UTF-16, little-endian // EF BB BF UTF-8 // // Match the first x bytes of the file against the // Byte-Order-Mark (BOM) lookup table // int TextDocument::detect_file_format(int *m_nHeaderSize) { BYTE header[4] = { 0 }; m_seq.render(0, header, 4); for(int i = 0; BOMLOOK[i].len; i++) { if(m_nDocLength_bytes >= BOMLOOK[i].len && memcmp(header, &BOMLOOK[i].bom, BOMLOOK[i].len) == 0) { *m_nHeaderSize = BOMLOOK[i].len; return BOMLOOK[i].type; } } *m_nHeaderSize = 0; return NCP_ASCII; // default to ASCII } // // Empty the data-TextDocument // bool TextDocument::clear() { m_seq.clear(); m_nDocLength_bytes = 0; if(m_pLineBuf_byte) { delete[] m_pLineBuf_byte; m_pLineBuf_byte = 0; } if(m_pLineBuf_char) { delete[] m_pLineBuf_char; m_pLineBuf_char = 0; } m_nNumLines = 0; return true; } bool TextDocument::EmptyDoc() { clear(); m_seq.init(); // this is not robust. it's just to get the thing // up-and-running until I write a proper line-buffer mananger m_pLineBuf_byte = new ULONG[0x1000]; m_pLineBuf_char = new ULONG[0x1000]; m_pLineBuf_byte[0] = 0; m_pLineBuf_char[0] = 0; return true; } // // Return a UTF-32 character value // int TextDocument::getchar(ULONG offset, ULONG lenbytes, ULONG *pch32) { // BYTE *rawdata = (BYTE *)(buffer + offset + m_nHeaderSize); BYTE rawdata[16]; lenbytes = min(16, lenbytes); m_seq.render(offset+ m_nHeaderSize, rawdata, lenbytes); #ifdef UNICODE UTF16 *rawdata_w = (UTF16 *)rawdata;//(WCHAR*)(buffer + offset + m_nHeaderSize); WCHAR ch16; size_t ch32len = 1; switch(m_nFileFormat) { case NCP_ASCII: MultiByteToWideChar(CP_ACP, 0, (CCHAR*)rawdata, 1, &ch16, 1); *pch32 = ch16; return 1; case NCP_UTF16: return utf16_to_utf32(rawdata_w, lenbytes / 2, pch32, &ch32len) * sizeof(WCHAR); case NCP_UTF16BE: return utf16be_to_utf32(rawdata_w, lenbytes / 2, pch32, &ch32len) * sizeof(WCHAR); case NCP_UTF8: return utf8_to_utf32(rawdata, lenbytes, pch32); default: return 0; } #else *pch32 = (ULONG)(BYTE)rawdata[0]; return 1; #endif } // // Fetch a buffer of UTF-16 text from the specified byte offset - // returns the number of characters stored in buf // // Depending on how Neatpad was compiled (UNICODE vs ANSI) this function // will always return text in the "native" format - i.e. Unicode or Ansi - // so the necessary conversions will take place here. // // TODO: make sure the CR/LF is always fetched in one go // make sure utf-16 surrogates kept together // make sure that combining chars kept together // make sure that bidirectional text kep together (will be *hard*) // // offset - BYTE offset within underlying data sequence // lenbytes - max number of bytes to process (i.e. to limit to a line) // buf - UTF16/ASCII output buffer // plen - [in] - length of buffer, [out] - number of code-units stored // // returns - number of bytes processed // ULONG TextDocument::gettext(ULONG offset, ULONG lenbytes, TCHAR *buf, ULONG *buflen) { // BYTE *rawdata = (BYTE *)(buffer + offset + m_nHeaderSize); ULONG chars_copied = 0; ULONG bytes_processed = 0; if(offset >= m_nDocLength_bytes) { *buflen = 0; return 0; } while(lenbytes > 0 && *buflen > 0) { BYTE rawdata[0x100]; size_t rawlen = min(lenbytes, 0x100); // get next block of data from the piece-table m_seq.render(offset + m_nHeaderSize, rawdata, rawlen); // convert to UTF-16 size_t tmplen = *buflen; rawlen = rawdata_to_utf16(rawdata, rawlen, buf, &tmplen); lenbytes -= rawlen; offset += rawlen; bytes_processed += rawlen; buf += tmplen; *buflen -= tmplen; chars_copied += tmplen; } *buflen = chars_copied; return bytes_processed; //ULONG remaining = lenbytes; //int charbuflen = *buflen; //while(remaining) /* { lenbytes = min(lenbytes, sizeof(rawdata)); m_seq.render(offset + m_nHeaderSize, rawdata, lenbytes); #ifdef UNICODE switch(m_nFileFormat) { // convert from ANSI->UNICODE case NCP_ASCII: return ascii_to_utf16(rawdata, lenbytes, buf, (size_t*)buflen); case NCP_UTF8: return utf8_to_utf16(rawdata, lenbytes, buf, (size_t*)buflen); // already unicode, do a straight memory copy case NCP_UTF16: return copy_utf16((WCHAR*)rawdata, lenbytes/sizeof(WCHAR), buf, (size_t*)buflen); // need to convert from big-endian to little-endian case NCP_UTF16BE: return swap_utf16((WCHAR*)rawdata, lenbytes/sizeof(WCHAR), buf, (size_t*)buflen); // error! we should *never* reach this point default: *buflen = 0; return 0; } #else switch(m_nFileFormat) { // we are already an ASCII app, so do a straight memory copy case NCP_ASCII: int len; len = min(*buflen, lenbytes); memcpy(buf, rawdata, len); *buflen = len; return len; // anything else is an error - we cannot support Unicode or multibyte // character sets with a plain ASCII app. default: *buflen = 0; return 0; } #endif // remaining -= lenbytes; // buf += lenbytes; // offset += lenbytes; }*/ } ULONG TextDocument::getdata(ULONG offset, BYTE *buf, size_t len) { //memcpy(buf, buffer + offset + m_nHeaderSize, len); m_seq.render(offset + m_nHeaderSize, buf, len); return len; } // // Initialize the line-buffer // // With Unicode a newline sequence is defined as any of the following: // // \u000A | \u000B | \u000C | \u000D | \u0085 | \u2028 | \u2029 | \u000D\u000A // bool TextDocument::init_linebuffer() { ULONG offset_bytes = 0; ULONG offset_chars = 0; ULONG linestart_bytes = 0; ULONG linestart_chars = 0; ULONG bytes_left = m_nDocLength_bytes - m_nHeaderSize; ULONG buflen = m_nDocLength_bytes - m_nHeaderSize; // allocate the line-buffer for storing each line's BYTE offset if((m_pLineBuf_byte = new ULONG[buflen+1]) == 0) return false; // allocate the line-buffer for storing each line's CHARACTER offset if((m_pLineBuf_char = new ULONG[buflen+1]) == 0) return false; m_nNumLines = 0; // loop through every byte in the file for(offset_bytes = 0; offset_bytes < buflen; ) { ULONG ch32; // get a UTF-32 character from the underlying file format. // this needs serious thought. Currently ULONG len = getchar(offset_bytes, buflen - offset_bytes, &ch32); offset_bytes += len; offset_chars += 1; if(ch32 == '\r') { // record where the line starts m_pLineBuf_byte[m_nNumLines] = linestart_bytes; m_pLineBuf_char[m_nNumLines] = linestart_chars; linestart_bytes = offset_bytes; linestart_chars = offset_chars; // look ahead to next char len = getchar(offset_bytes, buflen - offset_bytes, &ch32); offset_bytes += len; offset_chars += 1; // carriage-return / line-feed combination if(ch32 == '\n') { linestart_bytes = offset_bytes; linestart_chars = offset_chars; } m_nNumLines++; } else if(ch32 == '\n' || ch32 == '\x0b' || ch32 == '\x0c' || ch32 == 0x0085 || ch32 == 0x2029 || ch32 == 0x2028) { // record where the line starts m_pLineBuf_byte[m_nNumLines] = linestart_bytes; m_pLineBuf_char[m_nNumLines] = linestart_chars; linestart_bytes = offset_bytes; linestart_chars = offset_chars; m_nNumLines++; } // force a 'hard break' else if(offset_chars - linestart_chars > 128) { m_pLineBuf_byte[m_nNumLines] = linestart_bytes; m_pLineBuf_char[m_nNumLines] = linestart_chars; linestart_bytes = offset_bytes; linestart_chars = offset_chars; m_nNumLines++; } } if(buflen > 0) { m_pLineBuf_byte[m_nNumLines] = linestart_bytes; m_pLineBuf_char[m_nNumLines] = linestart_chars; m_nNumLines++; } m_pLineBuf_byte[m_nNumLines] = buflen; m_pLineBuf_char[m_nNumLines] = offset_chars; return true; } // // Return the number of lines // ULONG TextDocument::linecount() { return m_nNumLines; } // // Return the length of longest line // ULONG TextDocument::longestline(int tabwidth) { //ULONG i; ULONG longest = 0; ULONG xpos = 0; // char *bufptr = (char *)(buffer + m_nHeaderSize); /* for(i = 0; i < length_bytes; i++) { if(bufptr[i] == '\r') { if(bufptr[i+1] == '\n') i++; longest = max(longest, xpos); xpos = 0; } else if(bufptr[i] == '\n') { longest = max(longest, xpos); xpos = 0; } else if(bufptr[i] == '\t') { xpos += tabwidth - (xpos % tabwidth); } else { xpos ++; } } longest = max(longest, xpos);*/ return 100;//longest; } // // Return information about specified line // bool TextDocument::lineinfo_from_lineno(ULONG lineno, ULONG *lineoff_chars, ULONG *linelen_chars, ULONG *lineoff_bytes, ULONG *linelen_bytes) { if(lineno < m_nNumLines) { if(linelen_chars) *linelen_chars = m_pLineBuf_char[lineno+1] - m_pLineBuf_char[lineno]; if(lineoff_chars) *lineoff_chars = m_pLineBuf_char[lineno]; if(linelen_bytes) *linelen_bytes = m_pLineBuf_byte[lineno+1] - m_pLineBuf_byte[lineno]; if(lineoff_bytes) *lineoff_bytes = m_pLineBuf_byte[lineno]; return true; } else { return false; } } // // Perform a reverse lookup - file-offset to line number // bool TextDocument::lineinfo_from_offset(ULONG offset_chars, ULONG *lineno, ULONG *lineoff_chars, ULONG *linelen_chars, ULONG *lineoff_bytes, ULONG *linelen_bytes) { ULONG low = 0; ULONG high = m_nNumLines-1; ULONG line = 0; if(m_nNumLines == 0) { if(lineno) *lineno = 0; if(lineoff_chars) *lineoff_chars = 0; if(linelen_chars) *linelen_chars = 0; if(lineoff_bytes) *lineoff_bytes = 0; if(linelen_bytes) *linelen_bytes = 0; return false; } while(low <= high) { line = (high + low) / 2; if(offset_chars >= m_pLineBuf_char[line] && offset_chars < m_pLineBuf_char[line+1]) { break; } else if(offset_chars < m_pLineBuf_char[line]) { high = line-1; } else { low = line+1; } } if(lineno) *lineno = line; if(lineoff_bytes) *lineoff_bytes = m_pLineBuf_byte[line]; if(linelen_bytes) *linelen_bytes = m_pLineBuf_byte[line+1] - m_pLineBuf_byte[line]; if(lineoff_chars) *lineoff_chars = m_pLineBuf_char[line]; if(linelen_chars) *linelen_chars = m_pLineBuf_char[line+1] - m_pLineBuf_char[line]; return true; } int TextDocument::getformat() { return m_nFileFormat; } ULONG TextDocument::size() { return m_nDocLength_bytes; } TextIterator TextDocument::iterate(ULONG offset_chars) { ULONG off_bytes = charoffset_to_byteoffset(offset_chars); ULONG len_bytes = m_nDocLength_bytes - off_bytes; //if(!lineinfo_from_offset(offset_chars, 0, linelen, &offset_bytes, &length_bytes)) // return TextIterator(); return TextIterator(off_bytes, len_bytes, this); } // // // TextIterator TextDocument::iterate_line(ULONG lineno, ULONG *linestart, ULONG *linelen) { ULONG offset_bytes; ULONG length_bytes; if(!lineinfo_from_lineno(lineno, linestart, linelen, &offset_bytes, &length_bytes)) return TextIterator(); return TextIterator(offset_bytes, length_bytes, this); } TextIterator TextDocument::iterate_line_offset(ULONG offset_chars, ULONG *lineno, ULONG *linestart) { ULONG offset_bytes; ULONG length_bytes; if(!lineinfo_from_offset(offset_chars, lineno, linestart, 0, &offset_bytes, &length_bytes)) return TextIterator(); return TextIterator(offset_bytes, length_bytes, this); } ULONG TextDocument::lineno_from_offset(ULONG offset) { ULONG lineno = 0; lineinfo_from_offset(offset, &lineno, 0, 0, 0, 0); return lineno; } ULONG TextDocument::offset_from_lineno(ULONG lineno) { ULONG lineoff = 0; lineinfo_from_lineno(lineno, &lineoff, 0, 0, 0); return lineoff; } // // Retrieve an entire line of text // ULONG TextDocument::getline(ULONG nLineNo, TCHAR *buf, ULONG buflen, ULONG *off_chars) { ULONG offset_bytes; ULONG length_bytes; ULONG offset_chars; ULONG length_chars; if(!lineinfo_from_lineno(nLineNo, &offset_chars, &length_chars, &offset_bytes, &length_bytes)) { *off_chars = 0; return 0; } gettext(offset_bytes, length_bytes, buf, &buflen); *off_chars = offset_chars; return buflen; } // // Convert the RAW buffer in underlying file-format to UTF-16 // // // utf16len - [in/out] on input holds size of utf16str buffer, // on output holds number of utf16 characters stored // // returns bytes processed from rawdata // size_t TextDocument::rawdata_to_utf16(BYTE *rawdata, size_t rawlen, TCHAR *utf16str, size_t *utf16len) { switch(m_nFileFormat) { // convert from ANSI->UNICODE case NCP_ASCII: return ascii_to_utf16(rawdata, rawlen, (UTF16 *)utf16str, utf16len); case NCP_UTF8: return utf8_to_utf16(rawdata, rawlen, (UTF16 *)utf16str, utf16len); // already unicode, do a straight memory copy case NCP_UTF16: rawlen /= sizeof(TCHAR); return copy_utf16((UTF16 *)rawdata, rawlen, (UTF16 *)utf16str, utf16len) * sizeof(TCHAR); // need to convert from big-endian to little-endian case NCP_UTF16BE: rawlen /= sizeof(TCHAR); return swap_utf16((UTF16 *)rawdata, rawlen, (UTF16 *)utf16str, utf16len) * sizeof(TCHAR); // error! we should *never* reach this point default: *utf16len = 0; return 0; } } // // Converts specified UTF16 string to the underlying RAW format of the text-document // (i.e. UTF-16 -> UTF-8 // UTF-16 -> UTF-32 etc) // // returns number of WCHARs processed from utf16str // size_t TextDocument::utf16_to_rawdata(TCHAR *utf16str, size_t utf16len, BYTE *rawdata, size_t *rawlen) { switch(m_nFileFormat) { // convert from UTF16 -> ASCII case NCP_ASCII: return utf16_to_ascii((UTF16 *)utf16str, utf16len, rawdata, rawlen); // convert from UTF16 -> UTF8 case NCP_UTF8: return utf16_to_utf8((UTF16 *)utf16str, utf16len, rawdata, rawlen); // already unicode, do a straight memory copy case NCP_UTF16: *rawlen /= sizeof(TCHAR); utf16len = copy_utf16((UTF16 *)utf16str, utf16len, (UTF16 *)rawdata, rawlen); *rawlen *= sizeof(TCHAR); return utf16len; // need to convert from big-endian to little-endian case NCP_UTF16BE: *rawlen /= sizeof(TCHAR); utf16len = swap_utf16((UTF16 *)utf16str, utf16len, (UTF16 *)rawdata, rawlen); *rawlen *= sizeof(TCHAR); return utf16len; // error! we should *never* reach this point default: *rawlen = 0; return 0; } } // // Insert UTF-16 text at specified BYTE offset // // returns number of BYTEs stored // ULONG TextDocument::insert_raw(ULONG offset_bytes, TCHAR *text, ULONG length) { BYTE buf[0x100]; ULONG buflen; ULONG copied; ULONG rawlen = 0; ULONG offset = offset_bytes+ m_nHeaderSize; while(length) { buflen = 0x100; copied = utf16_to_rawdata(text, length, buf, (size_t *)&buflen); // do the piece-table insertion! if(!m_seq.insert(offset, buf, buflen)) break; text += copied; length -= copied; rawlen += buflen; offset += buflen; } m_nDocLength_bytes = m_seq.size(); return rawlen; } ULONG TextDocument::replace_raw(ULONG offset_bytes, TCHAR *text, ULONG length, ULONG erase_chars) { BYTE buf[0x100]; ULONG buflen; ULONG copied; ULONG rawlen = 0; ULONG offset = offset_bytes + m_nHeaderSize; ULONG erase_bytes = count_chars(offset_bytes, erase_chars); while(length) { buflen = 0x100; copied = utf16_to_rawdata(text, length, buf, (size_t *)&buflen); // do the piece-table replacement! if(!m_seq.replace(offset, buf, buflen, erase_bytes)) break; text += copied; length -= copied; rawlen += buflen; offset += buflen; erase_bytes = 0; } m_nDocLength_bytes = m_seq.size(); return rawlen; } // // Erase is a little different. Need to work out how many // bytes the specified number of UTF16 characters takes up // ULONG TextDocument::erase_raw(ULONG offset_bytes, ULONG length) { /*TCHAR buf[0x100]; ULONG buflen; ULONG bytes; ULONG erase_bytes = 0; ULONG erase_offset = offset_bytes; while(length) { buflen = min(0x100, length); bytes = gettext(offset_bytes, 0x100, buf, &buflen); erase_bytes += bytes; offset_bytes += bytes; length -= buflen; } // do the piece-table deletion! if(m_seq.erase(erase_offset + m_nHeaderSize, erase_bytes)) { m_nDocLength_bytes = m_seq.size(); return length; }*/ ULONG erase_bytes = count_chars(offset_bytes, length); if(m_seq.erase(offset_bytes + m_nHeaderSize, erase_bytes)) { m_nDocLength_bytes = m_seq.size(); return length; } return 0; } // // return number of bytes comprising 'length_chars' characters // in the underlying raw file // ULONG TextDocument::count_chars(ULONG offset_bytes, ULONG length_chars) { switch(m_nFileFormat) { case NCP_ASCII: return length_chars; case NCP_UTF16: case NCP_UTF16BE: return length_chars * sizeof(WCHAR); default: break; } ULONG offset_start = offset_bytes; while(length_chars && offset_bytes < m_nDocLength_bytes) { TCHAR buf[0x100]; ULONG charlen = min(length_chars, 0x100); ULONG bytelen; bytelen = gettext(offset_bytes, m_nDocLength_bytes - offset_bytes, buf, &charlen); length_chars -= charlen; offset_bytes += bytelen; } return offset_bytes - offset_start; } ULONG TextDocument::byteoffset_to_charoffset(ULONG offset_bytes) { switch(m_nFileFormat) { case NCP_ASCII: return offset_bytes; case NCP_UTF16: case NCP_UTF16BE: return offset_bytes / sizeof(WCHAR); case NCP_UTF8: case NCP_UTF32: case NCP_UTF32BE: // bug bug! need to implement this. default: break; } return 0; } ULONG TextDocument::charoffset_to_byteoffset(ULONG offset_chars) { switch(m_nFileFormat) { case NCP_ASCII: return offset_chars; case NCP_UTF16: case NCP_UTF16BE: return offset_chars * sizeof(WCHAR); case NCP_UTF8: case NCP_UTF32: case NCP_UTF32BE: default: break; } ULONG lineoff_chars; ULONG lineoff_bytes; if(lineinfo_from_offset(offset_chars, 0, &lineoff_chars, 0, &lineoff_bytes, 0)) { return count_chars(lineoff_bytes, offset_chars - lineoff_chars) + lineoff_bytes; } else { return 0; } } // // Insert text at specified character-offset // ULONG TextDocument::insert_text(ULONG offset_chars, TCHAR *text, ULONG length) { ULONG offset_bytes = charoffset_to_byteoffset(offset_chars); return insert_raw(offset_bytes, text, length); } // // Overwrite text at specified character-offset // ULONG TextDocument::replace_text(ULONG offset_chars, TCHAR *text, ULONG length, ULONG erase_len) { ULONG offset_bytes = charoffset_to_byteoffset(offset_chars); return replace_raw(offset_bytes, text, length, erase_len); } // // Erase text at specified character-offset // ULONG TextDocument::erase_text(ULONG offset_chars, ULONG length) { ULONG offset_bytes = charoffset_to_byteoffset(offset_chars); return erase_raw(offset_bytes, length); } bool TextDocument::Undo(ULONG *offset_start, ULONG *offset_end) { ULONG start, length; if(!m_seq.undo()) return false; start = m_seq.event_index() - m_nHeaderSize; length = m_seq.event_length(); *offset_start = byteoffset_to_charoffset(start); *offset_end = byteoffset_to_charoffset(start+length); m_nDocLength_bytes = m_seq.size(); return true; } bool TextDocument::Redo(ULONG *offset_start, ULONG *offset_end) { ULONG start, length; if(!m_seq.redo()) return false; start = m_seq.event_index() - m_nHeaderSize; length = m_seq.event_length(); *offset_start = byteoffset_to_charoffset(start); *offset_end = byteoffset_to_charoffset(start+length); m_nDocLength_bytes = m_seq.size(); return true; }
07-02
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值