写一些经常会忘记的知识点
1. 隐式类型转化
1.1 常见的写法
int a = 5;
long long b;
b = a;
这就是一种常见的类型转化。
基本都是低精度,转化成高精度;小范围转为大范围
上述示例中就是小范围,转为大范围。
| 类型 | 位 |
|---|---|
| int | 32 |
| long | 64 |
| float | 32 |
| double | 64 |
上面只是部分,不是全部的基本类型。
1.2 可能会造成精度丢失
int 类型 转为 float 类型
可能会出现精度丢失问题。
float 使用IEEE 754来表示,由三部分组成:
- 1位符号位
- 8位指数位
- 23位尾数
可见,float实际上只有23位有效位来存储数字,可是int是32位存储(1位符号位)。
1.3 类中为什么要禁止隐式类型转化
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int value) : value_(value) {}
void show() {
cout << "Value_: " << value_ << endl;
}
private:
int value_;
};
void print(MyClass obj) {
obj.show();
}
int main()
{
print(10); //这个10自动隐式转化为 MyClass类型
return 0;
}
会报错吗,不会。
但有什么问题呢?
- 可读性下降,第一感觉10不会是自定义的类型
- 性能下降,会创建临时变量, 10 -> MyClass类型,在调用show。额外的临时对象。
二义性报错
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int value) : value_(value) {}
void show() {
cout << "Value_: " << value_ << endl;
}
private:
int value_;
};
class OtherClass {
public:
OtherClass(double value) : value_(value) {}
void show() {
cout << "Value_: " << value_ << endl;
}
private:
int value_;
};
void print(MyClass obj) {
obj.show();
}
void print(OtherClass obj) {
obj.show();
}
int main()
{
print(10);
return 0;
}
二义性报错,不知道执行哪一个了。
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int value) : value_(value) {}
void show() {
cout << "Value_: " << value_ << endl;
}
private:
int value_;
};
void print(MyClass obj) {
obj.show();
}
void print(int num) { //重载
cout << num << endl;
}
int main()
{
print(10); //这个10自动隐式转化为 MyClass类型
return 0;
}
这样不是报错,优先比配最优的 printf(int num)
1.4 解决方法
关键字:explicit
explicit SkipList(Comparator cmp, Arena* arena);
构造函数前加上关键字就可以避免隐式类型转化。
1.5 哪些情况可以隐式类型转化
隐式类型转化不是所有情况都是错的
class Complex {
public:
Complex(double r) : real(r), imag(0) {}
private:
double real, imag;
};
Complex c = 10.5;
这样的情况下,是可以的。
具体就仔细看吧,尽量避免隐式类型转化。
2. 拷贝
拷贝是指创建一个对象的副本,即用已有对象的数据来生成新对象。
拷贝主要分为浅拷贝和深拷贝,通过拷贝构造和拷贝赋值来实现。
MyClass obj1;
MyClass obj2 = obj1;
这就是简单的拷贝构造:使用一个已有对象来初始化新对象。
MyClass obj1, obj2;
obj1 = obj2;
拷贝赋值:已有对象的内容赋值给另一个已存在的对象。
禁止拷贝构造和拷贝赋值
SkipList(const SkipList&) = delete; //禁止拷贝构造
SkipList& operator=(const SkipList&) = delete; //禁止拷贝赋值
const SkipList&:
- 为什么要引用?如果按值传递,会不停的创造副本,即无限使用拷贝构造函数来拷贝SkipList,会导致无限递归。
- const?常量引用,不可修改。
SkipList& (返回类型)
- 支持链式赋值,避免了临时变量。
3. 浅拷贝和深拷贝
浅拷贝
- 只是复制对象的指针或基本数据,但不复制指针指向的堆内存。
- 问题:如果对象包含指针成员变量,浅拷贝会导致多个对象共享同一块内存,一个对象析构时会释放该内存,另一个对象就会访问非法的内存。
深拷贝
- 为新对象重新分配内存,并复制数据,避免了多个对象共享同一块内存
- 解决了悬垂指针问题。(浅拷贝造成的,多个指针,指向同一块内存,内存删除后,指针悬垂)
一般自己写代码,会把默认的拷贝函数 delete,重新写一个深拷贝。
来看一个相似的概念,操作系统的文件管理。
软链接和硬链接
软连接 ~~ 浅拷贝
软链接的概念:
- 只存储路径,不复制文件数据,看成快捷方式
- 源文件删除,软链接打不开(像不像“悬垂指针”)
- 创建新链接不会影响原文件,软链接指向的内容始终依赖于原文件。
软链接和浅拷贝相似点
- 都是只复制引用,不复制数据本身。(浅拷贝复制基本数据(int, double),这里不复制数据本身,是指指针所指的数据)
- 如果原数据删除,引用失效。
- 多个对象,链接共享相同的资源。
硬链接的概念:
- 多个文件名指向同一个数据
- 删除原文件不会影响硬链接,数据依然存在
- 所有硬链接完全各自读离,任何一个都可以看成主文件。
硬链接和深拷贝的相似点:
- 都会重新分配内存,并复制数据,因此每个对象都有自己独立的数据。
- 删除原对象不会影响新对象。
- 修改一个也不会影响另一个。
PREVIOUSleveldb源码分析第四章 什么是跳表(SkipList)
NEXTatomic 头文件