C++中的字符串类型

1.字符()表示分类

C++提供了两种字符串的表示形式,即C风格的字符串和标准字符串。 C++引入的是string 类类型,但实际上在许多程序的情形中我们有必要理解和使用老式的C 风格字符串。总共包括以下几种类型:

a.char 

b.wchar_t

c.TCHAR

c.LPSTR

d.LPCSTR

e.LPTSTR

f.LPCTSTR

g.Cstring

h.string

i.BSTR

2.C风格字符串

C 风格的字符串起源于C 语言并在C++中继续得到支持,实际上在标准C++之前除了第三方字符串库类之外它是惟一一种被支持的字符串。字符串被存储在一个字符数组中,一般通过一个char*类型的指针来操纵它,标准C 库为操纵C 风格的字符串提供了一组函数例如:
//
返回字符串的长度
int strlen( const char* );
//
比较两个字符串是否相等
int strcmp( const char*, const char* );
//
把第二个字符串拷贝到第一个字符串中
char* strcpy(char*, const char* );
标准C 库作为标准的C++的一部分被包含在其中为使用这些函数我们必须包含相关的C 头文件
#include <cstring>
指向C 风格字符串的字符指针总是指向一个相关联的字符数组,即使当我们写一个字符串常量时,如:
const char *st = "The expense of spirit\n";
系统在内部也把字符串常量存储在一个字符串数组中,然后st 指向该数组的第一个元素。那么我们怎样以字符串的形式来操纵st 呢?
一般地我们用指针的算术运算来遍历C 风格的字符串,每次指针增加1 直到到达终止空字符为止例如:
while ( *st++ ) { ... }
char*
类型的指针被解除引用并且测试指向的字符是true 还是false true 值是除了空字符外的任意字符(空字符在判断语句中被莫认为是假)++是增加运算符它使指针指向数组中的下一个字符。
一般来说当我们使用一个指针时在解除指针的引用之前测试它是否指向某个对象是必要的,否则程序很可能会失败例如:
int
string_length( const char *st )
{
int cnt = 0;
if ( st )
while ( *st++ )
++cnt; return cnt;
}

 

3C++字符串类型
要使用string 类型必须先包含相关的头文件
#include <string>

例如下面是上一小节定义的字符数组
#include<string>
string st( "The expense of spiritn" );

st
的长度由size()操作返回不包含终止空字符
cout <<"The size of "<< st<< " is " <<st.size()<< " characters, including the newlinen";
string 构造函数的第二种形式定义了一个空字符串,例如
string st2; // 空字符串
我们怎样能保证它是空的当然一种办法是测试size()是否为0
if ( ! st.size() )
// ok:
 
更直接的办法是使用empty()操作
if ( st.empty() )
// ok:

如果字符串中不含有字符则empty()返回布尔常量true ,否则返回false
第三种形式的构造函数用一个string 对象来初始化另一个string 对象,例如
string st3( st );
st3 初始化成st 的一个拷贝,怎样验证呢?等于操作符比较两个string 对象,如果相等则返回true
if ( st == st3 )
//
初始化成功
怎样拷贝一个字符串呢?最简单的办法是使用赋值操作符,例如
st2 = st3; // st3 拷贝到st2
首先将与st2 相关联的字符存储区释放掉,然后再分配足够存储与st3 相关联的字符的存储区。最后将与st3 相关联的字符拷贝到该存储区中。
我们可以使用加操作符+ 或看起来有点怪异的复合赋值操作符+= 。将两个或多
个字符串连接起来。例如:给出两个字符串
string s1("hello, " );
string s2( "worldn" );
我们可以按如下方式将两个字符串连接起来,形成第三个字符串
string s3 = s1 + s2;
如果希望直接将s2 附加在s1 后面那么可使用+= 操作符
s1 += s2;
s1
s2 的初始化包含了一个空格一个逗号以及一个换行,这多少有些不方便,它们的存在限制了对这些string 对象的重用,尽管它满足了眼前的需要。一种替代做法就是混合
使用C 风格的字符串与string 对象。如下所示:
const char *pc =", ";
string s1( "hello" );
string s2( "world" );
string s3 = s1 + pc + s2 + "n";

