2008-11-01 40 views
12

Điều này nghe có vẻ giống như một câu hỏi ngớ ngẩn, nhưng tôi vẫn đang học C, vì vậy hãy chịu với tôi. :)Chuyển con trỏ/tham chiếu đến cấu trúc thành các hàm

Tôi đang làm việc trên chương 6 của K & R (cấu trúc), và do đó đến nay qua cuốn sách đã đạt được thành công lớn. Tôi quyết định làm việc với cấu trúc khá nhiều, và do đó đã làm rất nhiều công việc sớm trong chương với các ví dụ điểm và rect. Một trong những điều tôi muốn thử đã thay đổi chức năng canonrect (2nd Edition, p 131) hoạt động qua con trỏ, và do đó trả về void.

Tôi đã làm việc này, nhưng gặp phải một trục trặc, tôi hy vọng các bạn có thể giúp tôi. Tôi muốn canonRect để tạo một đối tượng hình chữ nhật tạm thời, thực hiện các thay đổi của nó, sau đó gán lại con trỏ nó được chuyển tới hình chữ nhật tạm thời, do đó đơn giản hóa mã.

Tuy nhiên, nếu tôi thực hiện điều đó, chế độ chỉnh sửa sẽ không thay đổi. Thay vào đó, tôi thấy mình tự tái tạo lại các trường của rect mà tôi chuyển vào, nó hoạt động.

mã sau:

#include <stdio.h> 

#define min(a, b) ((a) < (b) ? (a) : (b)) 
#define max(a, b) ((a) > (b) ? (a) : (b)) 

struct point { 
    int x; 
    int y; 
}; 

struct rect { 
    struct point lowerLeft; 
    struct point upperRight; 
}; 

// canonicalize coordinates of rectangle 
void canonRect(struct rect *r); 

int main(void) { 
    struct point p1, p2; 
    struct rect r; 

    p1.x = 10; 
    p1.y = 10; 
    p2.x = 20; 
    p2.y = 40; 
    r.lowerLeft = p2; // note that I'm inverting my points intentionally 
    r.upperRight = p1; 

    printf("Rectangle, lower left: %d, %d; upper right: %d %d\n\n", 
     r.lowerLeft.x, r.lowerLeft.y, r.upperRight.x, r.upperRight.y); 

    // can't pass a pointer, only a reference. 
    // (Passing pointers results in illegal indirection compile time errors) 
    canonRect(&r); 
    printf("Rectangle, lower left: %d, %d; upper right: %d %d\n\n", 
     r.lowerLeft.x, r.lowerLeft.y, r.upperRight.x, r.upperRight.y);  
} 

void canonRect(struct rect *r) { 
    struct rect temp; 
    temp.lowerLeft.x = min(r->lowerLeft.x, r->upperRight.x); 
    temp.lowerLeft.y = min(r->lowerLeft.y, r->upperRight.y); 
    temp.upperRight.x = max(r->lowerLeft.x, r->upperRight.x); 
    temp.upperRight.y = max(r->lowerLeft.y, r->upperRight.y); 

    r = &temp; // doesn't work; my passed-in rect remains the same 

    // I wind up doing the following instead, to reassign all 
    // the members of my passed-in rect 
    //r->lowerLeft = temp.lowerLeft; 
    //r->upperRight = temp.upperRight; 
} 

Vì vậy, đây là những câu hỏi sau:

  1. Tại sao r = &temp; không hoạt động? (Tôi nghĩ rằng điều này là do tôi chuyển qua tham chiếu thay vì một con trỏ; tôi có đúng là không thể sửa đổi được nhưng con trỏ là?)
  2. Tại sao tôi có thể gặp lỗi biên dịch gián tiếp bất hợp pháp nếu tôi thử để chuyển một con trỏ đến canonRect? (. IE, nếu tôi có canonRect(*r); trong main())

tôi nghi ngờ tôi đã biết câu trả lời cho # 1, nhưng # 2 giới làm bối rối cho tôi - Tôi nghĩ đó là pháp lý để vượt qua con trỏ xung quanh.

Dù sao ... xin vui lòng tha thứ cho C newb.

Trả lời

18

Tôi nghĩ rằng những gì bạn muốn làm điều này là:

void canonRect(struct rect *r) { 
    struct rect temp; 
    temp.lowerLeft.x = min(r->lowerLeft.x, r->upperRight.x); 
    temp.lowerLeft.y = min(r->lowerLeft.y, r->upperRight.y); 
    temp.upperRight.x = max(r->lowerLeft.x, r->upperRight.x); 
    temp.upperRight.y = max(r->lowerLeft.y, r->upperRight.y); 

    *r = temp; 
} 

Trong đoạn mã trên bạn đang thiết * r mà là loại rect để temp đó là loại rect.

Re 1: Nếu bạn muốn thay đổi thứ r trỏ đến bạn cần sử dụng con trỏ trỏ đến con trỏ. Nếu đó thực sự là những gì bạn muốn (xem ở trên, nó không thực sự là những gì bạn muốn) thì bạn phải chắc chắn để chỉ nó vào một cái gì đó trên đống. Nếu bạn trỏ nó đến một cái gì đó không được tạo ra với 'mới' hoặc malloc thì nó sẽ rơi ra khỏi phạm vi và bạn sẽ trỏ đến bộ nhớ không còn được sử dụng cho biến đó nữa.

Tại sao mã của bạn không hoạt động với r = & tạm thời?

Vì r là loại rect *. Điều đó có nghĩa rằng r là một biến chứa một địa chỉ bộ nhớ của những người bộ nhớ có chứa một rect. Nếu bạn thay đổi những gì r được trỏ đến, đó là tốt nhưng điều đó không thay đổi thông qua trong biến.

Re 2: * khi không được sử dụng trong khai báo kiểu là toán tử đơn nhất. Điều này có nghĩa rằng nó sẽ tra cứu những gì nằm bên trong địa chỉ của con trỏ. Vì vậy, bằng cách đi qua * r bạn không phải là đi qua một con trỏ ở tất cả.Trong khuôn mặt kể từ khi r không phải là một con trỏ, đó là cú pháp không hợp lệ.

+0

Ehhh ... Tôi phải có vấn đề về não hiện nay. Việc chuyển một con trỏ tới một con trỏ sẽ là canonRect (** r) ;, không? Cùng một lỗi gián tiếp bất hợp pháp. –

+0

Wow; chỉnh sửa tuyệt vời! Cảm ơn! –

+0

Chỉ cần thử nghiệm nó; đây là một. Cảm ơn bạn đã giải thích rất kỹ lưỡng. –

12

Điều đáng lưu ý là biến của bạn 'struct rec temp' sẽ biến mất khỏi phạm vi ngay khi phương thức canonRect kết thúc và bạn sẽ trỏ đến bộ nhớ không hợp lệ.

+0

Đó là một điểm tốt mà tôi đã không nghĩ đến. Nắm bắt tốt! Cảm ơn! –

4

Có vẻ như bạn đang nhầm lẫn giữa toán tử 'dereference' (*) với toán tử 'address of' (&).

Khi bạn viết &r, lấy địa chỉ của r và trả về con trỏ đến r (con trỏ chỉ là địa chỉ bộ nhớ của một biến). Vì vậy, bạn thực sự đang đi qua một con trỏ vào hàm.

Khi bạn viết *r, bạn đang cố gắng dereference r. Nếu r là một con trỏ, nó sẽ trả về giá trị mà r trỏ đến. Nhưng r không phải là một con trỏ, nó là một rect, vì vậy bạn sẽ nhận được một lỗi.

Để làm cho mọi thứ trở nên khó hiểu hơn, ký tự * cũng được sử dụng khi khai báo biến con trỏ. Trong tuyên bố chức năng này:

void canonRect(struct rect *r) { 

r được tuyên bố là một con trỏ đến một struct rect. Đây là hoàn toàn khác nhau từ việc sử dụng * như thế này:

canonRect(*r); 

Trong cả hai trường hợp, nhân vật * có nghĩa là một cái gì đó hoàn toàn khác nhau.

+0

Người gọi có trách nhiệm giải phóng tất cả bộ nhớ động –

2

Bạn có thể muốn đọc các thông số cách khác nhau (khái niệm) có thể được chuyển đến các hàm. C là call-by-value, vì vậy khi bạn chuyển con trỏ của bạn đến một rect vào hàm bạn đang truyền một bản sao của con trỏ. Bất kỳ thay đổi nào của hàm làm cho giá trị r trực tiếp (không gián tiếp) sẽ không hiển thị đối với người gọi.

Nếu bạn muốn chức năng cung cấp cho người gọi một cấu trúc mới thì có hai cách để thực hiện: 1. bạn có thể trả về số: 2. bạn có thể chuyển con trỏ tới con trỏ :

cách thứ nhất sẽ tự nhiên hơn:

struct rect* canonRect(struct rect* r) 
{ 
    struct rect* cr = (struct rect*) malloc(sizeof(struct rect)); 
    ... 
    return cr; 
} 

cách thứ hai sẽ là:

void canonRect(struct rect** r) 
{ 
    *r = (struct rect*) malloc(sizeof(struct rect)); 
} 

và người gọi sẽ sau đó sử dụng:

canonRect(&r); 

Nhưng người gọi mất con trỏ ban đầu của nó và bạn sẽ phải cẩn thận để không bị rò rỉ cấu trúc.

Cho dù bạn sử dụng kỹ thuật nào, hàm sẽ cần phân bổ bộ nhớ cho cấu trúc mới trên heap bằng cách sử dụng malloc. Bạn không thể phân bổ không gian trên ngăn xếp bằng cách chỉ khai báo một cấu trúc vì bộ nhớ đó trở nên không hợp lệ khi hàm trả về.

+0

Rob, tôi nghĩ bạn đã có cơ sở của vấn đề, nhưng câu trả lời của bạn có thể tốt hơn nếu bạn đưa ra ví dụ về sự rò rỉ của biến r ban đầu. Điều này có vẻ là một câu hỏi giới thiệu, vì vậy các lập trình viên C thiếu kinh nghiệm có thể không nắm bắt được chi tiết. –

2

Đầu tiên, K & R c không có khái niệm "tham chiếu", chỉ là con trỏ. Nhà điều hành & có nghĩa là "lấy địa chỉ của".


Thứ hai, r trong cannonRect() là một biến địa phương, và là không các r trong main(). Thay đổi địa điểm r điểm địa phương không ảnh hưởng đến r trong thói quen gọi điện.


Cuối cùng, như đã lưu ý các địa phương struct rect được cấp phát trên stack, và đi out-of-phạm vi tại nẹp chặt chẽ,

+0

Đúng, nhưng vấn đề lớn hơn là ông cần phải gán cho * r. –

-1

2.Why tôi có thể nhận được một compile- gián tiếp bất hợp pháp lỗi thời gian nếu tôi cố gắng để vượt qua trong một con trỏ để canonRect? (IE, nếu tôi có canonRect (* r); trong main().)

Bởi vì, nó không phải là mục đích của con trỏ.

Các vấn đề liên quan