C++语法(综合)

C++ reference:https://en.cppreference.com/w/
C++ 参考手册介绍(中文)
https://cplusplus.com/

cherno c++【bilibili】
于仕琪c++
韩曙亮c++专栏
泡沫o0 c++专栏

0、基本概念

0.1 数据类型中的:_t

t代表“type”或“typedef”。 很多带有time_t 、size_t、ssize_t, 这些基于操作系统和机器架构保持(不一定定义)特定位大小。_t 表示这些数据类型是通过typedef定义的,而不是新的数据类型。也就是说,它们其实是我们已知的类型的别名。

1. foreach的退出

在标准C++中,并没有foreach关键字。但是在QT中,可以使用这一个关键字,其主要原因是QT自己增加了这一个关键字,就像slots和signals、emit等一样。增加的foreach关键字在编译时会进行预处理。

QStringList slt = {"abc", "qwe", "upo"};
foreach(QString s , slt )
{
    cout<<s<<endl;
}
// 输出结果为:
abc
qwe
upo

1、return:跳出forEach当前循环,并不能跳出整个循环

arr.forEach((a, i) => {
    if (i === 2) {
        return
    }
    console.log('forEach===return', a)
})

// 结果
// forEach===return 1
// forEach===return 2
// forEach===return 4
// forEach===return 5

2、跳出整个循环,需要throw抛异常:

try {
    arr.forEach((a, i) => {
        if (i === 2) {
            throw new Error()
        }
        console.log('forEach===throw', a)
    })
} catch (e) {
    console.log(e)
}

2. 单例模式的懒加载

//.h
public:
    static CManager *GetInstance();
private:
    static CManager *m_pInstance;
    explicit CManager(QObject *parent = nullptr);
//.cpp
//类外
CManager *CManager::m_pInstance = NULL;
CManager *CManager::GetInstance()
{
       if(NULL == m_pInstance)
           m_pInstance = new CManager;
       return m_pInstance;
}

3. 声明函数=0(纯虚函数)

纯虚函数是在声明虚函数时被“初始化”为0的函数。并不是返回值为0。

//基类中
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;

4. virtual、override、final

1、virtual函数是在运行的时候来确定具体调用哪个类。

#include<iostream>  
using namespace std;  
  
class A  
{  
public:  
    void foo()  
    {  
        printf("1\n");  
    }  
    virtual void fun()  
    {  
        printf("2\n");  
    }  
};  
class B : public A  
{  
public:  
    void foo()  //隐藏:派生类的函数屏蔽了与其同名的基类函数
    {  
        printf("3\n");  
    }  
    void fun()  //多态、覆盖
    {  
        printf("4\n");  
    }  
};  
int main(void)  
{  
    A a;  
    B b;  
    A *p = &a;  
    p->foo();  //输出1
    p->fun();  //输出2
    p = &b;  
    p->foo();  //取决于指针类型,输出1
    p->fun();  //取决于对象类型,输出4,体现了多态
    return 0;  
}

2、override 仅在成员函数声明之后使用时才是区分上下文的且具有特殊含义;否则,它不是保留的关键字。使用 override 有助于防止代码中出现意外的继承行为。


class BaseClass
{
  virtual void funcA();
  virtual void funcB() const;
  virtual void funcC(int = 0);
  void funcD();
};
//没使用override,编译器不报错,但声明了新的函数(没重载)
class DerivedClass: public BaseClass
{
  virtual void funcA(); // 重写了虚函数 
  virtual void funcB(); // 没有const,这是一个新的函数 ,隐藏父类的funcB函数
  virtual void funcC(double = 0.0); //参数类型不一样,这是一个新的函数,隐藏父类的funcC函数
};
//使用override,编译器会报错
class DerivedClass: public BaseClass
{
  virtual void funcA() override; // 重写
  virtual void funcB() override; // compiler error: DerivedClass::funcB() does not override BaseClass::funcB() const
 
  virtual void funcC( double = 0.0 ) override; // compiler error: DerivedClass::funcC(double) does not override BaseClass::funcC(int)
 
  void funcD() override; // compiler error: DerivedClass::funcD() does not override the non-virtual BaseClass::funcD()
};

3、C++11的关键字final有两个用途:(1)、禁止虚函数被重写;(2)、禁止基类被继承。
(1)阻止子类覆写虚函数

struct Base {
	virtual void foo();
};
struct A : Base {
	virtual void foo() final; // A::foo is final
	// void bar() final; // Error: non-virtual function cannot be final
};
struct B final : A { // struct B is final
	// void foo(); // Error: foo cannot be overridden as it's final in A
};
// struct C : B { }; // Error: B is final
 
struct A_ {
	virtual void func() const;
};
struct B_ : A_ {
	void func() const override final; //OK
};
// struct C_ : B_ { void func()const; }; //error, B::func is final

(2)禁止继承。直接用在类上,紧跟着类名,表示这个类禁止任何其他类继承它。

 class DontDeriveFromMe final {
   // ...
 }; 
 class Failure : public DontDeriveFromMe { //ERROR
   // ...
 };

5. 结构体按字节存,不对齐

#pragma pack(1)
#pragma pack()

6. 拷贝构造函数

拷贝构造函数与移动构造函数
C/C++开发基础——拷贝构造/移动构造/委托构造
浅拷贝:指针直接指向原对象的指针地址;
深拷贝:为指针new出空间,将原对象指针的数据复制到新空间。

#include <iostream>  
using namespace std;  
  
class CExample 
{  
private:  
    int a;  
public:  
    //构造函数  
    CExample(int b)  
    { a = b;}  
      
    //拷贝构造函数  函数名称必须和类名称一致,一个必要参数是本类型的一个引用变量。
    CExample(const CExample& C)  
    {  
        a = C.a;  
    }  
  
    //一般函数  
    void Show ()  
    {  
        cout<<a<<endl;  
    }  
};  
  
int main()  
{  
    CExample A(100);  
    CExample B = A; // CExample B(A); 也是一样的  
     B.Show ();  
    return 0;  
}   

7. const用法

c语言中的const

const int * n;  //const 在*号左边,所指物为const:常量指针
int *const n;   //const 在*号右边,其本身为const:指针常量
//.h中
const int m_iCount;//const成员变量使用有参构造函数初始化,之后不能修改
void myInfo() const; //常成员函数,只能由常对象调用
//.cpp中
Sheep::Sheep(int count):m_iCount(count)
{
}
//类外
const Sheep sheep;
sheep.myInfo();

8. static用法

静态成员变量:类内定义类外初始化,可通过类或对象访问,存储在类外全局。
静态成员函数:可通过类或对象访问,属于类不属于对象,不能用this指针调用。

//.h中
static int sum;
static int getLen();
//.cpp中 
int Sheep::sum=0; //类外初始化

9. 数组初始化

数组初始化的几种方式

char array[ARRAY_SIZE_MAX] = {0};  //声明时使用{0}初始化为全0,'\0'的码就是0。只要有1个被初始化,其他的编译器会自动初始化为0  
char array1[ARRAY_SIZE_MAX];  
memset(array1, 0, ARRAY_SIZE_MAX);  //使用memset方法   

delete p 会调用一次析构函数
delete[] p会调用每个成员的析构函数

class A 
{
public:
	~A() { cout << "A的析构函数" << endl; }
};
int main() 
{
	A* pA = new A[10];
	delete[]pA;//输出10次A的析构函数
	//delete pA;//会出错,因为只析构了一次

	A* pA1 = new A;
	delete pA1;//输出一次A的析构函数
	//delete[] pA1;//编译器不会报错,但是系统会一直调用析构函数

	int* pInt = new int[10];
	delete[] pInt;
	//delete pInt;//不会出错
	int* pInt1 = new int;
	delete pInt1;
	//delete[] pInt1;//不会出错
	
	return 0;
}
unsigned int uiLen = 100;
unsigned char *pBuf;
pBuf = new unsigned char[uiLen];
if(pBuf)
{
    delete[] pBuf;
    pBuf = NULL;
}

10. bind()、function()、mem_fn

10.1. bind

bind使用分类详解
C++11 bind函数

(1)绑定普通函数

void fun1(int n1, int n2, int n3)
{
    cout << n1 << " " << n2 << " " << n3 << endl;
}
int main()
{     
    auto f1 = bind(fun1, 11, 22, _1); //原fun1接受三个参数,其中绑定了2个,第三个参数由新的可调用对象指定 
    f1(33);                                                                                                                                  
}
int plus(int a,int b)
{
   return a+b;
}
int main()
{
  //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
   function<int<int,int>> func1 = std::bind(plus, placeholders::_1, placeholders::_2);
   
  //func2的类型为 function<void(int, int, int)> 与func1类型一样
   auto  func2 = std::bind(plus,1,2);   //表示绑定函数 plus 的第一,二为: 1, 2 
   cout<<func1(1,2)<<endl; //3
   cout<<func2()<<endl; //3
   retunrn 0;
}

(2)绑定类成员函数

class A
{
public:
    void print(int n1, int n2, int n3)
    {
        cout << n1 << " " << n2 << " " << n3 << endl;
    }
};
int main()                                                                                                                                   
{
    A a;      
    auto f1 = bind(&A::print, &a, _2, 22, _1); //类成员函数需要绑定该类的this指针
    f1(44,55);
}
class Plus
{
   public:
   	int plus(int a,int b)
   	{
   	    return a+b;
   	}
}
int main()
{
   Plus p;
   // 指针形式调用成员函数
   function<int<int,int>> func1 = std::bind(&Plus::plus,&p, placeholders::_1, placeholders::_2);
  // 对象形式调用成员函数
   function<int<int,int>> func2 = std::bind(&Plus::plus,p, placeholders::_1, placeholders::_2);
   cout<<func1(1,2)<<endl; //3
   cout<<func2(1,2)<<endl; //3
   retunrn 0;
}

(3)std::bind(和闭包)

大多数的编程范式都提供了提高代码重用的方式,比如在面向对象编程中,我们可以通过抽象出特定类来将复杂系统拆分成小的组件,在降低耦合的同时也可以分开设计、测试代码。

在函数式编程中,通过组合现有的函数,我们可以创造出新的函数。标准库中的std::bind就是可以创造闭包(closure)的工具。

#include <algorithm>class Foo
{
 public:
  void methodA();
  void methodInt(int a);
};
class Bar
{
 public:
  void methodB();
};void main()
{
    std::function<void()> f1; 				// 无参数,无返回值
​
    Foo foo;
    f1 = std::bind(&Foo::methodA, &foo);	//将this指针在形参上去掉,即将this包进去
    f1(); // 调用 foo.methodA();
    Bar bar;
    f1 = std::bind(&Bar::methodB, &bar);
    f1(); // 调用 bar.methodB();
​
    f1 = std::bind(&Foo::methodInt, &foo, 42);
    f1(); // 调用 foo.methodInt(42);
​
    std::function<void(int)> f2; 					// int 参数,无返回值
    f2 = std::bind(&Foo::methodInt, &foo, _1);		//将类指针包进去,指定一个形参
    f2(53); // 调用 foo.methodInt(53);
}

通过std::bind,我们可以为同一个类的不同对象可以分派不同的实现,从而实现不同的行为。这种方式使得我们不在需要设计通过继承与虚函数来实现多态,无疑为程序库设计提供的新的方式。

10.2 function

C++11 function类模板

typedef function<void(SP_CONNECTION)> CONNECTION_CALLBACK;		//类型定义
CONNECTION_CALLBACK  m_fnConnectionCallback;					//对象声明
void SetConnectionCallback(const CONNECTION_CALLBACK& cb){m_fnConnectionCallback=cb;}	//设置函数
void CTcpConnection::OnEstablished()
{	
	m_fnConnectionCallback(shared_from_this());		//底层执行回调函数
}
client.SetConnectionCallback(bind(&CClientDelegate::onConnection,this,_1));		//在上层设置回调函数

10.3 mem_fun

auto fnWithParams = std::mem_fun(&Test::FnWithParams)
fnWithParams(Test{}, 1, 2);

11. 回调函数

11.1 应用场景

(1) 事件驱动机制

为了简单说明该机制,我们假定有两个类,类A与类B。该模式的工作机制如下:
类A提供一个回调函数F,该回调函数将根据不同的参数,执行不同的动作; 类A在初始化类B时,传入回调函数F的函数指针pF;
类B根据需要在不同的情况下调用回调函数指针pF,这样就实现了类B来驱动类A,类A来响应类B的动作。

(2)通信协议的“推”模式

“推”模式下,假定对象A要从对象B中获取实时数据信息,“推”模式的工作机制如下:
(1)对象A在调用对象B时,向其传递一个回调函数;(留个联系方式,方便联系)
(2)对象B一旦有新的信息,就调用对象A传递过来的函数指针,将最新的信息发送给对象A。(利用A的联系方式)

(3) 用于层间协作

​回调函数写在高层,底层通过一个函数指针保存这个函数,在某个事件的触发下,底层通过该函数指针调用高层那个函数(回调函数)。

11.2 各类型示例程序

回调函数的形式:全局函数、non capturing lambda、静态成员成员函数、普通成员函数(必须用bind()将this指针包装进去

(1)回调函数的指针:

typedef void (*pfunc)(int element);         //返回值为void  参数为int
typedef std::function<int(int)> Functional;  //返回值为int 参数为int

(2)注册回调函数:将回调函数赋给回调函数的指针

EchoServer::EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress& listenAddr)
           : server_(loop, listenAddr, "EchoServer")
{
	//注册回调函数,onConnection成员函数前的 “&” 必不可少。回调函数不是静态成员函数,要使用指向普通成员函数的指针。
  server_.setConnectionCallback(std::bind(&EchoServer::onConnection, this, _1));
  server_.setMessageCallback(std::bind(&EchoServer::onMessage, this, _1, _2, _3));
}

//TcpServer.h   注册回调函数
  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

//echo.h       回调函数
void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
  LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
}

(3)静态成员函数作回调
作为回调函数的可以是全局函数、类静态成员函数、类普通成员函数等方法。
使用静态成员函数作回调,又要在其中调用普通成员函数,则必须将类对象的指针在回调函数参数中传回来。

class A 
{
public:
    static void test(void *pData) 
    {
        A *a = static_cast<A*>(pData);
        a->m_a += 1;
    }   
private:
    static int m_staticA;
    int m_a
};

11.3 应用

#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <string>
#include <algorithm>
#include <functional>
#include <memory>
using namespace std;
 
//声明一个模板-(函数指针)-----------
typedef std::function<int(int)> Functional; 

//normal function--(回调函数)--------------
int TestFunc(int a)
{
    return a;
} 

//lambda expression--------
auto lambda = [](int a)->int{return a;};
 
//functor仿函数----------------
class Functor
{
public:
    int operator() (int a)
    {
        return a;
    }
}; 
 
//类的成员函数和类的静态成员函数-----
class CTest
{
public:
    int Func(int a)
    {
        return a;
    }
    static int SFunc(int a)
    {
        return a;
    }
}; 
 
int main(int argc, char* argv[])
{
    //封装普通函数
    Functional obj = TestFunc;   //注册回调函数---
    int res = obj(0);            //调用回调函数---
    cout << "normal function : " << res << endl;
 
    //封装lambda表达式
    obj = lambda;
    res = obj(1);
    cout << "lambda expression : " << res << endl;
 
    //封装仿函数
    Functor functorObj;
    obj = functorObj;
    res = obj(2);
    cout << "functor : " << res << endl;
 
    //封装类的成员函数和static成员函数
    CTest t;
    obj = std::bind(&CTest::Func, &t, std::placeholders::_1);
    res = obj(3);
    cout << "member function : " << res << endl;
 
    obj = CTest::SFunc;
    res = obj(4);
    cout << "static member function : " << res << endl;
 
    return 0;
}

下面这个是从回调函数参数的不同来解析:
C++中回调函数的使用
又一个容易理解的例子:

#include "stdafx.h"
#include<iostream>// std::cout
#include<functional>// std::function
 
class A
{
public:
    int i_ = 0; // C++11允许非静态(non-static)数据成员在其声明处(在其所属类内部)进行初始化
 
    void output(int x, int y)
    {
        std::cout << x << "" << y << std::endl;
    }
 
};
 
int main()
{
    A a;
    // 绑定成员函数,保存为仿函数
    std::function<void(int, int)> fr = std::bind(&A::output, &a, std::placeholders::_1, std::placeholders::_2);
    // 调用成员函数
    fr(1, 2);
 
    // 绑定成员变量
    std::function<int&(void)> fr2 = std::bind(&A::i_, &a);
    fr2() = 100;// 对成员变量进行赋值
    std::cout << a.i_ << std::endl;
 
 
    return 0;
}

12. 动态库调用

