2009-06-22 45 views
32

Tôi hiểu việc sử dụng con trỏ void để thực hiện malloc.Khi nào cần sử dụng con trỏ void?

void* malloc (size_t size); 

Bất kỳ ai cũng có thể đề xuất các lý do khác hoặc cung cấp một số trường hợp hữu ích trong thực tế.

Cảm ơn

Trả lời

11

Nếu bạn đang interfacing với mã C và cần phải đi qua một đối tượng C++, nhưng một thư viện C sẽ chỉ mất một con trỏ chung, sau đó khi bạn lấy con trỏ bạn cần phải tái đúc nó vào loại thích hợp.

Con trỏ có thể không được sử dụng thường xuyên, nhưng chúng có thể trợ giúp khi bạn sử dụng chức năng thư viện hoạt động với con trỏ tùy ý và không thực sự quan tâm dữ liệu nào được biểu thị bằng bộ nhớ đó.

3

Một cách tuyệt vời để tìm hiểu tất cả về void * và các chủ đề C khác là để xem nửa đầu tiên của Stanford "Lập trình mô hình tuyệt vời" trên iTunes-U. Nó thực sự giải thích void * (C generics) và con trỏ nói chung tuyệt vời! Nó chắc chắn đã giúp tôi tìm hiểu C tốt hơn ...

