[C++11] List initialization | decltype operator | nullptr | STL update

1. List initialization

1. Brace initialization

{ } initialization

In C++98, the standard allows the use of curly braces {} for uniform list initialization of array or structure elements. for example:

struct Point
{
    
    
	int _x;
	int _y;
};
int main()
{
    
    
	//使用大括号对数组元素进行初始化
	int array1[] = {
    
     1, 2, 3, 4, 5 };
	int array2[5] = {
    
     0 };

	//使用大括号对结构体元素进行初始化
	Point p = {
    
     1, 2 };
	return 0;
}

C++11 expands the scope of use of the list enclosed in curly braces {初始化列表}so that it can be used for all built-in types and user-defined types. When using the initialization list, the equal sign can be added or not. for example:

struct Point
{
    
    
	int _x;
	int _y;
};
int main()
{
    
    
	//使用大括号对内置类型进行初始化
	int x1 = {
    
     1 }; //可添加等号
	int x2{
    
     2 };    //可不添加等号

	//使用大括号对数组元素进行初始化
	int array1[]{
    
    1, 2, 3, 4, 5}; //可不添加等号
	int array2[5]{
    
    0};            //可不添加等号

	//使用大括号对结构体元素进行初始化
	Point p{
    
     1, 2 }; //可不添加等号

	//C++11中列表初始化也可以用于new表达式中(C++98无法初始化)
	int* p1 = new int[4]{
    
    0};       //不可添加等号
	int* p2 = new int[4]{
    
    1,2,3,4}; //不可添加等号
	return 0;
}

Note : The equal sign cannot be added when the new expression is initialized with curly braces.

When creating an object, you can also use the list initialization method to call the constructor initialization. for example:

class Date
{
    
    
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
    
    
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	//一般调用构造函数创建对象的方式
	Date d1(2022, 8, 29);

	//C++11支持的列表初始化,这里也会调用构造函数初始化
	Date d2 = {
    
     2022, 8, 30 }; //可添加等号
	Date d3{
    
     2022, 8, 31 };    //可不添加等号
	return 0;
}

2. initializer_list

A new container was added in C++11 initializer_list, which does not provide too many member functions.

  • The begin and end functions are provided to support iterator traversal.
  • And the size function supports getting the number of elements in the container.

insert image description here

The initializer_list is essentially a list enclosed in braces. If you define a variable with the auto keyword to receive a list enclosed in braces, and then check the type of the variable with typeid(variable name).name(), then You will find that the type of the variable is initializer_list.

int main()
{
    
    
	auto il = {
    
     1, 2, 3, 4, 5 };
	cout << typeid(il).name() << endl; //class std::initializer_list<int>
	return 0;
}

The initializer_list container does not provide the corresponding interface for adding, deleting, checking and modifying, because the initializer_list is not specially used to store data, but to allow other containers to support list initialization. for example:

class Date
{
    
    
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
    
    
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	//用大括号括起来的列表对容器进行初始化
	vector<int> v = {
    
     1, 2, 3, 4, 5 };
	list<int> l = {
    
     10, 20, 30, 40, 50 };
	vector<Date> vd = {
    
     Date(2022, 8, 29), Date{
    
     2022, 8, 30 }, {
    
     2022, 8, 31 } };
	map<string, string> m{
    
     make_pair("sort", "排序"), {
    
     "insert", "插入" } };

	//用大括号括起来的列表对容器赋值
	v = {
    
     5, 4, 3, 2, 1 };
	return 0;
}

C++98 does not support the initialization of containers directly with lists. This initialization method is only supported after the introduction of initializer_list in C++11.

The fundamental reason why these containers support the use of lists for initialization is that C++11 adds a constructor to these containers, and this constructor takes initializer_list as a parameter.

insert image description here

When the container is initialized with a list, the list is recognized as initializer_list type, so the newly added constructor is called to initialize the container.

What this new constructor needs to do is to traverse the elements in the initializer_list, and then insert these elements into the container to be initialized in turn.

