lambda表达式

Lambda表达式可以直接在需要调用函数的位置定义短小精悍的函数,而不需要预先定义好函数。适合短小不需要复用函数的场景

缺点:

  1. Lamdba表达式语法比较灵活,增加了阅读代码的难度。如果我要找某个函数的调用,搜索函数名即可,但是lambda表达式往往没有名称,我要记住它的位置

  2. 难以调试:lambda表达式往往是一种匿名函数,这意味着函数名称并不明确,所以在调试中很难分清楚是哪个函数出错了。

  3. 存在捕获的性能开销:捕获变量或对象需要花费额外的时间,而这些时间开销可能会在一些精细的程序中成为问题

  4. 不适合复用的场景

原理

编译器会把一个lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符,实现了一个operator()方法

auto print = []{cout << "Hello World!" << endl; };

编译器会把上面这一句翻译为下面的代码:

1
2
3
4
5
6
7
8
9
10
class print_class
{
public:
void operator()(void) const
{
cout << "Hello World!" << endl;
}
};
//用构造的类创建对象,print此时就是一个函数对象
auto print = print_class();

1.捕获列表。捕获列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据它来判断接下来的代码是否是Lambda函数,捕获列表能够捕捉上下文中的变量以供Lambda函数使用。

2.参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略。

3.可变规则。mutable修饰符,但使用很少。默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)

4.异常说明。用于Lamdba表达式内部函数抛出异常,使用也很少

5.返回类型。 可以在不需要返回值的时候也可以连同符号->一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。

6.lambda函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

捕获列表

[]包括起来的是捕获列表,捕获列表由多个捕获项组成,并以逗号分隔。捕获列表有以下几种形式:

  • []表示不捕获任何变量
1
2
3
4
5
6
auto function = ([]{
std::cout << "Hello World!" << std::endl;
}
);

function();

从这个例子可以看出,Lambda表达式可以作为仿函数

  • [var]表示值传递方式捕获变量var
1
2
3
4
5
6
7
int num = 100;
auto function = ([num]{
std::cout << num << std::endl;
}
);

function();
  • [=]表示值传递方式捕获所有父作用域的变量(包括this)
1
2
3
4
5
6
7
8
9
int index = 1;
int num = 100;
auto function = ([=]{
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);

function();
  • [&var]表示引用传递捕捉变量var

  • [&]表示引用传递方式捕捉所有父作用域的变量(包括this)

输入参数

除了捕获列表之外,lambda还可以接受输入参数。参数列表是可选的,并且在大多数方面类似于函数的参数列表。

1
2
3
4
5
auto function = [](int first, int second){
return first + second;
};

function(100, 200);


可变规格mutable和异常使用较少,不研究了。


for_each应用实例

1
2
3
int a[4] = {11, 2, 33, 4};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );


find_if应用实例

1
2
3
4
5
6
7
8
9
10
int x = 5;
int y = 10;
deque<int> coll = { 1, 3, 19, 5, 13, 7, 11, 2, 17 };

auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {
return i > x && i < y;
});

if(pos != coll.end() )
cout << *pos << endl;


Lamdba表达式应用于函数指针与function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
auto add = [](int a, int b) { return a + b; };
std::function<int(int, int)> Add = [=](int a, int b) { return a + b; };

cout << "add: " << add(1, 2) << endl;
cout << "Add: " << Add(3, 4) << endl;

return 0;
}


异常out_of_range

使用C++容器类访问成员时由于使用问题可能会遇到 terminate called after throwing an instance of ‘std::out_of_range’”或者”Abort message: ‘terminating with uncaught exception of type std::out_of_range

原因如下:

  • 索引超出容器范围
  • 容器为空,在尝试访问一个空容器的元素时,也会触发此异常,因为容器中没有任何元素可供访问。
  • 错误的索引计算

我们可以使用std::out_of_range捕获这个异常

1
2
3
4
5
6
7
8
9
10
11
12
vector<int>  vec;
vec.push_back(1);
vec.push_back(2);

