[笔记]C++并发编程实战 《二》线程管理

news2025/2/28 20:21:06

文章目录

  • 前言
  • 第2章 线程管理
  • 2.1 线程管理的基础
    • 2.1.1 启动线程
    • 2.1.2 等待线程完成
    • 2.1.3 特殊情况下的等待
    • 2.1.4 后台运行线程
    • 2.2 向线程函数传递参数


前言

第2章 线程管理

本章主要内容

  • 启动新线程
  • 等待线程与分离线程
  • 线程唯一标识符

好的!看来你已经决定使用多线程了。先做点什么呢?
启动线程、
结束线程,
还是如何监管线程?

C++标准库中只需要管理 std::thread 关联的线程,无需把注意力放在其他方面。不过,标准库太灵活,所以管理起来不会太容易。

本章将从基本的开始:启动一个线程,等待这个线程结束,或放在后台运行。再看看怎么给已经启动的线程函数传递参数,以及怎么将一个线程的所有权从当前 std::thread 对象移交给另一个。最后,再来确定线程数,以及识别特殊线程。

2.1 线程管理的基础

每个程序至少有一个线程:

  • 执行main()函数的线程,其余线程有其各自的入口函数。
  • 线程与原始线程(以main()为入口函数的线程)同时运行。如同main()函数执行完会退出一样,当线程执行完入口函数后,线程也会退出。
  • 在为一个线程创建了一个 std::thread 对象后,需要等待这个线程结束;不过,线程需要先进行启动。下面就来启动线程。

2.1.1 启动线程

第1章中,线程在 std::thread 对象创建(为线程指定任务)时启动。

最简单的情况下,任务也会很简单,通常是无参数无返回的函数。

这种函数在其所属线程上运行,直到函数执行完毕,线程也就结束了。在一些极端情况下,线程运行时,任务中的函数对象需要通过某种通讯机制进行参数的传递,或者执行一系列独立操作;可以通过通讯机制传递信号,让线程停止。线程要做什么,以及什么时候启动,其实都无关紧要。

总之,使用C++线程库启动线程,可以归结为构造 std::thread 对象:

void do_some_work();
std::thread my_thread(do_some_work);

为了让编译器识别 std::thread 类,这个简单的例子也要包含 头文件。

如同大多数C++标准库一样, std::thread 可以用可调用类型构造,将带有函数调用符类型的实例传入 std::thread 类中,替换默认的构造函数。

class background_task
{
	public:
		void operator()() const
		{
			do_something();
			do_something_else();
		}
};
background_task f;
std::thread my_thread(f);

代码中,提供的函数对象会复制到新线程的存储空间当中,函数对象的执行和调用都在线程的内存空间中进行。函数对象的副本应与原始函数对象保持一致,否则得到的结果会与我们的期望不同。

有件事需要注意,当把函数对象传入到线程构造函数中时,需要避免“最令人头痛的语法解析”(C++’s most vexing parse, 中文简介)。如果你传递了一个临时变量,而不是一个命名的变量;C++编译器会将其解析为函数声明,而不是类型对象的定义。

例如:

std::thread my_thread(background_task());

这里相当与声明了一个名为my_thread的函数,这个函数带有一个参数(函数指针指向没有参数并返回background_task对象的函数),返回一个std::thread 对象的函数,而非启动了一个线程。

使用在前面命名函数对象的方式,或使用多组括号①,或使用新统一的初始化语法②,可以避免这个问题。

如下所示:

std::thread my_thread((background_task())); // 1
std::thread my_thread{background_task()}; // 2

使用lambda表达式也能避免这个问题。lambda表达式是C++11的一个新特性,它允许使用一个可以捕获局部变量的局部函数(可以避免传递参数,参见2.2节)。想要具体的了解lambda表达式,可以阅读附录A的A.5节。

之前的例子可以改写为lambda表达式的类型:

std::thread my_thread([]{
	do_something();
	do_something_else();
});

