2009-05-08 6 views
5

Trên trang pg. 109 của K & R, chúng tôi thấy:K & R: mảng con trỏ ký tự

void writelines(char *lineptr[], int nlines) 
{ 
    while (nlines -- > 0) printf("%s\n", *lineptr++); 
} 

Tôi nhầm lẫn về những gì * lineptr ++ thực hiện chính xác. Từ sự hiểu biết của tôi, printf yêu cầu một con trỏ char, vì vậy chúng tôi cung cấp với * lineptr. Sau đó, chúng tôi tăng lineptr lên đến con trỏ char tiếp theo trong mảng? Điều này có bất hợp pháp không?

Trên trang 99, K & R viết rằng "tên mảng không phải là biến; công trình xây dựng như a = pa [trong đó a là mảng, pa là con trỏ tới mảng] và ++ là bất hợp pháp".

Trả lời

4

Câu trả lời đã chọn của Adam Rosenfield là sai. Câu trả lời của coobird cũng vậy. Vì lý do đó tôi đã bỏ phiếu bình chọn cả hai câu trả lời.

Giải thích Adam Markowitz của *lineptr++ là đúng, nhưng anh ấy không trả lời câu hỏi chính cho dù đây là mã C99 hợp pháp hay không. Chỉ Tom Future mới có; không may là anh ấy không giải thích được *lineptr++. Tôi đã cho họ một điểm.

Vì vậy, trong ngắn hạn, lineptr là một biến và có thể được thao tác như một con trỏ. Do đó, hợp pháp để tăng con trỏ.

lineptr là một con trỏ vào một chuỗi các con trỏ tới chuỗi các ký tự. Nói cách khác, nó là một con trỏ đến chuỗi đầu tiên của một mảng chuỗi. Theo mã, chúng ta có thể giả định rằng các chuỗi là null ('\ 0') kết thúc chuỗi char. nlines là số chuỗi trong mảng.

Biểu thức kiểm tra trong khi là nlines-- > 0. nlines-- là một bài đăng giảm (vì -- nằm ở bên phải của biến). Do đó, nó được thực hiện sau kiểm tra đã được thực hiện và bất kể kết quả thử nghiệm, vì vậy trong mọi trường hợp.

Vì vậy, nếu giá trị nlines được cho làm đối số là 0, thử nghiệm được thực hiện trước và trả về false; các lệnh trong vòng lặp không được thực hiện. Lưu ý rằng kể từ khi nlines bị giảm sút, giá trị của nlines sau vòng lặp while sẽ là -1.

Nếu nlines == 1, kiểm tra sẽ trả lại truenlines sẽ bị giảm; các lệnh trong vòng lặp sẽ được thực hiện một lần. Lưu ý rằng mặc dù các hướng dẫn này được thực thi giá trị của nlines0. Khi thử nghiệm được thực hiện lại, chúng tôi quay lại trường hợp khi nlines == 0.

Hướng dẫn printf sử dụng biểu thức *lineptr++. Nó là một sự gia tăng bài của con trỏ (++ là ở bên phải của biến). Điều này có nghĩa là biểu thức được đánh giá đầu tiên và gia số được thực hiện sau khi sử dụng. Vì vậy, trên thực hiện đầu tiên printf nhận được một bản sao của phần tử đầu tiên của mảng chuỗi, đó là một con trỏ đến char đầu tiên của chuỗi. lineptr chỉ được tăng lên sau đó. Lần sau, printf sẽ được thực hiện, lineptr điểm trên phần tử thứ hai và sẽ di chuyển đến phần tử thứ ba khi chuỗi thứ hai được in. Điều này có ý nghĩa bởi vì chúng tôi rõ ràng muốn in chuỗi đầu tiên. Nếu Adam Rosenfield đã đúng, chuỗi đầu tiên sẽ bị bỏ qua và cuối cùng chúng tôi sẽ cố gắng in chuỗi vượt quá chuỗi cuối cùng mà rõ ràng là một điều xấu để làm.

Vì vậy, hướng dẫn printf là một hình thức ngắn gọn về hai hướng dẫn sau đây

printf("%s\n", *lineptr); 
++lineptr; // or lineptr++, which is equivalent but not as good. lineptr += 1; is ok too. 

Lưu ý, như một quy luật của, mà khi trước và sau tăng tương đương trong hành động của họ, trước tăng là thích hợp hơn vì lý do hiệu suất. Các trình biên dịch sẽ cẩn thận để chuyển đổi nó cho bạn. Vâng, hầu hết thời gian. Nó là tốt hơn cho các nhà điều hành tiền thân mình, bất cứ khi nào có thể, vì vậy nó được sử dụng luôn. Lý do trở nên rõ ràng hơn khi bạn thực hiện một bài đăng và tự tăng giá trong C++.

