简单讨论cpp中的类型转换
扒自http://www.cplusplus.com/doc/tutorial/typecasting/
-
implicit conversion: int to short, short to int, short to bool, float to bool .... (without explicit converter), also called standard conversion.
- converting type such as int to float is known as promotion, is guaranteed to produced the same value in the destination type
- otherwise, may not always be able to represent the same value exactly (丢失精度问题,编译器会发出警告,可以通过explicit conversion去消除)
-
for non-fundamental types:
- arrays and functions implicitly convert to pointers
- pointers in general allow the following conversions:
- Null pointers can be converted to pointers of any type
- Pointers to any type can be converted to void pointers.
- Pointer upcast: pointers to a derived class can be converted to a pointer of an accessible and unambiguous base class, without modifying its const or volatile qualification. (子类指针可以转父类)
(不写英文了日,看不懂)
- implicit conversion
#include <iostream>
using namespace std;
class A {};
class B {
public:
// conversion from A (constructor):
B (const A& a) {}
// conversion from A (assignment):
B& operator= (const A& a) {
return *this;
}
// conversion to A (type-cast operator)
operator A() {
return A();
}
};
int main ()
{
A foo;
B bar = foo; // calls constructor
bar = foo; // calls assignment
foo = bar; // calls type-cast operator
return 0;
}
- 上述是官网给的例子,挺晦涩的
- 首先,构造函数的调用,A foo 调用了A类默认的构造函数,B bar = foo; 这个是调用了自己实现的拷贝构造函数B (const A& a) {},然后 bar = foo; 调用了B& operator=(const A& a) { return *this; },这个是一个针对等号的二元运算符重载,传入参数是const A& a,对应的是等号的左边,然后返回类型B&对应等号的右边,因为B bar是已存的,所以可以接受一个带引用的返回值(B&),这种叫做assignment,就是传说中的赋值,也就是说,赋值的本质就是默认的或者不是默认的=操作符重载(类内的)
- 对于那个type cast operator,就是类型转换操作符,官网的例子中不够引入,引用一个cnblog的例子
#include <iostream>
class D {
public:
D(double d) : d_(d);
operator int() const {
std::cout << "int d is called" << std::endl;
return static_cast<int>(d_);
}
private:
double d_;
};
int add(int a, int b) {
return a + b;
}
int main() {
D d1 = 1.1;
D d2 = 1.2;
std::cout << add(d1, d2) << std::endl;
}
- 这个例子很好理解,就是原本d1, d2是类对象,但是怎么就成了int呢?原因是operator int() const {return static_cast
(d_); } 决定的,cpp官网那个例子对应的也就可以理解了。
分割线-------------------------------------------------------------------------------------------------------
- 虽然这是c++官网上type conversions第一个最最最最最简单的例子,看起来像是hello world,但是我估计多数人(和我一样的学生)是扣不准的,为什么这么说呢?因为它需要点前置知识,比如深浅拷贝,运算符转换何时返回&,还有那个重载A()的运算符转换,赋值和复制,其中最折磨人的就是复制和赋值,网上的博客虽说很多,但是语言精确的很少,包括本小白语言也绝对很不精确,其中有个合集蛮有趣的 https://blog.csdn.net/liitdar/article/details/81877373 这个作者很认真的想把语言和概念还有例子讲清楚,还在linux上写了点例子,但是写到最后估计自己都没发现自己给自己带坑里了不少
- 我们现梳理一下最基本的结构
- 首先是普通构造函数,假设这个类里没有,那么就有一个默认的,对里面的成员做默认的初始化这谁都知道,然后构造函数不返回值,拷贝构造函数也不返回值,然后我们回忆起深浅拷贝的问题,默认的拷贝构造函数的实现就是把所有成员包括指针成员,全都复制一遍,假设有个指针的值是12345678,那么拷贝过去也是12345678,那么就会产生一个对象的生命周期结束会把另外一个对象的成员析构了的诡异现象,所以我们要自己手动定义构造函数,拷贝构造函数何时调用呢?官网写了,返回值的时候,当函数参数的时候,T t = tt; 的时候,T t(tt); 的时候 ----------------这种叫做copy而不是assignment,T t1; T t2 = t1; 这是是copy不是assignment,而T t1; T t2; t2 = t1; ,这是assignment而不是copy。
- 说了这么多废话,有何意义呢?网上很多博客说了,默认的运算符=是浅拷贝,默认的构造函数也是浅拷贝,难道这两个的浅拷贝没区别了么??不是,对象的赋值场景必须是建立在源对象与目标对象均已声明的基础上;而拷贝构造函数函数的赋值法,必须是针对新创建对象的场景,除了这个估计没什么区别了。
分割线---------------------------------------------------------------------------------
继续那个cpp官网的例子
-
接下来是explicit 的用法
// explicit:
#include <iostream>
using namespace std;
class A {};
class B {
public:
explicit B (const A& x) {}
B& operator= (const A& x) {return *this;}
operator A() {return A();}
};
void fn (B x) {}
int main ()
{
A foo;
B bar (foo);
bar = foo;
foo = bar;
// fn (foo); // not allowed for explicit ctor.
fn (bar);
return 0;
}
还有一个cnblog的例子不错 https://www.cnblogs.com/this-543273659/archive/2011/08/02/2124596.html
class Circle {
public:
Circle(double r) : R(r) {}
Circle(int x, int y = 0) : X(x), Y(y) {}
Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}
private:
double R;
int X;
int Y;
};
int main() {
Circle A = 1.23;
Circle B = 123;
Circle C = A;
}
- 在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换
- explicit 关键字只能用于类内部的构造函数声明上。
- explicit 关键字作用于单个参数的构造函数。
- 上述main里的三行Circle全都是implicit conversion
- 有一些博客竟然说explicit不常用??真是惊了我也,负责任么??
加了explicit关键字后,可防止以上隐式类型转换发生
class Circle
{
public:
explicit Circle(double r) : R(r) {}
explicit Circle(int x, int y = 0) : X(x), Y(y) {}
explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}
private:
double R;
int X;
int Y;
};
int _tmain(int argc, _TCHAR* argv[])
{
//一下3句,都会报错
//Circle A = 1.23;
//Circle B = 123;
//Circle C = A;
//只能用显示的方式调用了
//未给拷贝构造函数加explicit之前可以这样
Circle A = Circle(1.23);
Circle B = Circle(123);
Circle C = A;
//给拷贝构造函数加了explicit后只能这样了
Circle A(1.23);
Circle B(123);
Circle C(A);
return 0;
}
- 所以我们不难发现,不论是拷贝构造函数还是普通构造函数,跟=有关系的操作用了explicit全部ban掉了,这样严谨了很多,语意上是如此,好比Class c = 1; 是什么意思?难不成是让c = 1吗?? 显然这个语法糖 it is kind of weird...... 所以google c++ coding standard 写了:不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 explicit 关键字
分割线------------------------------------------------------------------------------------
终于到了type casting,这部分用英文
- C++ is a strong-typed language. Many conversions, specially those that imply a different interpretation of the value, require an explicit conversion, known in C++ as type-casting. There exist two main syntaxes for generic type-casting: functional and c-like:
double x = 10.3;
int y;
y = int (x); // functional notation
y = (int) x; // c-like cast notation
The functionality of these generic forms of type-casting is enough for most needs with fundamental data types. However, these operators can be applied indiscriminately on classes and pointers to classes, which can lead to code that -while being syntactically correct- can cause runtime errors. For example, the following code compiles without errors:
翻译一下,对于这些通用形式的type casting,对基本数据类型是够用的,但是对于指针和类有时候会产生linting正确run time error的情况。
比如
// class type-casting
#include <iostream>
using namespace std;
class Dummy {
double i,j;
};
class Addition {
int x,y;
public:
Addition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
};
int main () {
Dummy d;
Addition * padd;
padd = (Addition*) &d;
cout << padd->result();
return 0;
}
The program declares a pointer to Addition, but then it assigns to it a reference to an object of another unrelated type using explicit type-casting: padd = (Addition*) &d;
Unrestricted explicit type-casting allows to convert any pointer into any other pointer type, independently of the types they point to. The subsequent call to member result will produce either a run-time error or some other unexpected results.
- 综上,不加限制的类型转换可以任意乱转,就如同上面的例子一样,Dummy类和Addition类没有任何关系,但是我们定义了一个Addition类型的指针、一个普通的Dummy对象d,然后取d的指针,在&d左边加上 (Addition) ,这样竟然就可以赋值给一个 Addition 的指针变量了,显然这是不对的。
然后到了面试中最常考的四种cast的部分
1. const cast
// const_cast
#include <iostream>
using namespace std;
void print (char * str)
{
cout << str << '\n';
}
int main () {
const char * c = "sample text";
print ( const_cast<char *> (c) );
return 0;
}
- 以上是cpp官网的例子,它的意思是一个const char* 变量可以用const cast去掉const限制来符合某些函数的参数限制,解释的不够清楚,接下来引用一篇cnblogs https://www.cnblogs.com/ider/archive/2011/07/22/cpp_cast_operator_part2.html 用本人的语言解释一遍
- 要更改const int型变量cst的值,有人会犯这个错误,觉得改了temp, cst就会改,实际上不会
const int cst = 10;
int temp = cst;
temp = 20;
- 于是乎这样做,这样也不对,编译器会说 Error: invalid conversion from 'const int' to 'int',也就是说const int取&地址之后是一个const int * 而不是 int * , const int * 也是不可改的,** 注意c语言是可以这样改但是会给一个warning 而cpp不可以 **
const int cst = 10;
int *temp = &cst;
*temp = 20;
- 假设用引用可以么?不可以,会报错成Error: invalid initialization of reference of type 'int&' from expression of type 'const int' 然后对于c语言呢?c语言没有引用只有取地址&,一个有趣的事情就是这两句话报错的语法问题上, 一个是invalid conversion from XXX to xxx, 一个是invalid initialization of ref xxx of type XX,不知是为何,但是一定可以推敲。。。
const int cst = 10;
int& temp = cst;
temp = 20;
- 那么如何更改一个const的值呢?cpp很tricky的地方在于任意指针可以乱转,这样就不存在一个const int* 不能赋值给int* 的问题了,毕竟在上面的例子中Dummy类指针甚至可以转成一个和它毫无关系的类,const指针也是可以的。
const int constant = 10;
int* modifier = (int*)(&constant);
- 于是乎, const cast的用法
const int constant = 21;
const int* const_p = &constant;
int* modifier = const_cast<int*>(const_p);
*modifier = 7;
- 但是!我们打印constant的时候,却发现依旧是21,我们打印 *const_p 的时候也是21,然后打 *modifiler 却是7,然后这三个的地址全都是一个数。
- 同样下面的例子第一个输出的是100,第二个输出的是10
,玄学的地方是,在我转发那篇blog的评论区,有人指出在vs2008环境下,debug中值是改了的,楼主用的是xcode,我那个例子用的是online gdb(const值被改了,我们假定的是const不被改),有人指出,在调试中是这样,最后结果仍然是楼主的吻合的(const最后输出结果没被改但是调试中被改了)这是为什么?有人评论指出是编译器的问题,编译器识别出const int xxx = 100;然后结尾cout << x;的时候不论如何编译器都当作100输出(常量折叠)但是编译为了检查上下文整体语法只能将计就计的逐个编译,又有人指出这么写int a = 100; const int b = a;可以避免常量折叠,这就是最迷醉的地方。不过已经没有深究的必要了。总之,不要尝试给一个const变量去掉const赋值
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
const int* pa = &a;
int* pb = (int*)pa;
*pb = 100;
cout << a << endl;
cout << *pb << endl;
return 0;
}
static cast
- static_cast can perform conversions between pointers to related classes, not only upcasts (from pointer-to-derived to pointer-to-base), but also downcasts (from pointer-to-base to pointer-to-derived). No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type. Therefore, it is up to the programmer to ensure that the conversion is safe. On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast.
- 翻译一下,static_cast可以在指向相关类的指针之间执行转换,不仅是向上精度的转换(父类指针->子类指针),而且可以是(子类指针->父类指针), 运行时无那种“是否检查你这个对象是否彻彻底底对的上你要的对象”的检查,没有dynamic_cast那种安全检查。
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);
- This would be valid code, although b would point to an incomplete object of the class and could lead to runtime errors if dereferenced. 这是一段合法的代码,尽管b对不上a
- Therefore, static_cast is able to perform with pointers to classes not only the conversions allowed implicitly, but also their opposite conversions. 我不知道这话啥意思
- Convert from void* to any pointer type. In this case, it guarantees that if the void* value was obtained by converting from that same pointer type, the resulting pointer value is the same.(有人会用void*么?????)
- Convert integers, floating-point values and enum types to enum types. (int float转enum)
// copied from zhihu
int i, j;
double slope = static_cast<double>(j) / i;
void *p = &d;
double *dp = static_cast<double*>(p);
dynamic cast
- 我觉得最难的一种,google c++指南禁用了rtti,whatever,我就不用了,奈何面试还考。。。待更
reinterpret_cast
- google c++ coding guide 没有ban这种,这么说的:
用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用. 待更。。。。
(未完待续)
这篇文章写的战战兢兢生怕出错,才疏学浅瑟瑟发抖。。。。。。。。
ref: https://blog.csdn.net/u010700335/article/details/39830425
浙公网安备 33010602011771号