2010-06-09 47 views
24

Giả sử một chương trình rất đơn giản:Làm thế nào để sử dụng Unicode trong C++?

  • hỏi tên.
  • lưu tên trong một biến.
  • hiển thị nội dung biến trên màn hình.

Thật đơn giản, đó là điều đầu tiên mà người ta học.

Nhưng vấn đề của tôi là tôi không biết làm thế nào để làm điều tương tự nếu tôi nhập tên bằng ký tự japanese.

Vì vậy, nếu bạn biết làm thế nào để làm điều này trong C++, xin vui lòng chỉ cho tôi một ví dụ (mà tôi có thể biên dịch và thử nghiệm)

Cảm ơn.


user362981: Cảm ơn sự giúp đỡ của bạn. Tôi biên soạn mã mà bạn đã viết mà không có vấn đề, họ cửa sổ giao diện điều khiển xuất hiện và tôi không thể nhập bất kỳ ký tự tiếng Nhật vào nó (sử dụng IME). Ngoài ra nếu Tôi thay đổi một từ trong mã của bạn ("hello") thành một từ có chứa các ký tự tiếng Nhật, nó cũng sẽ không hiển thị các từ này.

Svisstack: Cũng cảm ơn sự giúp đỡ của bạn. Nhưng khi tôi biên dịch mã của bạn, tôi nhận được lỗi sau:

warning: deprecated conversion from string constant to 'wchar_t*' 
error: too few arguments to function 'int swprintf(wchar_t*, const wchar_t*, ...)' 
error: at this point in file 
warning: deprecated conversion from string constant to 'wchar_t*' 
+1

Bạn đã không đề cập đến một nền tảng, nhưng cửa sổ cmd dòng có thể không xử lý unicode rất tốt. Kiểm tra chủ đề này: http://stackoverflow.com/questions/379240/is-there-a-windows-command-shell-that-will-display-unicode-characters – zdav

Trả lời

1

Hãy thử thay thế cout bằng wcout, cin với wcin và chuỗi bằng chuỗi. Tùy thuộc vào nền tảng của bạn, điều này có thể hoạt động:

#include <iostream> 
#include <string> 

int main() { 
    std::wstring name; 
    std::wcout << L"Enter your name: "; 
    std::wcin >> name; 
    std::wcout << L"Hello, " << name << std::endl; 
} 

Có nhiều cách khác, nhưng đây là câu trả lời "thay đổi tối thiểu".

+0

