【C++高阶】深入理解C++ I/O流:标准库中的隐藏宝石

news2024/9/23 9:56:03

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:C++ 特殊类
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀ C++ IO流

  • 📒1. C语言的输入与输出
  • 📚2. 流是什么
  • 📜3. C++ IO流
    • 🌞C++标准IO流
    • ⭐C++文件IO流
  • 📝4. stringstream
    • 🍁将数值类型数据格式化为字符串
    • 🍂字符串拼接
    • 🌸序列化和反序列化
  • 📖5. 总结


前言:在编程的世界中,输入与输出(I/O)是连接程序与现实世界的桥梁。无论是从键盘接收用户指令,还是将处理结果输出到屏幕或文件,I/O操作都是程序设计中不可或缺的一部分。对于C++这一强大而灵活的编程语言而言,其丰富的I/O流库更是为开发者提供了高效、灵活且易于使用的数据交换机制

C++的I/O流库不仅涵盖了基本的输入输出操作,如标准输入输出流(cin和cout)、文件流(ifstream和ofstream)以及字符串流(istringstream、ostringstream和stringstream),还提供了丰富的格式化选项和错误处理机制,使得开发者能够轻松应对各种复杂的I/O需求

然而,尽管C++ I/O流库功能强大,但其使用方式却相对复杂,尤其是对于初学者而言,往往难以快速掌握。因此,本文旨在通过深入浅出的方式,引导读者逐步了解C++ I/O流库的基本原理、使用方法以及高级特性。我们将从最基本的输入输出操作讲起,逐步深入到文件处理、字符串流操作、格式化输出等高级话题,帮助读者建立起对C++ I/O流库的全面认识

让我们一起走进C++ I/O流的世界,探索其背后的奥秘,共同提升编程技能,创造出更加高效、优雅的C++程序!


📒1. C语言的输入与输出

C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()

scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中
printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)

注意宽度输出和精度输出控制

C语言借助了相应的缓冲区来进行输入与输出,如图:
在这里插入图片描述

输入输出缓冲区:

  • 可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏
    蔽这部分的差异,可以很容易写出可移植的程序
  • 可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这
    部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”

注意事项:

  • 在使用scanf()时,务必检查其返回值以确保成功读取了预期数量的输入项
  • 格式化字符串中的格式说明符应与输入数据的类型严格匹配

📚2. 流是什么

“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位可以是bit,byte,packet )的抽象描述

C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设
备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”

C++中的流(Streams)是一种抽象的概念,用于表示数据序列的源或目标。它们提供了一种统一的方法来执行输入/输出操作,无论是从文件、内存缓冲区、控制台或其他输入输出设备读取或写入数据。流的概念使得C++的输入输出操作变得既灵活又强大

流的特征:有序连续、具有方向性

  • 为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能

📜3. C++ IO流

C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类

在这里插入图片描述


🌞C++标准IO流

C++标准IO流(Standard Input/Output Streams)是C++标准库中的一部分,它们提供了一套丰富的类和函数,用于处理标准输入输出操作,如从控制台读取数据或向控制台输出数据。这些流是面向对象的,并且基于继承体系,使得它们能够灵活地处理各种输入输出任务

  • std::fstream:同时继承自std::istream和std::ostream,因此支持同时读写文件

C++标准库提供了4个全局流对象cin、cout、cerr、clog

  • 使用cout进行标准输出,即数据从内存流向控制台(显示器)
  • 使用cin进行标准输入即数据通过键盘输入到程序中
  • 同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出

在使用时候必须要包含文件并引入std标准命名空间

注意事项:

  • cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据
  • 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对
    应位置位(置1),程序继续
  • 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输
    入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有
    空格
    。回车符也无法读入
  • cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了 cin文档 cout文档
  • 对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载

调用的是operator>>,返回值是istream类型的对象,那么这里可以做逻辑条件值,源自于istream的对象又调用了operator booloperator bool调用时如果接收流失败,或者有结束标志,则返回false

在这里插入图片描述
代码示例 (C++):