Một trong những cách sử dụng lớn nhất là sử dụng void * nếu bạn muốn có thể chấp nhận các loại dữ liệu khác nhau trong một hàm. (heres một ví dụ: http://142.132.30.225/programming/node87.html)

Dưới đây là một ví dụ nữa về những gì bạn có thể sử dụng chúng cho:

int i; 
    char c; 
    void *the_data; 

    i = 6; 
    c = 'a'; 

    the_data = &i; 
    printf("the_data points to the integer value %d\n", *(int*) the_data); 

    the_data = &c; 
    printf("the_data now points to the character %c\n", *(char*) the_data); 

Nếu bạn không muốn xem các lớp học stanford miễn phí, tôi muốn khuyên googling trống con trỏ và đọc tất cả tài liệu ở đó.

+0

Có, chúng tôi nhận được nó nhưng tại sao bạn không chỉ tạo biến với loại con trỏ thích hợp? Tại sao đi qua những rắc rối của việc phải theo dõi những loại dữ liệu được lưu trữ trong một khoảng trống *? Tại sao bạn không chỉ sử dụng một int * hoặc char *? – Kekoa

+0

void * là hữu ích nhất khi bạn muốn tạo một hàm chung (một hàm nhận/trả về các kiểu khác nhau). Bạn có thể có một chức năng có thể lấy ints, hoặc tăng gấp đôi, hoặc kéo dài mà không cần phải có một chức năng cho mỗi! – micmoo

1

Con trỏ có ích khi bạn viết mã cần chạy trên nhiều hệ điều hành và cần phải khá độc lập với các API khung bên dưới.

Ví dụ: OS X, Windows và Linux đều có khái niệm cơ bản về đối tượng cửa sổ nhưng tất cả đều rất khác nhau. Vì vậy, tôi có mã phổ biến mà vượt qua chúng xung quanh như void * 's và sau đó nền tảng triển khai cụ thể mà bỏ void * để loại bản địa (HWND, vv).

Nhưng, vâng, như những người khác đã nói trong chủ đề này, loại điều này chắc chắn phải tránh trừ khi cần thiết.

8

void con trỏ nên được sử dụng bất cứ khi nào nội dung của một khối dữ liệu không quan trọng. Ví dụ khi sao chép dữ liệu, nội dung của vùng bộ nhớ được sao chép nhưng định dạng dữ liệu không quan trọng.

Đối với các chức năng hoạt động trên các khối bộ nhớ mà không cần phải hiểu nội dung bằng cách sử dụng void con trỏ làm rõ thiết kế cho người dùng để họ biết chức năng không quan tâm đến bất kỳ định dạng dữ liệu nào. Thường có chức năng được mã hóa để lấy số char * để xử lý các khối bộ nhớ khi hàm thực sự là nội dung bất khả tri.

1

Nhìn vào sqlite3_exec(). Bạn bắt đầu một truy vấn SQL và muốn xử lý các kết quả theo một cách nào đó (lưu trữ chúng vào một thùng chứa). Bạn gọi sqlite3_exec() và chuyển một con trỏ gọi lại và một con trỏ void * tới bất kỳ đối tượng nào bạn muốn (bao gồm vùng chứa). Khi sqlite3_exec() chạy nó gọi callback cho mỗi hàng đã truy xuất và truyền con trỏ void * đó vào nó để callback có thể bỏ con trỏ và làm bất cứ điều gì bạn định.

Điều quan trọng là sqlite3_exec() không quan tâm những gì gọi lại và con trỏ bạn vượt qua. void * là chính xác cho các con trỏ như vậy.

29

Một trường hợp tốt void* sử dụng là khi bạn muốn triển khai bất kỳ ADT chung nào, trong trường hợp bạn không biết loại dữ liệu nào sẽ giữ và xử lý. Ví dụ, một danh sách liên kết như sau:

typedef struct node_t node; 
struct 
{ 
    void* data; 
    node* prev, next; 
} node_t; 

typedef struct list_t list; 
typedef void* (func)(void*) cpy_func; 
typedef void (func)(void*) del_func; 
struct 
{ 
    node* head, tail, curr; 
    cpy_func copy; 
    del_func delete; 
} list_t; 

initializeLinkedList(cpy_func cpy, del_func del); 
//here you keep going defining an API 

Ở đây ví dụ bạn sẽ vượt qua trong con trỏ chức năng khởi tạo chức năng khác mà sẽ có khả năng sao chép datatype bạn vào danh sách của bạn và giải phóng nó sau đó. Vì vậy, bằng cách sử dụng void* bạn làm cho danh sách của bạn chung chung hơn.

Tôi nghĩ rằng void* vẫn còn trong C++ chỉ vì tính tương thích ngược, vì trong C++ bạn có nhiều cách an toàn và tinh vi hơn để đạt được kết quả tương tự như mẫu, functors, v.v ... và bạn không cần sử dụng malloc trong khi lập trình C++.

Về C++, tôi không có bất kỳ ví dụ hữu ích cụ thể nào.

+0

Cảm ơn Artem.Đó là điều. cũng trong trường hợp của C, điều tương tự có thể được thực hiện bằng cách sử dụng char *. Tôi đã không thể nghĩ về một kịch bản mà một char * không thể được sử dụng để thay thế. (áp dụng cho malloc). – Mac13

+0

Thành ngữ C++ là sử dụng các khuôn mẫu và thư viện chuẩn cung cấp nhiều thư viện hữu ích (bao gồm danh sách được liên kết). –

+2

Sử dụng 'void *' thay vì 'char *' cung cấp một số đo an toàn nhất định. Bạn đang nói với trình biên dịch "Tôi không bao giờ được phép dereference một trong những con trỏ." – Novelocrat

2

Nó thường được sử dụng trong mã số, ví dụ như một gốc chức năng giải C có thể trông như thế:

double find_root(double x0, double (*f)(double, void*), void* params) 
{ 
/* stuff */ 
y = f(x, params); 
/* other stuff */ 
} 

params được đúc bằng Đồng f đến một số cấu trúc nó biết về, nhưng find_root không.

+3

Nói chung, 'void *' tạo các đối số tốt cho hàm gọi lại và nhiều thứ khác mà ai đó sẽ cần phải chuyển một hàm , nhưng không cần phải biết loại đối số đó. – Novelocrat

3

Một ví dụ khác như C "Generics", được thực hiện với void *, là một hàm qsort tiêu chuẩn:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); 

Bạn có thể sắp xếp một mảng của bất kỳ loại: int, long, double, char * hoặc một số struct pointers ...

