Một char không dấu là gì?


479

Trong C / C ++, an unsigned chardùng để làm gì? Làm thế nào nó khác với thường xuyên char?

Câu trả lời:


548

Trong C ++, có ba loại ký tự riêng biệt :

  • char
  • signed char
  • unsigned char

Nếu bạn đang sử dụng các loại ký tự cho văn bản , hãy sử dụng loại không đủ tiêu chuẩn char:

  • nó là loại chữ nhân vật như 'a'hay '0'.
  • nó là kiểu tạo nên chuỗi C như "abcde"

Nó cũng hoạt động như một giá trị số, nhưng không xác định được giá trị đó được coi là đã ký hay chưa ký. Coi chừng so sánh các ký tự thông qua sự bất bình đẳng - mặc dù nếu bạn giới hạn bản thân ở ASCII (0-127) thì bạn chỉ an toàn.

Nếu bạn đang sử dụng các loại ký tự làm số , hãy sử dụng:

  • signed char, cung cấp cho bạn ít nhất phạm vi -127 đến 127. (-128 đến 127 là phổ biến)
  • unsigned char, cung cấp cho bạn ít nhất phạm vi từ 0 đến 255.

"Ít nhất", bởi vì tiêu chuẩn C ++ chỉ đưa ra phạm vi giá trị tối thiểu mà mỗi loại số được yêu cầu để che. sizeof (char)được yêu cầu là 1 (tức là một byte), nhưng về lý thuyết, một byte có thể là 32 bit. sizeofvẫn sẽ được báo cáo kích thước của nó là1 - có nghĩa là bạn có thểsizeof (char) == sizeof (long) == 1.


4
Để rõ ràng, bạn có thể có ký tự 32 bit và số nguyên 32 bit và có sizeof (int)! = Sizeof (char) không? Tôi biết tiêu chuẩn nói sizeof (char) == 1, nhưng kích thước tương đối (int) dựa trên sự khác biệt thực tế về kích thước hoặc sự khác biệt về phạm vi?
Joseph Garvin

14
+1. Nhưng có bốn loại ký tự riêng biệt trong C ++, wchar_t là một trong số đó.
Eric Z

11
kể từ c ++ 11, bạn có 6 loại khác nhau: char, char đã ký, char không dấu, wchar_t, char16_t, char32_t.
marcinj

12
@unheilig Việc đặt một khoảng trắng sau là phổ biến sizeofvì nó không phải là hàm mà là toán tử. Đó là phong cách thậm chí tốt hơn để bỏ qua dấu ngoặc đơn khi lấy kích thước của một biến. sizeof *phoặc sizeof (int). Điều này làm cho nó rõ ràng nhanh chóng nếu nó áp dụng cho một loại hoặc biến. Tương tự như vậy, nó cũng là dư thừa để đặt dấu ngoặc sau return. Đây không phải là một chức năng.
Patrick Schlüter

3
" char: đó là loại chữ nhân vật thích 'a'hay '0'." đúng trong C ++ nhưng không phải C. Trong C, 'a'là một int.
chux - Phục hồi Monica

92

Điều này phụ thuộc vào việc triển khai, vì tiêu chuẩn C KHÔNG xác định mức độ đã ký của char. Tùy thuộc vào nền tảng, char có thể signedhoặc unsigned, vì vậy bạn cần yêu cầu rõ ràng signed charhoặc unsigned charnếu việc triển khai của bạn phụ thuộc vào nó. Chỉ sử dụng charnếu bạn có ý định đại diện cho các ký tự từ các chuỗi, vì điều này sẽ khớp với những gì nền tảng của bạn đặt trong chuỗi.

Sự khác biệt giữa signed charunsigned charlà như bạn mong đợi. Trên hầu hết các nền tảng, signed charsẽ là một 8-bit số bù hai của từ -128để 127, và unsigned charsẽ là một-cắn 8 unsigned integer ( 0để 255). Lưu ý rằng tiêu chuẩn KHÔNG yêu cầu các charloại có 8 bit, chỉ sizeof(char)trả về 1. Bạn có thể lấy tại số bit trong một char với CHAR_BITtrong limits.h. Có rất ít nếu bất kỳ nền tảng nào ngày nay, nơi đây sẽ là một cái gì đó khác hơn 8, mặc dù.

Có một bản tóm tắt tốt đẹp về vấn đề này ở đây .

Như những người khác đã đề cập kể từ khi tôi đăng bài này, bạn nên sử dụng int8_tuint8_tnếu bạn thực sự muốn đại diện cho các số nguyên nhỏ.


2
char đã ký chỉ có phạm vi tối thiểu từ -27 đến 127, không từ -128 đến 127
12431234123412341234123

3
@ 12431234123412341234123: Về mặt kỹ thuật, trong đó tiêu chuẩn C định nghĩa -127 đến 127 là phạm vi tối thiểu. Tuy nhiên, tôi thách bạn tìm một nền tảng không sử dụng hai số học bổ sung. Trên gần như mọi nền tảng hiện đại, phạm vi ký tự thực tế đã ký sẽ là -128 đến 127.
Todd Gamblin

CHAR_BITđược yêu cầu tối thiểu 8 bit theo tiêu chuẩn.
martinkunev

39

Bởi vì tôi cảm thấy nó thực sự được yêu cầu, tôi chỉ muốn nêu ra một số quy tắc của C và C ++ (chúng giống nhau về vấn đề này). Thứ nhất, tất cả các bit của unsigned chartham gia trong việc xác định giá trị nếu bất kỳ đối tượng unsigned char. Thứ hai, unsigned charđược tuyên bố rõ ràng không dấu.

Bây giờ, tôi đã thảo luận với ai đó về những gì xảy ra khi bạn chuyển đổi giá trị -1của kiểu int thành unsigned char. Anh ta từ chối ý tưởng rằng kết quả unsigned charcó tất cả các bit của nó được đặt thành 1, vì anh ta lo lắng về biểu diễn dấu hiệu. Nhưng anh không phải làm thế. Ngay lập tức tuân theo quy tắc này là chuyển đổi thực hiện những gì được dự định:

Nếu loại mới không được ký, giá trị được chuyển đổi bằng cách lặp lại hoặc trừ đi nhiều hơn một giá trị tối đa có thể được biểu thị trong loại mới cho đến khi giá trị nằm trong phạm vi của loại mới. ( 6.3.1.3p2trong bản nháp C99)

Đó là một mô tả toán học. C ++ mô tả nó theo tính toán modulo, điều này dẫn đến cùng một quy tắc. Dù sao, điều không được đảm bảo là tất cả các bit trong số nguyên -1là một trước khi chuyển đổi. Vì vậy, chúng ta có gì để chúng ta có thể tuyên bố rằng kết quả unsigned charcó tất cả các CHAR_BITbit của nó được chuyển thành 1?

  1. Tất cả các bit tham gia vào việc xác định giá trị của nó - nghĩa là, không có bit đệm nào xảy ra trong đối tượng.
  2. Thêm một lần duy nhất UCHAR_MAX+1để -1sẽ mang lại một giá trị trong phạm vi, cụ thể làUCHAR_MAX

Thế là đủ rồi! Vì vậy, bất cứ khi nào bạn muốn có một unsigned charbit của nó, bạn làm

unsigned char c = (unsigned char)-1;

Nó cũng theo sau rằng một chuyển đổi không chỉ là cắt các bit thứ tự cao hơn. Sự kiện may mắn cho sự bổ sung của hai người là nó chỉ là một sự cắt ngắn ở đó, nhưng điều tương tự không nhất thiết đúng với các biểu diễn dấu hiệu khác.


2
Tại sao không chỉ sử dụng UCHAR_MAX?
Nicolás

1
Bởi vì (unsigned type)-1là một loại thành ngữ. ~0không phải.
Patrick Schlüter

1
nếu tôi có một cái gì đó như thế này int x = 1234char *y = &x. Đại diện nhị phân 1234 00000000 00000000 00000100 11010010. Máy của tôi là một endian nhỏ nên nó đảo ngược nó và lưu trữ trong bộ nhớ 11010010 00000100 00000000 00000000LSB đến trước. Bây giờ phần chính. nếu tôi sử dụng printf("%d" , *p). printfsẽ đọc byte đầu tiên 11010010chỉ ra là -46nhưng 11010010210vậy tại sao nó in -46. Tôi thực sự bối rối, tôi đoán một số chương trình khuyến mãi số nguyên đang làm gì đó nhưng tôi không biết.
Suraj Jain

27

Ví dụ như tập quán của char không dấu :

unsigned charthường được sử dụng trong đồ họa máy tính, mà rất thường xuyên (mặc dù không phải lúc nào) chỉ định một byte cho mỗi thành phần màu. Người ta thường thấy màu RGB (hoặc RGBA) được biểu thị là 24 (hoặc 32) bit, mỗi bit unsigned char. Vì unsigned charcác giá trị nằm trong phạm vi [0,255], các giá trị thường được hiểu là:

  • 0 có nghĩa là thiếu hoàn toàn một thành phần màu nhất định.
  • 255 có nghĩa là 100% của một sắc tố màu nhất định.

Vì vậy, bạn sẽ kết thúc với màu đỏ RGB là (255,0,0) -> (đỏ 100%, xanh 0%, xanh 0%).

Tại sao không sử dụng a signed char? Số học và bit dịch chuyển trở thành vấn đề. Như đã giải thích, signed charphạm vi của về cơ bản được thay đổi bởi -128. Một phương pháp rất đơn giản và ngây thơ (hầu hết không được sử dụng) để chuyển đổi RGB sang thang độ xám là trung bình cả ba thành phần màu, nhưng điều này gặp vấn đề khi các giá trị của các thành phần màu là âm. Màu đỏ (255, 0, 0) trung bình đến (85, 85, 85) khi sử dụng unsigned charsố học. Tuy nhiên, nếu các giá trị là signed chars (127, -128, -128), chúng tôi sẽ kết thúc bằng (-99, -99, -99), sẽ là (29, 29, 29) trong unsigned charkhông gian của chúng tôi , không chính xác .


13

Nếu bạn muốn sử dụng một ký tự như một số nguyên nhỏ, cách an toàn nhất là sử dụng các kiểu int8_tuint8_t.


2
Không phải là một ý tưởng tốt: int8_tuint8_tlà tùy chọn và không được xác định trên các kiến ​​trúc trong đó kích thước byte không chính xác là 8 bit. Ngược lại, signed charunsigned charluôn có sẵn và được đảm bảo giữ ít nhất 8 bit. Nó có thể là một cách phổ biến nhưng không phải là an toàn nhất .
chqrlie

2
Đây là một nhận xét, nó không trả lời câu hỏi.
Lundin

@chqrlie Vậy ý bạn là, cách an toàn nhất để biểu diễn một số nguyên nhỏ, nếu bạn muốn lưu bộ nhớ, là giữ theo signed charunsigned char? Hoặc bạn muốn đề xuất một sự thay thế "an toàn" tốt hơn trong trường hợp cụ thể đó? Ví dụ để gắn bó với các loại số nguyên "thực" signed intunsigned intthay vào đó vì một số lý do?
RobertS hỗ trợ Monica Cellio

@ RobertS-ReinstateMonica: Sử dụng signed charunsigned charcó thể mang theo cho tất cả các triển khai tuân thủ và sẽ tiết kiệm không gian lưu trữ nhưng có thể làm tăng một số kích thước mã. Trong một số trường hợp, người ta sẽ tiết kiệm nhiều không gian lưu trữ hơn bằng cách lưu trữ các giá trị nhỏ trong bitfield hoặc các bit đơn của các loại số nguyên thông thường. Không có câu trả lời tuyệt đối cho câu hỏi này, sự liên quan của phương pháp này phụ thuộc vào trường hợp cụ thể trong tay. Và câu trả lời này không giải quyết câu hỏi nào.
chqrlie

10

unsigned charchỉ mất các giá trị dương .... như 0 đến 255

trong khi

signed charlấy cả giá trị dương và âm .... như -128 đến +127


9

charunsigned charkhông được đảm bảo là loại 8 bit trên tất cả các nền tảng, chúng được đảm bảo là loại 8 bit hoặc lớn hơn. Một số nền tảng có byte 9 bit, 32 bit hoặc 64 bit . Tuy nhiên, các nền tảng phổ biến nhất hiện nay (Windows, Mac, Linux x86, v.v.) có byte 8 bit.


8

signed charcó phạm vi -128 đến 127; unsigned charcó phạm vi từ 0 đến 255.

