[C + + Object Oriented Advanced Programming] knowledge summary

Some small knowledge points

About C and C++

In C language, data and method (function) are separated, and data is used to create variables.

In C + + classes (struct, class), data and functions can be put together (member data, member function), which together produce objects.

Classes can also be regarded as two categories: those with pointers and those without pointers.

There are some differences between these two classes in implementation, because those without pointers can be included by class.

With pointers (such as strings), space will be called elsewhere in memory, and there are only pointers to the space in the class.

About header files

#ifndef ...
#define ...
...
#endif

Such a declaration is a defensive declaration, which can prevent repeated inclusion in multiple files.

About inline functions

Inline functions can solve the problem that some frequently called functions consume a lot of stack space (stack memory).

The concept of an inline candidate is mentioned here, that is, whether the real inline needs to be determined by the compiler. The simpler the function, the more likely it is.

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

If it is outside the class, you can add an inline declaration in front of it, but whether it can be selected depends on the compiler.

inline double imag(const complex& x){
	return x.imag();
}

About initialization list

Sometimes constructors appear as initialization lists.

class complex{
public:
	complex (double r=0,double i=0)
	:re(r),im(i){}
private:
    double re,im;
}

You can also use the method of assignment, such as re=r, but there are differences in efficiency, which is also the reason for using the initialization list.

The example here uses the default argument. It should be noted that the default argument may conflict with no parameter when overloaded, and the compiler cannot judge which class it belongs to.

Constructors can also be placed in the private area, that is, external construction is not allowed. (is a design pattern Singleton)

About const

Const will be discussed in depth later. Let's introduce it here first. We can understand const from another angle. Add const if you don't want to change the value, either after the function or before the variable.

 class complex{
 public:    
 	complex (double r=0,double i=0)    :re(r),im(i){}
 	double real() const {return re;}
 	double imag() const {return im;}
 private:
 	double re,im;
 	}

However, if it is not added, an error will be reported in the following cases

const complex c1(2,1);
cout<<c1,real();
cout<<c1.imag();

The compiler will think that this conflicts with the construction of the class.

Important knowledge points

About parameter passing

Parameter passing includes value passing, pointer passing and reference passing.

When we first encounter value passing, pointer passing and reference passing, we should encounter swap.

void swap(int a,int b)
{
    int temp = a;
    a = b;
    b = temp;
}
int main(void)
{
    int a = 10;
    int b = 20;
    printf("before swap:a = %d,b = %d\n",a,b);
    swap(a,b);
    printf("after  swap:a = %d,b = %d\n",a,b);
    return 0;

At this time, a and b are not exchanged because the swap function passes in a value.

Transferring values is equivalent to creating a copy of the original data. How the replica operates has nothing to do with the original data itself.

The address and pointer will change the data itself, so as to realize the real exchange.

(the relationship between reference and pointer will be described in detail later, and the reference can be regarded as a wrapped pointer)

If you can choose to pass a reference, you don't need to pass a value. The reference speed is faster (equivalent to passing a pointer). You can add const to make the function immutable.

ostream&
operator<<(ostream& os,const complex& x){
return os<<'('<<real(x)<<','<<imag(x)<<')';
}

About this

This can be regarded as the address of the object in this class.

this is implied in the member function.

inline complex& complex::operator+=(const complex& r){
	return _doapl(this,r);
}

inline complex& _doapl(complex* ths,const complex& r){
   	this->re+=r.re;
    this->im+=r.im;
    return *ths;
}

At this time, if c2+=c1 is executed; c2 is equivalent to this, and c1 is r. (note that operator overloading works on the left)

inline complex& complex::operator+=(this,const complex& r){
	return _doapl(this,r);
}

Non member functions have no this, for example

inline complex operator+(const complex& x,const complex& y){
	return ...
}

About return value passing

Return value transfer is also divided into value transfer and parameter transfer.

The difference is:

  • If the returned value needs a new space to store, you need to pass the value, because it will disappear if you don't save it at the end of the function.
  • If it is an operation such as superimposing on an existing value and does not need a new space, you can return a reference.

Use the above example to illustrate the two cases separately.

The first is the member function

inline complex& complex::operator+=(const complex& r){
	return _doapl(this,r);
}

inline complex& _doapl(complex* ths,const complex& r){
   	this->re+=r.re;
    this->im+=r.im;
    return *ths;
}

You can see that the operation here is performed on the existing this, so you can return a reference.

The sender does not need to know the receiving form of the receiver, which is also the benefit of reference

For example_ The doapl function returns * ths, that is, the value itself, but the receiver receives it by reference. Imagine that if this is pointer reception, you need to return the pointer, but the reference does not.

And the return reference can realize continuous operation. For example, c1+=c2+=c3; If the value is transferred, the above situation will not change.

For the example of non member functions

inline complex operator+(const complex& x,const complex& y){
	return complex(real(x)+real(y),imag(x)+imag(y));
}

Note that their return values are built by temporary objects, that is, they need new space to store, otherwise they will die with the function.

Therefore, the return value needs to be passed.

On three functions

There are three special functions in the class: copy construction, copy assignment and destruct

Next, take the string class as an example.

class String
{
public:
	String(const char* cstr=0);//Constructor
	String(const String& str);//copy construction 
	String& operator=(const String& str);//copy assignment 
	~String();//Deconstruction
	char* get_c_str() const {return m_data;}
private:
	char* m_data;
}

The characteristic of string class is that it is composed of pointers. Only the space pointed to by the pointer can really store the string.

For c + +, the end of this string is' \ 0 ', occupying one bit.

Constructor

The constructors and destructors of the String class are as follows, which are well written.

inline String::String(const char* cstr=0)
{
	if(cstr){
		m_data=new char[strlen(cstr)+1];
		strcpy(m_data,cstr);
	}
	else{
		m_data=new char[1];
		*m_data='\0';
	}
}

inline String::~String()
{
	delete[]m_data;
}

Let's see from the following calls

String s1();
String s2("hello");

The default parameter of the constructor of String is 0. If it is not zero, you need to allocate a new space with the size of the String + 1 (the ending '\ 0' is required), and then copy the String.

If it is 0, that is, no initial value is specified, then a new space of size 1 is required to store '\ 0'.

copy construction

For copy construction, you need to open up a new space for the new string, and then copy the original string

inline String::String(const String& str)
{
	m_data=new char[strlen(str.m_data)+1];
	strcpy(m_data,str.m_data);
}
String s1("hello");
String s2(s1);

copy assignment

Copy assignment is a common "=" form. Although it is overloaded "=", there are some small details in the actual implementation

inline String& String::operator=(const String& str)
{
	if(this==&str)
		return *this;
    
	delete[] m_data;
	m_data=new char[strlen(str.m_data)+1];
	strcpy(m_data,str.m_data);
	return *this;
}

For example, when executing the following code:

String s2=s1;

First, delete the original value of s2, then open up a new space, and then copy the value of s1 into it.

The first step here is to detect whether the value to be assigned is itself.

This is not simply to improve efficiency. If we notice the subsequent delete, we will find that when the equals sign is around us, we will destroy ourselves, so we can't complete the assignment.

About shallow copy and deep copy

When we understand how to write the above functions, the problems of shallow copy and deep copy will be solved.

If we don't write the special constructor of the String class, let the compiler handle it.

Then, when copying construction and copy assignment, there will be direct copying without new and delete processes.

Because String is a pointer class, this will cause two pointers to point to the same space, resulting in memory leakage. This is the shallow copy.

Deep copy is our correct way of writing. Each pointer corresponds to a piece of memory space.

Therefore, the conclusion is that classes with pointers must have copy construction and copy assignment, otherwise shallow copy will occur

Keywords: C++ OOP

Added by phui_99 on Sun, 20 Feb 2022 12:51:02 +0200