Mục đích của mảng trong C là gì, khi con trỏ có thể thực hiện công việc?


8

Mảng và con trỏ không giống nhau trong C, mặc dù chúng có liên quan và có thể được sử dụng tương tự nhau. Cho đến nay tất cả chúng ta đều đồng ý.

Tuy nhiên, tôi không thấy lý do tại sao các mảng được đưa vào C, khi các con trỏ có thể hoàn thành công việc của chúng một cách hoàn hảo.

Tôi không nói để loại bỏ ký hiệu mảng (ví dụ: a [5] hoặc int a [4] = {0,1,2,3};), khá hữu ích và thuận tiện. Nhưng bạn có thể có cùng một ký hiệu làm việc trên đầu con trỏ (như trường hợp), như một biện pháp thẩm mỹ. Vì vậy, ký hiệu mảng không phải là lý do để có mảng, chỉ là ký hiệu!

Sự khác biệt duy nhất tôi thấy là các mảng là các con trỏ không đổi và kích thước của bộ nhớ mà chúng trỏ tới không thể thay đổi. Nhưng điều này cũng có thể đạt được với các con trỏ, chính xác bằng cách làm cho chúng không đổi (bộ nhớ sẽ không có kích thước cố định, nhưng tôi không chắc đây có phải là vấn đề không).

Vậy tại sao không chỉ có con trỏ và để người lập trình quyết định con trỏ nên hành xử như thế nào (nghĩa là không đổi, không phải là hằng số, kích thước cố định, kích thước thay đổi, v.v.)?


6
Tại sao có char khi bạn có thể đạt được điều tương tự với byte?
WuHoUnited

2
Tuy nhiên, trên một lưu ý nghiêm trọng hơn, làm thế nào để bạn có ý định hoàn thành việc cấp phát bộ nhớ bằng một con trỏ?
WuHoUnited

5
Cá nhân, tôi nghĩ về con trỏ như (đôi khi) giả vờ là mảng hơn là ngược lại. Trong nhiều ngôn ngữ khác, con trỏ không có hành vi giống như mảng - nếu bạn muốn truy cập một mảng thông qua một con trỏ, bạn có thể sử dụng loại con trỏ đến mảng hoặc bạn thực hiện số học con trỏ (tính theo byte, không phải các phần tử). Tôi đã đặt cược một số tiền kha khá mà các nhà phát minh của C nghĩ về việc điều chỉnh con trỏ để có hành vi giống như mảng thuận tiện hơn - tôi rất nghi ngờ họ đã có hành vi con trỏ giống như mảng rồi quyết định "Tôi biết, hãy thêm mảng quá".
Steve314

3
Tại sao có 0xAB khi bạn có thể đạt được điều tương tự với 171?
e-MEE

2
Tại sao có các biểu thức ghép như x = a + b * 2;khi bạn có thể đạt được điều tương tự với một chuỗi các biểu thức đơn giản như thế x = b; x*=2; x+=a;nào?
fortran

Câu trả lời:


14

Mảng là bộ nhớ liền kề được tạo trên ngăn xếp. Bạn không thể đảm bảo bộ nhớ ngăn xếp liền kề mà không có đường cú pháp này và thậm chí nếu có thể, bạn phải phân bổ một con trỏ riêng để có thể thực hiện số học con trỏ (trừ khi bạn muốn làm *(&foo + x), điều mà tôi không làm chắc chắn nhưng nó có thể vi phạm ngữ nghĩa giá trị l, nhưng ít nhất là khá lúng túng, và sẽ hét lên vì một loại đường cú pháp). Thiết kế khôn ngoan, nó cũng là một hình thức đóng gói, vì bạn có thể tham khảo bộ sưu tập với một mã định danh duy nhất (nếu không sẽ yêu cầu một con trỏ riêng). Và ngay cả khi bạn có thể phân bổ chúng liên tục và phân bổ một con trỏ riêng để tham chiếu chúng, bạn sẽ có int fooForSomething, fooForSomethingElse... điều đó buộc một lượng sáng tạo khá lớn khi bộ sưu tập của bạn phát triển, vì vậy bạn có thể nghĩ đơn giản hóa với int foo1, foo2 ..., Trông giống như một mảng nhưng khó bảo trì hơn.


Ồ, và chuyển theo giá trị cho foo1, foo2 ... trở nên lộn xộn nhanh chóng, đặc biệt nếu bạn muốn cho phép số lượng thành viên thay đổi.
kylben

2
Chỉ các mảng được khai báo là tự động kết thúc trên ngăn xếp. Bất cứ điều gì khác (ví dụ, statics) thường kết thúc ở nơi khác tùy thuộc vào môi trường.
Blrfl

Nó không phải là "đường cú pháp", nó sâu hơn nhiều - một phần quan trọng của hệ thống loại C, cũng như sự phân rã mảng khét tiếng.
SK-logic

1
sự khác biệt giữa một mảng trên stack và alloca ()
sylvanaar

@sylvanaar, hãy xem câu hỏi SO này: stackoverflow.com/questions/1018853/ Khăn tôi thực sự chưa bao giờ nghe về nó trước đây, nhưng rõ ràng là không chuẩn và nguy hiểm theo cách mà tôi cho là một điều nguy hiểm như vậy.
kylben