char sẽ tương đương với char đã ký hoặc char không dấu, tùy thuộc vào trình biên dịch, nhưng là một loại khác biệt.

Nếu bạn đang sử dụng chuỗi kiểu C, chỉ cần sử dụng char. Nếu bạn cần sử dụng ký tự cho số học (khá hiếm), chỉ định rõ ràng đã ký hoặc không dấu cho tính di động.


8

An unsigned charlà một giá trị byte không dấu (0 đến 255). Bạn có thể nghĩ charvề việc trở thành một "nhân vật" nhưng nó thực sự là một giá trị số. Thông thường charđược ký, vì vậy bạn có 128 giá trị và các giá trị này ánh xạ tới các ký tự sử dụng mã hóa ASCII. Nhưng trong cả hai trường hợp, những gì bạn đang lưu trữ trong bộ nhớ là một giá trị byte.


7

Về mặt giá trị trực tiếp, char thông thường được sử dụng khi các giá trị được biết là nằm giữa CHAR_MINCHAR_MAXtrong khi char không dấu cung cấp gấp đôi phạm vi ở đầu dương. Ví dụ: nếu CHAR_BITlà 8, phạm vi thông thường charchỉ được đảm bảo là [0, 127] (vì có thể được ký hoặc không dấu) trong khi unsigned charsẽ là [0, 255] và signed charsẽ là [-127, 127].

Về mặt sử dụng, các tiêu chuẩn cho phép các đối tượng của POD (dữ liệu cũ đơn giản) được chuyển đổi trực tiếp thành một mảng char không dấu. Điều này cho phép bạn kiểm tra biểu diễn và mẫu bit của đối tượng. Sự bảo đảm tương tự của loại picky an toàn không tồn tại đối với char hoặc char đã ký.


Trên thực tế, nó thường sẽ là [-128, 128].
RastaJedi

Các tiêu chuẩn chỉ chính thức xác định đại diện đối tượng như một chuỗi của unsigned char, không phải là một mảng cụ thể, và bất kỳ "chuyển đổi" chỉ chính thức được xác định bằng cách sao chép từ đối tượng đến một thực tế, tuyên bố mảng của unsigned char& sau đó kiểm tra sau này. Không rõ liệu OR có thể được giải thích lại trực tiếp như một mảng như vậy hay không, với các khoản phụ cấp cho số học con trỏ mà nó sẽ đòi hỏi, tức là liệu "chuỗi" =="mảng" trong cách sử dụng này. Có một vấn đề cốt lõi # 1701 được mở ra với hy vọng làm rõ điều này. Rất may, vì sự mơ hồ này thực sự làm tôi khó chịu gần đây.
gạch dưới

1
@RastaJedi Không, nó sẽ không. Nó không thể. Phạm vi -128 ... + 128 không thể biểu diễn bằng 8 bit. Độ rộng đó chỉ hỗ trợ 2 ^ 8 == 256 giá trị rời rạc, nhưng -128 ... + 128 = 2 * 128 + 1 với 0 = 257. Biểu diễn cường độ ký hiệu cho phép -127 ... + 127 nhưng có 2 (lưỡng cực) số không. Đại diện bổ sung của hai duy trì một số 0 duy nhất nhưng tạo ra phạm vi bằng cách có thêm một giá trị ở phía tiêu cực; nó cho phép -128 ... + 127. (Và cứ thế cho cả hai ở độ rộng bit lớn hơn.)
underscore_d

Nhận xét thứ 2 của tôi, thật hợp lý khi cho rằng chúng ta có thể đưa con trỏ đến số 1 unsigned charcủa OR và sau đó tiếp tục sử dụng ++ptrtừ đó để đọc từng byte của nó ... nhưng AFAICT, nó không được định nghĩa cụ thể là được phép, vì vậy chúng tôi còn lại để suy luận rằng 'có lẽ OK' từ rất nhiều đoạn khác (và theo nhiều cách, sự tồn tại đơn thuần memcpy) trong Tiêu chuẩn, gần giống với trò chơi ghép hình. Mà không lý tưởng. Vâng, có thể từ ngữ sẽ cải thiện cuối cùng. Đây là vấn đề CWG tôi đã đề cập nhưng thiếu không gian để liên kết - open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1701
underscore_d

