C + + learning notes - structures and classes

Posted by lailaigogo on Tue, 04 Jan 2022 16:35:08 +0100

Structures and classes

definition

Place multiple objects together as a whole

//definition
struct str
{
    int x;
    double y;
};

//statement
struct str;
//Only the declaration only knows that str is a struct, but it is not known internally. At this time, str is incomplete type
//However, str* mystr can be defined; The pointer size of all structures is the same, and the 64 bit machine has 8 bytes

be careful:

  • A structure is defined at the translation unit level. It can have the same definition in different translation units
  • The definition of structure data members is implicit. They are defined when the object is constructed. They are regarded as declarations in the definition of struct, even if int x = 3 inside the structure; It's also a statement

initialization

  • Aggregate initialization: str m_str{3,4}; The default is 0 (default initialization)
  • C++20 assignment initialization str m_str{.x = 3,.y = 4};

mutable qualifier

const str m_ str; The member of const structure object cannot be modified, but you can open the back door by adding mutable qualifier during definition

struct str
{
    mutable int a = 0;
    int b = 3;
};
int main()
{
    str a;
    a.x = 1; //ok
    a.b = 1; //wrong
}

Static data member

Define the declaration inside the class outside the class. All static members except const static objects must be initialized outside the class

struct str
{
    static int x; //ok
    static int x = 10; //error
    const static int x = 10; //ok
};
int str::x=10; //An out of class definition gives a piece of memory, which is shared by all objects. If it is not defined, there will be a link error, and there will be no error during compilation
//Even const static int x = 10; It also needs to be defined outside the class. At this time, only the value of X is known. However, if you want to obtain the address, you also need to define const int::x = 10;
//The definition outside the class refers to the definition outside the function, and the main function is also a function. If it is defined in the function, it will be defined only when the function is run (run time), so there will be a link error

const data static member:

Const static data members can be initialized in the class, but they need to be defined externally. Otherwise, only values have no address. Constxpr static data members must be initialized in the class, because constxpr implies inline modification. Constxpr static should not be defined outside the class, which is redundant and may report errors

C++17 inline data static members:

struct str
{
    inline static int x = 10;   //Inline static members must be initialized inside the class
};
//You can no longer initialize and define outside the class, otherwise it will be redefined

Or so

struct str
{
	static int x;
};

inline int str::x = 10;

Member function

struct str
{
    int x = 3;
    void fun()
    {
        std::cout<<x;  //Objects inside a structure do not need to display description parameters
    }
};

Declaration and definition of member functions

  • Class definitions are implicitly inlined to prevent multiple definitions
  • The definition outside the class is declared inside the class and will not be implicitly inlined. If inlining is required, inline shall be added
struct str
{
    int x = 3;
    inline void fun();
};

void str::fun()
{
    std::cout<<x;  //Objects inside a structure do not need to display description parameters
}

Double compilation feature

struct str
{
    void fun()
    {
        x++;
    }
    int x;
}

The compiler will not report errors. In order to meet the needs of programmers (important ones are written in front), the compiler adopts two compilations. The first time, it will roughly look at the members of the structure, and the second time, it will look at the function content

this pointer

Type: str* const

A hidden parameter this is entered in all member functions, pointing to the address of the current object & STR, which is used to distinguish internal members and formal parameters of member functions

struct str
{
    int x;
    void(int x)
    {
        x;//Formal parameter x
        this->x;//Member x
    }
};

const member function

struct str
{
    void fun() const
    {
        
    }    //Make this const str* const type
};

Note: constant class objects can only call constant member functions and static member functions, and cannot call ordinary member functions. Therefore, if they are read-only, try to add const modification

Name finding and hiding relationship of member function

int x;
struct str
{
    int x;
    void fun(int x)
    {
        
    }
};

inline void str::fun(int x)
{
    x;  //Formal parameter x
    ::x;  //Global x
    this->x;  //Member x
    str::X;  //Member x
}

Static member function

static void fun()
{
    
}
//There is no hidden parameter inside this. Ordinary members cannot be used directly
//You can use static data members or return static data members
str::fun();
str1.fun();

