The struct type in C is analogous to a degenerate class in Java. Structs have no mechanism for data hiding - all fields defined in the struct are public and accessible to client code via the dot notation. Further there is no bundling of code - although function pointers can be stored as fields. Lastly, since there is no OOP in C - a struct cannot be used to derive other classes. There is no inheritance, only composition.
One other significant difference between a Java class and a C struct is that in Java when you declare a class variable you are creating a reference to an object, and then, optionally an object of the type. In C, when you declare a struct variable you are getting what is essentially a primitive value. As a result when you pass a struct into a function as a parameter you are passing a deep copy of the struct, not a pointer to it. A struct's name is NOT a pointer. A struct's name is like the name of a primitive type such as int, char, double etc. You can of course explicitly declare a pointer to a struct or use the & operator on a struct's name in order to pass a pointer to the struct into a function. We will now look at code that declares automatic and dynamic struct variables and passes those structs around by name and by address.
Note in the above sample we declare an automatic struct variable and use the dot notation to access the fields in side. The string hanging from the name field is dynamic but the struct itself is automatic.
HandsOn: Lets modify this sample by adding a swapStruct() function. We will at first just pass in the names of the structs instead of their addresses and observe the failed behavior. We will then revise our function and call to pass addresses thereby fixing our swap function. Moral of the story: A struct's name is NOT a pointer to the object. A struct's name is the value of the object just like primitives.
In our solution above, note we treat the structs as primitives as use direct assignment to copy the values stored in the structs. The assignment operator is to be used to assign the value of the entire struct at large. Further, it must be understood that pointer fields are copied by value thus the assignment is a shallow copy of the dynamic data that is pointed to by a pointer inside the struct. In our example that's just what we want to happen.
Note in demo 2 that we introduce the -> operator. This operator is a shortcut that combines dereference and dot notation. Thus p->name is synonymous with (*p).name. Clearly the -> notation is simpler and to be preferred. The rule of which operators to use is simple. If you are getting to the struct via its name then use the dot notation to access the fields. If you are getting to the struct via a pointer then use the -> notation. Of course both forms of the syntax work and you may use either you prefer.
It does not matter whether the struct being referenced is automatic or dynamic. If you are using a pointer to get to that struct, you may use the -> notation. If you are accessing a struct via its name - then you must use the dot notation to get to the fields in the struct.
Note in demo 3 we declare an automatic array of structs and treat them just like an automatic array of primitives.
Notice in our code samples below that we use a slightly different form of typedef to declare our list element struct. This is needed because in our list element struct definition we make reference to our own struct type. Further notice that we are careful to declare a pointer to our own struct type not a variable of our struct type. If we had declared a variable of our own type in the struct, the compiler would be unable to resolve the symbol, due to an infinite recursion of definition.
In our second demo program we perform the same operation of creating a linked list, but this time we pass our head pointer into a function named insertAtFront which mallocs, initializes and links the new Node into the front of the list.
void insertAtFront( Node ** head, int data )
{
Node * new = malloc( sizeof(Node) );
if (!new) fatal("malloc of Node failed");
new->data=data;
new->next = *head;
*head = new;
}
Node * head= NULL;
insertAtFront( &head, 1); /* add one node to the front of the list. it will contain the integer 1 in its data field */
Note we pass the head point in by its address because we want to modify it by putting the address of a new Node into head.
Again, let's step through each line of code in the insert and illustrate its effect on memory.
Node * new = malloc( sizeof(Node) ); /* allocate a new Node in the heap */
The word new is not a special word in C. It just seemed like a good name to represent the pointer to the newest Node about to be inserted into our list.
new->data=data; /* copy the incoming data value into the Node's data field */
new->next = *head; /* the new Node now points to the head of the old list */
*head = new; /* insertion complete. our local ptr "new" goes away with the insert function */
insertAtFront( &head, 2); /* add one node to the front of the list. it will contain the integer 2 in its data field */
Again, let's step through each line of code in the insert and illustrate its effect on memory.
Node * new = malloc( sizeof(Node) ); /* allocate a new Node in the heap */
The word new is not a special word in C. It just seemed like a good name to represent the pointer to the newest Node about to be inserted into our list.
new->data=data; /* copy the incoming data value into the Node's data field */
new->next = *head; /* the new Node now points to the head of the old list */
*head = new; /* insertion complete. our local ptr "new" goes away with the insert function */
insertAtFront( &head, 3); /* add one node to the front of the list. it will contain the integer 3 in its data field */
Again, let's step through each line of code in the insert and illustrate its effect on memory.
Node * new = malloc( sizeof(Node) ); /* allocate a new Node in the heap */
The word new is not a special word in C. It just seemed like a good name to represent the pointer to the newest Node about to be inserted into our list.
new->data=data; /* copy the incoming data value into the Node's data field */
new->next = *head; /* the new Node now points to the head of the old list */
*head = new; /* insertion complete. our local ptr "new" goes away with the insert function */
void removeAtFront( Node ** head)
{
Node * deadNode = *head;
if (!deadNode) return;
*head = deadNode->next;
free( deadNode );
}
removeAtFront( &head); /* remove the first Node at the head of the list */
Note we pass the head pointer in by its address because we want to modify it by putting the address of the 2nd Node (if any) of the list.
Let's step through each line of code in the remove and illustrate its effect on the list.
Node * deadNode = *head; /* local ptr to front Node */
*head = deadNode->next; /* head just jumped over the 1st node */
free( deadNode ); /* destroy the Node that was skipped over. local ptr "deadNode" goes away with the function. REMOVAL COMPLETE */
removeAtFront( &head); /* remove the first Node at the head of the list */
Note we pass the head pointer in by its address because we want to modify it by putting the address of the 2nd Node (if any) of the list.
Let's step through each line of code in the remove and illustrate its effect on the list.
Node * deadNode = *head; /* local ptr to front Node */
*head = deadNode->next; /* head just jumped over the 1st node */
free( deadNode ); /* destroy the Node that was skipped over. local ptr "deadNode" goes away with the function. REMOVAL COMPLETE */
removeAtFront( &head); /* remove the first Node at the head of the list */
Node * deadNode = *head; /* local ptr to front Node */
*head = deadNode->next; /* head just jumped over the 1st node */
free( deadNode ); /* destroy the Node that was skipped over. local ptr "deadNode" goes away with the function. REMOVAL COMPLETE */