一. 变长数组
严格说来,变长数组的实现在c++中并不是一件麻烦的事情。Stl中的vector本身就是一个变长数组,并且有自动管理内存的能力。
但是在c中,实现变长数组就稍显麻烦。用C实现,必然需要一个结构,结构当中应当有一个指针,指针分配一段内存空间,空间大小根据需要而定,而且必须有另外一个字段记录究竟开辟了多大多长的空间。
大致描述如下:
Struct MutableLenArray { Int count; Char* p; }; |
{
Int count;
Char* p;
};
P = new Char[Count];
没什么问题,但是C语言的使用者有个最大的自豪就在于对于效率、空间使用的掌控。他们会有这样的疑问,如果count=0,那么p就没必要了,白白占了4(64位系统为8)个字节的空间,简直浪费。
那有没有更好的方式能实现上面的需求,又保证空间合理呢?答案是有的,用0长度
Struct MutableLenArray { Int count; Char p[0]; }; |
和上面的结构使用方法一致,但是我们可以用sizeof尝试读取其大小,发现竟然只有count字段的长度4字节,p没有被分配空间。完美!
二. 宏的妙用
1. #和
“#”符号把一个符号直接转换为字符串,例如:
1
2 |
#define TO_STRING(x) #x const char *str = TO_STRING( test ); |
str的内容就是”test “,也就是说#会把其后的符号 直接加上双引号。
这个特性为c++反射的实现提供了极大便利,可以参考博主的下一篇文章,c++反射的简单实现。
##符号会连接两个符号,从而产生新的符号(词法层次),例如:
1
2 |
#define SIGN( x ) INT_##x int SIGN( 1 ); |
宏被展开后将成为:int INT_1;
可以把##看成连字符,连字符为则为新符号的产生提供了方便。Google的Gtest框架就巧妙的运用了连字符来生成新的测试案例。
2. 变参宏
1
2 |
#define LOG( format, ... ) printf( format, __VA_ARGS__ ) LOG( "%s %d" , str, count ); |
VA_ARGS是系统预定义宏,被自动替换为参数列表。
经常需要进行输出格式化,重定义时,可以用到以上技巧。
3. 宏参数的prescan
prescan的定义:当一个宏参数被放进宏体时,这个宏参数会首先被全部展开(有例外,见下文)。当展开后的宏参数被放进宏体时, 预处理器对新展开的宏体进行第二次扫描,并继续展开。例如:
1
2
3 |
#define PARAM( x ) x #define ADDPARAM( x ) INT_##x PARAM( ADDPARAM( 1 ) ); |
因为ADDPARAM( 1 ) 是作为PARAM的宏参数,所以先将ADDPARAM( 1 )展开为INT_1,然后再将INT_1放进PARAM。
例外情况是,如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开:
1
2 |
#define PARAM( x ) #x #define ADDPARAM( x ) INT_##x |
PARAM( ADDPARAM( 1 ) ); 将被展开为”ADDPARAM( 1 )”。
所以此时要得到“INT_1”的结果,必须加入一个中间宏:
1
2 |
#define PARAM(x) PARAM1(x) #define PARAM1( x ) #x |
PARAM( ADDPARAM( 1 ) );此时的结果将会是“INT_1”。根据prescan原则,当ADDPARAM(1)传入,会展开得到INT_1,然后将INT_1带入PARAM1宏,最终得到“INT_1”的结果。
4. 接口宏
以下部分,摘自网上博客,仅作声明。
C++的目标之一就是把类的声明和定义分离开来,这对于项目的开发极其有利——这可以使开发人员不用看到类的实现就能知晓类的功能。但是,C++实现类的声明与类定义的分离的方法会导致一些额外的工作——每个非内联函数的表示都需要写两次,一次在类声明中,一次在类定义中。
代码如下:
1
2
3
4
5
6
7
8
9
10
11 |
// .h File class Element { void Tick (); }; // .cpp File void Element ::Tick () { // todo } |
1
2
3
4
5
6
7 |
class Animal { public : virtual std :: string GetName () const = 0 ; virtual Vector3f GetPosition () const = 0; virtual Vector3f GetVelocity () const = 0; }; |
同时,这个基类拥有三个派生类——Monkey,Tiger,Lion。
那么我们三个方法的每一个都会在7个地方存在:Animal中一次,Monkey、Lion、Tiget的声明和定义各一次。
然后假设我们做一个小改动——我们想将GetPosition和GetVelocity的返回类型改为Vector4f以适应Transform变换,那么我们就要在7个地方进行修改:Animal的.h文件,Lion、Tiger和Monkey的.h文件和.cpp文件。
使用宏的实现
有一种很妙的处理方法就是将这些方法进行包装,改成所谓接口宏的形式。我们可以试试看:
1
2
3
4
5
6
7
8 |
#define INTERFACE_ANIMAL(terminal) \ public : \ virtual std::string GetName() const ##terminal \ virtual IntVector GetPosition() const ##terminal \ virtual IntVector GetVelocity() const ##terminal #define BASE_ANIMAL INTERFACE_ANIMAL(=0;) #define DERIVED_ANIMAL INTERFACE_ANIMAL(;) |
值得一提的是,##符号代表的是连接,\符号代表的是把下一行的连起来。
通过这些宏,我们就可以大大简化Animal的声明,还有所有从它派生的类的声明了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 |
// Animal.h class Animal { BASE_ANIMAL ; }; // Monkey.h class Monkey : public Animal { DERIVED_ANIMAL ; }; // Lion.h class Lion : public Animal { DERIVED_ANIMAL ; }; // Tiger.h class Tiger : public Animal { DERIVED_ANIMAL ; }; |
jsp复习资料汇总
[JSP]2017年1月24日asp教程编程辅导汇总
[ASP]2016年12月2日JSP快速入门教程汇总
[JSP]2016年12月2日jsp基本用法和命令汇总
[JSP]2016年10月3日ASP编码教程:如何实现/使用缓存
[ASP]2015年4月15日