c++程序編碼


    c++程序中涉及到中文字符的輸入輸出以及其他操作經常會出現亂碼。亂碼主要是由於程序的源文件編碼、可執行文件編碼以及程序運行環境的編碼不匹配導致。比如,c++源程序文件編碼為GB18030, 在源程序中有一中文窄字符串常量,程序運行時輸出該字符串常量,運行環境的系統編碼為UTF8時,就會輸出亂碼。

一、程序相關的編碼

1.程序源文件編碼 
    程序源文件編碼是指保存程序源文件內容所使用的編碼方案,該編碼方案可在保存文件的時候自定義。 
    通常在簡體中文windows環境下,各種編輯器(包括visual studio)新建文件缺省編碼都是GB18030,所以不特別指定的話,windows環境下的c++源文件的編碼通常為GB18030(GB18030兼容GBK);在linux環境下,默認的為UTF-8編碼。

2.c++程序內碼 
    源程序編譯后,c++中的字符串常量變成一串字節存放在可執行文件中,內碼指的是在可執行文件中,字符串以什么編碼進行存放。這里的字符串常量指的是窄字符(char)而不是寬字符(wchar_t)。寬字符通常都是以Unicode(VC使用UTF-16BE, gcc使用UTF-32BE)存放。 
    通常簡體中文版的VC使用內碼為GB18030,而gcc使用內碼缺省為UTF-8,單可以通過-fexec-charset參數進行修改。(可以通過在程序中打印字符串中每個字節的16進制形式來判斷程序使用的內碼)。

3.運行環境編碼 
    運行環境編碼指的是,執行程序時,操作系統或終端所使用的編碼。程序中輸出的字符最終要轉換為運行環境編碼才能顯示,否則就會出現亂碼。 
    常用的簡體中文版的windows環境編碼是GB18030,linux下最常用的環境編碼是UTF-8。

4.三種編碼之間的關系

程序源文件【源文件編碼】--->(編譯器編譯) ---->目標文件【程序內碼】----> (運行后輸出信息)---->輸出【運行環境編碼】

    編譯器需要正確識別源文件的編碼,把源文件編譯為目標文件,並把源文件中的以源文件編碼的字符串轉換為以程序內碼編制的字符串保存在目標文件中。 
    如果源程序中的為窄字符串常量,則程序運行時,直接將目標文件中對應的內碼字符串輸出;若為寬字符串常量,則程序運行時c++標准庫需要正確識別終端的運行環境編碼,並把程序的輸出轉換為運行環境所使用的編碼,以便正確顯示。

二、c++寬窄字符

    c++有窄字符char和寬字符wchar_t的區別,分別有一套相應的類和函數(string/cout/strlen和wstring/wcout/wsclen等)。窄字符在不同的編譯器下有不同的缺省編碼形式(在簡體中文VC下是GB18030編碼,gcc下是UTF-8編碼),寬字符一般都使用unicode編碼,在VC下使用UTF-16,gcc下使用UTF-32。 
    c++在輸出窄字符時會按照程序內碼原樣輸出,不會進行編碼轉換,因此在使用窄字符要求程序內碼和運行環境編碼一致,才不會出現亂碼; 
    c++在輸出寬字符時,會自動轉換為運行環境的編碼,因此只要正確設置了運行環境編碼,同一個程序就可以在不同編碼的運行環境下正確顯示中文。在程序中設置當前環境的字符編碼,通過 locale::global(locale(""))進行設置。

兼容windows和linux的字符顯示的做法 
    對於需要支持多語種的程序,使用窄字符存儲字符串常量,源程序中使用ascii字符,對於非ascii字符,如中文等通過gettext等工具放到單獨的語言包中。

三、用戶輸入、輸出以及持久化

    由於用戶輸入、輸出即從文件、網絡等設施讀寫的數據在程序底層看來都是字節,因此存在輸入時如何把字節流解釋成有效的信息,在輸出時怎么把程序中的信息轉換為正確的字節流的問題。

程序不對內容進行處理 
    如果用戶不需要對字節流的內容進行處理,而輸入的字符編碼和輸出的字符編碼一致,則程序不需要對數據進行任何的編碼轉換,只需要把讀入的數據原樣寫到輸出即可,數據的字符編碼與程序的編碼沒有關系。

程序需要對內容進行處理 
    如果程序需要在一定程度上對數據進行處理(如需要判斷字符個數、對字符進行比較。在字符串附加或者去掉內容),就要把數據轉換為一種明確的字符編碼,一般來說是程序內碼,再進行處理,在處理后再轉換為所需的字符編碼進行輸出。 