启动了线程,你需要明确是要等待线程结束(加入式——参见2.1.2节),还是让其自主运行(分离式——参见2.1.3节)。如果 std::thread 对象销毁之前还没有做出决定,程序就会终止( std::thread 的析构函数会调用 std::terminate() )。因此,即便是有异常存在,也需要确保线程能够正确的加入(joined)分离(detached)。2.1.3节中,会介绍对应的方法来处理这两种情况。需要注意的是,必须在 std::thread 对象销毁之前做出决定,否则你的程序将会终止( std::thread 的析构函数会调用 std::terminate() ,这时再去决定会触发相应异常)。

如果不等待线程,就必须保证线程结束之前,可访问的数据得有效性。这不是一个新问题——单线程代码中,对象销毁之后再去访问,也会产生未定义行为——不过,线程的生命周期增加了这个问题发生的几率。

这种情况很可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用。下面的清单中就展示了这样的一种情况。

清单2.1 函数已经返回,线程依旧访问局部变量

struct func
{
	int& i;
	func(int& i_) : i(i_) {}
	void operator() ()
	{
		for (unsigned j=0 ; j<1000000 ; ++j)
		{
			do_something(i); // 1 潜在访问隐患:悬空引用
		}
	}
};
void oops()
{
	int some_local_state=0;
	func my_func(some_local_state);
	std::thread my_thread(my_func);
	my_thread.detach(); // 2 不等待线程结束
} // 3 新线程可能还在运行

这个例子中,已经决定不等待线程结束(使用了detach() ② ),所以当oops()函数执行完成时③,新线程中的函数可能还在运行。如果线程还在运行,它就会去调用do_something(i)函数①,这时就会访问已经销毁的变量。如同一个单线程程序——允许在函数完成后继续持有局部变量的指针或引用;当然,这从来就不是一个好主意——这种情况发生时,错误并不明显,会使多线程更容易出错。运行顺序参考表2.1。

表2.1 分离线程在局部变量销毁后,仍对该变量进行访问
在这里插入图片描述
处理这种情况的常规方法:

  • 使线程函数的功能齐全,将数据复制到线程中,而非复制到共享数据中。如果使用一个可调用的对象作为线程函数,这个对象就会复制到线程中,而后原始对象就会立即销毁。但对于对象中包含的指针和引用还需谨慎,例如清单2.1所示。使用一个能访问局部变量的函数去创建线程是一个糟糕的主意(除非十分确定线程会在函数完成前结束)。

此外,可以通过join()函数来确保线程在函数完成前结束。

2.1.2 等待线程完成

如果需要等待线程,相关的 std::thread 实例需要使用join()。清单2.1中,将 my_thread.detach() 替换为 my_thread.join() ,就可以确保局部变量在线程完成后,才被销毁。在这种情况下,因为原始线程在其生命周期中并没有做什么事,使得用一个独立的线
程去执行函数变得收益甚微,但在实际编程中,原始线程要么有自己的工作要做;要么会启动多个子线程来做一些有用的工作,并等待这些线程结束。

join()是简单粗暴的等待线程完成或不等待。当你需要对等待中的线程有更灵活的控制时,比如,看一下某个线程是否结束,或者只等待一段时间(超过时间就判定为超时)。想要做到这些,你需要使用其他机制来完成,比如条件变量和期待(futures),相关的讨论将会在第4章继续。调用join()的行为,还清理了线程相关的存储部分,这样 std::thread 对象将不再与已经完成的线程有任何关联。这意味着,只能对一个线程使用一次join();一旦已经使用过join(), std::thread 对象就不能再次加入了,当对其使用joinable()时,将返回false。

2.1.3 特殊情况下的等待

如前所述,需要对一个还未销毁的 std::thread 对象使用join()或detach()。如果想要分离一个线程,可以在线程启动后,直接使用detach()进行分离。如果打算等待对应线程,则需要细心挑选调用join()的位置。当在线程运行之后产生异常,在join()调用之前抛出,就意味着这次调用会被跳过。

