TL; DR
C được thừa hưởng !
và các ~
toán tử từ ngôn ngữ khác. Cả hai &&
và ||
được thêm vào nhiều năm sau bởi một người khác.
Câu trả lời dài
Trong lịch sử, C đã phát triển từ các ngôn ngữ B ban đầu, dựa trên BCPL, dựa trên CPL, dựa trên Algol.
Algol , ông cố của C ++, Java và C #, đã định nghĩa đúng và sai theo cách cảm nhận trực quan cho các lập trình viên: các giá trị chân lý, được coi là số nhị phân (đúng tương ứng với 1 và sai thành 0), là giống như giá trị tích phân nội tại. Tuy nhiên, một nhược điểm của điều này là logic và bitwise không thể hoạt động giống nhau: Trên bất kỳ máy tính hiện đại nào, ~0
bằng -1 thay vì 1 và ~1
bằng -2 thay vì 0. (Ngay cả trên một máy tính lớn sáu mươi tuổi có ~0
đại diện - 0 hoặc INT_MIN
, ~0 != 1
trên mọi CPU từng được tạo ra, và tiêu chuẩn ngôn ngữ C đã yêu cầu nó trong nhiều năm, trong khi hầu hết các ngôn ngữ con gái của nó thậm chí không bận tâm đến việc hỗ trợ ký hiệu và cường độ của một người.)
Algol đã giải quyết vấn đề này bằng cách có các chế độ khác nhau và diễn giải các toán tử khác nhau trong chế độ boolean và tích phân. Đó là, một phép toán bitwise là một trên các kiểu số nguyên và một phép toán logic là một phép toán trên các kiểu boolean.
BCPL có một kiểu boolean riêng, nhưng một not
toán tử đơn , cho cả bitwise và logic thì không. Cách tiền thân đầu tiên này của C thực hiện công việc đó là:
Rvalue của true là một mẫu bit hoàn toàn bao gồm các mẫu; Giá trị sai là 0.
Lưu ý rằng true = ~ false
(Bạn sẽ quan sát rằng thuật ngữ rvalue đã phát triển có nghĩa là một cái gì đó hoàn toàn khác trong ngôn ngữ của gia đình C.
Định nghĩa này sẽ cho phép logic và bitwise không sử dụng cùng một hướng dẫn ngôn ngữ máy. Nếu C đã đi theo con đường đó, các tập tin tiêu đề trên toàn thế giới sẽ nói #define TRUE -1
.
Nhưng ngôn ngữ lập trình B là một cách yếu ớt, đánh máy, và không có kiểu boolean hoặc thậm chí dấu chấm động. Mọi thứ đều tương đương int
với người kế nhiệm của nó, C. Điều này khiến ngôn ngữ nên xác định điều gì đã xảy ra khi một chương trình sử dụng một giá trị khác với đúng hoặc sai làm giá trị logic. Đầu tiên, nó định nghĩa một biểu thức trung thực là không bằng 0. Nó hoạt động hiệu quả trên các máy tính mini mà nó chạy, có cờ không CPU.
Vào thời điểm đó, có một giải pháp thay thế: các CPU tương tự cũng có cờ âm và giá trị thật của BCPL là -1, vì vậy B có thể đã định nghĩa tất cả các số âm là trung thực và tất cả các số không âm là giả. (Có một phần còn lại của phương pháp này: nhiều cuộc gọi hệ thống trong UNIX, được phát triển bởi cùng một người cùng một lúc, định nghĩa tất cả các mã lỗi là số nguyên âm. Nhiều cuộc gọi hệ thống của nó trả về một trong nhiều giá trị âm khác nhau khi thất bại.) hãy biết ơn: nó có thể tồi tệ hơn
Nhưng định nghĩa TRUE
như 1
và FALSE
như 0
trong B có nghĩa là bản sắc true = ~ false
không còn được tổ chức, và nó đã đánh rơi gõ mạnh cho phép Algol để disambiguate giữa Bitwise và biểu thức logic. Điều đó đòi hỏi một toán tử không logic mới và các nhà thiết kế đã chọn !
, có thể vì chưa bằng - đã có !=
, trông giống như một thanh dọc thông qua một dấu bằng. Họ đã không tuân theo quy ước tương tự &&
hoặc ||
bởi vì chưa có ai tồn tại.
Có thể cho rằng, họ nên có: &
toán tử trong B bị hỏng như thiết kế. Trong B và C, 1 & 2 == FALSE
mặc dù 1
và 2
cả hai đều là giá trị trung thực và không có cách trực quan nào để diễn tả hoạt động logic trong B. Đó là một sai lầm mà C đã cố gắng khắc phục một phần bằng cách thêm &&
và ||
, nhưng mối quan tâm chính lúc đó là cuối cùng có được ngắn mạch để làm việc và làm cho các chương trình chạy nhanh hơn. Bằng chứng của điều này là không có ^^
: 1 ^ 2
là một giá trị trung thực mặc dù cả hai toán hạng của nó là trung thực, nhưng nó không thể hưởng lợi từ ngắn mạch.