【C++】列表初始化、声明、范围for、array容器

列表初始化、声明、范围for、array容器

一、统一的列表初始化

1.1 使用{ }初始化

在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:

cpp 复制代码
struct Person
{
	string _name
	int _age;
};
int main()
{
	int arr1[] = { 1, 2, 3, 4, 5 };
	int arr2[5] = { 0 };
	Person p = { "张三", 21};
	return 0;
}

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

cpp 复制代码
struct Person
{
	string _name
	int _age;
};
int main()
{
    //单个元素
	int a = 1;
	int b = { 2 };
	int c{ 2 };
    //数组
	int arr1[]{ 1, 2, 3, 4, 5 };
	int arr2[5]{ 0 };
    //自定义结构体
	Person p = { "张三", 21};
    //调用new时,对每个对象初始化
	int* p1 = new int(1);
	int* p2 = new int[3]{ 1, 3, 4 };
	return 0;
}

创建对象时也可以使用列表初始化方式调用构造函数初始化。

cpp 复制代码
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(2024, 12, 17); // old style
	// C++11支持的列表初始化,这里会调用构造函数初始化
	Date d2 = { 2024, 12, 17 };//类似隐式类型转换 + 优化
	Date d3{ 2024, 12, 17 };
 
	Date* p1 = new Date(2024, 12, 17);
	Date* p2 = new Date[3]{ { 2024, 12, 17}, { 2024, 12, 18}, { 2024, 12, 19} };
	return 0;
}

1.2 initializer_list容器

c++11里添加了initializer_list容器,介绍文档链接:initializer_list的介绍

此容器提供的成员函数只有三个,还有一个构造函数:

cpp 复制代码
int main()
{
	initializer_list<double> lt = { 24, 17, 21 };
	initializer_list<double>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";//24 17 21
		it++;
	}
	cout << endl;
    
	for (auto e : lt)
	{
		cout << e << " ";//24 17 21
	}
	return 0;
}

该类型用于访问C++初始化列表中的值,该列表是 类型的元素列表。这种类型的对象由编译器从初始化列表声明自动构造,初始化列表声明是用大括号括起来的逗号分隔元素的列表:const T。常量的花括号列表会被编译器识别成initializer_list

cpp 复制代码
int main()
{
	// the type of il is an initializer_list
	auto il = { 10, 20, 30 };
	cout << typeid(il).name() << endl;//class std::initializer_list<int>
	return 0;
}

initializer_list的使用场景:

initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器增加initializer_list作为参数的构造函数,这样初始化容器对象时就变得方便了,也可以作为operator =的参数,这样就可以用大括号赋值:

cpp 复制代码
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
	vector<int> v1 = { 1, 2 ,3 ,4, 5 };
    // 使用大括号对容器赋值,{}调用构造函数构造一个vector对象,再赋值
    v1 = {10, 20, 30};
	vector<int> v2{ 1, 2, 3, 4, 5 };
	vector<Date> v3 = { { 2024, 12, 17}, { 2024, 12, 18}, { 2024, 12, 19} };
 
	//list
	list<int> lt1{ 1, 2, 3 };
 
	//set
	set<int> s1{ 3, 4, 5, 6, 3 };
 
	//map
	map<string, string> dict = { {"apple", "苹果" }, {"english", "英语" } };
	return 0;
}

initializer_list的使用示例:

对我们以前实现过的vector类型中的构造函数进行改动,可以使构造函数还能实现的再简单点,直接在初始化列表里把三个成员变量初始化,在遍历ls,复用push_back依次插入到vector即可:

cpp 复制代码
vector(initializer_list<T> il)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstoage(nullptr)
{
	for (auto e : il)
	{
		push_back(e);
	}
}

注意:

  • 最好增加一个以initializer_list作为参数的赋值运算符重载函数,以支持直接用列表对容器对象进行赋值,但实际也可以不增加。

如下:

cpp 复制代码
vector<int> v1 = { 1, 2 ,3 ,4, 5 };
// 使用大括号对容器赋值,{}调用构造函数构造一个vector对象,再赋值
v1 = {10, 20, 30};

对于第二行的赋值操作,涉及到了隐式类型转换,先使用{}调用构造函数构造一个vector对象,再赋值。

二、声明

c++11提供了多种简化声明的方式,尤其是在使用模板时。

2.1 auto关键字

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

cpp 复制代码
int main()
{
	int i = 10;
    //int*
	auto p = &i;
    
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
    
	//map<string, string>::iterator
	auto it = dict.begin();
    
	return 0;
}

2.2 decltype关键字

decltype 是 C++11 引入的一个关键字,用于在编译时推导表达式的类型。它根据给定表达式的类型来确定一个新类型,可以在模板编程和类型推导中非常有用

