Another, and at least as important, function of a struct, which isn't usually discussed in the context of procedural languages like C or Pascal, but is a central topic in Object Based languages like Ada, or Object Oriented languages like C++ or Smalltalk or Python, is to create abstract data types (ADT's). C can do ADT's pretty well really, except for the information hiding part (which it has in common with Python, in a sense), and also C doesn't do inheritance well (C++, Smalltalk and Python do - this being the primary distinction between a mere "Object Based" language like Ada, and these "Object Oriented" languages).
The point of an ADT is to, for example, have a single API for a kind of data and the operations performed on that data, but implementing the data representation and operations in different ways, but with different tradeoffs - hence insulating the dependent code from being heavily impacted when the data representation needs to change.
One of the classic examples of why you might want an ADT, is implementing a stack. A stack is where you put items in at one end of a "list" (in the generic sense, not the Computer Science sense), and pull items back out from the same end, just like with a pile of plates at Souplantation.
Two common data representations that might be used internally by a stack ADT (abstract data type) would be an array, or a linked list.
The array implementation of a stack is nice because it's fast, and because it has low memory requirements when the stack is full, but it'll tend to lead to messiness if the array fills up (which C can get around with realloc, but a language like Pascal (the Niklaus Wirth definition - Borland likely extended for this) doesn't have much in the way of an equivalent).
The linked list implementation of a stack is nice because it has no fixed limit on how large the stack can grow (other than available memory, at least on non-segmented architectures), but it'll tend to be a little bit slower, and it'll use more memory than an array implementation when the two stack implementations hold a number of elements near the capacity of the array implementation.
If you implement both of these with identical API's, then you can very quickly and easily change your programs from using the plusses and minuses of one implementation (array) to the other (linked list - or vice versa).
As a further example of an abstract type, say you have a program that uses a singly-linked list (linked only forward, but not back again), and suddenly you find that the changing needs of the program require you to be able to quickly start at the end of the list, and work backward. You could easily approach this with a recursive algorithm (if you aren't worried about the CPU and memory requirements), but often a slight change in data representation is better.
In such a case, you have pretty much three choices:
Put another way, if you have poorly abstracted code, when you need to
change a data representation, you can easily get a sort of "ripple effect",
where changes in the code start in one small place, and kind of "ripple outward"
(a bit like a single drop into a pool of water) into ever further parts of the
code. But if things are well abstracted, then most of the time (some would
argue all of the time), the "ripples of change" stop at the ADT boundaries,
which can be quite a time-saver in the long run.
You can e-mail the author with questions or comments: