- Built-in primitive types (
int, double, char) are limiting: not everything is a number or character - Real world entities are made up of multiple pieces of data
- Example: Lab 10 sorting "teams"
- Kept a lot of different pieces of data in different arrays
- Every "swap" when sorting had to swap every value in every array
- It was really easy to screw this up! "Bookkeeping" is hard
- Really inconvenient to store related pieces of data in different arrays
- Encapsulation:
- The grouping of data
- Protection of data
- The grouping of functionality that acts on that data
- C only supports #1: the grouping of data, "weak encapsulation"
- C provides weak encapsulation through structures or "structs"
- Syntax:
typedef struct {
int day
int month
int year;
} Date;
typedef struct {
int nuid;
char *firstName;
char *lastName;
double gpa;
Date dateOfBirth;
} Student;- When a structure or object "owns" another structure or object it is called composition
- Naming conventions:
- Use
UpperCamelCasingfor structure names - Use
lowerCamelCasingfor "member" variables
- Use
- use
typedef structto declare a structure, curly brackets to define the "member" variables, each of which is separated by a semicolon - Structures are usually declared in a header file, say
student.h - Structures may contain other structures as members, BUT the order in which you declare them matters: "child" structures must be declared first
- Once declared, you can create variables of that particular type
Student me;
me.nuid = 35140602;
//create enough room to hold my first name:
me.firstName = (char *) malloc(sizeof(char) * 6);
strcpy(me.firstName, "Chris");
me.lastName = (char *) malloc(sizeof(char) * 7);
strcpy(me.lastName, "Bourke");
me.gpa = 3.5;
me.dateOfBirth.day = 9;
me.dateOfBirth.month = 7;
me.DateOfBirth.year = 1990;- You can create a factory function that more conveniently allows you to "construct" new instances of a structure.
Student * createStudent(int nuid,
char *firstName,
char *lastName,
double gpa,
int day,
int month,
int year) {
Student *s = (Student *) malloc(1 * sizeof(Student));
s->nuid = nuid;
s->firstName = (char *) malloc(sizeof(char) * (strlen(firstName)+1) );
strcpy(s->firstName, firstName);
s->lastName = (char *) malloc(sizeof(char) * (strlen(lastName)+1) );
strcpy(s->lastName, lastName);
s->gpa = gpa;
s->dateOfBirth.day = day;
s->dateOfBirth.month = month;
s->DateOfBirth.year = year;
return s;
}
//...
Student *joe = createStudent(1234, "Joe", "Schmoe", 4.0, 1, 1, 1970);
Student *jane = createStudent(5678, "Jane", "Doe", 4.0, 1, 2, 1970);-
Generally it is better to use pointers to structures as they can be potentially large
-
When using a pointer to a structure, you use the arrow operator,
->to access member variables -
When you have a regular old structure, you can use the
.(dot operator) -
Exercise: write a function that prints a student structure's information to the standard output
void printStudent(const Student *s) {
char *str = studentToString(s);
printf("%s\n", str);
free(str);
return;
}
char * studentToString(const Student *s) {
char buffer[1000];
//print the student to the buffer:
sprintf(buffer, "%s, %s (%08d), .2f", s->lastName, s->firstName,
s->nuid, s->gpa);
char *result = (char *) malloc(sizeof(char) *
(strlen(buffer) + 1) );
strcpy(result, buffer);
return result;
}- If you have a regular old structure,
Student sthen to access its member variables you use the "dot" operator:s.nuid - If you have a pointer to a structure,
Student *sthen to access its member variables you use the "arrow" operator:s->nuid - Create an "initializer" function that takes a structure that has already been allocated and set its member variables
void initStudent(Student *s,
int nuid,
char *firstName,
char *lastName,
double gpa) {
s->nuid = nuid;
s->firstName = (char *) malloc(sizeof(char) * (strlen(firstName)+1) );
strcpy(s->firstName, firstName);
s->lastName = (char *) malloc(sizeof(char) * (strlen(lastName)+1) );
strcpy(s->lastName, lastName);
s->gpa = gpa;
return;
}- Often "cleaning up" a structure involves several steps:
- Clean up both strings for the first/last name
- clean up the structure itself
- A function that cleans up a structure for you is sometimes referred to as a "destructor"
void destroyStudent(Student *s) {
free(s->firstName);
free(s->lastName);
free(s);
}- Often you may find it necessary or convenient to create copies of structures
- It is convenient to write a "copy function" that does this for you
/**
* This function creates a deep copy of the given
* student and all of its data.
*/
Student * copyStudent(const Student *s) {
return createStudent(s->nuid, s->firstName, s->lastName,
s->gpa, s->dateOfBirth.day, s->dateOfBirth.month, s->dateOfBirth.year);
}int n = 10;
int *arr = (int *) malloc(n * sizeof(int));
Student *roster = (Student *) malloc(n * sizeof(Student));
roster[0].nuid = 311421342;
void printRoster(Student *roster, int n) {
for(int i=0; i<n; i++) {
//TODO: print the i-th student here!
printStudent(&roster[i]);
}
}- Principles of Design: when defining/designing an object or structure: think about the essential pieces of data that make up that object
- Identify each "field" and each fields type
- Decompose objects into smaller parts, creating sub objects or "sub structures" as necessary
- Don't reinvent the wheel: if there is already a structure defined by the standard library or available in an external library, USE IT!
- The semantics of an object will dictate its design
- Don't over-engineer your structures (or your code for that matter): YAGNI (You Aren't Going To Need It)