多个进程调用共享动态库
多个进程调用同一动态库,共享的是代码段,而数据段各是各的。
同一进程中不同线程调用同一动态库,数据段是一样的。

12.1 动态库导出函数符号

extern "C"
{
#ifdef MC_OS_WIN
    __declspec(dllexport)
#else
    __attribute((visibility("default")))
#endif
    IBundleActivator* GetIActivator()
    {
        static CActivator s_objActivator;
        return static_cast<IBundleActivator*>(&s_objActivator);
    }
}

12.2 取值库中函数地址

int CBundle::StartPlugin()
{
    int iRet = -1;
    typedef IBundleActivator* (*GetIActivatorPtr)();  //函数指针类型定义
    GetIActivatorPtr GetActivator;
    GetActivator = reinterpret_cast<GetActivatorPtr>(m_pLibrary->GetAddress("GetIActivator"));
    IBundleActivator* pIActivator = GetIActivator();
    iRet = pIActivator->Start(m_pContext);
    return iRet;
}
#include <dlfcn.h>
void* CLibrary::GetAddress(const char* pszProcName)
{
    void* pFuncRet = nullptr;
#ifdef MC_OS_WIN
    pFuncRet = (void*)::GetProcAddress((HMODULE)m_pLibrary, pszProcName);
#else
    pFuncRet = (void*)dlsym(m_pLibrary, pszProcName);
#endif
    return pFuncRet;
}

13.内存管理

c++内存管理全景

14. stl

STL解析

14.1 vector

reserve()只修改capacity大小,不修改size大小,
resize( ) 既修改capacity大小,也修改size大小。

15. 结构体

1、对齐
#pack(1)
#pack()
2、定义及初始化
①结构体的构造函数中初始化。
在C++中,结构体与类在使用上已没有本质上的区别了,所以可以使用构造函数来初始化。如下代码所示:

struct Stu
{
int nNum;
bool bSex;
char szName[20];
char szEmail[100];

//构造函数初始化
Stu()
{
nNum = 0;
bSex = false;
memset(szName,0,sizeof(szName));
memset(szEmail,0,sizeof(szEmail));

}
};

你可能已经发现了,如果结构体中有大量成员,一个个赋值,相当麻烦。那么你可以这样写:

struct Stu
{
int nNum;
bool bSex;
char szName[20];
char szEmail[100];

//构造函数初始化
Stu()
{
memset(this,0,sizeof(Stu));
//或者是下面的格式
//memset(&nNum,0,sizeof(Stu));

}
};

如果在结构体中分配了指针,并且指针指向一个堆内存,那么就在析构函数中释放。以上便是在构造函数中初始化。

②继承模板类初始化

首先定义一个模板基类:
template
class ZeroStruct
{
public:
ZeroStruct()
{
memset(this,0,sizeof(T));
}
};

之后定义的结构体都继承于此模板类。

struct Stu:ZeroStruct<Stu>
{
    int        nNum;
    bool    bSex;
    char    szName[20];
    char    szEmail[100];
};


定义
struct InitMember
{
int first;
double second;
char* third;
float four;
};
③方法一:定义时赋值
struct InitMember test = {-10,3.141590,“method one”,0.25};
1
1
需要注意对应的顺序,不能错位。

④方法二:定义后逐个赋值
struct InitMember test;

test.first = -10;
test.second = 3.141590;
test.third = “method two”;
test.four = 0.25;
因为是逐个确定的赋值,无所谓顺序啦。

⑤方法三:定义时乱序赋值(C风格)
这种方法类似于第一种方法和第二种方法的结合体,既能初始化时赋值,也可以不考虑顺序;

struct InitMember test = {
.second = 3.141590,
.third = “method three”,
.first = -10,
.four = 0.25
};

16. RTTI

RTTI

17. 内存泄露

new delete malloc free务必成对
实现了一个DMP服务,其内存占用仍然居高不上。用了各种内存检测工具,加上了日志(申请内存和释放内存都加上),发现仍然没有问题,于是就去研究底层源码,即使调用了delete或者free,内存也不会立即释放给操作系统,而是被放在缓冲区中(这就是内存占用比较高的原因),等间隔一段时间,才会被操作系统回收,如果想立即被操作系统回收,需要malloc_trim(0)。

还是同一个DMP服务,由于其内存占用过大,导致性能不是很高,大量的Misscache,最终,通过将系统中page大小从默认的4k变为32k,解决了这个问题(这是因为page过小的话,导致大量的缺页中断,具体可参考<深入理解操作系统>)。

18. 向上取整除法

int ans = a%b ? a/b + 1 : a/b;

19. 虚析构函数

解决父类指针指向子类对象时,不调用子类的析构函数。将父类的析构函数声明为虚函数,就能调用子类的析构函数。

20. 运算符重载

C++运算符重载大全

21. std::call_once 只执行一次

std::once_flag flag1; 
void simple_do_once()
{
    std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}

22. 动态属性

C++ 创建动态属性

22.1 利用模板实现

#include <string>
#include <map>
using namespace std;
//单属性基类
class Prop
{
public:
    virtual ~Prop()
    {
    }
};
//单属性类
template<typename T>
class Property : public Prop
{
private:
    T data;
    bool isDirty;
public:
    virtual ~Property()
    {
    }

    Property(T d) : isDirty(true)
    {
        data = d;
    }

    void SetValue(T& d)
    {
        data = d;
    }
    
    T GetValue()
    {
        return data;
    }
};
//属性集
class Properties
{
private:
    map<string, Prop*> props;
public:
    ~Properties()
    {
        map<string, Prop*>::iterator iter;

        for(iter = props.begin(); iter != props.end(); ++iter)
            delete (*iter).second;

        props.clear();
    }

