Structs
We need some more powerful compounding data structures besides arrays
- Structures let us define custom data types with multiple named data elements.
- Unlike arrays, the different elements can have different types
- They are behaviorally similar to classes with only fields, no methods
Declaring & using struct variables
There are two steps needed for declaring them. First we define a new struct type:
struct sname {
// field declarations
};
For example:
struct person {
char * first, * last;
int age;
};
Next, declare variables of the struct type
struct person p1, p2; // notice how "struct" precedes name
We can both define a struct and declare variables p1 and p2 at the same time. This is not used too often:
struct person {
char * first, *last;
int age;
} p1, p2;
Once declared, the dot operator is used to access struct fields: p1.age = p1.age + 1.
Initializing structs
Can initialize struct variables during declaration:
- Use
{ ... }as with array initialization, and list of.values for all components:struct person joe = { .first = "Joe", .last= "Schmoe", .age = 33 }; - Fields are initialized in order top to bottom if not specified.
- If fewer values than parts, remainder are initialized to 0 or NULL
struct person joe = {"Joe", "Schmoe"};
// joe.first == "Joe", joe.last == "Schmoe", joe.age == 0
Passing structs to functions
- Struct variables are passed by value to functions by default: all components are copied!
--means changes to struct in function body won't be seen by calling function - To avoid copying, pass a pointer but as const (
const person *p) so function can't change struct contents - Can pass whole structure, or just an element of structure
Pointers to structs
Pointers to structs are commonly used. A very common expression is (*sptr).part as in this example.
struct person *ppp = malloc(sizeof(struct person));
(*ppp).age = 33;
In fact its so common, it has a shortcut:
ppp->age = 33 ; //same as (*ppp).age = 33;
NOTE: operators () [] . -> have the highest precedence (full C operator precedence reference)
– *ppp.age = 33 is a compile error, need parens (*ppp).age = 33;
Composing structs with other types
- Both within a struct and outside it, any type (operator) is possible
- Structs can be used inside other type operators: Array of some struct, array of pointers to some struct
- Any type can also be the type of a field inside a struct:
int, arrays, pointer to an array of structs, etc.
Type abbreviations via typedef
We use typedef to declare type abbreviations for easier typing / reading:
typedef void * blackhole; // new <em>type</em> blackhole defined
blackhole myblackhole; // declares myblackhole as a blackhole, i.e. as a void *
This is especially common with struct definitions:
typedef struct { // note the struct itself is anonymous -- no name given here
int n1, n2, n3;
} SSN; // SSN is the type abbreviation
SSN myssn; // no "struct" needed when declaring, "struct" is part of SSN
Sizes of structs
- Each struct at runtime is a contiguous block of memory
- e.g. each
struct personabove will be a 12- or 20-byte block of memory depending on whether pointers are 4- or 8-byte. - Plus, for computer efficiency the size is often rounded up to the next biggest word.
- So,
sizeof(struct person) == 12or24
Enum types
We can also define a data type to represent a small set of discrete values. Suppose you had a game with playing cards, hearts/clubs/spades/diamonds. Don’t just use the strings "Hearts" etc or 1/2/3/4 in the code for these different suits. Instead you can #define them
#define HEARTS 1
#define CLUBS 2
#define SPADES 3
#define DIAMONDS 4
Enum version of this is more compact and also defines a data type called suit that can be used to declare variables, parameters, etc.
enum suit {HEARTS=1,CLUBS,SPADES,DIAMONDS}
Same numerical abbreviation but makes a new type suit for the values (which is really an integer under the covers). The =1 makes the encoded numbers start at 1 not 0, which is the default. Each subsequent label gets the next integer value.