Under C++ classes and objects (initialization list, static members, explicit keywords, friends)

1. Initialization list

1.Why is there an initialization list?

Insert image description here
We added two kinds of member variables to Date:
reference type and const type. Why
does the compiler report an error?
Is it because the constructor generated by the compiler by default does not work?
How about we implement it ourselves?
Insert image description here
Still not working: It says that references and objects of const type must be initialized when they are defined.
Right, because references cannot change their pointers, the object must be specified when initializing the reference
. The value of a variable of const type cannot be modified, so it must also be set during initialization. good value

So what should we do?

To solve this problem, the founder of C++ stipulated the syntax of initialization list:

In order to solve the problem, some member variables must be initialized when they are defined!!!

2. Grammatical form of initialization list

Insert image description here

Date(int year = 1, int month = 2, int day = 3)
	:_year(year)
	, _month(month)
	, _day(day)
	, _ref(_year)
	, _cint(_year)
	{
    
    
		//函数体内可以继续进行代码的书写
	};
第一个是冒号
后面是逗号

Insert image description here
In this way, our program can pass.
In fact, the initialization list also solves the following problem:

3. Custom member variables without default constructor

Insert image description here
Detailed explanation of C++ classes and objects (constructors, destructors, copy constructors)
Insert image description here
Then the question arises:
What if this Stack class does not have a default constructor?
A compilation error will occur
Insert image description here
. So what should I do?

In fact, let's think about it carefully:
The Stack class in this MyQueue class does not have a default constructor. Doesn't it mean that this Stack class must be initialized when our MyQueue class is defined?

So this is when the initialization list comes in handy.

MyQueue(int capacity1, int capacity2,int size)
	:_st1(capacity1)
	,_st2(capacity2)
	,_size(size)
	{
    
    
	};

Insert image description here
This can solve this problem.
In fact, even if your Stack class has a default constructor, if I just want to pass parameters and call your default constructor myself, I can also do this.
Insert image description here
If there is no initialization list syntax, we How to call the parameter-passing constructor of the Stack class when declaring? An error
Insert image description here
will be reported directly

4. The initialization list is where member variables are defined

So what exactly is the identity of the initialization list?
So powerful

The initialization list is where member variables are defined

We have mentioned before:
Insert image description here
Insert image description here
that is to say, as long as your class has member variables, you will definitely use the constructor. As long as you use the constructor, you will definitely use the initialization list in the constructor.

The member variables are defined in the initialization list, which is the space allocated in the initialization list!!

5. The initialization list can be used in conjunction with the definition in the function body

So since the initialization list is so powerful, is it possible to eliminate the function body?
Isn’t it enough to only leave an initialization list in the constructor?
Of course not

For example: the following Stack class

	Stack(int capacity = 1)
		:_a((int*)malloc(sizeof(int)*capacity))
		,_capacity(capacity)
		,_top(0)
	{
    
    };

Insert image description here
Isn't that possible for you? This is a good situation.
What if my application space is too large and my application fails?

wzs::Stack st(1000000000000000000);

We directly apply for such a large space here.
Insert image description here
So what should we do?
Check it in the initialization list?
The initialization list is where member variables are defined to open up space for member variables.
You check whether _a is equal to a null pointer.
Isn't this the case? Is it overkill?
Wouldn't it be nice to just let _a initialize it in the function body?

That's it

Stack(long long capacity = 1)
	:_capacity(capacity)
	,_top(0)
{
    
    
	_a = ((int*)malloc(sizeof(int) * capacity));
	if (_a == nullptr)
	{
    
    
		perror("malloc fail");
		exit(-1);
	}
};
wzs::Stack st(1000000000000000000);

Failed to create space:
Insert image description here
Successfully created space:

wzs::Stack st(10);

Insert image description here
That is to say, the initial list and the function body of the constructor are complementary to each other,
similar to the relationship: reference and pointer

