大小端字节序转换,对称
// 大小端字节序转换
#define HTON16(x) ( (x >> 8 & 0x00FF) | (x << 8 & 0xFF00) )
#define HTON24(x) ( (x >> 16 & 0x0000FF) | (x & 0x00FF00) | (x << 16 & 0xFF0000) )
#define HTON32(x) ( (x >> 24 & 0x000000FF) | (x >> 8 & 0x0000FF00) | (x << 8 & 0x00FF0000) | (x << 24 & 0xFF000000) )
C_INCLUDE_PATH仅对预处理C有效
CPLUS_INCLUDE_PATH仅对预处理C++有效
CPATH对所有语言均有效。
LIBRARY_PATH 库目录
宜用{}参数初始化列表来初始化对象,发生截断时会显式报错
long double ld = 3.1415926536;
// 编译器报错
// (msvc报)错误 C2397 从“long double”转换到“int”需要收缩转换
int i{ ld };
比较前后两次时间,设置报警间隔
// class member
// uint64_t pre_timestamp_{0};
if (0 == pre_timestamp_) pre_timestamp_ = current_timestamp;
else
{
auto time_span = current_timestamp - pre_timestamp_;
if(time_span < time_interval_)
{
pre_timestamp_ = current_timestamp;
return;
}
else pre_timestamp_ = current_timestamp;
}
initializer_list对象中的元素永远是常量值我们无法改变initializer_list对象中元素的值。并且,拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,其实只是引用而已,原始列表和副本共享元素。
initializer_list初始化vectorstd::initializer_list<item_type> il{ {1, "dishes"}, {0, "watch tv"}, {2, "do homework"}, {0, "read comics"}, }; std::vector<std::pair<int, std::string>> vec(il.begin(), il.end()); # 或者借助编译器自动类型填充 std::vector vec(il.begin(), il.end());
按引用返回,能否在外面拿到对象的数据,引用返回的效果
- 要点一: 声明的时候
string& getStr() { return str; }按引用返回 - 要点二: 接收的时候
string& r_str = instance.getStr();按引用接收 - 复习:
char*转string, 直接将char*赋给stringclass Obj { public: string& getStr() { return str; } private: string str{ "hello" }; }; int main() { Obj instance; string& r_str = instance.getStr(); // 按引用接收 r_str = "changed"; // 确实把对象的数据原本拿出来了 cout << instance.getStr(); // changed } - 很有趣的现象,再次说明
reference其实是指针(复习:引用,编译时就需确定它所绑定的对象)
const从编译时的角度看,编译器禁止修改const修饰的对象,禁止const修饰的函数有修改变量的操作class Obj { public: const string& getConstStr() { return str; } string& getStr() { return str; } private: string str{ "hello" }; }; int main() { Obj instance; const string& r_const_str = instance.getConstStr(); string& r_str = instance.getStr(); r_str = "changed"; cout << r_const_str; // changed } - 有趣的现象,将
const reference修饰的对象取地址成string*,并去除const修饰int main() { Obj instance; const string& r_const_str = instance.getConstStr(); // ! 禁止从const string 强转成 string // string str = const_cast<string>(r_const_str); // 取地址,强制类型转换,去除const修饰 string* p_const_str = const_cast<string*>(&r_const_str); *p_const_str = "你好"; // ! 不能强制类型转换, 无法去除const修饰 // string r_str = const_cast<string>(instance.getConstStr()); cout << r_const_str; // 你好 } - 另一个去改动
const修饰的对象的方法,也是取地址,去掉const修饰int main() { Obj instance; const string& r_const_str = instance.getConstStr(); // 强制类型转换,去除const修饰 char* p_const_str = const_cast<char*>(r_const_str.c_str()); *p_const_str = 'A'; p_const_str[1] = 'B'; cout << r_const_str << endl; // ABllo }
priority_queue 模板有 3 个参数,其中两个有默认的参数;第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,它定义了一个用来决定元素顺序的断言;默认底层类型是vector(第二参数是vector)
- 顺序容器默认不会根据元素特点对元素进行排序,只会以元素的逻辑顺序插入
- 模板类声明
第三参可选std::less<T>(默认,降序)std: :greater<T>(升序)template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue - 拷贝构造函数会生成一个和现有对象同类型的 priority_queue 对象,它是现有对象的一个副本(也提供了移动构造函数)
vector初始化一个priority_queuestd::vector<int> values{ 21, 22, 12, 3, 24, 54, 56 }; std::priority_queue<int> numbers{ std::less<int>(),values };- push(const T& obj):将obj的副本放到容器的适当位置,这通常会包含一个排序操作。
- push(T&& obj):将obj放到容器的适当位置,这通常会包含一个排序操作。
emplace(T constructor a rgs...):通过调用传入参数的构造函数,在序列的适当位置构造一个T对象。为了维持优先顺序,通常需要一个排序操作。top():返回优先级队列中第一个元素的引用。pop():移除第一个元素。size():返回队列中元素的个数。priority_queue的访问函数template<typename T> void list_pq(std::priority_queue<T> pq, size_t count = 5) { size_t n{count}; while (!pq. empty()) { std::cout << pq. top() << " "; pq.pop(); if (--n) continue; std::cout << std::endl; n = count; } std::cout << std::endl; }- 如果将自定义数据类型放入
priority_queue先重载bool operator<(const tmp1& a) const函数bool operator<(const tmp1& a) const { return x < a.x; //大顶堆 }
友元类的声明
friend class XX
std::function 的调用
声明定义了一个std::function对象, 调用它时,要
std::function<void(int)> fun_obj;
// 注意,调用要用上括号()
fun_obj(7);
std::function 结合到类对象的使用
class getNumber {
public:
int getThree() {
return 3;
}
static int getFour() {
return 4;
}
};
int main()
{
// non static member function
class getNumber n;
std::function< int(class getNumber *) > getNumber3 = &getNumber::getThree;
std::cout << getNumber3(&n) << std::endl;
std::function< int(class getNumber *) > getNumber4 = std::bind(&getNumber::getThree, &n);
std::cout << getNumber4(&n) << std::endl;
std::function< int(class getNumber *) > getNumber5(&getNumber::getThree);
std::cout << getNumber5(&n) << std::endl;
// static member function
std::function< int() > getNumber6(&getNumber::getFour);
std::cout << getNumber6() << std::endl; return 0;
}
bind函数绑定成员函数时简例, 代码示例,注意成员函数的写法(取址, 不写()):
thread_find_devices = std::move(std::thread(std::bind(&VideoCapPusherManager::threadFuncFindDevices, this)));
看似简单在容器中查找指定的元素, 不同的容器使用的方法不尽相同
vector自身没实现find(),只有使用std::find()std::count()(特点:内存线性容器(vectorarray) 以及其他的有序(逻辑序,不是内存序)容器没有成员函数版的查找)mapset(关联容器)使用成员函数版的find()count()(特点:内存非线性的(mapset)提供成员函数版的查找方法)// #include <algorithm> // count template< class InputIt, class T > typename iterator_traits<InputIt>::difference_type count( InputIt first, InputIt last, const T &value ); // find iterator std::find(iterator first, iterator last, const T& val); // 成员函数版的 iterator std::map::find (const key_type& k); size_type std::set::count (const value_type& val) const;
string 字符串找到某一字段然后替换
cmd = CMD_TEMPLATE;
// replace TIME_OUT
auto pos = cmd.find("TIME_OUT");
while(std::string::npos != pos)
{
cmd.replace(pos, 8, std::to_string(timeout_ms_in*1000));
pos = cmd.find("TIME_OUT");
}
std::make_shared<> 的使用辨析
// 用赋值语句 = 接收返回值
auto pusher_instance = std::make_shared<Daemon>();
vector::insert() 函数原型,注意第一参数是迭代器
iterator insert (const_iterator position, const value_type& val);
iterator insert (const_iterator position, size_type n, const value_type& val);
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
std::call_once 的用法
int winner;
void set_winner (int x) { winner = x; }
std::once_flag winner_flag;
void wait_1000ms (int id) {
// count to 1000, waiting 1ms between increments:
for (int i=0; i<1000; ++i)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// claim to be the winner (only the first such call is executed):
std::call_once (winner_flag,set_winner,id);
}
字符串数组或叫参数化列表, 用stringstream 来接收,(比如控制台上一串句子,单词间间隔空格). stringstream类似于容器, 元素类型是string
控制台或文本输入, 转成相应的字符串string示例:
// std::cin 对象来实例化istringstream iss 首迭代器
istringstream iss{ std::cin };
fstream infile{ "db.txt" };
for (string line; getline(infile, line);) {
istringstream iss{ move(line) };
istream_iterator<string> istream_it{ iss };
// 注意看istream_iterator<string> 尾迭代器用空对象实例化它
for (istream_iterator<string> it_end({}); ; ++istream_it)
{
if (istream_it == it_end) break;
printf("each word is: %s \n", *istream_it);
}
}
- 前言,经过了两次的验证,没有问题,关键参数和步骤做了注释
- 要点:
- 演示获取
fstream文件大小函数 - 只读文件打开时
std::ios::ate | std::ios::in比较好 - 文件最好以
std::ios::binary打开, 这样操作的数据(图像),才原汁原味 - 应用了移动文件指针的做法
fs.seekg(cur_index, std::ios::beg) - 将对象的内存读取出来的做法
#include <fstream> #include <memory> #include <stdio.h> template<typename T> uint64_t getFileSize(T& fs) { uint64_t cur_index = fs.tellg(); // 注意! 移动文件指针到尾部不能一步到位 // fs.seekg(std::ios::end); fs.seekg(0, std::ios::end); uint64_t file_size = fs.tellg(); fs.seekg(cur_index, std::ios::beg); return file_size; } int main() { std::ifstream fstream_instance("./1.jpg", std::ios::in | std::ios::binary); // 注意! 不宜一来就将文件指针定位到文件尾 // std::ifstream fstream_instance("./1.jpg", std::ios::ate | std::ios::in | std::ios::binary); uint64_t file_size = getFileSize(fstream_instance); printf("file_size: %d \n", file_size); // 获取当前文件指针位置 uint64_t cur_index = fstream_instance.tellg(); // 注意! 如果文件在打开时,指定 std::ios::ate,那么现在需要将文件指针移动到头部,为接下来读取文件数据作准备了 // fstream_instance.seekg(std::ios::beg); printf("cur_index: %d \n", cur_index); // 这种要求掌握自定义删除器的用法即可 // std::shared_ptr<uint8_t> sp_data(new uint8_t[file_size], [](uint8_t* p_data) {delete[] p_data; }); // 推荐用法 std::shared_ptr<uint8_t[]> sp_data(new uint8_t[file_size]); // ifstream 对象数据读到内存上 fstream_instance.read((char*)(sp_data.get()), file_size); // 注意! 要想完整读取文件不宜用 get(),第三常指定了结束符,默认是0 // fstream_instance.get((char*)(sp_data.get()), file_size, 0xd9); // write std::ofstream ofs; ofs.open("./2.jpg", std::ios::out | std::ios::binary); ofs.write((char*)sp_data.get(), file_size); ofs.close(); fstream_instance.close(); return 0; } - 参考:ostream::write - C++ Reference (cplusplus.com)
- 演示获取
智能指针与自定义删除器以及动态数组
shared_ptr直接定义的时候写上自定义删除器std::shared_ptr<int> sp_i(new int(7), [](int* p) {delete p; });shared_ptr声明和定义分开写,声明的时候不声明自定义删除器类型shared_ptr类型参数就没有第二参数,但它还是能在定义的时候接收到自定义删除器,像下面这样std::shared_ptr<int> sp_i; sp_i = std::move(std::shared_ptr<int>(new int(10), [](int* p) {printf("delete done \n"); delete p; }));make_shared在创建shared_ptr时,不能创建自定义删除器- 注意,
unique_ptr配合到前置声明,前置声明时看不到实现,而std::unique_ptr中需要静态检测类型的大小static_assert(sizeof(Impl)>0,所以unique_ptr不适合配合到前置声明 unique_ptr可以在声明时就写上自定义删除器(shared_ptr就不能在声明时写上自定义删除器, 自定义删除器是一种类型,尖括号内要求填入一种类型)
注意,自定义删除器的类型是用一个用function封装的东西,注意,这里就体现了function用法std::unique_ptr<int, std::function <void(int*)>> sp_i(new int(7), [](int* p) {printf("delete done \n"); delete p; });unique_ptr也可以接收函数对象(可调对象)template<typename T> class MYDeleter { public: void operator()(T* p) { printf(" deleter run \n"); delete p; } }; std::unique_ptr<int, std::function <void(int*)>> sp_i(new int(7), MYDeleter<int>());unique_ptr声明和定义分开写,注意,声明时必须写上自定义删除器类型,且定义时没法用make_unique来传入自定义删除器的类型std::unique_ptr<int, std::function <void(int*)>> sp_i; // 函数对象作为自定义删除器 // std::unique_ptr<int, std::function <void(int*)>> sp_i1(new int(7), MYDeleter<int>()); std::unique_ptr<int, std::function <void(int*)>> sp_i1(new int(7), [](int* p) {printf("delete done \n"); delete p; }); // 使用时, std::move 来把定义转移到声明的地方 sp_i = std::move(sp_i1);- 动态数组(堆上数组)交由智能指针管理
class A { public: A() { printf("A() done \n"); } ~A() { printf("~A() done \n"); } }; int main() { // 一 声明了直接用 { std::unique_ptr<A[]> up_a = std::make_unique<A[]>(20); } // 一 声明了直接用 { std::unique_ptr<A[]> up_a(new A[3]); } // 二 先声明,再使用 { std::unique_ptr<A[]> up_a; up_a.reset(new A[5]); } // 一 声明了直接用 { std::shared_ptr<A[]> sp_a(new A[12]); } // 一 声明了直接用 { // 不被允许,编译报错 // std::shared_ptr<A[]> sp_a = std::make_shared<A[]>(14); } // 二 先声明再使用 { std::shared_ptr<A[]> sp_a; sp_a.reset(new A[3]); } getchar(); return 0; }
function与可调对象
之所以选择function来封装一个可调对象,而不单单采用函数指针来搞是因为function还可以适配lambda以及bind对象详见下
键值对用{}来表示,不是()来表示
void print(int i) { printf("i: %d \n", i); }
int main()
{
auto fun_1 = [](int i) {printf("i: %d \n", i); };
int j = 7;
// std::function<void(int)> b_obj = std::bind(print, 20);
std::function<void(int)> b_obj = std::bind(print, 20);
std::function<void(int)> b_obj1 = fun_1;
std::map<int, std::function<void(int)>> mp_fun;
// 键值对这么写才对
mp_fun.insert({ 0, b_obj });
mp_fun.insert({1, b_obj1});
// 调用
auto it = mp_fun.find(0);
if (mp_fun.end() != it) it->second(10);
// mp_fun[1].operator()(99);
mp_fun[1](99);
std::cin.get();
return 0;
}
bind function 占位符 的应用
#include <functional>
void fun(const std::string& in_str)
{
printf("str: %s \n", in_str);
}
int main()
{
auto b_fun = std::bind(fun, std::placeholders::_1);
b_fun("hahah");
auto b_fun1 = std::bind(fun, "ni hao");
b_fun1(); // 输出 "ni hao"
b_fun1("不需要"); // b_fun1参数已经绑定死了,不能传入该参数,还是输出 "ni hao",不应该这么调用
std::cin.get();
return 0;
}
#pragma pack(1) 内存对齐方式
#pragma pack(1)
struct Data
{
double d;
char ch;
};
#pragma pack()
int main()
{
// 如果美哦有上面的 #pragma pack(1) 内存对齐说明,那么默认的对齐就是,跟最长的 double 对齐 一共 16 字节
// 但是类内只有一个 char 型变量的类的大小 1 Bytes
printf("Data: %d \n", sizeof(Data));
std::cin.get();
return 0;
}
自己实现strcpy,源串和目标串内存分析(防止内存越界):strcpy是如何实现的? - 知乎 (zhihu.com)
注意,c库的该函数和自己实现的该函数都不是安全的,两点:1.未指明目标串的内存大小;2.源串的最后的元素可能不是\0;还有!目标串结束符赋为\0;源串用const修饰
char * mystrcpy(char *dst, const char *src)
{
if(src == NULL || dst == NULL)
{
printf("%s\n", "Error!");
return NULL;
}
char *addr = dst;
while (*src!='\0') *dst++=*src++;
dst = `\0`;
return addr;
}
自己实现memcpy,很高效合理,来自:(46条消息) c语言中的memcpy实现_Tony363的博客-CSDN博客_memcpy实现
看下上面博客地址,自己是否考虑了踩坑点,甚至要考虑内存重叠
void * memcpy(void *dst,const void *src,size_t num)
{
int nchunks = num/sizeof(dst); /*按CPU位宽拷贝*/
int slice = num%sizeof(dst); /*剩余的按字节拷贝*/
unsigned long * s = (unsigned long *)src;
unsigned long * d = (unsigned long *)dst;
while(nchunks--)
*d++ = *s++;
while (slice--)
*((char *)d++) =*((char *)s++);
return dst;
}
这个也作为参考
char *my_memcpy(char *dst, const char* src, int cnt)
{
assert(dst != NULL && src != NULL);
char *ret = dst;
if (dst >= src && dst <= src+cnt-1) //内存重叠,从高地址开始复制
{
dst = dst+cnt-1;
src = src+cnt-1;
while (cnt--)
*dst-- = *src--;
}
else //正常情况,从低地址开始复制
{
while (cnt--)
*dst++ = *src++;
}
return ret;
}
自定义的string
#include <iostream>
#include <string>
// char arr[]="jkhsdkf";
using namespace std;
// 自己实现一个字符串对象
class String
{
public:
String(const char *p = nullptr)
{
if (p != nullptr)
{
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else
{
_pstr = new char[1];
*_pstr = '\0';
}
}
~String()
{
delete[]_pstr;
_pstr = nullptr;
}
String(const String &str)
{
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
}
String& operator=(const String &str)
{
if (this == &str)
return *this;
delete[]_pstr;
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
return *this;
}
bool operator>(const String &str)const
{
return strcmp(_pstr, str._pstr) > 0;
}
bool operator<(const String &str)const
{
return strcmp(_pstr, str._pstr) < 0;
}
bool operator==(const String &str)const
{
return strcmp(_pstr, str._pstr) == 0;
}
int length()const { return strlen(_pstr); }
const char* c_str()const { return _pstr; }
// char ch = str6[6]; str6[6] = '7'
char& operator[](int index) { return _pstr[index]; }
// char ch = str6[6]; 不允许修改!str6[6] = '7'
const char& operator[](int index)const { return _pstr[index]; }
// 给String字符串类型提供迭代器的实现
class iterator
{
public:
iterator(char *p = nullptr) :_p(p) {}
bool operator!=(const iterator &it)
{
return _p != it._p;
}
void operator++()
{
++_p;
}
char& operator*() { return *_p; }
private:
char *_p;
};
// begin返回的是容器底层首元素的迭代器的表示
iterator begin() { return iterator(_pstr); }
// end返回的是容器末尾元素后继位置的迭代器的表示
iterator end() { return iterator(_pstr + length()); }
private:
char *_pstr;
friend String operator+(const String &lhs, const String &rhs);
friend ostream& operator<<(ostream &out, const String &str);
};
String operator+(const String &lhs, const String &rhs)
{
//char *ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
String tmp;
tmp._pstr = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
strcpy(tmp._pstr, lhs._pstr);
strcat(tmp._pstr, rhs._pstr);
//delete[]ptmp;
return tmp;
}
ostream& operator<<(ostream &out, const String &str)
{
out << str._pstr;
return out;
}
int main04()
{
// 迭代器的功能:提供一种统一的方式,来透明的遍历容器
String str1 = "hello world!"; // str1叫容器吗?底层放了一组char类型的字符
// 容器的迭代器类型
auto it = str1.begin();
for (; it != str1.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
// c++11 foreach的方式来遍历容器的内部元素的值=>底层,还是通过迭代器进行遍历的
for(char ch : str1)
{
cout << ch << " ";
}
cout << endl;
// vector allocator 提供迭代器iterator的实现
#if 0
String str1;
String str2 = "aaa"; // string(const char*)
String str3 = "bbb";
String str4 = str2 + str3;
String str5 = str2 + "ccc";
String str6 = "ddd" + str2;
cout << "str6:" << str6 << endl;
// bool operator>(const String &str)
if (str5 > str6)
{
cout << str5 << " > " << str6 << endl;
}
else
{
cout << str5 << " < " << str6 << endl;
}
int len = str6.length();
for (int i = 0; i < len; ++i)
{
// char& str6.operator[](i)
cout << str6[i] << " ";
}
cout << endl;
// string -> char*
char buf[1024] = { 0 };
strcpy(buf, str6.c_str());
cout << "buf:" << buf << endl;
#endif
return 0;
}
模板结合到自定义的泛型算法std::greater<int>
注意两点:
- 1.模板类型显式指定,上面是自动推导的,感到奇怪的是,上面显式指定类型没成功过
- 2.模板类型显式指定为标准库的std::greater,切构造时也显式传入了一个临时对象
#include <algorithm>
#include <iostream>
template<typename T>
bool mygreater(const T& a, const T& b)
{
return a > b;
}
template<typename T>
bool myless(const T& a, const T& b)
{
return a < b;
}
template<typename T, typename Compare>
bool compare(const T& a, const T& b, Compare cmp)
{
return cmp(a, b);
}
int main()
{
auto ret = compare(50, 30, mygreater<int>);
printf("ret: %d \n", ret);
/* 注意两点:
1.模板类型显式指定,上面是自动推导的,感到奇怪的是,上面显式指定类型没成功过
2.模板类型显式指定为标准库的std::greater<int>,切构造时也显式传入了一个临时对象
*/
ret = compare<int, std::greater<int>>(50, 530, std::greater<int>());
printf("ret: %d \n", ret);
std::cin.get();
return 0;
}
为容器指定比较器 using =类型别名
// priority_queue 默认大根堆,数据从大到小,现在定义成小根堆
using MinHeap = std::priority_queue<int, std::vector<int>, std::greater<int>>;
注意自定义的比较或查找器,逆序查找目标,删除,或插入元素
- 注意
lambda返回值
int main()
{
std::vector<int> vec_int{ 2, 3, 4, 5, 6, 7, 11, 17 };
auto it = std::find_if(vec_int.rbegin(), vec_int.rend(), [](const int& i)->bool {if (i < 11) return true; });
if (it != vec_int.rend())
{
printf("*it: %d \n", *it);
auto base_it = it.base();
vec_int.insert(base_it, 10);
}
for_each(vec_int.begin(), vec_int.end(), [](const int& i) {printf("i: %d \n", i); });
std::cin.get();
return 0;
}
有组合,继承,类的构造和析构次序
- 构造(可以确定的是构造派生类自己部分是最后的操作):
- 基类
- 组合对象
- 派生类
- 析构(顺序正好与构造相反)
- 派生类自己部分
- 组合
- 基类
数据结构置为0
- 跨平台的
epoll_evevnt event; memset(&event, 0, sizeof(epoll_event)); - 平台相关
// windows下使用的话使用宏替换 // #define bzero(b,len) (memset((b), '\0', (len)), (void) 0) memZero(&event, 0, sizeof event); bzero(&event, sizeof event);
为什么普通迭代器能赋给常迭代器,因为有如下关系
class iterator : public const_iterator
com技术导入.dll
# 64位工程的话如下, 32位工程则在 System下
#import "C:\\Windows\\SysWOW64\\winhttpcom.dll" no_namespace
A B锁防死锁自己的写法
#include <vector>
#include <algorithm>
#include <iostream>
#include <queue>
#include <memory>
#include <mutex>
#include <thread>
// A B 锁, 死锁
std::mutex mutex1;
std::mutex mutex2;
// 死锁
#if 0
void fun1()
{
while (true)
{
std::lock_guard<std::mutex> guarder1(mutex1);
printf("fun1 run \n");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::lock_guard<std::mutex> guarder2(mutex2);
}
}
void fun2()
{
while (true)
{
std::lock_guard<std::mutex> guarder2(mutex2);
printf("fun2 run \n");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::lock_guard<std::mutex> guarder1(mutex1);
}
}
#endif
#if 0
/*
不推荐
因为fun2()可能抢不到锁, 造成fun1()一直运行
要给对方(线程)提供抢锁的时间片
如下改造
1.缩小锁的粒度,出某个作用域后要释放锁
2.两个线程都要在释放了锁之后,做睡眠操作
*/
void fun1()
{
while (true)
{
{
std::lock(mutex1, mutex2);
std::unique_lock<std::mutex> un_locker1(mutex1, std::adopt_lock);
printf("fun1 run ------------------- \n");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::unique_lock<std::mutex> un_locker2(mutex2, std::adopt_lock);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
void fun2()
{
while (true)
{
{
std::lock(mutex1, mutex2);
std::unique_lock<std::mutex> un_locker2(mutex2, std::adopt_lock);
printf("fun2 run $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ hahaha\n");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::unique_lock<std::mutex> un_locker1(mutex1, std::adopt_lock);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
#endif
- 推荐写法
std::try_lock()// #if 0 // 推荐 void fun1() { while (true) { bool ret = std::try_lock(mutex1, mutex2); if (true == ret) { std::unique_lock<std::mutex> un_locker1(mutex1, std::adopt_lock); printf("fun1 run ------------------------ \n"); std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::unique_lock<std::mutex> un_locker2(mutex2, std::adopt_lock); } } } void fun2() { while (true) { bool ret = std::try_lock(mutex1, mutex2); if(true == ret) { std::unique_lock<std::mutex> un_locker2(mutex2, std::adopt_lock); printf("fun2 run $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \n"); std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::unique_lock<std::mutex> un_locker1(mutex1, std::adopt_lock); } } } // #endif int main() { std::thread t1(fun1); std::thread t2(fun2); // for (uint16_t i = 0; i >= 0; std::this_thread::sleep_for(std::chrono::seconds(5))); // std::cin.get(); // 这个命令也可以的 system("pause"); return 0; }
借用std::function和std::bind将模板方法的Service改造成组合形式
一 将全局函数注册到Service里
#include <thread>
#include <functional>
#if 0
class Service
{
public:
void start() {
// 两种方式亦可
th_work = std::move(std::thread(std::bind(&Service::work_loop, this)));
// th_work = std::thread(std::bind(&Service::work_loop, this));
}
void stop() {
flag_exit = true;
if (th_work.joinable()) th_work.join();
}
private:
void work_loop() {
while (!flag_exit)
{
printf("work_loop run \n");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
std::thread th_work;
bool flag_exit{ false };
};
#endif // 0
// #if 0
template<typename T>
class Service
{
public:
~Service() { stop(); }
void start() {
// 两种方式亦可
// th_work = std::move(std::thread(std::bind(&Service::work_loop, this)));
th_work = std::thread([this]() {
while (!flag_exit)
{
fun_work();
}
});
}
void stop() {
flag_exit = true;
if (th_work.joinable()) th_work.join();
}
void regFun(T fun) {
fun_work = fun;
}
private:
std::function<T> fun_work;
std::thread th_work;
bool flag_exit{ false };
};
// #endif
void fun()
{
printf("fun \n");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
/*
不宜将这种死循环的函数注册到 Service
因为 Service 提供的就是死循环的服务
*/
void fun_loop()
{
while (true)
{
printf("fun_loop \n");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
#if 0
// 显式写出 Service<>需要的模板类型
int main()
{
Service<void()> ser;
ser.regFun(fun);
// ser.regFun(fun_loop);
ser.start();
// for (uint16_t i = 0; i >= 0; std::this_thread::sleep_for(std::chrono::seconds(5)));
// std::cin.get();
// 这个命令也可以的
system("pause");
return 0;
}
#endif
#if 0
// 借用 decltype(fun) 推断类型
// 与上面 main() 一样都是将普通全局函数注册到 Service, 但实际上这种将普通成员函数注册的做法不会太常见
int main()
{
Service<decltype(fun)> ser;
ser.regFun(fun);
// ser.regFun(fun_loop);
ser.start();
// for (uint16_t i = 0; i >= 0; std::this_thread::sleep_for(std::chrono::seconds(5)));
// std::cin.get();
// 这个命令也可以的
system("pause");
return 0;
}
#endif
二 将类的成员函数注册到 Service1 里,构造函数初始化列表就把 std::function<>对象实例化了
// #if 0
template<typename T>
class Service1
{
public:
Service1(T fun_work_in):fun_work(fun_work_in) {}
~Service1() { stop(); }
void start() {
// 两种方式亦可
// th_work = std::move(std::thread(std::bind(&Service::work_loop, this)));
th_work = std::thread([this]() {
while (!flag_exit)
{
fun_work();
}
});
}
void stop() {
flag_exit = true;
if (th_work.joinable()) th_work.join();
}
private:
T fun_work;
std::thread th_work;
bool flag_exit{ false };
};
// #endif
#if 0
// 尝试不用初始化列表, 尝试之后再实例化 std::function<>对象
// 使用的时候 Service1<decltype(fun_work)> ser; 编译都通不过,构造函数调用不上
template<typename T>
class Service1
{
public:
Service1() { fun_work = T(); }
void start() {
// 两种方式亦可
// th_work = std::move(std::thread(std::bind(&Service::work_loop, this)));
th_work = std::thread([this]() {
while (!flag_exit)
{
fun_work();
}
});
}
void stop() {
flag_exit = true;
if (th_work.joinable()) th_work.join();
}
void regFun(T fun_in)
{
fun_work = fun_in;
}
private:
T fun_work;
std::thread th_work;
bool flag_exit{ false };
};
#endif
class Worker
{
public:
void worker() {
printf("worker run \n");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
};
#if 0
int main()
{
Worker work_man;
auto fun_work = std::bind(&Worker::worker, &work_man);
Service1<decltype(fun_work)> ser(fun_work);
ser.start();
// for (uint16_t i = 0; i >= 0; std::this_thread::sleep_for(std::chrono::seconds(5)));
// std::cin.get();
// 这个命令也可以的
system("pause");
return 0;
}
#endif // 0
三 组合的方式来使用 Service1
// CommissionWorker 是个委托类,提供干活的代码实体, 但是循环干活这件事是交由 Service1 来驱动的
// 同时把它设计成 RAII 类
class CommissionWorker
{
public:
CommissionWorker() :proxy_server(std::bind(&CommissionWorker::worker, this)) { proxy_server.start(); }
~CommissionWorker() { proxy_server.stop(); }
void worker() {
printf("worker run \n");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
private:
/*
Service1<decltype(CommissionWorker::worker)> proxy_server;
Service1<void (CommissionWorker*)> proxy_server;
注意,只有这么才可以, 上面两种编译都不通过
特别注意! 模板类型声明成 Service1<std::function<void()>>
表示 Service1::fun_work 调用时传入空参数,像这么调, Service1::fun_work();
而绑定时,编译器自动检查出 CommissionWorker::worker 是成员函数, this 指针就绑定在一起
我要手工填入 干活函数的类型,即 worker
*/
Service1<std::function<void()>> proxy_server;
};
int main()
{
CommissionWorker commiss_worker;
// for (uint16_t i = 0; i >= 0; std::this_thread::sleep_for(std::chrono::seconds(5)));
// std::cin.get();
// 这个命令也可以的
system("pause");
return 0;
}
四. 组合的方式使用 Service2 并想延后注册
template<typename T>
class Service2
{
public:
~Service2() { stop(); }
void start() {
// 两种方式亦可
// th_work = std::move(std::thread(std::bind(&Service::work_loop, this)));
th_work = std::thread([this]() {
while (!flag_exit)
{
fun_work();
}
});
}
void stop() {
flag_exit = true;
if (th_work.joinable()) th_work.join();
}
void regFun(T fun) {
fun_work = fun;
}
private:
T fun_work;
std::thread th_work;
bool flag_exit{ false };
};
// 四, 组合的方式来使用 Service2 并是延后注册
// CommissionWorker1 是个委托类,提供干活的代码实体, 但是循环干活这件事是交由 Service2 来驱动的
// 同时把它设计成 RAII 类
class CommissionWorker1
{
public:
CommissionWorker1(){
proxy_server.regFun(std::bind(&CommissionWorker1::worker, this));
proxy_server.start(); }
~CommissionWorker1() { proxy_server.stop(); }
void worker() {
printf("CommissionWorker1::worker run \n");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
private:
Service2<std::function<void()>> proxy_server;
};
// 延后注册也是可以的
int main()
{
CommissionWorker1 commiss_worker;
// for (uint16_t i = 0; i >= 0; std::this_thread::sleep_for(std::chrono::seconds(5)));
// std::cin.get();
// 这个命令也可以的
system("pause");
return 0;
}
std::packaged_task std::future主要用在获取异步线程的返回值
一, 封装可调对象到std::packaged_task, 从可调对象获取期物std::future,并以阻塞形式获取期物执行结果
#include <iostream>
#include <future>
#include <thread>
auto sub_task = []() ->int {
printf("task run \n");
std::this_thread::sleep_for(std::chrono::seconds(5));
return 7;
};
// 一 期物简单用法, 阻塞式获取期物结果
int main() {
// 将一个返回值为 7 的 lambda 表达式封装到 task 中
// std::packaged_task 的模板参数为要封装函数的类型
std::packaged_task<int()> task(sub_task);
// 获得 task 的期物
std::future<int> result = task.get_future(); // 在一个线程中执行 task
// 一 匿名临时线程对象
// std::thread(std::move(task)).detach();
// 二 命名的线程对象 t 子线程自此启动
std::thread t(std::move(task));
printf("in main is waittting for sub_task done \n");
result.wait(); // 在此设置屏障,阻塞到期物的完成
// 输出执行结果
std::cout << "future result is " << result.get() << std::endl;
if (t.joinable())
{
t.join();
printf("t join safety \n");
}
return 0;
}
二, 轮询非阻塞式获取期物执行结果
auto sub_task = []() ->int {
printf("task run \n");
std::this_thread::sleep_for(std::chrono::seconds(5));
return 7;
};
// 二 以轮询方式询问执行结果是否 ready
int main() {
// 注意! 首先,封装可调对象 std::packaged_task 的目的,是为了下一步获取期物
// std::packaged_task 的模板参数为要封装函数的类型
std::packaged_task<int()> task(sub_task);
std::future<int> result = task.get_future();
if (result.valid()) printf("1 result.valid() is true");
// 注意! std::future 期物的执行要依附于 std::thread
// 一 匿名临时线程对象
// std::thread(std::move(task)).detach();
// 二 命名的线程对象 t 子线程自此启动
std::thread t(std::move(task));
printf("in main is waittting for sub_task done \n");
while (!result._Is_ready())
{
printf("result._Is_ready() is false, try sleep 1 sec \n");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 在此设置屏障,阻塞到期物的完成
result.wait();
// 输出执行结果
std::cout << "future result is " << result.get() << std::endl;
if (t.joinable())
{
t.join();
printf("t join safety \n");
}
return 0;
}
尤其注意!! 线程内获取线程id
- 正确
std::this_thread::get_id(); - 错误
std::this_thread::get_id; linux获取线程id#include <unistd.h> #include <sys/syscall.h> static_cast<pid_t>(::syscall(sys_gettid))
__thread修饰的变量等价于thread_local
派生类的析构函数写上overide表示让编译器检查其基类的析构是否是虚方法
std::async返回的对象必须具名, 否则std::async下一行代码得不到执行
// 返回值必须具名接收,像下面这样,否则没具名接收的 std::asyc 返回值的下一行不会执行
auto a1 = std::async(std::launch::async, consumer1);
auto a2 = std::async(std::launch::async, consumer1);
condition_variable与线程同步的使用
#include <fstream>
#include <memory>
#include <stdio.h>
#include <thread>
#include <iostream>
#include <functional>
#include <set>
#include <string>
#include <list>
#include <algorithm>
#include <unordered_map>
#include <iostream>
#include <future>
#include <thread>
auto sub_task = []() ->int {
printf("task run \n");
std::this_thread::sleep_for(std::chrono::seconds(5));
return 7;
};
/*
<<C++标准库>>中示例, 修改一下
修改在
1. 消费者添加对 readyFlag 的复位
2. 生产者与消费者函数里,具体的生产和消费动作改成循环执行
*/
std::mutex g_mutex;
bool readyFlag;
std::condition_variable cond;
std::list<uint64_t> ls_int;
void privider()
{
uint64_t moni_data = 0;
const uint64_t moni_data_per = 55;
while (true)
{
// notify
{
std::lock_guard<std::mutex> guarder(g_mutex);
readyFlag = true;
// TODO product, 适宜在保护的区间内生产数据
for (uint16_t i = 0; i < moni_data_per; ++i)
{
ls_int.push_back(++moni_data);
}
// printf("---------------- privider product data once \n");
// fflush(stdout);
}
// 注意! 通知动作不必放在 lock 的保护区内
cond.notify_all();
// debug
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}
}
void consumer()
{
while (true)
{
// wait for notify
{
std::unique_lock<std::mutex> un_locker(g_mutex);
cond.wait(un_locker, [&]() {return readyFlag; });
// TODO consum data, 现在还在保护区间内, 适宜执行对共享数据的访问
size_t ls_size = ls_int.size();
uint64_t out_data = ls_int.front();
ls_int.pop_front();
std::cout << "consumer id: " << std::this_thread::get_id() << ", ls_size: " << ls_size << " get data: " << out_data << std::endl;
// debug
// std::this_thread::sleep_for(std::chrono::milliseconds(120));
// 处理完之后将通知标志位复位,这么搞的话其他等待的消费者线程就成串行执行的了
readyFlag = false;
}
// 模拟耗时的操作,下面不会涉及到共享数据访问,上面锁的粒度要尽可能小
// 注意,关键的一点,上面的锁 g_mutex,对消费者而言,只有一个线程能竞争到锁,然后 readyFlag 复位
// 这时就产生一个关键的现象:消费者子线程并不是并发的,因为其他没执行但是又想执行的消费者子线程检查 readyFlag 发现是 false 则继续 wait
// 没能实现多个线程同时并发,消费者子线程搞成串行的了
std::this_thread::sleep_for(std::chrono::milliseconds(120));
}
}
#if 0
// 一 用 readyFlag 来作虚假唤醒的判断依据, 它的问题是在 消费者子线程来将 readyFlag 复位
// 不合理,其他子线程在等待,子线程间搞成阻塞的了, 下面二 就是对它的整改
int main()
{
std::thread t1(privider);
std::vector<std::thread> vec_consume_th;
const int th_num = 10;
for (int i = 0; i < th_num; ++i)
{
// std:thread 转移到容器内要使用 std::move()
/*
std::thread t(consumer);
vec_th.push_back(std::move(t));
*/
// 下面这种亦可,推荐
vec_consume_th.emplace_back(std::thread(consumer));
}
std::cin.get();
return 0;
}
#endif // 0
// 二, 想用 readyFlag 来实现消费者子线程并发的现象, readyFlag 的置位和复位都在生产者线程中
// 这种方法并发效率奇低,主要依赖于生产者线程在将 readyFlag 置位后,释放锁期间,消费者线程能否抢到锁
// 可能有消费者线程只有几个能抢到锁,并判断出 readyFlag 是 true
// 注意!结论:
// 一个生产者对应一个消费者,那么 readyFlag 复位操作放在消费者或生产者亦可,因为借用 readyFlag 来判断虚假唤醒,无法避免串行
// 若是一个生产者要唤醒多个消费者,让消费者并发执行,唯一最优解:将容器的size()来作为虚假唤醒判断依据
void privider3()
{
uint64_t moni_data = 0;
const uint64_t moni_data_per = 50;
while (true)
{
// notify
{
std::lock_guard<std::mutex> guarder(g_mutex);
readyFlag = true;
// TODO product, 适宜在保护的区间内生产数据
for (uint16_t i = 0; i < moni_data_per; ++i)
{
ls_int.push_back(++moni_data);
}
// printf("---------------- privider product data once \n");
// fflush(stdout);
}
// 注意! 通知动作不必放在 lock 的保护区内
cond.notify_all();
// notify 完了 readyFlag 就复位
{
std::lock_guard<std::mutex> guarder(g_mutex);
readyFlag = false;
}
// debug
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}
}
void consumer3()
{
while (true)
{
// wait for notify
{
std::unique_lock<std::mutex> un_locker(g_mutex);
cond.wait(un_locker, [&]() {return readyFlag; });
// cond.wait(un_locker);
// TODO consum data, 现在还在保护区间内, 适宜执行对共享数据的访问
// 手工判断 ls_int.size() 是否为0,容器空间是否为0,天然就是虚假的判断条件
size_t ls_size = ls_int.size();
if (0 != ls_size)
{
uint64_t out_data = ls_int.front();
ls_int.pop_front();
std::cout << "consumer id: " << std::this_thread::get_id() << ", ls_size: " << ls_size << " get data: " << out_data << std::endl;
// std::cout << "consumer id: " << std::this_thread::get_id() << ", ls_size: " << ls_size << std::endl;
// debug
// std::this_thread::sleep_for(std::chrono::milliseconds(120));
}
}
// 模拟耗时的操作,下面不会涉及到共享数据访问,上面锁的粒度要尽可能小
std::this_thread::sleep_for(std::chrono::milliseconds(120));
}
}
// #if 0
int main()
{
std::thread t1(privider3);
std::vector<std::thread> vec_consume_th;
const int th_num = 12;
for (int i = 0; i < th_num; ++i)
{
// std:thread 转移到容器内要使用 std::move()
/*
std::thread t(consumer3);
vec_th.push_back(std::move(t));
*/
// 下面这种亦可,推荐
vec_consume_th.emplace_back(std::thread(consumer3));
}
std::cin.get();
return 0;
}
// #endif // 0
// 三, 确实消费的子线程也实现了并发, 在生产者中每次多 push_back 数据, 看到消费者并发的现象更明显
void privider1()
{
uint64_t moni_data = 0;
const uint64_t moni_data_per = 30;
while (true)
{
// notify
{
std::lock_guard<std::mutex> guarder(g_mutex);
// TODO product, 适宜在保护的区间内生产数据
for (uint16_t i = 0; i < moni_data_per; ++i)
{
ls_int.push_back(++moni_data);
}
}
// 注意! 通知动作不必放在 lock 的保护区内
cond.notify_all();
// debug sleep
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
}
}
void consumer1()
{
while (true)
{
// wait for notify
{
std::unique_lock<std::mutex> un_locker(g_mutex);
// 添加代码, 将 readyFlag
cond.wait(un_locker, [&]() {return !ls_int.empty(); });
// TODO consum data, 现在还在保护区间内, 适宜执行对共享数据的访问
size_t ls_size = ls_int.size();
uint64_t out_data = ls_int.front();
ls_int.pop_front();
std::cout << "consumer id: " << std::this_thread::get_id() << ", ls_size: " << ls_size << " get data: " << out_data << std::endl;
}
// debug
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
#if 0
// 三, list<> 是否为空作为虚假唤醒的判断依据, 创建 std::thread 作子线程
int main()
{
std::thread t1(privider1);
std::vector<std::thread> vec_consume_th;
const int th_num = 12;
for (int i = 0; i < th_num; ++i)
{
// std:thread 转移到容器内要使用 std::move()
///*
std::thread t(consumer1);
vec_consume_th.push_back(std::move(t));
//*/
// 下面这种亦可,推荐
// vec_consume_th.emplace_back(std::thread(consumer1));
}
std::cin.get();
return 0;
}
#endif // 0
#if 0
// 三, list<> 是否为空作为虚假唤醒的判断依据,用 std::async 启动线程
int main()
{
std::thread t1(privider1);
std::vector<std::future<void>> vec_consume_th;
const int th_num = 5;
for (int i = 0; i < th_num; ++i)
{
vec_consume_th.emplace_back(std::async(std::launch::async, consumer1));
}
std::cin.get();
return 0;
}
#endif // 0
// 四 仅仅就是想子线程并发,在子线程中不涉及共享资源的访问
// 这种不涉及共享资源访问,只做 notify 和 wait 的线程同步
// 测试的时候没出现虚假唤醒
// 注意,经过测试消费者子线程也是也同时被唤醒了,达到了并发的目的
void privider2()
{
uint64_t moni_data = 0;
while (true)
{
// 注意! 通知动作不必放在 lock 的保护区内
cond.notify_all();
// debug
std::this_thread::sleep_for(std::chrono::milliseconds(200));
printf("privider2 trigeried \n");
}
}
void consumer2()
{
while (true)
{
// wait for notify
{
std::unique_lock<std::mutex> un_locker(g_mutex);
cond.wait(un_locker);
std::cout << "consumer2 has been notify, "<< "consumer id: " << std::this_thread::get_id() << std::endl;
}
//
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}
}
#if 0
int main()
{
std::vector<std::thread> vec_consume_th;
const int th_num = 12;
for (int i = 0; i < th_num; ++i)
{
// std:thread 转移到容器内要使用 std::move()
/*
std::thread t(consumer);
vec_th.push_back(std::move(t));
*/
// 下面这种亦可,推荐
vec_consume_th.emplace_back(std::thread(consumer2));
}
// 触发信号的放在后面,这样保证消费者是先 wait 不会漏了信号
std::thread t1(privider2);
std::cin.get();
return 0;
}
#endif // 0
自己写的异步队列 condition_variable wait notify_one()
#include <thread>
#include <queue>
#include <mutex>
#include <functional>
#include <iostream>
struct Task
{
Task(uint32_t in) :i(in) {}
void print()
{
printf("Task::print i: %d \n", i);
}
uint32_t i;
};
void fun(void* data)
{
Task* task = (Task*)data;
task->print();
}
class A
{
public:
A(uint32_t in) : i(in) {}
void fun()
{
printf("A::fun i: %d \n", i);
}
private:
uint32_t i;
};
class AsyncTask
{
public:
AsyncTask()
{
flag_exit = false;
th_work = std::move(std::thread(&AsyncTask::run, this));
}
~AsyncTask()
{
flag_exit = true;
cnd.notify_one();
th_work.join();
}
void postTask(std::function<void()> task)
{
{
std::lock_guard<std::mutex> lk(mutex_th);
que_task.push(task);
}
cnd.notify_one();
}
std::function<void()> getTask()
{
std::unique_lock<std::mutex> lk(mutex_th);
cnd.wait(lk, [this]() {return flag_exit || !que_task.empty(); });
// 最后一次 notify 会走这个逻辑
if (flag_exit)
{
return []() { printf("check flag_exit is been set true, no task been take \n"); };
}
// 不是因为要退出而被唤醒的话,取任务
auto task = que_task.front();
// auto task = que_task.front();
que_task.pop();
return task;
}
private:
void run()
{
while (!flag_exit)
{
auto fun = getTask();
fun();
}
std::cout << "--------------------------- AsyncTask::run() exit ---------------------------" << std::endl;
}
private:
std::queue<std::function<void()>> que_task;
std::mutex mutex_th;
std::condition_variable cnd;
bool flag_exit;
std::thread th_work;
};
class ProductTask
{
public:
ProductTask(uint32_t per_task_size_in, AsyncTask& async_task_in) :per_task_size(per_task_size_in), async_task(async_task_in)
{
flag_exit = false;
th_work = std::move(std::thread(&ProductTask::run, this));
}
~ProductTask()
{
flag_exit = true;
th_work.join();
}
private:
void run()
{
while (!flag_exit)
{
postTask();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
std::cout << "#################### ProductTask::run() exit ####################" << std::endl;
}
void postTask()
{
async_task.postTask(std::bind(fun, &Task(cnt++)));
}
private:
AsyncTask& async_task;
uint32_t per_task_size;
uint32_t cnt = 0;
std::thread th_work;
bool flag_exit;
};
int main()
{
std::function<void()> func = nullptr;
if (nullptr == func)
{
printf("hello \n");
}
Task task(20);
A a(50);
{
AsyncTask async_task;
// post 普通函数
async_task.postTask(std::bind(fun, &task));
// post 对象的成员函数
async_task.postTask(std::bind(&A::fun, &a));
std::this_thread::sleep_for(std::chrono::seconds(2));
// 生产者持续生产
// product_task 对象依赖于 async_task;栈上释放对象,会先入栈的后释放,product_task 先释放,async_task 后释放
ProductTask product_task(4, async_task);
}
system("pause");
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(10));
}
return 0;
}
一个很牛批的windows下的动态库依赖查看工具
Dependencies.exe DependenciesGui.exe peview.exe 这三个都是一个东西
这里只说函数调用过程中,进入子函数时,开辟子函数栈帧的过程
参考极客时间:07 | 函数调用:为什么会发生stack overflow? (geekbang.org)
/ 源码
// function_example.c
#include <stdio.h>
int static add(int a, int b)
{
return a+b;
}
int main()
{
int x = 5;
int y = 10;
int u = add(x, y);
}
编译及反汇编过程
$ gcc -g -c function_example.c
$ objdump -d -M intel -S function_example.o
反汇编得到的文件
int static add(int a, int b)
{
0: 55 push rbp // 将调用方的栈底地址压栈(rbp放的永远是函数栈底地址)
1: 48 89 e5 mov rbp,rsp // 将调用方的栈顶地址(rsp始终指向栈顶)赋给当前rbp
// 即调用方的栈顶就是被调用方的栈底,如此实现保存调用方的栈底,给被用方开辟栈帧的目的
4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
return a+b;
a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
10: 01 d0 add eax,edx
}
12: 5d pop rbp // 回退到主函数的栈帧,子函数在栈上创建的变量就归还给 os 了
13: c3 ret
0000000000000014 <main>:
int main()
{
14: 55 push rbp
15: 48 89 e5 mov rbp,rsp
18: 48 83 ec 10 sub rsp,0x10
int x = 5;
1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5
int y = 10;
23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa
int u = add(x, y);
2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
30: 89 d6 mov esi,edx
32: 89 c7 mov edi,eax
34: e8 c7 ff ff ff call 0 <add>
39: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
3c: b8 00 00 00 00 mov eax,0x0 // 用 eax 把返回值带出去,其实这是编译器的优化(源代码上没有返回语句)
}
41: c9 leave
42: c3 ret
用float来判等,参考:同事用"两个浮点数相等" 被说了一顿 (qq.com)
乘方函数使用
typeid().name()使用,都是下面这份代码
#include <cmath>
// eg: eps = 0.0000001
template<typename T>
bool float_eq(T x, T y, T eps)
// bool float_eq(float x, float y, float eps)
{
return std::fabs(x - y) < eps;
}
// 这个逻辑没理解到,参考:https://mp.weixin.qq.com/s/-Vu9FecgYfKlOSfiPP0jhA
// 这个示例没卵用
bool float_eq(float x,float y)
{
if (x == y) return true;
else
{
if ( (*((uint32_t*)&x) ^ *((uint32_t*)&y) ) == 1) return true;
}
return false;
}
#if 1
int main()
{
// typeid().name() 用法
// bit 构造数
#define FACTOR (1UL << 28)
printf("FACTOR: %u, FACTOR type: %s, size:%zd Bytes\n", FACTOR, typeid(FACTOR).name(), sizeof FACTOR);
// float 判等演示
// 一, 两个 float 直接比较,错误示范
// auto ret = (1 - 0.3 - 0.6 -0.1 == 0)? true : false;
// 二, 使用模板函数
// auto ret = float_eq<double>(1 - 0.3 - 0.6 -0.1, 0, 0.00001);
// 三, 使用函数,没达到预期
auto ret = float_eq(1 - 0.3 - 0.6 -0.1, 0);
// 四, 来个量级差别大的,同样没问题
auto ret = float_eq(pow(2, 23) - 0.00000001, pow(2, 23), 0.00000002);
printf("ret: %d \n", ret);
// 五, 演示大数吃小数
float result = pow(2, 23); // 8.38861e+06
for(auto i = 0; i < 1000000; ++i)
{
result -= 0.1;
}
std::cout << "result: " << result << std::endl; // 8.38861e+06
// 乘方
std::cout << "pow(2, 3): " << pow(2, 3) << std::endl;
// printf() 格式化 float
// 3.2f : 最小宽度.精度 f(float)数据类型
// 最小宽度指 . 前后的位数和,高位不足空格补齐
printf("pow(2, 4): %7.2f \n", pow(2, 4));
// std::cin.get();
return 0;
}
#endif
float的s f e表示1时的逻辑
// float 的 1 对应 s f e 表示
s = 0, f = 0, e的映射 = 0, (-1)^s * 1.f * 2^e = 1 * 1.0 * 1 = 1.0 注意,不是直接 e = 0,e 其实是 127,被映射到 -126~127 中的 0 (e - 127 = e的映射)
IEEE754逐位设置验证网站:IEEE-754 Floating Point Converter (h-schmidt.net)
严格编译规范,如果函数的入参不用,则用宏关闭它
int main(int argc, char *argv[])
{
UNUSED_PARAM(argc);
最简单的存放 int 的循环队列(比起盛放自定义的数据类型简单得多,不用考虑定位 new 和显式调用析构函数的操作),之前对循环队列如何"循环"的概念不是清楚,代码是抄袭施磊老师的,自己添加注释
// 循环队列 memcpy realloc
// 文章中提到了几处注意!!
class Queue
{
public:
Queue(int size = 5)
{
_pQue = new int[size];
_front = _rear = 0;
_size = size;
}
//Queue(const Queue&) = delete;
//Queue& operator=(const Queue&) = delete;
Queue(const Queue &src)
{
_size = src._size;
_front = src._front;
_rear = src._rear;
_pQue = new int[_size];
for (int i = _front;
i != _rear;
i = (i + 1) % _size)
{
_pQue[i] = src._pQue[i];
}
}
Queue& operator=(const Queue &src)
{
if (this == &src)
return *this;
delete[]_pQue;
_size = src._size;
_front = src._front;
_rear = src._rear;
_pQue = new int[_size];
for (int i = _front;
i != _rear;
i = (i + 1) % _size)
{
_pQue[i] = src._pQue[i];
}
return *this;
}
~Queue()
{
delete[]_pQue;
_pQue = nullptr;
}
// 注意!!在队尾位置放入数据
// 关键处:插入前判断是否需要扩容
// 判满的逻辑:比如最简单的情况: _front 就没动过,然后一直向队内 push 数据, _rear 到了队尾, 此时 (_rear + 1 % _sise) == _front 了
// 所以判满的逻辑就是 _rear 与 _front 已经相邻了,并且是 _rear 在前,要是不扩容的话 _rear 就将覆盖 _front 索引上的内存
void push(int val) // 入队操作
{
if (full())
resize();
_pQue[_rear] = val;
_rear = (_rear + 1) % _size;
}
// 出队过程中不断将 _front 向后移
// 注意!! _front 与 _rear 之间才是数据,所以在吐数据前要先判断(_front == _rear 时,他们之间就没有数据)
// 判断的逻辑即 _front != _rear 即他们之间有数据
void pop() // 出队操作
{
if (empty())
return;
_front = (_front + 1) % _size;
}
int front() // 获取队头元素
{
return _pQue[_front];
}
// 之前自己没想到的逻辑,判断循环队列已满的逻辑,后面深入思考下也想到了
// 逻辑的关键在于, _front 是会变化的,并非死的,并非就指向内存的开始位置,它会受 pop() 的驱动而改变,会吐出 _front 索引上的数据, 并将 _front 索引向尾部移动
bool full() { return (_rear + 1) % _size == _front; }
bool empty() { return _front == _rear; }
private:
int *_pQue; // 申请队列的数组空间
int _front; // 指示队头的位置
int _rear; // 指示队尾的位置
int _size; // 队列扩容的总大小
// 注意!!如何创建新队,以及关键在于如何将旧队上的数据搬到新队上,并更新新队的 _front 和 _rear 和 _size
void resize()
{
int *ptmp = new int[2 * _size];
int index = 0;
for (int i = _front;
i != _rear;
i = (i + 1) % _size)
{
ptmp[index++] = _pQue[i];
}
delete[]_pQue;
_pQue = ptmp;
_front = 0;
_rear = index;
_size *= 2;
}
};
int main()
{
Queue queue;
for (int i = 0; i < 20; ++i)
{
queue.push(rand() % 100);
}
while (!queue.empty())
{
cout << queue.front() << " ";
queue.pop();
}
cout << endl;
Queue queue1 = queue;
queue1 = queue;
return 0;
}
学习老余的do{} while(false) 编码风格,规范的合乎逻辑的写法
bool flag_ok = false;
do
{
// packet
p_packet = av_packet_alloc();
if (!p_packet)
{
break;
}
// frame
p_frame = av_frame_alloc();
if (!p_frame)
{
break;
}
// sws
p_sws_context = sws_getContext(param_img.width,
param_img.height,
getInputPixelFormat(param_img.flag_is_color),
param_img.width,
param_img.height,
AV_PIX_FMT_YUV420P,
SWS_POINT, NULL, NULL, NULL);
if (p_sws_context == nullptr)
{
break;
}
flag_ok = true;
}
while(false);
提取缓冲区中的潜在报文
- 前提:
- 是有报文头的
- 报文中带有校验字段,报文经过校验后的结果需要和该字段吻合起
- 步骤:
- 首先检查整个
buf是否在容量上是满足一帧报文的条件,是的话下一步;否的话return - 检查是否有报文头,否的话清空
buf并return - 检查到有报头,做两件事:
-
- 将报头之前的数据切掉,只保留报头之后的
buf
- 将报头之前的数据切掉,只保留报头之后的
-
- 切剩下的
buf在容量上是否满足一帧完整的报文,否的话直接return等待buf能容纳完整的报文
- 切剩下的
-
- 前面就是确认有潜在的一帧报文会出现,也有可能报文是变长的,上面已经确认了报文结束符,下面的工作就是从上面的
buf中截取潜在的一帧报文
,做报文校验报文了 - 校验报文,校验无论是否通过,都抛掉刚才校验失败的数据,区别在不通过的话就
return, 校验通过的话就是后续报文的处理
- 首先检查整个
release 版也可以运行 assert
/* assert example */
#include <stdio.h> /* printf */
#include <assert.h> /* assert */
template <typename T>
void print_assert(T in)
{
assert(in && "print_assert assert failed \n");
}
int main()
{
int a = 10;
int * b = NULL;
int * c = NULL;
b = &a;
// print_assert(b);
// print_assert(c);
print_assert(0);
return 0;
}
写段程序验证不同平台下 x64 与 x86 下 size_t 类型;以及尝试 typeid(size_t).name
- 测试源码
#include <typeinfo> #include <stdio.h> #include <iostream> int main() { printf("typeid(size_t).name: %s \n", typeid(size_t).name()); printf("sizeof(size_t): %d \n", sizeof(size_t)); std::cin.get(); return 0; } x64打印typeid(size_t).name: unsigned __int64 sizeof(size_t): 8x86打印typeid(size_t).name: unsigned int sizeof(size_t): 4
最简单的用模板的单例函数
- Q:为什么是线程安全的?必须回答出来,A:"C++ 语言会保证静态变量的初始化是线程安全的"
#include <typeinfo> #include <stdio.h> #include <iostream> class A { public: A() = default; A(const A&) = delete; private: }; // 模板 返回值自动类型推导 template<typename T> static auto& getSingleton() { static T instance; return instance; } int main() { A& a = getSingleton<A>(); printf("typeid(a).name(): %s \n", typeid(a).name()); // class A printf("sizeof(a): %d \n", sizeof(a)); // 1 Byte }
Q:局部的 static 对象是否是在程序一开始( main 执行前),就构建出来了?
A:测试过,全局的 static 和局部的 static 发现: 全局的是 main 之前就构建出来了,而局部的不是在 main 之前就构建了,局部的 static 而言.程序运行时只开辟内存,只有当运行到该段代码上时才构建 static 对象
自旋锁,抄袭自罗剑锋老师,自己稍加改动,将 SpinLock 的 lock 和 unlock 方法改成私有,锁的效果比较好,两个子线程得到了几乎相同的执行机会
class SpinLock final // 自旋锁类
{
public:
using this_type = SpinLock; // 类型别名
using atomic_type = std::atomic_flag;
public:
SpinLock() = default; // 默认构造函数
~SpinLock() = default;
SpinLock(const this_type&) = delete; // 禁止拷贝
SpinLock& operator=(const this_type&) = delete;
friend class SpinLockGuard;
private:
atomic_type m_lock{ false }; // 成员变量初始化
private:
void lock() noexcept // 自旋锁定,绝不抛出异常
{
for (;;) { // 无限循环
if (!m_lock.test_and_set()) { // 原子变量的TAS操作
return; // TAS成功则锁定
}
std::this_thread::yield(); // TAS失败则让出线程
}
}
void unlock() noexcept // 解除自旋锁定,绝不抛出异常
{
m_lock.clear();
}
};
class SpinLockGuard final // 自旋锁RAII类,自动解锁
{
public:
using this_type = SpinLockGuard; // 类型别名
using spin_lock_type = SpinLock;
public:
SpinLockGuard(const this_type&) = delete; // 禁止拷贝
SpinLockGuard& operator=(const this_type&) = delete;
public:
SpinLockGuard(spin_lock_type& lock) noexcept
: m_lock(lock)
{
m_lock.lock();
}
~SpinLockGuard() noexcept
{
m_lock.unlock();
}
private:
spin_lock_type& m_lock;
};
SpinLock g_lock;
bool flag_exit = false;
auto sub_fun = []() {
while (!flag_exit)
{
thread_local static uint32_t count = 0;
++count;
std::cout << "this thread id:" << std::this_thread::get_id() << " count: " << count << std::endl;
SpinLockGuard locker(g_lock);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
};
int main()
{
std::thread t(sub_fun);
std::thread t1(sub_fun);
std::cin.get();
flag_exit = true;
t.join();
t1.join();
return 0;
}
互斥锁上锁失败的线程会陷入睡眠,自旋锁则是忙等待,不休眠就不会引起上下文切换,但是会比较浪费CPU。 知乎文章: (3 封私信) 如何理解互斥锁、条件锁、读写锁以及自旋锁? - 知乎 (zhihu.com)
std::string 转成数值
字符串是 "0x22" 这种字符串也能解成10进制数值
非成员函数strtof, strtod, strtold - C++中文 - API参考文档 (apiref.com)它这个示例算是进阶版的了
uint64_t tid = std::stod(root["TID"].asString());
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int main(void)
{
// 带错误处理的剖析
const char *p = "111.11 -2.22 Nan nan(2) inF 0X1.BC70A3D70A3D7P+6 1.18973e+4932zzz";
printf("Parsing '%s':\n", p);
char *end;
for (double f = strtod(p, &end); p != end; f = strtod(p, &end))
{
printf("'%.*s' -> ", (int)(end-p), p);
p = end;
if (errno == ERANGE){
printf("range error, got ");
errno = 0;
}
printf("%f\n", f);
}
// 无错误处理的剖析
printf("" -0.0000000123junk" --> %g\n", strtod(" -0.0000000123junk", NULL));
printf(""junk" --> %g\n", strtod("junk", NULL));
空了再看下:std::string 的其他函数std::basic_string - C++中文 - API参考文档 (apiref.com)
其他数值类型转 std::string就一个 std::to_string()
文档示例:std::to_string - C++中文 - API参考文档 (apiref.com)
#include <iostream>
#include <string>
int main()
{
double f = 23.43;
double f2 = 1e-9;
double f3 = 1e40;
double f4 = 1e-40;
double f5 = 123456789;
std::string f_str = std::to_string(f);
std::string f_str2 = std::to_string(f2); // 注意:返回 "0.000000"
std::string f_str3 = std::to_string(f3); // 注意:不返回 "1e+40".
std::string f_str4 = std::to_string(f4); // 注意:返回 "0.000000"
std::string f_str5 = std::to_string(f5);
std::cout << "std::cout: " << f << '\n'
<< "to_string: " << f_str << "\n\n"
<< "std::cout: " << f2 << '\n'
<< "to_string: " << f_str2 << "\n\n"
<< "std::cout: " << f3 << '\n'
<< "to_string: " << f_str3 << "\n\n"
<< "std::cout: " << f4 << '\n'
<< "to_string: " << f_str4 << "\n\n"
<< "std::cout: " << f5 << '\n'
<< "to_string: " << f_str5 << '\n';
}
流相关
- ostringstream <--> string <--> istringstream 互转
std::ostringstream header_oss; Poco::Net::MessageHeader header; header["Access-Control-Allow-Origin"] = '*'; header.write(header_oss); std::string str(header_oss.str()); std::istringstream in_stream(str); response_.read(in_stream); - 任意数值(float)转 string 可以控制精度和宽度
#include <iostream> #include <sstream> #include <iomanip> template <class T> std::string fmt(T in, int width = 0, int prec = 0) { std::ostringstream s; s << std::setw(width) << std::setprecision(prec) << in; return s.str(); } int main(){ std::string s = fmt(66.0 / 30.0, 2, 2); std::cout << s << "\n"; }
- 把一行字符串放入流中,单词以空格隔开。之后把一个个单词从流中依次读取到字符串
#include <iostream> #include <sstream> using namespace std; int main() { istringstream istr; string line,str; while (getline(cin,line))//从终端接收一行字符串,并放入字符串line中 { istr.str(line);//把line中的字符串存入字符串流中 while(istr >> str)//每次读取一个单词(以空格为界),存入str中 { cout<<str<<endl; } } system("pause"); return 1; } - C++ 处理带有空格的字符串流,把它格式化
#include <sstream> #include <string> #include <iostream> #include <vector> #include <iterator> int main() { std::stringstream istr{"hello nihao chilema nizaina?"}; std::vector<std::string> labels; std::copy(std::istream_iterator<std::string>(istr), std::istream_iterator<std::string>(), std::back_inserter(labels)); for (const auto& each: labels) { std::cout << "label:" << each << std::endl; } return 0; }
精度设置,"用于表达式 out << setprecision(n) 或 in >> setprecision(n) 时,设置流 out 或 in 的 precision 参数准确为 n 。" std::setprecision: std::setprecision - C++中文 - API参考文档 (apiref.com)
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
int main()
{
const long double pi = std::acos(-1.L);
std::cout << "default precision (6): " << pi << '\n'
<< "std::setprecision(10): " << std::setprecision(10) << pi << '\n'
<< "max precision: "
<< std::setprecision(std::numeric_limits<long double>::digits10 + 1)
<< pi << '\n';
}
区分编译器和系统的宏,经过测试,确实可以的
int main()
{
std::string os;
//系统宏
#ifdef __ANDROID__
os = "ANDROID";
#elif __linux__
os = "linux";
#elif _WIN32
os = "WIN32";
#endif
//编译器宏
#ifdef _MSC_VER
std::cout << "hello MSVC " << os << std::endl;
#elif __GNUC__
cout << "hello gcc" << " " << os << endl;
#elif __BORLANDC__
cout << "hello Borland c++" << " " << os << endl;
#endif
getchar();
}
char* 与 wchar_t* 之间的转换
C/C++中char与wchar_t之间的转换 - 云+社区 - 腾讯云 (tencent.com)
c系统调用判断文件是否存在
(153条消息) VC++判断文件或文件夹是否存在_天律界中子之不写博客不读书-CSDN博客_vc判断文件是否存在
typedef 与 using 对函数指针取别名
#include <iostream>
using namespace std;
void fun(int a);
// 函数指针
typedef void(*P)(int a);
// 类型别名
using P_FUN = void(*)(int a);
int main()
{
P a = fun;
a(32);
P_FUN b = fun;
b(2);
getchar();
return 0;
}
void fun(int a)
{
cout << a << endl;
}
数组的范围 for 和 for_each
注意 printf() 格式化 char 的方式 %c
char ch[20] = {"hello world"};
using p_fun = void(*)(const char&);
auto fun = [](const char& c) { printf("%c", c); };
int main()
{
std::cout << "ch: ";
// 范围 for
for (const auto& it : ch)
{
// std::cout << it;
printf("%c", it);
}
// for_each 搭配 lanmbda
std::for_each(std::begin(ch), std::end(ch), [](const char& c) { printf("%c", c); });
// 直接把 lambda 可调对象传入
std::for_each(std::begin(ch), std::end(ch), fun);
// for_each 搭配函数指针
p_fun print_fun = fun;
std::for_each(std::begin(ch), std::end(ch), print_fun);
getchar();
return 0;
}
printf 格式化:printf()详解之终极无惑 - 云+社区 - 腾讯云 (tencent.com)
C 库函数 – printf() | 菜鸟教程 (runoob.com)
msdn 中关于 wchar_t 版的 strlen
strlen, wcslen, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l | Microsoft Docs
strncpy, strncpy_s - C++中文 - API参考文档 (apiref.com)
耗时时间戳
参考,比较准确全面:zhuanlan.zhihu.com/p/373392670
- 粒度到秒时间戳数据结构
struct tm { int tm_sec; // seconds after the minute 0-60* int tm_min // minutes after the hour 0-59 int tm_hour; // since midnight 0-23 int tm_mday; // day of the month 1-31 int tm_mon; // months since January 0-11 int tm_year; // years since 1900 int tm_wday; // days since Sunday 0-6 int tm_yday; // days since January 1 0-365 int tm_isdst; // Daylight Saving Time flag }; - 获取该数据结构结构
/* localtime example */ #include <stdio.h> /* puts, printf */ #include <time.h> /* time_t, struct tm, time, localtime */ int main () { time_t rawtime; struct tm * timeinfo; time (&rawtime); timeinfo = localtime (&rawtime); printf ("Current local time and date: %s", asctime(timeinfo)); return 0; } - 计算耗时示例
// 推荐 auto time_stamp = std::chrono::time_point_cast<std::chrono::milliseconds, std::chrono::system_clock>(std::chrono::system_clock::now()).time_since_epoch().count(); auto time_span = time_stamp - last_pts_; AI_Log("FrameProviderSDC", KLINFO, "get raw frame once use time : {}", time_span); // 方案二 { using namespace std; auto start = chrono::system_clock::now(); ret = detect->Detect(algo_img, rects, cls, confidences); auto end = chrono::system_clock::now(); auto time_use = chrono::duration_cast<chrono::milliseconds>(end - start); AI_Log("KlSdkAlgoObjectDetect", KLINFO, "detect frame once, use time: {} ", time_use.count()); } - 最基础.分辨率最低
// 1970 年来的时间戳,秒为单位 // time_t 类型是 long time_t now = time(nullptr); - 时间戳,数值转字符串
time_t now = time(nullptr); cout << "Now is: " << ctime(&now) << endl; // Now is: Fri Jul 15 18:01:29 2022 - c++ 风格,获取时间戳
time_t time = chrono::system_clock::to_time_t(now); - c++ 风格,获取耗时,不同分辨率
auto start = chrono::steady_clock::now(); auto end = chrono::steady_clock::now(); auto time_diff = end - start; auto duration = chrono::duration_cast<chrono::seconds>(time_diff); auto duration_m = chrono::duration_cast<chrono::milliseconds>(time_diff); auto duration_n = chrono::duration_cast<chrono::nanoseconds>(time_diff); cout << "Operation cost : " << duration.count() << endl; cout << "Operation cost : " << duration_m.count() << endl; cout << "Operation cost : " << duration_n.count() << endl;
时间戳转成字符串
static std::string GenTimeStr(time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()))
{
time_t timep;
time (&timep);
char tmp[64];
strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S",localtime(&timep) );
return tmp;
}
static std::string GenTimeStr(time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()))
{
using std::chrono::system_clock;
struct std::tm * ptm = std::localtime(&t);
std::stringstream ss;
ss << std::put_time(ptm, "%Y-%m-%d-%H-%M-%S") << '\n';
return ss.str();
}
时间戳类
#pragma once
#include <iostream>
#include <string>
class Timestamp
{
public:
Timestamp();
explicit Timestamp(int64_t microSecondsSinceEpoch);
static Timestamp now();
std::string toString() const;
private:
int64_t microSecondsSinceEpoch_;
};
#include <time.h>
#include "Timestamp.h"
Timestamp::Timestamp() : microSecondsSinceEpoch_(0)
{
}
Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
: microSecondsSinceEpoch_(microSecondsSinceEpoch)
{
}
Timestamp Timestamp::now()
{
return Timestamp(time(NULL));
}
std::string Timestamp::toString() const
{
char buf[128] = {0};
tm *tm_time = localtime(µSecondsSinceEpoch_);
snprintf(buf, 128, "%4d/%02d/%02d %02d:%02d:%02d",
tm_time->tm_year + 1900,
tm_time->tm_mon + 1,
tm_time->tm_mday,
tm_time->tm_hour,
tm_time->tm_min,
tm_time->tm_sec);
return buf;
}
// #include <iostream>
// int main() {
// std::cout << Timestamp::now().toString() << std::endl;
// return 0;
// }
宽窄字节及其转换
《实用VC编程之玩转字符串》第3课:宽窄字节字符串的转换-【实用VC编程】之玩转字符串-全套教程 | 课件下载-VC驿站 (cctry.com)
《实用VC编程之玩转字符串》第2课:宽窄字节字符串的使用-【实用VC编程】之玩转字符串-全套教程 | 课件下载-VC驿站 (cctry.com)
上调至 8 的倍数以及根据申请数据块大小找到相应空下标的函数,n 从 0 起算
enum { _ALIGN = 8 }; // 小型区块的上调边界
enum { _MAX_BYTES = 128 }; // 小区区块的上限
enum { _NFREELISTS = 16 }; // _MAX_BYTES/_ALIGN free-list 的个数
// 将任何小额区块的内存需求量上调至 8 的倍数
static size_t _S_round_up(size_t __bytes)
{
return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
}
// 根据申请数据块大小找到相应空闲链表的下标,n 从 0 起算
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
}
// 注意,使用 _S_free_list 的时候,数组名本身是 _Obj**
// 所以,以 _S_free_list 加偏移量取出来的是 _Obj**
union _Obj {
union _Obj* _M_free_list_link; // 利用联合体特点
char _M_client_data[1]; /* The client sees this. */
};
stl 中 __default_alloc_template<__threads, __inst>::_S_refill(size_t __n)函数浅析
static _Obj* volatile _S_free_list[];
// 根据申请空间的大小寻找相应的空闲链表(16个空闲链表中的一个)
_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__n);
// 19 个元素(第一个元素马上要分配出去了),给每个元素的 next 指针域赋值
// 循环 19 次,最后一次将元素的 next 指针域赋为 nullptr
// 看点在逻辑的细节上
template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
int __nobjs = 20;
// 调用 _S_chunk_alloc(),缺省取 20 个区块作为 free list 的新节点
char* __chunk = _S_chunk_alloc(__n, __nobjs);
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;
// 如果只获得一个数据块,那么这个数据块就直接分给调用者,空闲链表中不会增加新节点
if (1 == __nobjs) return(__chunk);
__my_free_list = _S_free_list + _S_freelist_index(__n); // 否则根据申请数据块的大小找到相应空闲链表
/* Build free list in chunk */
__result = (_Obj*)__chunk;
*__my_free_list = __next_obj = (_Obj*)(__chunk + __n); // 第0个数据块给调用者,地址访问即chunk~chunk + n - 1
for (__i = 1; ; __i++) {
__current_obj = __next_obj;
__next_obj = (_Obj*)((char*)__next_obj + __n);
if (__nobjs - 1 == __i) {
__current_obj->_M_free_list_link = 0;
break;
}
else {
__current_obj->_M_free_list_link = __next_obj;
}
}
return(__result);
}
仿函数
class CMyFunctor
{
public:
CMyFunctor(char ch, uint16_t cnt) :m_ch(ch), m_cnt(cnt) {}
int operator()(uint16_t i) { auto ret = m_cnt * i; ++m_cnt; return ret; }
private:
char m_ch;
uint16_t m_cnt;
};
#if 1
int main()
{
uint16_t i = 10;
// 错误,定义具名对象调用构造函数,从语法上讲就不能链式调用 operator()(uint16_t)
// CMyFunctor a('H', 22)(i);
// 只能是匿名的临时对象,先定义接着调用 operator()(uint16_t)
auto ret = CMyFunctor('H', 22)(i);
std::cout << "ret: " << ret << std::endl;
// 定义具名对象
CMyFunctor a('H', 22);
ret = a(10); // 220
ret = a(10); // 230
getchar();
return 0;
}
#endif // 1
vs 编译器指定预编译宏
extern "C" 由于 C 语言的头文件可能被不同类型的编译器读取,因此写 C 语言的头文件必须慎重。
extern "C" 一般对头文件中 c 风格函数声明进行修饰
另外还有个东西叫extern "C++" 它只能被 C++ 编译器识别
参考:关于 C++ 中的 extern "C" - 知乎 (zhihu.com)
总结:写 C++ 程序时,声明 c 风格的头文件时,记到写上extern "C",而写 C++ 代码时,声明的头文件时不用管
基础数据类型
基础类型 - C++中文 - API参考文档 (apiref.com)
一些安全函数
sprintf_s
(155条消息) 详解sprintf()&sprintf_s()lanzh-CSDN博客_sprintf_s头文件fprintf_s
fprintf_s (File input/output) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 (tencent.com)fopen_s
fopen, fopen_s - C++中文 - API参考文档 (apiref.com)
(155条消息) C++ 文件读写fopen_s/fopen_yaoyaoqiekenaoo的博客-CSDN博客_c++ fopen_sstrncpy_s
strncpy_s (Strings) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 (tencent.com)localtime_s
(155条消息) localtime、localtime_s、localtime_r的使用_开源的才是世界的-CSDN博客_localtime_rprintf, fprintf, sprintf, snprintf, printf_s, fprintf_s, sprintf_s, snprintf_s官方手册
printf, fprintf, sprintf, snprintf, printf_s, fprintf_s, sprintf_s, snprintf_s - C++中文 - API参考文档 (apiref.com)mbstowcs_s
mbstowcs_s (Strings) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 (tencent.com)sscanf_s
sscanf,sscanf_s及其相关用法 - 小 楼 一 夜 听 春 雨 - 博客园 (cnblogs.com)
WaitForSingleObject 检查事件是否满足,不满足则阻塞(INFINITE 就阻塞,或者超时返回). SetEvent / ResetEvent 分别将EVENT置为发信号与不发信号。
注意作者说了这么句:当在线程里运行阻塞的函数时,就需要在退出线程 时,先要把阻塞状态变成非阻塞状态,比如使用一个线程去接收网络数据,同时使用阻塞的SOCKET时,那么要先关闭SOCKET,再发送事件信号,才可以 退出线程的。
线程中CreateEvent和SetEvent及WaitForSingleObject的用法 - 超酷小子 - 博客园 (cnblogs.com)
GetLastError 查看它:查看GetLastError返回值代表的意义-Windows & Linux 系统编程-【技术交流】-VC驿站 (cctry.com)
vs 调试时,返回值一般放在 eax 寄存器里
变长数组,柔性数组,数据域的内存申请和释放挺有意思:(167条消息) C语言0长度数组(可变数组/柔性数组)详解_OSKernelLAB(gatieme)-CSDN博客_柔性数组
不用 sizeof 求出 int 字节的位宽
#include <stdio.h>
#include <cmath>
#include <iostream>
int main()
{
int i = 0;
i--;
printf("i: %d \n", i);
unsigned long int ui = (unsigned int)i;
ui++;
std::cout << "ui:" << ui << std::endl;
unsigned long int bit_width = log2(ui);
std::cout << "bit_width: "<< bit_width << std::endl;
unsigned long int bytes_width = bit_width / 8;
std::cout << "bytes_width: " << bytes_width << std::endl;
return 0;
}
给出大量的数,比如 10000 个整数,找出最小(或最大)的 50 个,这 50 个数可能是重复的
#include <queue>
#include <ctime>
#include <stdio.h>
#include <iostream>
#include <set>
using namespace std;
int main()
{
auto cnt = 1000000;
srand((int)time(0)); // 产生随机种子 把0换成NULL也行
// 越小的数优先级越高,放在队列尾部,优先级越低越靠近 top, 这个队列区分出了最小的 n 个数
std::priority_queue<uint32_t, std::vector<uint32_t>, std::less<uint32_t>> prior_que;
// 越大的数优先级越高,放在队列尾部,优先级越低越靠近 top, 这个队列区分出了最大的 n 个数
// std::priority_queue<uint32_t, std::vector<uint32_t>, std::greater<uint32_t>> prior_que;
while (cnt--)
{
// prior_que.push(rand() % 1000);
prior_que.emplace(rand() % 1000);
if (prior_que.size() > 50)
{
// std::cout << "will be pop: " << prior_que.top() << std::endl;
// top() 位置的优先级是最低的,把它扔出容器
prior_que.pop();
}
}
while (prior_que.size())
{
std::cout << "each: " << prior_que.top() << std::endl;
prior_que.pop();
}
getchar();
return 0;
}
给出大量的数,比如 10000 个整数,找出最小(或最大)的 50 个,这 50 个数不能是重复的
int main()
{
auto cnt = 1000000;
srand((int)time(0)); // 产生随机种子 把0换成NULL也行
// 优先级按从大到小排序,就是优先保存最大的数,保存了 999 - 950
std::set<uint32_t, std::greater<uint32_t>> set_int;
// 优先级按从小到大排序,就是优先保存最小的数,保存了 0 - 49
// std::set<uint32_t, std::less<uint32_t>> set_int;
while (cnt--)
{
set_int.emplace(rand() % 1000);
if (set_int.size() > 50)
{
set_int.erase(--set_int.end());
}
}
std::cout << "set_it size: " << set_int.size() << std::endl;
for (auto& const it : set_int)
{
std::cout << "it: " << it << std::endl;
}
getchar();
return 0;
}
容器插入删除元素时更新迭代器注意
// 给vector容器中所有的奇数前面都添加一个小于奇数1的偶数 44 45 56 57
for (it1 = vec.begin(); it1 != vec.end(); ++it1)
{
if (*it1 % 2 != 0)
{
it1 = vec.insert(it1, *it1 - 1);
++it1;
}
// 注意!不应该再写这个多余的逻辑
//else
//{
// ++it1;
//}
}
对于成员函数内的 static 变量我一直理解错了,一直以为在对象间是不共享的
class B
{
public:
void change(bool flag)
{
static uint16_t i = 20;
i++;
if (flag) printf("i: %d \n", i);
}
private:
// uint16_t i = 20;
};
int main()
{
B b;
B b1;
b.change(false);
b.change(false);
b.change(true); // 23
b1.change(true); // 24
return 0;
}
判断 std::function 对象是否为空
int main()
{
std::function<void ()> fun;
if(fun == nullptr) printf("fun empty \n");
fun = [](){};
if(fun != nullptr) printf("fun not empty \n");
return 0;
}
xml node --> json
#pragma once
#include "Poco/DOM/DOMParser.h"
#include "Poco/DOM/DOMWriter.h"
#include "Poco/DOM/Document.h"
#include "Poco/DOM/Element.h"
#include "Poco/DOM/AutoPtr.h"
#include "Poco/DOM/NamedNodeMap.h"
#include "Poco/SAX/InputSource.h"
#include "Poco/DOM/TreeWalker.h"
#include "Poco/DOM/NodeFilter.h"
using Poco::XML::TreeWalker;
using Poco::XML::NodeFilter;
using Poco::XML::Element;
using Poco::XML::Document;
using Poco::XML::Text;
using Poco::XML::Node;
using Poco::XML::AutoPtr;
using Poco::XML::XMLString;
class SkipNodeFilter : public Poco::XML::NodeFilter {
short acceptNode(Poco::XML::Node *node) {
if (node->nodeType() == Poco::XML::Node::COMMENT_NODE)
return NodeFilter::FILTER_SKIP;
else
return NodeFilter::FILTER_ACCEPT;
}
};
bool MagicMessageNotifier::Init(Poco::XML::Node *entry)
{
SkipNodeFilter skip_filter;
TreeWalker iter(entry, NodeFilter::SHOW_ALL, &skip_filter);
auto node = iter.firstChild();
int threads = 0;
int tasks = 0;
while (node)
{
Element *elem = dynamic_cast<Element *>(node);
if (elem && elem->nodeName() == "DisableVideo")
{
std::string array_ability_names = elem->getAttribute("Params").data();
Json::Value root;
JSONCPP_STRING err;
Json::CharReaderBuilder builder;
const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (!reader->parse(array_ability_names.data(), array_ability_names.data() + array_ability_names.length(), &root, &err) || root.isNull() || !root.isConvertibleTo(Json::objectValue))
{
AI_Log("MagicMessageNotifier", KLERROR, "parse AiAbilityNames string to json error {}", err);
}
std::string key_name = "AiAbilityNames";
auto key_obj = root.find(key_name.data(), key_name.data() + key_name.size());
if (key_obj)
{
if (!key_obj->isArray())
{
AI_Log("MagicMessageNotifier", KLERROR, "parse AiAbilityNames error, isn't a array");
}
else
{
for (auto it_abilities = key_obj->begin(); it_abilities != key_obj->end(); ++it_abilities)
{
unset_disable_video_abilities_.insert(it_abilities->asString());
}
}
}
}
jsoncpp 相关 string 与 json 互转,原始字符串R"()"
- 很有参考价值:www.cnblogs.com/__tudou__/p…
- string 转 json
string laser = R"("{"data":[{"createdAt":"2016-08-11 04:08:30","dataFileName":"40dd8fcd-5e6d-4890-b620-88882d9d3977.data","id":0,"mapInfo":{"gridHeight":992,"gridWidth":992,"originX":-24.8,"originY":-24.8,"resolution":0.05000000074505806},"name":"demo","status":4}],"successed":true}")" ; Json::Value tree; Json::String err; Json::CharReaderBuilder reader; std::unique_ptr<Json::CharReader>const json_read(reader.newCharReader()); json_read->parse(laser.c_str(), laser.c_str() + laser.length(), &tree,&err); - json 转 string
Json::Value root; root["map_name"] = "test"; root["position_name"] = "test1"; Json::StreamWriterBuilder writerBuilder; std::unique_ptr<Json::StreamWriter> json_write(writerBuilder.newStreamWriter()); std::ostringstream ss; json_write->write(root, &ss); std::string strContent = ss.str(); std::cout << strContent << std::endl; - jsoncpp 解析 json 并判断是否解析成功
std::string json_str = params; Json::Value root; JSONCPP_STRING err; Json::CharReaderBuilder builder; const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); if (!reader->parse(json_str.data(), json_str.data() + json_str.length(), &root, &err) || root.isNull() || !root.isConvertibleTo(Json::objectValue)) { AI_Log(kModuleName, KLERROR, "parse string to json error {}", err); return false; } - json 中取出数组,并放到容器里
models_.clear(); if (root["models"].isArray()) { auto models_arrays = root["models"]; std::transform(models_arrays.begin(), models_arrays.end(), std::back_inserter(models_), [](const Json::Value &js_model) { return js_model.asString(); }); } else if (root["model"].isString()) { models_.push_back(root["model"].asString()); } else { AI_Log(kModuleName, KLERROR, "must set model or models"); return false; } - json 中取某个键的值,需要确保该键存在
std::string key_tmp = "interval"; auto key_value_js = root.find(key_tmp.data(), key_tmp.data() + key_tmp.size()); if (key_value_js) { min_interval_ = key_value_js->asFloat() * 1000; } - c++ 获取时间戳对象(system_clock::time_point对象),并将时间戳对象转成毫秒精度的时间戳数值
std::int64_t ConvertTimestamp(system_clock::time_point& tp) { time_point<system_clock, milliseconds> new_tp = time_point_cast<milliseconds>(tp); auto tmp = duration_cast<milliseconds>(new_tp.time_since_epoch()); return tmp.count(); }举一反三,时间戳对象也可以转成秒为单位的数值使用时 auto ts = high_resolution_clock::now(); auto time_stamp = ConvertTimestamp(ts);time_point<system_clock, seconds> new_tp = time_point_cast<seconds>(tp); auto tmp = duration_cast<seconds>(new_tp.time_since_epoch()); return tmp.count();
Poco 相关
处理跨域,http header 部分添加字段
response.add("Access-Control-Allow-Origin","*");
response.add("Access-Control-Allow-Methods","POST,GET,OPTIONS,DELETE");
response.add("Access-Control-Max-Age","3600");
response.add("Access-Control-Allow-Headers","x-requested-with,content-type");
response.add("Access-Control-Allow-Credentials","true");
.hpp 注意
c++中的.hpp文件 - _Mr_y - 博客园 (cnblogs.com)
-
不可包含全局对象和全局函数 由于hpp本质上是作为.h被调用者include,所以当hpp文件中存在全局对象或者全局函数,而该hpp被多个调用者include时,将在链接时导致符号重定义错误。要避免这种情况,需要去除全局对象,将全局函数封装为类的静态方法。
-
类之间不可循环调用 在.h和.cpp的场景中,当两个类或者多个类之间有循环调用关系时,只要预先在头文件做被调用类的声明即可,
-
不可使用静态成员
map 与 unordered_map 的简析
【转载】map与unordered_map的性能对比 - zeroPatrick - 博客园 (cnblogs.com)
提高代码安全性,注意需要检查的事项,值得阅读
漫谈 C++ 的各种检查 | BOT Man JL (bot-man-jl.github.io)
并行编程
- 查看模型相关信息(比特大陆)
/system/bin/bm_model.bin --info FaceDetect_b1-810-1440_20201022.bmodel - 查看显卡信息
/system/bin/bm-smi
计算两个向量夹角,可以看出方向,及谁超前谁
#include <iostream>
#include <cmath>
struct Point
{
Point() = default;
Point(int x_in, int y_in):x(x_in),y(y_in){};
int x;
int y;
};
const double CV_PI = 3.1415926;
// 以pt1为基准
float getAngelOfTwoVector(Point &pt1, Point &pt2, Point &c)
{
float theta = atan2(pt1.y - c.y, pt1.x - c.x) - atan2(pt2.y - c.y, pt2.x - c.x);
if (theta > CV_PI)
theta -= 2 * CV_PI;
if (theta < -CV_PI)
theta += 2 * CV_PI;
theta = theta * 180.0 / CV_PI;
return theta;
}
struct Line
{
Point start;
Point finish;
};
// 表从 v1 逆时针转多少度到 v,也表示 v 超前 v1 的度数,逆时针即为超前
float getAngelOfTwoVector(const Line& v, const Line& v1)
{
float theta = atan2(v.finish.y - v.start.y, v.finish.x - v.start.x) - atan2(v1.finish.y - v1.start.y, v1.finish.x - v1.start.x);
if (theta > CV_PI)
theta -= 2 * CV_PI;
if (theta < -CV_PI)
theta += 2 * CV_PI;
theta = theta * 180.0 / CV_PI;
return theta;
}
int main()
{
// Point c(0, 0);
// Point pt1(4, 3);
// Point pt2(4, 3);
// float theta = getAngelOfTwoVector(pt1, pt2, c);
// std::cout << "theta: " << theta << std::endl;
Line v;
v.start.x = 0;
v.start.y = 0;
v.finish.x = -4;
v.finish.y = 1;
Line v1;
v1.start.x = 0;
v1.start.y = 0;
v1.finish.x = 4;
v1.finish.y = 0;
float theta = getAngelOfTwoVector(v1, v);
std::cout << "theta: " << theta << std::endl;
return 0;
}