250 likes | 322 Views
CS 31 Discussion, Week 9. Faisal Alquaddoomi, faisal@cs.ucla.edu Office Hours: BH 2432, W 4:30-6:30pm, F 12:30-1:30pm. Structs Review. Structs are useful for holding collections of named variables
E N D
CS 31 Discussion, Week 9 Faisal Alquaddoomi, faisal@cs.ucla.edu Office Hours: BH 2432, W 4:30-6:30pm,F 12:30-1:30pm
Structs Review • Structs are useful for holding collections of named variables • Differ from arrays in that the types can be different, and each ‘slot’ has a name rather than a number • Use the struct <name> { <vars> }; syntax to create a new type, e.g.structMyStruct { int p; }; • Create instances using the new type, e.g. MyStruct thing; • Use the field access operator ‘.’ to access a variable in a struct, e.g.thing.p = 150;
Structs Review, Part 2 • Pointers to structs can be declared, e.g.MyStruct *ptr = &thing; • Field accesses on a pointers can be accomplished either through (*ptr).p = 300; // deference-and-dot, or… ptr->p = 300; // arrow, works same as above • Both change the original object, ‘thing’
Structs Review, Part 3 • Structs can be passed to functions • the default is pass-by-value, which copies the entire struct, and can be inefficient • Struct pointers can be passed instead • can be marked const to prevent modification to the original object (const == “read-only”) • As with all other types, structs can be passed-by-reference using the ‘&’ type, e.g.void someFunc(MyStruct& thing) { /* thing is a reference; changing it changes the original param */ }
Aside: Dynamic Memory • Two types of memory: • stack (what we’ve been using so far for variables, parameters, etc.) • heap (aka “dynamic memory”) • Stack memory is allocated inside of functions and is deallocated automatically when the function ends • “int x;” inside of main(), for instance, allocates 4 bytes on the stack, which is released when main() ends • This is why we couldn’t pass back the local array in project 6; it was in the stack memory for that function, and was thus freed when the function ended
Dynamic Memory Cont’d • Heap memory, on the other hand, is explicitly allocated and deallocated • allocated via the ‘new’ keyword (returns a pointer to the new memory)int*p = new int; • deallocated via the delete keyword:delete p; • Heap memory, unlike stack memory, is allocated at runtime, meaning that expressions like the following will work:int x = 250; int *p = new int[x]; • Losing track of heap memory (i.e. by allocating it and then forgetting to deallocate it) is called a memory leak • Over time, memory leaks can cause your program to crash, because all of the memory may eventually be leaked • If new can’t allocate the memory you ask for, it’ll return NULL; for example:int *p = new int[999999999999999];
Problems with Structs • The main problem: structs only store data • Up till now, data (values/variables) and code (functions) have been totally separate things • We write functions to manipulate parameters (data), and return the result as data • Functions have local variables, but they’re only used as temporaries to perform the purpose of the function; once the function ends, the only thing that remains is the result • (and any side effects, like changing call-by-reference parameters) • This is out of whack with how we view the world • Objects in the world have data (a person’s name, for instance) • Objects change over time (i.e. the data is modified) • Objects in the world also have operations that only make sense *for that object* and can only modify the data within that particular object
Typical Example: A Car • Let’s say that we’re writing an inventory system for a used car dealership • Need to keep track of cars, as well as update them • What sorts of data do cars have? • Make, model, mileage… • What kinds of operations can we perform on cars? Only concerned with two for now: • drive(miles), which updates the mileage • checking the mileage’s current value
First Approach: Using a Struct • We’d declare a struct Car, with fields make (string), model (string), and mileage (int):struct Car { string make; string model; int mileage;}; • We’ll declare a function for updating the mileage, drive(int miles, …) (classes, ex. 1) • We can check the mileage by just accessing the mileage field directly
Problems with the Struct Approach • Say that we’re an unscrupulous car dealer • We have a car instance myCar; we can easily set the mileage to 10 miles like so:myCar.mileage = 10; // good as new! • There’s no relationship between the drive() function and the struct • if that function can modify it, so can anyone else • Not to mention that having a bunch of functions floating around that relate to the struct is kind of messy…
More Problems with Structs • When creating an instance of a struct, all the fields have default values until we explicitly set them • It’s easy to forget to set a field, with potentially disastrous results • When a struct goes out of scope (e.g. the function in which it’s declared ends), it’s simply freed • It can’t do anything intelligent, like free up dynamic memory; as mentioned, this leads to memory leaks • Back to the problem at hand: We need a way to associate structs and functions so that a function “belongs” to a struct
Enter Classes • A class is very much like a struct, except that it contains both data and functions • Much like structs, classes are like blueprints • When we build a house (or a car, or a person) out of a class, we call the resulting thing an instance • We call functions in a class methods • The fields of a class are often called members • The methods of a class operate on the data in the instance of a class • (Minor exception: “static” members belong to the class itself and not to an instance)
Rewriting Our Car as a Class • A class looks almost exactly like a struct (at first):class Car { string make, model; int mileage;}; • We now add the methods we need to it (classes, ex. 2), specifically drive() • In order to fix the problem of anyone being able to change the mileage, we have to introduce an additional concept: access protection
Classes and Access Protection • Since structs are just data, there’s no way to indicate that only certain functions should be able to change certain fields of a struct • Now that classes can have methods, we can identify methods as “privileged” to accessing certain data • They’re “privileged” in the sense that they can see and change data that outside code can’t • Making data “privileged” is accomplished via “access protection specifiers”: • public: anyone can see/modify this data • private: only this class can see/modify this data • protected: this class and any classes that inherit from it can see/modify this data (don’t need to understand this) • Access protection specifiers apply to methods as well • if a method is private, only that class can call it (usually from other, public methods of the class)
Back to the Car Class • We’ll make mileage private so that the car dealer can’t change it • (except by calling the public method drive(), which can only increase it) • Problem: how do we check the mileage? • Solution: we write an “accessormethod” (also called a “getter”), which just returns the value of a private member (classes, ex. 3) • This way, the value can be read, but not changed • In order to make these methods play nice with const references, we should flag any methods that don’t change the class’s data as ‘const’ • Even more problematic: how do we set the mileage initially? • mileage is now private, so we can’t set it after we create it, e.g.:Car myCar;myCar.mileage = 0; // ^ error: it’s private! • We’ll address this problem with a constructor in a bit…
Using Methods of a Class • First, let’s construct an instance of car:Car myCar; • Now we can call methods of it like so:myCar.drive(100); // update our mileage • We can use the accessors, too:cout << myCar.getMileage() << endl; • Each method has access to a special keyword called this that points to the current instance on which the method was called • In this case, inside of drive() and getMileage(), this points to myCar • Since accessing a member of the current instance is so common, ‘this’ is implied • you can refer to mileage as either ‘mileage’ or ‘this->mileage’
The Car Class and Constructors • If we make a new instance, we want the instance to start with valid data • mileage should be 0, for instance, and the make/model should always be set to something • By default, we have to set these manually • even worse, private members like mileage can’t be set when we create the class • This is where a constructor becomes useful, a special method that’s invoked when an instance is first created
Constructor Definition • A constructor is created by defining a method with no return type and with the same name as its enclosing class, e.g.:class Car { private: string make, model;int mileage; public: Car() { mileage = 0; }}; • The constructor will be run when the class is instantiated, e.g.:Car myCar; // mileage is set to 0
Constructors with Parameters • We can optionally specify initial values for our other fields, like the make and model, accomplished by adding parameters to our constructor: class Car { private: string make, model; int mileage; public: Car(string make, string model) { this->make = make; this->model = model; mileage = 0; } };
Creating a Car Instance with a Parameterized Constructor • Now that the Car constructor includes parameters, they must be specified like so:Car myCar(“Chevrolet”, “Malibu”);// make == “Chevrolet”, model == “Malibu” • Note also the use of the special this pointer in the constructor • Always points to the current instance inside of a method • Usually implied, e.g. using ‘mileage’ inside of the constructor implicitly refers to ‘this->mileage;’ • You can use it explicitly when there’s a conflict between the name of a local variable/parameter and a member • In the constructor example, parameters “make” and “model” shadow the members with the same names, thus this must be used to access the members
Aside #1: Multiple Constructors • Say that you don’t want to require that the user of your class specify the make and model every time they make a car instance • A class can contain multiple constructors, each one with a different “signature” (that is, the parameters for that method) • The appropriate method will be chosen based on the arguments that were given (classes, ex. 4) • It’s usually nice to have a constructor that takes no arguments (or at least the bare minimum), and include other constructors that allow the user to set other fields • In general, the practice of using multiple functions with the same name, but different signatures is called overloading • Each function is actually distinct from the others; the function to call is determined by examining the arguments
Aside #2: Destructors • Just as a constructor is called when an instance is created, a destructor is called when an instance is destroyed • This occurs when the instance goes out of scope (i.e. if the instance is a local variable in a function and the function ends) • It can also occur when a dynamically allocated instance is freed via the delete keyword • Destructors follow the same naming convention as constructors (no return type, same name as class), with some caveats: • Their name begins with a tilde (~), e.g. ~Car() { } • Destructors don’t take any parameters, as there’s no way to specify what the parameters should be • Destructors, like constructors, are optional, but are useful when you need to clean up after yourself • Most often used to free dynamic memory that belongs to the class
Classes Review • Classes are the same as structs, except for the use of the class keyword and the ability to have methods, functions that operate on the class’s fields (which are called members) • (In fact, in C++ structs are actually classes that have all their data public as default) • Methods and members can be public, private, or protected, which determines how code outside the class can interact with it • A class’s methods have access to all of that class’s data, public or private; only public methods of a class can be called, and public members accessed, from outside the class • It’s common to add public “getter” methods that return private fields, allowing them to be read but not modified • “getters” should be const so that they can be used with const class references • The this keyword, when used inside of the methods of a class, always refers to the current instance • Usually implied, so you can refer to a field of a class without it, but can be useful when a local variable/parameter has the same name as a field
Classes Review, Part 2 • Classes can optionally include a special constructor method that sets up an instance • Has no return type, same name as the class • This constructor can include parameters, which must be specified when the class is created • A class can have multiple constructors differentiated by their signatures (namely, the parameters they take) • Classes can also include a destructorwhich cleans up an instance when it’s destroyed • Defined the same way as a constructor… • …except that it has no parameters and has a tilde (~) before the class name • Since it takes no parameters, there can only be a single destructor for each class
Extra Dynamic Memory Exercise:a Car Repair Work Queue • Let’s write a class that will keep track of a variable number of cars in a structure called a “queue” • First-in, first-out data structure • We don’t want to waste space, so allocating a giant array isn’t going to work • We’ll have to use both classes and dynamically allocated memory • Refer to (carinventory, ex.1) for the code