C++ classes and objects (6)—initialization list, explicit keyword, static members

Table of contents

1. Initialization list 

1. Definition 

2. Things to note

3. Try to use initialization list initialization

4. Initialization sequence

2. explicit keyword

1. Definition

2. Features

3. static members

1. Definition 

2. Features 

3. Example questions


1. Initialization list 

The following code compiles normally:

class A {
private:
	int _a1;//成员声明
	int _a2;
};
int main()
{
	A a;//对象整体定义
	return 0;
}

If you add a const type member variable _x, the compilation will not pass.​ 

class A {
private:
	int _a1;
	int _a2;
	const int _x;
};
int main()
{
	A a;
	return 0;
}

This is because const variables must be initialized where they are defined, otherwise compilation will not pass.

class A {
private:
	int _a1;//声明
	int _a2;
	const int _x;
};

In the private scope, the const variable and the two int variables are declarations of member variables. If we declare a const variable, we must define it, so where do we define it?

After C++11, variables can be assigned initial values ​​at the declaration location.

const int _x = 0;

Before C++11, there was also a solution. Find a location to define each member variable. This solves the problem of variable initialization. This location uses an initialization list for initialization and assignment.

1. Definition 

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.
This solves the problem of const member variable initialization.
class A {
public:
	A()
		:_x(1)
	{}
private:
	int _a1;
	int _a2;
	const int _x;
};
int main()
{
	A a;
	return 0;
}

As long as the object calls the constructor, the initialization list is where all its member variables are defined.
Regardless of whether it is written in the initialization list, the compiler will initialize each variable in the initialization list definition.

class A {
public:
	A()
		:_x(1),_a1(6)
	{}
private:
	int _a1 = 1;
	int _a2 = 2;
	const int _x;
};
int main()
{
	A a;
	return 0;
}

Variables initialized in the initialization list do not use the default value; variables that do not use the initialization list use the default value.​ 

2. Things to note

  1. Each member variable can only appear once in the initialization list (initialization can only be initialized once)
  2. The class contains the following members, which must be placed in the initialization list for initialization:
  • Reference member variables
  • const member variable
  • Custom type members (when the class does not have a default constructor)
class B {
public:
	B():_b(0)
	{
		cout << "B()" << endl;
	}
private:
	int _b;
};

class A {
private:
	B _bb;
};
int main()
{
	A aa;
	return 0;
}

 The member variable custom type _bb of aa here can be initialized by calling the initialization list of its default constructor.

 Default construction can be parameterless or completely default.

class B {
public:
	B(int n) :_b(0)//会报错
    B(int n=9) :_b(0)//全缺省
    B( ) :_b(0)//无参
private:
	int _b;
};

3. Try to use initialization list initialization

Because regardless of whether you use an initialization list, custom type member variables must be initialized using an initialization list first.

 Let’s look at a queue implemented with two stacks.

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		cout << "Stack(size_t capacity = 10)" << endl;

		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			exit(-1);
		}

		_size = 0;
		_capacity = capacity;
	}

	void Push(const DataType& data)
    {
		_array[_size] = data;
		_size++;
	}

	Stack(const Stack& st)
	{
		cout << "Stack(const Stack& st)" << endl;
		_array = (DataType*)malloc(sizeof(DataType)*st._capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			exit(-1);
		}

		memcpy(_array, st._array, sizeof(DataType)*st._size);
		_size = st._size;
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType *_array;
	size_t    _size;
	size_t    _capacity;
};

class MyQueue
{
public:
	MyQueue(int pushN, int popN)
		:_pushST(pushN)
		, _popST(popN)
	{}

private:
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

int main()
{	
	MyQueue q(2, 3);
	return 0;
}

You can see in debugging that 2 and 3 are passed as parameters to the constructor of MyQueue , initialize these two member variables through the initialization list.

 What if we use this parameterless constructor to initialize the MyQueue object?

class MyQueue
{
public:
	MyQueue()
	{}

private:
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

It can be seen that if we do not write an initialization list, the MyQueue class can also call the default constructor of Stack to initialize the objects of the two Stack classes. The constructor of MyQueue will also be initialized in the same way, which is essentially the same.

4. Initialization sequence

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.

class A{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main() {
	A aa(1);
	aa.Print();
}

_a2 is declared before _a1, so _a2 is initialized in the initialization list before _a1. When _a2 is initialized, _a1 has not been initialized yet, so _a2 is a random value.

2. explicit keyword

class A {
public:
	A(int a):_a1(a)
	{}
private:
	int _a2;
	int _a1;
};
int main()
{
	A aa1(1);	//构造函数
	A aa2 = 1;	//隐式类型转换
	int i = 1;	
	double d = i;//隐式类型转换
	return 0;
}

By default, the implicit type conversion here will be implemented with the help of additionally created temporary variables. Temporary variables are created through construction, and then the process of assigning values ​​to variables through copy construction is optimized into direct construction. The next article will explain the optimization process in detail.

In both cases, temporary variables are created to complete the type conversion process. These temporary variables are destroyed after the conversion is completed and are not visible to other parts of the program. The creation and destruction of such temporary variables is handled automatically by the compiler without manual intervention.

