C + + object oriented learning notes

Object Based vs. Object Oriented

Object Based: it is designed for a single class

Object Oriented: facing the design of multiple classes * *, the relationship between classes and classes.

Two classic classifications of classes:

  • Classes without pointer members
  • Classes with pointer members

1, Classes without pointer members

1. Defensive declaration of header file

#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif

Function: prevent duplicate definitions caused by the same header file being included multiple times.

Suggestion: for unnecessary trouble, it's best to add such a defensive statement to each header file. Even if only one cpp uses the header file now, it will cause unnecessary trouble if another cpp uses the header file later.
Note: defensive declarations are generally written as follows: (2 underscores, not 1) + uppercase of header file name (excluding. h) + (2 underscores, not 1), for example, header file is head h, use__ HEAD__, This is to prevent duplication.

2. inline function

Inline functions are faster and better, but whether they eventually become inline is up to the compiler

If the function is defined in the class, it will automatically become an inline candidate function

3. access level

data is generally private, and most functions need to be public to the outside world

4. Constructor:

The following is the unique writing method of constructor, (initialization list) in the form of initialization list, which is better and more efficient

  • Variable generation has two stages
    • 1. Initialization, after colon
    • 2. Assignment: in braces

Note: most classes without pointers do not need to write destructors

complex (double r = 0, double i = 0)
  : re (r), im (i) { }

It is different from the following form (this is assignment operation assignments):

complex (double r = 0, double i = 0)
{ re = r; im = i; }

This writing method is not allowed. The above constructor already has a default value, which is in conflict

complex () : re(0), im(0) { }

5. Design mode Singleton singleton mode. The constructor is placed in private and cannot be instantiated by the outside world. The outside world is only allowed to use one copy and use the static keyword

class A {
public:
  static A& getInstance();
  setup() { ... }
private:
  A();
  A(const A& rhs);
  ...
};

A& A::getInstance()
{
  static A a; // I created a copy of myself, and there is only one copy
  return a;
}

A::getInstance().setup(); // When calling from outside, you need getInstance() to get a unique instance

6. const member function (constant member function)

  • Good habit: const must be added to member functions that do not change member variables, otherwise an error may be reported when using.

  • Const modifier function is placed after () {} and const is added before the object to modify variables. Modification is not allowed

    double real () const { return re; }
    double imag () const { return im; }
    
const complex c1(1, 2);
cout << c1.real();  // If complex::real() does not add const, the const object will call a non const function and an error will be reported
cout << c1.imag();

7. Parameter transfer

Try not to use value passing. The whole parameter is passed in. The slow reference passing is equivalent to the fast pointer passing. Try to pass all parameters in the form of reference passing const + reference, indicating that the passed parameters cannot be modified inside the function and do not want to be modified

8. Return value transfer

Reference shall be returned as far as possible when passing the return value & reference shall be used as far as possible when possible (reference shall not be returned when returning local variables, and reference shall be returned as far as possible in other cases.)

If a temporary variable is created inside the function, the reference cannot be returned when the temporary variable is returned, because once the function is left, the temporary variable disappears

9. Friends

  • The friend function can freely obtain the private members of the class. The friend can directly take the data. Otherwise, the function is used to take it, but the encapsulation is opened
  • objects of the same class are friends with each other
class complex
{
public:
	complex (double r = 0, double i = 0)
	: re (r), im (i) 
	{ }
	int func(const complex& param)
	{ return param.re + param.im; }
private:
	double re, im;
};

{
complex c1(2,1);
complex c2;
c2.func(c1);
}

10. Operator overloading

  • The member function consists of the this pointer

An operator with two left and right operands must act on the left operand:

c2 += c1;// Here this==c2

All member functions must take a hidden parameter this, which represents that the caller (for example, c2 above) adds the right to the left, and this is a pointer

inline complex&
__doapl(complex* ths, const complex& r)
{
	...
	return *ths;//The return form here does not care whether the receiver accepts it in the form of reference or value. It will be faster to use reference.
}
//The concatenation operation c2 makes both left and right values. The return value cannot be void, so pay special attention to the design of the return value of the function
c3 += c2 += c1;
inline complex&
complex::operator += (const complex& r)
{
return __doapl(this,r);
}
  • Non member function (global function) has no this pointer
