当前位置: 首页 > news >正文

C++——C++11线程库

目录

一,线程库简介

二,线程库简单使用

2.1 传函数指针

​编辑 2.2 传lamdba表达式

2.3 简单综合运用

2.4 线程函数参数

三,线程安全问题

3.1 为什么会有这个问题?

3.2 锁

3.2.1 互斥锁

3.2.2 递归锁

3.3 原子操作

3.3.1 原子操作变量

3.3.2 atomic模板

五,lock_guard和unique_lock

5.1 可能发生的情况

5.2 lock_guard

5.3 unique_lock

六,两个线程交替打印,一个打印奇数,一个打印偶数


一,线程库简介

在Linux中我们可以使用用户层的线程接口来实现线程操作,但是Linux下的接口无法在Windows下使用,因为Linux支持了POSIX线程标准,但是Windows没有支持,它搞了一套属于它自己的线程标准。所以在C++11之前,涉及到多线程问题的代码可移植性比较差

所以C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类得概念。

C++,Linux和Windows下都可以支持多线程程序 -- 条件编译
#ifdef _WIN32CreateThread...
#elsepthread_create...
#endif

下面是C++中常用的线程函数的介绍:

①thread():构造一个线程对象,没有关联任何线程函数,即没有启动任何线程

②thread(fn, args1, args2...):构造一个线程对象,并使其关联函数fn,args是fn线程函数的参数

③get_id:获取线程id

④join():类似malloc或new之后需要手动free或者delete,join被调用后会阻塞主先,当该线程结束后,主线程继续执行

⑤detach():一般在创建线程对象后马上调用,用于把创建出来的线程与线程对象分离开,分离后的线程变为后台线程,在这之后后台线程就与主线程无关

二,线程库简单使用

线程函数一般情况下可以按照三种方式提供:①函数指针  ②lamdba表达式 ③函数对象

2.1 传函数指针

先来个最简单的线程使用,如下代码:

void Func(int n, int num)
{for (int i = 0; i < n; i++){cout <<num<<":" << i << endl;}cout << endl;
}void main()
{thread t1(Func, 10, 1);thread t2(Func, 20, 2);t1.join();t2.join();
}

注:由于是多线程同时运行,所以打印结果会比较乱,属于正常情况!

 2.2 传lamdba表达式

我们还可以直接用lamdba表达式传给线程,如下代码:

void main()
{int n1, n2;cin >> n1 >> n2;thread t1([n1](int num){for (int i = 0; i < n1; i++){cout << num << ":" << i << endl;}cout << endl;}, 1);thread t2([n1](int num){for (int i = 0; i < n1; i++){cout << num << ":" << i << endl;}cout << endl;}, 2);t1.join();t2.join();
}

2.3 简单综合运用

但是单独用lamdba表达式传过去的话,代码会重复,所以也可以用循环把多个线程放进数组里,如下代码:

void main()
{int m;cin >> m;//要求m个线程分别打印nvector<thread> vthds(m);//把线程作为一个对象放到容器里去size_t n;for (size_t i = 0; i<m; i++){size_t n = 10;vthds[i] = thread([i, n]() {for (int j = 0; j < n; j++){cout << this_thread::get_id() << ":" << j << endl; //用this_thread命名空间打印线程idthis_thread::sleep_for(chrono::seconds(1)); //每打印一次休眠一秒}cout << endl;});//cout << vthds[i].get_id() << endl;}for (auto& t : vthds) //库线程禁掉了拷贝赋值,所以这里用引用{t.join();}}

2.4 线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此,即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参

void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;// 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际
引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;// 如果想要通过形参改变外部实参时,必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a);t2.join();cout << a << endl;// 地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}

三,线程安全问题

3.1 为什么会有这个问题?

先看下面代码:

unsigned long sum = 0;void fun(size_t num)
{for (size_t i = 0; i < num; ++i)sum++;
}
int main()
{cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}

 从上面的运行结果可以看出,两个线程都对一个全局变量进行相加时,结果与我们预期的不同。

多线程最主要的问题就是访问临界资源带来的线程安全问题,如果所有临界资源都是只读的,那就是线程安全的,因为数据不会被修改,但是当一个或多个线程要修改临界资源时,如果没有对临界资源给予相关的保护措施,比如++,那么就是线程不安全的,因为我们说一条汇编语句是原子的,但是++等操作经过编译器编译后会编程三条汇编语句,那么++操作就不是原子的,具体的底层细节我们到linux系统编程部分再讲。

3.2 锁

关于锁的概念我们到Linux系统编程再进行讲解,这里只展示用法:

3.2.1 互斥锁

list<int> lt;
int x = 0;
mutex mtx;
void Func2(int n)
{//并行for (int i = 0; i < n; i++){mtx.lock(); ++x;lt.push_back(x);mtx.unlock();cout << i << endl;cout << i << endl;cout << i << endl;}//串行 --> 如果我们只是++,没有push_back等其他操作时,可以看出并行并不比串行快,并行会有大量的加锁和解锁,而且由于计算太快了,还有切换线程的切换上下文的消耗//但是当我们++后再push_back插到链表后,还是并行快,而且随着多临界资源的访问消耗变大时,并行的优势更大/*mtx1.lock(); for (int i = 0; i < n; i++){++x;lt.push_back(x);cout << i << endl;cout << i << endl;cout << i << endl;}mtx1.unlock();*/
}void main()
{int n = 2000000;size_t begin = clock();thread t1(Func2, 10000); //每个线程都有自己独自的栈,这个栈在共享区中,由库提供thread t2(Func2, 20000);t1.join();t2.join();size_t end = clock();cout << (end - begin) << endl;cout << x << endl;
}

 上面的有点复杂,我们直接传lamdba

void main()
{int n = 20000;int x = 0;mutex mtx1;size_t begin = clock();thread t1([&, n](){mtx.lock();for (int i = 0; i < n; i++){++x;}mtx1.unlock();});thread t2([&, n]() {mtx1.lock();for (int i = 0; i < n; i++){++x;}mtx1.unlock();});t1.join();t2.join();size_t end = clock();cout << (end - begin) << endl;cout << x << endl;
}

3.2.2 递归锁

recursive_mutex mtx2;
void Func3(int n)
{if (n == 0)return;mtx2.lock();++x;Func3(n - 1);mtx2.unlock(); //当普通锁的解锁在这里定义的时候会造成死锁,递归锁解决在递归场景中的死锁问题
}
void main()
{thread t1(Func3, 1000); thread t2(Func3, 1000);t1.join();t2.join();cout << x << endl;
}

3.3 原子操作

虽然加锁可以解决线程安全问题,但是加锁有一个缺陷就是:只要一个线程在对sum++,其他线程就必须阻塞等待,一旦线程很多的情况下就会影响总体的效率,而且如果控制不好容易造成死锁问题。

因此C++11引入了原子操作,也叫无锁操作,需要用到头文件#include<atomic>

(该操作也是用到了一个叫做CAS的同步原语,就是当我写一个数的时候,如果这个数已经改变过了,那我就不写了,如果每改变就写)

3.3.1 原子操作变量

#include <thread>
#include <atomic>atomic_long sum{ 0 }; //这只是其中一个原子操作变量void fun(size_t num)
{for (size_t i = 0; i < num; ++i)sum ++;   // 原子操作
}
int main()
{cout << "Before joining, sum = " << sum << std::endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining, sum = " << sum << std::endl;return 0;
}

3.3.2 atomic模板

atmoic<T>  t; //声明一个类型为T的原子类型变量t

 注意:原子类型通常属于“资源型”数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行改造,不允许原子类型进行拷贝构造,移动构造和operator=等,所以为了防止意外,标准库直接将atomic模板类中的拷贝构造,移动构造,赋值运算符重载全部删除了

#include <atomic>
int main()
{atomic<int> a1(0);//atomic<int> a2(a1);   // 编译失败atomic<int> a2(0);//a2 = a1;               // 编译失败return 0;
}

下面是atomic模板类的演示代码:

void Func5(int n)
{cout << x << endl;
}
void main()
{int n = 200;//atomic<int> x = 0; 三种写法都一样,都是去调用构造函数//atomic<int> x = {0};atomic<int> x{ 0 };thread t1([&, n]() {for (int i = 0; i < n; i++){++x;}});thread t2([&, n]() {for (int i = 0; i < n; i++){++x;}});Func5(x.load());t1.join();t2.join();cout << x << endl;
}

五,lock_guard和unique_lock

5.1 可能发生的情况

在多线程中,如果想要保证临界资源的安全性,可以将其设置为原子类型避免死锁,也可以通过加锁保证一段代码的安全性。但是还有一种情况,那就是如果在加锁和解锁中间抛异常了,那么代码会直接跳到捕获的地方导致无法执行unlock来释放锁。因此,C++11采用RAII的方式对锁进行了封装,即lock_guard和unique_lock

5.2 lock_guard

template<class Lock>
class LockGuard
{
public:LockGuard(Lock& lk):_lk(lk){_lk.lock();}~LockGuard(){_lk.unlock();}
private:Lock& _lk; //三类成员必须在初始化列表初始化:引用,const和没有默认构造的成员变量
};
void Func4(int n)
{for (int i = 0; i < n; i++){try{//把锁交给对象,构造函数时加锁,析构函数解锁 --> RAII//mtx.lock();//LockGuard<mutex> lock(mtx); // --> 我们自己实现的lock_guard<mutex> lock(mtx);  // --> 库里的++x;//...抛异常if (rand() % 3 == 0){throw exception("抛异常");}mtx.unlock();}catch (const exception& e){cout << e.what() << endl;}}
}
void main()
{thread t1(Func4, 100);thread t2(Func4, 100);t1.join();t2.join();
}

 通过上述代码可以看到,lock_guard类模板通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方生成一个lock_guard,调用构造函数自动上锁,然后出作用域时通过析构函数自动解锁

5.3 unique_lock

lock_guard的缺陷:太单一,用户没办法对其进行控制,因此C++11还提供了unique_lock.

unique_lock与lock_guard类似,也是采用RAII进行封装,但是unique_lock对象需要传一个mutex对象作为参数,对传入的锁进行上锁和解锁操作。而且它还提供了更多的成员函数:

①上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until和unlock

②修改操作:移动赋值,交换(swap:与另一个unique_lock对象交换所管理的互斥量),释放(release:返回它所管理的互斥量的指针,释放所有权)

③获取属性:owns_lock(返回当前对象是否上了锁),operator bool(),mutex(返回当前unique_lock所管理互斥量的指针)

六,两个线程交替打印,一个打印奇数,一个打印偶数

//C++也支持条件变量(不是线程安全的,所以要配合锁使用)
//题目:两个线程交替打印,一个线程打印奇数,一个打印偶数
//condition_variable::wait  当前进程会进行阻塞,直到被唤醒,和linux一样,在阻塞之前会进行解锁,防止死锁问题,然后唤醒后重新申请锁
//文档中的 wait_for和wait_until表示等待的时间,notify_one和notify_all表示唤醒一个线程和唤醒所有线程
#include<condition_variable>
void main39()
{condition_variable cv;int n = 100;int x = 1;//如何做到t1和t2不管谁抢到锁都保证t1先运行,t2阻塞 --> t1不等待,t2有等待,这样t1获得了锁,t2就等着,t2获得了锁但是wait,释放锁,t1获得锁//如何防止一个线程不断申请锁释放锁,不断运行?( t1打印的时候,释放锁并且通知t2,t2重新获得锁,但是t1没停下来又等待了,t1又获得了锁) --> t1, if(x%2 == 0) cv.wait(lock);  t2,if (x % 2 != 0) cv.wait(lock);//为什么要防止一个线程连续打印? --> 假设t1,先获取到锁,t2后获取到锁,t2就阻塞在锁上面 --> t1打印奇数,++x,x变成偶数 --> t1 notift唤醒,但是没有线程wait(因为t1有锁,t2没有锁所以在阻塞等待) --> t1解锁,t1时间片到了,切出去了 // --> t2获取到锁,打印,notify,但是没有线程等待,t2再出作用域,解锁 --> t2的时间片充裕会比t1先获得锁,所以如果没有条件控制,就会导致t2连续打印thread t1([&, n]() {while(1){unique_lock<mutex> lock(mtx);if (x >= 100)break;//if(x%2 == 0) //奇数//	cv.wait(lock);cv.wait(lock, [&x]() {return x % 2 != 0; });cout << this_thread::get_id() << ":" << x << endl;x++;cv.notify_one();}});thread t2([&, n]() {while(1){unique_lock<mutex> lock(mtx);if (x > 100)break;//if (x % 2 != 0) //偶数//	cv.wait(lock);cv.wait(lock, [&x](){return x % 2 == 0; });cout << this_thread::get_id() << ":" << x << endl;++x;cv.notify_one();}});t1.join();t2.join();
}unsigned long sum = 0;void fun(size_t num)
{for (size_t i = 0; i < num; ++i)sum++;
}
int main()
{cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}

相关文章:

C++——C++11线程库

目录 一&#xff0c;线程库简介 二&#xff0c;线程库简单使用 2.1 传函数指针 ​编辑 2.2 传lamdba表达式 2.3 简单综合运用 2.4 线程函数参数 三&#xff0c;线程安全问题 3.1 为什么会有这个问题&#xff1f; 3.2 锁 3.2.1 互斥锁 3.2.2 递归锁 3.3 原子操作 3…...

机器学习 | 线性判别分析(Linear Discriminant Analysis)

1 机器学习中的建模 1.1 描述性建模 以方便的形式给出数据的主要特征&#xff0c;实质上是对数据的概括&#xff0c;以便在大量的或有噪声的数据中仍能观察到重要特征。重在认识数据的主要概貌&#xff0c;理解数据的重要特征。 Task&#xff1a;聚类分析&#xff0c;数据降…...

TypeScript-数组、函数类型

1.数组类型 1.1类型 方括号 let arry:number[][5,2,0,1,3,1,4] 1.2 数组泛型 let arry2:Array<number>[5,2,0,1,3,1,4] 1.3接口类型 interface makeArryRule{[index:number]:number }let arry3:makeArryRule[5,2,0,1,3,1,4] 1.4伪数组 说明&#xff1a; argument…...

Python深度学习034:cuda的环境如何配置

文章目录 1.安装nvidia cuda驱动CMD中看一下cuda版本:下载并安装cuda驱动2.创建虚拟环境并安装pytorch的torch_cuda3.测试附录1.安装nvidia cuda驱动 CMD中看一下cuda版本: 注意: 红框的cuda版本,是你的显卡能装的最高的cuda版本,所以可以选择低于它的版本。比如我的是11…...

【论文笔记】Text2QR

论文&#xff1a;Text2QR: Harmonizing Aesthetic Customization and Scanning Robustness for Text-Guided QR Code Generation Abstract 二维码通常包含很多信息但看起来并不美观。stable diffusion的出现让平衡扫描鲁棒性和美观变为可能。 为了保证美观二维码的稳定生成&a…...

【ReadPapers】A Survey of Large Language Models

LLM-Survey的llm能力和评估部分内容学习笔记——思维导图 思维导图 参考资料 A Survey of Large Language Models论文的github仓库...

站群CMS系统

站群CMS系统是一种用于批量建立和管理网站的内容管理系统&#xff0c;它能够帮助用户快速创建大量的网站&#xff0c;并实现对这些网站的集中管理。以下是三个在使用广泛的站群CMS系统&#xff0c;它们各具特色&#xff0c;可以满足不同用户的需求。 1. Z-BlogPHP Z-BlogPHP是…...

landsat8数据产品说明

1、下载数据用户手册 手册下载网址&#xff0c;搜索landsat science关键词&#xff0c;并点击到官网下载。 2、用户手册目录 3、landsat8数据产品说明 具体说明在手册的第四章&#xff0c;4.1.4数据产品章节&#xff0c;具体描述如下&#xff1a; 英文意思&#xff1a; L8 的…...

Golang 内存管理和垃圾回收底层原理(二)

一、这篇文章我们来聊聊Golang内存管理和垃圾回收&#xff0c;主要注重基本底层原理讲解&#xff0c;进一步实战待后续文章 垃圾回收&#xff0c;无论是Java 还是 Golang&#xff0c;基本的逻辑都是基于 标记-清理 的&#xff0c; 标记是指标记可能需要回收的对象&#xff0c…...

OpenHarmony:全流程讲解如何编写ADC平台驱动以及应用程序

ADC&#xff08;Analog to Digital Converter&#xff09;&#xff0c;即模拟-数字转换器&#xff0c;可将模拟信号转换成对应的数字信号&#xff0c;便于存储与计算等操作。除电源线和地线之外&#xff0c;ADC只需要1根线与被测量的设备进行连接。 一、案例简介 该程序是基于…...

计算机学生求职简历的一些想法

面试真的是一件非常难的事情&#xff0c;因为在短短的半小时到一个小时&#xff0c;来判断一个同学行不行&#xff0c;其实是很不全面的。作为一个求职的同学应该怎么办呢&#xff1f;求职的同学可以提前做一些准备&#xff0c;其中比较重要的要数简历的编写。 简历的作用 简…...

网工内推 | 售前专场,需熟悉云计算技术,上市公司,提成高

01 神州数码 招聘岗位&#xff1a;售前工程师 职责描述&#xff1a; 1.负责所在区域华为IT产品线&#xff08;服务器、存储、云、虚拟化&#xff09;的售前技术支持工作&#xff0c;包括客户交流、方案编写、配置报价、投标支持、测试等&#xff1b; 2.与厂商相关人员建立和保…...

excel匹配替换脱敏身份证等数据

假如excel sheet1中有脱敏的身份证号码和姓名&#xff0c;如&#xff1a; sheet2中有未脱敏的数据数据 做法如下&#xff1a; 1、在sheet2的C列用公式 LEFT(A2,6)&REPT("*",8)&RIGHT(A2,4) 做出脱敏数据&#xff0c;用来与sheet1的脱敏数据匹配 2、在sheet…...

[技术笔记] Flash选型之基础知识芯片分类

1、按照接口分类 分为 Serial串口Flash 和 Parallel并口Flash&#xff1b; 市场大量使用Serial Flash&#xff1b;价格便宜&#xff1b;已满足系统对数据读写速度的要求&#xff1b; Serial Flash已经可以代表 NOR Flash&#xff1b; 小知识&#xff1a; 1&#xff09;在…...

Jenkins常用插件安装及全局配置

Jenkins常用插件安装及全局配置 前言 ​ Jenkins是一个流行的持续集成工具&#xff0c;通过安装适用的插件&#xff0c;可以扩展Jenkins的功能&#xff0c;并与其他工具和系统集成。本文将介绍一些常用的Jenkins插件以及安装和配置的步骤。通过安装和配置这些常用插件&#xf…...

C++初学者:如何优雅地写程序

我喜欢C语言的功能强大&#xff0c;简洁&#xff0c;我也喜欢C#的语法简单&#xff0c;清晰&#xff0c;写起来又方便好用。 一、为什么不用C语言写程序。 C语言用来做题目&#xff0c;考试研究是很方便的&#xff0c;但是用来写程序做软件&#xff0c;你就会发现&#xff0c…...

图论- 最小生成树

一、最小生成树-prim算法 1.1 最小生成树概念 一幅图可以有很多不同的生成树&#xff0c;比如下面这幅图&#xff0c;红色的边就组成了两棵不同的生成树&#xff1a; 对于加权图&#xff0c;每条边都有权重&#xff08;用最小生成树算法的现实场景中&#xff0c;图的边权重…...

LeetCode刷题记(一):1~30题

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

芒果YOLOv5改进89:卷积SPConv篇,即插即用,去除特征图中的冗余,FLOPs 和参数急剧下降,提升小目标检测

芒果专栏 基于 SPConv 的改进结构,改进源码教程 | 详情如下🥇 👉1. SPConv 结构、👉2. CfSPConv 结构 💡本博客 改进源代码改进 适用于 YOLOv5 按步骤操作运行改进后的代码即可 即插即用 结构。博客 包括改进所需的 核心结构代码 文件 YOLOv5改进专栏完整目录链接:…...

Linux:详解TCP报头类型

文章目录 温习序号的意义序号和确认序号报文的类型 TCP报头类型详解ACK: 确认号是否有效SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段FIN: 通知对方, 本端要关闭了PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走RST: 对方要求重新建立连接; 我们把携带RST标识的称…...

【Leetcode】top 100 二分查找

35 搜索插入位置 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。 基础写法&#xff01;&#xff01;&#xff01;牢记…...

Redis高级面试题-2024

说说你对Redis的理解 Redis是一个基于Key-Value存储结构的开源内存数据库&#xff0c;也是一种NoSQL数据库。 它支持多种数据类型&#xff0c;包括String、Map、Set、ZSet和List&#xff0c;以满足不同应用场景的需求。 Redis以内存存储和优化的数据结构为基础&#xff0c;提…...

HarmonyOS 应用开发之FA模型与Stage模型应用组件

应用配置文件概述&#xff08;FA模型&#xff09; 每个应用项目必须在项目的代码目录下加入配置文件&#xff0c;这些配置文件会向编译工具、操作系统和应用市场提供描述应用的基本信息。 应用配置文件需申明以下内容&#xff1a; 应用的软件Bundle名称&#xff0c;应用的开发…...

6个黑科技网站,永久免费

1、http://mfsc123.com https://www.mfsc123.com 一个非常赞的免费商用素材导航网站。 收集了各种免费、免版权的图片、插画、视频、视频模板、音乐、音效、字体、图标网站。 再也不用担心版权问题&#xff0c;都能免费商用&#xff0c;自媒体作者必备。 而且还在每个网站…...

Linux 内核优化简笔 - 高并发的系统

简介 Linux 服务器在高并发场景下&#xff0c;默认的内核参数无法利用现有硬件&#xff0c;造成软件崩溃、卡顿、性能瓶颈。 当然&#xff0c;修改参数只是让Linux更好软件的去利用已有的硬件资源&#xff0c;如果硬件资源不够也无法解决问题的。而且当硬件资源不足的时候&am…...

整型之韵,数之舞:大小端与浮点数的内存之旅

✨✨欢迎&#x1f44d;&#x1f44d;点赞☕️☕️收藏✍✍评论 个人主页&#xff1a;秋邱’博客 所属栏目&#xff1a;人工智能 &#xff08;感谢您的光临&#xff0c;您的光临蓬荜生辉&#xff09; 1.0 整形提升 我们先来看看代码。 int main() {char a 3;char b 127;char …...

变量作用域

变量作用域 标识符的作用域是定义为其声明在程序里的可应用范围, 或者即是我们所说的变量可见性。换句话说,就好像在问你自己,你可以在程序里的哪些部分去访问一个制定的标识符。变量可以是局部域或者全局域。 全局变量与局部变量 定义在函数内的变量有局部作用域,在一个…...

数据结构:链表的双指针技巧

文章目录 一、链表相交问题二、单链表判环问题三、回文链表四、重排链表结点 初学双指针的同学&#xff0c;请先弄懂删除链表的倒数第 N 个结点。 并且在学习这一节时&#xff0c;不要将思维固化&#xff0c;认为只能这样做&#xff0c;这里的做法只是技巧。 一、链表相交问题 …...

用WHERE命令可以在命令行搜索文件

文章目录 用WHERE命令可以在命令行搜索文件概述笔记没用的小程序END 用WHERE命令可以在命令行搜索文件 概述 想确认PATH变量中是否存在某个指定的程序(具体是在PATH环境变量中给出的哪个路径底下?). 开始不知道windows有where这个命令, 还自己花了2个小时写了一个小程序. 后…...

持续交付/持续部署流水线介绍(CD)

目录 一、概述 二、典型操作流程 2.1 CI/CD典型操作流 2.2 CI/CD操作流程说明 2.3 总结 三、基于GitHubDocker的持续交付/持续部署流水线&#xff08;公有云&#xff09; 3.1 基于GitHubDocker的持续交付/持续部署操作流程示意图 3.2 GitHubDocker持续交付/持续部署流水…...

ih5网站制作平台/百度首页百度一下

用oracle sql对数字进行操作: 取上取整、向下取整、保留N位小数、四舍五入、数字格式化取整&#xff08;向下取整&#xff09;&#xff1a; select floor(5.534) from dual;select trunc(5.534) from dual;上面两种用法都可以对数字5.534向下取整&#xff0c;结果为5. 如果要向…...

最近重大新闻事件2021/太原seo招聘

源码地址&#xff1a;https://github.com/YANGKANG01/Spring-Boot-Demo 代码生成操作 在pom.xml文件中引入以下包&#xff1a; <!-- mybatisplus与springboot整合 --> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boo…...

网站建设步骤 教 程/优化科技

1) 字符串指针变量是个变量&#xff0c;指向字符串的首地址&#xff1b;而字符串数组名是个常量&#xff0c;为字符串数组第一个元素的地址&#xff1b;2)字符串指针变量可以赋值&#xff0c;而字符串数组名不能赋值&#xff1b;对于字符数组只能对各个元素赋值&#xff0c;不能…...

做网站的 简历/临沂做网络优化的公司

霍夫变换将笛卡尔坐标系的直线用统计展示坐标系A中的点坐标系B中的线坐标系A中的线坐标系B中的点A中多点的连线B中多曲线的交点先理解这样一个思维左边的x-y坐标系中&#xff0c;设定k,b定值&#xff0c;则有一系列x&#xff0c;y值组成直线。相应的在右边k-b坐标系中&#xff…...

长沙产品网站建设/单个药品营销策划方案

一、昨天完成的 因为昨天课程较满&#xff0c;所以没有写太多的代码&#xff0c;在功能实现的方面并没有实质性的进展。 二、今天做的 继续Text文本框添加文字&#xff0c;解决不能显示的问题&#xff0c;添加文本框可以出现在截图区域任意位置的功能。 三、出现的问题 添加的文…...

中小企业建立网站最经济的方式/查关键词排名软件

最近在弄linux系统的环境&#xff0c;搭建vue环境&#xff0c;由于是第一次接触vue以前也没使用过&#xff0c;所以才搭建环境的时候还是挺闷逼的。记录一下&#xff0c;免得以后忘记了。 安装cnpm 由于不知道vue和npm的关系&#xff0c;所以自己想先安装npm并设置成阿里的cnpm…...