12

Ký hiệu mảng thuận tiện, dễ đọc hơn và ít bị lỗi hơn. Nó cung cấp một chủ nghĩa hình thức trên con trỏ. Nó có thể là đường cú pháp, nhưng tất cả chúng ta cần một chút vị ngọt một lần trong một thời gian, phải không?

Như với tất cả các khái niệm trừu tượng, bạn từ bỏ một chút linh hoạt cho sự thuận tiện mà sự trừu tượng cung cấp.


1
Như tôi đã đề cập trong câu hỏi, tôi không chống lại ký hiệu mảng. Nhưng ký hiệu tương tự đó có thể hoạt động trên các con trỏ, vậy nhu cầu về mảng là ở đâu?
Daniel Scocco

1
@DanielScocco - Có lẽ nó có thể. Nhưng nó đã được thực hiện như thế này, tuy nhiên. Kết thúc câu chuyện. Không ai có thể thực sự trả lời điều này, vì không ai biết những gì đang diễn ra trong đầu của tác giả tại thời điểm đó.
Rook

10

Tôi ngạc nhiên khi chưa có ai bình luận gì về mảng đa chiều.

Nếu bạn có một ma trận được tạo từ "con trỏ lồng nhau" (giả sử int **p) những gì bạn có trong mỗi "hàng" (thứ nguyên bên ngoài) là một con trỏ trỏ đến phần tử đầu tiên trong hàng đó, vì vậy việc truy cập một giá trị cần có hai truy cập bộ nhớ. Thêm vào đó, bộ nhớ nó yêu cầu là sizeof(*int)*n + n*m*sizeof(int).

Trong kịch bản mảng hai chiều int p[n][m], việc truy cập một phần tử chỉ cần một lần truy cập bộ nhớ, vì địa chỉ của hàng được tính thay vì tra cứu; và bộ nhớ cần thiết là chỉ n*m*sizeof(int).

Một nơi khác mà một mảng không thể được thay thế bằng một con trỏ nằm trong các cấu trúc.

struct s {
    int[2];
    float;
}

chắc chắn không giống như

struct s {
   *int;
   float;
}

kích thước của mảng là quan trọng ở đó và con trỏ không có thông tin đó.

Vì vậy, có, mảng đơn chiều và con trỏ đơn hầu hết có thể thay thế cho nhau, nhưng sự tương đồng của chúng kết thúc ở đó.


Cho chúng tôi thấy sự khác biệt trong lắp ráp. Các mảng đa chiều, đóng gói cấu trúc và đệm tất cả chỉ là những cách để cung cấp một khung tham chiếu có ý nghĩa đối với những người phải hiểu và sử dụng ngôn ngữ.
sylvanaar

@sylvanaar bạn có nghiêm túc không? Bạn có thực sự cần phải xem trình biên dịch chương trình để chắc chắn rằng cấu trúc đầu tiên giữ không gian cho hai số nguyên và số float, và cấu trúc thứ hai chỉ để lưu địa chỉ bộ nhớ và số float?
fortran

1
Tôi không theo bạn. Điều gì phải làm tập lệnh với tất cả điều này? Các op đã hỏi những gì bạn có thể làm với mảng chứ không phải với con trỏ và việc khai báo một cấu trúc với một mảng có kích thước cố định không thể được mô phỏng bằng các con trỏ. Về mảng đa chiều, tất nhiên bạn có thể có chỉ một đoạn bộ nhớ và tính toán các chỉ số của tế bào i,jbằng i*cols+j, nhưng tôi nghĩ rằng không cần phải làm điều đó một mình là đủ lý do chính đáng để biện minh cho sự tồn tại của kiểu mảng.
fortran

2
"Khai báo cấu trúc [...] không tạo phân bổ bộ nhớ tĩnh trong ảnh đã biên dịch" Không, khai báo một biến (thuộc kiểu cấu trúc hoặc bất kỳ loại nào khác) thực hiện việc này. Khai báo một cấu trúc chỉ cho trình biên dịch biết loại đó lớn như thế nào và những gì bù đắp cho mỗi thành viên của nó; bạn có thể không bao giờ phân bổ một hoặc chỉ phân bổ chúng dưới dạng các biến cục bộ.
Random832

1
+1 để xác định đồng thời giá trị thực tế của việc có các mảng có kích thước cố định là một "loại" theo như trình biên dịch có liên quan - các mảng đa chiều, và đặc biệt là bạn có thể lập chỉ mục một cách tầm thường vào chúng với trình biên dịch thực hiện số học con trỏ với kích thước phù hợp với bạn, thoát ra một cách tự nhiên và thanh lịch từ các quy tắc ngôn ngữ đơn giản khác bằng cách biến mảng thành một "loại" thực sự - bạn có thể đạt được cùng một cú pháp cú pháp mà không cần điều đó, nhưng logic phải là trường hợp đặc biệt hơn, cả hai trong trình biên dịch và trong đặc tả ngôn ngữ.
mtraceur

2

Tại sao tôi muốn không thể sử dụng mảng cho các loại giá trị?