  • A aa2 = 1; An implicit type conversion from int to A occurs here. The compiler will automatically call the constructor of class A to create a temporary A object, and then pass the integer value 1 to the constructor as a parameter. This temporary A object will be copied to aa2 to complete implicit type conversion.

  • double d = i; An implicit type conversion from int to double occurs here. The compiler will create a temporary double variable and copy the value of the integer variable i to this temporary variable. Then, the value of this temporary double variable will be assigned to variable d, completing implicit type conversion.

Copy construction is also a construction, and an initialization list can also be used. But will the following member variables call copy construction?

class A
{
public:
	A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a2;
	int _a1;
};

int main()
{
	A aa1(1);	//构造函数
	A aa2 = 1;	//隐式类型转换
	return 0;
}

The output shows that the copy constructor of the reference type is not called.

 

This is because the compiler in C++ will optimize the custom type and optimize the process of construction + copy + optimization into a construction.

In the following code, why does the first one report an error, but the second one is fine?​ 

A& ref = 10;
const A& ref = 10;
  • This is because in C++, when you declare a reference (such as A& ref) and try to initialize it to an rvalue (such as a temporary object or a literal), the compiler The server usually reports an error. This is because non-const references cannot be bound to rvalues, preventing non-const references to temporary objects, as this could lead to unexpected modifications to the temporary object, resulting in undefined behavior. However, when you declare a const reference (such as const A& ref), the compiler allows this binding, because const references can be bound to rvalues.
  • In the above code, const A& ref = 10; 10 in this line of code is an integer literal and an rvalue. You try to bind this rvalue to the reference ref . Since ref is declared as const A&, it is a constant reference, so the compiler allows this binding and calls the A class The constructor of A(int a) creates a temporary A object, and then binds ref to this temporary object.

1. Definition

The constructor can not only construct and initialize objects, but also have default values ​​for a single parameter or for all parameters except the first parameter, which has no default value.
The constructor also has the function of type conversion.
For single parameter constructor: no explicit modification is used, and it has type conversion effect
Explicit modifies the constructor and prohibits type conversion---After removing explicit, the code can be compiled.

 Regarding the code just now, what would happen if an explicit program was added before the constructor?

class A{
public:
	explicit A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a2;
	int _a1;
};
int main()
{
	A aa1(1);	//构造函数
	A aa2 = 1;	//隐式类型转换
	const A& ref = 10;

	return 0;
}

 These two pieces of code will report an error, and the program prohibits type conversion.

 

2. Features

class A
{
public:
	//explicit A(int a)
	A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}

private:
	int _a2;
	int _a1;
};
int main()
{
    // 单参数构造函数 C++98
	A aa1(1);	//构造函数
    A aa2 = 1;	//隐式类型转换

	// 多参数构造函数 C++11
	A aa2(1, 1);
    //A aa3= 2,2;//C98不支持
	A aa3 = { 2, 2 };//C++11支持

	return 0;
}
  1. A aa1(1); This is an example of directly calling a single-parameter constructor to create an object.

  2. A aa2 = 1; This is an example of implicit type conversion. Here, the integer 1 is implicitly converted to an object of class A. This is because class A defines a constructor that accepts int type parameters, so the compiler will automatically call the constructor to create a temporary A object and assign it to aa2.

  3. A aa2(1, 1); This is an example of directly calling the two-parameter constructor to create an object.

