Classes ● ● ● We've seen that structs can be used to bundle data together into a single object. As well being able to store state, it's useful if objects can do things. We're going to take the idea of an Employee at a company. 1 Classes ● structs and classes are very similar behind the scenes but are used for different things: – A struct should be used when you want to bundle together some related data (e.g. our command-line arguments) – A class should be used when the data inside are correlated and you want to provide an interface to that e.g. a std::vector keeps track of its data and the number of elements. 2 Defining a class ● A class is defined in a similar way to a struct //Employee.h class Employee { public: std::string name; ///< The name of the Employee int salary; ///< Total salary of the Employee in pounds }; ● ● Key difference is the public specifier By default a class's members are hidden to the outside. You need to explicitly make them available 3 Constructors ● A constructor is used to set up the initial values of an object ● It is named the same as the class ● It is declared inside the class class Employee { public: Employee(const std::string& name); }; std::string name; ///< The name of the Employee int salary; ///< Total salary of the Employee in pounds 4 Constructors ● A constructor is defined like any other function ● Put the definition in the .cpp file ● You need to specify the scope with :: ● Access the members of the class directly Employee::Employee(const std::string& employeeName) { name = employeeName; salary = 0; } ● This is equivalent to: int salary; //Declare salary = 0; //Initialise 5 Constructors ● It's always safer (and sometimes necessary) to initialise variables at definition Employee::Employee(const std::string& employeeName) : name{employeeName}, salary{0} { } ● Which is more like: std::string name {employeeName}; //Declare and initialise int salary {0}; //Declare and initialise ● Data members declared as const must be initialised this way 6 Default values for members ● You can set default values of data members directly in the class definition class Employee { public: Employee(const std::string& name); }; std::string name; ///< The name of the Employee int salary = 0; ///< Total salary of the Employee in pounds ● This defines the default value used for initialisation in case one is not given in the constructor 7 Constructing an object ● std::string and std::vector are defined as classes so we use a similar syntax to that we used there: #include "Employee.h" int main() { Employee jane {"Jane"}; //Calls the contructor we declared std::cout << jane.name; } 8 Aside: naming conventions ● ● Most software projects have naming conventions. In this course we use: – UpperCamelCase for class and struct names – lowerCamelCase for function and variable names – trailingUnderscore_ for data members This makes it easier to see at a glance the context of a name in the source code 9 Exercise 1 ● ● Make a class skeleton for a CaesarCipher with the key as a member variable The constructor should take the key as an argument 10 Information hiding ● ● There is a general principle in programming of information hiding. You should separate what something does from how it does it This is implemented in C++ classes by marking member variables as private and providing public functions to make the information available 11 Information hiding ● ● ● The name is stored as a single std::string In the future we may want to store it as a pair of strings for first and last name Getters and setters provide this interface: class Employee { public: void setName(const std::string& newName); std::string name() const; private: std::string name_; ///< The name of the Employee }; 12 Getters and setters ● Most getters and setters are simple void Employee::setName(const std::string& newName) { name_ = newName; } std::string Employee::name() const { return name_; } ● But they could be more complex as we will see later std::string Employee::name() const { return firstName_ + " " + lastName_; //for example... } 13 const functions ● ● The name() function had a const label after it This means that nothing inside that function can change the object it is acting on std::string Employee::name() const { name_ = "Fran"; //Compiler error return name_; //for example... } ● It also means you can call it on a const object const Employee bill {"Bill"}; bill.setName("John"); //Compiler error std::cout << bill.name(); //This is fine ● Member functions should be made const unless 14 explicitly needed otherwise Member functions ● ● Member functions can do anything, not just get and set Classes can perform actions: class Employee { public: std::string speak(const std::string& phrase) const; //... }; std::string Employee::speak(const std::string& phrase) const { return "'" + phrase + "', said " + name(); } 15 Exercise 2 ● ● Add a member function to CaesarCipher which encodes a string and returns the result You should use the key_ data member as the key 16 Documentation ● ● ● ● All classes, functions and data members should be commented A class comment should describe what the class is used for and how to use it A function comment should describe all arguments, the return value, any side effects and if applicable any pre- or post- conditions A standard syntax exists called Doxygen 17 Doxygen ● ● ● Doxygen uses a special comment block syntax. Single line comments start with /// and multiline comments start with /** It defines a number of special commands which start with a backslash for structured information Documentation comment comes just before the thing it is annotating 18 /** * Employee has a salary and a name * Use it by doing ... */ class Employee { public: /** * Create a new Employee with a name and default values for salary * * \param name the name of the Employee */ Employee(const std::string& newName); /** * Sets the salary of the employee. * * \param newSalary the value to set the salary to */ void setSalary(int newSalary); /// \return the base gross salary of the employee int salary() const; /// \return the name of the employee std::string name() const; //... 19 We'll cover how to generate this automatically in more detail next week 20 Exercise 3 ● ● Add some Doxygen documentation to your CaesarCipher class Make sure all the functions and data members are documented as well as the class overall 21 Enumerations ● ● Enums provides a way of defining a set of named values They are useful when there is a small finite set and you want to perform different actions depending on the value 22 Enumerations ● Declaring an enum is like declaring a class /// The rank of the employee enum class Rank { Junior, ///< A new person at the company Senior, ///< Someone whos been here a while Chief ///< Someone super special }; ● It makes a new type which can be instantiated: Rank personsRank {Rank::Senior}; if(personsRank == Rank::Senior) { std::cout << "Senior" << std::endl; } 23 Enums and switches ● Enums are very useful when mixed with switch statements Rank personsRank {Rank::Senior}; switch(personsRank) { case Rank::Junior: return 0; case Rank::Senior: return 3000; } ● The compiler will warn you that you missed one of the entries in the enum (Rank::Chief) 24 Enum conversions ● Even though enums are just integers behind the scenes, you can't convert freely between them enum class Colour { Red, Blue, Green }; Colour c {Colour::Red}; c = Rank::Senior; //COMPILER ERROR //Rank::Senior is '1', doesn't set 'c' to 'Blue' if(c == Rank::Junior) { //COMPILER ERROR std::cout << "This doesn't make sense" << std::endl; } 25 Exercise 4 ● Add an enum called CipherMode to designate the encryption mode (encrypt or decrypt) ● It only needs the two states ● Make sure you document the enum 26 Operator overloading ● Built-in types in C++ know how to perform mathematical operations ● They can do +, -, *, / between them ● They can also be printed with std::cout << ● More complex types can do more like std::vector and its subscript[] operator 27 Operator overloading ● ● We can provide this functionality for our own types by providing specially named function, either to our class or which take our class as an argument. When it sees std::cout << employee the compiler will look for a function called operator<< which takes a std::ostream& and an Employee as arguments 28 Stream operator ● We define the overload function like any other std::ostream& operator<<(std::ostream& os, const Employee& employee) { os << " Name: " << employee.name() << std::endl; os << "Salary: " << employee.salary() << std::endl; return os; //Return the reference we were passed } ● ● Similarly you can overload other operators, see en.cppreference.com/w/cpp/language/operators Only overload those which make sense. Employee+Employee doesn't mean anything! 29