网络课程设计出错汇总

I.关于Unicode

           转自:http://blog.csdn.net/jxluofeng/article/details/9132649

         最近因为项目需求,在windows平台下总是碰到一些关于字符串类型的变换,吃了不少苦头,索性花了这个晚上来整理下有关字节的知识,碰到的困难以及自己的一些解决方案。

一,字符的基础  ASCII DBCS( MBCS ) UNICODE


        1.1ASCII( SBCS )

        ASCII 码使用指定的位或二进制数组合来表示128 256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用二进制数来表示所有的大写和小写字母,数字9、标点符号, 以及在美式英语中使用的特殊控制字符。其中:

        031127(33)控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR回车)、FF(换页)、DEL(删除)、BS(退格)BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8910 13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。

        32126(95)字符(32sp是空格),其中485709十个阿拉伯数字

        659026个大写英文字母( A-Z ),97122号为26个小写英文字母( a-z )

        单字节字符集(single-byte character set or SBCS)。在这种编码模式下,所有的字符都只用一个字节表示。ASCIISBCS。一个字节表示的0用来标志SBCS字符串的结束。


        1.2DBCS( MBCS )

        英语用ascii编码就够了,但是我们知道,世界上很多国家都有自己的文字,ascii肯定是不能满足,在这里我们就讨论下中文字符,对于中文而言,则必须使用两个字节(byte)来代表一个字符,具第一个字节必须大于127(所以我们有许程序判断中文都是以ascii码大于127作为条件)以上用两个字节来表示一个中文的方式,在习惯上称为双字节(DBCS: Double-Byte Character Set),由于Windows里使用的多字节字符绝大部分是两个字节长,所以MBCS常被用DBCS代替。

        我们运行下面这段代码:

 #include <stdio.h>  
 #include <string.h>  
 int main()  
 {  
   char *str1="hello world!";  
   char *str2="世界,你好!";  
   char *str3="我爱你";  
   printf("the length of str1=%d\n",strlen(str1));  
   printf("the length of str2=%d\n",strlen(str2));  
   1printf("the length of str3=%d\n",strlen(str3));  
   return 0;  
 }

         我们可以看出中文字符占两个字节,英文字符占一个字节。


         1.3UNICODE

         虽然双字节(DBCS)足以解决中英文字符混合使用情况,但对于不同字符系统而言,必须经过字符码转换,非常麻烦。例如:中英文混合情况,日文,韩文等等。 为解决这个问题,Apple,Xerox,Microsoft,IBM,Novell,Borland...很多公司联合起来制订了一套可以适用于全世界所有国家的字符码,就称为Unicode ,java的内核就是16位双字节编码Unicode为基准

        char类型表示Unicode编码方案中的字符。Unicode可同时包含65536个字符,ASCII/ANSI只包含255个字符,实际上是Unicode的一个子集。Unicode字符通常用十六进制编码方案表示,范围在'\u0000''\uFFFF'之间。\u0000\u00FF表示ASCII/ANSI字符。\u表示这是一个Unicode值。


二,windows平台下各种字符串定义

         VC6.0默认的是DBCS字符集,VS默认的是UNICODE字符集。

         char :单字节变量类型,最多表示256个字符,

         wchar_t :宽字节变量类型,用于表示Unicode字符,

        它实际定义在<string.h>里:typedef unsigned short wchar_t

        为了让编译器识别Unicode字符串,必须以在前面加一个“L”,定义宽字节类型方法如下:

        wchar_t c = 'A' ;

        wchar_t * p = L"Hello!" ;

        wchar_t a[] = L"Hello!" ;

        其中,宽字节类型每个变量占用2个字节,故上述数组asizeof(a) = 14

        TCHAR / _T( ) :

        如果在程序中既包括ANSI又包括Unicode编码,需要包括头文件tchar.hTCHAR是定义在该头文件中的宏,它视你是否定义了_UNICODE宏而定义成:

定义了_UNICODE:    typedef wchar_t TCHAR ;

没有定义_UNICODE: typedef char TCHAR ;