int a[4] = {0,1,2,3};


Bạn vẫn có thể sử dụng nó ngay cả khi chúng tôi chỉ có con trỏ, phải không? Trình biên dịch sẽ chỉ cần biết phải làm gì.
Daniel Scocco

2
@DanielScocco và làm thế nào để biết phải làm gì? mảng
ratchet freak

Đồng ý - bạn sẽ gọi tính năng nào dành không gian cho một chuỗi các mục cùng loại là biến cục bộ hoặc toàn cục, nếu không phải là một mảng? Một loại con trỏ là một loại con trỏ - nó không xác định xem những gì nó trỏ đến là một chuỗi, hoặc chuỗi có kích thước.
Steve314

4
@DanielScocco Điều gì đi kèm với một mảng không đi kèm với một con trỏ là thông tin về kích thước. Vì vậy, một cặp (con trỏ, kích thước) hoặc một cặp (con trỏ, con trỏ) thực sự có thể chứa nhiều thông tin như tên của một mảng. Cho dù điều này có nghĩa là mảng cặp con trỏ (hoặc ngược lại) có vẻ giống như một câu hỏi siêu hình, vì vậy tôi không thực sự quan tâm đến việc trả lời điều đó. (Câu trả lời có lẽ là không vì các cặp con trỏ trông giống như các lát cắt và do đó có thể làm được nhiều hơn là chỉ tham chiếu đến một mảng.)
Luc Danton

1
@Daniel Scocco, mảng có kích thước . Bạn có thể phân bổ một mảng trên ngăn xếp, bạn có thể đặt một mảng vào một cấu trúc liền kề, bạn có thể sử dụng nó trong một liên minh. Nếu kiểu mảng không tồn tại, nó sẽ không thể thực hiện được.
SK-logic

1

Làm thế nào bạn có thể xử lý các nền tảng, như 8031 ​​mà không có bộ nhớ ngoài, không hỗ trợ mallochay alloca? Có lẽ bạn đang quên rằng C không chỉ dành cho bàn ủi lớn mà còn dành cho bộ điều khiển thang máy và lò nướng bánh.


1

Trong C, ký hiệu mảng trong các biểu thức luôn chỉ đơn giản là số học con trỏ. Tất cả việc sử dụng một định danh mảng trong một biểu thức ngay lập tức được chuyển đổi từ "mảng T" thành "con trỏ thành T" và giá trị được chuyển đổi thành một con trỏ thành phần tử đầu tiên của mảng. Ký hiệu mảng (vd a[1][2]) luôn được mở rộng thành số học con trỏ (vd *(*(t+1)+2)).

Tuy nhiên, ký hiệu mảng trong khai báo và định nghĩa là một cái gì đó hoàn toàn khác. Một declarator mảng mô tả một "mảng của T " loại nơi các giá trị của loại hình này là sự nối tiếp của phần tử kiểu T . Một định nghĩa của một đối tượng mảng là tất cả về việc sử dụng ký hiệu mảng thuận tiện và dễ hiểu để phân bổ lượng lưu trữ thích hợp cho mảng đối tượng mong muốn sao cho định danh mảng tham chiếu đến bộ lưu trữ này mà không trông giống như một con trỏ. Trong ký hiệu mảng hiệu ứng trong một khai báo hoặc định nghĩa là một macro tạo ra một biểu thức sử dụng sizeof()và số học, và trong trường hợp của một định nghĩa, tương đương vớialloca() cho các mảng tự động hoặc tương đương của nó trong trình liên kết cho các mảng toàn cục và thực hiện tất cả tại thời gian biên dịch (ngoại trừ các mảng có độ dài biến C99).

Việc sử dụng ký hiệu mảng trong biểu thức và sử dụng các kiểu mảng không được kết nối chặt chẽ, mặc dù đó là truyền thống và thành ngữ để sử dụng ký hiệu mảng trong biểu thức để lưu trữ tham chiếu trong các đối tượng được khai báo và / hoặc được định nghĩa là mảng. Bạn có thể dễ dàng sử dụng ký hiệu mảng với một loại con trỏ để làm cho con trỏ số học sạch hơn và có ý nghĩa hơn. Thật vậy, trong C một biểu thức có dạng e1[e2]tương đương chính xác với biểu thức *((e1)+(e2)). Chuyển đổi nhị phân thông thường được áp dụng cho hai toán hạng và kết quả luôn luôn là một giá trị . Do toán tử indirection ( *) phải có một con trỏ là toán hạng của nó, một trong e1e2phải là một con trỏ và cái kia phải là một số nguyên, nhưng nó không quan trọngvì chuyển đổi đơn nguyên ban đầu cho bất kỳ "mảng T" nào là chuyển đổi nó thành "con trỏ thành T". Ký hiệu mảng trong biểu thức có hiệu lực là macro của trình biên dịch (cấp ngôn ngữ) để tạo các biểu thức số học con trỏ.

Vì vậy, thực sự C đã làm việc theo cách bạn đề xuất nhưng bạn đang nhầm lẫn việc sử dụng ký hiệu trong hai bối cảnh rất riêng biệt.

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.