C++笔记
多线程
切忌勿在多线程的时候访问同一线程的内容,会产生死锁,解决办法是用队列(访问队列的时候加锁)
方法返回值为数组类型
返回值设为该数组类型的指针
static UINT8* HeartBeat();
static
静态变量需要再cpp的类名上方初始化,并且结束的时候需要单独释放
thread
线程关闭资源的时候需要先让线程自身跑完,在关闭线程资源,否则会出现野指针。
解决方法是:获取线程后调用线程的join();再delete线程的对象
回调函数
通过函数指针调用的函数,叫回调函数
虚函数
特点:由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应
使用方法:
1. 在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用
在类外定义虚函数时,不必再加virtual
2. 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体
3. 定义一个指向基类对象的指针变量,并使它指向同一类族中的某一对象
4. 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数
纯虚函数
在声明虚函数时被“初始化”为0的函数
virtual 函数类型 函数名(参数列表) = 0;
注意:
1. 纯虚函数没有函数体;
2. 最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”
3. 这是一个声明语句,最后应有分号
如果在基类中没有保留函数名字,则无法实现多态性
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数
运算符重载
重载格式:operatorop(argument-list)
例如:operator+()重载+运算符
重载限制:
1. 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符
2. 使用运算符时不能违反运算符原来的句法规则。
3. 不能修改运算符的优先级
4. 不能创建新运算符
5. 不能重载下面的运算符
1. sizeof :sizeof运算符
2. . :成员运算符
3. .* :成员指针运算符
4. :: :作用域解析运算符
5. ?: :条件运算符
6. typeid :一个RTTI运算符
7. const_cast :强制类型转换运算符
8. dynamic_cast :强制类型转换运算符
9. reinterpret_cast:强制类型转换运算符
10. static_cast :强制类型转换运算符
6.大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载
1. = :赋值运算符
2. () :函数调用运算符
3. [] :下标运算符
4. -> :通过指针访问类成员的运算符
string 转 int
string a = "123";
int b =atoi(a.c_str());
宏定义--#define条件编译
```
#define条件编译
头文件(.h)可以被头文件或C文件包含;重复包含(重复定义)由于头文件包含可以嵌套,
那么C文件就有可能包含多次同一个头文件,就可能出现重复定义的问题的。 通过条件编译开关来避免重复包含(重复定义)
例如
#ifndef __DEMO_H
#define __DEMO_H
...
#endif
防止重复定义
```
inline内联函数
就是把内联函数拼接到要调用的地方,提高效率
a. 从inline的原理,我们可以看出,inline的原理,是用空间换取时间的做法,是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。所以,如果函数体代码过长或者函数体重有循环语句,if语句或switch语句或递归时,不宜用内联
b. 关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。内联函数调用前必须声明
类似于“宏”:
可以将内联理解为C++中对于函数专有的宏,对于C的函数宏的一种改进。对于常量宏,C++提供const替代;而对于函数宏,C++提供的方案则是inline。在C中,大家都知道宏的优势,编译器通过复制宏代码的方式,省去了参数压栈,生成汇编的call调用,返回参数等操作,虽然存在一些安全隐患,但在效率上,还是很可取的。
纯/虚函数
虚函数为了重载和多态的需要,在基类中是由定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数!
纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数!
只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的
句柄
windows中指向资源的一个标示符,可以理解成指向指针的指针。
1. 进程创建时,windows系统为进程构造了一个句柄表
2. 当该进程希望获得一个内核对象句柄或者创建一个内核对象从而获得该对象句柄时,系统会在句柄表中增加一个表项,表项的内容中存储了指向目标内核对象的指针
3. 同时,系统返回这个表项在句柄表中的索引作为句柄
dynamic_cast和static_cast
dynamic_cast <type-id> (expression)
该运算符把expression转换成type-id类型的对象。Type-id 必须是类的指针、类的引用或者void*;
如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
dynamic_cast运算符可以在执行期决定真正的类型。如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果 downcast 不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
dynamic_cast还支持交叉转换(cross cast)。
static_cast不需要RTTI,在使用的时候不需要基类声明虚函数
const、volatile、mutable的用法
const修饰普通变量和指针
const修饰变量,一般有两种写法:
const TYPE value;
TYPE const value;
这两种写法在本质上是一样的。它的含义是:const修饰的类型为TYPE的变量value是不可变的。对于一个非指针的类型TYPE,无论怎么写,都是一个含义,即value值不可变。 例如:
const int nValue; //nValue是const
int const nValue; //nValue是const
但是对于指针类型的TYPE,不同的写法会有不同情况:
l 指针本身是常量不可变
(char*) const pContent;
l 指针所指向的内容是常量不可变
const (char) *pContent;
(char) const *pContent;
l 两者都不可变
const char* const pContent;
识别const到底是修饰指针还是指针所指的对象,还有一个较为简便的方法,也就是沿着*号划一条线:
如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。
const修饰函数参数
const修饰函数参数是它最广泛的一种用途,它表示在函数体中不能修改参数的值(包括参数本身的值或者参数其中包含的值):
void function(const int Var); //传递过来的参数在函数内不可以改变(无意义,该函数以传值的方式调用)
void function(const char* Var); //参数指针所指内容为常量不可变
void function(char* const Var); //参数指针本身为常量不可变(也无意义,var本身也是通过传值的形式赋值的)
void function(const Class& Var); //引用参数在函数内不可以改变
参数const通常用于参数为指针或引用的情况,若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。
const修饰类对象/对象指针/对象引用
const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
const修饰数据成员
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么
const修饰成员函数
const修饰类的成员函数,用const修饰的成员函数不能改变对象的成员变量。一般把const写在成员函数的最后
const修饰成员函数的返回值
1、一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回const对象,或返回const对象的引用,则返回值具有const属性,返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。
2、如果给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针所指的内容)不能被修改,该返回值只能被赋给加const 修饰的同类型指针
const常量与define宏定义的区别
l 编译器处理方式不同
define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
l 类型和安全检查不同
define宏没有类型,不做任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。
l 存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(可以是堆中也可以是栈中)。
volatile关键字
volatile的本意是“易变的”,volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被寄存
mutable关键字
mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能由于修饰类的非静态数据成员),将永远处于可变的状态,即使在一个const函数中。
我们知道,假如类的成员函数不会改变对象的状态,那么这个成员函数一般会声明为const。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰
定时器
定时器的原型是:
//windows API
函数WINUSERAPI UINT WINAPI SetTimer ( HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc);
hWnd 是欲设置定时器的窗体句柄。定时时间到时,系统会向该窗体发送WM_TIMER消息。
nIDEvent 定时器标识符。在一个窗体内可以使用多个定时器,不同的定时器根据nIDEvent来区分。
uElapse 定时时间,单位是毫秒。
lpTimerFunc 定时器的回调函数。如果该值为NULL,定时时间到时,定时器发送的消息WM_TIMER由窗体映像该消息的函数处理;
否则由回调函数处理,说白一点,回调函数就是取代OnTimer的处理函数。
extern "C" _declspec(dllimport) 和 extern "C" _declspec(dllexport)
使用MFC提供的修饰符号_declspec(dllexport)
在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。__declspec(dllexport)在C调用约定、C编译情况下可以去掉输出函数名的下划线前缀。
extern "C"使得在C++中使用C编译方式成为可能。在“C++”下定义“C”函数,需要加extern “C”关键词。用extern "C"来指明该函数使用C编译方式。输出的“C”函数可以从“C”代码里调用
在C++里要使用C的编译方式,在用C写的头文件上面加上#define IVLABELSTRUPARSETRANS_EXPORT extern "C" _declspec(dllimport)
在C++里要使用C的编译方式,在用C写的源文件上面加上#define IVLABELSTRUPARSETRANS_EXPORT extern "C" _declspec(dllexport)
malloc和memset区别
malloc申请的内存没有初始化,但是memset会初始化
结构体
除了基本数据类型长度是直接存放在结构体中,其余存放的均为对象类型,对象类型长度不定。
sprintf和sprintf_s区别
sprintf_s和sprintf的主要不同是sprintf_s对于格式化string中的格式化的字符的有效性进行了检查,而sprintf仅仅检查格式化string或者缓冲区是否是空指针。如果有错误则返回相应的错误代码。
另一个不同,sprintf_s也携带着接收格式化字符串的缓冲区的大小。如果,格式化字符串过大,则sprintf_s会返回一个空string和设置无效参数句柄为激活。与snprintf不同,sprintf_s不会保证缓冲区为以null结尾,除非,缓冲区的大小为0。
sprintf_s将格式化字符串存到缓冲区,并在下一个位置填充Null后将格式化字符串未占用的缓冲区(Null之后的Buffer)全部填充为-3,而sprintf却不会填充而是保持缓冲区中未占用的存储位置上的数据。
map集合在结构体中不能初始化
如果需要初始化, 需要typedef map<int, TEST_EVENTDES> MAP_TEST_EVENTDES; 如下方式即可,否则insert会有问题
typedef map<int, TEST_EVENTDES> MAP_TEST_EVENTDES;
//单个视频信息
typedef struct _TEST_VIDEO_EVENTS_
{
HIKLIB_TYPE type; //测试库类型
TEST_ABOUTVIDEO videodes; //视频描述
TEST_EAREA areades; //事件区域描述,规则区域
MAP_TEST_EVENTDES evnetsdes;
}TEST_VIDEO_EVENTS,*PTEST_VIDEO_EVENTS;
dll和exe各有一个本地堆
在exe中申请一块内存,并且在dll中也申请一块内存放入exe申请的那块内存中,当exe的内存释放时,不能释放dll的内存会报错
解决方法是:都在同一个本地堆中申请和释放内存
否则会报: _CrtIsValidHeapPointer(pUserData) 错误
error
1. error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
添加using namespace std;
#pragma 编译指示
```
#include <omp.h>
1. #pragma omp parallel for 将for并行化
循环多线程化需要避免“迭代相关”,要求循环执行的次序可以被任意打乱和组织,而不必安装常规的循环变量依次变化的方式执行;
循环并行化的一种方式是:把循环均分成n段,每段由一个线程并行执行;
消除迭代相关的一种方法:手动对循环分段,再使用section指令,每段一个section:
#pragma omp section
2. #pragma omp parallel for private(x)
将被多线程共享,而逻辑上独立的变量使用private;private变量会在每个线程中保存一个副本,并使用默认值初始化;
int x;
#pragma omp parallel for private(x)
for(…){
x = …
}
上面也可以通过将x声明到循环体内解决
3. #pragma omp schedule(kind[,chunksize])
static调度策略,循环被近乎均分的方式分配给各线程,一般不指定块大小;
dynamic,使用内部任务队列,线程执行完被分配的块后再从队列中取新的任务执行;
guided,与dynamic类似,但先取较大块后逐渐减小,以减少访问内部队列的次数;
runtime,运行时通过环境变量确定具体调度策略,如:
export OMP_SCHEDULE=dynamic,16
4. #pragma omp parallel for reduction(+:sum)
sum = 0
#pragma omp parallel for reduction(+:sum)
for(…)
sum = sum + fun(k);
这样编译器生成每个线程的sum副本,并按一定规则附相应初始值,循环完成时再将它们合并到原始的sum变量;类似“+”的操作还可以有-/*/&/等;
5. For循环与section结构中隐含使用了栅障,nowait子句去除它 Barrier栅障——线程同步
#pragma omp for nowait
#pragma omp barrier 显示使用栅障
6. 只由一个线程执行一次-----并行区中的串行区
#pragma omp master 只由主线程执行
#pragma omp single 只由一个线程执行一次
7. 临界区与原子操作
#pragma omp critical
#pragma omp critical(name) 命名临界区,这样该临界区就可以在多处被引用了
#pragma omp atomic 只能是简单操作
8. 函数
需要#include <omp.h>,不建议使用;
omp_set_num_threads(n)
omp_get_thread_num() 线程编号
omp_get_num_procs() CPU数量
9. If子句
#pragma omp parallel for if(n>100) n>100时才执行并行化
10. 环境变量
OMP_SCHEDULE
OMP_NUM_THREADS 默认线程数
```
检查内存溢出代码块位置
解决方法
1、在程序开始的地方,加上代码:_CrtSetBreakAlloc(1654); //1654为上面内存泄漏的块号。
2、用Debug模式运行,程序自动断点在“内存块1654”分配的位置。
3、这个时候一直利用“调试”-“退出”,或快捷键“SHIFT+F11”跳转,直到看到了自己的代码。
4、检查自己在该代码中分配的内存块,是否进行了合理的释放。
高精度计算时间差
LARGE_INTEGER m_nFreq;
LARGE_INTEGER m_nBeginTime;
LARGE_INTEGER nEndTime;
QueryPerformanceFrequency(&m_nFreq); // 获取时钟周期
QueryPerformanceCounter(&m_nBeginTime); // 获取时钟计数
Sleep(100);
QueryPerformanceCounter(&nEndTime);
cout << (double)(nEndTime.QuadPart-m_nBeginTime.QuadPart)*1000/m_nFreq.QuadPart << endl;
常成员函数问题
在常成员函数内是只读的,不能改变成员变量的值