define 相关用法
1.1 添加自定义的函数声明
#define DECLARE_FONT(Type) \
void GUI##Type##_DispChar (U16P c); \
int GUI##Type##_GetCharDistX(U16P c); \
void GUI##Type##_GetFontInfo (const GUI_FONT GUI_UNI_PTR * pFont, GUI_FONTINFO * pfi); \
char GUI##Type##_IsInFont (const GUI_FONT GUI_UNI_PTR * pFont, U16 c)
typedef 复杂声明
typedef void GUI_DISPCHAR(char c);
typedef void (*pGUI_DISPCHAR)(char c);
//GUI_DISPCHAR* XXA == pGUI_DISPCHAR xxB
2.1 对复杂变量建立类型别名的方式
只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。 从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完(复杂声明解析)
解剖一下signal函数,理解函数指针和typedef
void (signal(int signum,void( handler)(int)))(int); //等价于下面两行
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler)
分析:先分析第一行,看到这一串的函数声明,不要慌,从内而外一点一点分析:
- signal(int signum,void(* handler)(int))这是一个函数,函数名为signal
- 第一个形参:int signum,是一个整形变量;
- 第二个形参:void(*handler)(int),这是一个函数指针,指向:返回值为void型,且带一个int型的形参的函数。
- 也就是说,这个形参,是一个指针,或者说是一个地址,可以理解为这个形参是函数名,因为函数名是函数的入口地址。里面分析完了。
- signal函数的外面是 void(*)(int),想一想,在定义或者声明函数的时候,都需要说明函数返回值的类型,这个signal
- 函数的形参已经明确了,但是返回值还不知道,这个void(*)(int)就是返回值类型,这个类型是一个函数指针,碰巧的是,这个函数指针指向的函数类型和signal的第二个形参指向的类型相同,都是指向:返回值为空,且带一个int类型形参的函数。
- 因此先用typedef定义一种数据类型,这个类型就是void(*)(int),将这种类型定义为,或者说是起名别,叫做sighandler_t
- sighandlert = void()(int)_,所以可以将void()(int)类型的函数,在声明或者定义的时候用sighandler_t。
- signal函数返回值为void()(int)型,用sighandler_t代替,第2个形参是void()(int)型,用sighandler_t代替。
例1:int (a[5])(int, char*);
//这是一个函数指针数组,函数的输入参数为int和char类型,返回值为int型的指针。
**例2:double((pa)[9])();**
typedef double(pFun)();//首先为整体声明一个新类型
typedef pFun (pFunParam)[9];//再为上面((pa)[9])部分声明一个新类型
pFunParam pa;//使用定义的新类型来声明对象,等价于double((pa)[9])();
C++ STL中的vector的内存分配与释放
3.1 其他情况释放内存
如果vector中存放的是指针,那么当vector销毁时,这些指针指向的对象不会被销毁,那么内存就不会被释放。如下面这种情况,vector中的元素时由new操作动态申请出来的对象指针:
#include <vector>
using namespace std;
vector<void *> v;
每次new之后调用v.push_back()该指针,在程序退出或者根据需要,用以下代码进行内存的释放:
for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++)
if (NULL != *it)
{
delete *it;
*it = NULL;
}
v.clear();
在模板中,使用iterator
在模板中使用迭代器时,必须要使用typename ,加上typename后编译通过。将上面代码中的红色部分改为下面的代码,编译没有错误。typename vector
typename两种用法:
1。用在模板声明中,如下两个声明是一样的:
template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"
2.用于标识 nested dependent type name(嵌套依赖类型名)
智能函数指针调用
// 智能指针转换
template <typename dst_type, typename src_type>
dst_type pointer_cast(src_type src)
{
return *static_cast<dst_type*>(static_cast<void*>(&src));
}
C++ const 修饰
void test() const //const 修饰的是this
字节对齐算法
//字节对齐是在分配内存时需要考虑的问题,两个小算法:
(1)最容易想到的算法:
unsigned int calc_align(unsigned int n,unsigned align)
{
if ( n / align * align == n)
return n;
return (n / align + 1) * align;
}
(2)更好的算法:
unsigned int calc_align(unsigned int n,unsigned align)
{
return ((n + align - 1) & (~(align - 1)));
}
对于2算法原理如下:
2字节对齐,要求地址位为2,4,6,8…,要求二进制位最后一位为0(2的1次方)
4字节对齐,要求地址位为4,8,12,16…,要求二进制位最后两位为0(2的2次方)
8字节对齐,要求地址位为8,16,24,32…,要求二进制位最后三位为0(2的3次方)
16字节对齐,要求地址位为16,32,48,64…,要求二进制位最后四位为0(2的4次方)
…
由此可见,我们只要对数据补齐对齐所需最少数据,然后将补齐位置0就可以实现对齐计算。
(1)(align-1),表示对齐所需的对齐位,如:2字节对齐为1,4字节为11,8字节为111,16字节为1111…
(2)(x+(align-1)),表示x补齐对齐所需数据
(3)&~(align-1),表示去除由于补齐造成的多余数据
(4) (x+(align-1))&~(align-1),表示对齐后的数据
举个例子:如8字节对齐。起始地始是6
6 + (8 - 1)=0000 0110 + 0000 0111 = 0000 1101
0000 1101 & ~(0000 0111) = 0000 1000 //去除由于补齐造成的多余数据。
总的来说,字节对齐有以下准则:
- 结构体变量的首地址能够被其对齐字节数大小所整除。
- 结构体每个成员相对结构体首地址的偏移都是成员大小的整数倍,如不满足,对前一个成员填充字节以满足。
- 结构体的总大小为结构体对齐字节数大小的整数倍,如不满足,最后填充字节以满足。
假设 a的首地址为0,则0/4=0 (1)成立,a大小为4,则b的首地址为4,4/1=0(1) 成立,c的首地址为5,5/4 不能整除,(1)不成立,所以填充字节数,5+3=8/4,所以c的首地址为8,d的首地址为8+4=12/4 (1)成立。总自己数为14,但14/4 不能整除,估填充2个字节,14+2=16/4,所以结构体最终字节数为16;struct test { int a; char b; int c; short d; };