C++ 类型转化和拷贝

 

写一些经常会忘记的知识点

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,重新写一个深拷贝。

来看一个相似的概念,操作系统的文件管理。

软链接和硬链接

软连接 ~~ 浅拷贝

软链接的概念

  • 只存储路径,不复制文件数据,看成快捷方式
  • 源文件删除,软链接打不开(像不像“悬垂指针”
  • 创建新链接不会影响原文件,软链接指向的内容始终依赖于原文件。

软链接和浅拷贝相似点

  1. 都是只复制引用,不复制数据本身。(浅拷贝复制基本数据(int, double),这里不复制数据本身,是指指针所指的数据)
  2. 如果原数据删除,引用失效。
  3. 多个对象,链接共享相同的资源。

硬链接的概念:

  • 多个文件名指向同一个数据
  • 删除原文件不会影响硬链接,数据依然存在
  • 所有硬链接完全各自读离,任何一个都可以看成主文件。

硬链接和深拷贝的相似点:

  1. 都会重新分配内存,并复制数据,因此每个对象都有自己独立的数据。
  2. 删除原对象不会影响新对象。
  3. 修改一个也不会影响另一个。