写在前面
简单记录一些《C++ STL源码剖析中》涉及到的C++语法和注意事项。
1. 静态常量成员在类内直接初始化
- 如果含有
const static integral
类型的成员变量,可以在类内定义时直接初始化;
- 注意
integral
不只是int
类型,而是包含所有的整型,如下所示:short
;char
,特别注意char类型也是整型;int
;long
;long long
;
一些关于static
和const
约束的类内成员变量初始化要求:
- 如果只是
static
成员变量,则只能类内定义,然后在类外初始化; - 如果只是
const
成员变量,则可以在类内定义时初始化,也可以在构造函数中初始化,但一定要初始化; - 如果是
const static float/double
则只能在类外初始化,且一定要初始化; const static integral
可以在类内定义时初始化,也可以在类外初始化,但一定要初始化;
可以参考博客:C++ 的类中static和const关键字声明变量的初始化方式总结
2. union类型
- union是C/C++中的一种类型,类型中的所有变量共用同一段内存,存储的起点均一致,大小为占用空间最大的变量的大小;
- 因此一个union变量同一时间中只能存其中一个变量的值,其余变量的值均被覆盖(或至少覆盖一部分);
- 目的是减少存储所需的空间,提高空间的利用率;
- 一个例子如下:
- 上图中的
obj
和client_data
共用相同起始地址的一段空间,如下图所示:
- 但要注意尽量不要在union中定义C++风格的
class
类型对象,而是用C风格的struct
或者基本类型,避免出错; - 可以参考博文:C++中的union介绍;
- C风格的类型也被称为POD (Plain Old Data)类型,包括基本数据类型、指针、union、数组、POD的struct或者class;
- POD的struct或者class需同时满足以下条件:
- 不显式定义构造函数、拷贝构造函数、重载赋值函数和析构函数;
- 类内无非静态非POD成员变量;
- 无基类;
3. typedef用法
- 用于为现有的类型创建一个新名字;
- 只为类型增加别名,便于使用;
- typedef并不创建新的类型,仅让新别名在编译时被解释,让编译器进行超越预处理器能力的文本替换而已;
- 使用方法如下:
typedef existing_type new_type_name;
- 和基本类型的使用,例如:
/*改写前*/
unsigned int a;
/*改写后*/
typedef unsigned int WORD;
WORD a;
- 和指针的使用,例如:
/*改写前*/
char * str;
/*改写后*/
typedef char * pstr;
pstr str = "abc";
- 和数组的使用,例如:
/*改写前*/
char line[81];
char text[81];
/*改写后*/
typedef char Line[81];
Line text, secondline;
- 和函数指针的使用,例如:
void printHello(int i);
/*改写前*/
void (*pFunc)(int);
pFunc = &printHello;
(*pFunc)(110);
/*改写后*/
typedef void (*PrintHelloHandle)(int);
PrintHelloHandle pFunc;
pFunc = &printHello;
(*pFunc)(110);
- 参考博文:typedef 用法总结;
4. typename用法
- 作用是用来声明后面跟的
name
是类型名而不是变量名; - 在旧用法中是可以用
class
代替typename
的; - 在泛型中的用法如下:
template<typename T>
class test_typename{
//...
};
template<class T>
class test_class{
//...
};
- 但如果要在
typedef
中使用泛型元素,则必须使用typename
加以声明,以表明元素为类型,因为typedef
只能为类型赋别名; - 参考博文:typename的两种用法;
5. explicit用法
- 只能修饰只有一个参数的类构造函数;
- 作用是取消在类对象构造时自动的隐式转换,避免引起语义的漏洞,导致难以察觉的构造错误;
- 一个例子如下:
class CxString
{
public:
char *_pstr;
int _size;
// 使用关键字explicit的类声明, 显式转换
// 限定了只能用以下方式调用:
// CxString string1(24);
// 以下调用无法通过编译:
// CxString string2 = 10; // 相当于是将CxString string1(10)封装后再赋值给string2
// CxString string3 = 'c'; // 相当于是将CxString string1(int('c'))封装后再赋值给string3
explicit CxString(int size)
{
_size = size;
// ...
}
};
- 可以参考博文:C++中的explicit详解;
6. 指针常量和常量指针
- 指针常量
- 指针类型的常量;
int * const ptr
;- 指针的指向不可修改,在定义时必须同时初始化;
- 但指针指向的空间内容可以修改;
- 常量指针
- 指向常量的指针;
const int *ptr
或者int const *ptr
;- 指针指向的空间内容不可修改;
- 但指针的指向可以修改;
7. copy_backward()函数用法
- 和
copy()
类似,用于复制一段序列,且只用于序列容器; - 复制时从这段序列的尾部开始复制,避免覆盖后面的值;
- 可以用于将某段序列整体后移,相比之下,
copy()
用于将序列整体前移;
8. void*指针
void*
占据和其他指针相同空间大小,只是它的指向暂时还不明;- 可以强制转换成任何类型的指针,也可以赋值为任何类型的指针,如:
void *pv;
int i = 5;
int *pi = &i;
// 1. void*可以赋值为任意类型的指针
pv = pi;
// 但反过来不行
// pi = pv; // 会出错
// 加上强制类型转换可以
pi = (int *)pv;
// 2. void*可以经过强制类型转换来使用
int j = *(int *)pv;
// 但一定要经过强制类型转换才能使用
// int j = *pv; // 会出错
- 取值不可以不经过强制转换;
- 其他类型的指针也不可以用未经强制转换的
void*
来赋值; - 可以赋初始值为
0
或者nullptr
; - 如果作为函数的参数类型或者返回类型,则表示可以接收任意类型的指针或者可以返回任意类型的指针;
- 相当于是普通类型指针的父类,用于实现多态或者充当泛型;
- 可以参考博文:C++中的void*理解;
9. 自增和自减函数重载
- 自增函数重载:
self& operator++() {}
相当于++i
,前置自增,先自增再返回;- 返回的是自身的引用,可以作为左值和右值;
++i = a; // 虽然这样写很奇怪
self operator++(int) {}
相当于i++
,后置自增,先返回再自增;- 返回的是值,只能作为右值;
a = i++;
- 其中的
int
是没有意义的,仅用于区分前置还是后置; - 自减函数重载同理;