Tại sao chúng ta phải đề cập đến kiểu dữ liệu của biến trong C


19

Thông thường trong C, chúng ta phải nói với máy tính loại dữ liệu trong khai báo biến. Ví dụ, trong chương trình sau, tôi muốn in tổng của hai số dấu phẩy động X và Y.

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

Tôi đã phải nói với trình biên dịch loại biến X.

  • Trình biên dịch có thể tự xác định loại của Xnó không?

Vâng, nó có thể nếu tôi làm điều này:

#define X 5.2

Bây giờ tôi có thể viết chương trình của mình mà không cần nói cho trình biên dịch loại Xnhư:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

Vì vậy, chúng ta thấy rằng ngôn ngữ C có một số loại tính năng, sử dụng ngôn ngữ này có thể tự xác định loại dữ liệu. Trong trường hợp của tôi, nó đã xác định đó Xlà loại float.

  • Tại sao chúng ta phải đề cập đến loại dữ liệu, khi chúng ta khai báo một cái gì đó trong hàm main ()? Tại sao trình biên dịch không thể tự xác định kiểu dữ liệu của một biến main()như trong #define.

14
Trên thực tế hai chương trình này không tương đương nhau, ở chỗ chúng có thể cho đầu ra hơi khác nhau! 5.2là một double, do đó, chương trình đầu tiên làm tròn hai chữ thành floatđộ chính xác, sau đó thêm chúng dưới dạng nổi, trong khi chương trình thứ hai làm tròn biểu diễn kép 5.1 trở lại doublevà thêm vào doublegiá trị 5.2 bằng cách sử dụng phép doublecộng, sau đó làm tròn kết quả của phép tính đó thành floatđộ chính xác . Bởi vì làm tròn xảy ra ở những nơi khác nhau, kết quả có thể khó khăn hơn. Đây chỉ là một ví dụ cho các loại biến ảnh hưởng đến hành vi của một chương trình giống hệt nhau.

12
Khi bạn làm #define X 5.2, Xkhông phải là một biến, mà là một hằng số, vì vậy nó được thay thế theo nghĩa đen là tiền xử lý với 5.2bất kỳ nơi nào bạn đề cập X. Bạn không thể chỉ định lại X.
scriptin

16
Cũng như một ghi chú: đây là một phước lành và một lời nguyền. Một mặt, bạn phải nhập một vài ký tự khi trình biên dịch thực sự có thể đã làm điều đó cho bạn (C ++ autothực sự làm những gì bạn muốn). Mặt khác, nếu bạn nghĩ rằng bạn biết mã của mình đang làm gì và bạn thực sự đã gõ một cái gì đó khác, việc gõ tĩnh như thế này sẽ bắt lỗi sớm hơn, trước khi nó trở thành một vấn đề lớn. Mỗi ngôn ngữ tạo ra sự cân bằng: gõ tĩnh, gõ vô sinh, gõ động. Đối với một số tác vụ, việc gõ thêm thực sự đáng giá. Đối với những người khác, đó là một sự lãng phí.
Cort Ammon - Phục hồi Monica

Tìm hiểu Ocaml và / hoặc Haskell .... bạn sẽ hài lòng về khả năng suy luận kiểu của họ.
Basile Starynkevitch

Câu trả lời:


46

Bạn đang so sánh các khai báo biến với #defines, không chính xác. Với a #define, bạn tạo ánh xạ giữa mã định danh và đoạn mã nguồn. Bộ tiền xử lý C sau đó sẽ thay thế theo nghĩa đen bất kỳ lần xuất hiện nào của số nhận dạng đó bằng đoạn mã được cung cấp. Viết

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

kết thúc là điều tương tự với trình biên dịch khi viết

int foos = 40 + 2 + 40 + 2 * 40 + 2;

Hãy nghĩ về nó như là sao chép và dán tự động.

Ngoài ra, các biến thông thường có thể được chỉ định lại, trong khi macro được tạo bằng #definekhông thể (mặc dù bạn có thể xác định lại #define). Biểu thức FOO = 7sẽ là một lỗi trình biên dịch, vì chúng ta không thể gán cho rvalues ​​phạm lỗi: 40 + 2 = 7là bất hợp pháp.

Vì vậy, tại sao chúng ta cần các loại? Một số ngôn ngữ dường như loại bỏ các loại, điều này đặc biệt phổ biến trong các ngôn ngữ kịch bản. Tuy nhiên, họ thường có một cái gì đó được gọi là động động gõ gõ trong đó các biến không có kiểu cố định, nhưng các giá trị thì có. Trong khi điều này linh hoạt hơn nhiều, nó cũng ít hiệu suất hơn. C thích hiệu suất, vì vậy nó có một khái niệm biến rất đơn giản và hiệu quả:

Có một dải bộ nhớ được gọi là stack stack. Mỗi biến cục bộ tương ứng với một khu vực trên ngăn xếp. Bây giờ câu hỏi là khu vực này phải có bao nhiêu byte? Trong C, mỗi loại có kích thước được xác định rõ mà bạn có thể truy vấn thông qua sizeof(type). Trình biên dịch cần biết loại của từng biến để có thể dự trữ đúng dung lượng trên ngăn xếp.

Tại sao các hằng số được tạo không #definecần chú thích kiểu? Chúng không được lưu trữ trên ngăn xếp. Thay vào đó, hãy #definetạo các đoạn mã nguồn có thể tái sử dụng theo cách dễ bảo trì hơn một chút so với sao chép và dán. Các chữ trong mã nguồn như "foo"hoặc 42.87được trình biên dịch lưu trữ dưới dạng nội tuyến dưới dạng các hướng dẫn đặc biệt hoặc trong một phần dữ liệu riêng biệt của tệp nhị phân kết quả.

Tuy nhiên, nghĩa đen làm gì có loại. Một chuỗi ký tự là a char *. 42là một intnhưng cũng có thể được sử dụng cho các loại ngắn hơn (thu hẹp chuyển đổi). 42.8sẽ là một double. Nếu bạn có một nghĩa đen và muốn nó có một loại khác (ví dụ để tạo 42.8một floathoặc 42một unsigned long int), thì bạn có thể sử dụng hậu tố - một chữ cái sau nghĩa đen thay đổi cách trình biên dịch xử lý nghĩa đen đó. Trong trường hợp của chúng tôi, chúng tôi có thể nói 42.8fhoặc 42ul.

Một số ngôn ngữ có kiểu gõ tĩnh như trong C, nhưng chú thích loại là tùy chọn. Ví dụ là ML, Haskell, Scala, C #, C ++ 11 và Go. Làm thế nào mà làm việc? Ma thuật? Không, cái này được gọi là kiểu suy luận. Trong C # và Go, trình biên dịch nhìn vào phía bên phải của một bài tập và suy ra kiểu đó. Điều này khá đơn giản nếu phía bên tay phải là một nghĩa đen như 42ul. Sau đó, rõ ràng loại biến là gì. Các ngôn ngữ khác cũng có các thuật toán phức tạp hơn có tính đến cách sử dụng một biến. Ví dụ, nếu bạn làm như vậy x/2, thì xkhông thể là một chuỗi nhưng phải có một số loại số.


Cảm ơn bạn đã giải thích. Điều tôi hiểu là khi chúng ta khai báo loại biến (cục bộ hoặc toàn cầu), chúng ta thực sự đang nói với trình biên dịch, nó nên dành bao nhiêu dung lượng cho biến đó trong ngăn xếp. Mặt khác, #definechúng ta đang có một hằng số được chuyển đổi trực tiếp thành mã nhị phân - tuy nhiên nó có thể tồn tại lâu - và nó được lưu trữ trong bộ nhớ như hiện tại.
dùng106313

2
@ user31782 - không hẳn. Khi bạn khai báo một biến, kiểu sẽ cho trình biên dịch biết các thuộc tính của biến đó. Một trong những tính chất đó là kích thước; các thuộc tính khác bao gồm cách nó thể hiện các giá trị và các hoạt động nào có thể được thực hiện trên các giá trị đó.
Pete Becker

@PeteBecker Sau đó, trình biên dịch biết các thuộc tính khác này #define X 5.2như thế nào?
dùng106313

1
Đó là bởi vì bằng cách chuyển loại sai cho printfbạn đã gọi hành vi không xác định. Trên máy của tôi, đoạn trích đó in một giá trị khác nhau mỗi lần, trên Ideone, nó bị hỏng sau khi in số không.
Matteo Italia

4
@ user31782 - "Có vẻ như tôi có thể thực hiện bất kỳ thao tác nào trên bất kỳ loại dữ liệu nào" Không X*Yhợp lệ nếu XYlà con trỏ, nhưng không sao nếu chúng là ints; *Xkhông hợp lệ nếu Xlà một int, nhưng không sao nếu đó là một con trỏ.
Pete Becker

4

X trong ví dụ thứ hai không bao giờ là float. Nó được gọi là macro, nó thay thế giá trị macro được xác định 'X' trong nguồn bằng giá trị. Một bài viết dễ đọc trên #define có ở đây .

Trong trường hợp mã được cung cấp, trước khi biên dịch, bộ tiền xử lý sẽ thay đổi mã

Z=Y+X;

đến

Z=Y+5.2;

và đó là những gì được biên dịch.

Điều đó có nghĩa là bạn cũng có thể thay thế các 'giá trị' đó bằng mã như

#define X sqrt(Y)

hoặc thậm chí

#define X Y

3
Nó chỉ được gọi là một macro, không phải là một macro macro. Một macro matrixdic là một macro có số lượng đối số thay đổi, ví dụ #define FOO(...) { __VA_ARGS__ }.
hvd

2
Xấu của tôi, sẽ sửa :)
James Snell

1

Câu trả lời ngắn gọn là C cần các loại vì lịch sử / đại diện cho phần cứng.

Lịch sử: C được phát triển vào đầu những năm 1970 và dự định là ngôn ngữ để lập trình hệ thống. Mã là lý tưởng nhanh chóng và sử dụng tốt nhất các khả năng của phần cứng.