Take the vector simulated by the blogger as an example, if you want it to support list initialization, you need to add a constructor with initializer_list as a parameter. for example:

namespace cl
{
    
    
	template<class T>
	class vector
	{
    
    
	public:
		typedef T* iterator;
		vector(initializer_list<T> il)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
    
    
			resize(il.size());
			for (auto e : il)
			{
    
    
				push_back(e);
			}
		}
		vector<T>& operator=(initializer_list<T> il)
		{
    
    
			//迭代器遍历
			//typename initializer_list<T>::iterator it = il.begin();
			//while (it != il.end())
			//{
    
    
			//	push_back(*it);
			//	it++;
			//}
			//范围for遍历

			vector<T> tmp(il);
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_endofstorage, tmp._endofstorage);
			return *this;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

Explain:

  • When traversing the initializer_list in the constructor, you can use iterator traversal, or you can use range for traversal, because the bottom layer of range for actually uses iterator traversal.
  • When using the iterator method to traverse, you need to add the typename keyword in front of the iterator type to indicate that this is a type name. Because this iterator type is defined in a class template, the compiler cannot recognize this type until the class template is instantiated.
  • It is best to add an assignment operator overload function that takes initializer_list as a parameter to support the assignment of the container object directly with the list, but it does not need to be added.

If there is no assignment operator overload function that takes initializer_list as a parameter, the following code can also be executed normally:

vector<int> v = {
    
     1, 2, 3, 4, 5 };
v = {
    
     5, 4, 3, 2, 1 };

explain:

  • For the first line of code, it is to call the constructor with initializer_list as a parameter to complete the initialization of the object.
  • For the second line of code, it will first call the initializer_list constructor to construct a vector object, and then call the original assignment operator overload function of vector to complete the assignment between two vector objects.

2. Decltype

The keyword decltype can declare the type of the variable as the type specified by the expression. for example:

template<class T1, class T2>
void F(T1 t1, T2 t2)
{
    
    
	decltype(t1*t2) ret;
	cout << typeid(ret).name() << endl;
}
int main()
{
    
    
	const int x = 1;
	double y = 2.2;

	decltype(x*y) ret;
	decltype(&x) p;
	cout << typeid(ret).name() << endl; //double
	cout << typeid(p).name() << endl;   //int const *

	F(1, 'a'); //int
	F(1, 2.2); //double

	return 0;
}

Note : typeid(变量名).name()The type of a variable can be obtained through the method, but the obtained type cannot be used to define the variable.
In addition to being able to deduce the type of an expression, decltype can also deduce the type of the return value of a function. for example:

void* GetMemory(size_t size)
{
    
    
	return malloc(size);
}
int main()
{
    
    
	//如果没有带参数,推导函数的类型
	cout << typeid(decltype(GetMemory)).name() << endl;
	//如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
	cout << typeid(decltype(GetMemory(0))).name() << endl;
	return 0;
}

decltype can not only specify the defined variable type, but also specify the return type of the function. for example:

template<class T1, class T2>
auto Add(T1 t1, T2 t2)->decltype(t1+t2)
{
    
    
	decltype(t1+t2) ret;
	ret = t1 + t2;
	cout << typeid(ret).name() << endl;
	return ret;
}
int main()
{
    
    
	cout << Add(1, 2) << endl;;   //int
	cout << Add(1, 2.2) << endl;; //double
	return 0;
}

Three.nullptr

Since NULL is defined as a literal 0 in C++, this may cause some problems, because 0 can represent both a pointer constant and an integer constant. So from the perspective of clarity and safety, nullptr is added in C++11 to represent a null pointer.

/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL    0
#else  /* __cplusplus */
#define NULL    ((void *)0)
#endif  /* __cplusplus */
#endif  /* NULL */

In most cases, there is no problem with using NULL, but in some extreme scenarios, it may cause matching errors. for example:

void f(int arg)
{
    
    
	cout << "void f(int arg)" << endl;
}
void f(int* arg)
{
    
    
	cout << "void f(int* arg)" << endl;
}
int main()
{
    
    
	f(NULL);    //void f(int arg)
	f(nullptr); //void f(int* arg)
	return 0;
}

The meanings of NULL and nullptr are both null pointers, so when calling the function here, you must hope to match the overloaded function whose parameter type is int*, but in the end, because NULL is essentially a literal value of 0, NULL matches the parameter as The overloaded function of int type, so it is generally recommended to use nullptr in C++.

Four. STL update

1. STL new container

Four new containers have been added in C++11, namely array, forward_list, unordered_map and unordered_set.

array container

The essence of an array container is a static array, that is, an array of fixed size.

The array container has two template parameters, the first template parameter represents the storage type, and the second template parameter is a non-type template parameter, representing the number of elements that can be stored in the array. for example:

int main()
{
    
    
	array<int, 10> a1;   //定义一个可存储10个int类型元素的array容器
	array<double, 5> a2; //定义一个可存储5个double类型元素的array容器
	return 0;
}

The array container is compared with a normal array:

  • Like ordinary arrays, the array container supports accessing elements with specified subscripts through [], and also supports using range for to traverse array elements, and the size of the array cannot be changed after creation.
  • The difference between an array container and an ordinary array is that the array container encapsulates the array with a class, and an out-of-bounds check is performed when accessing elements in the array container . When using [] to access elements, use assertion checking, and when calling at member functions to access elements, use throwing exception checking.
  • For ordinary arrays, the out-of-bounds check is generally only performed when the array is written. If the read operation is only out-of-bounds, an error may not be reported.

However, the difference between the array container and other containers is that the objects of the array container are created on the stack, so the array container is not suitable for defining a large array.

forward_list container

The forward_list container is essentially a singly linked list.

forward_list is rarely used for the following reasons:

  • forward_list only supports head-plug deletion, not tail-plug deletion, because the single-linked list needs to find the tail first when performing tail-plug deletion, and the time complexity is O(N).
  • The insertion function provided by forward_list is called insert_after, which is to insert an element after the specified element, unlike other containers that insert an element in front of the specified element, because if the single linked list wants to insert an element in front of the specified element, it needs to traverse The linked list finds the previous element of this element, and the time complexity is O(N).
  • The deletion function provided by forward_list is called erase_after, which is to delete an element after the specified element, because if the single linked list wants to delete the specified element, it needs to traverse the linked list to find the previous element of the specified element, and the time complexity is O(N).

Therefore, in general, if we want to use a linked list, we still choose to use a list container.

unordered_map and unordered_set containers

The bottom layer uses hash tables.

These two containers are very useful, and the blogger's other blogs have introduced these two containers in detail.

2. String conversion function

C++11 provides functions for converting between various built-in types and strings, such as to_string, stoi, stol, stod and other functions.

Built-in types converted to string

The to_string function is uniformly called to convert the built-in type to the string type, because the to_string function overloads the corresponding processing functions for various built-in types.

insert image description here

string is converted to a built-in type

If you want to convert the string type to a built-in type, just call the corresponding conversion function.

insert image description here

3. Some new methods in the container

C++11 adds some new methods to each container, such as:

  • A constructor that takes an initializer_list as an argument is provided to support list initialization.
  • Provides cbegin and cend methods for returning const iterators.
  • Provides the emplace series of methods, and overloads an rvalue reference version of the insertion function based on the original insertion method of the container to improve the efficiency of inserting elements into the container.

Let me explain : The emplace series methods and the new insertion function improve the efficiency of container insertion, involving mechanisms such as rvalue references, move semantics, and variable parameters of templates in C++11. Bloggers will update their blogs in succession.


This is the end of this article, the code text is not easy, please support me a lot! ! !

Guess you like

Origin blog.csdn.net/weixin_67401157/article/details/132123466