避免应用被抛出的异常所终止,就需要作出一个决定。通常,当倾向于在无异常的情况下使用join()时,需要在异常处理过程中调用join(),从而避免生命周期的问题。下面的程序清单是一个例子。

清单 2.2 等待线程完成

struct func; // 定义在清单2.1中
void f()
{
	int some_local_state=0;
	func my_func(some_local_state);
	std::thread t(my_func);
	try
	{
		do_something_in_current_thread();
	}
	catch(...)
	{
		t.join(); // 1
	throw;
	}
	t.join(); // 2
}

清单2.2中的代码使用了 try/catch 块确保访问本地状态的线程退出后,函数才结束。当函数正常退出时,会执行到②处;当函数执行过程中抛出异常,程序会执行到①处。 try/catch 块能轻易的捕获轻量级错误,所以这种情况,并非放之四海而皆准。如需确保线程在函数之前结束——查看是否因为线程函数使用了局部变量的引用,以及其他原因——而后再确定一下程序可能会退出的途径,无论正常与否,可以提供一个简洁的机制,来做解决这个问题。

一种方式是使用“资源获取即初始化方式”(RAII,Resource Acquisition Is Initialization),并且提供一个类,在析构函数中使用join(),如同下面清单中的代码。看它如何简化f()函数。

清单 2.3 使用RAII等待线程完成

class thread_guard
{
std::thread& t;
public:
	explicit thread_guard(std::thread& t_):
	t(t_)
	{}
	~thread_guard()
	{
		if(t.joinable()) // 1
		{
			t.join(); // 2
		}
	}

	thread_guard(thread_guard const&)=delete; // 3
	
	thread_guard& operator=(thread_guard const&)=delete;
};
struct func; // 定义在清单2.1中
void f()
{
	int some_local_state=0;
	func my_func(some_local_state);
	std::thread t(my_func);
	thread_guard g(t);
	do_something_in_current_thread();
} // 4

当线程执行到④处时,局部对象就要被逆序销毁了。因此,thread_guard对象g是第一个被销毁的,这时线程在析构函数中被加入②到原始线程中。即使do_something_in_current_thread抛出一个异常,这个销毁依旧会发生。

在thread_guard的析构函数的测试中,首先判断线程是否已加入①,如果没有会调用join()②进行加入。这很重要,因为join()只能对给定的对象调用一次,所以对给已加入的线程再次进行加入操作时,将会导致错误。

拷贝构造函数和拷贝赋值操作被标记为 =delete ③,是为了不让编译器自动生成它们。直接对一个对象进行拷贝或赋值是危险的,因为这可能会弄丢已经加入的线程。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。想要了解删除函数的更多知识,请参阅附录A的A.2节。

如果不想等待线程结束,可以分离_(_detaching)线程,从而避免异常安全(exception-safety)
问题。不过,这就打破了线程与 std::thread 对象的联系,即使线程仍然在后台运行着,分离
操作也能确保 std::terminate() 在 std::thread 对象销毁才被调用。

2.1.4 后台运行线程

使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有 std::thread 对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。不过C++运行库保证,当线程退出时,相关资源的能够正确回收,后台线程的归属和控制C++运行库都会处理。

通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化。另一方面,分离线程的另一方面只能确定线程什么时候结束,发后即忘(fire and forget)的任务就使用到线程的这种方式。

如2.1.2节所示,调用 std::thread 成员函数detach()来分离一个线程。之后,相应的 std::thread 对象就与实际执行的线程无关了,并且这个线程也无法加入:

std::thread t(do_background_work);
t.detach();
assert(!t.joinable());