Có thể suy ra các kiểu trong thời gian biên dịch, nhưng thời gian biên dịch chậm đã tăng lên (tham khảo phim hoạt hình 'biên dịch' của XKCD. Điều này được sử dụng để 'xin chào thế giới' trong ít nhất 10 năm sau khi C được xuất bản ). Suy ra các kiểu trong thời gian chạy sẽ không phù hợp với mục tiêu của lập trình hệ thống. Thời gian vô sinh yêu cầu thư viện thời gian chạy bổ sung. C đến rất lâu trước khi PC đầu tiên. Trong đó có 256 RAM. Không phải Gigabyte hay Megabyte mà là Kilobytes.

Trong ví dụ của bạn, nếu bạn bỏ qua các loại

   X=5.2;
   Y=5.1;

   Z=Y+X;

Sau đó, trình biên dịch có thể vui vẻ làm việc rằng X & Y là float và làm cho Z giống nhau. Trên thực tế, một trình biên dịch hiện đại cũng sẽ tìm ra rằng X & Y không cần thiết và chỉ cần đặt Z thành 10.3.

Giả sử rằng phép tính được nhúng bên trong một hàm. Người viết hàm có thể muốn sử dụng kiến ​​thức của họ về phần cứng hoặc vấn đề đang được giải quyết.

Một đôi sẽ thích hợp hơn một phao? Mất nhiều bộ nhớ hơn và chậm hơn nhưng độ chính xác của kết quả sẽ cao hơn.

Có thể giá trị trả về của hàm có thể là int (hoặc dài) vì số thập phân không quan trọng, mặc dù việc chuyển đổi từ float sang int không phải là không có chi phí.

Giá trị trả về cũng có thể được thực hiện gấp đôi để đảm bảo rằng float + float không bị tràn.

Tất cả những câu hỏi này dường như vô nghĩa đối với phần lớn mã được viết ngày hôm nay, nhưng rất quan trọng khi C được sản xuất.


1
điều này không giải thích, ví dụ tại sao khai báo kiểu không được tùy chọn, cho phép lập trình viên chọn khai báo chúng một cách rõ ràng hoặc dựa vào trình biên dịch để suy ra
gnat

1
Thật vậy, nó không @gnat. Tôi đã điều chỉnh văn bản nhưng không có điểm nào để làm như vậy vào lúc đó. Miền C được thiết kế để thực sự muốn quyết định lưu trữ 17 trong 1 byte, hoặc 2 byte hoặc 4 byte hoặc dưới dạng chuỗi hoặc 5 bit trong một từ.
itj

0

C không có suy luận kiểu (đó là những gì nó được gọi khi trình biên dịch đoán loại biến cho bạn) vì nó đã cũ. Nó được phát triển vào đầu những năm 1970

Nhiều ngôn ngữ mới hơn có các hệ thống cho phép bạn sử dụng các biến mà không chỉ định loại của chúng (ruby, javascript, python, v.v.)


12
Không có ngôn ngữ nào bạn đề cập (Ruby, JS, Python) có kiểu suy luận như một tính năng ngôn ngữ, mặc dù việc triển khai có thể sử dụng nó để tăng hiệu quả. Thay vào đó, họ sử dụng kiểu gõ động trong đó các giá trị có kiểu, nhưng các biến hoặc biểu thức khác thì không.
amon

2
JS không cho phép bạn bỏ qua loại này - nó chỉ không cho phép bạn khai báo nó. Nó sử dụng kiểu gõ động, trong đó các giá trị có kiểu (ví dụ trueboolean), không phải biến (ví dụ: var xcó thể chứa giá trị của bất kỳ loại nào). Ngoài ra, loại suy luận cho các trường hợp đơn giản như những câu hỏi có lẽ đã được biết đến một thập kỷ trước khi C được phát hành.
scriptin

2
Điều đó không làm cho tuyên bố sai (để buộc một cái gì đó bạn cũng phải cho phép nó). Kiểu suy luận hiện tại không thay đổi thực tế rằng hệ thống loại C là kết quả của bối cảnh lịch sử của nó (trái ngược với lý luận triết học hoặc giới hạn kỹ thuật được nêu cụ thể)
Tristan Burnside

2
Xem xét rằng ML - vốn khá lâu đời như C - có kiểu suy luận, "nó cũ" không phải là một lời giải thích tốt. Bối cảnh trong đó C đã được sử dụng và phát triển (các máy nhỏ đòi hỏi một dấu chân rất nhỏ cho trình biên dịch) có vẻ nhiều khả năng hơn. Không biết tại sao bạn lại đề cập đến các ngôn ngữ gõ động thay vì chỉ một số ví dụ về các ngôn ngữ có kiểu suy luận - Haskell, ML, heck C # có nó - hầu như không còn là một tính năng tối nghĩa nữa.
Voo

2
@BradS. Fortran không phải là một ví dụ hay vì chữ cái đầu tiên của tên biến khai báo kiểu, trừ khi bạn sử dụng implicit nonetrong trường hợp đó bạn phải khai báo một kiểu.
dmckee
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.