环境:ubuntu系统
IDE:visual studio code
语言:C++,CMake
只是个人的学习笔记,用于记录一些零散的知识点,如有错误,欢迎大佬指出纠正。
参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔
编译与执行由VS进行,编译的build与运行的三角在最下面的蓝色横条上这也太不起眼了找了好久啊,VS终端的默认字体太丑了,需要参照网上教程修改一下 。
# 最低 c++ 版本要求
cmake_minimum_required( VERSION 2.8 )
# 创建项目
project( Hello )
# 添加一个运行程序
add_executable( hello hello.cpp )
# 添加一个库
add_library( hello libhello.cpp )
# 将执行程序与库链接
add_executable( useHello useHello.cpp )
target_link_libraries( useHello hello )
# 添加额外库的路径
# eigen作为只有头文件没有二进制文件的库不需要链接
include_directories("/usr/include/eigen3")
第三方库安装步骤:
1、cmake [path]
path为CMakeLists.txt所在的路径,中间文件将会生成在当前路径。
2、make
编译项目。
3、sudo make install
将编译好的第三方库安装到/usr/local/
与/usr/local/include/
路径下。
大型项目中使用的写法
# cmake最低版本
cmake_minimum_required(VERSION 3.10.2)
project(项目名)
# 添加.cpp文件路径
add_library(生成的库名 STATIC
src/第1个CPP.cpp
src/第2个CPP.cpp
)
# 添加.h文件路径
target_include_directories(生成的库名
PUBLIC
$<BUILD_INTERFACE: ${CMAKE_CURRENT_LIST_DIR}/include>
)
# 生成时需要导入的其它库
target_link_libraries(生成的库名
# 第三方库
yaml-cpp
Eigen3::Eigen
glog
${OpenCV_LIBS}
# 自己的库
自己的库1
)
# 其它cmake文件的导入路径
add_subdirectory(ThirdParty/xxx)
#include
using namespace std;
int main(int argc, char **argv) {
cout << "测试一下……" << "连接输出!" << endl;
cout << "换行输出!" << endl;
cout << "测试一下不换行" ;
cout << "看看在哪里!" << endl;
cout << "测试一下换3行" << endl << endl << endl;
cout << "看看在哪里!" << endl;
// 控制cout输出数据精度,设置N位有效数字
double i = 2.4321515151341;
cout.precision(3);
cout << i << endl;
cout << i << endl;
cout.precision(7);
cout << i << endl;
return 0;
}
输出为:
测试一下……连接输出!
换行输出!
测试一下不换行看看在哪里!
测试一下换3行
看看在哪里!
2.43
2.43
2.432152
结论:cout的换行取决于endl,作为换行符使用。
将cout换成cerr则输出错误信息,不经过缓冲直接输出到屏幕,用于快速报错输出,而且只能输出到显示器。
#include
#include
using namespace std;
int main(int argc, char **argv) {
cout << M_PI << endl;
return 0;
}
得到:
3.14159
虽然输出短,但是资料中表示实际精度为:
#define M_PI 3.14159265358979323846
#define M_E 2.71828182845904523536
#include
using namespace std;
// sleep, time的头文件
#include
// clock的头文件
#include
#define MATRIX_SIZE 50
int main(int argc, char **argv) {
// 基于 的计时,类似python的time模块
// 通过两个时间戳的相减来获得持续时间
// 持续时间受到sleep的影响
// usleep,微秒级休眠
double start_time = time(NULL);
usleep(1E6);
cout << " time of usleep is " << (double)difftime(time(NULL), start_time) << "s" << endl;
// sleep,秒级休眠
start_time = time(NULL);
sleep(3);
cout << " time of sleep is " << (double)difftime(time(NULL), start_time) << "s" << endl;
// 基于ctime的时钟周期计时,被用于检测算法的运行时间从而对比算法效率
// 除CLOCKS_PER_SEC,即用硬件的时钟周期数除以每秒的时钟周期数,最大精度为毫秒
// 计算时间不会受到sleep影响,原因未知
// usleep,微秒级休眠
clock_t time_stt = clock();
usleep(1E6);
cout << " time of usleep is " << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
// sleep,秒级休眠
time_stt = clock();
sleep(3);
cout << " time of sleep is " << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
return 0;
}
一种无符号整数类型,为了增加可移植性而设计。
能够自动扩展到当前运行环境下最大可能出现的对象大小。
例子:
for (size_t y = 0; y < image.rows; y++) {}
优点:连续存储可以用偏移访问,高效访问元素,高效从末尾添加或者删除
缺点:不适合在末尾以外的位置添加或者删除元素
返回由第一个最小值和最后一个最大值的迭代器构成的一个pair
输入开头与结尾,然后输入比较规则。
auto min_max = minmax_element(matches.begin(), matches.end(), [](const DMatch &m1, const DMatch &m2) { return m1.distance < m2.distance; });
double min_dist = min_max.first->distance;
double max_dist = min_max.second->distance;
一个能够用于接受任意类型指针的万能指针,但是不确定数据类型无法使用,接收该类型参数后,必须按照正确的指针类型转换后,才可以被用于功能模块运算。
[](){}
此处引用来源:C++ [](){}
总体结构分为三部分:
1、捕获说明[]
[]不截取任何变量
[&} 截取所有外部作用域变量,作为引用使用
[=] 截取所有外部作用域变量,拷贝之后使用
[=, &a] 截取所有外部作用域变量,拷贝之后使用,而&标记的变量使用引用
[a] 截取变量a拷贝之后使用,不截取其他变量
[a, &b] x按值传递,y按引用传递
[this] 截取当前类中的this指针,&或者=默认添加此选项
2、参数列表()
函数的输入参数。
3、函数体{}
函数结构与返回部分。
根目录为工程文件夹路径,小型算法库的普遍分类法:
|-c_plus_project
|-include
|-myproject
|-存放所有.h头文件,引用时写include "myproject/xxx.h"
|-app
|-src
|-存放.cpp的源代码文件
|-bin
|-存放编译的二进制文件
|-config
|-存放配置文件
|-test
|-存放测试文件
|-cmake_modules
|-存放第三方库的cmake文件
|-CMakeLists.txt
可被访问的范围:
public:当前类 当前包 子孙类 其他包
protected:当前类 当前包 子孙类
friendly(不写时默认):当前类 当前包
private:当前类
使用operator为特殊类型自定义运算符的计算方式。
两个类之间的运算符重载就太多了,网上有很多的写法,这里记录一下基本数据类型的重载运算。
直接重载基本类型会报错,提示需要是一个类类型或者枚举类型,可能是出于安全性或者一些未知的考虑不允许这么做,因此需要把左侧的参数转换为一个类的类型再进行运算符重载。
自己写的样例非常丑 :重载char与int的加减法,使得char在加减int时操作在ascii码上进行并返回一个char类型的返回值。
// 运算符重载
#include
#include
using namespace std;
class Char
{
public:
char operator+(const int& i){
return (char)((int)this->char_value + i);
}
char operator-(const int& i){
return (char)((int)this->char_value - i);
}
char char_value;
};
int main(int argc, char **argv) {\
string n_input;
getline(cin, n_input);
int n = atoi(n_input.c_str());
string str;
getline(cin, str);
cout << n <<endl;
cout << str << endl;
Char *p=new Char();
p->char_value = 's';
cout << *p + 2 << endl;
cout << *p - 2 << endl;
return 0;
}
输入
5
abcde
运行得到
bzfzj
向导师请教后,得知应该确实没法直接重载基本类型之间的运算符,需要转换为类来进行运算,但是这又涉及到一个运行效率的问题。
以's'+2='u'
的重载加法运算符为例,导师演示了两种写法,核心区别在于类当中的数据是直接存储char c_;
还是引用const char& c_;
,两种写法各有优劣。
写法1:将字符变量复制到类的内存空间进行运算
优点:可以使用临时字符变量’s’直接创建类的实例
缺点:需要复制字符变量内存,占用了额外存储空间开销
// 运算符重载,'s'可使用临时变量
#include
#include
using namespace std;
class Char
{
public:
Char(const char& c) :c_(c) {};
const char& Data() const { return c_;}
private:
// 类使用赋值过来的内存在自身内存空间中储存数据
char c_;
};
char operator+(const Char& j, const int& i)
{
return (char)((int)(j.Data()) + i);
}
int main(int argc, char **argv) {
Char p= 's';
cout << p + 2 << endl;
return 0;
}
写法2:将字符变量引用到类中进行运算
优点:直接引用原本数据的内存地址,类本身几乎没有额外运行开销
缺点:必须用一个变量储存临时字符变量’s’,否则类进行运算时,临时变量已被销毁,会产生未知的结果
// 运算符重载,'s'无法使用临时变量
#include
#include
using namespace std;
class Char
{
public:
Char(const char& c) :c_(c) {};
const char& Data() const { return c_;}
private:
// 类自身不储存变量,只使用引用
const char& c_;
};
char operator+(const Char& j, const int& i)
{
return (char)((int)(j.Data()) + i);
}
int main(int argc, char **argv) {\
// 这里必须用一个变量作为存储空间储存's'!
// 否则会出现未知的结果!
char s = 's';
Char p= s;
cout << p + 2 << endl;
return 0;
}
cin输入一行中多个元素,元素间以空格分开,不会记录换行符
样例:
#include
#include
#include
using namespace std;
int main(int argc, char **argv) {
int n, m;
cin >> n >> m;
return 0;
}
getline()一次获取一行作为string,不会记录换行符
样例:
#include
#include
using namespace std;
int main(int argc, char **argv) {\
// 获取输入
string n_input;
// getline没有读换行符
getline(cin, n_input);
int n = atoi(n_input.c_str());
string str;
getline(cin, str);
return 0;
}
参数表示宽度,用数字表示。
setw() 函数只对紧接着的输出产生作用。
当后面紧跟着的输出字段长度小于 n 的时候,在该字段前面用空格补齐,当输出字段长度大于 n 时,全部整体输出。
此处转自:菜鸟教程C++ setw() 函数
#include
#include
using namespace std;
int main()
{
// 开头设置宽度为 4,后面的 runoob 字符长度大于 4,所以不起作用
cout << setw(4) << "runoob" << endl;
// 中间位置设置宽度为 4,后面的 runoob 字符长度大于 4,所以不起作用
cout << "runoob" << setw(4) << "runoob" << endl;
// 开头设置间距为 14,后面 runoob 字符数为6,前面补充 8 个空格
cout << setw(14) << "runoob" << endl;
// 中间位置设置间距为 14 ,后面 runoob 字符数为6,前面补充 8 个空格
cout << "runoob" << setw(14) << "runoob" << endl;
return 0;
}
详见:c++ auto关键字使用
auto:根据值的类型自动为变量分配类型
decltype:根据一个变量的类型来为另一个变量分配类型
#include
using namespace std;
int main ()
{
auto a = 5;
cout << a << endl;
decltype(a) b = 7;
cout << b << endl;
return 0;
}
运行得到:
5
7
将std::vector
// 保存特征点
ostringstream out1;
// 将vector中的转换为字符串储存
for (auto p = cur_pts.begin(); p != cur_pts.end(); p++) {
out1 << to_string((*p).x) << " " << to_string((*p).y) << " " << endl;
}
// 写入文件
ofstream fout1(save_path + "cur_pts.txt");
if (fout1) {
//将out流转换为string类型,写入到文件流中
fout1 << out1.str() << endl;
fout1.close();
}
反向操作,从磁盘读取并还原为原本的数据类型:
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char **argv) {
string save_path = "存储目录";
// 读特征点
string temp;
int pos;
float x;
float y;
ifstream cur_pts_file(save_path + "cur_pts.txt");
vector<cv::Point2f > cur_pts;
if (!cur_pts_file.is_open())
{
cout << "未成功打开文件cur_pts.txt" << endl;
}
while(getline(cur_pts_file, temp))
{
if (temp.length() != 0) {
// 获得字符串
// 找到分割的空格位置
pos = temp.find(" ");
// x坐标
x = stof(temp.substr (0, pos));
// y坐标
y = stof(temp.substr (pos+1, temp.length()-pos-1));
cur_pts.push_back(Point2f(x, y));
}
}
cur_pts_file.close();
// 打印行数进行验证
cout << cur_pts.size() << endl;
return 0;
}
++i:先对i+1再返回当前值
i++:先返回当前值再对i+1
在不影响功能的场合,尽量使用前置自增++i,因为i++会对i进行一次复制创建临时副本,如果i是迭代器之类的复杂数据,可能会对效率有很大的影响。
在【int i;
与之后的程序块】的外面再套一个大括号,可以让i成为临时变量。
在for循环中,使用continue中止循环,会执行for中的i++
使用list的p=list.erase(p);
也相当于执行了一次p++
会导致直接跳过了两个
break后不会执行i++
适用于vector,Matrix等
传入a.data()
使用
for (auto &a : vec)
比
for (auto a = vec.begin(); a != vec.end(); a++)
#endif
效率更高
float a = std::atof(std_string.c_str());
int b = std::atoi(std_string.c_str());
int pos=path.find_last_of('/');
std::string img_name(path.substr(pos+1));
std::string doubleToString(const double &val)
{
char* chCode;
chCode = new char[20];
// 保留多少位小数
sprintf(chCode, "%.3lf", val);
std::string str(chCode);
delete[]chCode;
return str;
}
#include
#include
#include
#include
#include
#include
ifstream logfile;
std::string timestamp;
// 打开记录文件名的txt
logfile.open("log.txt");
// 获取不带换行符的每一个文件名内容
while (getline(logfile, filename))
{
// 读取磁盘bin数字矩阵文件的流
ifstream datafile;
// 打开其中一帧的点云文件
datafile.open(filename + ".bin");
// 点云
std::vector<Vector3d> point_cloud;
// 读取不确定长度的bin点云文件
while (!datafile.eof())
{
// 单个3d点
Vector3d point;
// 从bin文件中读取单个3d点
datafile.read((char *)(&point), sizeof(point));
// 存入点云列表
point_cloud.push_back(point);
}
// 关闭bin数据文件
datafile.close();
}
// 关闭记录文件
logfile.close();
假设A.h为:
#ifndef A
#define A
...
#endif
假设B.h导入A.h为:
#ifndef B
#define B
#include "A.h"
...
#endif
假设C.h同时导入A和B
#include "A.h"
#include "B.h"
此时B中A的导入复制部分会因为宏定义不运行,从而避免A中的内容被重复运行2次后出现重复定义错误。
std::shared_ptr<ClassA> new_class(new ClassA());
#include
using namespace std::chrono;
std::this_thread::sleep_for(10s);
std::this_thread::sleep_for(10ms);
std::this_thread::sleep_for(10us);
编译命令
rm -rf build
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j4
cd ..
vscode的launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "执行程序路径",
"args": [
"参数1",
"参数2",
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
然后在VScode中按F5启动调试模式运行,查看左下角堆栈中的崩溃点。
Cmake语法
# 静态库
add_library(xxx STATIC
...
# 动态库
add_library(xxx SHARED
...
#include
#include
std::string path = "test/";
// 如果目录不存在
if (!filesystem::exists(path))
{
return -1;
}
// 获取开头和结尾标记
auto begin = filesystem::recursive_directory_iterator(path);
auto end = filesystem::recursive_directory_iterator();
for (auto it = begin; it != end; it++)
{
auto &entry = *it;
// 文件
if (filesystem::is_regular_file(entry))
{
cout << entry.path() << endl;
}
// 目录
else if (filesystem::is_directory(entry))
{
cout << entry.path() << endl << endl;
}
}
double dbStart = (double)cv::getTickCount();
Run();
double dbEnd = (double)cv::getTickCount();
std::cout << "Time Cost: " << (dbEnd - dbStart) * 1000.0 / cv::getTickFrequency() << std::endl;
// 用于清理运行日志
void clear_log() { remove("log.txt"); }
// 将数据写入日志文件
void write_log(string text)
{
std::ofstream file;
file.open("log.txt", ios::app);
file << text;
file.close();
}
C++ 内存中的堆和栈,在堆和栈上创建对象,new、delete
linux内存检测工具valgrind
使用参考:
linux下内存泄露检测工具介绍
Linux下几款C++程序中的内存泄露检查工具
安装
sudo apt-get install valgrind
使用
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes --log-file=memory_leak.log [执行程序名] [命令行参数]
注:使用该工具进行内存检测时需要编译为debug版本,即使用:
cmake -DCMAKE_BUILD_TYPE=Debug ..
对于release版本编译的库,在检测结果中会显示为???
,例如调用的第三方库检测到错误。
使用emplace_back
为vector
对象添加元素到末尾,比push_back
更加高效,而效果完全相同。
缺点是emplace_back
适用于高版本,低版本缺少支持,因此代码向下兼容可能会出问题。
在执行build命令时:
cmake .. -DTEST_DEBUG=1
在cmakelist文件末尾加上:
# 设置为ON时,在cmake命令后加入-DTEST_DEBUG=1启用debug模式
option(TEST_DEBUG "option for debug" ON)
if (TEST_DEBUG)
add_definitions(-DTEST_DEBUG)
endif(TEST_DEBUG)
注:add_definitions()中宏定义必须以-D开头,实际上变量名没有这个D
实际c++代码里面:
#ifdef TEST_DEBUG
// 代码
#endif
Class1::Class1(int a, double b, int c)
: a_(a), b_(b), c_(c)
假设类A中有个成员b_是类B,而类B没有无参数构造函数
所以需要在A的构造函数中
A::A() : b_(input)
{}
而不能是
A::A()
{b_ = B(input);}
在C/C++混合代码中出现内存越界错误,但是报错的代码段又看不出问题。
错误笔记:在更之前的地方,C代码写内存越界导致整个程序已经崩溃了,而且C没有报错。
用于跨设备传输时设置好内存对齐
#pragma pack(push) //保存对齐状态
#pragma pack(1)//设定为1字节对齐
struct test
{
char m1;
double m4;
int m3;
};
struct test2
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
#ifdef __cplusplus
extern "C"
{
#endif
// 代码内容
#ifdef __cplusplus
}
#endif
无需自行delete释放的指针
#include
std::shared_ptr<TypeName> point = std::make_shared<TypeName>();
为枚举类型赋值时,先强制转换为该变量的类型,而不是直接用int赋值。
a = static_cast<枚举类型名>(变量名);
使用std::any
可以匹配任何参数类型,类似于void指针。
// 测试读取用的数据,类型为CV_32FC3
std::string file_path = "xxx.raw";
std::ifstream fin;
// 指定binary读取模式
fin.open(file_path, std::ios::binary);
if (!fin)
{
std::cerr << "open failed: " << file_path << std::endl;
}
// seek函数会把标记移动到输入流的结尾
fin.seekg(0, fin.end);
// tell会告知整个输入流(从开头到标记)的字节数量
int length = fin.tellg();
// 再把标记移动到流的开始位置
fin.seekg(0, fin.beg);
std::cout << "file length: " << length << std::endl;
// load buffer
char *buffer = new char[length];
// read函数读取(拷贝)流中的length各字节到buffer
fin.read(buffer, length);
// 重新转换为float32类型
float *img_buffer = (float *)buffer;
// 图像的数据
int width = 320;
int height = 320;
int channel = 1;
// 查看图像
cv::Mat image(cv::Size(width, height), CV_32FC1, img_buffer);
cv::imshow("test", image);
cv::waitKey();
// c++遍历文件夹
std::vector<cv::String> vcPath;
vcPath.clear();
cv::glob(input_data_path, vcPath, false);
#include
// opencv读取yaml配置文件
cv::FileStorage fs_setting(config_path, cv::FileStorage::READ);
std::string data = static_cast<std::string>(fs_setting["node_name"]);