 C is not object-oriented and so has no concept of classes, but it does have user-defined data types called structures. Unlike a class, a C structure has no notion of methods, a structure is just pure data. To declare a structure type, we use a reserved word struct, followed by a name, followed by a list of members and curly braces. Having declared this struct type, we can then create a variable of this type just as if we were creating a variable of a built-in type, except the type name is preceded by the reserved word struct. In fact, it's easiest to think of the word struct as if it's part of the type name. Once we have a variable of a struct type, we can access the individual members that make up the struct with the dot operator. So here, for example, we declare a type called struct cat with two members, a char pointer name and an int age. We then create a struct cat variable mittens and then assign its name member the char pointer value represented by the string m-i-t-t-e-n-s and then assign its age member the int value 5. We can assign one struct instance to another as we do here, assigning mittens to fluffy. This copies the value of every member of mittens to the corresponding member of fluffy. We can also create struct pointers as we can with any other type. Here we assign a struct cat pointer p and assign it the reference of fluffy, so p now holds a pointer representing the address of the struct cat variable fluffy. Somewhat surprisingly, we cannot compare struct values with the equality operators, so this expression is illegal. If you want to compare the corresponding members of two struct values, you have to compare them individually. Here, we use malloc to allocate a block the size of eight struct cats and we assign the result of malloc to a struct cat pointer. We then can retrieve the individual cats of this block using pointer arithmetic and the dereference operator and once we have an individual cat instance value, we can assign its members with the dot operator. So here we assign the string oscar to the name member of the first cat, the string kitty to the name member of the second cat, and the number four to the age member of the last cat. If we want a stack allocated block of cats, we can create an array instead of using malloc. We then use the array in the same fashion, but just keep in mind that the block of memory only exists for the duration of the local scope. So far, we've created pointers and arrays out of our base types and now also the custom struct types which we can create. But the general rule in C is that we can create pointers and arrays of any type, including other pointers and arrays. So we can have pointers to pointers, pointers to arrays, arrays of pointers, arrays of arrays, pointers to pointers to pointers, arrays of pointers to arrays, addInfinitem. For these complex types, a key thing to understand is that arrays of different sizes are considered different types. So say an array of five-ints is not the same type as an array of six-ints. So say when we create a pointer to an array of five-ints, that is a different type of pointer than a pointer to an array of six-ints. Here's a more elaborate example of a complex type. Because there exists in C an int type, we can also create arrays of int. For example, we can have an array of 12-ints type. And because that's its own type, we can create an array of that type. So we can have an array of four arrays of 12-ints type. And because that's its own type, we can create a pointer to that type, a pointer to an array of four arrays of 12-ints. And because that's its own type, we can create a pointer to that type, a pointer to pointers to arrays of four arrays of 12-ints. And because that's its own type, we can create an array of that type, so we can have an array of seven pointers to pointers to arrays of four arrays of 12-ints. Again, be clear that arrays of different sizes are different types. types. So here we have three pointer types, which all point to arrays of arrays of ints, but because the arrays are of different sizes, these are three separate types. Now you're probably trying to picture what these data types look like in memory. Well, pointers are the easy case. A pointer of any type always represents just a single address. For example, a pointer that points to an individual char is an address, a pointer that points to a pointer to a char is also an address, and likewise a pointer that points to an array of arrays is also just an individual address. As for arrays, an array of nx's is a contiguous block of nx's. So an array of seven doubles is a contiguous block that fits seven doubles. An array of 15 pointers to char's is a contiguous block that fits 15 pointers to char's. An array of four arrays of 12 ints is a contiguous block that fits four arrays of 12 ints, and each array of 12 ints is in turn a contiguous block that fits 12 ints. For illustration, here's an array of four arrays of three char's. The whole block is made up of four blocks, each of which is made up of three char's. Here's an array of two pointers to arrays of four arrays of 12 ints. What we have in memory is simply two pointers. The pointers point to arrays of arrays, but the pointers themselves as always are merely addresses. Here we have an array of two arrays of three pointers to arrays of 12 ints. Again, reading left to right, as soon as we're talking about a pointer, we're talking about just a single address, regardless of the type to which the pointer points. One thing that makes these complex types very confusing is that C programmers often use the words pointer and array as modifying adjectives rather than as nouns, and this ends up reversing the order of the components when we state a type. For example, instead of saying an array of 12 ints, we could say an int array sized 12. Even more confusing, we generally leave the array sizes unstated, so we would more commonly just say int array. The more complex our array and pointer types, the more confusion this creates. Instead of saying an array of four arrays of 12 ints, we commonly say an int array array, and instead of saying a pointer to an array of four arrays of 12 ints, we commonly say int array array pointer. Because it's awkward to include the array sizes in this way of expressing types, I find it less clear, even though it is less verbose. What you absolutely shouldn't do, however, is mix these two ways of expressing types. For example, instead of saying a pointer to an array of four arrays of 12 ints, or instead of saying an int array array pointer, which is equivalent, it would be valid English to say a pointer to an int array array, or say a pointer to an array of int arrays. But expressing types this way is horribly confusing. Sadly, many programmers use this manner of expressing types quite commonly. Just watch out for it and try to avoid using it yourself. If you're ever confused about a complex pointer or array type, put the component types in a consistent order, as I've demonstrated. Okay, so the next question is what syntax do we use to create these complex types? Well, before getting to the syntax, consider the nature of unary operators. For a moment, imagine if in the C language, dollar sign, number sign, and the at sign were all unary operators. They aren't, but just imagine this were the case. This use of the three operators on foo for example here would be applied from right to left. Now imagine if the at sign were post-fix rather than prefix, meaning it would be written after its operand rather than before. Now the order of operations depends upon the order of precedence. If the at sign had highest precedence, then this would be the evaluation order. If though the number sign had highest precedence and the at sign had second highest, this would be the evaluation order, and if the at sign had lowest precedence, then this would be the order. So the point is that when both prefix and post-fix operators are involved, we have to be concerned about the respective precedence. Now what do operators have to do with declarations? Normally we think of operations only existing in expressions, and the declaration is not an expression. Well for some strange reason, the C language decided that declaration syntax should mirror expressions, and so square brackets in a declaration act like a post-fix operator, and asterisk in a declaration acts like a prefix operator, with the square brackets having higher precedence than asterisk. And just like in an actual expression, we can use parens to subvert this precedence. So in this declaration of an array, you should think of the square brackets as an operator on the name foo, but which has the effect of modifying the base type int to be an array of the size specified in the square brackets. So this is a declaration of an array of 12-ints. In this next declaration, we have another pair of square brackets containing the number 4. Because this pair is closer to the pseudo-operant foo, this is firstly an array of 4. So this is an array of 4 arrays of 12-ints. In this next declaration, we've added an asterisk and enclosed it in parentheses around foo. Without the parentheses, the square brackets would have higher precedence, but with the parentheses, the asterisk is applied first, so this is a pointer to arrays of 4 arrays of 12-ints. Lastly, we've added an asterisk outside the parens and moved the first square brackets to be inside the parens. So because square brackets have a higher precedence than asterisk looking inside the parens, this is an array of 4-pointers, and then looking outside the parens, these pointers point to arrays of 12-pointers to ints. So the general pattern of declaration syntax is that we start with a name and read the modifiers inside out going by the order of precedence, except where parens intervene. Now as for actually using these complex types, we need to cover a few rules. First, referencing a pointer type variable will return a pointer to that type. Here we assign the reference of int variable i to pointer to int variable json, and then in turn assign the reference of json to pointer to pointer to int variable Amanda. So the reference of json returns a pointer to pointer to int value. When dereferencing a pointer to pointer to x, we get back a pointer to x value. So here when we dereference Amanda, which is a pointer to pointer to int, we get back a pointer to int. If we use a dereference operator on Amanda twice, then we're dereferencing the pointer to int value returned by the dereference of Amanda, and so we get back a plain int value. As previously discussed, an array name is really a pointer value. Notice that I said pointer value and not variable because we cannot assign two array names. Here we can assign the array name foo to an int pointer variable, but we cannot assign the int pointer variable back to the array name. For this array of seven pointers to ints, the array name is itself a pointer to pointers to ints. For this array of seven arrays of four ints, the array name is itself a pointer to arrays of four ints. Normally we can only use the reference operator on L values, which as we discussed earlier are valid targets of assignment. A special exception though is made for array names. If you reference the name of an array of nx's, you get back a pointer to an array of nx's. So here we can assign the reference of foo to this pointer to arrays of seven ints. Again, as we've said repeatedly, the size of an array is an inherent part of its type, so this example fails compilation. A pointer to arrays of seven ints value cannot be assigned to a pointer to arrays of nine ints variable. Now here's the rule that's most surprising. Normally when we dereference a pointer to x, we get back the x value to which the pointer pointed. The exception is with pointers to arrays. An array is not an actual kind of value, so dereferencing a pointer to an array can't possibly return an array value. There's just no such thing. Instead, dereferencing a pointer to an array of nx's will return a pointer to x. So here dereferencing this pointer to arrays of seven ints variable bar returns a pointer to int, which we can assign to the pointer to int variable p.