Let's talk about structs. Structs provide us with a way to group a bunch of variables together into a nice package. It's probably easiest to see an example right away, so we say struct, then opening curly brace, and in this struct, we'll have an int age, a char * name, and that's it. It may seem weird with a semicolon after a curly brace, but it's in fact necessary with structs. Any valid type can go within the struct definition. Here, we've used an int and a char *, but you could also use an array, of say, 100 elements or even another struct. When you're using structs in C, you're creating new types out of a collection of other types. Here, we're making a new type out of an integer and a char *. As we'll see later, a struct type is in a lot of ways equivalent to any other type you're used to. Usually, I'll be comparing how a struct type is similar to an integer type. While the code we wrote is valid C, it's not very useful, and clang will give us a warning. Remember how structs and its are similar? Well, we basically just said int, which isn't a very helpful line. So let's actually declare a variable of that type by giving it a name before the semicolon. We'll call the variable student. Now we've declared a variable called student with the type given by the struct. How do we get to the variables inside the struct? Technically, the names for these variables are members. To access any particular member in a student struct, you append a dot to the variable name, followed by the name of the member you want. So here, the only 2 valid possibilities are student.age and student.name. And we can do something like student.age = 12 and student.name = student. Now what if we wanted to make a second student? You might think to copy and paste these lines and change student to student 2 or something, and that will work, but technically, student and student 2 don't have the same type. See, you won't be able to assign them to one another. This is because, so far, your struct has been anonymous. We need to give it a name. To do that, we insert the name of the struct after the word struct. student, followed by the definition. We can still immediately declare a variable of type struct student, like we did before. We'll call it S1 By giving the struct a name, we can now use struct student in almost the exact same way we would use int. So we can declare a variable of type struct student, like struct student S2. Like arrays, structs provide a shortcut initialization syntax, so we can say, struct student S2 equals left curly brace 3, S2. Here, S2.age will be 3, and S2.name will point to S2. Think of all the things you can do with an int type and most of them you can do with a struct student type. We can use a struct student as a type of a function parameter. We can use struct student inside of a new struct. We can have a pointer to a struct student. We can do size of struct student. Struct student is a type just like int is a type. We can also assign S1 to S2 since both are of the same type, so we can do S1 = S2. What happens if we do S1.age = 10? Does S2 change at all? Again, think of the structs just as regular integers. If we assign some int X to some int Y, like X = Y and then change X, as in X++, does Y change at all? Y does not change here, and so neither does S2 above. S2.age is still 3. But note that when assigning one struct to another, all of the pointers still point to the same thing, since they were just copied. If you don't want the pointers to be shared, you'll need to manually handle that, perhaps by malicking one block of memory for one of the pointers to point to and copying the data over. It might be annoying to have to write struct student everywhere. Using a type def, we can do type def struct and we'll call it student. Now, we can use student everywhere that we used to use struct student. This type def's an anonymous struct and calls it student. But if we also keep the student identifier next to the word struct, as in typedef struct student, we could use both struct student and student interchangeably now. They don't even have to have the same name. We could type def struct student to Bob and then struct student and Bob would be interchangeable types. Regardless of the type def, we need the identifier next to struct if the definition of the struct is recursive. For example, type def struct node and it will be defined as an int val and it will have a pointer that points to another struct node., as in struct node * next. And then we'll call it node. This struct is recursive, since the definition of struct node contains within it a pointer to a struct node. Notice that we have to say struct node * next inside of the definition of the struct node, since the type def hasn't finished yet to allow us to simplify this to just node * next. You'll learn more about structs similar to this when dealing with linked lists and trees. What about structs in a function? This is also perfectly valid. We could have void func which takes as an argument, student s and does something with that student. And then we can pass it as student struct like so. Func of S1 from before. The struct behaves exactly as an integer would when passed to a function. Func receives a copy of S1 and so can't modify S1; rather, only the copy of it that's stored in S. If you want the function to be able to modify S1, func will need to take a student * S, and you'll have to pass S1 by address, like so. Student * S, func & S1. There's another reason to pass by address here. What if our struct contained 100 fields? Every single time we pass a student to func, our program needs to copy all of those 100 fields into func's argument S, even if it never uses the vast majority of them. So even if func doesn't plan on modifying the student, if can still be valuable to pass by address. Okay, what if we want to create a pointer to a struct? We could do something like student* S equals malloc size of student. Notice that size of still works here. So how do we now access the age member of the block that S points to? You might first think to do * S.age = 4, but this won't quite work. Since this will really be interpreted as * S.age in parentheses = 4, which won't even compile, since S isn't a struct or rather a pointer to a struct, and so the dot won't work here. We could do (*S).age=4 but the parentheses can get annoying and confusing. Thankfully, we have a special arrow operator that looks something like S->age=4. These 2 ways of referencing age are equivalent and we don't really ever need the arrow operator, but it makes things look nicer. Since S is a pointer to some block of memory that contains the struct, you can think of S>age as follow the pointer arrow and grab the age member. So why should we ever use structs? It's definitely possible to get away with just the primitive integers, chars, pointers and the like that we're used to; instead of S1 and S2 before, we could have had age1, age2, name1, and name2 all at separate variables. This is fine with only 2 students, but what if we had 10 of them? And what if instead of only 2 fields, the student struct had 100 fields? GPA, courses, hair color, gender, and so on. Instead of just 10 structs, we need 1,000 separate variables. Also, consider a function that takes that struct with 100 fields with its only argument and prints out all fields. If we didn't use a struct, every single time we call that function, we need to pass at all 100 variables, and if we have 100 variables for student 1, and 100 variables for student 2, we need to be sure we don't accidentally pass some variables from student 1 and some variables from student 2. It's impossible to make that mistake with a struct, since all 100 variables are contained in a single package. Just a couple of final notes: If you've understood everything up to this point, great. The rest of the video is just for completeness' sake. Because structs can hold any type of pointer, they can also hold function pointers. If you're familiar with object oriented programming, this provides a way to use structs to program in an object oriented style. More on function pointers at another time. Also, sometimes you may have 2 structs whose definitions depend on one another. For example, we could have struct A, which is defined as a pointer to a struct B, struct B * X, and now we can have a struct B which is defined as a pointer to a struct A, struct A * Y. But this won't compile, since struct B doesn't exist at the time that struct A is being compiled. And if we swap struct A and struct B, then we'd just be left with the same problem; this time, with struct A not existing. To solve this, we can write struct B; before the definition of struct A. This is called a forward declaration. This just lets the compiler know that struct B is a valid type that will be fully defined later or elsewhere. My name is Rob Bowden, and this is CS50. [CS50.TV]