[Elementary C++] Classes and Objects (3)

1. Let’s talk about the constructor again

1.1 Initialization list

We have learned most of the constructor before, but not all of it. There is another very important thing - the initialization list.

1.1.1 How to write the initialization list

Initialization list: Begins with a colon, followed by a comma-separated list of data members, each "member variable" followed by an initial value or expression in parentheses.

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    
	
	}
	//
	Date d1(2023, 11, 9);

Run result:
Insert image description here
Numeric values ​​can also be included in the brackets (no parameters are passed):

	Date()
		:_year(2023)
		, _month(11)
		, _day(9)
	{
    
    

	}

Supplement: The declaration of the default value is actually for the initialization list, but the constructor is not shown; if a constructor is written, the value defined in the constructor is used. The constructor has no parameters or the specific value is a random value. (The random value under VS is 0)

1.1.2 Which members should use the initialization list?

Let’s summarize it first and then analyze it one by one.

The members of the list that must be initialized are:
1. Reference member variables
2. Const member variables
3.Custom-type member variables without default construction

public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
		,t(year)
		,a(5)
	{
    
    

	}

private:
	int _year;
	int _month;
	int _day;
	int& t;//引用成员变量
	const int a;//const成员变量
};

operation result:
Insert image description here

1️⃣There is a requirement for using references, that is, they must be initialized. There is no initialization in the above because these are all statements, and next we need to defineThis refers to a member variable, so where is it defined? Just in the initialization list.

2️⃣Const member variables are the same as references. They are only declared in the private area. If they are defined, they must be in the initialization list.

3️⃣When using the queue class in the previous article, we did not write the constructor of the queue class. The built-in type declaration has a default value and uses the default value. If not, it is a random value; the custom type member variable allows the compiler to automatically call its default constructor.

Review what a default construct is:
1. We do not display the constructor automatically generated by the compiler by default
2. There is no parameter passed
3. Fully default

If we want to control the number of parameters ourselves, that is to say, we need to pass parameters to the constructor of a custom type (or. must be explicitly written to initialize the constructor, and the constructor must have an initialization list, otherwise we will neither display the constructor of the queue class nor customize it The type's construction is the default construction, and the result is a random value), The constructor of a custom type is not a default constructor, so we need to pass the parameters ourselves

To summarize:
Reference member variables and const member variables must be initialized where they are defined, which is in the initialization list; custom type member variables do not write the constructor of this class , automatically call the default constructor of this custom type member; write the constructor of this class, and then analyze it~~

1.2 Characteristics of initialization list

1.2.1 Solving queue problems

We didn't write the constructor of the queue class before, but we will write it this time. To initialize the member variables of the custom type in the constructor, we need to use the initialization list.

The following code:

class Stack
{
    
    
public:
	Stack(int capacity = 3)
	{
    
    
		cout << "Stack(int capacity = 3)" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_a);
		_a = NULL;
		_top = 0;
		_capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
class Queue
{
    
    
public:
//   队列类的构造函数
	Queue()
		:_st1()
		,_st2()
		,_size()
	{
    
    

	}
private:

	Stack _st1;
	Stack _st2;
	int _size;
};
int main()
{
    
    
	Queue q1;
	return 0;
}

Who is the parameter? There are several situations:
1️⃣There is only a constructor, no parameters, and no specific value after the brackets< a i=3> Check the allowed results through debugging: We found that it is 0, 3, 0, 3, 0, so without passing any value,

Insert image description here
The initialization list has a random value for the built-in type (it becomes 0 under VS), and the custom type calls its default constructor.

2️⃣No parameters, specific values ​​after brackets

	Queue()
		:_st1(10)
		, _st2(20)
		, _size(30)
	{
    
    

	}

Debugging results:
Insert image description here

3️⃣Some parameters are fully defaulted and have specific values ​​after brackets

	Queue(Stack st1 = 44, Stack st2 = 66, int size = 88)
		:_st1(1)
		, _st2(2)
		, _size(3)
	{
    
    

	}

Debug result:
Insert image description here
4️⃣Some parameters are fully defaulted, and the parameters are after the brackets

	Queue(Stack st1 = 44, Stack st2 = 66, int size = 88)
		:_st1(st1)
		, _st2(st2)
		, _size(size)
	{
    
    

	}

Debugging results:
Insert image description here

5️⃣Self-service

	Queue(Stack st1 = 44, Stack st2 = 66, int size = 88)
		:_st1(st1)
		, _st2(st2)
		, _size(size)
	{
    
    

	}
	///
	Queue q1(11, 77, 99);

Debug result:
Insert image description here
6️⃣There is no constructor, declared to default value

private:
	Stack _st1 = 7;
	Stack _st2 = 8;
	int _size = 9;
};

Debugging results:
Insert image description here

To summarize:
1. When the constructor of a custom type member is not the default constructor, there are the following:
Pass the parameters yourself 5️⃣; if there are parameters, they are all default, and the parameters are after the brackets 4️⃣; if there are parameters, they are all defaults, and there are specific values ​​after the brackets 3️⃣; if there are no parameters, there are specific values ​​after the brackets 2️⃣; it is declared as the default value 6️⃣ . In short, parameters are passed to the constructor of the custom type member.
If there is a specific value, use the specific value. If there is no specific value, the parameter will be passed first, followed by the default value
In order to facilitate the control of parameters, it is better to use 5️⃣.
2. When the constructor of a custom type member is the default constructor:
There is only a constructor, no parameters, and no specific value 1️⃣
There is also a seventh method 7️⃣ Not writing a constructor (the way of writing in the previous article)
There is actually no difference between the two, so just don’t write a constructor.

1.2.2 The declaration order is the order of the initialization list

Show it directly with an example:

class Date
{
    
    
public:
	
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
    
    

	}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _day;//day放在最前面
	int _year;
	int _month;
};
int main()
{
    
    
	Date d1(2023, 11, 9);
	d1.Print();
	return 0;
}