cpp 复制代码
// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
	decltype(t1 * t2) ret;
	cout << typeid(ret).name() << endl;//double
}
int main()
{
	const int x = 1;
	double y = 2.2;
	decltype(x * y) ret; // ret的类型是double
	decltype(&x) p; // p的类型是int const *
	cout << typeid(ret).name() << endl;//double
	cout << typeid(p).name() << endl;//int const *
	F(1, 'a');
	return 0;
}

下面来区分下typeid和decltype:

  • typeid(变量名).name():专门用来输出一个变量的类型,返回的是一个字符串。帮助我们观察此字符串的类型,不能用其去定义变量。
  • decltype:将变量的类型声明为表达式指定的类型,可以用其去定义变量。

2.3 nullptr关键字

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

cpp 复制代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endi

如果没有定义宏,如果在cplusplus里,NULL被定义成0。可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

cpp 复制代码
void print(int* a){
	cout << "int*" << endl;
}
void print(int a){
	cout << "int" << endl;
}

int main(){
	print(NULL); //int
	return 0;
}

程序本意是想通过print(NULL)调用指针版本的print(int *)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

三、范围for

范围for的底层就是被替换成了迭代器。

范围for的语法

若是在C++98中我们要遍历一个数组,可以按照以下方式:

cpp 复制代码
int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//将数组元素值全部乘以2
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		arr[i] *= 2;
	}
	//打印数组中的所有元素
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

以上方式也是C语言中所用的遍历数组的方式,但对于一个有范围的集合而言,循环是多余的,有时还容易犯错。

C++11中引入了基于范围的for循环,for循环后的括号由冒号分为两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。比如

cpp 复制代码
int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//将数组元素值全部乘以2
	for (auto& e : arr)
	{
		e *= 2;
	}
	//打印数组中的所有元素
	for (const auto e : arr)
	{
		cout << e << " "; // 2 4 5 8 10 12 14 16 18 20
	}
	cout << endl;
	return 0;
}

注意: 与普通循环类似,可用continue来结束本次循环,也可以用break来跳出整个循环。

范围for的使用条件

1. for循环迭代的范围必须是确定的

  • 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

2. 迭代的对象要支持++和==操作

  • 范围for本质上是由迭代器支持的,在代码编译的时候,编译器会自动将范围for替换为迭代器的形式。而由于在使用迭代器遍历时需要对对象进行++和操作,因此使用范围for的对象也需要支持++和操作。

四、array容器和forward_list容器

1、array容器

array就是一个静态数组,其有两个模板参数,第一个模板参数代表的是存储的数据类型,第二个是非类型模板参数,代表的是存储元素的个数:

cpp 复制代码
int main()
{
	array<int, 10> a1;
	array<double, 15> a2;
	return 0;
}

array和普通数组最大的区别在于对于越界访问的检查:

cpp 复制代码
int main()
{
	int a[10];
	cout << a[10] << endl;//越界不一定能检查出来
	array<int, 10> b;
	cout << b[10] << endl;//只要越界,一定能检查出来
	return 0;
}

总结:

  1. array容器的对象是建立在栈区的,不适合定义大数组
  2. array容器的设计可能是为了代替静态数组,因为array容器更安全,能够检查除越界的错误,而静态数组并不一定能够检查出来。

2、forward_list容器

forward_list容器本质就是一个单链表,相比list的区别在于forward_list节省了空间,实际使用上使用forward_list的比率还是比较低的,还是使用list来的方便。

容器中的一些新方法

如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的。

比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作。

相关推荐
爱吃涮毛肚的肥肥(暂时吃不了版)21 分钟前
Leetcode——链表:143.重排链表
数据结构·c++·后端·算法·leetcode·链表·职场和发展
Eiceblue1 小时前
在.NET用C#将Word文档转换为HTML格式
开发语言·vscode·c#·html·word·.net
步、步、为营1 小时前
C#局部函数 VS Lambda表达式
开发语言·windows·c#
Kevinyu_1 小时前
Java ArrayList
java·开发语言·windows
忆源1 小时前
Linux高级--3.3.1 C++ spdlog 开源异步日志方案
java·c++·开源
stormjun2 小时前
基于 Python 的深度学习的车俩特征分析系统,附源码
开发语言·python·深度学习·车辆特征分析·python 车辆特征分析
BinaryBardC2 小时前
Dart语言的字符串处理
开发语言·后端·golang
数据的世界012 小时前
C#表达式和运算符
开发语言·c#
一只小菜鸡2 小时前
python+django+Nacos实现配置动态更新-集中管理配置(实现mysql配置动态读取及动态更新)
开发语言·python·django
有梦想的咕噜2 小时前
Qt Quick 和 Qt Designer
开发语言·qt