Fundamentals
The definition of a Class consists of two parts: the declaration of the class and the body immediately following the declaration.The main part is enclosed by a pair of braces and ends with a semicolon.Two keywords within the principal, public and private, are used to denote "member access" for each block.Public member s can be accessed anywhere in the program, and private member s can only be accessed within member function or class friend.
The class's forward declaration tells the compiler the class name and does not provide any additional information about the class (such as the class-supported behavior and the included data member).Pre-declarations allow us to define a class pointer or use this class as a data type.
All member functions must be declared within the class body.It is free to decide whether to define them at the same time.If defined within the class body, this member function is automatically considered an inline function.To define a member function outside the class body, a special syntax must be used to distinguish which class the function belongs to, using the double colon':', the so-called class scope resolution operator.
For inline functions, there is no difference in definition from within or outside the class body.However, like the non-member-inline function, it should also be placed in the header file.Inline member functions along with class definitions are usually placed in header files with the same name as the class.The non-inline member function should be defined in the program code file, which usually has the same name as the class followed by an extension of.C,.cc,.cpp, or.cxx(x stands for a horizontal +).
Initialize the data member, magic will not be generated automatically, and the compiler will not process it for us automatically.If we provide one or more special initialization functions, the compiler will call the appropriate function to handle each time the class object is defined.These special initializers are called constructors.Constructor's function must have the same class name.The syntax states that the constructor should not specify a return type or return any value, and it can be overloaded.The simplest constructor is the so-called default constructor, which does not require any arguments.(There are no parameters without parentheses, which interpret them as a function for compatibility with C).
The opposite of a constructor is a destructor.A destructor is a user-defined class member.Once a class has a destructor, it automatically calls the destructor when its object ends its life.Destructors are primarily used to release resources that are allocated in a constructor or in the life cycle of an object.Destructor names are strictly defined: class names are prefixed with the'~'prefix.It will never return a value or have any parameters.It is never possible to overload because its parameter list is empty.Destructors are not absolutely necessary and we have no obligation to provide them. In fact, one of the hardest parts of C++ programming is knowing when destructors need to be defined and when they don't.
When designing a class, we must ask ourselves whether it is appropriate to have a behavior pattern of "member-by-member initialization" on top of it.If the answer is yes, we don't need to provide an additional copy constructor.If the answer is no, we must define a copy constructor and write the correct initialization in it.If it is necessary to write a copy constructor for a class, it is also necessary to write a copy assignment operator for it.
The static member function can be called in the case where it has nothing to do with any object.A member function can only be declared static if it does not access any non-static member by adding the keyword static before the declaration.When we define a member function outside the class body, there is no need to repeat the keyword static, and this rule also applies to static data member s.
Operator overload rules:
- New operators cannot be introduced.Other operators can be overloaded except for the four operators'.','. *',':','?:'.
- The number of operands of an operator cannot be changed.Each binary operator requires two operands, and each unary operator requires exactly one.Therefore, we cannot define an equality operator and have it accept more than two or fewer operands.
- The precedence of an operator cannot be changed.For example, division always takes precedence over addition.
- At least one parameter in the parameter list of an operator function must be of type class.That is, we cannot redefine an existing operator for a non-class type, such as a pointer, or introduce a new operator for it.
For overloads of the increment increment operator (or decrement), there are two versions, the front and the back.The parameter list in the front version is empty, and the parameter list in the back version should be spatiotemporal in nature. However, overload rules require that the parameter list be unique.Therefore, C++ comes up with a workaround that requires the rear version to have an int parameter.Where and from the int parameter occurs, the compiler automatically postpones an int parameter (its value must be 0), which the user does not have to worry about.
The so-called friend has the same access rights as the class member function and can access the private member of the class.You can declare a function prototype as a friend of a class by simply preceding it with the keyword friend.This declaration can appear anywhere in the class definition and is not affected by privates or public s.If you want to declare several overloaded functions as friends of a class, you must explicitly add the keyword friend to each function.
maximal munch compiles rules that require each symbol sequence to always be interpreted as the longest of the Legal Symbol Sequences.Since >> is a legal sequence of operators, these two symbols must be viewed together if there is no gap between the two >.Similarly, if we write a+++p, it must be interpreted as "a+++p".
Exercise Answers
Exercise 4.1 Create Stack.h and Stack.suffix, where suffix is an extension that your compiler can accept or that your project uses.Write the main() function, practice manipulating all open interfaces of Stack, and compile and execute them.Program code file and main () must contain Stack.h:#include "Stack.h"
Stack.h #include <string> #include <vector> using namespace std; class Stack { public: bool push(const string&); bool pop(string& elem); bool peek(string& elem); bool empty() const { return _stack.empty(); } bool full() const { return _stack.size() == _stack.max_size(); } int size() const { return _stack.size(); } private: vector<string> _stack; }; Stack.cpp #include "Stack.h" bool Stack::pop(string& elem) { if (empty()) return false; elem = _stack.back(); _stack.pop_back(); return true; } bool Stack::peek(string& elem) { if (empty()) return false; elem = _stack.back(); return true; } bool Stack::push(const string& elem) { if (full()) return false; _stack.push_back(elem); return true; } main.cpp #include "Stack.h" #include <iostream> int main() { Stack st; string str; while (cin >> str && !st.full()) { st.push(str); } if (st.empty()) { cout << '\n' << "Oops: no strings were read -- bailing out\n "; return 0; } st.peek(str); if (st.size() == 1 && str.empty()) { cout << '\n' << "Oops: no strings were read -- bailing out\n "; return 0; } cout << '\n' << "Read in " << st.size() << " strings!\n" << "The strings, in reverse order: \n"; while (st.size()) { if (st.pop(str)) cout << str << ' '; } cout << '\n' << "There are now " << st.size() << " elements in the stack!\n"; }
Exercise 4.2 Extends the Stack function to support find () and count () operations.find() returns true or false to see if a value exists.count() returns the number of occurrences of a string.Re-implement main() of exercise 4.1 so that it calls both functions.
Stack_2.h #include <string> #include <vector> using namespace std; class Stack { public: bool push(const string&); bool pop(string& elem); bool peek(string& elem); bool empty() const { return _stack.empty(); } bool full() const { return _stack.size() == _stack.max_size(); } int size() const { return _stack.size(); } bool find(const string& elem) const; int count(const string& elem) const; private: vector<string> _stack; }; Stack_2.cpp #include "Stack_2.h" #include <algorithm> bool Stack::pop(string& elem) { if (empty()) return false; elem = _stack.back(); _stack.pop_back(); return true; } bool Stack::peek(string& elem) { if (empty()) return false; elem = _stack.back(); return true; } bool Stack::push(const string& elem) { if (full()) return false; _stack.push_back(elem); return true; } bool Stack::find(const string& elem) const { vector<string>::const_iterator end_it = _stack.end(); return ::find(_stack.begin(), end_it, elem) != end_it; } int Stack::count(const string& elem) const { return ::count(_stack.begin(), _stack.end(), elem); } main.cpp #include "Stack_2.h" #include <iostream> int main() { Stack st; string str; while (cin >> str && !st.full()) { st.push(str); } if (st.empty()) { cout << '\n' << "Oops: no strings were read -- bailing out\n "; return 0; } st.peek(str); if (st.size() == 1 && str.empty()) { cout << '\n' << "Oops: no strings were read -- bailing out\n "; return 0; } cout << '\n' << "Read in " << st.size() << " strings!\n"; cin.clear(); //Eliminate end-of-file Settings for cout << "what word to search for? "; cin >> str; bool found = st.find(str); int count = found ? st.count(str) : 0; cout << str << (found ? " is " : "isn't ") << "in the stack."; if (found) cout << "It occurs " << count << " times\n"; }
Exercise 4.3 Consider the global data defined below:
string program_name; string vector_stamp; int version_number; int tests_run; int tests_passed;
Write a class to wrap this data.
#include <string> using std::string; class globalWrapper { public: static int tests_passed() { return _tests_passed; } static int tests_run() { return _tests_run; } static int version_number() { return _version_number; } static string version_stamp() { return _version_stamp; } static string program_name() { return _program_name; } static void tests_passed(int nval) { _tests_passed = nval; } static void tests_run(int nval) { _tests_run = nval; } static void version_stamp(const string&nstamp) { _version_stamp = nstamp; } static void version_number(int nval) { _version_number = nval; } static void program_name(const string& npn) { _program_name = npn; } private: static string _program_name; static string _version_stamp; static int _version_number; static int _tests_run; static int _tests_passed; }; string globalWrapper::_program_name; string globalWrapper::_version_stamp; int globalWrapper::_version_number; int globalWrapper::_tests_run; int globalWrapper::_tests_passed;
Exercise 4.4 A User Profile contains the following data: logon records, actual names, logon times, guesses, guesses, ranks - including beginners, intermediate, advanced, experts, and guess percentages (which can be calculated in real time or stored for later use).Write out a class named UserProfile that provides the following actions: input, output, equal test, unequal test.Its constructor must be able to handle the default user level, default login name ("guest").For multiple users with the same name as guest, how can you ensure that each guest has its own unique login session that won't be confused with others?
UserProfile.h #include <iostream> #include <string> #include <map> using namespace std; class UserProfile { public: enum uLevel { Beginner, Intermediate, Advanced, Guru }; UserProfile(string login, uLevel = Beginner); UserProfile(); //default memberwise initilaization and default memberwise copy That's enough, //No need to design copy constructor or copy assignment operator, //Neither destructor bool operator==(const UserProfile&); bool operator!=(const UserProfile& rhs); //The following functions are used to read data string login()const { return _login; } string user_name() const { return _user_name; } int login_count() const { return _times_logged; } int guess_count() const { return _guesses; } int guess_correct() const{ return _correct_guesses; } double guess_average() const; string level() const; //The following functions are used to write data void reset_login(const string& val) { _login = val; } void user_name(const string& val) { _user_name = val; } void reset_level(const string&); void reset_level(uLevel newlevel) { _user_level = newlevel; } void reset_login_count(int val) { _times_logged = val;} void reset_guess_count(int val) { _guesses = val; } void reset_guess_correct(int val) { _correct_guesses = val; } void bump_login_count(int cnt = 1) { _times_logged += cnt; } void bump_guess_count(int cnt=1) { _guesses = cnt; } void bump_guess_correct(int cnt = 1) { _correct_guesses = cnt; } private: string _login; string _user_name; int _times_logged; int _guesses; int _correct_guesses; uLevel _user_level; static map<string, uLevel> _level_map; static void init_level_map(); static string guest_login(); }; inline double UserProfile::guess_average() const { return _guesses ? double(_correct_guesses) / double(_guesses) * 100 : 0.0; } inline UserProfile::UserProfile(string login,uLevel level) :_login(login),_user_level(level), _times_logged(1), _guesses(0), _correct_guesses(0) {} #include <cstdlib> inline UserProfile::UserProfile() : _login("guest"), _user_level(Beginner), _times_logged(1), _guesses(0), _correct_guesses(0) { static int id = 0; char buffer[16]; //_itoa()yes C Functions provided by the Standard Library convert integers to their corresponding ASCII String Form _itoa(id++, buffer, 10); //In the light of guest,Add a unique session identifier ( session id) _login += buffer; } inline bool UserProfile:: operator==(const UserProfile& rhs) { if (_login == rhs._login && _user_name == rhs._user_name) return true; return false; } inline bool UserProfile::operator!=(const UserProfile& rhs) { return !(*this == rhs); } inline string UserProfile::level() const { static string _level_table[] = { "Beginner","Intermediate","Advanced","Guru" }; return _level_table[_user_level]; } //The following are difficult, but they serve as examples map<string, UserProfile::uLevel> UserProfile::_level_map; void UserProfile::init_level_map() { _level_map["Beginner"] = Beginner; _level_map["Intermediate"] = Intermediate; _level_map["Advanced"] = Advanced; _level_map["Guru"] = Guru; } inline void UserProfile::reset_level(const string& level) { map<string, uLevel>::iterator it; if (_level_map.empty()) init_level_map(); //ensure level Does represent a recognized user level _user_level = ((it = _level_map.find(level)) != _level_map.end()) ? it->second : Beginner; } main.cpp #include "UserProfile.h" #include <iostream> ostream& operator <<(ostream& os, const UserProfile& rhs) { //Output formats, such as: stanl Beginner 12 100 10 10% os << rhs.login() << ' ' << rhs.level() << ' ' << rhs.login_count() << ' ' << rhs.guess_count() << ' ' << rhs.guess_correct() << ' ' << rhs.guess_average() << endl; return os; } istream& operator >>(istream& is, UserProfile& rhs) { //Yes, the following assumptions are valid for all inputs without error checking string login, level; is >> login >> level; int lcount, gcount, gcorrect; is >> lcount >> gcount >> gcorrect; rhs.reset_login(login); rhs.reset_level(level); rhs.reset_login_count(lcount+10); rhs.reset_guess_count(gcount); rhs.reset_guess_correct(gcorrect); return is; } int main() { UserProfile anon; cout << anon; //test output operator UserProfile anon_too; //See if we get a unique identifier cout << anon_too; UserProfile anna("Annal", UserProfile::Guru); cout << anna; anna.bump_guess_count(27); anna.bump_guess_correct(25); anna.bump_login_count(); cout << anna; cin >> anon; //test input operator cout << anon; return 0; }
Exercise 4.5 Implement a 4*4 Martix class that provides at least the following interfaces: matrix addition, matrix multiplication, print(), composite operator +=, and a set of function call operators that support subscripting, as follows:
float& operator()(int row, int cloumn); float operator()(int row, int cloumn) const;
Provide a default constructor that optionally accepts 16 data values.Provide a constructor that accepts an array of 16 elements.You do not need to provide copy constructor, copy assignment operator, destructor for this class.These functions are needed to support matrices of any row or column when the Matrix class is re-implemented in Chapter VI.
end.Matrix.h #include <iostream> using namespace std; typedef float elemType; //Convenient for us to template class Matrix { //friend Declarations are not affected by access rights //I like to put them in class At the beginning friend Matrix operator+(const Matrix&, const Matrix&); friend Matrix operator*(const Matrix&, const Matrix&); public: Matrix(const elemType*); Matrix(const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0.); //Do not need to Matrix provide copy constructor,destructor, //copy assignment operator //Simplify "Convert to Universal Matrix" ( general Matrix)"Process int rows() const { return 4; } int cols() const { return 4; } ostream& print(ostream&) const; void operator+=(const Matrix&); elemType operator()(int row, int column) const { return _Matrix[row][column]; } elemType& operator()(int row, int column) { return _Matrix[row][column]; } private: elemType _Matrix[4][4]; }; inline ostream& operator<<(ostream& os, const Matrix& m) { return m.print(os); } Matrix operator+(const Matrix& m1, const Matrix& m2) { Matrix result(m1); result += m2; return result; } Matrix operator*(const Matrix& m1, const Matrix& m2) { Matrix result; for (int ix = 0;ix < m1.rows();ix++) { for (int jx = 0;jx < m1.cols();jx++) { result(ix, jx) = 0; for (int kx = 0;kx < m1.cols();kx++) { result(ix, jx) += m1(ix, kx) * m2(kx, jx); } } } return result; } void Matrix::operator+=(const Matrix& m) { for (int ix = 0;ix < 4;++ix) { for (int jx = 0;jx < 4;++jx) { _Matrix[ix][jx] += m._Matrix[ix][jx]; } } } ostream& Matrix::print(ostream& os) const { int cnt = 0; for (int ix = 0;ix < 4;++ix) { for (int jx = 0;jx < 4;++jx, ++cnt) { if (cnt && !(cnt % 8)) os << endl; os << _Matrix[ix][jx] << ' '; } } os << endl; return os; } Matrix::Matrix(const elemType* array) { int array_index = 0; for (int ix = 0;ix < 4;++ix) { for (int jx = 0;jx < 4;++jx) _Matrix[ix][jx] = array[array_index++]; } } Matrix::Matrix(elemType a11, elemType a12, elemType a13, elemType a14, elemType a21, elemType a22, elemType a23, elemType a24, elemType a31, elemType a32, elemType a33, elemType a34, elemType a41, elemType a42, elemType a43, elemType a44) { _Matrix[0][0] = a11;_Matrix[0][1] = a12; _Matrix[0][2] = a13;_Matrix[0][3] = a14; _Matrix[1][0] = a21;_Matrix[1][1] = a22; _Matrix[1][2] = a23;_Matrix[1][3] = a24; _Matrix[2][0] = a31;_Matrix[2][1] = a31; _Matrix[2][2] = a33;_Matrix[2][3] = a34; _Matrix[3][0] = a41;_Matrix[3][1] = a42; _Matrix[3][2] = a43;_Matrix[3][3] = a44; } main.cpp #include "Matrix.h" int main() { Matrix m; cout << m << endl; elemType ar[16] = { 1.,0.,0.,0.,0.,1.,0.,0., 0.,0.,1.,0.,0.,0.,0.,1., }; Matrix identity(ar); cout << identity << endl; Matrix m2(identity); m = identity; cout << m2 << endl; cout << m << endl; elemType ar2[16] = { 1.3,0.4,2.6,8.2,6.2,1.7,1.3,8.3, 4.2,7.4,2.7,1.9,6.3,8.1,5.6,6.6 }; Matrix m3(ar2); cout << m3 << endl; Matrix m4 = m3 * identity; cout << m4 << endl; Matrix m5 = m3 + m4; cout << m5 << endl; m3 += m4; cout << m3 << endl; return 0; }
"No winter is insurmountable, and no spring will come."