Using static member functions to construct objects

struct str
{
    static auto& instance()
    {
        static str x;
        return x;
    }
}

Overloading of member functions based on reference qualifiers

struct str
{
    void fun() &; //Lvalue call
    void fun() &&; //Right value call
    void fun() const &;//Constant lvalue call
    void fun() const &&;//Constant R-value call
};
str a;
a.fun(); //Call void fun() &;
str().fun(); //Call void fun() & &;

Access qualifier

struct str
{
private:
    int x;
public:
    int y;
protected:
    int z;
};
  • struct does not write the qualifier. The default is public
  • class does not write the qualifier. The default is private
  • private means that these objects can only be accessed in class friend functions or class member functions. This class can be different objects
struct str
{
public:
    void fun(str& input)
    {
        input.x;  //ok
    }
private:
    int x;
};
  • Access restriction provides encapsulation features (three major features of C + +, encapsulation, polymorphism and inheritance)
  • Encapsulation: encapsulate some things so that others can't see them, and only provide interfaces for external access. For example, in a TV, you can't see the internal parts. You can only control it through the remote control

friend function

Break access restrictions

//Declare that main is a friend function of the class
int main();
class str2;
class str
{
    friend str2;
    friend int main();
    inline static int x;
};

int main()
{
    std::cout<<str::x;
}

class str2
{
    
};

The declaration is troublesome and can be deleted, but it cannot be qualified

struct str
{
    friend str2; //Although I haven't seen it, I think it's a statement here
	friend void fun();
	//Cannot add qualifier friend void:: fun(); Error cannot be considered a declaration at this time
    int x = 10;
};

void fun(str& input)
{
    std::cout<<input.x;
}

class str2
{
    
};

In class definition of friend function

class str
{
    int y;
    friend void fun()
    {               //Although it is defined within a class, it is still a global domain function
        str val;
        std::cout<<val.x;
    }
};
C++Although the hidden friend mechanism is defined, the friend function cannot be found in the conventional search (which can reduce the burden on the compiler), so it cannot be called directly in the global fun(),You must declare it outside the class or put the definition outside the class.

ADL argument dependent lookup: in addition to regular lookup, this function will also be searched in the parameter namespace (only for custom types) during unqualified lookup

class cls
{
    friend void fun(const str val)
    {
        
    }
};
int main()
{
    str val;
    fun(val);  //This function will be found in the namespace of val (the definition of cls)
}

It solves the problem that hidden friends cannot be found. This is the correct way to open hidden friends. The definition of friend function is to access private objects without introducing class object arguments. Why use friend functions.

Constructor

class cls
{
public:
    cls(int z):x(z),y(z)  //Initialization list
    {
        
    }
private:
    int x;
    int y;
};

be careful:

  • The initialization order of elements is related to the declaration and independent of the order of the initialization list
  • The construction and initialization of elements are completed in the initialization list. Even if they are not written, the system will give them out by default for default initialization
  • The default constructor cls(), called with cls a{}, cannot cls a(), which will cause ambiguity (it is mistakenly regarded as a function declaration by the compiler)
  • The existing constructor compiler will no longer provide the default constructor, but can explicitly give the description cls() = default

copy constructor

struct str
{
    str() = default;
    
    str(const str& x)  //You must add a reference, otherwise it will loop indefinitely. Try to add const
       :val(x.val){}
       
    int val;
    
};

str m;  //Default constructor
str m1(m); //copy constructor 
str m1 = m; //copy constructor 
Explicitly declare the default copy constructor: str(const str&) = default;

move constructor

Lvalue right value reference

A reference is an alias of an object, an lvalue reference is a reference to an lvalue, and an rvalue reference is a reference to an rvalue. Through the right value reference, the declaration cycle of the right value is the same as the life cycle of the right value reference type variable. With the help of right value reference, mobile semantics and perfect forwarding are realized. See the template knowledge supplement at the end for perfect forwarding.

int x; 
int& y = x;  //lvalue reference 
int& z = 3;  //Error, reference to R-value and l-value
const int& z = 3;  //Correct const is an exception and can be referenced to an R-value or an l-value
int&& a = 1;  //rvalue reference 
int&& a = x;  //Error, reference to lvalue and lvalue

Mobile semantics:

std::string ori("abc");
std::string newstr = std::move(ori);
//move: steal resources from the input variables. After stealing, newstr is "abc" and ori is empty
//move refers to the movement of class type objects. Non class type objects can display the movement of non type members with y = std::exchange(x,new_value)
//exchange is in #include < utility >

move constructor

struct str
{
    str(str&& x)
       :a(std::move(x.a)){}
       
    std::string a = "abc";
};

be careful:

  • Default move constructor str (STR & &) = Default;
  • If a copy constructor is defined, the compiler will not automatically generate a move constructor
struct str2
{
    str() = default;
    str2(const str2&)
    {
        std::cout<<"copy"<<std::endl;
    }
};

struct str
{
    str() = default;
    str(const str&) = default;
    str(str&&) = default;
    int val = 3;
    std::string a = "abc";
    str2 m_str2;
};

int main()
{
    str m;
    str m2 = std::move(m);
}
  • When performing mobile construction, all members will be mobile constructed. For the class or structure object without mobile construction, its copy constructor will be called. If there is neither, an error will be reported
  • It is recommended to add noexcept when defining the mobile constructor to ensure that no exceptions are thrown to improve efficiency
  • An R-value reference object is an l-value in an expression in the body of a class or function
str(str&& x)
{
    std::string tmp = x.a;
    std::cout<<&x<<std::endl;
}

Copy assignment function and move assignment function

str m;
str m2 = std::move(m);  //Call the move constructor
str m3 = m;  //Call copy constructor
m2 = m;  //Call copy assignment function
m2 = std::move(m) //Call the move assignment function

str& operator = (const str& x)
{
    val = x.val;
    return *this;  //To realize a=b=c connection, etc
}

str& operator = (str&& x)
{
    val = std::move(x.val);
    return *this;
}

Destructor destructor

~str()
{
    
}  //No parameter return type, invisible parameter. Called when the object is released

After the destructor is executed, the memory is recycled, and the explicit memory must be explicitly released

str* m = new str();
delete m;

The destructor should use the noexcept modifier as much as possible

Using new to open up space is prone to problems

class str
{
public:
    str():ptr(new int()){}
    ~str(){delete ptr;}
    int& data()
    {
        return *ptr;
    }
private:
    int* ptr;
};

int main()
{
    str a;
    a.data() = 3;
    str b(a);
    //Error, no copy constructor, automatically generated by the system, including a.ptr = b.ptr;
    //Make two pointers point to the same memory. This memory is released twice during destruct, and the system crashes.
}

//correction
//Add copy constructor and copy assignment function
str (const str& val)
    :ptr(new int())
{
    *ptr = *(val.ptr);
}

str& operator = (const str& val)
{
    *ptr = *(val.ptr);
    return *this;
}

delete keyword

After addition, the function cannot be called (not undeclared)

C + + compiler optimization

//According to common sense
str a1(20); //Direct construction
str a1(a2); //Direct construction
str a1 = str(2); //copy construction 
str a1 = a2; //copy construction 
str a1 = std::move(a2) //Mobile structure

//But in fact, due to the optimization behavior of the compiler
str a1(a2); //a2 already exists, so the copy constructor will be used
str a1 = str(2);
str a1{str(2)}; //Use direct construction to construct objects directly, instead of moving them first
//But you can't add delete to the mobile structure, otherwise you will find the secret of this optimization
//After C++17, it must be optimized, and no compiler parameters can be added

Operator overloading

  • You cannot invent new operators, change the priority and associativity of operators, and generally do not change the original meaning
  • Operators other than () cannot be defaulted
auto operator + (str x, int y = 2){}  //error

class str
{
    auto operator () (int y = 3)
    {
        return val+y;
    }
    int val = 5;
};

int main()
{
    str x;
    x();
    x(3);
}
  • In class operator overloading, the default first parameter is * this
  • Operator that can be overloaded and must be implemented as a member function = [] () - > transformation operator
  • Operators that can be implemented as non member functions + - * /%, etc
  • Overloaded operators & & |, are not recommended,
  • Non overloadable operator?: (ternary operator), etc
  • Symmetric operators should not be defined as member functions
class str
{
public:
    str(int x):val(x){}
    
    auto operator + (str a)
    {
        return str(val + x.val);
    }
private:
    int val;
};

int main()
{
    str x = 3;
    str y = x + 4;  //x + 4 is OK, but 4 + x is not
}

Some members may be inaccessible due to the private attribute, so they are declared as friend functions. When using the + operator, they can be used because of ADL

struct str
{
    str(int x):val(x){}
    friend auto operator + (str in1, str in2)
    {
        return str(in1.val + in2.val);
    }
private:
    int val;
};
  • The shift operator must be defined as a non member function to ensure that the first parameter is a stream
friend auto& operator << (std::ostream& str, str input)
{
    ostr<<input.val;
    return ostr;  //Used to implement something similar to cout < < a < < B;
}
  • operator [] implements reading and writing
int& operator [] (input id)
{
    return val;   //The first parameter is * this
}
//To ensure that the input object can still be used when const is used, overload is introduced
int operator [] (int id ) const
{
    return val;
}
  • ++– overloading of operators
str& operator ++ ()   //Prefix self increment
{
    ++val;
    return *this;
}

str operator ++ (int x)  //Suffix self increment
{
    str tmp(*this);   //Performance is low and temporary objects are created
    ++val;
    return tmp;
}
  • ->* overloading of operators
class str
{
public:
    str(int* p):ptr(p){}
    
    str* operator -> ()
    {
        return this;
    }

    int& operator * ()
    {
        return *ptr;
    }
private:
    int* ptr;
    int val = 100;
};
int main()
{
    int x = 10;
    str ptr = &x;
    *ptr; //10
    *ptr = 11;
    ptr -> val;  //Automatically translated by the compiler into PTR operator->()->val;
}
  • Type conversion operator overload
class str
{
public:
    str(int x):val(x){}
    operator int () const  //There is no need to explicitly give the return type
    {
        return val;
    }
private:
    int val;
}
str obj(100);
int v = obj;

The above code may introduce ambiguity obj + 3; It can be (int)obj + 3 or obj + (str)3. To avoid ambiguity, you need to add the explicit modifier to the constructor or explicit to the overloaded function. When using, try to use explicit type conversion

class str
{
public:
    explicit str(int x):val(x){}
    operator int () const  //There is no need to explicitly give the return type
    {
        return val;
    }
private:
    int val;
}
str obj(100);
int v = obj;
  • Overloading of = = and < = > in C++20
friend bool operator == (str obj1, str obj2)
{
    return obj1.val == obj1.val;
}

friend bool operator == (str obj1, int x)
{
    return obj1.val == x;
}
obj == 100;
100 == obj;
//Overload = = in C++20 will be introduced automatically= But it cannot be overloaded= Introduce==
#include <compare>
auto operator <=> (int x)
{
    return val<=>x;
}
//Return type strong_ordering,weak_ordering,partial_ordering
//It can be used for arbitrary comparison without overloading others

Class inheritance

graph LR
triangle-->shape
right_triangle-->triangle
rectangle-->shape
square-->rectangle

Knowledge points

  • A right triangle is a triangle and a triangle is a shape
    *Usually use public inheritance (public inheritance)
  • struct default public inheritance
  • Private is private inheritance by default
  • Public inheritance: the access rights of public members and protected members inherited from the base class remain unchanged
  • Private inheritance: public and protected members inherited from the base class become private
  • protected qualifier: open a back door for derived classes so that they can be inherited but still inaccessible from the outside
header 1public inheritanceprotected inheritanceprivate inheritance
public int xpublic int xprotected int xprivate int x
protected int yprotected int yprotected int yprivate int y
private int zCannot inheritCannot inheritCannot inherit
  • Inheritance is not a declaration of a class and cannot be class STR: public base;
  • You can point to a derived class object with a base class pointer or reference, but you can only call or access the inherited object in the base class
class Base
{
public:
    int x = 10;
    void fun()
    {
        std::cout<<"asd";
    }
};

class Derive : public Base
{
public:
    void fun2()
    {
        std::cout<<"asdasd";
        fun();
    }
};
int main()
{
    Derive a;
    Base& ref = a;
    ref.fun2(); //error
    ref.fun(); //Correct is equivalent to that ref only refers to the Base part of Derive
}
  • Derive is a Base, so you can access those inherited members
  • Class will form a derived domain. The domain of the derived class is located inside the base class. The name definition of the derived class will override the base class. You can use the domain operator to explicitly access the base class
class Derive : public Base
{
    int x;
    x;
    Base::x;
};
  • Call the base class constructor in the derived class.
struct Base
{
    Base(int){}
};

class Derive : public Base
{
    Derive(int a)
        :Base(a)   //If not, the system implicitly calls the default constructor
    {
            
    }
};

virtual function

Non static function, non constructor

definition

class Base
{
    public:
    virtual void f(){cout<<"Base::f"<<endl;}
    virtual void g(){cout<<"Base::g"<<endl;}
    virtual void h(){cout<<"Base::h"<<endl;}
};

class Derived:public Base
{
	public:
    virtual void f(){cout<<"Derived::f"<<endl;}
    virtual void g1(){cout<<"Derived::g1"<<endl;}
    virtual void h1(){cout<<"Derived::h1"<<endl;}
};

Virtual function table VTable (virtual table) structure

Base class object storage structure
vtable
Other members
Other members

The base class vtable stores information about virtual functions defined by the base class itself

Base::f()Base::g()Base::h()
Derived class object storage structure
vtable
vtable of other base classes
Other members

The derived class vtable stores the information of the virtual function in the base class (if the virtual function is overridden, the base class will be overwritten) and the information of the virtual function defined by itself

Derive::f()Base::g()Base::h()Derive::g()Derive::h()
class Base
{
public:
	virtual void func()
	{
		cout << "Base!" << endl;
	}
};
class Derived :public Base
{
public:
	virtual void func()
	{
		cout << "Derived!" << endl;
	}
};

void show(Base& b)
{
	b.func();
}
Base base;
Derived derived;

int main()
{
	show(base); //Base!
	show(derived);  //Derived!
	base.func();  //Base!
	derived.func();  //Derived!
	return 0;
}
Polymorphisms introduced by dynamic types also call a function,
Different types of inputs lead to different behaviors of functions.
  • Virtual functions are defined in the base class, and default logic is introduced or simply declared as pure virtual functions. virtual void fun() = 0; Declare the base class as an abstract base class and override the virtual function in the derived class (the derived class of the abstract base class must override the virtual function).
class Base
{
public:
    virtual void fun() = 0;    
};
class Derive : public Base
{
public:
    void fun(){}
};
class Derive2 : Derive
{
    //Derive2 base class is defined in derive2, so derive2 is not an abstract class
};
  • If declared as a pure virtual function, the function must be overridden in the derived class, otherwise it is regarded as an abstract class and cannot construct an object
  • An abstract class cannot construct an object, but its pointer or reference type can be defined and bound to a derived class
  • Virtual function override requires that the function range type and parameter list are exactly the same, and the override is not an overload
  • The rewritten virtual function is still a virtual function, and its properties remain unchanged
  • You can explicitly declare the override and add the override modifier at the end of the function

Points for attention of virtual functions:

  • Dynamic binding introduced by virtual functions is a runtime behavior
  • The default argument only considers static types
  • The calling cost of virtual function is higher than that of non virtual function
  • You need to introduce dynamic binding with pointers (or references)
  • When calling a virtual function in the constructor, the virtual function of the derived class may be called because the virtual function of the derived class is not well constructed, but the base class has been well constructed
class Base
{
public:
    Base()
    {
        fun();
    }
    virtual void fun(){}
};

class Derive : public Base
{
    Derive()
        :Base()
    {
        fun();  //The fun of Base will be called first, and the fun of Derive will be called later
    }
    virtual void fun(){}
};
  • final keyword, which tells the compiler that all virtual functions of its derived classes will not be rewritten to improve performance
void fun() override final
{
    ...
    //Indicates that the function will no longer be overridden
}

class Derived final: public Base
{
    ...
    //Indicates that the class will no longer be inherited
};
  • If the destructor is declared as a virtual function, the destructor of all Derived classes is also a virtual function. Purpose: to delete the Derived class object using the Base class pointer, which will cause uncertainty in the compiler behavior. However, if the destructor is declared as a virtual function, virtual ~Base() = default; When deleting an object, Derived will be destructed first, and then Base will be destructed
Derived* d = new Derived();
Base* b = dl
delete b;
//Note that deleting a derived class object with a base class pointer will cause uncertainty. If a derived class object is deleted with a derived class pointer, the derived class will be destructed first and then the base class, which will not cause ambiguity. (constructed before destroyed)
  • The default function defined in the derived class will implicitly call the corresponding constructor of the base class, and other constructor functions of the derived class (self-defined constructors) will implicitly call the default constructor (if not explicitly described)
class Base
{
public:
    Base(int x)
        :a(x){}
        
    Base(const Base& str1)
        :a(str1.a){}
        
    Base(Base&& str1)
        :a(std::move(str1.a)){}
        
    Base& operator = (const Base& val)
    {
        a = val.a;
        return *this;
    }
    
    int a;
};

class Derived : public Base
{
public:
    Derived(int x)
        :Base(x){}
        
    Derived(const Derived& str1)
        :Base(str1){}
    
    Derived& operator = (const Derived& val)
    {
        Base::operator = (val);  //a = val.a
        return *this;
    }
    
};

Changing member modifiers with using

header 1public inheritanceprotected inheritanceprivate inheritance
public int xpublic int xprotected int xprivate int x
protected int yprotected int yprotected int yprivate int y
private int zCannot inheritCannot inheritCannot inherit
struct Base
{
public:
    int x;
private:
    int y;
protected:
    int z;
    void fun(){}
};

struct Derive : public Base
{
public:
    using Base::z;  //protected z -->public
    using Base::fun;  //Fun -- > public is for all overloaded versions of fun
private:
    using Base::x  //public x -->private
};

using inherits the base class constructor logic, which is applicable to derived classes without introducing new data members. At this time, there is no need to override the constructor of derived classes.

struct Base
{
public:
    Base(int x)
        :val(x){}
    int val;
};

struct Derive : public Base
{
public:
    using Base::Base;
};
Derive A(100);

Friend functions cannot access base class members

struct Base
{
    int x = 10;
};

struct Derive : public Base
{
    friend void fun(const Derive& val);
};

void fun(const Derive& val)
{
    val.x;  //error: a friend cannot access a base class member
}
//And the friend relationship cannot be inherited

Using the base class pointer to save different types of objects in the container

struct Base
{
    virtual double getvalue() = 0;
    virtual ~Base() = default;
};

struct Derive : public Base
{
    Derive(int x)
        :val(x){}
    double getvalue() override
    {
        return val;
    }
    int val;
};

struct Derive2 : public Base
{
    Derive2(double x)
        :val(x){}
    double getvalue() override
    {
        return val;
    }
    double val;
};

int main()
{
    std::vector<std::shared_ptr<Base>> vec;
    vec.emplace_back(new Derive{1});
    vec.emplace_back(new Derive2{13.14});
    std::cout<<vec[0]->getvalue()<<vec[1]->getvalue()<<std::endl;
}
//Set the destructor as a virtual function to prevent errors in delete

Possible problems of multiple inheritance

struct Base
{
    
};
struct Base1 : public Base
{
    
};
struct Base2 : public Base
{
    
};
struct Derive : public Base1, public Base2
{
    
};
graph LR
Base1-->Base
Base2-->Base
Derive-->Base1
Derive-->Base2

There are both Base1 and Base2 objects in the Base, so Derive doesn't know which to use

Introducing virtual inheritance

struct Base1 : virtual Base
struct Base2 : virtual Base
struct Derive : public Base1, public Base2
 At this point, the compiler introduces only one common object

Topics: C++ Class OOP inheritance