So in daily life: for member variables,
in most cases we use the initialization list.
For the rest, those that can only be initialized with the function body, use the function body.

6. The order in which the initialization list is executed

The order in which member variables are declared in a class is the order in which they are initialized in the initialization list,
regardless of their order in the initialization list.

You can take a look at this question. This is a question on "Sword Finger Offer".
Insert image description here
Answer:
n1: random value
n2:0
. Because the order in which member variables are declared in the class is the order in which they are initialized in the initialization list,
n1 is initialized first. , and then n2 is initialized.
That is to say,
their execution order in the initialization list is as follows:

n1 = n2+2;
n2 = 0;

Both n1 and n2 are random values ​​before they are initialized.
Therefore, n1 is a random value, and n2 is initialized to 0.
Insert image description here

7. Summary + suggestions

Insert image description here
The first one of these notes is the difference between the initialization list and the definition in the function body:
that is to say, if someone writes like this:
Insert image description here
that is to say, in the initialization list, member variables can only be initialized once, so it is called the initialization list
and in the function body , member variables can be assigned many times, so in the function body it can only be called initial value assignment, not initialization!!!

That is to say, for our constructor

Date(int year = 1, int month = 2, int day = 3)
	: _ref(_year)
	, _cint(_year)
	{
    
    
		_year=year;
		_month=month;
		_day=day;
	};
private:
	int _year;
	int _month;
	int _day=26;
	int& _ref;
	const int _cint;

Essentially the compiler does this:

Date(int year = 1, int month = 2, int day = 3)
	:_year(随机值)
	,_month(随机值)
	,_day(缺省值26)
	,_ref(_year)
	, _cint(_year)
	{
    
    
		//下面是重新对_year,_month,_day进行再次赋值
		_year=year;
		_month=month;
		_day=day;
	};
private:
	int _year;
	int _month;
	int _day=26;
	int& _ref;
	const int _cint;

That is to say, in the constructor:
the initialization list is where the member variables are defined, and each member variable needs to go through the initialization list.

If we do not explicitly define the member variable in the initialization list, the default value given when the member variable is declared will be used. If there is no default value, the random value given by the compiler will be used.

The definition within the function body is essentially a second assignment to the member variable.

That is like this

int var = 随机值; //初始化列表
var=1;//函数体内定义

In fact, after over and over again, there is only one sentence: the initialization list is where member variables are defined,
and the function body is where member variables can be assigned values ​​twice.

2. Static member variables and static member functions

Insert image description here
We have a need: calculate how many objects a class has instantiated

1. Export of static member variables

In fact, the method is very simple: you only need to define a global variable count=0, and then use this count++ in all the constructors of this class.
The value of the final count is the number of objects instantiated by this class.
Insert image description here
This is feasible, but Not good.
Why?
Because this variable count is a global variable, which means that this count can be accessed and modified anywhere in the program.

So this may happen:
Insert image description here
in this func function, it successfully modified the global variable count
, causing the final answer to be 1 less

That is to say, we cannot avoid this extreme situation from happening,
so what should we do?

Moreover, it is not possible to define this count as a member variable
because each object has its own count, which is ++’s own count.

So is there a member variable that is shared by all objects instantiated by my class?
Static member variables appear.

2.Characteristics of static member variables

Insert image description here
Now that we understand the characteristics of static member variables

3. To solve the above needs: shortcomings of static member variables

So how should we solve the above requirement?
Insert image description here
But there is still a problem.
Here I set the access attribute of the static member variable _count to public, but I cannot prevent the following situation from happening:
Insert image description here
Yes, you are You put the global variable _count inside your class A
, but you set its access attribute to public,
so if I func want to change your _count, I only need to add your class field A, and I can still change it. , you can't stop me

So what should we do?
Put _count under the private attribute:

3. Anonymous objects

