C++ 11--》初始化

本问将提及C++11以及C++98的初始化相关的知识,以及关于基于右值引用演化到移动赋值,移动拷贝。 谈及初始化,在98以前以及c语言我们要知道关于内置类型: char,int ,short,数组等内置类型的初始化,以及他们处于不同区域初始化后的结果,以及初始化的重要性,其次就是自定义类型: 如string,vector等stl 容器他们做了初始化的优化以及我们如何理解,以及如果我们自己定义类如何保证初始化。

以及理解C++ 11以后引入的initializer_list ,使得C++11 具有动态初始化的,以及原来98以前为什么叫做支持静态初始化,以及编译器对{} 这样的花括号做了特殊处理。

右值引用的引用是为了节约内存开销,为什么它能做到,以及它如何做到,右值和左值又到底是什么呢

一. 初始化

1.1 无初始化的-->值情况?

提及静态初始化,你首先得明白有哪些类型: 自定义类型和内置类型。

char,int ,short,数组等内置类型的初始化,以及他们处于不同区域初始化后的结果,以及初始化的重要性,其次就是自定义类型: 如string,vector等stl 容器他们做了初始化的优化以及我们如何理解,以及如果我们自己定义类如何保证初始化。

我偏不初始化--- 内置类型

cpp 复制代码
// 内置类型: int bool char 数组等    自定义类型: string .vector 这些结构体或是类

int all_a; // 全局变量

int main() {

	int arr[18];
	int a;
	int* pa;
	printf("all_a:%d ,pa: %p, a:%d",all_a,pa,a);

	return 0;
}

:输出结果: 0,3123123214,312313123

总之当我们全局/静态的成员变量谈若我们不初始化,变量/数组 所有元素默认被初始化为0,

但是在栈上的类型,如果你不初始化是给垃圾值的,如上面的输出结果,这是很危险的,比如你在栈上定义的指针,不初始化,但是又不小心访问了,这是导致野指针的哈,像我的编译器已经很高级了,在编译前就已经报错了,你切记小心要是不小心运行时报错,哭都没得哭。

所以你在栈上定义的变量,哪怕就是一个整形 都最好写成 int a = 0;给他老老实实的初始化。 指针初始化你就老老实实的 int* ptr = nullptr; 这是基本的好习惯。

我偏不初始化--- 自定义类型

有人说到了现在我们已经是面向对象的时代了,我们如果在栈上或者全局创建的对象已经无需初始化了,因为自从我们定义那一刻起: 之后创建会自动调用内置类型赋值为0,以及自定义类型成员调用它的构造函数帮我们初始化。是的没错,自定义类型在98开始就可以你不需要主动初始化也是允许的,但是还是有必要了解在c语言阶段,倘若不初始化的情况:

这里是gcc 编译器 输出的结果如下,首先 我们的全局结构体输出的结果是一个全是0,其次对应这个sta_b的输出就有点难受了它输出的是一个 nil 它表示指名这是一个未初始化的值, 也就意味着在c语言阶段的结构体的初始化是分区域的,栈上垃圾值,全局变量会给你赋值为0.

1.2 programmer 良好的初始化习惯!

无论什么语言,无论在哪,作为一名好的程序猿良好的初始化习惯是必要的:

通常内置类型

如下的习惯 :

cpp 复制代码
   int a = 0;// 写个0吧

   char b = '0';   // 写个0吧

 int* pa = NULL; // nullptr C++11以后支持 是最好的,指针初始化为空吧!

初始化是非常必要的, 否则就会出现: 栈上的变量初始化为随机值,全局变量啊,等等

结构体对象你可以不初始化了吗? :

其实自从C++到了面向对象以后,结构体也差不多跟类一样,内部自带了默认成员函数,会帮你初始化 所以你不显示初始化,也没有关系 :

比如你的结构体A -------- A a ; 你这也也没有关系

但是c语言是没有的,c语言的结构体是一堆变量的结合体,依然保持栈变量随机值(垃圾值/未定义的值称呼都可以)

但是你如果想显示初始化的话,结构体通常可以用花括号,但是c++98也就支持到结构体花括号初始化和数组的,不支持类!

关于花括号{} C++98&&C++11

1. c++98的花括号初始化

在C++98的时候花括号主要有用于内置类型: 数组和结构体类型进行初始化,对于数组的初始化和结构体: 如下

cpp 复制代码
int main(){

 // 数组的初始化
 int arr[9] = {1,3,4,5,6};
 struct mytest t = {1,NULL};// 98之前没有nullptr NULL本质上是 0

 std::cout<<"数组: ";
 for(int i=0;i<9;i++){

   std::cout<<arr[i]<<" ";
 }
 std::cout<<"结构体的成员";
  std::cout<<t.a<<" 指针"<<t.pb<<" ";



  return 0;
}

采用的编译器和输出结果:

C++98 的new操作 不支持参数初始化

cpp 复制代码
int main(){

 // 数组的初始化
 int *pb = new int[3];
 for(int i=0;i<3;i++){
   printf("%d ",*pb++);
 }
 printf("\n");
  int *pa = new int[4]{1,2,4}; 

 for(int i=0;i<4;i++){
   printf("%d ",*pa++);
 }


  return 0;
}

输出结果: new的int数组结果都是0,下一个按道理应该是报错才对

那能动态初始化吗: 能!

cpp 复制代码
struct mytest{

  int a;
  int* pb;

};


int main(){

 // 数组的初始化
 char* arr[2] = {new char('c'),NULL};
 struct mytest t = {1,new int(2)};

 std::cout<<"数组: ";
 for(int i=0;i<2;i++){

   printf("%p",arr[i]);
 }
 std::cout<<std::endl;
 std::cout<<"结构体的成员";
  std::cout<<t.a<<" 指针"<<t.pb<<" ";



  return 0;
}
2. C++11的花括号初始化

C++11 花括号扩大了使用范围: 所以的内置类型 和自定义类型 都可以使用{}

基本内置类型:

cpp 复制代码
int main() {

	int a{ 2 };
	int b = { 3 };
	int arr1[5] = {1,2,3,4,5};
	int arr2[5]{1};
	// new也可以使用 
	int* parr = new int[4] {1,2,3,4};


	return 0;
}

output:

自定义类型:

拿着下面这段代码去调试一些,你会理解这个花括号自定义对象的初始化是类似于构造函数

它把Data(year,month,day) 构造函数的三个参数分别赋值了,也就是说这里的花括号帮你调用了构造函数以及把值一一赋给了构造函数的形参,结果可以参考下面的调试结果:

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(2022, 1, 1); // old style 调用函数的方式 
	// C++11支持的列表初始化,这里会调用构造函数初始化
	Date d2{ 2022, 1, 2 };
	Date d3 = { 2022, 1, 3 };




}

调试结果:

二. C++11 的 initializer_list

先别着急飞起来,先来了解一下这个类的基本使用,它使用起来很像一个封装了迭代器的,然后内置了一个模板类型T的数组;

2.1 构造函数 &&迭代器

构造函数 :

成员函数:

cpp 复制代码
// initializer_list example
#include<initializer_list>

int main() {

	// 这样的花括号会自动调用它的构造函数放入它内置的一个数组中
	std::initializer_list<int> myint_list{1,2,3,4,5}; 

	// 它也有自己的迭代器begin end 还有size 
	std::initializer_list<int>::iterator it;
	it = myint_list.begin();

	while (it != myint_list.end()) {
		std::cout << *it <<" ";
		it++;
	}

	return 0;
}

输出结果:

2.2 C++11 花括号与初始化列表

参考文档: initializer_list - C++ Reference

Initializer list

This type is used to access the values in a C++ initialization list, which is a list of elements of type const T.

Objects of this type are automatically constructed by the compiler from initialization list declarations, which is a list of comma-separated elements enclosed in braces:

|---|---------------------------------------------------------------------|---|
| | auto il = { 10, 20, 30 }; // the type of il is an initializer_list | |

Notice though that this template class is not implicitly defined and the header <initializer_list> shall be included to access it, even if the type is used implicitly.

initializer_list objects are automatically constructed as if an array of elements of type T was allocated, with each of the elements in the list being copy-initialized to its corresponding element in the array, using any necessary non-narrowing implicit conversions.

The initializer_list object refers to the elements of this array without containing them: copying an initializer_list object produces another object referring to the same underlying elements, not to new copies of them (reference semantics).

The lifetime of this temporary array is the same as the initializer_list object.

Constructors taking only one argument of this type are a special kind of constructor, called initializer-list constructor. Initializer-list constructors take precedence over other constructors when the initializer-list constructor syntax is used:

我的理解:

在C++ 11以后 {} 已经可以用于内置类型,自定义类型,特别是自定义类型可以直接调用它的构造函数来初始化这一点在前面C++11花括号的初始化已经提及了,读者可以在看看

关键是initializer_list 到底有什么用呢,你只需要理解编译器做了一个特殊处理

整个转换过程完全由编译器自动完成,无需开发者手动处理,步骤如下:

  1. 当编译器遇到 {1,2,3} 作为函数 / 构造函数参数(且目标参数是 initializer_list<T>)时:
    • 第一步:在内存中创建一个临时常量数组 (如 const int temp[3] = {1,2,3});
    • 第二步:构造 initializer_list<int> 对象,该对象保存 temp 的首地址和长度(仅两个指针大小);
    • 第三步:将这个 initializer_list 对象传递给构造函数 / 用initializer_list函数直接构造。

到这你就发现一个很有意思的事情本质上{} 会做一个处理就是先把数据转为initializer_list,然后去给构造函数的参数一一赋值,那为什么不直接用initializer_list的构造函数呢!

是的所以文档中这么说: 当有普通的构造函数和initializer_list的构造函数,后者的优先级更高,其实不仅仅是这样,既然编译器能把花括号转化为initializer_list 其实我们还可以借这个实现我们的赋值重载初始化列表 (使用起来就像花括号一样)

2.3 调试日期类实例理解编译器初始化列表行为

cpp 复制代码
#include<initializer_list>
#include<assert.h>
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;
	}
