Using C language to implement a "generic" linked list

Source: Internet
Author: User

You may be surprised to see this title. Can the C language also implement a generic linked list? We know that linked lists are very common data structures, but in C, there is not a list template class like STL in C ++, can we use C to implement a generic linked list like list in STL? The answer is yes. The following uses my linked list designed in C language as an example to describe the key points of my design and implementation, hoping to provide you with some useful help.
1. We know that there are many types of linked lists, including single-chain tables, single-cycle linked lists, double-chain tables, and two-way cyclic linked lists. In my design, my linked list uses a bidirectional cyclic linked list with a header node that does not store real data. The reasons are as follows: 1) it is inconvenient to operate a single-chain table because it cannot be located from the successor to the precursor. 2) Although the double-chain table can easily find the precursor, however, if a node is always inserted or deleted at the end of the node, in order to facilitate positioning and unify operations (all Delete and insert operations are the same as inserting and deleting nodes in the middle ), add a tail node for it, and the program also needs to save a pointer pointing to the tail node, and manage the pointer to increase the complexity of the program. The circular two-way linked list with the leading node can be easily located (the previous element is the last element of the linked list, And the next element is the 0th element of the linked list ), and unify all insert and delete operations, because the header node is also the end node. Note: The subscript of a node starts from 0, and the header node is not counted as the base value. 3) The use of interfaces is roughly the same as that of list and generic algorithms in stl in C ++.
Ii. Definition of list type
In order to make everyone think fast, the following is a definition of "generic" implemented in C language. The reasons and key points of my design are described as follows: it is defined in the list_v2.c file.