Insert image description here
Yes, my func cannot be changed now, but your main function cannot be accessed either.
What should I do?
1. Encapsulate the get function.
Insert image description here
Yes, and my func function cannot modify your count.
But the premise for this is that your main function There is an object of A in it
, so you can use the object to access the get function.

But if I didn't create an object of type A in this main function,
then you can only do this.
Insert image description here
But you said that in order to get how many objects this class has instantiated, I have to go out of my way to create an object of type A in my main function.
It's too frustrating to specifically instantiate an object .

And I also need to consider the name, and when I create this object, I only need to let it complete one task. I don’t want to use it later. Can I let it be destroyed after completing this task?

This allows you to use anonymous objects.
Insert image description here
The anonymous object is A().
Its characteristics are:
1. No need to take a name
. 2. Its life cycle is only

cout << wzs::A().GetCount()-1 << endl;

This line
perfectly meets my needs just now

Please note: But this -1 is a bit eye-catching, which makes my code not very handsome
and makes me seem to be very low-level.

What should I do?
But if you want to access this get function, you must create an object
and then use the object to access it.

Can we not use objects?
So static member functions appeared

4.static member function

Insert image description here
Insert image description here
In this way, the -1 thing that affects the aesthetics of my code will disappear.
At this point, our need is perfectly solved.

5. Summary

Insert image description here
At this point, everyone will be able to have a deeper understanding of the picture below.
Insert image description here

6.OJ calculates 1+2+…+n

Let's do an OJ question to consolidate the above knowledge. What should we do if there are so many restrictions as
1+2+3+...+n ? In fact, the original intention of this question is to let us use static members of the class to solve this problem.
Insert image description here

Note: The compiler used in Niuke.com's programming questions supports variable-length arrays. The VS compiler does not support variable-length arrays.
Insert image description here
Insert image description here
However, this code is not particularly good. After we introduce the inner class, we will also This code is further modified

3.explicit keyword

1. A strange phenomenon

Insert image description here

wzs::B b2 = 1;
竟然能这么创建一个对象,这是怎么做到的呢?

In fact: we have introduced it
before in Introduction to C++-Quotation :

Insert image description here
So we can think of it like this.
Insert image description here
Then the following question is
why can the built-in type 1 be implicitly type converted to my class type B?

Actually there is a rule here:

当某个类的构造函数可以只传入一个参数时,而且这个参数的类型跟我这个内置类型相同的时候
就可以发生这个类类型对象和这个内置类型变量之间的隐式类型转换

下面这个例子能帮大家更好地去理解
class A
{
    
    
public:
    构造函数第1种情况:
    A(int val) //单参数构造函数
    :_var1(val)
    {
    
    }         
    A(int val1,int val2 = 2,int val3 = 3)//半缺省,且只传一个参数即可完成对象的构造
    :_var1(val1)
    ,_var2(val2)
    ,_var3(val3)
    {
    
    }
    A(int val1 = 1,int val2 = 2,int val3 = 3)//全缺省,这个构造函数允许只传一个参数进行构造
    :_var1(val1)
    ,_var2(val2)
    ,_var3(val3)
    {
    
    }
  private:
      int _var1;
      int _var2;
      int _var3;
};
它们都允许: A a = 1;
但是当这个内置类型跟我这个参数的类型不匹配时:
例如   A a = nullptr; 这样就无法发生隐式类型转换

Insert image description here
Insert image description here
Insert image description here
And there is another way to write it: list initialization

zs::A a={
    
    2023,11,3};

Insert image description here
The essence of this is the same as the above implicit conversion of built-in type 1 to an object of type A:
Insert image description here
But there is a problem
. What's the use of telling me so much?

2.Use

Let’s first use the vector container in STL
when we do OJ questions in leetcode:

#include <vector>
int main()
{
    
    
	vector<zs::A> v;

	//当我们在leetcode做OJ题时:

	//1.在没有学习这个知识之前我们平常的做法
	zs::A a(1);
	v.push_back(a);

	//2.学习了匿名对象后 
	v.push_back(zs::A(1));//  这代码写起来爽了很多

	//3.学习了内置类型和自定义类型之间的隐式转换后
	v.push_back(1);//     这代码写起来太爽了,可是只有在构造函数允许只传一个参数的时候才可以啊

	//4.学习了列表初始化隐式类型转换为类类型对象后
	v.push_back({
    
     2023,11,3 });//   爽飞了  ,而且允许只传一个参数对我无效
	return 0;
}

Insert image description here
It can be seen that this knowledge is great

3. Why should there be explicit?

So what is this explicit you introduced?
What is its use?
Insert image description here
Insert image description here

4. Friendship

Insert image description here

1. Friend function

We mentioned friend functions before when introducing operator overloading

At that time, it was to solve the problem that the stream insertion and stream extraction operators of the date class cannot be defined within the class, but you still want to access the member variables of this class.
For details, you can read this blog:
C++ classes and objects: Operator overloading + const member function + improvement of date class
Insert image description here

2. Friend class

1. Grammar:

这里以C类是B类的友元为例
class B
{
    
    
	friend class C;//友元声明不受类访问限定符的限制
}
class C
{
    
    
   B b;//需要有一个B类的对象
   //然后想要访问B类的成员变量或者成员函数的时候就可以用这个B类的对象.去访问
}

2. Example:

namespace zs
{
    
    
class B
{
    
    
public:
	friend class C;
private:
	void FuncOfB()
	{
    
    
		cout << "private:  FuncOfB()调用" << endl;
	}
	int _bint = 1;
	static int _StaticInt;
};
int B::_StaticInt = 5;
class C
{
    
    
	public:
		void SetMemberOfB(int val)
		{
    
    
			b._bint = val;
			B::_StaticInt = val;
		}
		void ReadMemberOfB()
		{
    
    
			cout << b._bint << endl;
			cout << B::_StaticInt << endl;//只能这样访问
		}
		void ReadFuncOfB()
		{
    
    
			b.FuncOfB();
		}
	private:
		int _cint;
		B b;
	};
}
int main()
{
    
    
	zs::C c;
	c.SetMemberOfB(100);
	c.ReadMemberOfB();
	c.ReadFuncOfB();
	return 0;
}

3. Summary

Regarding the access methods of friend classes, you only need to remember one thing:

Friend class: I am your friend,
and I can only access your private members.
The access method is the same as that used by ordinary classes to access your public members.

Insert image description here
Note: The friend relationship is one-way and not commutative.
Therefore, the above class B cannot access the private members of class C.

3.Inner class

1. Grammar

Insert image description here

2.Examples

namespace zs
{
    
    
	//注意:D是外部类,E是内部类.E天生就是D的友元,但是默认情况下D是不能访问E的,除非在E中声明D是E的友元类
	class D
	{
    
    
	public:
		class E
		{
    
    
		public:
			void GetStaticMemberOfD()
			{
    
    
				cout << _StaticInt << endl;
				//yes  这是上述第3条特性:  内部类可以直接访问外部类的static成员,不需要外部类的对象/类名
				//cout << _NonStaticInt << endl;//err  内部类不能直接访问外部类的非static成员
				cout << d._NonStaticInt << endl;//yes  只能用对象.去访问

				//也就是说内部类访问外部类:只不过是静态成员可以直接访问而已,非静态成员的访问跟普通类访问外部类的非静态成员的方法一样
			}
		private:
			zs::D d;
		};
	private:
		static int _StaticInt;
		int _NonStaticInt = 10;
	};
	int D::_StaticInt = 1;
}

3. Summary:

Insert image description here
In fact, inner classes are not commonly used in C++

Optimization of OJ questions

After learning the inner class, we can optimize the OJ question
Insert image description here
Insert image description here

The above is all the content under classes and objects, I hope it can be helpful to everyone!!!

Guess you like

Origin blog.csdn.net/Wzs040810/article/details/134150530