Debug result:
Insert image description here
So the day is initialized first, followed by the year and month.

1.3 explicit keyword

1.3.1 The role of explicit keyword

For single-parameter constructors with only built-in types, there is a type conversion function
Look at the following code:

class A
{
    
    
public:
	A(int a)
	{
    
    
		_a = a;
	}
	void Print()
	{
    
    
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	A aa(1);
	aa = 3;
	aa.Print();
	return 0;
}

aa is a custom type, 3 is an integer, the running result is:
Insert image description here
If you don’t want the conversion to occur, add explicit before the constructor, and the compiler will report an error.

Supplement: Adding the explicit keyword can prevent implicit type conversion, but it cannot prevent forced conversion.

Without explicit modification, multi-parameters and semi-default are also supported.

class Date
{
    
    
public:
	Date(int year, int month = 2, int day = 3)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1 = (2023, 11, 10);
	return 0;
}

Debug result:
Insert image description here
Note: The parentheses are comma expressions, only the last number is counted, the others are default values.

Modify it by changing the parentheses into curly braces and removing the default value:

Date d1 = {
    
     2023, 11, 10 };

Debugging results:
Insert image description here

2. static members

2.1 The concept of static members of a class

Class members declared as static are called static members of the class. Member variables modified with static are called static member variables; member functions modified with static are called static member functions.

2.2 How many objects are created in the class?

Define a variable count for counting. Each time an object is created (constructed) or an object is copied and constructed, count++. Let’s first use the global variable count to count and see what happens:
1️⃣

int count = 0;
class A
{
    
    
public:
	A() {
    
     ++count; }
	A(const A& t) {
    
     ++count; }
	~A() {
    
      }
private:

};

A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa;
	Func();
	cout << count << endl;
	return 0;
}

Insert image description here
The compiler reported an error, indicating that count was an ambiguous symbol, indicating a naming conflict.

Solution to naming conflict - namespace
2️⃣