Typedef struct node {// The void * data node Structure of the cyclic double-stranded table; // The data domain pointer struct node * next; // The struct node * last pointing to the next node of the current node; // point to the previous Node of the current node} Node; struct list {struct node * head; // the header pointer pointing to the int data_size of the header Node; // int length of memory occupied by data corresponding to the linked list; // length of the linked list };
Its declaration is in the list_v2.h file.
// Generic cyclic double-stranded table, with the leading node and the subscripts starting from 0, the header Node is not included in the lower mark value // defines the node pointer Node * as the List type Iterator typedef struct node * Iterator; // The List type definition typedef struct list * List; // initialize the linked List. The size of the memory occupied by the data field is indicated by data_size and int InitList (list * List, int data_size ); // insert data content to the end of the linked list // assign specifies the value assignment method Iterator Append (List list, void * data, void (* assign) between data) (void *, const void *)); // Insert the data content to the front of the it_before Iterator of the linked List // assign specifies the value assignment method Iterator Insert (list List, void * data, Iterator it_before, void (* assign) (void *, const void *)); // move the vertex pointed by it_a in List A to Iterator MoveFromAtoB (List A, Iterator it_a, List B, Iterator it_ B _before) in List B ); // Delete the int Remove (list List, Iterator it) node pointed by the Iterator in the linked list; // Delete the 0th nodes in the linked list, subscript starts from 0 int RemoveFirst (List list); // Delete the last node int RemoveLast (list List) of the list ); // return the pointer void * At (list List, int index) of the index data in the list. // you can find the first element conforming to the condition between begin and end, // The comparison function is directed by condition, and the comparison value is directed by data // when the value of the first parameter is smaller than the value of the second parameter, 1 is returned, otherwise, 0 is returned. // depending on the condition function, you can find the first Iterator FindFirst (Iterator begin, Iterator end, void * data, int (* condition) (const void *, const void *); // finds the subscript of the first element in the list that is equal to data, // equal function, if the value of the first parameter is equal to that of the second parameter, 1 is returned. Otherwise, 0int IndexOf (List list, void * data, int (* equal) (const void *, const void *); // find the minimum value between begin and end. The comparison function directs from less to // when the value of the first parameter is smaller than the value of the second parameter, 1 is returned. Otherwise, 0 Iterator GetMin (Iterator begin, Iterator end, int (* less) (const void *, const void *) is returned *)); // query the maximum value between begin and end. The comparison function points to the value from large. // if the value of the first parameter is greater than the value of the second parameter, 1 is returned, otherwise, 0 Iterator GetMax (Iterator begin, Iterator end, int (* large) (const void *, const void *) is returned *)); // obtain the length of the list int GetLength (List list); // if the list is an empty linked List, 1 is returned; otherwise, 0 int IsEmpty (list) is returned ); // destroy listvoid DestroyList (List * list); // Iterator Begin (list List), the first Iterator for obtaining the list; // The Last Iterator for obtaining the list, point to the Next position of the last element Iterator End (List list); // point it to the Next position, and return the Iterator Next (Iterator * it) after pointing to the Next position ); // point it to the previous position and return the Iterator Last (Iterator * it) pointing to the previous position; // obtain data through the Iterator it, equivalent to * pvoid * GetData (Iterator it); // gets the next Iterator of the current Iterator. Note that the current Iterator GetNext (Iterator it) is not changed ); // obtain the previous Iterator of the current Iterator. Note that the current Iterator GetLast (Iterator it) is not changed );
To better express the structure of the linked list, we can see the structure of the linked list: After the InitList function is called (taking the linked list of integer data as an example), the structure of the linked list is as follows:



After inserting a certain number of nodes into the Linked List (taking the linked list of integer data as an example), the structure of the linked list is as follows:



PS: its complete code can be clicked here download: http://download.csdn.net/detail/ljianhui/6881881

3. How to hide the member variables (encapsulation) of the linked list? First, why do we need to encapsulate them? I think Encapsulation has three major advantages.
1) Isolate changes. What needs to be encapsulated in a program is usually the most vulnerable to changes in the program, such as member variables. We can encapsulate them, so that their changes do not affect other parts of the system, that is, the changes are encapsulated.
2) reduce complexity, because we encapsulate the details of how an object is implemented and leave the user with a minimum-dependent interface, so that the system variables are simple and clear, to a certain extent, the complexity of the system is reduced, making it easier for users to use.
3) allow users to operate only one object or type according to the interface we designed, rather than directly operate an object, thus reducing users' misoperations and improving system stability.
In the object-oriented design, if we want to hide the member variables of a class, we can declare these member variables as private. In the C language, how can we implement them? In fact, its implementation is very simple. in C language, when we want to use a defined type or function, we will include the header file that declares it, as long as we declare in the file that its type is a struct, and write its implementation in. c file.
In this example, I define the struct list and struct node in. in the c file, but in the header file, only the pointer type is declared, that is, the typedef struct node * Iterator and typedef struct list * List; when we want to use this type, you only need to include this header file in the file where it is located. During compilation, the compiler can know the memory size as long as the List and Iterator are pointer types, and allocate memory for them. Therefore, the compilation is successful. Because the header file does not have the definition of this type (struct list and struct node), we can only use the interfaces we provide to operate on objects. For example, we cannot use the List, list, and data operations, but can only be obtained through the defined interface GetData.
4. How to Implement generic generics the first thing that comes to mind is the template, but this is not available in the C language. However, in C language, there is a pointer type that can point to any type. When used, it is void * based on the specific pointer type *.
Why does void * point to any type of data? This must be explained in the C language's data type processing method. In C language, we use functions such as malloc to apply for memory. From the memory perspective, data is of no type. They are all strings of 0 or 1, the program explains the meaning of the data in the memory unit according to different types, for example, for data in the memory, FFFFFFFF, if it is a signed integer data, it represents-1, and if it is an unsigned integer data, it represents 2 ^ 32-1. Further, if you use an int pointer Variable p to point to the memory, * p is-1. If you use the unsigned int pointer p to point to the memory, * p = 2 ^ 32-1.
When using functions such as malloc, you only need to specify the size of the applied memory, and do not need to specify the type of data stored in the applied memory, for example, when we apply for a memory space to store an integer data, we only need malloc (sizeof (int, of course, you can use it as a char array with four units. Therefore, we can use the void pointer to point to the memory we applied for. The size of the applied memory is defined by the data_size member in the linked list. It is also the memory size occupied by the real data.
5. Why do we need to assign a value to the function pointer assign? Here we will introduce the data insertion method of the linked list. Our insertion method is to create a new node and copy the data pointed to by data to the node, and insert the node into the linked list. The inserted function is defined as follows: Iterator Insert (List list, void * data, Iterator it_before, void (* assign) (void *, const void *));
From the above explanation, we can see that the member data_size in the linked list indicates the memory size occupied by the data in the linked list, then we can use the memcpy function to copy the data pointed to by data to the memory pointed to by the data of the new node. Why do I need a function pointer assign to point to a function that defines how to assign values between data? In fact, this is related to deep replication, which is often mentioned in object-oriented languages, and light replication.
Note: The prototype of the memcpy function is void * memcpy (void * destination, const void * source, size_t num );
Imagine that the data type of your linked list is not a basic type such as int type, nor a struct without pointers, but such a struct, such as struct student {char * name; char * no; int age ;}; the student's name and student ID can be dynamically allocated with memory and directed by the name and no pointers in the student struct. When we use memcpy, you can only copy its pointer, but not the data it points to. This will cause some problems in many cases. This is similar to when you need to define a copy constructor in C ++. In this case, the default replication constructor cannot meet our needs. We can only define the replication constructor.
Therefore, when inserting a knot, the assign function pointer is used in the same principle as the copy constructor defined in C ++. It defines how to generate a copy object of an existing object. Of course, in most cases, the data types we need do not contain pointers. Therefore, in the implementation of the Insert function, I also use the memcpy function, when assign is NULL, the memcpy function is used to assign values between data objects. In fact, it is equivalent to the default copy constructor in C ++. If assign is NULL, the default bit-by-bit replication mode is used.
6. Why not use typedef?
This is actually a good answer to this question. Many people implement a universal linked list in this way. They implement the node structure as follows: typedef struct node {// The node Structure of the cyclic double-stranded table DataType data; // data domain pointer struct node * next; // point to the next node of the current Node, struct node * last; // point to the previous node of the current Node} node; then, when you need to use an integer linked list, use typedef as int for DataType. In fact, the biggest drawback of doing so is that a program can only have one data-type linked list. For example, if I need an int-type linked list and a float-type linked list, so should we define DataType as int or float? Therefore, this seemingly feasible method is actually just an imaginary table. In this case, it cannot be done. Although many data structure books are implemented in this way, it has no practical value.
The essential reason is that the data type of the data domain of the node is bound with a specific data type DataType, so that the linked list cannot be changed independently.
7. Why does Iterator define a node pointer as a class in C ++? Why is it that I declare a node pointer as an iterator? In fact, due to STL, I implemented Iterator as a struct in the first place. It has only one data member and is a pointer to Node. However, in later practices, it was discovered that it was not necessary. In C ++, why do I define iterator as a class to overload *,-> and other runtimes so that iterator can be used as a normal pointer. However, in C language, there is no way to reload the runtime operator, so it is most convenient, direct, and easy to use to directly declare Ierator as a Node pointer. All comparative operations can be carried out directly, function is not required. Declaring it as a struct is troublesome and cumbersome.
8. Why do I need two Iterator search? In fact, this is a reference to the idea of STL generic algorithms. And I think this is a better implementation. Why is the FindFirst function prototype not Iterator FindFirst (List list, int (* condition) (const void *, const void *); but Iterator FindFirst (Iterator begin, Iterator end, void * data, int (* condition) (const void *, const void *); let's imagine that the linked list is a char linked list, and the linked list element is ABCBCBC, we need to find all B in the linked list. If the search algorithm uses the first definition, it can only find the first B, and the second two B will be powerless, in the second definition, you can find the position of the target character B between different sequence segments by changing the iterator at the beginning and end of the loop.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.