为了从 std::thread 对象中分离线程(前提是有可进行分离的线程),不能对没有执行线程的 std::thread 对象使用detach(),也是join()的使用条件,并且要用同样的方式进行检查——当 std::thread 对象使用t.joinable()返回的是true,就可以使用t.detach()。
试想如何能让一个文字处理应用同时编辑多个文档。无论是用户界面,还是在内部应用内部进行,都有很多的解决方法。虽然,这些窗口看起来是完全独立的,每个窗口都有自己独立的菜单选项,但他们却运行在同一个应用实例中。一种内部处理方式是,让每个文档处理窗口拥有自己的线程;每个线程运行同样的的代码,并隔离不同窗口处理的数据。如此这般,打开一个文档就要启动一个新线程。因为是对独立的文档进行操作,所以没有必要等待其他线程完成。因此,这里就可以让文档处理窗口运行在分离的线程上。

下面代码简要的展示了这种方法:

清单2.4 使用分离线程去处理其他文档

void edit_document(std::string const &filename)
{
    open_document_and_display_gui(filename);
    while (!done_editing())
    {
        user_command cmd = get_user_input();
        if (cmd.type == open_new_document)
        {
            std::string const new_name = get_filename_from_user();
            std::thread t(edit_document, new_name); // 1
            t.detach();                             // 2
        }
        else
        {
            process_user_input(cmd);
        }
    }
}

如果用户选择打开一个新文档,需要启动一个新线程去打开新文档①,并分离线程②。与当前线程做出的操作一样,新线程只不过是打开另一个文件而已。所以,edit_document函数可以复用,通过传参的形式打开新的文件。这个例子也展示了传参启动线程的方法:不仅可以向 std::thread 构造函数①传递函数名,还可以传递函数所需的参数(实参)。C++线程库的方式也不是很复杂。当然,也有其他方法完成这项功能,比如:使用一个带有数据成员的成员函数,代替一个需要传参的普通函数。

2.2 向线程函数传递参数

清单2.4中,向 std::thread 构造函数中的可调用对象,或函数传递一个参数很简单。需要注意的是,默认参数要拷贝到线程独立内存中,即使参数是引用的形式,也可以在新线程中进行访问。再来看一个例子:

void f(int i, std::string const& s);
std::thread t(f, 3, "hello");

代码创建了一个调用f(3, “hello”)的线程。注意,函数f需要一个 std::string 对象作为第二个参数,但这里使用的是字符串的字面值,也就是 char const * 类型。之后,在线程的上下文中完成字面值向 std::string 对象的转化。需要特别要注意,当指向动态变量的指针作为参数传递给线程的情况,代码如下:

void f(int i, std::string const &s);
void oops(int some_param)
{
    char buffer[1024]; // 1
    sprintf(buffer, "%i", some_param);
    std::thread t(f, 3, buffer); // 2
    t.detach();
}

这种情况下,buffer①是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线程中②。并且,函数有很有可能会在字面值转化成 std::string 对象之前崩溃(oops),从而导致一些未定义的行为。并且想要依赖隐式转换将字面值转换为函数期待的 std::string 对象,但因 std::thread 的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字
面值。

解决方案就是在传递到 std::thread 构造函数之前就将字面值转化为 std::string 对象:

void f(int i, std::string const &s);
void not_oops(int some_param)
{
    char buffer[1024];
    sprintf(buffer, "%i", some_param);
    std::thread t(f, 3, std::string(buffer)); // 使用std::string,避免悬垂指针
    t.detach();
}

还可能遇到相反的情况:期望传递一个非常量引用(但这不会被编译),但整个对象被复制了。

你可能会尝试使用线程更新一个引用传递的数据结构,比如:

void update_data_for_widget(widget_id w, widget_data &data); // 1
void oops_again(widget_id w)
{
    widget_data data;
    std::thread t(update_data_for_widget, w, data); // 2
    display_status();
    t.join();
    process_widget_data(data);
}

虽然update_data_for_widget①的第二个参数期待传入一个引用,但是 std::thread 的构造函数②并不知晓;构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量。不过,在代码会将参数以右值的方式进行拷贝传递,这是为了照顾到那些只能进行移动的类型,而后会以右值为参数调用update_data_for_widget。因为函数期望的是一个非常量引用作为参数,而非一个右值作为参数,所以会在编译时出错。对于熟悉 std::bind 的开发者来说,问题的解决办法是显而易见的:可以使用 std::ref 将参数转换成引用的形式,从而可将线程的调用改为以下形式:

std::thread t(update_data_for_widget,w,std::ref(data));

在这之后,update_data_for_widget就会接收到一个data变量的引用,而非一个data变量拷贝的引用,这样代码就能顺利的通过编译。

如果你熟悉 std::bind ,就应该不会对以上述传参的形式感到奇怪,因为 std::thread 构造函数和 std::bind 的操作都在标准库中定义好了,可以传递一个成员函数指针作为线程函数,并提供一个合适的对象指针作为第一个参数:

class X
{
public:
	void do_lengthy_work();
};

X my_x;
std::thread t(&X::do_lengthy_work,&my_x); // 1

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

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

相关文章

使用压缩包安装jdk多版本并能领过切换

使用压缩包安装jdk多版本并能领过切换 1.下载2.解压包到指定位置3.使用pdate-alternatives 进行版本切换管理3.1. jdk173.2. jdk1.8 3.切换版本4.解决JAVA_HOME环境变量识别的问题 1.下载 官网的下载地址&#xff1a; 下载地址&#xff1a; jdk17: jdk1.8在当前页面的下面: …

基于差分进化算法的微电网调度研究(Matlab代码实现)​

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MAC突然打不开Notion,你遇到过这个问题吗?

目录 解决办法 为什么Notion会突然打不开呢&#xff1f; Notion是一款适合记录/规划的应用&#xff0c;而且页面简洁&#xff0c;模板强大&#xff0c;深得大家喜爱。我也经常在Notion上制定计划、记录学习笔记等。不过&#xff0c;今天突然打不开了&#xff0c;网页版、本地…

基于SpringBoot的生鲜管理系统的设计与实现

背景 困扰交易市场的许多问题当中,生鲜交易管理一定是交易市场不敢忽视的一块。但是管理好生鲜交易又面临很多麻烦需要解决,例如有几个方面:第一,生鲜市场往往人数都比较多,如何保证能够管理到每一个商家,如何在工作琐碎,记录繁多的情况下将生鲜交易的当前情况反应给领导相关部…

【大数据】Hadoop高可用集群搭建

知识目录 一、写在前面&#x1f495;二、Zookeeper安装✨三、Hadoop配置✨四、Hadoop HA自动模式✨五、HA脚本分享✨七、结语&#x1f495; 一、写在前面&#x1f495; 大家好&#xff01;这篇文章是我在搭建Hdfs的HA(高可用)时写下的详细笔记与感想&#xff0c;希望能帮助到大…

分布式调度XXL-JOB

分布式调度XXL-JOB 1.概述 1.1什么是任务调度 比如: 某电商平台需要每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券某银行系统需要在信用卡到期还款日的前三天进行短信提醒某财务系统需要在每天凌晨0:10分结算前一天的财务数据&#xff0c;统计汇总 以…

【图床】SpringBoot上传图片

知识目录 一、写在前面✨二、新建开源仓库✨2.1 新建仓库2.2 将仓库设置为开源2.3 生产私人令牌 三、代码实现&#x1f604;3.1 工具类3.2 上传图片 四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是初心&#xff0c;很高兴再次和大家见面。 今天跟大家分享…

【Unity】Animation Playable Bug、限制及解决方案汇总

【Unity】Animation Playable Bug、限制及解决方案汇总 先自荐一下我的PlayableGraph监控工具&#xff0c;比官方的Visualizer好用得多&#xff1a;https://github.com/SolarianZ/UnityPlayableGraphMonitorTool Bug 文中提及的各项Bug及解决方案的最小化测试工程可在此仓库下…

基于java的竞赛预约管理信息系统的设计与实现

