Appearance
explicit
explicit 表示:禁止编译器使用这个单参数构造函数进行隐式类型转换。
假设没有 explicit:
class Type {
public:
Type(TypeId type_id) : type_id_(type_id) {}
};那么下面这种写法可能成立:
TypeId id = ...;
Type t = id; // 隐式调用 Type(id)加上 explicit 后:
explicit Type(TypeId type_id) : type_id_(type_id) {}只能显式构造:
Type t(id); // 可以
Type t{id}; // 可以
Type t = Type(id); // 可以
Type t = id; // 不可以explicit 的主要用途是避免意外的隐式转换。
enum 使用示例
CmpBool a = GetCmpBool(true);
CmpBool b = GetCmpBool(false);
CmpBool c = CmpBool::kNull;结果分别是:
a == CmpBool::kTrue
b == CmpBool::kFalse
c == CmpBool::kNull可以写判断逻辑:
CmpBool result = GetCmpBool(10 > 3);
if (result == CmpBool::kTrue) {
// 比较成立
}enum class
推荐使用 enum class:
enum class CmpBool { kFalse = 0, kTrue, kNull};
inline CmpBool GetCmpBool(bool boolean) {
return boolean ? CmpBool::kTrue : CmpBool::kFalse;
}enum class 的类型约束更严格,不会把枚举项暴露到外层作用域,也不会轻易隐式转换成整数。
例如:
CmpBool value = CmpBool::kTrue;而下面这种写法不会成立:
int number = CmpBool::kTrue; // enum class 下编译失败需要显式转换:
int number = static_cast<int>(CmpBool::kTrue);在现代 C++ 工程中,enum class 通常比普通 enum 更安全。
Learn macros.h:
c++
#ifndef MINISQL_MACROS_H
#define MINISQL_MACROS_H
#include <cassert>
#define ASSERT(expr, message) assert((expr) && (message))
// Macros to disable copying and moving
#define DISALLOW_COPY(cname) \
cname(const cname &) = delete; /* NOLINT */ \
cname &operator=(const cname &) = delete; /* NOLINT */
#define DISALLOW_MOVE(cname) \
cname(cname &&) = delete; /* NOLINT */ \
cname &operator=(cname &&) = delete; /* NOLINT */
#define DISALLOW_COPY_AND_MOVE(cname) \
DISALLOW_COPY(cname); \
DISALLOW_MOVE(cname);
#define MACH_WRITE_TO(Type, Buf, Data) \
do { \
*reinterpret_cast<Type *>(Buf) = (Data); \
} while (0)
#define MACH_WRITE_UINT32(Buf, Data) MACH_WRITE_TO(uint32_t, (Buf), (Data))
#define MACH_WRITE_INT32(Buf, Data) MACH_WRITE_TO(int32_t, (Buf), (Data))
#define MACH_WRITE_STRING(Buf, Str) \
do { \
memcpy(Buf, Str.c_str(), Str.length()); \
} while (0)
#define MACH_READ_FROM(Type, Buf) (*reinterpret_cast<const Type *>(Buf))
#define MACH_READ_UINT32(Buf) MACH_READ_FROM(uint32_t, (Buf))
#define MACH_READ_INT32(Buf) MACH_READ_FROM(int32_t, (Buf))
#define MACH_STR_SERIALIZED_SIZE(Str) (4 + Str.length())
#define ALLOC(Heap, Type) new (Heap.Allocate(sizeof(Type))) Type
#define ALLOC_P(Heap, Type) new (Heap->Allocate(sizeof(Type))) Type
#define ALLOC_COLUMN(Heap) ALLOC(Heap, Column)
#endif // MINISQL_MACROS_Hinline
基本上是给函数用的
现代 C++ 编译器会自行决定是否内联优化,所以现在 inline 的更重要的作用是允许函数定义直接写到头文件 .h 里而不会引起 multiple definition fault 。
For instance:
cpp
inline const RowId GetRowId() const {
return rid_;
}这里 getter 很短,所以直接写在头文件里了。
this
class 里的成员函数一般先在当前函数内找变量,找不到再到成员变量里找。
一般:
cpp
class A {
private:
int val = 10;
public:
int get() {
return val; // 等价于 return this->val;
}
};class 里存在同名遮蔽,只有在需要消除歧义时需要显示写 this->val
cpp
class A {
private:
int val = 10;
public:
void set(int val) {
val = val; // 两边都是参数 val,成员变量没有被修改
this->val = val; // 左边是成员变量,右边是参数
}
};void print() const { cout << val; } 这里的const表示什么?
这里的 const 表示:这个成员函数不会修改当前对象的成员变量。
cpp
class Counter{
void print() const { cout << val; }
}等价含义是:
cpp
void print(const Counter* this)前置 ++c 和后置 c++ 是怎么区分的?
靠函数签名区分。
在 C++ 里,前置 ++c 和后置 c++ 都可以重载,但写法不同:
// 前置 ++cCounter& operator++() { ++val; return *this;}// 后置 c++Counter operator++(int) { Counter old = *this; ++val; return old;}关键区别是:
operator++() // 前置++operator++(int) // 后置++后置版本多了一个假的 int 参数。这个参数只是给编译器区分前置/后置用的,通常不用它。
调用关系:
++c;编译器翻译成:
c.operator++();调用:
Counter& operator++()c++;编译器翻译成:
c.operator++(0);调用:
Counter operator++(int)这里的 0 是编译器自动传进去的占位参数。
new Derived 等于 new Derived() 吗?
大多数你现在遇到的类对象场景,可以先理解为:基本等价,都会调用默认构造函数。
Derived* p1 = new Derived;Derived* p2 = new Derived();如果 Derived 是类,并且有默认构造函数,二者都会构造一个 Derived 对象。
例如:
class Derived {public: Derived() { cout << "constructor" << endl; }};Derived* p1 = new Derived; // 调用 Derived()Derived* p2 = new Derived(); // 调用 Derived()都会输出构造函数信息。
但对内置类型有区别:
int* p1 = new int; // 未初始化,值不确定int* p2 = new int(); // 值初始化,得到 0所以:
new int得到不确定值;
new int()得到 0。
final 怎么理解?
作用 1:禁止类继续被继承
class A final {};那么:
class B : public A {};会报错。
意思是:
A到此为止,不能再派生子类。
作用 2:禁止虚函数继续被重写
cpp
class Base {
public:
virtual void show() {}
};
class Derived : public Base {
public:
void show() final {}
};
class MoreDerived : public Derived {
public:
void show() {} // 错误
};意思是:
Derived::show()是最后版本,后面的子类不能再 override。
更完整常见写法:
void show() override final {}表示:
这是对基类虚函数的重写,并且禁止后续子类继续重写。虚函数解决的是“用基类指针调用派生类函数”的问题
核心场景不是:
Derived* p = new Derived;p->show();而是:
Base* p = new Derived;p->show();因为实际工程里常常这么写:
vector<Base*> objects;objects.push_back(new Derived1);objects.push_back(new Derived2);objects.push_back(new Derived3);for (Base* p : objects) { p->show();}你希望虽然它们都被放在 Base* 里,但运行时能调用各自派生类的版本。这就需要 virtual。
在继承中:
class Base {public: void show() {}};class Derived : public Base {public: void show(int x) {}};Derived::show(int) 会隐藏 Base::show()。
所以:
Derived d;d.show(1); // 正确,调用 Derived::show(int)d.show(); // 错误,Base::show() 被隐藏了很多初学者会以为 d.show() 应该调用 Base::show(),但不会。因为只要派生类里出现了同名函数 show,基类的同名函数先被隐藏,不参与普通查找。
类型转换
const_cast
对象本身不是 const,只是被 const 指针/引用看到了:
int x = 10;const int* cp = &x; // 通过 cp 看,不能修改int* p = const_cast<int*>(cp); // 去掉 const*p = 20; // 合法,因为 x 本身不是 const所以 const_cast 不是“让 const 对象变可改”,而是:
去掉表达式类型里的 const 限制。对象本身如果真是 const,仍然不能安全修改。