thực sự tôi nghĩ bạn vẫn phải tạo một ngôn ngữ với một khía cạnh ctype phù hợp với bảng mã giao diện điều khiển sử dụng, và sau đó làm một 'std :: wcout.imbue' và một' std :: wcin.imbue' (và afaik với microsofts buggy stl thực hiện một 'std :: locale :: global 'tốt 'trước khi sử dụng wstreams. – smerlin

1
#include <stdio.h> 
#include <wchar.h> 

int main() 
{ 
    wchar_t name[256]; 

    wprintf(L"Type a name: "); 
    wscanf(L"%s", name); 

    wprintf(L"Typed name is: %s\n", name); 

    return 0; 
} 
+0

Bạn muốn wscanf và wprintf, không phải là chuỗi đọc và chuỗi tương đương. –

+0

@Owen: Có, tôi đã bỏ lỡ, nhờ – Svisstack

1

Bạn có thể làm những việc đơn giản với sự hỗ trợ nhân vật rộng chung trong hệ điều hành của bạn lựa chọn, nhưng nói chung C++ không có tốt built-in hỗ trợ cho unicode, vì vậy bạn sẽ được tốt hơn trong thời gian dài nhìn vào một cái gì đó như ICU.

36

Bạn sẽ nhận được rất nhiều câu trả lời về các ký tự rộng. Các ký tự rộng, cụ thể là wchar_tkhông bằng Unicode. Bạn có thể sử dụng chúng (với một số cạm bẫy) để lưu trữ Unicode, giống như bạn có thể unsigned char. wchar_t phụ thuộc rất nhiều vào hệ thống. Để trích dẫn Unicode Standard, version 5.2, chapter 5:

With the wchar_t wide character type, ANSI/ISO C provides for inclusion of fixed-width, wide characters. ANSI/ISO C leaves the semantics of the wide character set to the specific implementation but requires that the characters from the portable C execution set correspond to their wide character equivalents by zero extension.

The width of wchar_t is compiler-specific and can be as small as 8 bits. Consequently, programs that need to be portable across any C or C++ compiler should not use wchar_t for storing Unicode text. The wchar_t type is intended for storing compiler-defined wide characters, which may be Unicode characters in some compilers.

Vì vậy, nó thực hiện được xác định. Dưới đây là hai triển khai: Trên Linux, wchar_t rộng 4 byte và biểu thị văn bản trong mã hóa UTF-32 (bất kể ngôn ngữ hiện tại). (Hoặc là BE hoặc LE tùy thuộc vào hệ thống của bạn, tùy theo nguồn gốc nào.) Windows, tuy nhiên, có chiều rộng 2 byte wchar_t và đại diện cho các đơn vị mã UTF-16 với chúng. Hoàn toàn khác.

Đường dẫn tốt hơn: Tìm hiểu về ngôn ngữ, vì bạn cần biết điều đó.Ví dụ, bởi vì tôi đã thiết lập môi trường của tôi sử dụng UTF-8 (Unicode), các chương trình sau đây sẽ sử dụng Unicode:

#include <iostream> 

int main() 
{ 
    setlocale(LC_ALL, ""); 
    std::cout << "What's your name? "; 
    std::string name; 
    std::getline(std::cin, name); 
    std::cout << "Hello there, " << name << "." << std::endl; 
    return 0; 
} 

...

$ ./uni_test 
What's your name? 佐藤 幹夫 
Hello there, 佐藤 幹夫. 
$ echo $LANG 
en_US.UTF-8 

Nhưng không có gì là Unicode về nó . Nó chỉ đọc trong các ký tự, có dạng UTF-8 vì tôi có môi trường của tôi được đặt theo cách đó. Tôi có thể dễ dàng nói "heck, tôi là một phần người Séc, chúng ta hãy sử dụng ISO-8859-2": Đột nhiên, chương trình nhận được đầu vào trong ISO-8859-2, nhưng vì nó chỉ làm lại nó, nó không thành vấn đề , chương trình sẽ vẫn hoạt động chính xác.

Bây giờ, nếu ví dụ đó đã đọc trong tên của tôi, và sau đó cố gắng viết nó ra thành một tệp XML và viết nguệch ngoạc <?xml version="1.0" encoding="UTF-8" ?> ở trên cùng, nó sẽ đúng khi thiết bị đầu cuối của tôi là UTF-8, nhưng sai khi thiết bị đầu cuối của tôi ở ISO-8859-2. Trong trường hợp thứ hai, nó sẽ cần phải chuyển đổi nó trước khi serializing nó vào tập tin XML. (Hoặc, chỉ cần viết ISO-8859-2 làm mã hóa cho tệp XML.)

Trên nhiều hệ thống POSIX, ngôn ngữ hiện tại thường là UTF-8, vì nó cung cấp một số lợi thế cho người dùng, nhưng điều này không phải là ' t được bảo đảm. Chỉ cần xuất UTF-8 đến stdout thường sẽ chính xác, nhưng không phải lúc nào cũng vậy. Giả sử tôi đang sử dụng ISO-8859-2: nếu bạn không biết đầu ra ISO-8859-1 "è" (0xE8) vào thiết bị đầu cuối của mình, tôi sẽ thấy "č" (0xE8). Tương tự, nếu bạn xuất UTF-8 "è" (0xC3 0xA8), tôi sẽ thấy (ISO-8859-2) "è" (0xC3 0xA8). Việc chặn các ký tự không chính xác này được gọi là Mojibake.

Thông thường, bạn chỉ cần xáo trộn dữ liệu xung quanh và không quan trọng lắm. Điều này thường xuất hiện khi bạn cần serialize dữ liệu. (Nhiều giao thức internet sử dụng UTF-8 hoặc UTF-16, ví dụ: nếu bạn nhận dữ liệu từ thiết bị đầu cuối ISO-8859-2 hoặc tệp văn bản được mã hóa trong Windows-1252, thì bạn phải chuyển đổi hoặc bạn sẽ gửi Mojibake.)

Đáng buồn thay, đây là trạng thái hỗ trợ Unicode, trong cả C và C++. Bạn phải nhớ: những ngôn ngữ này thực sự là hệ thống bất khả tri, và không ràng buộc với bất kỳ cách cụ thể nào để thực hiện nó. Điều đó bao gồm các bộ ký tự. Tuy nhiên, có rất nhiều thư viện để xử lý Unicode và các bộ ký tự khác.

Cuối cùng, nó không phải là tất cả những gì phức tạp thực sự: Biết những gì mã hóa dữ liệu của bạn, và biết những gì mã hóa đầu ra của bạn nên in Nếu họ không giống nhau, bạn cần phải làm một chuyển đổi. Điều này áp dụng cho dù bạn đang sử dụng std::cout hoặc std::wcout. Trong ví dụ của tôi, stdin hoặc std::cinstdout/std::cout đôi khi ở dạng UTF-8, đôi khi là ISO-8859-2.

+1

UTF-8 "è" là '0xC3 0xA8', không phải' 0xE8'. Bạn có thể có nghĩa là ISO-8859-1. – dan04

+0

@ dan04: Bắt tuyệt vời, cảm ơn bạn! '0xE8' là điểm mã Unicode (nhưng, như bạn đã nói, không phải mã hóa UTF-8) cho" è ". Tôi đã cập nhật ví dụ của mình. – Thanatos

0

Pre-kiện tiên quyết: http://www.joelonsoftware.com/articles/Unicode.html

Các bài viết ở trên là phải đọc điều này giải thích những gì unicode là nhưng vài câu hỏi kéo dài vẫn còn. Có UNICODE có một điểm mã duy nhất cho mỗi ký tự trong mọi ngôn ngữ và hơn nữa chúng có thể được mã hóa và lưu trữ trong bộ nhớ có khả năng khác với mã thực tế. Bằng cách này, chúng tôi có thể tiết kiệm bộ nhớ bằng cách sử dụng mã hóa UTF-8 rất tốt nếu ngôn ngữ được hỗ trợ chỉ là tiếng Anh và do đó biểu diễn bộ nhớ cơ bản giống như ASCII - điều này tất nhiên là biết mã hóa chính nó. Về mặt lý thuyết nếu chúng ta biết mã hóa, chúng ta có thể lưu trữ các ký tự UNICODE dài hơn này tuy nhiên chúng ta thích và đọc nó lại. Nhưng thế giới thực thì hơi khác một chút.

Làm cách nào để lưu trữ một chuỗi/chuỗi ký tự UNICODE trong chương trình C++? Bạn sử dụng mã hóa nào?Câu trả lời là bạn không sử dụng bất kỳ mã hóa nào nhưng bạn trực tiếp lưu trữ các điểm mã UNICODE trong chuỗi ký tự unicode giống như bạn lưu trữ các ký tự ASCII trong chuỗi ASCII. Câu hỏi đặt ra là kích thước ký tự bạn nên sử dụng vì các ký tự UNICODE không có kích thước cố định. Câu trả lời đơn giản là bạn chọn kích thước ký tự đủ rộng để giữ điểm mã ký tự cao nhất (ngôn ngữ) mà bạn muốn hỗ trợ.

Lý thuyết cho rằng ký tự UNICODE có thể mất 2 byte trở lên vẫn đúng và điều này có thể gây nhầm lẫn. Chúng ta có nên lưu trữ các điểm mã trong 3 hoặc 4 byte hơn là thực sự những gì đại diện cho tất cả các ký tự unicode? Tại sao Visual C++ lưu trữ unicode trong wchar_t sau đó chỉ là 2 byte, rõ ràng là không đủ để lưu trữ mọi điểm mã UNICODE?

Lý do chúng tôi lưu trữ điểm mã UNICODE ký tự trong 2 byte trong Visual C++ thực sự chính xác cùng một lý do tại sao chúng tôi lưu trữ ký tự ASCII (= tiếng Anh) thành một byte. Vào thời điểm đó, chúng tôi chỉ nghĩ đến tiếng Anh nên một byte là đủ. Bây giờ chúng tôi đang nghĩ đến hầu hết các ngôn ngữ quốc tế nhưng không phải tất cả vì vậy chúng tôi đang sử dụng 2 byte là đủ. Đúng vậy, biểu diễn này sẽ không cho phép chúng tôi đại diện cho những điểm mã đó có dung lượng từ 3 byte trở lên nhưng chúng tôi không quan tâm đến những điểm đó bởi vì những người này thậm chí chưa mua máy tính. Có, chúng tôi không sử dụng 3 hoặc 4 byte vì chúng tôi vẫn còn keo kiệt với bộ nhớ, tại sao lưu trữ thêm 0 (không) byte với mỗi ký tự khi chúng ta sẽ không bao giờ sử dụng nó (ngôn ngữ đó). Một lần nữa đây chính xác là lý do tại sao ASCII lưu trữ từng ký tự trong một byte, tại sao lưu trữ một ký tự trong 2 byte trở lên khi tiếng Anh có thể được biểu diễn trong một byte và phòng để dành cho những ký tự đặc biệt này!

Về lý thuyết 2 byte không đủ để hiển thị mọi điểm mã Unicode nhưng đủ để giữ bất kỳ thứ gì mà chúng tôi có thể quan tâm bây giờ. Biểu diễn chuỗi UNICODE thực sự có thể lưu trữ mỗi ký tự trong 4 byte nhưng chúng tôi không quan tâm đến các ngôn ngữ đó.

Hãy tưởng tượng 1000 năm kể từ bây giờ khi chúng tôi tìm thấy người ngoài hành tinh thân thiện và phong phú và muốn giao tiếp với họ kết hợp với vô số ngôn ngữ của họ. Một kích thước ký tự unicode duy nhất sẽ phát triển hơn nữa có lẽ đến 8 byte để chứa tất cả các điểm mã của chúng. Điều đó không có nghĩa là chúng ta nên bắt đầu sử dụng 8 byte cho mỗi ký tự unicode ngay bây giờ. Bộ nhớ là nguồn lực hạn chế, chúng tôi phân bổ những gì chúng tôi cần.

Tôi có thể xử lý chuỗi UNICODE dưới dạng chuỗi kiểu C?

Trong C++, chuỗi ASCII vẫn có thể được xử lý trong C++ và điều này khá phổ biến bằng cách lấy nó bằng con trỏ char * của nó, trong đó các hàm C có thể được áp dụng. Tuy nhiên, việc áp dụng các hàm chuỗi kiểu C hiện tại trên một chuỗi UNICODE sẽ không có ý nghĩa gì vì nó có thể có một byte NULL duy nhất trong đó kết thúc chuỗi C.

Chuỗi UNICODE không còn là bộ đệm văn bản thuần túy nữa, nhưng giờ đây nó phức tạp hơn một dòng các ký tự byte đơn kết thúc bằng một byte NULL. Bộ đệm này có thể được xử lý bởi con trỏ của nó ngay cả trong C nhưng nó sẽ yêu cầu một cuộc gọi tương thích UNICODE hoặc một thư viện C mà không thể đọc và viết các chuỗi đó và thực hiện các thao tác.

Điều này được thực hiện dễ dàng hơn trong C++ với một lớp chuyên biệt đại diện cho chuỗi UNICODE. Lớp này xử lý sự phức tạp của bộ đệm chuỗi unicode và cung cấp một giao diện dễ dàng. Lớp này cũng quyết định nếu mỗi ký tự của chuỗi unicode là 2 byte hoặc nhiều hơn - đây là những chi tiết thực hiện. Hôm nay nó có thể sử dụng wchar_t (2 byte) nhưng ngày mai nó có thể sử dụng 4 byte cho mỗi nhân vật để hỗ trợ nhiều hơn (ít được biết đến) ngôn ngữ. Đây là lý do tại sao nó luôn luôn tốt hơn để sử dụng TCHAR hơn một kích thước cố định mà bản đồ với kích thước phù hợp khi thực hiện thay đổi.

Làm cách nào để lập chỉ mục chuỗi UNICODE?

Điều này cũng đáng chú ý và đặc biệt là trong việc xử lý chuỗi kiểu C mà họ sử dụng chỉ mục để duyệt qua hoặc tìm chuỗi phụ trong chuỗi. Chỉ mục này trong chuỗi ASCII trực tiếp tương ứng với vị trí của mục trong chuỗi đó nhưng nó không có ý nghĩa trong chuỗi UNICODE và nên tránh.

Điều gì xảy ra với chuỗi kết thúc byte NULL?

Chuỗi UNICODE vẫn bị chấm dứt bằng byte NULL? Là một byte NULL duy nhất đủ để chấm dứt chuỗi? Đây là một câu hỏi thực hiện nhưng một byte NULL vẫn là một điểm mã unicode và giống như mọi điểm mã khác, nó vẫn phải có cùng kích thước với bất kỳ mã nào khác (đặc biệt khi không có mã hóa). Vì vậy, các ký tự NULL phải là hai byte là tốt nếu thực hiện chuỗi unicode dựa trên wchar_t. Tất cả các điểm mã UNICODE sẽ được biểu diễn bằng cùng một kích thước không phân biệt nếu nó là một byte rỗng hoặc bất kỳ khác.

Trình gỡ lỗi Visual C++ có hiển thị văn bản UNICODE không?

Có, nếu bộ đệm văn bản là loại LPWSTR hoặc bất kỳ loại nào khác hỗ trợ UNICODE, Visual Studio 2005 và hỗ trợ hiển thị văn bản quốc tế trong cửa sổ xem trình gỡ lỗi (phông chữ và gói ngôn ngữ được cung cấp).

Tóm tắt:

C++ không sử dụng bất kỳ mã hóa để lưu trữ các ký tự unicode nhưng nó trực tiếp lưu trữ các điểm mã UNICODE cho mỗi ký tự trong một chuỗi. Nó phải chọn kích thước ký tự đủ lớn để giữ ký tự lớn nhất của ngôn ngữ mong muốn (lỏng lẻo nói) và kích thước ký tự đó sẽ được cố định và sử dụng cho tất cả các ký tự trong chuỗi.

Hiện tại, 2 byte đủ để đại diện cho hầu hết các ngôn ngữ mà chúng tôi quan tâm, đây là lý do tại sao nó được sử dụng để đại diện cho điểm mã. Trong tương lai nếu một thuộc địa không gian thân thiện mới được phát hiện muốn giao tiếp với chúng, chúng ta sẽ phải gán các mã picnts unicode mới cho ngôn ngữ của chúng và sử dụng kích thước ký tự lớn hơn để lưu trữ các chuỗi đó.

Các vấn đề liên quan