    template<typename T>
    void Add(string name, T data)
    {
        if (props.count(name) != 0)
        {
            props[name]->SetValue(data);
        }
        else
        {
            props[name] = new Property<T>(data);
        }
    }

    template<typename T>
    T Get(string name)
    {
        Property<T>* p = (Property<T>*)props[name];
        return p->GetValue();
    }
    
    bool IsDirty(string name)
    {
        if (props.count(name) == 0)
        {
            return false;
        }
        return props[name]->IsDirty();
    }
};
//使用环境
int main()
{
    Properties p;

    p.Add<int>("age", 10);

    int age = p.Get<int>("age");

    return 0;
}

23. 多线程和并发

C++并发编程
互斥对象:std::mutex、std::shared_mutex、std::recursive_mutex、timed_mutex、std::recursive_timed_mutex
互斥锁:std::lock_guard、std::unique_lock、std::shared_lock

std::mutex mtx;
std::lock_guard<std::mutex> lck (mtx); 	//在lock_guard对象lck作用域内自动加锁
std::shared_mutex mutex_;  //互斥对象
int value_ = 2;  
unsigned int read() const {      
	std::shared_lock<std::shared_mutex> lock(mutex_);// shared_lock共享锁
	return value_;  
}  
void write() {      
	std::unique_lock<std::shared_mutex> lock(mutex_);// unique_lock独占锁
	value_++; 
}  
//递归锁可以允许一个线程对同一互斥量多次加锁,解锁时,需要调用与lock()相同次数的unlock()才能释放使用权
#include <iostream>
#include <thread>
#include <mutex>
 
class X
{
    std::recursive_mutex m;
    std::string shared;
  public:
    void fun1() 
    {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun1";
      std::cout << "in fun1, shared variable is now " << shared << '\n';
    }
    void fun2() 
    {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun2";
      std::cout << "in fun2, shared variable is now " << shared << '\n';
      fun1(); // ① 递归锁在此处变得有用
      std::cout << "back in fun2, shared variable is " << shared << '\n';
    };
};
 
int main() 
{
    X x;
    std::thread t1(&X::fun1, &x);
    std::thread t2(&X::fun2, &x);
    t1.join();
    t2.join();
}
//std::timed_mutex是待超时的独占互斥量,他有两个方法:try_lock_for()和try_lock_until(),一个参数是时间段,一个参数是时间点
//(1)try_lock_for()是等待一段时间,如果拿到了锁,或者超时了未拿到锁,就继续执行(有选择执行)
std::timed_mutex m_mutex; //定义一个timed_mutex变量

std::chrono::milliseconds timeout(100);
if (m_mutex.try_lock_for(timeout)){
    //......拿到锁返回ture
}
else{
    std::chrono::milliseconds sleeptime(100);
    std::this_thread::sleep_for(sleeptime);
}
//(2)try_lock_until()参数是一个未来的时间点,在这个未来的时间没到的时间内,如果拿到了锁头,流程就走下来,如果时间到了没拿到锁,流程也可以走下来。
std::timed_mutex m_mutex; //定义一个timed_mutex变量

std::chrono::milliseconds timeout(100);
if (m_mutex.try_lock_until(chrono::steady_clock::now() + timeout)){
    //......拿到锁返回ture
}
else{
    std::chrono::milliseconds sleeptime(100);
    std::this_thread::sleep_for(sleeptime);
}

24. define的特殊用法

#define宏定义中的##、@#、#、\

#define Conn(x,y) x##y		//连接
#define ToChar(x) #@x		//字符化,将传的单字符参数名转换成字符
#define ToString(x) #x		//字符串化,
#define ToString(x) \		//行继续
 #x

25. 池化技术(线程池、连接池、对象池)

连接池、线程池、内存池、异步请求池

26. C++11中新增的int类型

这些类型的位数是确定的,类似于quint8、quint16等

#include <cstdint>
int8_t		uint8_t		INT8_MIN	INT8_MAX  UINT8_MIN/MAX
int16_t		uint16_t
int32_t		uint32_t
int64_t		uint64_t

27. double的判断

double比较等问题

浮点数赋值给整数,是舍去小数位。
运算的数据类型有4种:int, long, folat, double