// 日期类
class Date
{
	// 友元
	friend ostream& operator << (ostream& out, const Date& d);
	friend istream& operator >> (istream& in, Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	operator bool()
	{
		// 假设输入_year为0,则结束
		if (_year == 0)
			return false;
		else
			return true;
	}
private:
	int _year;
	int _month;
	int _day;
};

istream& operator >> (istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

ostream& operator << (ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}

// C++ IO流,使用面向对象+运算符重载的方式
// 能更好的兼容自定义类型,流插入和流提取
int main()
{
	// 自动识别类型的本质--函数重载
	// 内置类型可以直接使用--因为库里面ostream类型已经实现了
	int i = 520;
	double j = 13.14;
	cout << i << endl;
	cout << j << endl;

	// 自定义类型则需要我们自己重载<< 和 >>
	Date d(2024, 9, 11);
	cout << d;
	while (d)
	{
		cin >> d;
		cout << d;
	}
	return 0;
}

⭐C++文件IO流

C++文件IO流(File Input/Output Streams)是C++标准库中的一部分,用于处理文件的读写操作。这些流封装了文件作为数据的来源或目标,使得文件的读写变得既方便又灵活。C++通过< fstream >头文件提供了文件IO流的相关类和函数

C++根据文件内容的数据格式分为二进制文件和文本文件

主要类

  • std::ifstream:继承自std::istream,用于从文件读取数据
  • std::ofstream:继承自std::ostream,用于向文件写入数据
  • std::fstream:同时继承自std::istream和std::ostream,因此支持同时读写文件

采用文件流对象操作文件的一般步骤:

    1. 定义一个文件流对象
    1. 使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系
    1. 使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写
    1. 关闭文件

二进制读写代码示例 (C++):

// 文件流对象
struct ServerInfo
{
	// 二进制读写时,尽量避免使用容器
	string _address;
	//char _address[32];
	int _port;
	Date _date;
};

// 二进制读写
class BinIO
{
public:
	BinIO(const char* filename = "info.bin")
		:_filename(filename)
	{}

	void Write(const ServerInfo& winfo)
	{
		ofstream ofs(_filename, ofstream::out | ofstream::binary);
		ofs.write((const char*)&winfo, sizeof(winfo));
	}

	void Read(ServerInfo& rinfo)
	{
		ifstream ifs(_filename, ifstream::in | ifstream::binary);
		ifs.read((char*)&rinfo, sizeof(rinfo));
	}

private:
	string _filename = "info.bin"; // 配置文件
};

int main()
{
	ServerInfo winfo = { "10.0.2.15", 22, {2024, 9, 12 } };

	BinIO bin;
	bin.Write(winfo);

	ServerInfo info;
	bin.Read(info);

	cout << info._address << endl;
	cout << info._port << endl;
	cout << info._date << endl;

	return 0;
}

注意:二进制读写时,尽量避免使用容器,容器中存放的指针可能会在读取文件时,释放变成野指针


文件读写代码示例 (C++):

// 文件流对象
struct ServerInfo
{
	string _address;
	//char _address[32];
	int _port;
	Date _date;
};

// 文件读写
class TestIO
{
public:
	TestIO(const char* filename = "info.txt")
		:_filename(filename)
	{}

	void Write(const ServerInfo& winfo)
	{
		ofstream ofs(_filename);
		ofs << winfo._address << endl;
		ofs << winfo._port << endl;;
		ofs << winfo._date << endl;;
	}

	void Read(ServerInfo& rinfo)
	{
		ifstream ifs(_filename);
		ifs >> rinfo._address;
		ifs >> rinfo._port;
		ifs >> rinfo._date;
	}

private:
	string _filename = "info.txt"; // 配置文件
};

int main()
{
	ServerInfo winfo = { "10.0.2.15", 22, {2024, 9, 12 } };
	
	TestIO test;
	test.Write(winfo);
	
	ServerInfo info;
	test.Read(info);
	
	cout << info._address << endl;
	cout << info._port << endl;
	cout << info._date << endl;
	
	return 0;
}

📝4. stringstream

stringstream 是 C++ 标准库中的一个非常有用的类,它属于 < sstream > 头文件。stringstream 是一种输入/输出流(I/O stream),用于在内存中执行字符串的输入输出操作,类似于 cin 和 cout,但是它是针对字符串的。stringstream 可以被用来进行字符串的格式化、解析和转换,而不需要通过文件或控制台

在程序中如果想要使用stringstream,必须要包含头文件。在该头文件下,标准库三个类:
istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作


🍁将数值类型数据格式化为字符串

C语言中,如果想要将一个整形变量的数据转化为字符串格式:

  • 使用itoa()函数 (C++中为 _itoa())
  • 使用sprintf()函数

但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,
而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃

代码示例 (C++):

int main()
{
	int n = 123456789;

	char s1[32];
	_itoa(n, s1, 10);

	char s2[32];
	sprintf(s2, "%d", n);

	char s3[32];
	sprintf(s3, "%f", n);

	return 0;
}

在C++中,可以使用stringstream类对象来避开此问题

代码示例 (C++):

#include<sstream>
int main()
{
	int a = 12345678;
	string sa;
	// 将一个整形变量转化为字符串,存储到string类对象中
	stringstream s;
	s << a;
	s >> sa;
	cout << sa << endl;

	s.str("");
	s.clear(); // 清空s, 不清空会转化失败
	double d = 13.14;
	s << d;
	s >> sa;
	string sValue;
	sValue = s.str(); // str()方法:返回stringsteam中管理的string类型
	cout << sValue << endl;
	return 0;
}

注意:

  • 多次转换时,必须使用clear将上次转换状态清空掉
    stringstream s在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit,因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换,但是clear()不会将stringstreams底层字符串清空掉
  • s.str(""),将stringstream底层管理string对象设置成"",否则多次转换时,会将结果全部累积在底层string对象中

🍂字符串拼接

关于字符串拼接,我们之前也了解过,两种常见方式:

  • std::string类中的append()函数
  • +=操作符

代码示例 (C++):

int main()
{
	string s1;
	s1 += "hello";
	s1 += " ";
	s1 += "world";
	cout << s1 << endl;

	string s2;
	s2.append("hello");
	s2.append(" ");
	s2.append("world");
	cout << s2 << endl;
	
	return 0;
}

现在我们也可以使用stringstreamostringstream来完成这一操作

代码示例 (C++):

#include<sstream>
int main()
{
	// stringstream
	stringstream sstream;
	// 将多个字符串放入 sstream 中
	sstream << "hello" << " " << "world";
	cout << sstream.str() << endl;
	// 清空 sstream
	sstream.str("");
	
	// ostringstream
	ostringstream oss;
	oss << "hello" << " " << "world";
	cout << oss.str() << endl;
	return 0;
}

🌸序列化和反序列化

序列化和反序列化是计算机科学中常用的两个概念,它们主要涉及到将数据结构或对象状态转换为可以存储或传输的格式(序列化),以及将这些格式恢复回原始的数据结构或对象状态(反序列化)。这两个过程在数据持久化、网络通信、对象深拷贝等场景中非常有用

我们通过网络这个字符串发送给对象,实际开发中,信息相对更复杂,一般会选用Json、xml等方式进行更好的支持

代码示例 (C++):

#include<sstream>

struct ChatInfo
{
	string _name; // 名字
	int _id; // id
	Date _date; // 时间
	string _msg; // 聊天信息
};
int main()
{
	// 结构信息序列化为字符串
	ChatInfo winfo = { "张宝龙", 1314520, { 2024, 9, 12 }, "晚上一起激战CS:GO"
	};

	ostringstream oss;
	oss << winfo._name << endl;
	oss << winfo._id << endl;
	oss << winfo._date << endl;
	oss << winfo._msg << endl;

	string str = oss.str();
	cout << str << endl << endl;

	// 网络输出
	// 字符串解析成结构信息
	ChatInfo rInfo;
	istringstream iss(str);
	iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;
	cout << "-------------------------------------------------------"<< endl;

	cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") ";
	cout << rInfo._date << endl;
	cout << rInfo._name << ":>" << rInfo._msg << endl;

	cout << "-------------------------------------------------------"<< endl;
	return 0;
}

注意事项:

  • stringstream实际是在其底层维护了一个string类型的对象用来保存结果
  • 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将stringstream底层的string对象清空
  • 可以使用s. str("")方法将底层string对象设置为""空字符串
  • 可以使用s.str()将让stringstream返回其底层的string对象
  • stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全

📖5. 总结

通过本次对C++ I/O流的学习之旅,我们一同探索了C++语言中这一强大而灵活的输入输出机制。从最初的标准输入输出流cin和cout,到文件流ifstream和ofstream的深入应用,再到字符串流istringstream、ostringstream和stringstream的灵活操作,我们见证了C++ I/O流库在数据处理和交换中的无限可能

学习过程中,我们不仅掌握了C++ I/O流库的基本用法,还学会了如何利用格式化选项来定制输出格式,使数据呈现更加符合需求的形式。同时,我们也深入了解了I/O操作中可能出现的异常和错误,并学习了相应的处理策略,以确保程序的健壮性和稳定性

最后,我希望这篇文章能够成为你学习C++ I/O流过程中的一盏明灯,为你指引方向,提供帮助。在未来的编程道路上,愿你能够运用所学,创造出更加精彩、高效的C++程序。再次感谢你的阅读,期待与你在更广阔的编程世界中相遇!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2157248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux中的动静态库

目录 前言 1.库的文件名 2.库的制作 对于动态库&#xff1a; 对于静态库&#xff1a; 3.库文件的查找 4.库的加载与使用 对于动态库&#xff1a; 对于静态库&#xff1a; 可执行程序分段&#xff1a; 程序的编址于动态库&#xff1a; 总结 前言 在刚开始学习C语言…

NASA:ASTER L1A 重建未处理仪器数据 V003

ASTER L1A 重建未处理仪器数据 V003 简介 先进星载热发射和反射辐射计&#xff08;ASTER&#xff09;1A 级&#xff08;AST_L1A&#xff09;包含重建的仪器数字编号&#xff08;DN&#xff09;&#xff0c;这些数字编号来自所获取的望远镜遥测数据流&#xff1a; 可见光和近红…

综合题第一题(地址表的填写)

题目 第一题的形式大概就是这样的&#xff0c;通常IP地址和子网掩码会给我们。 地址类别 补充知识 IP地址&#xff08;Internet Protocol Address&#xff09;是分配给网络中设备的数字标签&#xff0c;用于标识设备在网络中的位置。IP地址分为IPv4和IPv6两种版本&#xff0…

用Qt 对接‌百度AI平台

很多同学想利用几大模型AI弄点东西&#xff0c;但又不知道如何去介入&#xff1f;&#xff1f;最近帮同学弄点东西&#xff0c;刚好要接入到AI平台&#xff0c;就顺便研究了一下&#xff0c;并记录下来。 首先我们选择的 AI模型是百度的&#xff0c;然后注册&#xff0c;申请密…

vue实现数据栏无缝滚动实现方式-demo

效果 方式一 通过实现两个item 进行循环 <!--* Author: Jackie* Date: 2023-08-16 21:27:42* LastEditTime: 2023-08-16 21:41:51* LastEditors: Jackie* Description: scroll 水平滚动 - 效果基本满足需求* FilePath: /vue3-swiper-demo/src/components/scroll/Scroll12.…

开始场景的制作+气泡特效的添加

3D场景或2D场景的切换 1.新建项目时选择3D项目或2D项目 2.如下图操作&#xff1a; 开始前的固有流程 按照如下步骤进行操作&#xff0c;于步骤3中更改Company Name等属性&#xff1a; 本案例分辨率可以如下设置&#xff0c;有能力者可根据需要自行调整&#xff1a; 场景制作…

python是什么语言写的

Python是一种计算机程序设计语言。是一种面向对象的动态类型语言。现今Python语言很火&#xff0c;可有人提问&#xff0c;这么火的语言它的底层又是什么语言编写的呢&#xff1f; python是C语言编写的&#xff0c;它有很多包也是用C语言写的。 所以说&#xff0c;C语言还是很…

算法.图论-并查集

文章目录 1. 并查集介绍2. 并查集的实现2.1 实现逻辑2.2 isSameSet方法2.3 union方法(小挂大优化)2.4 find方法(路径压缩优化) 3. 并查集模板4. 并查集习题4.1 情侣牵手4.2 相似字符串组 1. 并查集介绍 定义&#xff1a; 并查集是一种树型的数据结构&#xff0c;用于处理一些不…

(学习记录)使用 STM32CubeMX——配置时钟(入门)

使用STM32CubeMX配置STM32F103C8T6时钟部分 选择芯片 ①&#xff1a;选择MCU型号 ①&#xff1a;这里使用英文输入法&#xff0c;输入你想要的芯片型号&#xff0c;我这里采用STM32F103C8T6 ②&#xff1a;这里能看到搜索后出来的芯片具体型号&#xff0c;选择匹配度最高的一个…

类和对象(2)(重点)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 类的默认成员函数 概念概述 默认成员函数就是用户没有显式实现&#xff0c;编译器会自…

【CSS in Depth 2 精译_034】5.4 Grid 网格布局的显式网格与隐式网格(下)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

ACM MM24 | Hi3D: 3D生成领域再突破!新视角生成和高分辨率生成双SOTA(复旦智象等)

文章链接&#xff1a;https://arxiv.org/pdf/2409.07452 Github 链接&#xff1a;https://github.com/yanghb22-fdu/Hi3D-Official 亮点直击 本文提出了高分辨率图像到3D模型&#xff08;Hi3D&#xff09;&#xff0c;这是一种基于视频扩散的新范式&#xff0c;将单个图像重新定…

计算机毕业设计python+spark知识图谱房价预测系统 房源推荐系统 房源数据分析 房源可视化 房源大数据大屏 大数据毕业设计 机器学习

《PythonSpark知识图谱房价预测系统》开题报告 一、研究背景与意义 随着城市化进程的加速和房地产市场的不断发展&#xff0c;房价成为影响人们生活质量的重要因素之一。准确预测房价不仅有助于政府制定科学的房地产政策&#xff0c;还能为开发商提供市场参考&#xff0c;同时…

NLP-transformer学习:(7)evaluate实践

NLP-transformer学习&#xff1a;&#xff08;7&#xff09;evaluate 使用方法 打好基础&#xff0c;为了后面学习走得更远。 本章节是单独的 NLP-transformer学习 章节&#xff0c;主要实践了evaluate。同时&#xff0c;最近将学习代码传到&#xff1a;https://github.com/Mex…

c++类与对象一

C类与对象(一) 面向对象初步认识 在c语言中&#xff0c;编程是面向过程编程&#xff0c;注重求解问题列出过程&#xff0c;然后调用函数求解问题。 在日常生活中。我们经常会遇到面向过程的问题 手洗衣服就是面向过程 而C是基于面向对象的。关注的是对象&#xff0c;把事情…

SpringSecurity -- 入门使用

文章目录 什么是 SpringSesurity &#xff1f;细节使用方法 什么是 SpringSesurity &#xff1f; 在我们的开发中&#xff0c;安全还是有些必要的 用 拦截器 和 过滤器 写代码还是比较麻烦。 SpringSecurity 是 SpringBoot 的底层安全默认选型。一般我们需要认证和授权&#xf…

【Finetune】(三)、transformers之P-Tuning微调

文章目录 0、P-Tuning基本原理1、代码实战1.1、导包1.2、加载数据集1.3、数据集预处理1.4、创建模型1.5、P-tuning*1.5.1、配置文件1.5.2、创建模型 1.6、配置训练参数1.7、创建训练器1.8、模型训练1.9、模型推理 0、P-Tuning基本原理 P-Tuning的基本思想是在prompt-tuning的基…

Spring Boot管理用户数据

目录 学习目标前言Thymeleaf 模板JSON 数据步骤 1: 创建 Spring Boot 项目使用 Spring Initializr 创建项目使用 IDE 创建项目 步骤 2: 添加依赖步骤 3: 创建 Controller步骤 4: 新建index页面步骤 5: 运行应用程序 表单提交步骤 1: 添加 Thymeleaf 依赖在 Maven 中添加依赖 步…

Vue3.3新特性defineModel

defineModel的使用: defineModel选项可以帮我们省去很多麻烦 不仅需要上述操作&#xff0c;还需要进行一定的配置&#xff1a; 在vite.config.js中进行配置 defineModel是一个宏&#xff0c;所以不需要从vue中import导入&#xff0c;直接使用就可以了。这个宏可以用来声明一个…

Java之线程篇六

目录 CAS CAS伪代码 CAS的应用 实现原子类 实现自旋锁 CAS的ABA问题 ABA问题导致BUG的例子 相关面试题 synchronized原理 synchronized特性 加锁过程 相关面试题 Callable 相关面试题 JUC的常见类 ReentrantLock ReentrantLock 和 synchronized 的区别: 原…