namespace yss
{
    
    
	int count = 0;
}
class A
{
    
    
public:
	A() {
    
     ++yss::count; }
	A(const A& t) {
    
     ++yss::count; }
	~A() {
    
      }

private:
	
};

A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa;
	Func();
	cout << yss::count << endl;
	return 0;
}

Running result:
Insert image description here
This result is indeed the answer we want, but there is a problem here: if there is one more class, the two classes cannot be distinguished using global variables. As a result, after counting one class and then counting another class, the count value is not updated, which means that the objects of the two classes are merged together.

Let’s change the way and define the member variables of the class
3️⃣

class A
{
    
    
public:
	A() {
    
     ++count; }
	A(const A& t) {
    
     ++count; }
	~A() {
    
      }

//private:
	int count = 0;
};

A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa;
	Func();
	cout << aa.count << endl;
	return 0;
}

Let’s comment out the private permissions temporarily and see what the results are:
Insert image description here
Why is it 1? Because every time an object is created, count+1, but when another object is created, count is cleared and then+1. It means that each object in the class has a count, and the result we want is the number of all objects in the class, but each object has its own count.

What to do if you want all the objects in the class to have a count? Use static member variables and static modify count.
Static member variables must be initialized outside the class
4️⃣

class A
{
    
    
public:
	A() {
    
     ++count; }
	A(const A& t) {
    
     ++count; }
	~A() {
    
      }

//private:
	static int count;
};
int A::count = 0;
A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa;
	Func();
	cout << aa.count << endl;
	return 0;
}

Run result:
Insert image description here
The result is no problem, but without private permissions, the encapsulation of the class cannot be very good.

Improvement: The value of count can be returned through a member function
5️⃣

class A
{
    
    
public:
	A() {
    
     ++count; }
	A(const A& t) {
    
     ++count; }
	~A() {
    
      }

	int Getcount()
	{
    
    
		return count;
	}
private:
	static int count;
};
int A::count = 0;
A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa;
	Func();
	cout << aa.Getcount() << endl;
	return 0;
}

Run result:
Insert image description here
But if we want tocount how many objects are created by calling the Func function , what should I do if the object instantiated in the main function is not counted?

Assume that the Func function is called twice,don’t comment on the instantiation of the object first, the result should be 5

int main()
{
    
    
	A aa;
	Func();
	Func();
	cout << aa.Getcount() << endl;
	return 0;
}

Insert image description here
Then I commented out the object and found that the compiler reported an error: Undefined identifier aa.
Insert image description here

You can usestatic member function to solve the problem
6️⃣

	static int Getcount()
	{
    
    
		return count;
	}

Note: To access throughclass name::static member

operation result:
Insert image description here

Summary:
Static member variables and static member functions are very similar to global variables and global functions, except that they are restricted by scope qualifiers and dot member operators
Supplement:
Static member functions cannot call non-static member functions because static member functions do not have this pointers ; Non-static member functionscancall static member functions of a class because they belong to the same class.

3. Youyuan

3.1 Concept

Friends provide a way to break through encapsulation and sometimes provide convenience. However, friends will increase coupling and destroy encapsulation, so friends should not be used more than once.

Friends are divided into:Friend functions and friend classes

3.2 Friend functions

When a function is not a member function of a class but needs to be able to access member variables in the class, a friend function must be used.

A friend function can directly access the private members of a class. It is an ordinary function defined outside the class and does not belong to any class. However, it needs to be declared inside the class. The friend keyword needs to be added when declaring.

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//友元函数
	friend ostream& operator<<(ostream& out, Date& d);
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, Date& d)
{
    
    
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}
int main()
{
    
    
	Date d1(2023, 11, 10);
	cout << d1;
	return 0;
}

Features:
Friend functions can access private and protected members of the class, but are not member functions of the class
Friend functions cannot be modified with const
Friend functions can be declared anywhere in the class definition and are not restricted by class access qualifiers
A function can be a friend function of multiple classes a>
The principle of calling friend functions is the same as that of ordinary functions