// 花括号的构造函数 优先调用这个
	Date(const initializer_list<int>&tmp) {
		if (tmp.size() < 3) {
			assert(false);
		}
		auto it = tmp.begin();
		_year = *it++;
		_month = *it++;
		_day= *it++;
	}
// 之后可以直接用花括号赋值
	Date& operator=(const initializer_list<int>& tmp) {
		if (tmp.size() < 3) {
			assert(false);
		}
		auto it = tmp.begin();
		_year = *it++;
		_month = *it++;
		_day = *it++;
		return *this;
	}



private:
	int _year;
	int _month;
	int _day;
	
};

int main() {

	Date d2{ 2022, 1, 2 };
	Date d3 = { 2022, 13, 3 };
	 d3 = { 2023, 22, 3 };
}

调试过程:

输出结果:

2.4 STL容器的实现initializer_list 构造函数和赋值重载爽感

vector:

list:

map:

unordered_map:

set/unordered_set:

嵌套的我就不继续举例啦,都是类似的:

标准容器的花括号的使用

参考如下代码便基本理解他们的使用了:

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <unordered_map>
using namespace std;

int main() {
    // ====================== 1. string ======================
    // initializer_list构造(字符列表初始化)
    string str{ 'h', 'e', 'l', 'l', 'o' };
    cout << "string 初始化: " << str << endl;
    // initializer_list赋值重载(直接重置内容)
    str = { 'w', 'o', 'r', 'l', 'd' };
    cout << "string 赋值后: " << str << "\n\n";

    // ====================== 2. vector(序列容器) ======================
    // initializer_list构造(一行初始化数值)
    vector<int> vec{ 10, 20, 30, 40, 50 };
    cout << "vector 初始化: ";
    for (int num : vec) cout << num << " ";
    // initializer_list赋值重载(一行替换所有元素)
    vec = { 100, 200, 300 };
    cout << "\nvector 赋值后: ";
    for (int num : vec) cout << num << " ";
    cout << "\n\n";

    // ====================== 3. list(链表容器) ======================
    // initializer_list构造
    list<double> lst{ 3.14, 1.618, 2.718 };
    cout << "list 初始化: ";
    for (double num : lst) cout << num << " ";
    // initializer_list赋值重载
    lst = { 0.618, 9.8, 5.2 };
    cout << "\nlist 赋值后: ";
    for (double num : lst) cout << num << " ";
    cout << "\n\n";

    // ====================== 4. map(有序键值对) ======================
    // initializer_list构造({键,值} 直接初始化)
    map<string, int> mp{
        {"苹果", 5},
        {"香蕉", 3},
        {"橙子", 7}
    };
    cout << "map 初始化: ";
    for (const auto& p : mp) cout << p.first << ":" << p.second << " ";
    // initializer_list赋值重载(一键替换所有键值对)
    mp = {
        {"葡萄", 9},
        {"梨", 4},
        {"芒果", 6}
    };
    cout << "\nmap 赋值后: ";
    for (const auto& p : mp) cout << p.first << ":" << p.second << " ";
    cout << "\n\n";

    // ====================== 5. unordered_map(无序哈希键值对) ======================
    // initializer_list构造
    unordered_map<int, string> umap{
        {1001, "张三"},
        {1002, "李四"},
        {1003, "王五"},
        {1003, "王的"}//这里会被丢弃 
    };
    cout << "unordered_map 初始化: ";
    for (const auto& p : umap) cout << p.first << ":" << p.second << " ";
    // initializer_list赋值重载(混合新增/修改)
    umap = {
        {1001, "张三"},   // 保留键,值不变
        {1004, "赵六"},   // 新增键值对
        {1005, "钱七"}    // 新增键值对
    };
    cout << "\nunordered_map 赋值后: ";
    for (const auto& p : umap) cout << p.first << ":" << p.second << " ";
    cout << endl;

    return 0;
}

set会去重本质上是内部实现的时候复用了set的insert: map也一样会根据key去重这是基本的实现都是类似的;

相关推荐
LiamTuc2 小时前
Java 接口定义变量
java·开发语言
昇腾CANN3 小时前
自定义算子开发系列:TilingKey模板化编程介绍
c++·mfc
赵得C3 小时前
软件设计师进阶知识点解析:分布式与数据应用考点精讲
java·开发语言·分布式·设计模式
oioihoii3 小时前
在MFC桌面应用中嵌入现代浏览器内核:原理、选型与实践全解析
c++·mfc
木心爱编程3 小时前
Qt C++ 串口通信+数据可视化:工业设备数据实时采集与界面显示
c++·qt·信息可视化
_OP_CHEN3 小时前
【从零开始的Qt开发指南】(九)Qt 常用控件之显示类控件(下):ProgressBar 与 CalendarWidget 实战进阶
开发语言·c++·qt·gui·前端开发·图形化界面开发·qt常用控件
oioihoii3 小时前
VS Code终端从入门到精通完全指南
开发语言
是一个Bug3 小时前
声明式事务
java·开发语言·面试
武藤一雄3 小时前
C#:深入浅出委托(Delegate/Func/Action/Predicate)
开发语言·后端·microsoft·微软·c#·.net