try
{
cout<< vec.at(vec.size() ) <<endl;
}
catch (const std::out_of_range& err)
{
std::cerr << "\nOut of range error:" << err.what()<< endl;
}


CMAKE_CXX_COMPILE_FEATURES

CMAKE_CXX_COMPILE_FEATURES变量用于获取当前C++编译器支持的编译特性列表,列表中是一些定义在CMAKE_CXX_KNOWN_FEATURES(C++已知特性)中的特性名字,比如cxx_lambdas即为当前编译器支持lambda表达式。

1
2
3
4
5
6
7
8
9
10
11
12
message("Your C++ compiler supports these C++ features:")
foreach(i ${CMAKE_CXX_COMPILE_FEATURES})
message("${i}")
endforeach()
# list命令在CMAKE_CXX_COMPILE_FEATURES 查找 cxx_std_20, 如果能找到就说明编译支持C++20
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_20 _cxx20_enable)
if(_cxx11_enable)
message(STATUS "C++ 20 supported")
else()
message(FATAL_ERROR "Compiler not supported C++ 20 standard")
endif()
unset(_cxx20_enable)

C++的不合理之处

以下功能不太合理或者比较冷门,应该尽量少使用

  • Exception

  • 多继承

  • 继承指定类型是public还是private,几乎全是public

  • delete []

  • char和string之间的关系

  • 隐式转换

  • 模板

  • 友元

  • 数组类型,能退化成指针,但不能当返回值类型,还不能用另一个数组初始化。


RDP算法

这个算法很简单,可以说是对路径的拉直。可以用于处理A*。也可以说是无约束的降采样,可以消除小的抖动,但无法保证路径仍然安全。

有时路径规划获得的点太多,或者录制机器人路径获得的点太多,可以使用RDP算法,对得到的点进行缩减,用少量目标点表示路径。

首先,将起始点和终点连成一条线,找到剩下的点中距离这条线垂直距离最大的点P,记住点P和最大距离max,如果max小于设定的距离epsilon,则直接返回起始点和终止点就可以了。由于最大距离离这条直线也不远,因此可以把所有的点都看做在这条直线上,相当于把整个路径按照首尾拉直了。

如果max大于设定的距离epsilon,那么开始递归,以点P为中心将线段分为两部分,每一部分都重复上述过程,直到递归结束,相当于对两部分各自拉直。

参考:
一种路径优化方法-拉直法
矢量数据压缩算法提取直线(RDP)


opencv基本使用

void cv::circle(InputOutputArray img, Point center, int radius, const Scalar& color, int thickness = 1, LineTypes lineType = LINE_8, int shift = 0);

  • img: 输入输出参数,表示待绘制的目标图像。
  • center: 输入参数,表示圆心坐标,是一个 cv::Point 类型的对象。
  • radius: 输入参数,表示圆的半径。
  • color: 输入参数,表示绘制圆的颜色以及透明度,是一个 cv::Scalar 类型的对象。
  • thickness: 可选参数,表示圆线条的宽度。默认值为 1 表示绘制一个像素宽度的圆,如果设置为负值,则表示绘制一条填充的圆。
  • lineType
    : 可选参数,表示圆边界的类型,可以取以下几个值:
    cv::LINE_4: 表示绘制四个相邻的点的圆边界,默认值。
    cv::LINE_8: 表示绘制八个相邻的点的圆边界。
    cv::LINE_AA: 表示绘制抗锯齿的圆边界。

  • shift: 可选参数,表示坐标点像素值所占用的位数,默认值为 0。

矩形

void cv::rectangle(InputOutputArray img, Rect rect, const Scalar& color, int thickness = 1, LineTypes lineType = LINE_8, int shift = 0)

  • img: 输入输出参数,表示待绘制的目标图像。
  • rect: 输入参数,表示矩形,是一个 cv::Rect 类型的对象,可以通过传递左上角和右下角坐标的方式来定义一个矩形。
  • color: 输入参数,表示绘制矩形的颜色以及透明度,是一个 cv::Scalar 类型的对象。
  • thickness: 可选参数,表示矩形边框的宽度。默认值为 1 表示绘制一个像素宽度的矩形,如果设置为负值,则表示绘制一条填充的矩形。
  • lineType: 可选参数,表示矩形边框的类型,可以取以下几个值:
    cv::LINE_4: 表示绘制四个相邻的点的矩形边框,默认值。
    cv::LINE_8: 表示绘制八个相邻的点的矩形边框。
    cv::LINE_AA: 表示绘制抗锯齿的矩形边框。
  • shift: 可选参数,表示坐标点像素值所占用的位数,默认值为 0。