3.3 Friend classes

Create a time class and declare the date class as a friend class of the time class. Then the private member variables in the Time class can be directly accessed in the date class.

class Time
{
    
    
	// 友元类
	friend class Date; 
public:
	Time(int hour = 2020, int minute = 6, int second = 5)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{
    
    }

private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	Date(int year = 2023, int month = 11, int day = 10)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }

	void SetTimeOfDate(int hour, int minute, int second)
	{
    
    
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
		Print();
	}
	void Print()
	{
    
    
		cout << _t._hour << "-" << _t._minute << "-" << _t._second << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
    
    
	Date d1;
	d1.Print();
	return 0;
}

Insert image description here

Characteristics:
Friend relationship is one-way and not exchangeable.
Declare the Date class as its friend class in the Time class, then you can directly access the private member variables of the Time class in the Date class, but you want to access the private members of the Date class in the Time class Variables don't work.
Friend relationship is not transitive
If C is a friend of B and B is a friend of A, it cannot be said that C is a friend of A.
Friend relationships cannot be inherited

4. Internal classes

A class is defined inside another class. This inner class is called an inner class.. The inner class is an independent class. It does not belong to the outer class, and the members of the inner class cannot be accessed through the objects of the outer class.

Inner classes are friend classes of outer classes. The inner class can access all members of the outer class through the object parameters of the outer class. But the outer class is not a friend of the inner class.

class A
{
    
    
public:
	class B
	{
    
    
	public:
		void Func()
		{
    
    
			cout << _a << endl;
		}
	};
	private:
		int _b;
private:
	static int _a;
};
int A::_a = 10;
int main()
{
    
    
	A::B b1;
	b1.Func();
	return 0;
}

Insert image description here

Features:
Internal classes can be defined as public, protected, or private in external classes.
Note that internal classes can directly access external classes. The static members in do not require the object/class name of the external class
sizeof (external class) = external class, which has nothing to do with the internal class

5. Some compiler optimizations when copying objects

In the process of passing parameters and returning values, the compiler will generally do some optimizations to reduce object copies.Different compilers may optimize in different ways.

1️⃣Implicit type, continuous construction + copy construction->Optimized to direct construction

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}
private:
	int _a;
};
void Func(A aa)
{
    
    
	
}
int main()
{
    
    
	Func(1);
	return 0;
}

Insert image description here
1 is an integer type. Implicit type conversion occurs by calling the constructor to generate a temporary variable. The temporary variable is then copied to the formal parameter and the copy constructor is called.

2️⃣In an expression, continuous construction + copy construction->Optimize to one construction

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}
private:
	int _a;
};
void Func(A aa)
{
    
    

}
int main()
{
    
    
	Func(A(1));
	return 0;
}

Insert image description here
In an expression, A(1) calls the constructor to generate a temporary variable. The temporary variable is then copied to the formal parameter and calls the copy constructor, which is optimized into a one-time construction.

3️⃣In an expression, continuous copy construction + copy construction->Optimize a copy construction

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}
private:
	int _a;
};
A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa2 = Func();
	return 0;
}

Insert image description here
Instantiate aa2 and call the construction once, enter the Func() function, create the aa object and call the construction once, and the two constructions are optimized into one construction; copy the local variables to the return value and call the copy construction, copy the return value to aa2 and then call the copy construction, twice The copy construction is optimized into a copy construction.

4️⃣In an expression, continuous copy construction + assignment overloading->Cannot be optimized

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}
private:
	int _a;
};
A Func()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	A aa3;
	aa3 = Func();
	return 0;
}

Insert image description here
Instantiate aa3 and call the constructor. At this time, aa3 already exists. The return value of calling Func() is assignment. The aa object is created in the Func() function and the constructor is called. The local variables are copied to the return value and the copy constructor is called, and finally the value is assigned.

Guess you like

Origin blog.csdn.net/2301_77459845/article/details/134254654