这种连接策略比较受欢迎,因为它使s1 s2 处于一种更容易被重用的形式。这种方法
能够生效是由于string 类型能够自动将C 风格的字符串转换成string 对象。例如:这使我们可以将一个C 风格的字符串赋给一个string 对象。
string s1;
const char *pc = "a character array";
s1 = pc; // ok
但是反向的转换不能自动执行,对隐式地将string 对象转换成C 风格的字符串。string
类型没有提供支持,例如,
下面试图用s1 初始化str就会在编译时刻失败
char *str = s1; // 编译时刻类型错误
为实现这种转换必须显式地调用名为c_str()的操作
char *str =s1.c_str(); // 几乎是正确的但是还差一点
名字c_str()代表了string 类型与C 风格字符串两种表示法之间的关系。字面意思是给
我一个C 风格的字符串,表示——即指向字符数组起始处的字符指针。
但是这个初始化还是失败了,这次是由于另外一个不同的原因,为了防止字符数组被
程序直接处理,c_str()返回了一个指向常量数组的指针
(下一节将解释常量修饰符const
const char*
str
被定义为非常量指针所以这个赋值被标记为类型违例,正确的初始化如下
const char *str =s1.c_str(); // ok
string
类型支持通过下标操作符访问单个字符,例如在下面的代码段中,字符串中的
所有句号被下划线代替
string str( "fa.disney.com" );
int size = str.size();
for ( int ix = 0; ix < size; ++ix )
if ( str[ ix ] == '.' )
str[ ix ] = '_';

4. 基本类型转换

4.1.区别wchar_t,char,WCHAR

   ANSI
:即 char,可用字符串处理函数:strcat( ),strcpy( ), strlen( )等以str打头的函数。
   UNICODE
wchar_tUnicode字符的数据类型,它实际定义在里:
   typedef unsigned short wchar_t;
  
另外,在头文件中有这样的定义:typedef wchar_t WCHAR; 所以WCHAR实际就是wchar_t
   wchar_t
可用字符串处理函数:wcscat(),wcscpy(),wcslen()等以wcs打头的函数。为了让编译器识别Unicode字符串,必须以在前面加一个“L”,例如: wchar_t *szTest=L"This is aUnicode string.";

4.2.TCHAR

C语言里面提供了 _UNICODE宏(有下划线),在Windows里面提供了UNICODE宏(无下划线),只要定了_UNICODE宏和UNICODE宏,系统就会自动切换到UNICODE版本,否则,系统按照ANSI的方式进行编译和运行。只定义了宏并不能实现自动的转换,他还需要一系列的字符定义支持。
1
TCHAR
如果定义了UNICODE宏则TCHAR被定义为wchar_t
typedef wchar_t TCHAR;
  
否则TCHAR被定义为char typedef char TCHAR;
2
LPTSTR
  
如果定义了UNICODE宏则LPTSTR被定义为LPWSTR
   typedef LPTSTR LPWSTR;
  
否则TCHAR被定义为char typedef LPTSTR LPSTR;
  
说明:在使用字符串常量的时候需要使用_TEXT(“MyStr”)或者_T("")来支持系统的自动转换。

4.3.BSTR

   BSTR
是一个带长度前缀的字符串,主要由操作系统来管理的,所以要用api.主要用来和VB打交道的(VB里的string就是指它)要操作它的API函数有很多.比如SysAllocString,SysFreeString等等.
   vc
里封装它的类如_bstr_t,ATL中的CComBSTR.
  
一个 BSTR 由头部和字符串组成,头部包含了字符串的长度信息,字符串中可以包含嵌入的 null 值。
   BSTR
是以指针的形式进行传递的。(指针是一个变量,包含另外一个变量的内存地址,而不是数据。) BSTR Unicode 的,即每个字符需要两个字节。 BSTR 通常以两字节的 null 字符结束。 wstr是宽字符,以双字节表示一个字符 bstr是为了与原先的basic字符兼容,它的最前面的4个字节为其长度,以'\0'结束.

4.4.更进一步的字符串以及其指针的类型定义 

由于Win32 API文档的函数列表使用函数的常用名字(例如, "SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于UnicodeAPI)。下面列出一些常用的typedefs,你可以在msdn中看到他们。

type

Meaning in MBCS builds

Meaning in Unicode builds

WCHAR

wchar_t

wchar_t

LPSTR

char*

char*

LPCSTR

const char*

const char*

LPWSTR

wchar_t*

wchar_t*

LPCWSTR

wchar_t*

wchar_t*

TCHAR

TCHAR char

wchar_t

LPTSTR

TCHAR*

TCHAR*

LPCTSTR

const TCHAR*

const TCHAR*



4.5.相互转换

(1) char*
转换成CString
  若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
char chArray[] = "This is a test";
char * p = "This is a test";
  或
LPSTR p = "This is a test";
  或在已定义Unicode应的用程序中
TCHAR * p = _T("This is a test");
  或
LPTSTR p = _T("This is a test");
CString theString = chArray;
theString.Format(_T("%s"), chArray);
theString = p;
(2) CString转换成char*
  若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
  方法一,使用强制转换。例如:
CString theString( "This is a test" );
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;
 
  方法二,使用strcpy。例如:
CString theString( "This is a test" );
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
_tcscpy(lpsz, theString);
  需要说明的是,strcpy(或可移值Unicode/MBCS_tcscpy)的第二个参数是 const wchar_t* (Unicode)const char* (ANSI),系统编译器将会自动对其进行转换。
  方法三,使用CString::GetBuffer。例如:
CString s(_T("This is a test "));
LPTSTR p = s.GetBuffer();
//
在这里添加使用p的代码
if(p != NULL) *p = _T('\0');
s.ReleaseBuffer();
//
使用完后及时释放,以便能使用其它的CString成员函数
(3) BSTR转换成char*
  方法一,使用ConvertBSTRToString。例如:
#include
#pragma comment(lib, "comsupp.lib")
int _tmain(int argc, _TCHAR* argv[]){
BSTR bstrText = ::SysAllocString(L"Test");
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
SysFreeString(bstrText); //
用完释放
delete[] lpszText2;
return 0;
}
 
  方法二,使用_bstr_t的赋值运算符重载。例如:
_bstr_t b = bstrText;
char* lpszText2 = b;
(4) char*转换成BSTR
  方法一,使用SysAllocStringAPI函数。例如:
BSTR bstrText = ::SysAllocString(L"Test");
BSTR bstrText = ::SysAllocStringLen(L"Test",4);
BSTR bstrText = ::SysAllocStringByteLen("Test",4);
  方法二,使用COleVariant_variant_t。例如:
//COleVariant strVar("This is a test");
_variant_t strVar("This is a test");
BSTR bstrText = strVar.bstrVal;
  方法三,使用_bstr_t,这是一种最简单的方法。例如:
BSTR bstrText = _bstr_t("This is a test");
  方法四,使用CComBSTR。例如:
BSTR bstrText = CComBSTR("This is a test");
  或
CComBSTR bstr("This is a test");
BSTR bstrText = bstr.m_str;
  方法五,使用ConvertStringToBSTR。例如:
char* lpszText = "Test";
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);
(5) CString转换成BSTR
  通常是通过使用CStringT::AllocSysString来实现。例如:
CString str("This is a test");
BSTR bstrText = str.AllocSysString();

SysFreeString(bstrText); //
用完释放 
(6) BSTR转换成CString
  一般可按下列方法进行:
BSTR bstrText = ::SysAllocString(L"Test");
CStringA str;
str.Empty();
str = bstrText;
 
  或
CStringA str(bstrText);
(7) ANSIUnicode和宽字符之间的转换
  方法一,使用MultiByteToWideCharANSI字符转换成Unicode字符,使用WideCharToMultiByteUnicode字符转换成ANSI字符。
  方法二,使用“_T”ANSI转换成一般类型字符串,使用“L”ANSI转换成Unicode,而在托管C++环境中还可使用SANSI字符串转换成String*对象。例如:
TCHAR tstr[] = _T("this is a test");
wchar_t wszStr[] = L"This is a test";
String* str = S”This is a test”;
  方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:
  其中,第一个C表示,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”EX表示要开辟一定大小的缓冲。SourceTypeDestinationType可以是A TWOLE,其含义分别是ANSIUnicode一般类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:
LPTSTR tstr= CA2TEX<16>("this is a test");
LPCTSTR tcstr= CA2CT("this is a test");
wchar_t wszStr[] = L"This is a test";
char* chstr = CW2A(wszStr);

5.LPTSTRLPCSTRLPCTSTRLPSTR的来源及意义

5.1来源

UNICODE:它是用两个字节表示一个字符的方法。比如字符'A'ASCII下面是一个字符,可'A'UNICODE下面是两个字符,高字符用0填充,而且汉字''ASCII下面是两个字节,而在UNICODE下仍旧是两个字节。UNICODE的用处就是定长表示世界文字,据统计,用两个字节可以编码现存的所有文字而没有二义。MBCS,它是多字节字符集,它是不定长表示世界文字的编码。

MBCS表示英文字母时就和ASCII一样(这也是我们容易把MBCSASCII搞混的原因),但表示其他文字时就需要用多字节。WINDOWS 下面的程序设计可以支持MBCSUNICODE两种编码的字符串,具体用那种就看你定义了MBCS宏还是UNICODE宏。MBCS宏对应的字符串指针是char*也就是LPSTRUNICODE对应的指针是unsigned  short*也就是LPWSTR,为了写程序方便微软定义了类型LPTSTR,在MBCS下他就是char*,   UNICODE下它是unsigned   short*,这样你就可以重定义一个宏进行不同字符集的转换了。

5.2 LPTSTRLPCSTRLPCTSTRLPSTR的意义:

LPSTR32bit指针指向一个字符串,每个字符占1字节

LPCSTR:32-bit指针指向一个常字符串,每个字符占1字节
LPCTSTR:32-bit
指针指向一个常字符串,每字符可能占1字节或2字节,取决于Unicode是否定义
LPTSTR:32-bit
指针每字符可能占1字节或2字节,取决于Unicode是否定义

Windows使用两种字符集ANSIUNICODE,前者就是通常使用的单字节方式,但这种方式处理象中文这样的双字节字符不方便,容易出现半个汉字的情况。而后者是双字节方式,方便处理双字节字符。

WindowsNT 的所有与字符有关的函数都提供两种方式的版本,而Windows9x只支持ANSI方式。_T一般同字常数相关,如_T("Hello"。如果你编译一个程序为ANSI方式,_T实际不起任何作用。而如果编译一个程序为UNICODE方式,则编译器会把"Hello"字符串以UNICODE方式保存。_T _L的区别在于,_L不管你是以什么方式编译,一律UNICODE方式保存.

L是表示字符串资源为Unicode的。比如
wchar_t Str[] = L"Hello World!";
这个就是双子节存储字符了。

_T是一个适配的宏~


#ifdef _UNICODE
的时候
_T
就是L
没有#ifdef _UNICODE的时候
_T
就是ANSI的。

比如

LPTSTR lpStr =new TCHAR[32];
TCHAR* szBuf = _T("Hello");
以上两句使得无论是在UNICODE编译条件下还是ANSI编译条件下都是正确编译的。

而且MS推荐你使用相匹配的字符串函数。
比如处理LPTSTR或者LPCTSTR 的时候,不要用strlen ,而是要用_tcslen,否则在UNICODE的编译条件下,strlen不能处理 wchar_t*的字符串。

T是非常有意思的一个符号(TCHARLPCTSTRLPTSTR_T()_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集?编译的时候才决定 

vc++中有着各种字符串的表示法:        

首先char*   是指向ANSI字符数组的指针,其中每个字符占据8位(有效数据是除掉最高位的其他7位),这里保持了与传统的C,C++的兼容。      

LP的含义是长指针(long   pointer)

LPSTR是一个指向以‘/0’结尾的ANSI字符数组的指针,与char*可以互换使用,在win32中较多地使用 LPSTR。而LPCSTR中增加的‘C’的含义是“CONSTANT”(常量),表明这种数据类型的实例不能被使用它的API函数改变,除此之外,它与 LPSTR是等同的。    

   为了满足程序代码国际化的需要,业界推出了Unicode标准,它提供了一种简单和一致的表达字符串的方法,所有字符中的字节都是16位的值,其数量也可以满足差不多世界上所有书面语言字符的编码需求,开发程序时使用Unicode(类型为wchar_t)是一种被鼓励的做法。    

   LPWSTRLPCWSTR由此产生,它们的含义类似于LPSTRLPCSTR,只是字符数据是16位的wchar_t而不是char        

 然后为了实现两种编码的通用,提出了TCHAR的定义:    

如果定义_UNICODE,声明如下:     typedef   wchar_t  TCHAR;    

如果没有定义_UNICODE,则声明如下:     typedef   char  TCHAR;      

LPTSTRLPCTSTR中的含义就是每个字符是这样的TCHAR        

CString类中的字符就是被声明为TCHAR类型的,它提供了一个封装好的类供用户方便地使用。

LPTSTRLPCSTRLPCTSTRLPSTR之间的转换:


如何理解LPCTSTR类型?

L表示long指针,这是为了兼容Windows 3.116位操作系统遗留下来的,在win32中以及其他的32为操作系统中, long指针和near指针及far修饰符都是为了兼容的作用。没有实际意义。P表示这是一个指针,C表示是一个常量,T表示在Win32环境中,有一个_T宏,这个宏用来表示你的字符是否使用UNICODE, 如果你的程序定义了UNICODE或者其他相关的宏,那么这个字符或者字符串将被作为UNICODE字符串,否则就是标准的ANSI字符串,STR表示这个变量是一个字符串,所以LPCTSTR就表示一个指向常固定地址的可以根据一些宏定义改变语义的字符串。
同样, LPCSTR就只能是一个ANSI字符串,在程序中我们大部分时间要使用带T的类型定义。

LPCTSTR == constTCHAR *

CString LPCTSTR 可以说通用。原因在于CString定义的自动类型转换,没什么奇特的,最简单的C++操作符重载而已。 

常量字符串ansiunicode的区分是由宏_T来决定的。但是用_T("abcd")时,字符串"abcd"就会根据编译时的是否定一_UNICODE来决定是char* 还是 w_char*同样,TCHAR 也是相同目的字符宏。看看定义就明白了。简单起见,下面只介绍 ansi 的情况,unicode 可以类推。 

ansi
情况下,LPCTSTR 就是 const char*, 是常量字符串(不能修改的)。 
LPTSTR 就是 char*, 即普通字符串(非常量,可修改的)。 
这两种都是基本类型,CString C++类,兼容这两种基本类型是最起码的任务了。 
由于const char* 最简单(常量,不涉及内存变更,操作迅速), CString 直接定义了一个类型转换函数 operator LPCTSTR() {......}直接返回他所维护的字符串。 
当你需要一个const char* 而传入了CString时, C++编译器自动调用 CString重载的操作符 LPCTSTR()来进行隐式的类型转换。 
当需要CString , 而传入了 const char* 时(其实 char* 也可以),C++编译器则自动调用CString的构造函数来构造临时的 CString对象。 
因此CString LPCTSTR 基本可以通用。 

但是 LPTSTR又不同了,他是 char*意味着你随时可能修改里面的数据,这就需要内存管理了(如字符串变长,原来的存贮空间就不够了,则需要重新调整分配内存) 
所以不能随便的将 const char* 强制转换成 char* 使用。 
楼主举的例子 
LPSTR lpstr = (LPSTR)(LPCTSTR)string;
 
就是这种不安全的使用方法。 
这个地方使用的是强制类型转换,你都强制转换了,C++编译器当然不会拒绝你,但同时他也认为你确实知道自己要做的是什么。因此是不会给出警告的。 
强制的任意类型转换是C(++)的一项强大之处,但也是一大弊端。这一问题在 vc6 以后的版本(仅针对vc而言)中得到逐步的改进(你需要更明确的类型转换声明) 

其实在很多地方都可以看到类似 
LPSTR lpstr = (LPSTR)(LPCTSTR)string;
 
地用法,这种情况一般是函数的约束定义不够完善的原因,比如一个函数接受一个字符串参数的输入,里面对该字符串又没有任何的修改,那么该参数就应该定义成 const char*,但是很多初学者弄不清const地用法,或者是懒,总之就是随意写成了 char* 这样子传入CString时就需要强制的转换一下。 

这种做法是不安全的,也是不被建议的用法,你必须完全明白、确认该字符串没有被修改。 

CString
转换到 LPTSTR (char*), 预定的做法是调用CStringGetBuffer函数,使用完毕之后一般都要再调用ReleaseBuffer函数来确认修改 (某些情况下也有不调用ReleaseBuffer的,同样你需要非常明确为什么这么做时才能这样子处理,一般应用环境可以不考虑这种情况) 

同时需要注意的是,GetBuffer ReleaseBuffer之间,CString分配了内存交由你来处理,因此不能再调用其他的CString函数。

CString LPCTSTR:
CString cStr;
const char *lpctStr=(LPCTSTR)cStr;

LPCTSTR
CString:
LPCTSTR lpctStr;
CString cStr=lpctStr;

 6.字符串与其他类型那个转换

6.1. c++stringint的转换

1) C标准库里面,使用atoi

#include<cstdlib>
#include <string>

std::string text= "152";
int number = std::atoi( text.c_str() );
if (errno == ERANGE) //
可能是std::errno
{
 //number
可能由于过大或过小而不能完全存储
}
else if (errno == ????)
//
可能是EINVAL
{
 //
不能转换成一个数字
}

2) C++标准库里面,使用stringstream(stringstream 可以用于各种数据类型之间的转换)

#include<sstream>
#include <string>

std::string text= "152";
int number;
std::stringstream ss;


ss << text;//
可以是其他数据类型
ss >> number; //string -> int
if (! ss.good())
{
//
错误发生
}

ss <<number;// int->string
string str = ss.str();
if (! ss.good())
{
 //
错误发生
}

3) Boost库里面,使用lexical_cast