2

Tiếp theo để giao tiếp với C, tôi thấy mình chỉ sử dụng con trỏ void khi tôi cần gỡ lỗi/theo dõi một số mã và muốn biết địa chỉ của một con trỏ nhất định.

SomeClass * myInstance; 
// ... 
std::clog << std::hex << static_cast< void* >(myInstance) << std::endl; 

sẽ in cái gì đó như

0x42A8C410 

Và, theo ý kiến ​​của tôi, độc đáo tài liệu những gì tôi đang cố gắng để làm gì (biết địa chỉ trỏ, không bất cứ điều gì về các ví dụ)

+2

Bạn đã xem xét sử dụng trình gỡ rối chưa? – Eva

-3
int (*f) (void); 
f =(void*) getprocaddress(dll,"myfunction"); 

để làm cho trình biên dịch hài lòng

+5

Điều này sẽ không biên dịch. Làm thế nào nó sẽ làm cho trình biên dịch hạnh phúc là hoàn toàn không rõ ràng. – AnT

1

void * thực sự là C-ism và cho phép C thực hiện một số việc s mà nó có thể không hợp lý làm khác.

char * không thể sử dụng được cho bất kỳ điều gì, vì các nền tảng khác nhau có thể tạo các loại con trỏ khác nhau - char * không nhất thiết phải xử lý giống nhau (hoặc thậm chí cùng kích cỡ) với dạng void *. Vì vậy, khi loại dữ liệu không được biết đến trong C (hoặc là đa hình hoặc năng động), thì void * cho phép bạn tạo đúng loại con trỏ cơ bản - một loại có thể trỏ đến bất kỳ điều gì chính xác.

Trong C++ void * thường không bao giờ xuất hiện ngoại trừ trong ngữ cảnh giao tiếp với mã C cũ trong một biểu mẫu này hoặc dạng khác.

1

Có lợi thế lớn khi sử dụng con trỏ trống. Biến con trỏ là một biến lưu trữ địa chỉ của một biến khác. ví dụ:

int a; 
int *x= &a; 

Bây giờ 'x' lưu địa chỉ của biến số nguyên.

Nhưng có một điều này không:

float f; 
int *x = &f; 

Bởi vì Integer biến con trỏ có thể lưu trữ chỉ số nguyên địa chỉ khác nhau. theo cùng cách thức áp dụng cho các loại dữ liệu khác.

Khi bạn sử dụng con trỏ void *, nó cung cấp một cạnh để lưu trữ địa chỉ của bất kỳ biến TYPE nào.

void *pointer = &i; 
void *pointer = &f; 

trong khi truy xuất nó phải được trì hoãn.

*((int*)pointer) 

Vì vậy, hãy cẩn thận sử dụng con trỏ trống.

Điều này có thể giúp bạn, Cảm ơn bạn.

7

Trong C++, tôi đã tìm thấy trường hợp sử dụng hấp dẫn nhất cho con trỏ void * là cung cấp mã tùy chọn để lưu trữ "dữ liệu người dùng" tùy ý trên một đối tượng mà họ đang sử dụng.

Giả sử bạn đã viết một lớp đại diện cho Car, để sử dụng trong phần mềm có những thứ hữu ích với các đối tượng Car (mô phỏng giao thông, khoảng không quảng cáo cho thuê ô tô, bất kỳ thứ gì). Bây giờ chúng ta hãy nói rằng bạn thấy mình trong một tình huống mà ứng dụng của bạn muốn theo dõi các nội dung tùy ý của thân của Car. Các chi tiết của những gì được lưu trữ trong thân cây không quan trọng đối với lớp Car, và có thể là bất cứ điều gì - nó thực sự phụ thuộc vào mục đích của ứng dụng bằng cách sử dụng lớp Car. Nhập con trỏ void *.

class Car 
{ 
    public: 

     // Existing methods of your Car class 

     void setContentsOfTrunk(void* contentsOfTrunk); 
     void* contentsOfTrunk() const; 

    private: 

     void* m_contentsOfTrunk; 
} 

Bây giờ, bất kỳ ứng dụng sử dụng lớp Car bạn có tùy chọn để đính kèm một đối tượng dữ liệu tùy ý để một Car đối tượng hiện có như vậy mà nó có thể được lấy từ bất kỳ mã trong đó có các đối tượng Car. Nội dung của thân cây "di chuyển với" đối tượng Car, bất kể nó ở đâu trong mã của bạn.

Có hai lựa chọn thay thế để sử dụng void * trong trường hợp này.

Đầu tiên là để lớp mẫu của bạn dựa vào loại nội dung thân đối tượng:

template <class TrunkContentsType> 
class Car 
{ 
    public: 

     // Existing methods of your Car class 

     void setContentsOfTrunk(TrunkContentsType contentsOfTrunk); 
     TrunkContentsType contentsOfTrunk() const; 

    private: 

     TrunkContentsType m_contentsOfTrunk; 
} 

Điều này có vẻ không cần thiết xâm lấn. Loại nội dung của thân cây chỉ quan trọng đối với ứng dụng. Các thuật toán và cấu trúc dữ liệu hoạt động với các đối tượng Ô tô không quan tâm những gì trong thân cây. Bằng cách tạo khuôn mẫu cho lớp, bạn đang buộc các ứng dụng sử dụng lớp này để chọn một kiểu cho nội dung chính, nhưng trong nhiều trường hợp, các ứng dụng cũng không quan tâm đến nội dung thân cây.

Các lựa chọn thứ hai là để lấy được một lớp mới từ xe có thêm một thành viên dữ liệu và các phụ kiện cho nội dung thân:

class Car 
{ 
    public: 

     // Existing methods of your Car class 
     // No methods having anything to do with trunk contents. 

    private: 

     // No data member representing trunk contents. 
} 

class CarWithTrunkContents 
{ 
    public: 

     // Existing methods of your Car class 

     void setContentsOfTrunk(TrunkContentsType contentsOfTrunk); 
     TrunkContentsType contentsOfTrunk() const; 

    private: 

     TrunkContentsType m_contentsOfTrunk; 
} 

Các CarWithTrunkContents lớp mới là một lớp ứng dụng cụ thể có thêm một thành viên dữ liệu của gõ ứng dụng cần lưu trữ nội dung thân trên xe. Điều này cũng có vẻ nặng nề không cần thiết. Tại sao bạn phải lấy được một lớp hoàn toàn mới để thêm một đoạn dữ liệu bổ sung mà không ảnh hưởng đến hành vi của lớp? Và nếu nó khá phổ biến đối với các ứng dụng sử dụng lớp Car để muốn lưu trữ nội dung thân cây, tại sao buộc mỗi ứng dụng lấy được một lớp mới cho loại nội dung cụ thể của chúng?

Cuối cùng, trong khi ví dụ contrived của tôi về nội dung thân cây có thể vẽ nên một bức tranh sinh động về nội dung thân cây tùy ý đi du lịch với mục đích Car, trong thực tế bạn sẽ có khả năng cung cấp một cơ chế thậm chí tổng quát hơn để gắn dữ liệu ứng dụng cụ thể cho các Car:

class Car 
{ 
    public: 

     // Existing methods of your Car class 

     void setUserData(void* userData); 
     void* userData() const; 

    private: 

     void* m_userData; 
} 

Bằng cách này, ứng dụng có thể đính kèm đối tượng đại diện cho nội dung thân cây hoặc đối tượng đại diện cho giấy phép lái xe và đăng ký. Tôi đã thấy loại void * pointer này được gọi là "userData" (tức là được hiểu bởi người dùng của lớp), "blindData" (tức là lớp bị mù với nội dung của đối tượng nó mang) hoặc "applicationData" (tức là dữ liệu về loại và mục đích được xác định bởi ứng dụng).

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