+0

Cảm ơn bạn đã làm rõ ... Tôi đã tự hỏi liệu mình có phát điên hay không :) –

6

lineptr không thực sự là mảng; đó là một con trỏ đến một con trỏ. Lưu ý rằng các nhà điều hành postincrement ++ có độ ưu tiên cao hơn toán tử tham chiếu *, vì vậy những gì xảy ra trong lineptr++ là thế này:

  1. lineptr được tăng lên để trỏ đến phần tử tiếp theo trong mảng
  2. Kết quả của lineptr++ là giá trị cũ của lineptr, cụ thể là một con trỏ tới phần tử hiện tại trong mảng
  3. *lineptr++ dereferenced rằng, vì vậy nó là giá trị của phần tử hiện tại trong mảng

Do đó, vòng lặp while lặp lại trên tất cả các phần tử trong mảng được trỏ đến bởi lineptr (mỗi phần tử là char*) và in chúng ra.

+0

Ah, điều này có ý nghĩa. Cảm ơn mọi người đã trả lời câu hỏi của tôi! –

5

Nó có thể được dễ dàng hơn để tưởng tượng *lineptr++ như sau:

*(lineptr++) 

Ở đó, con trỏ đến mảng char* s được chuyển đến phần tử tiếp theo của mảng, có nghĩa là, từ lineptr[0], chúng tôi di chuyển đến lineptr[1]. (Các increment của con trỏ xảy ra sau khi dereferencing do thặng dư postfix của con trỏ.)

Vì vậy, về cơ bản, những điều sau đây xảy ra:

  1. lineptr[0] (loại char*) được lấy bằng cách Truy cập vào.
  2. Con trỏ được tăng lên thành phần tử tiếp theo của mảng. (Bằng cách tăng số postfix trên con trỏ lineptr.)
  3. Con trỏ bây giờ trỏ đến lineptr[1], lặp lại quy trình từ bước 1 lần nữa.
+0

Tôi nghĩ rằng * (lineptr ++) không phải là rất hữu ích ở đây. Điều đó ngụ ý rằng lineptr được tăng lên * trước * nó bị hủy bỏ. Những gì thực sự xảy ra là giống như hai nhiệm vụ riêng biệt: * lineptr, ++ lineptr. – Naaff

+0

Nếu bạn sử dụng (* lineptr) ++ bạn sẽ dereference và sau đó tăng. Các lineptr được con trỏ đến một mảng bạn chọn hàng đầu tiên bởi dereferencing và sau đó bạn tăng con trỏ để trỏ đến ký tự tiếp theo. Vì vậy, nếu con trỏ dòng là mảng của 3 dòng, khung của bạn sẽ in dòng đầu tiên 3 lần bắt đầu từ ký tự đầu tiên, thứ hai và thứ ba. * (Lineptr ++) sẽ in từng dòng. – stefanB

+0

* (lineptr ++) vẫn là một câu lệnh, do đó, dereferencing của con trỏ sẽ xảy ra trước khi post-increment diễn ra. Việc thêm dấu ngoặc đơn chỉ thay đổi quyền ưu tiên của toán tử. Trong trường hợp này, như được chỉ ra bởi câu trả lời của Adam Rosenfield, ++ là ưu tiên cao hơn *, vì vậy dấu ngoặc đơn chỉ phục vụ như là một dấu hiệu trực quan về hai điều xảy ra trong câu lệnh đơn đó. – coobird

7

Tiếp tục đọc! Ở dưới cùng của p. 99

Như thông số chính thức trong định nghĩa hàm,

char s[]; 

char *s; 

là tương đương; chúng ta thích cái sau vì nó nói rõ ràng hơn rằng tham số là một con trỏ.

I.e. bạn không bao giờ có thể vượt qua một mảng (mà không phải là một biến) cho một hàm. Nếu bạn khai báo một hàm trông giống như một mảng, nó thực sự lấy một con trỏ (trong đó một biến). Điều này thật ý nghĩa. Nó sẽ là kỳ quặc đối với một đối số hàm không phải là một biến - nó có thể có một giá trị khác nhau mỗi khi bạn gọi hàm đó vì vậy nó chắc chắn không phải là một hằng số!

+0

Vâng, bạn có thể chuyển một tên mảng vào một hàm, chỉ có một số hành động đằng sau hậu trường làm cho nó xuất hiện như thể bạn thực sự đã vượt qua trong một con trỏ. Nó có vẻ giống như đối phó hơn là có ý nghĩa. –

+0

Ồ đúng, tôi nhớ bây giờ. Mảng không được sao chép vào khung ngăn xếp của hàm, chỉ một con trỏ tới mảng đó. Cảm ơn lời nhắc! –