if(fabs(f1-f2) < FLT_EPSILON)  //f1与f2相等
float f1 = 2.0f / 0.0f; 		//f1就是inf无穷大
inf: (符号位表±,指数位=11111111,小数位=0//正无穷大,负无穷大
nan:(           指数位=11111111,小数位!0) //不是一个数

double eps=1e-8
int sgn(double a)
{
	return (a < -eps) ? -1 : (a < eps) ? 0 : 1;
}

QGis中关于double的判断:

inline bool qgsDoubleNear( double a, double b, double epsilon = 4 * std::numeric_limits<double>::epsilon() )
{
  const bool aIsNan = std::isnan( a );
  const bool bIsNan = std::isnan( b );
  if ( aIsNan || bIsNan )
    return aIsNan && bIsNan;

  const double diff = a - b;
  return diff > -epsilon && diff <= epsilon;
}

/**
 * Compare two floats (but allow some difference)
 * \param a first float
 * \param b second float
 * \param epsilon maximum difference allowable between floats
 */
inline bool qgsFloatNear( float a, float b, float epsilon = 4 * FLT_EPSILON )
{
  const bool aIsNan = std::isnan( a );
  const bool bIsNan = std::isnan( b );
  if ( aIsNan || bIsNan )
    return aIsNan && bIsNan;

  const float diff = a - b;
  return diff > -epsilon && diff <= epsilon;
}

//! Compare two doubles using specified number of significant digits
inline bool qgsDoubleNearSig( double a, double b, int significantDigits = 10 )
{
  const bool aIsNan = std::isnan( a );
  const bool bIsNan = std::isnan( b );
  if ( aIsNan || bIsNan )
    return aIsNan && bIsNan;

  // The most simple would be to print numbers as %.xe and compare as strings
  // but that is probably too costly
  // Then the fastest would be to set some bits directly, but little/big endian
  // has to be considered (maybe TODO)
  // Is there a better way?
  int aexp, bexp;
  const double ar = std::frexp( a, &aexp );
  const double br = std::frexp( b, &bexp );

  return aexp == bexp &&
         std::round( ar * std::pow( 10.0, significantDigits ) ) == std::round( br * std::pow( 10.0, significantDigits ) );
}

/**
 * Returns a double \a number, rounded (as close as possible) to the specified number of \a places.
 *
 * \since QGIS 3.0
 */
inline double qgsRound( double number, int places )
{
  const double m = ( number < 0.0 ) ? -1.0 : 1.0;
  const double scaleFactor = std::pow( 10.0, places );
  return ( std::round( number * m * scaleFactor ) / scaleFactor ) * m;
}
#include <cmath>
#include <limits>

bool isClose(double a, double b)
{
    if (std::fabs(a-b) <= std::numeric_limits<double>::epsilon())
        return true;
    else
    	return false;
}
#include <cmath>
#include <iostream>
#include <limits>
 
int main()
{
    double f = 123.45;
    std::cout << "给定数字 " << f << "(十六进制表示为 " << std::hexfloat
              << f << std::defaultfloat << "),\n";
 
    double f3;
    double f2 = std::modf(f, &f3);
    std::cout << "modf() 会把它拆分成 " << f3 << " + " << f2 << '\n';
 
    int i;
    f2 = std::frexp(f, &i);
    std::cout << "frexp() 会把它拆分成 " << f2 << " * 2^" << i << '\n';
 
    i = std::ilogb(f);
    std::cout << "logb()/ilogb() 会把它拆分成 " << f / std::scalbn(1.0, i)
              << " * " << std::numeric_limits<double>::radix
              << "^" << std::ilogb(f) << '\n';
}

//可能的输出:
//给定数字 123.45(十六进制表示为 0x1.edccccccccccdp+6),
//modf() 会把它拆分成 123 + 0.45
//frexp() 会把它拆分成 0.964453 * 2^7
//logb()/ilogb() 会把它拆分成 1.92891 * 2^6

取double的整数部分和小数部分:

//double值f,分为整数部分f_Ingeger(123)、小数部分f_Fraction(0.45)
double f = 123.45; 
double f_Integer;
double f_Fraction = std::modf(f, &f_Integer);

28、c结构体和c++结构体的不同

29、类型转换

C++11之后,C++中就有四种类型转换,分别是 dynamic_cast、static_cast、const_cast、reinterpret_cast。

reinterpret_cast:

  • dynamic_cast:动态类型转换,可以在父类与子类之间进行安全的类型转换,运行时类型检查,并且可以知道自己子类的类型(必须有虚函数)。主要用在存在多态类的转换,用于保证安全转换。
  • static_cast:静态类型转换,不能进行不相关类之间的转换可以实现上行转换和下行转换;继承父类和子类之间的上行转换和下行转换,不保证安全;能够进行void * 到其他指针的任意转换;能够将 int float double以及枚举类型的转换;实现转换到右值引用;
  • const_cast:常量转换,通常用作去除变量的const属性,对于原生类型,修改对应的值可能比较麻烦,但是对于大部分类型,是可以进行修改的,一般是通过转换为指针或者引用进行修改;
  • reinterpret_cast:强制类型转换,可以在不同类型之间进行强制转换,很难保证移植性。主要用在两个完全没有联系的类型之间的转换,可以实现指针和整数之间的转换;
//主要是用来去掉const属性,当然也可以加上const属性。主要是用前者,后者很少用。
const_cast < type-id > ( expression )

//去掉const属性:常用,因为不能把一个const变量直接赋给一个非const变量,必须要转换。
const int num = 10;
int *pNum = const_case<int*> (&num);

//加上const属性:一般很少用,因为可以把一个非const变量直接赋给一个const变量,比如:
const int* k = j;
const int* k = const_case<const int*>(j);

30、前向声明与#include的区别

(1)前向声明的作用:
在预处理时,不需要包含#include"xxx",相对节约编译时间
方便的解决两种类类型互相使用的问题。
(2)注意:
类的前向声明只能用于定义指针、引用、以及用于函数形参的指针和引用
前向声明的类是不完全的类型,因为只进行了声明而没有定义

31、容器用iter进行删除

C++中利用迭代器删除元素
(1)对于关联容器(如map,set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前的iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入,删除一个结点不会对其他结点造成影响。使用方式如下例子:

set<int> valset = { 1,2,3,4,5,6 };  
set<int>::iterator iter;  
for (iter = valset.begin(); iter != valset.end(); )  
{  
     if (3 == *iter)  
          valset.erase(iter++);  
     else  
          ++iter;  
} 

因为传给erase的是iter的一个副本,iter++是下一个有效的迭代器。

(2)对于序列式容器(如vector,deque,list等),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。不过erase方法可以返回下一个有效的iterator。使用方式如下,例如:

vector<int> val = { 1,2,3,4,5,6 };  
vector<int>::iterator iter;  
for (iter = val.begin(); iter != val.end(); )  
{  
     if (3 == *iter)  
          iter = val.erase(iter);     //返回下一个有效的迭代器,无需+1  
     else  
          ++iter;  
}  

32、两个类的相互引用

C++两个类互相调用彼此的方法
两个类A和B实现互相调用彼此的方法,如果采用彼此包含对方头文件的方式会出现循环引用,所以采用了类的前置声明的方式 。

(1)class A采用前置声明的方式声明class B
(2)在ClassB的头文件中包含class A 的头文件
(3)在class A中只能声明class B类型的指针或者引用

33、位域(位段)、std::bitset、QBitArray

33.1 位域

C语言冷门知识点之–位段

33.2 std::bitset

std::bitset使用

33.3 QBitArray

QBitArray使用

34 std::sort()

STL常用算法

#include <algorithm>
#include <functional>
#include <array>
#include <iostream>
 
int main()
{
    std::array<int, 10> s = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3}; 
 
    // 用默认的 operator< 排序
    std::sort(s.begin(), s.end());
    for (auto a : s) {
        std::cout << a << " ";
    }   
    std::cout << '\n';
 
    // 用标准库比较函数对象排序
    std::sort(s.begin(), s.end(), std::greater<int>());
    for (auto a : s) {
        std::cout << a << " ";
    }   
    std::cout << '\n';
 
    // 用自定义函数对象排序
    struct {
        bool operator()(int a, int b) const
        {   
            return a < b;
        }   
    } customLess;
    std::sort(s.begin(), s.end(), customLess);
    for (auto a : s) {
        std::cout << a << " ";
    }   
    std::cout << '\n';
 
    // 用 lambda 表达式排序
    std::sort(s.begin(), s.end(), [](int a, int b) {
        return b < a;   
    });
    for (auto a : s) {
        std::cout << a << " ";
    } 
    std::cout << '\n';
}
class goods
{
public:
	goods(string name, int price)
	{
		m_name = name;
		m_price = price;
	}
public:
	string m_name;
	int m_price;
};

//自定义排序---根据商品价格排序
struct comp
{
	bool operator()(const goods& value1, const goods& value2)
	{
		return value1.m_price < value2.m_price;
	}
}	
class print
{
public:
	void operator()(const goods& value)
	{
		cout<<"name =" << value.m_name << "\tPrice = "<< value.m_price << endl;
	}
}
int main()
{
	vector<goods> vec;
	for_each(vec.begin(), vec.end(), print());		//打印元素
	sort(vec.begin, vec.end(), comp());				//排序
	for_each(vec.begin(), vec.end(), print());		//打印元素	
}

bool cmp(const string& s1, const string& s2) { 
    return s1.size() < s2.size(); 
}

QTableView的自定义排序:

void MyTableModel::sort(int column, Qt::SortOrder order)
{
    if(modelData.isEmpty()||column<0||column>=columnCount())
        return;
    //判断升序降序
    const bool is_asc = (order == Qt::AscendingOrder);
    //排序 modelData是存储数据的容器, MyModelItem是存储的自定义数据类型,QList<MyModelItem> modelData
    std::sort(modelData.begin(), modelData.end(),
              [column, is_asc, this](const MyModelItem &left,const MyModelItem &right){
        //我用QVariant只是在以前的基础上改的,自定义类型可以不用这个
        //这里假设单元格数据都是任意类型的
        const QVariant left_val = left.at(column);
        const QVariant right_val = right.at(column);
 
        //辅助接口,a<b返回true
        return is_asc
                ?lessThan(left_val,right_val)
               :lessThan(right_val,left_val);
    });
    //更新view
    dataChanged(index(0,0),index(modelData.count()-1,columnCount()-1));
}
 
bool MyTableModel::lessThan(const QVariant &left, const QVariant &right) const
{
    //参照QAbstractItemModelPrivate::isVariantLessThan的实现
    //这些都是通用型的排序规则,一般我们会有自定义的需求,比如根据字符串中的数字排序
    //有些类型需要包含头文件才能使用,如datetime
    if (left.userType() == QMetaType::UnknownType)
        return false;
    if (right.userType() == QMetaType::UnknownType)
        return true;
    switch (left.userType()) {
    case QMetaType::Int:
        return left.toInt() < right.toInt();
    case QMetaType::UInt:
        return left.toUInt() < right.toUInt();
    case QMetaType::LongLong:
        return left.toLongLong() < right.toLongLong();
    case QMetaType::ULongLong:
        return left.toULongLong() < right.toULongLong();
    case QMetaType::Float:
        return left.toFloat() < right.toFloat();
    case QMetaType::Double:
        return left.toDouble() < right.toDouble();
    case QMetaType::QChar:
        return left.toChar() < right.toChar();
    case QMetaType::QDate:
        return left.toDate() < right.toDate();
    case QMetaType::QTime:
        return left.toTime() < right.toTime();
    case QMetaType::QDateTime:
        return left.toDateTime() < right.toDateTime();
    case QMetaType::QString: break;
    default: break;
    }
    //Locale表示支持本地字符串
    //if (isLocaleAware)
    return left.toString().localeAwareCompare(right.toString()) < 0;
    //else
    //   return left.toString().compare(right.toString(), cs) < 0;


}

——————————————————————————————————
void CustomModel::sort(int column, Qt::SortOrder order)
{
    beginResetModel();
    DataSort comp(column,order);
    std::sort(m_data.begin(), m_data.end(),comp);
    endResetModel();
}

//排序类
class DataSort
{
public: 
    int  mColumn;
    Qt::SortOrder   mSortOrder;
    DataSort(int column, Qt::SortOrder order)
        : mColumn(column)
        , mSortOrder(order)
    {}
    bool operator()(const QVector<QString>* v1, const QVector<QString>*  v2)
    {
        int compare = 0;        //>0:大于 <0:小于
        bool ret=false;
        switch ( mColumn )
        {
            case 0 :     //序号,需要判断数字
            case 3 :     //信号ID,需要判断数字
                compare = v1->at(mColumn).toInt() -  v2->at(mColumn).toInt();
                break;
            default :    //其它,只判断字符串
                compare = v1->at(mColumn).compare(v2->at(mColumn));
                break;
        }
 
        if(compare==0)      //相等必须返回flase,否则的话,对于一列相同的值进行降序,那么会一直返回true,从而死循环
        {
            return false;
        }
        else
            ret = compare>0?false:true;
 
        if ( mSortOrder == Qt::DescendingOrder )    //降序
        {
            ret =  !ret;
        }
        return ret;
    }
};

36、 std::count_if()

count_if()用于获取指定范围内满足条件的元素数。

template <class InputIterator, class UnaryPredicate>  
typename iterator_traits<InputIterator>::difference_type
    count_if (InputIterator first, InputIterator last, UnaryPredicate pred);
#include <iostream>
#include <vector>
#include <algorithm>

bool isOdd(int num) 
{
    return num % 2 != 0;
}

int main() 
{
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int count = std::count_if(numbers.begin(), numbers.end(), isOdd);
    std::cout << "Odd numbers count: " << count << std::endl;
    return 0;
}

37、0x5f3759df的数学原理 :一种快速开方根求倒原理

f ( x ) = 1 x f(x)=\frac{1}{\sqrt x} f(x)=x 1

float Q_rsqrt( float number )
{
	long i;
	float x2, y;
	const float threehalfs = 1.5f;
	
	x2 = number * 0.5f;
	y = number;
	i = * ( long* ) &y; 						// evil floating point bit level hacking
	i = 0x5f3759df - ( i >> 1); 				// what the fuck?
	y = * ( float* ) &i;
	y = y * ( threehalfs - ( x2 * y * y ) ); 	// 1st iteration
	// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
	
	return y;
}
float InvSqrt(float x)
{
	float xhalf = 0.5f*x;
	int i = *(int*)&x; 			// get bits for floating VALUE 
	i = 0x5f375a86- (i>>1); 	// gives initial guess y0
	x = *(float*)&i; 			// convert bits BACK to float
	x = x*(1.5f-xhalf*x*x); 	// Newton step, repeating increases accuracy
	return x;
}  

38、枚举

C++11枚举类——enum class

/**
* Flags which adjust the way maps are rendered.
*
* \since QGIS 3.22
*/
enum class MapSettingsFlag SIP_MONKEYPATCH_SCOPEENUM_UNNEST( QgsMapSettings, Flag ) : int
{
	Antialiasing             = 0x01,  //!< Enable anti-aliasing for map rendering
	DrawEditingInfo          = 0x02,  //!< Enable drawing of vertex markers for layers in editing mode
	ForceVectorOutput        = 0x04,  //!< Vector graphics should not be cached and drawn as raster images
	UseAdvancedEffects       = 0x08,  //!< Enable layer opacity and blending effects
	DrawLabeling             = 0x10,  //!< Enable drawing of labels on top of the map
	UseRenderingOptimization = 0x20,  //!< Enable vector simplification and other rendering optimizations
	DrawSelection            = 0x40,  //!< Whether vector selections should be shown in the rendered map
	DrawSymbolBounds         = 0x80,  //!< Draw bounds of symbols (for debugging/testing)
	RenderMapTile            = 0x100, //!< Draw map such that there are no problems between adjacent tiles
	RenderPartialOutput      = 0x200, //!< Whether to make extra effort to update map image with partially rendered layers (better for interactive map canvas). Added in QGIS 3.0
	RenderPreviewJob         = 0x400, //!< Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering
	RenderBlocking           = 0x800, //!< Render and load remote sources in the same thread to ensure rendering remote sources (svg and images). WARNING: this flag must NEVER be used from GUI based applications (like the main QGIS application) or crashes will result. Only for use in external scripts or QGIS server.
	LosslessImageRendering   = 0x1000, //!< Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some destination devices (e.g. PDF). This flag only works with builds based on Qt 5.13 or later.
	Render3DMap              = 0x2000, //!< Render is for a 3D map
	HighQualityImageTransforms = 0x4000, //!< Enable high quality image transformations, which results in better appearance of scaled or rotated raster components of a map (since QGIS 3.24)
	SkipSymbolRendering      = 0x8000, //!< Disable symbol rendering while still drawing labels if enabled (since QGIS 3.24)
	ForceRasterMasks         = 0x10000,  //!< Force symbol masking to be applied using a raster method. This is considerably faster when compared to the vector method, but results in a inferior quality output. (since QGIS 3.26.1)
};
//! Map settings flags
Q_DECLARE_FLAGS( MapSettingsFlags, MapSettingsFlag ) SIP_MONKEYPATCH_FLAGS_UNNEST( QgsMapSettings, Flags )
Q_ENUM( MapSettingsFlag )
Q_FLAG( MapSettingsFlags )

【QT】枚举常用宏

Q_DECLARE_FLAGS(Flags, Enum)宏展开为: 
typedef QFlags<Enum> Flags;	//QFlags<Enum>是一个模板类,其中Enum是枚举类型,QFlags用于存储枚举值的组合。
Q_ENUM(Orientation)  		//向元对象系统注册枚举类型,通过QMetaEnum对象可以获得枚举的许多信息。例如枚举的名称、枚举的数量、枚举的key和value值等等。
Q_FLAG( MapSettingsFlags )	//宏Q_FLAG会向元对象系统注册一个单一的标志类型。使用宏Q_FLAG声明的枚举,其枚举值可以作为标志,并使用位或操作符(|)进行组合。

39、各种头文件的引用

//std::max
#include <iostream>
#include <algorithm>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值