#1.模板类的模板成员函数
template <typename type1>
class C {
public:
template <typename type2>
void f();
}
如果需要在类外定义函数f时,要这样写:
template<typename type1, typename type2>
void C<type1>::f() {...}
而不能写成
template<typename type1>
template<typename type2>
void C<type1>::f() {...}
#2.宏定义中的“#”,“##”,“#@”用法
“#”用于将宏参数转化为字符串,即在宏参数的两边加上双引号。例如:
#define STR(x) #x
int abc = 1;
cout << STR(abc) << endl;
上面会输出程序会将会输出字符串“abc”而不是变量abc的值。
“##”用于宏参数之间的连接或宏变量与其他代码的连接。例如:
#define VAL1(x) x##1
#define VAL2(x, y) x##y
int abc1 = 1;
cout << VAL1(x) << endl;
cout << VAL2(x, 1) << endl;
两个输出语句都会输出变量abc1的值。
“#@”是将宏参数转成字符,即在宏参数的两边加上单引号,不过gcc不支持。
在使用“#”,“##”,“#@”时,如果宏参数本身也是宏的话,该宏参数是不会进行宏展开的,这里用一个简单的例子解释下:
#define STR(x) #x
cout << STR(__LINE__) << endl;
上面的代码并不会将宏__LINE__展开,因此得到的输出是“__LINE__”,而不是当前代码行号的字符串表示。为了能够将__LINE__展开,我们需要借助转换宏。
#define _STR(x) #x
#define STR(x) _STR(x)
cout << STR(__LINE__) << endl;
这样宏__LINE__就会在调用STR的时候展开,因此传给_STR的就是__LINE__的值了。
#3. 匿名命名空间
匿名空间中定义的函数或变量的作用域被限制在文件范围内,功能等同于用static修饰这些函数或变量。例如:
#a.cpp
void f1(){...};
static void f2(){...};
namespace {
void f3(){...};
}
#b.cpp
extern void f1(); //ok
extern void f2(); //链接出错
extern void f3(); //链接出错
当父命名空间中定义了一个嵌套的普通命名空间时,访问该嵌套命名空间中函数或变量等时需要使用域作用符。但当该嵌套命名空间换成一个内联命名空间时,该内联命令空间中的函数或变量等自动成为父命名空间的成员,访问时不需要域作用符。版本控制场景下可能会需要这个特性,下面举个例子说明:
namespace Pro {
namespace v1 {
void f1();
void f2();
}
inline namespace v2 {
void f1();
}
}
当你的项目版本1提供两个函数f1和f2,但版本2修改f1并且去掉了f2,这时你如果调用Pro::f1那么调用的函数是内联命名空间v2中的f1,并且调用Pro::f2会报错,如果你确实需要用到老版本中的函数,你可以这样调用:Pro::v1::f1和Pro::v1::f2。
#5.对齐
编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。