#include<boost/lexical_cast.hpp>
#include <string>

try
{
 std::string text = "152";
 int number = boost::lexical_cast< int >( text );
}
catch( const boost::bad_lexical_cast & )
{
 //
转换失败
}                     

6.2.string CString
CString.format(”%s”, string.c_str());
c_str()确实比data()要好;

6.3.char CString
CString.format(”%s”, char*);

6.4.char string
string s(char *);
只能初始化,在不是初始化的地方最好还是用assign().

6.5.string char *
char *p = string.c_str();

6.6.CString string
string s(CString.GetBuffer());
GetBuffer()
后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间.

6.7.字符串的内容转换为字符数组和C—string
(1)  data(),
返回没有”\0“的字符串数组
(2)  c_str()
,返回有”\0“的字符串数组
(3)  copy()

6.8.CStringintchar*char[100]之间的转换

(1) CString互转int

将字符转换为整数,可以使用atoi_atoi64atol。而将数字转换为CString变量,可以使用CStringFormat函数。如
CString s;
int i = 64;
s.Format(”%d”, i)
Format
函数的功能很强,值得你研究一下。

voidCStrDlg::OnButton1()
{
   CString
   ss=”1212.12″;
   int temp=atoi(ss);
   CString aa;
   aa.Format(”%d”,temp);
   AfxMessageBox(”var is ” + aa);
}

(2) CString互转char*

///char * TOcstring
CString strtest;
char * charpoint;
charpoint=”give string a value”; //?
strtest=charpoint;

///cstring TOchar *
charpoint=strtest.GetBuffer(strtest.GetLength());

(3) 标准C里没有string,char *==char []==string, 可以用CString.Format(”%s”,char *)这个方法来将char *转成CString
    
要把CString转成char *,用操作符(LPCSTRCString就可以了。
    CString
转换 char[100]
   char a[100];
   CString str(”aaaaaa”);
   strncpy(a,(LPCTSTR)str,sizeof(a));

 引用自:http://blog.sina.com.cn/s/blog_7a4030010100rjf1.html

引用自:http://www.cnblogs.com/fire-phoenix/archive/2010/09/04/1818248.html

引用自:http://www.cnblogs.com/chuncn/archive/2009/02/24/1397518.html

 

  • 0
    点赞
  • 1
    收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值