Rect函数的基本形式是Rect(x, y, width, height),其中x和y代表矩形左上角的坐标,widthheight分别代表矩形的宽度和高度。

如果创建一个Rect对象rect(100, 50, 50, 100),有以下常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
rect.area();     //返回rect的面积 5000

rect.size(); //返回rect的尺寸 [50 × 100]

rect.tl(); //返回rect的左上顶点的坐标 [100, 50]

rect.br(); //返回rect的右下顶点的坐标 [150, 150]

rect.width(); //返回rect的宽度 50

rect.height(); //返回rect的高度 100

rect.contains(Point(x, y)); //返回布尔变量,判断rect是否包含Point(x, y)点


用opencv处理轮廓

Rect boundingRect(InputArray points)

寻找包裹轮廓的最小正矩形。矩形的边界与图像边界平行。 唯一一个参数是输入的二维点集,可以是 vector 或 Mat 类型。只需要 #include "opencv2/opencv.hpp"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main(){
Mat src = imread("/home/user/test.jpg");
imshow("src", src);

Mat gray, bin_img;
cvtColor(src, gray, COLOR_BGR2GRAY); //将原图转换为灰度图
imshow("gray", gray);

//二值化
threshold(gray, bin_img, 150, 255, THRESH_BINARY_INV);
imshow("bin_img", bin_img);

//寻找最外围轮廓
vector<vector<Point> >contours;
findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

//绘制边界矩阵
RNG rngs = { 12345 };
Mat dst = Mat::zeros(src.size(), src.type());
for (int i = 0; i < contours.size(); i++)
{
Scalar colors = Scalar(rngs.uniform(0, 255), rngs.uniform(0, 255), rngs.uniform(0, 255));
drawContours(dst, contours, i, colors, 1);
Rect rects = boundingRect(contours[i]);
rectangle(dst, rects, colors, 2);
}
imshow("dst", dst);

waitKey(0);
}

RotatedRect minAreaRect(InputArray points)

寻找包裹轮廓的最小斜矩形。唯一一个参数是输入的二维点集,可以是 vector 或 Mat 类型。只需要 #include "opencv2/opencv.hpp"

boundingRect 返回结果的区别是:矩形的边界不必与图像边界平行

RotatedRect旋转矩形结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    Mat gray, bin_img;
cvtColor(src, gray, COLOR_BGR2GRAY); //将原图转换为灰度图
imshow("gray", gray);

//二值化
threshold(gray, bin_img, 150, 255, THRESH_BINARY_INV);
imshow("bin_img", bin_img);

//寻找最外围轮廓
vector<vector<Point> >contours;
findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

//绘制最小边界矩阵
RNG rngs = { 12345 };
Mat dst = Mat::zeros(src.size(), src.type());
Point2f pts[4];
for (int i = 0; i < contours.size(); i++)
{
Scalar colors = Scalar(rngs.uniform(0, 255), rngs.uniform(0, 255), rngs.uniform(0, 255));
drawContours(dst, contours, i, colors, 1);
RotatedRect rects = minAreaRect(contours[i]);
// 确定旋转矩阵的四个顶点
rects.points(pts);
for (int i = 0; i < 4; i++)
line(dst, pts[i], pts[(i + 1) % 4], colors, 2);

}
}

double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

计算点与轮廓的距离及位置关系,只需要 #include "opencv2/opencv.hpp"

  • contour: 所需检测的轮廓对象

  • pt: Point2f 类型的pt, 待判定位置的点

  • measureDist: 是否计算距离的标志, 当其为true时, 计算点到轮廓的最短距离, 当其为false时, 只判定轮廓与点的位置关系, 具体关系如下:

① 返回值为-1, 表示点在轮廓外部

② 返回值为0, 表示点在轮廓上

③ 返回值为1, 表示点在轮廓内部

如果不需要知道具体的距离,建议将第三个参数设为false,这样速度会提高2到3倍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//计算点到轮廓的距离与位置关系
Mat srcImg = imread("1.png");
imshow("src", srcImg);

Mat dstImg = srcImg.clone();
cvtColor(srcImg, srcImg, CV_BGR2GRAY);
threshold(srcImg, srcImg, 100, 255, CV_THRESH_BINARY);
imshow("threshold", srcImg);

vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(srcImg, contours, hierarcy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);//TREE:提取所有轮廓 NONE:画出轮廓所有点
cout << "contours.size()=" << contours.size() << endl;
for (int i = 0; i < contours.size(); i++)//遍历每个轮廓
{
for (int j = 0; j < contours[i].size(); j++)//遍历轮廓每个点
{
cout << "(" << contours[i][j].x << "," << contours[i][j].y << ")" << endl;
}
}
double a0 = pointPolygonTest(contours[0], Point(3, 3), true);//点到轮廓的最短距离
double b0 = pointPolygonTest(contours[0], Point(212, 184), false);//点与轮廓的位置关系:-1表示外部;0表示在轮廓上;1表示轮廓内部

求轮廓面积 cv::contourArea

double contourArea( InputArray contour, bool oriented = false );

  • InputArray类型的contour,输入的向量,二维点(轮廓顶点),可以为std::vector或Mat类型。
  • bool类型的oriented,面向区域标识符。若其为true,会返回一个带符号的面积值,正负取决于轮廓的方向。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(void)
{
Mat A = Mat::zeros(500, 500, CV_8UC1);
circle(A, Point2i(100, 100), 3, 255, -1);
circle(A, Point2i(300, 400), 50, 255, -1);
circle(A, Point2i(250, 100), 100, 255, -1);
circle(A, Point2i(400, 300), 60, 255, -1);

std::vector<std::vector<cv::Point> > contours; // 创建轮廓容器
std::vector<cv::Vec4i> hierarchy;

cv::findContours(A, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE, cv::Point());
if (!contours.empty() && !hierarchy.empty())
{
std::vector<std::vector<cv::Point> >::const_iterator itc = contours.begin();
// 遍历所有轮廓
int i = 1;
while (itc != contours.end())
{
double area = cv::contourArea(*itc);
cout << "第" << i << "个轮廓的面积为:" << area << endl;
i++;
itc++;
}
}
imshow("A", A);
waitKey(0);
system("pause");
return 0;
}

第一个圆的面积并不是9π,而是20。面积值是按照轮廓的内部面积进行计算的,会损失一些。

参考: cv::contourArea


深入探究虚函数 (二) 虚函数表vtbl和虚表指针vptr

虚函数表是通过一块内存来存储虚函数的地址,它实际是一个函数指针数组,每一个元素都是虚函数的指针,虚函数表的最后一个元素是一个空指针。假如虚函数类型为int,函数指针就是int*类型.虚函数表将被该类的所有对象共享,每个对象内部包含了一个虚表指针,指向虚函数表

有虚函数的类的最开始部分就是虚指针,它指向虚函数表起始地址,类型为int**(如果虚函数类型int),表中存放虚函数的地址。通过这个指针可以获取到该类对象的所有虚函数,包括父类的。

在编译期,编译器完成了虚表的创建,而虚指针在构造函数期间被初始化

因为派生类会继承基类的虚函数表,所以通过虚函数表,我们就可以知道该类对象的父类,在转换的时候就可以用来判断对象有无继承关系。派生类中增加的虚函数,如果覆盖了基类的虚函数,虚函数表中会替换相应的基类虚函数,地址换成派生类的;如果没有覆盖基类的虚函数,就添加到原虚函数表后面,以空指针结尾. 所以说派生类的虚函数表中的函数地址不是连续的,基类的是连续的。

类Base的虚表如下图:
Base.png
如果派生类Derived没有覆盖基类的虚函数,它的虚函数表如下图:

如果覆盖vFunc1,则替换Base的vFunc1;如果还定义了一个虚函数vFunc3,那么继续往虚函数表之后填,最后一个数组成员还是空指针

更详细的说明

当类有虚函数时候,类的第一个成员变量是一个虚函数指针,而this指针的值和第一个成员变量的地址相同(this指针指向第一个成员变量)。因此当有虚函数时,this指针的值等于虚函数指针的地址 this==&_vptr;

参考:
C++ 虚函数表
深入C++对象模型&虚函数表


Boost教程(四)atomic无锁编程

atomic 无锁编程

多个线程之间共享地址空间,所以多个线程共享进程中的全局变量和堆,都可以对全局变量和堆上的数据进行读写,但是如果两个线程同时修改同一个数据,可能造成某线程的修改丢失;如果一个线程写的同时,另一个线程去读该数据时可能会读到写了一半的数据。这些行为都是线程不安全的行为,会造成程序运行逻辑出现错误。下面的程序很常见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// boost::atomic<int> i(0);
int i=0;
boost::mutex mut;
void thread_1(int n);

int main()
{
int n=100000; // n不够大时,i不容易出现不同的情况
boost::thread th1 = boost::thread(boost::bind(&thread_1,n));
boost::thread th2 = boost::thread(boost::bind(&thread_1,n));
th1.join();
th2.join();
cout<< i<<endl;
return 0;
}

void thread_1(int n)
{
while(n--)
{
mut.lock();
i++;
mut.unlock();
}
}

如果不加lock,i最终值是不确定的,因为两个线程同时对i进行了写操作。一般地,我们用互斥锁mutex保护临界区,保证同一时间只能有一个线程可以获取锁,持有锁的线程可以对共享变量进行修改,修改完毕后释放锁,而不持有锁的线程阻塞等待直到获取到锁,然后才能对共享变量进行修改,最后i必定为200000

Boost提供了原子类型atomic,通过使用原子类型可摆脱每次对共享变量进行操作都进行的加锁解锁动作,节省了系统开销,同时避免了线程因阻塞而频繁的切换。atomic封装了不同计算机硬件的底层操作原语,提供了跨平台的原子操作功能,解决并发竞争读写变量的困扰,只需要包含文件<boost/atomic.hpp>,在上面的代码中使用boost::atomic<int> i(0);,然后去掉函数中的互斥锁,运行效果是一样的,而且节省了系统开销。

atomic可以把对类型T的操作原子化,T的要求:

  1. 标量类型(算数,枚举,指针)
  2. POD类型,可以使用memcmp,memset等函数

两种方式创建atomic对象:

1
2
3
4
5
6
atomic<int> a(10);
assert(a==10); //安全函数,若表达式不成立结束程序


atomic<long> L;
cout << L <<endl; //初始值不确定

最重要的两个成员函数: store() (operator=) 和 load() (operator T() )以原子方式存取,不会因为并发访问导致数据不一致。

1
2
3
4
5
6
7
8
9
boost::atomic<bool> b(1);
assert(b != 0);
std::cout << b << std::endl;
b.store(0);//存值
std::cout << b << std::endl;

boost::atomic<int> n1(100);
std::cout << n1.exchange(200) << std::endl; //交换两个值,并且返回原值100
std::cout << n1 << std::endl;

测试代码中临界区非常短,只有一个语句,所以显得加锁解锁操作对程序性能影响很大,但在实际应用中,我们的临界区一般不会这么短,临界区越长,加锁和解锁操作的性能损耗越微小,无锁编程和有锁编程之间的性能差距也就越微小。

无锁编程最大的优势在于两点:

  • 避免了死锁的产生。由于无锁编程避免了使用锁,所以也就不会出现并发编程中最让人头疼的死锁问题,对于提高程序健壮性有很大积极意义
  • 代码更加清晰与简洁

参考:C++11多线程编程


容器常用的工具函数

std::begin() 和 std::end()

std::begin()std::end()是STL中的函数模板,用于获取容器(数组、std::initializer_list、STL标准容器、std::string_view、std::array等)的起始和结束迭代器。它们提供了一种通用的方式来访问这些序列的边界,而不依赖于具体的容器类型。一般结合STL的算法一起使用

1
2
int a[] = { 1, 3, 5, 2, 9, 6, 8 };
std::sort(std::begin(a), std::end(a) );

感觉和现有的begin,end迭代器有些冗余

std::find

find() 函数是一个泛型算法,可以用于操作所有STL容器。它用于在数组或标准库容器(如vector, map)中查找指定元素,查找成功则返回一个指向指定元素的迭代器,查找失败则返回end迭代器。注意: 不是返回 true or false

时间复杂度是O(n)

1
2
3
4
5
6
7
8
9
10
vector<int> num_list = { 2,4,6,8,10,12 };
int find_num = 8; //要查找的元素,类型要与vector中的元素类型一致
std::vector<int>::iterator num = std::find(num_list.begin(), num_list.end(), find_num); //返回一个迭代器指针

if (num != num_list.end()) //查找成功
{
cout << "其索引为 " << distance(num_list.begin(), num) << endl;
}
else
cout<< "元素 " << find_num << " 不在num_list中" << endl;

注意区分map::findset::find,由于map和set内部是红黑树实现的。因此map和set内部的find函数查找时间复杂度是 O(logn)

copy

std::copy(start, end, container.begin());

copy只负责复制,不负责申请空间,所以复制前必须有足够的空间。如果container的大小小于输入序列的长度N的话,这段代码会导致崩溃(crash)。所以此时引入了back_inserter

1
std::copy(src.begin(), src.end(), std::back_inserter(dest));

标准库提供的back_inserter模板函数很方便,因为它为container返回一个back_insert_iterator迭代器,这样,复制的元素都被追加到container的末尾了。(就算container为空也没事)。

1
2
3
4
5
6
7
8
9
10
11
vector<int>  vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

vector<int> vv;
std::copy(vec.begin(), vec.end(), std::back_inserter(vv) );
for(const auto &it: vv)
cout << it << " ";

fill

std::fill 是C++标准库中的一个函数,和copy相对。定义在 头文件中。它将指定的值赋给一个给定范围内的所有元素,常用于数据结构初始化,可以用于vector, list和数组。

1
2
std::vector<int> vec = {1, 2, 3, 4, 5};
std::fill(vec.begin(), vec.end(), 0); // 将 vec 中的所有元素设置为 0

使用之前,必须对vector分配内存,比如调用resize函数。

std::fill有很多重载形式,但最常用的是根据容器迭代器赋值的形式。

std::distance

用于计算两个迭代器之间的距离。它提供了一种方便的方法处理迭代器范围时进行遍历和计算。它在头文件<iterator>中定义,函数原型如下:

1
2
3
template< class InputIt >
typename std::iterator_traits<InputIt>::difference_type
distance( InputIt first, InputIt last );

1
2
std::vector<int> v{3, 1, 4, 1, 5, 9};
std::cout << "distance: " << std::distance(v.begin(), v.end()) << std::endl; // 6

std::distance()函数计算的是迭代器之间的距离,而不是元素的个数。因此,在使用时需要确保迭代器范围有效,否则可能导致未定义行为。
对于顺序容器(如向量、列表等),std::distance的时间复杂度为O(N),其中N是迭代器范围内的元素数量。对于随机访问迭代器(如指针、数组等),时间复杂度为O(1)

reverse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
std::vector<int> v({1,2,3});
std::reverse(std::begin(v), std::end(v));
std::cout << v[0] << v[1] << v[2] << '\n';

int a[] = {4, 5, 6, 7};
std::reverse(std::begin(a), std::end(a));
std::cout << a[0] << a[1] << a[2] << a[3] << '\n';
}

cmp函数

cmp 函数的特点:

  1. 返回值为 bool 类型,用来表示当前的排序是否正确
  2. 参数为两个相同类型的变量,且类型与要排序的容器模板类型相同
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 从小到大排列
    bool cmp( const Data& d1, const Data& d2)
    {
    // return(d1.dist < d2.dist);
    // 由于重载了 <= ,可以直接这么写
    return(d1 <= d2);
    }

    vector<Data> vec;
    Data temp;
    for ( int i = 0; i < 8; ++i )
    {
    temp.dist = rand() %50;
    temp.confidence = 100;
    vec.push_back( temp );
    }
    for(int i=0; i<8; i++)
    {
    cout << vec.at(i).dist << " ";
    }
    cout << endl;
    // 第三个参数可以是一个函数指针,一般使用cmp函数
    stable_sort( vec.begin(), vec.end(), cmp );
    for(int i=0; i<8; i++)
    {
    cout << vec.at(i).dist << " ";
    }

std::accumulate 函数

用来计算特定范围内(包括连续的部分和初始值)所有元素的和,除此之外,还可以用指定的二进制操作来计算特定范围内的元素结果,需要 #include <numeric>。 三个形参:头两个形参指定要累加的元素范围,第三个形参则是累加的初值。

1
2
3
4
5
6
7
8
9
10
11
12
13
vector<int>  vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

for(const auto &it: vec)
{
cout << it << " ";
}
cout << endl;
int num = std::accumulate(vec.begin(), vec.end(), 0, [](int input){return 2*input; } );

我们期待的结果是num为30,但是这样会报错 candidate: main()::<lambda(int)> int num = std::accumulate(vec.begin(), vec.end(), 0, [](int input){return 2*input; } ); ^ candidate expects 1 argument, 2 provided

最后一句应该改为 int num = std::accumulate(vec.begin(), vec.end(), 0, [](int n, int input){return n + 2*input; } );,得到num为30

sort

sort使用快速排序的递归形式,时间复杂度是O(nlog(n) )

Sort函数有三个参数:(第三个参数可不写)

  1. 第一个是要排序的数组的起始地址。
  2. 第二个是结束的地址(最后一位要排序的地址)
  3. 第三个参数是排序的方法,可以是从大到小也可是从小到大,还可以不写第三个参数,此时默认的排序方法是从小到大排序。
1
2
3
int b = 3, c = 12;
qDebug() << std::greater<int>()(b, c);
qDebug() << std::less<int>()(b, c);
1
2
3
4
5
6
7
8
int a[]={3,4,1,12,0,8,4};
// 默认从小到大排列
std::sort(a, a+7);
// 从大到小
std::sort(a, a+7, std::greater<int>());
for(int i=0; i < 7; i++)
cout << a[i] <<" ";
// 0 1 3 4 4 8 12

使用Lambda表达式作为排序标准

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vector<int>  vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

for(const auto &it: vec)
{
cout << it << " ";
}
cout << endl;
std::sort(vec.begin(), vec.end(),
[](int a, int b){
return (a%2) < (b%2);
}
);
for(auto it : vec)
{
cout << it << " ";
}

运行结果:
1
2
1  2  3  4  5  
2 4 1 3 5


针对自定义的数据类型,需要自定义排序的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct Frontier
{
Frontier(double c):
cost(c){}

double cost;
};

bool compareFrontier(Frontier f1, Frontier f2)
{
return (f1.cost < f2.cost);
}
void showFrontiers(const vector<Frontier>& v)
{
for(auto f: v)
cout << f.cost << " ";
cout << endl;
}

vector<Frontier> frontiers;
frontiers.push_back(Frontier(1.2) );
frontiers.push_back(Frontier(4.9) );
frontiers.push_back(Frontier(12.7) );
frontiers.push_back(Frontier(0.3) );
frontiers.push_back(Frontier(3.6) );
showFrontiers(frontiers);

std::sort(frontiers.begin(), frontiers.end(),
[](const Frontier& f1, const Frontier& f2) { return f1.cost < f2.cost; });
// std::sort(frontiers.begin(), frontiers.end(), compareFrontier);

showFrontiers(frontiers);

frontiers一般用vector或数组,不能是list, dequeue, queue

运行结果:

1
2
1.2  4.9  12.7  0.3  3.6  
0.3 1.2 3.6 4.9 12.7