目录
在C/C++中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化。注意,类型转换发生也需要两种类型是有一定关联的。类型转换中间都会产生临时变量。
1、内置类型之间
内置类型之间有两种转换方式:
(1)隐式类型转换 整型之间/整型与浮点数之间
(2)显示类型转换 指针与整型/指针之间
隐式类型转换要求两种类型关联性更强,如上面整型的int和char,整型与浮点数等都是表示一个数据的大小,只是有可能会丢失一些精度而已。
显示类型转换要求的关联性就没那么强,但也是必须要有一定关联的。如指针是一个编号,所以整型才可以转。
void Test()
{
int i = 1;
// 隐式类型转换
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%x, %d\n", p, address);
}
2、内置类型与自定义类型之间
2.1 自定义类型 = 内置类型
class A
{
public:
A(int a)
:_a1(a)
,_a2(a)
{}
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
A aa1 = 1;
A aa2 = { 2,2 };
A& aa3 = { 2,2 }; // 会报错
return 0;
}
aa1和aa2分别是单参数的隐式类型转换和多参数的隐式类型转换,注意aa2不是initializer_list。
所以,内置类型要转换为自定义类型不是直接转,而是依靠构造函数来转的
若不想一个构造函数参与隐式类型转换,可以在这个构造函数前面加上explicit
class A
{
public:
explicit A(int a)
:_a1(a)
,_a2(a)
{}
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
像这样就是禁止了单参数的隐式类型转换,但是仍然是可以强转的
int main()
{
A aa1 = 1; // 会报错
A aa4 = (A)1;
A aa2 = { 2,2 };
A& aa3 = { 2,2 }; // 会报错
return 0;
}
2.2 内置类型 = 自定义类型
此时需要重载运算符,()被仿函数占用了,所以不能用
class A
{
public:
A(int a)
:_a1(a)
,_a2(a)
{}
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
operator int()
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
A aa1 = 1;
A aa2 = { 2,2 };
int x = aa1;
int y = aa2;
int z = aa1.operator int();
cout << x << endl;
cout << y << endl;
cout << z << endl;
return 0;
}
转换为int需要重载一个operator int
也可以在operator int前面加一个explicit,这样可以防止隐式类型转换,但是还是可以强制类型转换
shared_ptr中就重载了operator bool,用来返回内部指针是否是空指针
3、自定义类型与自定义类型之间
class A
{
public:
A(int a)
:_a1(a)
, _a2(a)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(int b)
:_b1(b)
{}
private:
int _b1 = 1;
};
int main()
{
A aa1(1);
B bb1(2);
bb1 = aa1; // 会报错
return 0;
}
直接转换是不行的,需要对B实现一个关于A的构造函数
class A
{
public:
A(int a)
:_a1(a)
, _a2(a)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
int get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(int b)
:_b1(b)
{}
B(const A& aa)
:_b1(aa.get())
{}
private:
int _b1 = 1;
};
int main()
{
A aa1(1);
B bb1(2);
bb1 = aa1; // 会报错
return 0;
}
实际上,自定义类型与内置类型、自定义类型与自定义类型之间是超过构造函数或运算符重载来建立关联的,因为类型转换前提是要有关联。
template<class T>
struct __List_node//创建一个T类型的链表结点
{
__List_node(const T& data = T())//构造函数
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
__List_node<T>* _next;
__List_node<T>* _prev;
T _data;
};
template<class T, class Ref, class Ptr>
struct __List_iterator//封装链表的迭代器
{
typedef __List_node<T> Node;
typedef __List_iterator<T, Ref, Ptr> Self;
Node* _node;//成员变量
__List_iterator(Node* node)//构造函数。将迭代器中的结点初始化成传过来的结点
:_node(node)
{}
// *it
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
// ++it
Self& operator++()
{
_node = _node->_next;
return *this;
}
// it++
Self operator++(int)
{
Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的
//_node = _node->_next;
++(*this);
return tmp;
}
// --it
Self& operator--()
{
_node = _node->_prev;
return *this;
}
// it--
Self operator--(int)
{
Self tmp(*this);
//_node = _node->_prev;
--(*this);
return tmp;
}
// it != end()
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
template<class T>
class List//真正的链表
{
public:
typedef __List_node<T> Node;//将链表结点的名称重命名为Node
typedef __List_iterator<T, T&, T*> iterator;
typedef __List_iterator<T, const T&, const T*> const_iterator;
//带头双向循环链表
List()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
List(std::initializer_list<T> il)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (auto e : il)
push_back(e);
}
~List()
{
clear();
delete _head;
_head = nullptr;
}
List(const List<T>& lt)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
//const_iterator it = lt.begin();//这里迭代器不需要指定是那个类域,因为就是在这个类中使用
//while (it != lt.end())
//{
// push_back(*it);
// ++it;
//}
for (const auto& e : lt)//这里与上面用迭代器一样,因为最终也会被替换成迭代器
push_back(e);
}
/*List<T>& operator=(const List<T>& lt)
{
if (this != <)
{
clear();
for (const auto& e : lt)
push_back(e);
}
return *this;
}*/
List<T>& operator=(List<T> lt)
{
swap(_head, lt._head);//原来的空间给这个临时变量,因为这个临时变量是自定义类型,出了作用域后会自动调用析构函数
return *this;
}
void clear()//clear是清除除了头节点意外的所以结点
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
void push_back(const T& x)//一定要用引用,因为T不一定是内置类型
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
/*insert(end(),x);*/
}
void pop_back()
{
Node* tail = _head->_prev;
Node* prev = tail->_prev;
delete tail;
_head->_prev = prev;
prev->_next = _head;
//erase(--end());
}
void push_front(const T& x)
{
Node* first = _head->_next;
Node* newnode = new Node(x);
_head->_next = newnode;
newnode->_prev = _head;
newnode->_next = first;
first->_prev = newnode;
//insert(begin(), x);
}
void pop_front()
{
Node* first = _head->_next;
Node* second = first->_next;
delete first;
_head->_next = second;
second->_prev = _head;
//erase(begin());
}
void insert(iterator pos, const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
}
void erase(iterator pos)
{
assert(pos != end());//不能删除头节点
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
cur = nullptr;
}
iterator begin()
{
return iterator(_head->_next);//使用这个结点去构造一个迭代器,并将这个迭代器返回
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);//使用这个结点去构造一个迭代器,并将这个迭代器返回
}
const_iterator end() const
{
return const_iterator(_head);
}
private:
Node* _head;
};
在外面前面自己实现的链表中,是没有办法用普通迭代器来构造一个const迭代器的,这两个是自定义类型之间的转换,我们没有实现对应的构造函数,所以不行。现在,我们来实现一个用普通迭代器构造const迭代器的构造函数
int main()
{
List<int> lt = { 1,2,3,4 };
List<int>::const_iterator cit = lt.begin();
return 0;
}
这样会报错,因为lt不是const的,调用begin后返回的是一个普通迭代器,赋值给const迭代器就会报错
emplate<class T, class Ref, class Ptr>
struct __List_iterator//封装链表的迭代器
{
typedef __List_node<T> Node;
typedef __List_iterator<T, Ref, Ptr> Self;
Node* _node;//成员变量
__List_iterator(Node* node)//构造函数。将迭代器中的结点初始化成传过来的结点
:_node(node)
{}
__List_iterator(const __List_iterator<T, T&, T*>& it)
:_node(it._node)
{}
// *it
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
// ++it
Self& operator++()
{
_node = _node->_next;
return *this;
}
// it++
Self operator++(int)
{
Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的
//_node = _node->_next;
++(*this);
return tmp;
}
// --it
Self& operator--()
{
_node = _node->_prev;
return *this;
}
// it--
Self operator--(int)
{
Self tmp(*this);
//_node = _node->_prev;
--(*this);
return tmp;
}
// it != end()
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
加入了用普通迭代器构造迭代器的构造函数后就可行了
4、C++强制类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
4.1 static_cast
static_cast就是隐式类型转换 -- 数据的意义没有发生变化,只是丢了精度或增加了精度
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
int b = d; // a和b是相同的
cout << a << endl;
cout << b << endl;
// 下面这样是不行的,不能隐式类型转换的也不能用static_cast
int* p = static_cast<int*>(a);
return 0;
}
4.2 reinterpret_cast
reinterpret_cast就是强制类型转换 -- 数据的意义已经发生变化
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
int b = d; // a和b是相同的
cout << a << endl;
cout << b << endl;
// 下面这样是不行的,不能隐式类型转换的也不能用static_cast
//int* p = static_cast<int*>(a);
int* p1 = reinterpret_cast<int*>(a);
int* p2 = (int*)(a); // p1和p2是相同的
int c = reinterpret_cast<int>(d); // 这样不行
return 0;
}
4.3 const_cast
const_cast也是强制类型转换,用const_cast是为了提示是存在风险的去掉const属性
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << *p << endl;
cout << a << endl;
return 0;
}
结果是
为什么已经将*p = 3了,a的值依旧没有变化呢?
是因为编译器的优化,编译器面对const变量,有些会将其放到寄存器中,有些地方会直接在打印时将a的值替换成2,内存中的a确实变成了3(通过监视窗口也可以看见),只是打印时被替换成了2
此时可以在const前面添加一个volatile
int main()
{
volatile const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << *p << endl;
cout << a << endl;
return 0;
}
结果是
此时就不是直接打印2了,而是先将a放入寄存器,再从寄存器中取值
4.4 dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
class A
{
public:
virtual void f() {}
int _a = 1;
};
class B : public A
{
public:
int _b = 2;
};
void fun(A* pa)
{
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
B* pb1 = static_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
cout << pb1->_b << endl;
pb1->_b++;
cout << pb1->_b << endl; // 这一句会报错
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
父类转为子类可以通过构造函数来完成
父类转为子类默认是不完全的,因为子类多出的成员变量是随机值,若对其就行写操作,则会报错
所以,父类转子类需要使用dynamic_cast,dynamic_cast会检查是否安全,能成功则转换,不能成功则返回空
class A
{
public:
virtual void f() {}
int _a = 1;
};
class B : public A
{
public:
int _b = 2;
};
void fun(A* pa)
{
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
B* pb1 = dynamic_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
结果是,父类转成子类不行,子类转成子类可以,若像父类转成子类,需要在子类实现一个关于父类的构造函数
需要父类包含虚函数,因为本质是通过在虚表中加入一些标识来完成的。在父类中加一个标识,在子类中加一个标识,调用dynamic_cast时,通过标识看是指向子类还是父类,然后返回相应的值