Skip to content

Instantly share code, notes, and snippets.

@cbourke
Created October 30, 2018 01:22
Show Gist options
  • Select an option

  • Save cbourke/9304cdd3bd86e7619a8f167704e368c9 to your computer and use it in GitHub Desktop.

Select an option

Save cbourke/9304cdd3bd86e7619a8f167704e368c9 to your computer and use it in GitHub Desktop.
auto-posted gist

CSCE 155E - Computer Science I

Fall 2018

Encapsulation: Structures & Objects


  • 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:
    1. The grouping of data
    2. Protection of data
    3. The grouping of functionality that acts on that data

Encapsulation in C

  • 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 UpperCamelCasing for structure names
    • Use lowerCamelCasing for "member" variables
  • use typedef struct to 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

Using Structures

  • 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;

Solution: write a function!

  • 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;
}

Quick Review

  • If you have a regular old structure, Student s then to access its member variables you use the "dot" operator: s.nuid
  • If you have a pointer to a structure, Student *s then 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);

}

Arrays of Structures

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]);
  }
}

Summary

  • 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)













Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment