C++技法汇总

1,107 阅读20分钟

大小端字节序转换,对称

// 大小端字节序转换
#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初始化vector
    std::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*赋给string
    class 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_queue
    std::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() (特点:内存线性容器(vector array) 以及其他的有序(逻辑序,不是内存序)容器没有成员函数版的查找)
  • map set(关联容器)使用成员函数版的find() count() (特点:内存非线性的(map set)提供成员函数版的查找方法)
    // 
    #include <algorithm>
    // count
    templateclass InputItclass 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::functionstd::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  

floats 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,被映射到 -126127 中的 0 (e - 127 = e的映射)      

IEEE754逐位设置验证网站:IEEE-754 Floating Point Converter (h-schmidt.net) float的sef表示1,利用IEEE754.png

严格编译规范,如果函数的入参不用,则用宏关闭它

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
    • 检查是否有报文头,否的话清空 bufreturn
    • 检查到有报头,做两件事:
        1. 将报头之前的数据切掉,只保留报头之后的 buf
        1. 切剩下的 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;
}

写段程序验证不同平台下 x64x86size_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): 8  
    
  • x86 打印
    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 对象

自旋锁,抄袭自罗剑锋老师,自己稍加改动,将 SpinLocklockunlock 方法改成私有,锁的效果比较好,两个子线程得到了几乎相同的执行机会

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";
    }
    

参考:blog.csdn.net/cckluv/arti…

  • 把一行字符串放入流中,单词以空格隔开。之后把一个个单词从流中依次读取到字符串
    #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判断文件是否存在

typedefusing 对函数指针取别名

#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;
}

数组的范围 forfor_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(&microSecondsSinceEpoch_);
    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)

宽窄字节转换.png

上调至 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 编译器指定预编译宏

编译器添加宏开关.png

extern "C" 由于 C 语言的头文件可能被不同类型的编译器读取,因此写 C 语言的头文件必须慎重。

extern "C" 一般对头文件中 c 风格函数声明进行修饰
另外还有个东西叫extern "C++" 它只能被 C++ 编译器识别
参考:关于 C++ 中的 extern "C" - 知乎 (zhihu.com)
总结:写 C++ 程序时,声明 c 风格的头文件时,记到写上extern "C",而写 C++ 代码时,声明的头文件时不用管

基础数据类型

基础类型 - C++中文 - API参考文档 (apiref.com)
基础数据类型-1.png
基础数据类型.png
基础数据类型1.png

一些安全函数

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;
}