1.寬字符程序 
    如果只需要處理采用當前運行環境字符編碼的數據,可以通過ios::imbue(可以指定io流的字符編碼),在輸入、輸出時c++標准庫會自動在所指定的字符編碼與程序內碼之間進行編碼轉換。如果不使用流的話,也可以通過標准的wcstombs()或者mbstowcs()函數進行當前編碼(通過locale::globale()或setlocale()指定)與寬字符之間的轉換。 
2.窄字符程序 
    對於窄字符程序,如果數據的字符編碼與程序內碼一致也不需要進行編碼轉換,直接處理即可。

四、幾個小實驗

case 1 
    在windows簡體中文環境下,設置vs2013源文件的編碼為utf-8(默認為gb2312,通過“文件”——“高級保存選項”,選擇UTF-8),同時設置編譯器編譯結果的內碼為UTF-8(通過在cpp和h文件的頭部添加 
#pragma once 
#pragma execution_character_set("utf-8") 
即可),然后使用如下程序:

#pragma once
#pragma execution_character_set("utf-8")
// 本文件為utf-8 編碼格式
#include<iostream>
#include<string>
using namespace std;
int main(){
	std::string str = "好人";
	cout << str << endl;
	return 0;
}

 

    由於簡體中文windows的系統環境的編碼為GB18030,而源文件被設置成UTF-8編碼,同時也指示編譯器編譯的目標文件的程序內碼使用UTF-8編碼,那么在輸出的時候會將中文輸出亂碼。

case 2 
    在windows簡體中文環境下,設置vs2013源文件為utf-8編碼,而可執行文件的內碼保持默認為gb2312:

//文件選擇保存為utf-8格式
#include<iostream>
#include<string>
using namespace std;
int main(){
	std::string str = "好人";
	cout <<str << endl;
	return 0;
}

 

    編譯器會按照utf-8格式解析源文件,並進行編譯,編譯出可執行程序的內碼為gb2312,系統運行環境的編碼為GBK,可以正常顯示中文。

case 3 
    (3.0)在windows簡體中文環境下,vs2013源文件分別采用gb2312和utf-8編碼,可執行程序的內碼分別采用gb2312和utf-8編碼(四種組合),直接wcout輸出寬字符,結果均為空。

#pragma once
//#pragma execution_character_set("utf-8")
// 本文件為utf-8 編碼格式
#include<iostream>
#include<string>
using namespace std;
int main(){
	std::wstring wstr = L"好人";
	std::wcout << wstr.size() << endl; //輸出為2
	std::wcout << wstr << endl;
	return 0;
}

     (3.1)在windows簡體中文環境下,vs2013源文件分別采用gb2312和utf-8編碼,可執行程序的內碼分別采用gb2312和utf-8編碼(四種組合),imbue設置輸出格式為std::locale("chs")或者通過locale::global(locale("")) 設置,然后wcout輸出寬字符:

#pragma once
//#pragma execution_character_set("utf-8")
// 本文件為utf-8 編碼格式
#include<iostream>
#include<string>
using namespace std;
int main(){
//	locale::global(locale("")); 或者  std::wcout.imbue(std::locale("chs"));
	std::wstring wstr = L"好人";
	std::wcout << wstr.size() << endl;
	std::wcout << wstr << endl;
	return 0;
}

 

    因為,寬字符在輸出時候會自動進行編碼格式的轉換,對wcout指定了輸出流的輸出編碼,則可以輸出正確的結果。

case 4 
    在windows下使用mysqlconn庫連接mysql數據庫,由於為了和客戶端通信,mysql數據庫采用UTF-8編碼。在visual studio 2013下程序訪問數據庫中中文字符的某條屬性,在select的時候,需要將中文字符寫在源代碼中。由於vistual studio默認的源文件編碼和可執行程序的內碼都是GBK2312,因此在訪問mysql數據庫的時候中文字符變為亂碼從而無法訪問。 
    可以通過將包含中文字符的那個源文件(只需要含中文字符的那個即可,不需要全部)強制編譯為UTF-8編碼的可執行程序內碼,然后訪問mysql即可。

#pragma once
#pragma execution_character_set("utf-8")
// 本文件為utf-8 編碼格式
#include<iostream>
#include<sstream>
#include"SQLExecutor.h"
using namespace std;
int main(){
	SQLExecutor* sql_conn = new SQLExecutor();
	std::ostringstream os;
	os << "select attribute_id from attribute_define where attribute_name = '入網狀態'";
	std::cout << os.str() << endl;
	ResultSet* rs = sql_conn->ExecuteQuery(os.str());
	while (rs && rs->next()){
		std::cout << "attribute_id = " << rs->getUInt(1) << endl;
	}
	delete rs;
	delete sql_conn;
	return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM