GBK转UTF-8时,奇数个中文会乱码,偶数个中文不会乱码

本文详细分析了GBK编码的字符串在转换为UTF-8编码时出现乱码的问题,尤其是奇数个中文字符时更为明显。通过源代码示例展示了乱码产生的原因,并给出了解决方案。

问题出现:GBK转UTF-8时,奇数个中文会乱码,偶数个中文不会乱码。
三个中文

Java代码   收藏代码
  1. public static void encodeError() throws UnsupportedEncodingException {  
  2.     String gbk = "我来了";  
  3.     String utf8 = new String(gbk.getBytes("UTF-8"));  
  4.   
  5.     //模拟UTF-8编码的网站显示  
  6.     System.out.println(new String(utf8.getBytes(),"UTF-8"));  
  7. }  
  8. /* 
  9. 我来?? 
  10. */  

 前面三个中文,后面一个中文,都是奇数

Java代码   收藏代码
  1. public static void encodeError2() throws UnsupportedEncodingException {  
  2.     String gbk = "今年是2011年";  
  3.     String utf8 = new String(gbk.getBytes("UTF-8"));  
  4.   
  5.     //模拟UTF-8编码的网站显示  
  6.     System.out.println(new String(utf8.getBytes(),"UTF-8"));  
  7. }  
  8. /* 
  9. 今年??011?? 
  10. */  

 

原因:为什么只有奇数个中文才乱码,偶数个却不乱码?下面来分析原因

Java代码   收藏代码
  1. public static void analyze() throws UnsupportedEncodingException {  
  2.     String gbk = "我来了";  
  3.     String utf8 = new String(gbk.getBytes("UTF-8"));  
  4.     for (byte b : gbk.getBytes("UTF-8")) {  
  5.         System.out.print(b + " ");  
  6.     }  
  7.     System.out.println();  
  8.     for (byte b : utf8.getBytes()) {  
  9.         System.out.print(b + " ");  
  10.     }  
  11. }  
  12. /* 
  13. -26 -120 -111 -26 -99 -91 -28 -70 -122  
  14. -26 -120 -111 -26 -99 -91 -28 -70 63  
  15. */  

 注意最后一个字节不同,上面一行才是正确的UTF-8编码。那么为什么下面一行最后一个字节是63,而不是-122呢?这就是导致乱码的原因所在。
GBK编码是一个中文2个字节,而UTF-8编码是一个中文3个字节,当我们调用getBytes("UTF-8")方法时,会通过计算来增加字节,使得从GBK的2个字节变成UTF-8对应的3个字节。因此,上例3个中文输出了9个字节。

 

这里讲一下怎么通过计算增加字节,不深究的读者可以跳过此段。为了醒目,直接用代码讲解

Java代码   收藏代码
  1. public static void gbk2Utf() throws UnsupportedEncodingException {  
  2.     String gbk = "我来了";  
  3.     char[] c = gbk.toCharArray();  
  4.     byte[] fullByte = new byte[3*c.length];  
  5.     for (int i=0; i<c.length; i++) {  
  6.         String binary = Integer.toBinaryString(c[i]);  
  7.         StringBuffer sb = new StringBuffer();  
  8.         int len = 16 - binary.length();  
  9.         //前面补零  
  10.         for(int j=0; j<len; j++){  
  11.                 sb.append("0");  
  12.             }  
  13.         sb.append(binary);  
  14.         //增加位,达到到24位3个字节  
  15.         sb.insert(0"1110");  
  16.             sb.insert(8"10");  
  17.             sb.insert(16"10");  
  18.             fullByte[i*3] = Integer.valueOf(sb.substring(08), 2).byteValue();//二进制字符串创建整型  
  19.             fullByte[i*3+1] = Integer.valueOf(sb.substring(816), 2).byteValue();  
  20.             fullByte[i*3+2] = Integer.valueOf(sb.substring(1624), 2).byteValue();  
  21.     }  
  22.     //模拟UTF-8编码的网站显示  
  23.     System.out.println(new String(fullByte,"UTF-8"));  
  24. }  

 

现在我们来找出最后一个字节是63,而不是-122的原因。

Java代码   收藏代码
  1. public static void analyze2() throws UnsupportedEncodingException {  
  2.     String gbk = "我来了";  
  3.     byte[] utfBytes = gbk.getBytes("UTF-8");//得到9个字节  
  4.     String utf8 = new String(utfBytes);//问题就出在这  
  5.     System.out.print(utf8);  
  6. }  
  7. /* 
  8. 鎴戞潵浜? 
  9. */  

 因为文件是GBK编码,new String(utfBytes)默认就是new String(utfBytes,"GBK")。它会2个字节2个字节地转换成字符,当字节是奇数时最后1个字节转字符就会计算错误,然后直接赋予最后这个字符为?,对应ASCII代码就是63。

 

解决问题
保证字节正确才是硬道理。当调用getBytes("UTF-8")转换成字节数组后,创建ISO-8859-1编码的字符串,ISO-8859-1编码是一个字节对应一个字符,因此不会使最后一个字节错误。

Java代码   收藏代码
  1. public static void correctEncode() throws UnsupportedEncodingException {  
  2.     String gbk = "我来了";  
  3.     String iso = new String(gbk.getBytes("UTF-8"),"ISO-8859-1");  
  4.     for (byte b : iso.getBytes("ISO-8859-1")) {  
  5.         System.out.print(b + " ");  
  6.     }  
  7.     System.out.println();  
  8.   
  9.     //模拟UTF-8编码的网站显示  
  10.     System.out.println(new String(iso.getBytes("ISO-8859-1"),"UTF-8"));  
  11. }  
  12. /* 
  13. -26 -120 -111 -26 -99 -91 -28 -70 -122  
  14. 我来了 
  15. */  
 

 