@underscore_d xin lỗi, đó là một lỗi đánh máy. [-128, 127] là những gì tôi muốn gõ: p. Vâng, tôi biết về các số không đôi ('dương' và 'âm' 0) với dấu / độ lớn. Tôi đã phải mệt mỏi: p.
RastaJedi

5

unsigned charlà trái tim của tất cả các mánh khóe bit. Trong hầu hết TẤT CẢ trình biên dịch cho nền tảng TẤT CẢ, unsigned charchỉ đơn giản là một byte và một số nguyên không dấu (thường) 8 bit có thể được coi là một số nguyên nhỏ hoặc một gói bit.

Trong nghiện, như người khác đã nói, tiêu chuẩn không xác định dấu hiệu của một char. vì vậy bạn có 3 phân biệt charcác loại: char, signed char, unsigned char.


1
Thủ thuật bit, hay còn gọi là bit twiddling hoặc hack bit thực sự được biết là gây nghiện ;-)
chqrlie

3
Đó là 0 gây ra vấn đề. Để tránh nghiện từ twiddling, hãy tránh xa các bit noughty.
DragonLord

5

Nếu bạn thích sử dụng các loại khác nhau có độ dài cụ thể và signedness, có lẽ bạn đang tốt hơn off với uint8_t, int8_t, uint16_t, vv đơn giản chỉ vì họ thực hiện chính xác những gì họ nói.


4

Một số googling tìm thấy điều này , nơi mọi người đã thảo luận về điều này.

Một char không dấu về cơ bản là một byte đơn. Vì vậy, bạn sẽ sử dụng điều này nếu bạn cần một byte dữ liệu (ví dụ: có thể bạn muốn sử dụng nó để đặt và tắt cờ được chuyển đến một chức năng, như thường được thực hiện trong API Windows).


4

Một char không dấu sử dụng bit được dành riêng cho dấu của một char thông thường như một số khác. Điều này thay đổi phạm vi thành [0 - 255] trái ngược với [-128 - 127].

Các ký tự không dấu thường được sử dụng khi bạn không muốn có một dấu hiệu. Điều này sẽ tạo ra sự khác biệt khi thực hiện những việc như dịch chuyển bit (shift mở rộng dấu hiệu) và những thứ khác khi xử lý char dưới dạng byte thay vì sử dụng nó làm số.


4

unsigned charchỉ lấy các giá trị dương: 0 đến 255 trong khi signed charlấy các giá trị dương và âm: -128 đến +127.


3

trích dẫn từ cuốn sách "hành vi lập trình c":

Vòng loại signedhoặc unsignedcó thể được áp dụng cho char hoặc bất kỳ số nguyên nào. số không dấu luôn luôn dương hoặc bằng 0 và tuân theo định luật modulo số học 2 ^ n, trong đó n là số bit trong loại. Vì vậy, ví dụ, nếu các ký tự là 8 bit, các biến char không dấu có các giá trị trong khoảng từ 0 đến 255, trong khi các ký tự được ký có các giá trị trong khoảng từ -128 đến 127 (trong máy bổ sung hai). phụ thuộc, nhưng các ký tự có thể in luôn luôn tích cực.


2

signed charunsigned charcả hai đại diện cho 1byte, nhưng chúng có phạm vi khác nhau.

   Type        |      range
-------------------------------
signed char    |  -128 to +127
unsigned char  |     0 to 255

Trong trường signed charhợp chúng tôi xem xét char letter = 'A', 'A' đại diện cho nhị phân 65 in ASCII/Unicode, Nếu 65 có thể được lưu trữ, -65 cũng có thể được lưu trữ. Không có giá trị nhị phân âm trong ASCII/Unicodeđó vì không cần phải lo lắng về giá trị âm.

Thí dụ

#include <stdio.h>

int main()
{
    signed char char1 = 255;
    signed char char2 = -128;
    unsigned char char3 = 255;
    unsigned char char4 = -128;

    printf("Signed char(255) : %d\n",char1);
    printf("Unsigned char(255) : %d\n",char3);

    printf("\nSigned char(-128) : %d\n",char2);
    printf("Unsigned char(-128) : %d\n",char4);

    return 0;
}

Đầu ra -:

Signed char(255) : -1
Unsigned char(255) : 255

Signed char(-128) : -128
Unsigned char(-128) : 128
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.