inline complex operator + (const complex& x, const complex& y) {
  return complex (real(x) + real(y), imag(x) + imag(y));  // Return local object, not return by reference
//Here you can return a reference    
inline complex
operator + (const complex& x)
{
	return x;
//This function must never return by reference. It must return a local object.  
inline complex
operator - (const complex& x)
{
	return complex (-real (x), -imag (x));
}

The form of temporary object: typename() has no name, just like int i; equally

//This is also a temporary object. There is no name. When it goes to the next line, its life disappears
complex();
#include <iostream.h>
ostream&
   //   os is changing all the time. const cannot be added here
operator << (ostream& os, const complex& x)
{
	return os << '(' << real (x) << ',' 
		<< imag (x) << ')';
}

{
complex c1(2,1);
    //All symbols in C + + act on the left parameter. For special operators < < can only be written as non member function global function
cout << conj(c1);
    //Continuous output cannot return void. It can return the overloaded function of the operator above the reference os is not a temporary object
cout << c1 << conj(c1);
}

be careful:

// The reference and value passed here have the same effect of double 4 bytes
complex (double r = 0, double i = 0)
: re (r), im (i) 
{ }

Summary:

  • The constructor will use initialization list
  • The member function with const should be added with const
  • pass by reference
  • Return value by reference
  • The data is placed in the private area
  • Both member functions and global functions can be declared as inline. In fact, it depends on whether the compiler can really become inline functions
  • Temporary object form, cannot return reference
  • Friend functions directly access member variables

2, Classes with pointer members

1. Classes with pointers must write their own copy functions

class String
{
public:
  String(const char* cstr = 0);
  String(const String& str);
  String& operator=(const String& str);
  ~String();
  inline char* get_c_str() const { return m_data; }
private
  char* m_data; // Dynamically allocate memory

Big Three, three special functions

//The copy construct accepts the type of the class itself
String(const String& str);
//The operator overload copy assignment also passes in its own class. As long as there are pointer copy constructs and copy assignment destructors in the class, they must be written
String& operator=(const String& str);
// Add const
char* get_c_str() const { return m_data; }
//Pointer dynamically allocates memory
char* m_data;

Constructor:

inline String::String(const char* cstr = 0)
{
  if (cstr) {
    m_data = new char[strlen(cstr) + 1];
    strcpy(m_data, cstr);
  } else {
      //Allocate memory to place empty strings
    m_data = new char[1];
    *m_data = '\0';
}

Destructor:

inline ~String()
{
  delete[] m_data;  // Free dynamically allocated memory, otherwise memory leaks
}

{
	String s1(), 
	String s2("hello");
	String* p = new String("hello");
    //Three strings need to call the destructors s1 and s2 three times. When leaving the scope, the destructor is automatically called to release p
	delete p;
}

2. Note: classes with pointers must have copy constructs and = operator overloaded functions

Shallow copy is just the same memory pointed by the pointer, which will cause memory leakage

  • Copy constructor:
inline String::String(const String& str)
{
   	//Directly get the private data of another object (they are friends with each other) + 1 is the ending symbol
  m_data = new char[strlen(str.m_data) + 1];
  strcpy(m_data, str.m_data);
}
  • Copy assignment function

    Difference from copy constructor: when copying assignment, this object already exists, so it needs to be emptied before copying

inline String::String operator= (const String& str)
{
    //Be sure to write detection and self assignment. If the pointer passed in and the pointer to the left of the operator (the original pointer) point to the same memory space, it will be returned directly, which is efficient. Otherwise, if the self assignment is not detected, uncertain behavior will occur when the self assignment is true, resulting in errors.
  if (this == &str) // Detect self assignment, otherwise self replication will fail
    return *this; 
    //There are three steps. First, delete s2 creates the same dynamic space as s1 and copies it
  delete[] m_data;
  m_data = new char[ strlen(str.m_data) + 1];
  strcpy(m_data, str.m_data);
  return *this;

3. Stack and heap

**Stack is a memory space that exists in a scope** For example, when you call a function, the function itself will form a stack to place the parameters it receives and the return address. Any variable declared inside the function and the memory block used in it are from stack.

**Heap, or system heap, refers to a global memory space provided by the operating system, * * the program can dynamically allocate to obtain several memory blocks.

class Complex { ... }; 
... 
{
    //When leaving the scope, c1 will disappear naturally. The occupied space of c1 comes from the stack stack. p must manually delete the dynamic space of the heap
	Complex c1(1,2);
	Complex* p = new Complex(3);
}

Summary:

  • Lifetime of variable

    • stack object (also known as local object, auto object): when leaving the scope, it will be automatically cleaned up

      class Complex { ... }; 
      ... 
      {
      Complex c1(1,2);
      }
      
    • static object: it still exists after the scope ends, and ends only when the program ends

      class Complex { ... }; 
      ... 
      {
      static Complex c2(1,2);
      }
      
    • global object: it ends at the end of the program. It can also be regarded as a static object

      class Complex { ... }; 
      ... 
      Complex c3(1,2);
      int main()
      {
      ...
      }
      
    • heap object: it will not end until it is deleted, otherwise there will be a memory leak if it is not deleted (the pointer will die when it leaves the scope and loses control of the dynamic space, but the space is still there and not returned to the system)

      lass Complex { ... }; 
      ... 
      {
      Complex* p = new Complex;
      ... 
      delete p;
      }
      class Complex { ... }; 
      ... 
      {
      Complex* p = new Complex;
      ... 
      delete p;
      }
      
      
  • new allocates memory first and then calls the constructor (3 actions)

    • 1. Allocate memory: call malloc(n) void* mem = operator new(sizeof(Complex));

    • 2. PC = static_ cast<Complex*>(mem);

    • 3. Constructor PC - > complex:: complex (1,2);

      Complex *pc;
      void* mem = operator new( sizeof(Complex) ); //Allocate memory
      pc = static_cast<Complex*>(mem); //transformation
      pc->Complex::Complex(1,2); //Constructor
      
  • delete first calls the destructor and then frees up memory

    • 1. Destructor String::~String(ps)

    • 2. Free memory: call free(ps) operator delete(ps) internally

      Complex::~Complex(pc); // Destructor
      operator delete(pc);  //Free memory
      
  • array new must be combined with array delete, otherwise it will cause memory leakage, the memory of the allocated array and the call of the destructor

String* p = new String[3];
...
delete[] p;

be careful:

  • Continuous assignment is not applicable if the reference void must be returned during continuous assignment

  • &After typename is a reference, & before object is a pointer to get the address

  • If something is sent out, (return) doesn't care whether the receiver is a reference or a value

3, Class template function template static

1,static

**When static is added to the data member, the data does not belong to an object, * * there is only one copy in a certain area of memory, and all objects need the same data. At this time, the data can be set to static, such as bank interest rate.

Summary:

  • static
    • static member variable: it is separated from the object and has only one in memory
      • Usage scenario: general data irrelevant to objects
      • **Static data must be defined in a line outside the class, whether or not the initial value is given** To assign a value outside the class: double Account::m_rate = 8.0;
    • non static member function: there is only one copy in memory. It depends on this pointer to distinguish different objects to be processed. Double real() const {return this - > re;}
    • Static member function: without this pointer, it can only process static member variables and cannot access or access the data in the object. Static functions can only access static data.
      • Two ways to call static function
        • 1. Call a.set through object_ rate(7,0);
        • 2. Call account:: set through class name_ rate(5.0);

2. Class template

template<typename T>
class complex
{
public:
	complex (T r = 0, T i = 0)
	: re (r), im (i) 
	{ }
	complex& operator += (const complex&);
	T real () const { return re; }
	T imag () const { return im; }
private:
	T re, im;
	friend complex& __doapl (complex*, const complex&); 
};

{
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
...
}

3. Function template

template <class T> // class and typename are the same
inline
const T& min(const T& a, const T& b) 
{
	return b < a ? b : a;
}

Unlike class templates, function templates automatically derive arguments.

4, Combination and inheritance

  • Composition

    • Represents has-a. there is a deque < T > C in this queue class

    • Design pattern Adapter

      • There is already a powerful class deque. Now open some of its interfaces and refit it into class queue
    • Construction and deconstruction (Container owns Component)

      • Structure from inside to outside

        //    2                                                1
        Container::Container Container::Container((......): ): Component() Component() { { ...... };
        
      • Deconstruction from outside to inside

        // 1                                 2
        Container::~Container((......){ ){ ...... ~Component() ~Component() };}
        
  • Delegation delegation

    • In the class (handle), there is a pointer StringRep* rep pointing to another class (body). Although the pointer points to another class, it also becomes different from life by reference academically
    • Reference count shared property
  • Inheritance inheritance

  • Three ways of inheritance
    The most important is- a relation of public
    The data of the parent class is inherited
    Inheritance and virtual function collocation are the most valuable

    • Indicates is-a class List_node : public List_node_base

    • Structure and Deconstruction

      • Construct from inside to outside (parent class before child class)

        //    2                                        1
        Derived::Derived Derived::Derived((......): ): Base() Base() { { ...... };};
        
      • Destruct from outside to inside (subclass before parent)

        //   1                                          2
        Derived::~Derived Derived::~Derived((......){ ){ ...... ~Base() ~Base() };};
        
        • The destructor of the parent class must be virtual, otherwise undefined behavior will appear
    • virtual function

  • Non virtual function. You don't want to override it.

  • virtual function: you want to allow derived classes or subclasses to redefine override

  • Pure virtual subclasses of pure virtual functions must be defined. Note: unlike empty functions, abstract classes

  • Design pattern observer

    Delegate + inherit

class Subject
{
private:
  int m_value;
  vector<Observer*> m_views;
public:
  void attch(Observer* obs)
  {
    m_miews.push_back(obs);
  }
  void set_val(int value)
  {
    m_value = value;
    notify();
  }
  void notify()
  {
    for (int i = 0; i < m_views.size(); i++)
      m_views[i]->update(this, m_value);
  }
};
class Observer
{ 
public:
  virtual void update(Subject* sub, int value) = 0;
};

Keywords: C++ Back-end

Added by mrblom on Thu, 27 Jan 2022 07:57:26 +0200