<think>我们遇到了程序运行后出现乱码的问题。乱码通常字符编码不匹配有关,特别是在Windows控制台中。在C语言程序中,乱码可能由以下原因引起: 1. 程序输出的编码控制台使用的编码不一致(例如,程序输出UTF-8而控制台使用GBK,或者反之)。 2. 在输出中文字符串,源代码文件的编码编译器处理的方式不一致。 3. 控制台本身未配置为支持程序输出的编码。 在Windows系统中,控制台默认使用GBK编码(代码页936),而许多现代文本编辑器默认使用UTF-8编码。如果源代码文件以UTF-8保存,并且程序输出中文字符串,那么在没有设置控制台代码页的情况下,就会显示乱码。 ### 解决方案 我们可以通过以下两种方式之一解决乱码问题: #### 方法1:修改源代码文件的编码为GBK 将源代码文件(.c文件)另存为GBK编码,这样中文字符串就会以GBK编码存储,Windows控制台默认编码一致。 #### 方法2:在程序中设置控制台代码页为UTF-8 在程序开始处,使用Windows API函数`SetConsoleOutputCP(65001)`将控制台输出代码页设置为UTF-8。同,需要确保源代码文件以UTF-8编码保存。 这里我们使用方法2,因为UTF-8是更通用的编码格式。但是注意,Windows控制台对UTF-8的支持有一些限制(例如字体需要支持),但通常现代Windows系统(如Win10)已经支持。 ### 修改后的代码 在程序入口处(main函数开始)添加设置控制台输出代码页的代码。 ```c #include <stdio.h> #include <ctype.h> #include <windows.h> // 用于SetConsoleOutputCP // 函数声明 double even_sum(int n); double odd_sum(int n); int main() { // 设置控制台输出代码页为UTF-8,以支持中文字符 SetConsoleOutputCP(65001); // 也可以设置控制台字体(可选),但通常不需要 // 设置控制台输入代码页(如果需要输入中文) SetConsoleCP(65001); int n; char choice; do { printf("请输入整数n: "); if (scanf("%d", &n) != 1) { // 处理无效输入 printf("输入错误,请输入整数!\n"); while(getchar() != '\n'); // 清空输入缓冲区 continue; } double (*sum_func)(int); // 定义函数指针 // 根据n的奇偶性分配函数指针 if (n % 2 == 0) { sum_func = even_sum; // 指向偶数求和函数 printf("计算1/2+1/4+...+1/%d\n", n); } else { sum_func = odd_sum; // 指向奇数求和函数 printf("计算1/1+1/3+...+1/%d\n", n); } double result = sum_func(n); // 通过函数指针调用 printf("结果为: %.6f\n", result); // 清除输入缓冲区 while(getchar() != '\n'); // 正确询问是否继续 printf("还要继续求值吗?(Y/N): "); if (scanf(" %c", &choice) != 1) { // 处理字符输入错误 printf("输入错误,程序将退出。\n"); break; } } while (toupper(choice) == 'Y'); // 使用toupper处理大小写 printf("程序已退出。\n"); return 0; } // 偶数求和函数 double even_sum(int n) { double sum = 0.0; for (int i = 2; i <= n; i += 2) { sum += 1.0 / i; } return sum; } // 奇数求和函数 double odd_sum(int n) { double sum = 0.0; for (int i = 1; i <= n; i += 2) { sum += 1.0 / i; } return sum; } ``` ### 注意事项 1. 使用`SetConsoleOutputCP(65001)`设置输出代码页为UTF-8,同使用`SetConsoleCP(65001)`设置输入代码页为UTF-8(如果程序需要输入中文字符)。 2. 确保源代码文件以UTF-8编码保存(通常使用带BOM的UTF-8在Windows上兼容性更好)。 3. 如果控制台字体不支持UTF-8字符,可能需要手动设置控制台字体(如Consolas或等宽字体)以正确显示字符。 ### 替代方案 如果不想使用Windows API,也可以考虑在编译指定执行字符集(如GBK),但这种方法依赖于编译器。例如,使用GCC编译可以添加编译选项: ```sh -fexec-charset=GBK ``` 但这样设置后,程序中的字符串常量会被编译器转换GBK编码,这样在默认的Windows控制台就能正确显示。 ### 总结 乱码问题主要是由于编码不一致导致的。我们通过设置控制台代码页为UTF-8,并确保源代码文件以UTF-8保存,可以解决乱码问题。 §§相关问题§§: 1. 在Linux系统中如何解决终端乱码问题? 2. 除了设置控制台代码页,还有哪些方法可以确保程序在不同语言环境下正确显示文本? 3. 如何检测当前系统的默认编码并在程序中进行适配? 4. 在跨平台(Windows/Linux)程序中,如何处理编码问题以同支持两者?</think># 解决C语言程序运行后乱码问题 在C语言程序运行后出现乱码,通常是由于字符编码不匹配或终端设置问题造成的。以下是全面的解决方案和代码实现。 ## 问题原因分析 乱码问题通常由以下原因引起: 1. **控制台编码问题**:Windows控制台默认使用GBK编码,而程序可能使用UTF-8 2. **区域设置不匹配**:系统区域设置程序输出不兼容 3. **宽字符处理不当**:中文字符需要使用宽字符函数处理 4. **字体支持问题**:控制台字体不支持中文字符 ## 解决方案一:设置控制台代码页(推荐) ```c #include <stdio.h> #include <ctype.h> #include <windows.h> // 添加Windows API支持 // 函数声明 double even_sum(int n); double odd_sum(int n); int main() { // 设置控制台输出代码页为UTF-8 SetConsoleOutputCP(65001); // 设置控制台输入代码页为UTF-8 SetConsoleCP(65001); // 设置控制台字体(可选) CONSOLE_FONT_INFOEX font = {sizeof(font)}; font.dwFontSize.Y = 16; wcscpy(font.FaceName, L"Consolas"); SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &font); int n; char choice; do { printf("请输入整数n: "); if (scanf("%d", &n) != 1) { // 处理无效输入 printf("输入错误,请输入整数!\n"); while(getchar() != '\n'); // 清空输入缓冲区 continue; } double (*sum_func)(int); // 定义函数指针 // 根据n的奇偶性分配函数指针 if (n % 2 == 0) { sum_func = even_sum; // 指向偶数求和函数 printf("计算1/2+1/4+...+1/%d\n", n); } else { sum_func = odd_sum; // 指向奇数求和函数 printf("计算1/1+1/3+...+1/%d\n", n); } double result = sum_func(n); // 通过函数指针调用 printf("结果为: %.6f\n", result); // 清除输入缓冲区 while(getchar() != '\n'); // 正确询问是否继续 printf("还要继续求值吗?(Y/N): "); if (scanf(" %c", &choice) != 1) { // 处理字符输入错误 printf("输入错误,程序将退出。\n"); break; } } while (toupper(choice) == 'Y'); // 使用toupper处理大小写 printf("程序已退出。\n"); return 0; } // 偶数求和函数 double even_sum(int n) { double sum = 0.0; for (int i = 2; i <= n; i += 2) { sum += 1.0 / i; } return sum; } // 奇数求和函数 double odd_sum(int n) { double sum = 0.0; for (int i = 1; i <= n; i += 2) { sum += 1.0 / i; } return sum; } ``` ### 关键代码解释 1. **设置控制台编码**: ```c SetConsoleOutputCP(65001); // 设置输出为UTF-8 SetConsoleCP(65001); // 设置输入为UTF-8 ``` - 65001是UTF-8的代码页标识符 - 确保控制台输入输出都使用UTF-8编码 2. **设置控制台字体**: ```c CONSOLE_FONT_INFOEX font = {sizeof(font)}; font.dwFontSize.Y = 16; wcscpy(font.FaceName, L"Consolas"); SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &font); ``` - 设置支持中文的等宽字体(如Consolas) - 确保控制台能正确渲染中文字符 ## 解决方案二:使用宽字符函数 ```c #include <stdio.h> #include <ctype.h> #include <wchar.h> // 宽字符支持 #include <locale.h> // 区域设置 // 函数声明 double even_sum(int n); double odd_sum(int n); int main() { // 设置区域环境 setlocale(LC_ALL, ""); int n; wchar_t choice; // 使用宽字符 do { wprintf(L"请输入整数n: "); if (wscanf(L"%d", &n) != 1) { // 处理无效输入 wprintf(L"输入错误,请输入整数!\n"); while(getwchar() != L'\n'); // 清空输入缓冲区 continue; } double (*sum_func)(int); // 定义函数指针 // 根据n的奇偶性分配函数指针 if (n % 2 == 0) { sum_func = even_sum; // 指向偶数求和函数 wprintf(L"计算1/2+1/4+...+1/%d\n", n); } else { sum_func = odd_sum; // 指向奇数求和函数 wprintf(L"计算1/1+1/3+...+1/%d\n", n); } double result = sum_func(n); // 通过函数指针调用 wprintf(L"结果为: %.6f\n", result); // 清除输入缓冲区 while(getwchar() != L'\n'); // 正确询问是否继续 wprintf(L"还要继续求值吗?(Y/N): "); if (wscanf(L" %lc", &choice) != 1) { // 处理字符输入错误 wprintf(L"输入错误,程序将退出。\n"); break; } } while (towupper(choice) == L'Y'); // 使用宽字符版本 wprintf(L"程序已退出。\n"); return 0; } // 偶数求和函数(不变) double even_sum(int n) { double sum = 0.0; for (int i = 2; i <= n; i += 2) { sum += 1.0 / i; } return sum; } // 奇数求和函数(不变) double odd_sum(int n) { double sum = 0.0; for (int i = 1; i <= n; i += 2) { sum += 1.0 / i; } return sum; } ``` ### 宽字符函数关键点 1. **设置区域环境**: ```c setlocale(LC_ALL, ""); ``` - 使用当前系统的默认区域设置 - 确保宽字符函数正常工作 2. **使用宽字符函数**: - `wprintf()` 替代 `printf()` - `wscanf()` 替代 `scanf()` - `getwchar()` 替代 `getchar()` - `towupper()` 替代 `toupper()` 3. **宽字符常量**: - 字符串前加 `L` 前缀:`L"中文"` - 字符前加 `L` 前缀:`L'Y'` ## 解决方案三:修改编译器设置(跨平台) 如果使用IDE(如Visual Studio、Code::Blocks等),可以修改项目设置: 1. **源文件编码**: - 将源文件保存为UTF-8 with BOM编码 - 在IDE设置中指定字符集为"使用Unicode字符集" 2. **Visual Studio设置**: - 项目属性 → 配置属性 → 高级 → 字符集 → 使用多字节字符集 - 项目属性 → C/C++ → 命令行 → 添加 `/utf-8` 选项 3. **GCC编译选项**: ```bash gcc -finput-charset=UTF-8 -fexec-charset=UTF-8 -o program source.c ``` - `-finput-charset`: 指定源文件编码 - `-fexec-charset`: 指定执行字符编码 ## 验证解决方案 1. **编译并运行程序**: ```bash gcc -o program source.c ./program ``` 2. **测试中文输入输出**: - 确保所有提示信息正确显示 - 测试输入和输出的中文字符 - 验证计算结果格式正确 ## 最佳实践建议 1. **统一编码**: - 源代码文件使用UTF-8编码 - 明确指定输入输出编码 - 在Windows上使用BOM标记 2. **跨平台考虑**: ```c #ifdef _WIN32 #include <windows.h> #endif void init_console() { #ifdef _WIN32 SetConsoleOutputCP(65001); SetConsoleCP(65001); #else setlocale(LC_ALL, "en_US.UTF-8"); #endif } ``` 3. **错误处理**: ```c if (!SetConsoleOutputCP(65001)) { // 处理错误 DWORD err = GetLastError(); printf("设置控制台代码页失败,错误码: %d\n", err); } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值