#ifdef  UNICODE
typedef char TCHAR;
#else
typede wchar_t TCHAR;
#endif

           _T( )也是定义在该头文件中的宏,视是否定义了_UNICODE宏而定义成:

定义了_UNICODE:    #define _T(x) L##x

没有定义_UNICODE: #define _T(x) x

          注意:如果在程序中使用了TCHAR,那么就不应该使用ANSIstrXXX函数或者UnicodewcsXXX函数了,而必须使用tchar.h中定义的_tcsXXX函数。

         以strcpy函数为例子,总结一下:

//如果你想使用ANSI字符串,那么请使用这一套写法:
char szString[100];
strcpy(szString,"test");
//如果你想使用Unicode字符串,那么请使用这一套:
wchar_t szString[100];
wcscpy(szString,L"test");
//如果你想通过定义_UNICODE宏,而编译ANSI或者Unicode字符串代码:
TCHAR szString[100];
_tcscpy(szString,_TEXT("test"));

         这样我们用TCHAR_tcscpy等就可以实现在UNICODE字符集和DBCS字符集兼容,明显地,这样同样的代码可以在VC6.0VS平台下运行。


         2.1、在字符串前加一个L作用:

   如  L"我的字符串"    表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。

   strlen("asd")   =   3;  

  strlen(L"asd")   =   6;


        2.2、_T宏可以把一个引号引起来的字符串,根据你的环境设置,使得编译器会根据编译目标环境选择合适的(Unicode还是ANSI)字符处理方式

       如果你定义了UNICODE,那么_T宏会把字符串前面加一个L。这时 _T("ABCD") 相当于 L"ABCD" ,这是宽字符串。

       如果没有定义,那么_T宏不会在字符串前面加那个L_T("ABCD") 就等价于 "ABCD"


        2.3、TEXT,_TEXT _T 一样的

        如下面三语句:  

       TCHAR   szStr1[]   =   TEXT("str1");  

       char   szStr2[]   =   "str1";  

        WCHAR   szStr3[]   =   L("str1");  

       那么第一句话在定义了UNICODE时会解释为第三句话,没有定义时就等于第二句话。  

       但二句话无论是否定义了UNICODE都是生成一个ANSI字符串,而第三句话总是生成UNICODE字符串。  

       为了程序的可移植性,建议都用第一种表示方法。  

       但在某些情况下,某个字符必须为ANSIUNICODE,那就用后两种方法。


        2.4、_T()函数详解

      _T("")是一个宏,他的作用是让你的程序支持Unicode编码因为Windows使用两种字符集ANSIUNICODE前者就是通常使用的单字节方式,但这种方式处理象中文这样的双字节字符不方便,容易出现半个汉字的情况。而后者是双字节方式,方便处理双字节字符。Windows NT的所有与字符有关的函数都提供两种方式的版本,而Windows 9x只支持ANSI方式。如果你编译一个程序为ANSI方式,_T实际不起任何作用。而如果编译一个程序为UNICODE方式,则编译器会把"Hello"字符串以UNICODE方式保存。_T_L的区别在于,_L不管你是以什么方式编译,一律以UNICODE方式保存。

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

LPCSTR:32-bit指针指向一个常字符串,每个字符占1字节

LPWSTR32bit指针指向一个字符串,每个字符占2字节

LPCWSTR:32-bit指针指向一个常字符串,每个字符占2字节

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