  4. A aa3 = { 2, 2 }; This is an example of list initialization introduced in C++11. This method can be used to initialize an object without explicitly calling the constructor.

explicitThe keyword is used to prevent the compiler from performing unwanted implicit type conversions. If you remove the comments in front of the constructor so that there is the explicit keyword in front of the constructor, then implicit type conversions like A aa2 = 1; will be prohibited and compile The server will report an error.

For example, if you change the single-argument constructor to explicit A(int a), then the line A aa2 = 1; will result in Compilation error because the compiler is prohibited from implicit type conversion from int to A. You must call the constructor explicitly, like A aa2(1);.

In general, theexplicit keyword can help you control type conversions and prevent errors caused by unwanted implicit type conversions.

3. static members

Implement a class and count how many class objects are created in the program

int count = 0;
class A
{
public:
	A(int a = 0)
	{
		++count;
	}
	A(const A& aa)
	{
		++count;
	}
};
void func(A a)
{}
int main()
{
	A aa1;
	A aa2(aa1);
	func(aa1);
	A aa3 = 1;

	cout << count << endl;

	return 0;
}

This caused a naming conflict problem because there is a function count in the C++ xutility file that conflicts with the global variable count we defined.

We can not expand std and only call the required stream input and output.

#include <iostream>
//using namespace std;
using std::cout;
using std::endl;

Successful output: 

In order to solve the above problems, C++ can expand std and use count as a statically modified member of the class.

1. Definition 

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. Characteristics

  • Static members are shared by all class objects and do not belong to a specific object. They are stored in the static area.
  • Static member variables must be defined outside the class. The static keyword is not added when defining. It is just declared in the class.
  • Class static members can be accessed using classname::static member or object.static member
  • Static member functions have no hidden this pointer and cannot access any non-static members.
  • Static members are also members of the class and are restricted by public, protected, and private access qualifiers.
class A
{
public:
	A(int a = 0)
	{
		++count;
	}

	A(const A& aa)
	{
		++count;
	}
	int Getcount()
	{
		return count;
	}
private:
	static int count; // 此处为声明
	int _a = 0;
};
	
int A::count = 0; // 定义初始化

void func(A a)
{}

When we want to output:

int main()
{
	A aa1;
	A aa2(aa1);
	func(aa1);
	A aa3 = 1;

	cout << A::Getcount() << endl;

	return 0;
}
The output statement will report an error:
If you want output, you can use static member functions.
	//静态成员函数 没有this指针
	static int Getcount()
	{
		// _a++; // 不能直接访问非静态成员
		return count;
	}

Successful output:

Can a static member function call a non-static member function?

The answer is "No". Static member functions have no implicit this pointers, so there is no way to access non-static member functions or non-static member variables of any particular object. Non-static member functions rely on a specific object instance (through this pointer) to access and operate non-static member variables and functions. Within a static member function, non-static members cannot be accessed directly.

Can non-static member functions call static member functions of a class?

Yes, non-static member functions can call static member functions of a class. Non-static member functions can access static member functions directly through the class name or object name during execution. Static member functions do not depend on a specific class instance, so they can be called directly from any instance or class.

How many class objects are created by the following statement?
A aa4[10];

Output result:

 

3. Example questions

The link is as follows:

Asking for 1+2+3+...+n_Niuke Question Ba_Niuke.com (nowcoder.com)

  • The following code implements a class Sum and a class Solution, where the Sum class is used to calculate from The cumulative sum of 1 to n, while the Solution class uses the Sum class to compute the cumulative sum of a given integer n. 
  • This design takes advantage of the characteristics of the class's constructor and static member variables to implement the calculation and acquisition of the cumulative sum.
class Sum{
public:
    Sum()
    {
        _sum+=_i;
        _i++;
    }
    static int Getsum()
    {
        return _sum;
    }
private:
    static int _sum;
    static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;
class Solution {
public:
    int Sum_Solution(int n) {
        Sum a[n];
        return Sum::Getsum();
    }
};

First, let us explain step by step the implementation of the Sum class:

  • Sum The class has two static member variables _sum and _i, which are used to save the cumulative sum and the current counter value respectively.
  • Constructor Sum() is a parameterless constructor. Each time it is called, it will add the current counter value _i to the cumulative sum a>  by 1. _sum and increment the counter _i
  • Static member function Getsum() is used to obtain the value of the cumulative sum _sum .

Next, let’s look at the implementation of the Solution class:

  • Solution The member function in the class Sum_Solution(int n) accepts an integer n as a parameter and returns the cumulative sum from 1 to n.
  • In the Sum_Solution function, we created an array named a of type Sum , the size of the array for n.
  • Since the constructor of the Sum class will be automatically called when creating the object, during the process of creating the array a , it will be called in sequence  The constructor of the Sum class implements the calculation of the cumulative sum from 1 to n.
  • Finally, we get the value of the cumulative sum by calling the Sum::Getsum() function and use it as the return value of the function.

Guess you like

Origin blog.csdn.net/m0_73800602/article/details/134612286