背景 本系统提供给管理员对首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;项目分类管理&#xff0c;竞赛项目管理&#xff0c;赛事预约管理&#xff0c;系统管理等诸多功能进行管理。本系统对于用户输入的任何信息都进行了一定的验证&#xff0c;为管理员操作提高…

2023语言与智能技术竞赛开辟“双赛道”:寻找“全民测评官”,探索AI多模态能力...

开年以来&#xff0c;人工智能大语言模型&#xff08;LLM&#xff09;掀起新一轮全球科技竞赛&#xff0c;全球科技巨头打响“百模大战”。当大语言模型正深刻改变人类生产生活方式时&#xff0c;该如何进一步释放其潜能&#xff0c;成为业界关注的问题&#xff0c;也成为了202…

计网之HTTP请求的构造

文章目录 1. form表单请求构造2. ajax请求构造3. Postman的简单使用 常见的构造 HTTP 请求的方式有以下几种: 直接通过浏览器地址栏, 输入一个 URL 就可以构造出一个 GET 请求.直接点击收藏夹, 得到的也是 GET 请求.HTML 中的一些特殊标签也会触发 GET 请求, 如: link, script…

线程池ThreadPoolExecutor底层原理源码分析

线程池执行任务的具体流程是怎样的&#xff1f; ThreadPoolExecutor中提供了两种执行任务的方法&#xff1a; void execute(Runnable command)Future<?> submit(Runnable task) 实际上submit中最终还是调用的execute()方法&#xff0c;只不过会返回⼀个Future对象&am…

【项目实战】基于Vue3+TypeScript+Pinia的后台管理系统(coderwhy)

是基于Vue3、Pinia、VueRouter、Vite、ElementPlus、TypeScript、Echarts等后台系统 效果 项目地址 https://gitee.com/yangyang993/vue3_ts_cms_admin.git 超级管理员 登录 系统总览 侧边栏是动态形成的&#xff1a;动态路由加载。路由地址路径和菜单相匹配。 注意&…

CAN总线上的报文帧类型(N_PCI)

1.四种报文类型&#xff08;简洁明了&#xff09; 请记住对于CAN报文来说&#xff0c;可以通过识别每条CAN的首个字节来确定它的类型&#xff0c;4种&#xff1a; 单帧 0 首帧 1 连续帧 2 流控帧 3 2. 单帧&#xff08;SF&#xff0c;Single Frame&#xff09; 0X 单帧首个…

【Spring MVC】后端处理多文件上传如何保持最大的灵活性

文章目录 前言找文档Spring MVC 如何接收多文件formdata 接收其他传参结论 前言 有一个多文件上传的需求&#xff0c;翻看了Spring MVC的官网&#xff0c;总结一下&#xff1a; 如何根据版本号找官方文档后端如何声明Controller能保持较好的灵活性 找文档 spring-framework…

Hive基础概论

HIVE 基础 Hive基础什么是Hive&#xff1f;为什么用Hive&#xff1f;Hive与Hadoop的关系Hive架构、组件组件用户接口元数据存储Driver驱动程序&#xff0c;包括语法解析器、计划编译器。优化器、执行器执行引擎 数据模型Data ModelDataBase 数据库Tables 表Partitions 分区Buck…

LIMUML04数据标注(note)

数据标注的思维导图 目标&#xff1a;是提升模型还是提升标注&#xff0c;本小节讨论提升标准&#xff0c;提升模型后面介绍。 如果有足够标注&#xff1a;使用半监督学习 没有足够标注&#xff0c;有足够预算&#xff1a;请人标注 没有足够预算&#xff1a;使用弱监督学习 问题…

每日学术速递5.22

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Drag Your GAN: Interactive Point-based Manipulation on the Generative Image Manifold(SIGGRAPH 2023) 标题&#xff1a;拖动你的 GAN&#xff1a;生成图像流形上基于点的交互…

基础IO(总)

接口介绍 open&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); pathname&#xff1a;要打开或创建的目标文件 fla…

两数之和 C++实现(力扣题目1)

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出和为目标值 target 的那两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案…