LPTSTR:32-bit指针每字符可能占1字节或2字节,取决于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编译条件下都是正确编译的。而且MS推荐你使用相匹配的字符串函数。比如处理LPTSTR或者LPCTSTR 的时候,不要用strlen ,而是要用_tcslen否则在UNICODE的编译条件下,strlen不能处理 wchar_t*的字符串。T是非常有意思的一个符号(TCHARLPCTSTRLPTSTR_T()_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集?编译的时候才决定

II.使用fstream类打开文件的时候,如果文件名中含有中文或者路径中含有中文的话,文件打开失败!


      首先,用一个简单的例子来重现一下我所遇到的问题:

      (1). 在VS2008的“Property  Pages”属性页中,选择“Configuration Properties”-->“General”,可以看到当前使用的字符集是“Multi-Byte Character Set”,也就是说程序中使用的是多字节字符集。


      (2).接下来看看ifstream打开txt文件的简单代码:

    

#include <fstream>  
#include <iostream>  
using namespace std;  
int _tmain(int argc, _TCHAR* argv[])  
{  
    ifstream infile("d:\\测试.txt");  
    if(infile.is_open())  
   {  
       cout<<"Open Success!";  
   }  
    else  
    {  
        cout<<"Open Fail!";  
    }  
    return 0;  
} 

         (3).运行结果:输出“Open Fail”  (打开文件失败!)


         从设置选项中可以看到,工程中使用的字符集可设置为“Multi-Byte Character Set”或“Unicode Character Set”,其中“Multi-Byte Character Set”表示使用ANSI编码方式,“Unicode Character Set”表示使用UNICODE编码方式。

那么这两种编码方式有什么样的区别呢?


        (1).传统的计算机使用ANSI编码,在ANSI编码模式下,英文字符都用1个字节表示,而某些其它国家的文字(如汉字、日文),无法用单个字节来表示,ANSI便采用多个字节来表示这些字符(汉字是2个字节)。


        (2).UNICODE包含UTF-8、UTF-16、UTF-32等多种编码方案(目前windows一般使用UTF-16)。拿UTF-16来说,规定所有字符都使用2个字节表示(不论英文字母还是汉字),对于超出2个字节范围的字符采用代理(采用4个字节表示)。


        UNICODE相比ANSI有很多方面的优势(优势体现在哪?),微软非常提倡使用UNICODE编码方式,在MS较新版本的系统中都是采用UNICODE编码的。因此,即便我们在自己写的程序中使用了ANSI编码,系统会将其转换为UNICODE再对其进行处理。


        接下来我们说一下ifstream。在调用ifstream的open方法时,系统内部调用mbstowcs_s进行文件名转换(mbstowcs_s函数的作用是把多字节字符转化为宽字符),需要注意的是,该函数的调用结果依赖于程序的本地化设置(什么是本地化设置?)。而本地化设置可以通过setlocale函数来设置,譬如:setlocale(LC_ALL, "chinese")表示将程序本身的语言设置为中文,而程序启动时默认设置为LC_ALL="C"。在使用mbstowcs_s进行字符串转换时,只有当LC_ALL="chinese"时,含中文的字符串才能正确的转换成其对应的宽字节字符,否则(在LC_ALL="C"时),汉字会被看成2个单字节的字符,然后再转换成宽字节的字符,这样转换的结果显然是错误的!这就是ifstream打开含中文路径的文件失败的原因,因为"d://测试.txt"转换后得到错误的路径,因此文件打不开!
     解决方法如下:

#include <tchar.h>
 #include <fstream>
 #include <iostream>
 using namespace std;
 int main()
 {
	 /************************************************************************/
	 /* 方法1,使用_TEXT()宏定义将字符串常量指定为TCHAR*类型 */
	 /* 如果是我,首选此类型 */
	 /************************************************************************/
	 fstream file;
	 file.open(_TEXT("c:\\测试\\测试文本.txt"));
	 cout<<file.rdbuf();
	 file.close();
	/************************************************************************/
	/* 方法2,使用STL中的locale类的静态方法指定全局locale */
	/* 使用该方法以后,cout可能不能正常输出中文,十分蹊跷 */
	/* 我发现了勉强解决的方法:不要在还原区域设定前用cout或wcout 输出中文 */
	/* 否则后果就是还原区域设定后无法使用cout wcout输出中文 */
	/************************************************************************/
	locale::global(locale(""));//将全局区域设为操作系统默认区域
	file.open("c:\\测试\\测试文本2.txt");//可以顺利打开文件了
	locale::global(locale("C"));//还原全局区域设定
	cout<<file.rdbuf();
	file.close();
	/************************************************************************/
	/* 方法3,使用C函数setlocale,不能用cout输出中文的问题解决方法同上
	/************************************************************************/
	setlocale(LC_ALL,"Chinese-simplified");//设置中文环境
	file.open("c:\\测试\\测试文本3.txt");//可以顺利打开文件了
	setlocale(LC_ALL,"C");//还原
	cout<<file.rdbuf();
	file.close();
}

           由于windows提倡使用UNICODE编码,因此,我们在使用VS编写程序的时候,最好也都使用UNICODE字符集。这样有利于避免字符集转换带来的问题,同时,也有利于提高效率(前面提到,windows内部会把ANSI编码转换为UNICODE再处理,这些转换当然也带来了额外的时间消耗)。
  在示例的程序中,可以将工程字符集设置为UNICODE,然后将字符串前面加上_T(这样,在字符集已经设置为UNICODE的情况下,该字符串会自动采用宽字符表示),例如:ifstream infile(_T("d:\\测试.txt")),便不会有打开文件不成功的问题了。

III.关于MFC里面一系列指针

    LPTSTR

     LP:长指针(long pointer)。
  T:win32环境中有一个_T宏,用来标识字符是否采用Unicode编码(两字节表示一个字符),若程序中定义了Unicode,该字符/字符串被作为Unicode字符串,否则就是标准的ANSI(单字节表示一个字符)字符串。
  STR:表示这个变量是一个字符串。


        LPCTSTR

      与上面的类似,不过多了一个C,表示常量,即这是一个常指针。(即不允许修改指针指向的字符)


       ANSI情况下,LPCTSTR 就是 const char*, 是常量字符串(不能修改的)。而LPTSTR 就是 char*, 即普通字符串(非常量,可修改的)。类似的,在Unicode情况下,LPCTSTR 就是 const wchar_t*, 是常量字符串(不能修改的)。而LPTSTR 就是wchar_t*.


IV. vs 2005调试“没有为任何调用堆栈框架加载任何符号

    其实这主要是初学者才会犯的错误,建工程的时候不能直接建空白工程,要先建控制台,再在里面选空项目就可以正常调试了.~~
     其实问题在于,在空项目中不生成调试文件pdb,所以无法调试。
     要让项目生成pdb文件,需要更改:
     项目属性,configuration properties->linker->Generate Debug Info 从 no 改为 yes
     但这样还是不够的,还需要更改:
     项目属性,configuration properties->c/c++->debug information format为/ZI
     项目属性,configuration properties->c/c++->optimization为Disabled
     因为为了生成这个文件,需要设定debug信息的格式并关掉O2,还要更改linker生成调试信息的开关
     在你的代码编译时会根据编译器选项会生成符号文件,它用于你调试时可以映射到源代码.所以如果没有这个文件,它只能显示汇编码.如果你在调试进程,这种情况是正常的;如果是自己的代码,你可以用Debug配置重置下试试.


V.关于stack around the variable “” was corrupted问题的处理

     把“project->配置属性->c/c++->代码生成->基本运行时检查 设置为默认值,就没有这样的错误了。关于MSDN的解释是在堆栈外面读写某数据。错误是名为RTC1的编译器检测的。又看了更多的技术文章,发现这样的错误是程序员在项目到了一定大的时候,它占用的堆栈量就比较大。


总结一下:

       程序在Unicode环境和多字节环境下面转来转去是在是麻烦,因此我们要尽量采用微软给我们提供的一些类型来做,让程序自己完成相应的转化!

      如写char类型的时候可以定义为tchar, char*类型的时候定义为LPTSTR,等等等等!


       备忘:为了适用于Unicode环境,要养成使用_T()宏的习惯
       1、格式化字符串
       CString s;

      s.Format(_T("The num is %d."), i);


       2、转为 int
       转10进制最好用_ttoi(),它在 ANSI 编码系统中被编译成_atoi(),而在 Unicode 编码系统中编译成_wtoi()。用_tcstoul()或者_tcstol()可以把字符串转化成任意进制的(无符号/有符号)长整数。
       CString hex = _T("FAB");

       CString decimal = _T("4011");

      ASSERT(_tcstoul(hex, 0, 16) == _ttoi(decimal));
 
       3、转为 char *
    强制类型转换为 LPCTSTR,不能修改字符串。

       LPCTSTR p = s; 

      或者直接 (LPCTSTR)s;

      

你可能感兴趣的:(C++,Visual)