C++ 隐式转换 explicit 单双引号 this指针

隐式转换

什么是隐式转换

C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换。

C++隐式转换原则

  • 基本数据类型:基本数据类型的转换以取值范围作为转换基础(保证精度不丢失)。 隐式转换发生在从小->大的转换中,比如从char转换为int 从int->long。
  • 自定义对象: 子类对象可以隐式转换为父类对象。

C++ 隐式转换发生条件

  • 混合类型的算术运算表达式中,eg:
int a = 3;
double b = 4.5;
a+b; // a将会自动转换为double类型,转换的结果和b进行加法操作
  • 不同类型的赋值操作中。eg:
int a = true; // bool类型转换为int类型
int *ptr = null; //null 转换为 int*类型
  • 函数传参。
void func(double a);
func(1); // 1被隐式的转换为double类型1.0
  • 函数返回值。
double add(int a, int b)
{
    return a + b;
} //运算的结果会被隐式的转换为double类型返回

以上四种情况下的隐式转换,都满足了一个基本原则:低精度 –》 高精度转换。
不满足该原则,隐式转换是不能发生的。

当然这个时候就可以使用与之相对于的显式类型转换(又称强制类型转换),使用方法如下:

double a = 2.0;
int b = (int)a;

使用强制类型转换会导致精度的损失

隐式转换的风险

隐式转换的风险一般存在于自定义的类构造函数中。

按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象

  • 例1
class String
{
public:
    String ( const char* p ); // 用C风格的字符串p作为初始化值
    //…
}
 
String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(”hello”)

但是有的时候可能会不需要这种隐式转换,如下:

class String
{
public:
    String ( int n ); //本意是预先分配n个字节给字符串
    String ( const char* p ); // 用C风格的字符串p作为初始化值
 
    //…
}

下面两种写法比较正常:
String s2 ( 10 ); //OK 分配10个字节的空字符串
String s3 = String ( 10 ); //OK 分配10个字节的空字符串

下面两种写法就比较疑惑了:
String s4 = 10; //编译通过,也是分配10个字节的空字符串
String s5 = ‘a’; //编译通过,分配int(‘a’)个字节的空字符串
s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。

  • 例2
class Test
{
public:
  Test(int a);
  bool isSame(Test other)
  {
    return m_val == other.m_val;
  }
 
privateint m_val;
}

如下调用:
Test a(10);
If(a.isSame(10)) //该语句将返回true

本来用于两个Test对象的比较,竟然和int类型相等了。
这里就是由于发生了隐式转换,实际比较的是一个临时的Test对象。
这个在程序中是绝对不能允许的。

参考:https://www.cnblogs.com/solidblog/p/3381628.html

explicit 关键字

explicit作用:

在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。

explicit使用注意事项:

  • explicit 关键字只能用于类内部的构造函数声明上。
  • explicit 关键字作用于单个参数的构造函数。
  • 在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换

例子:

未加explicit时的隐式类型转换

class Circle 
   2. { 
   3. public: 
   4.     Circle(double r) : R(r) {} 
   5.     Circle(int x, int y = 0) : X(x), Y(y) {} 
   6.     Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} 
   7. private: 
   8.     double R; 
   9.     int    X; 
  10.     int    Y; 
  11. }; 
  12.  
  13. int _tmain(int argc, _TCHAR* argv[]) 
  14. { 
  15. //发生隐式类型转换 
  16. //编译器会将它变成如下代码 
  17. //tmp = Circle(1.23) 
  18. //Circle A(tmp); 
  19. //tmp.~Circle(); 
  20.     Circle A = 1.23;  
  21. //注意是int型的,调用的是Circle(int x, int y = 0) 
  22. //它虽然有2个参数,但后一个有默认值,任然能发生隐式转换 
  23.     Circle B = 123; 
  24. //这个算隐式调用了拷贝构造函数 
  25.     Circle C = A; 
  26.      
  27.     return 0; 
  28. } 

加了explicit关键字后,可防止以上隐式类型转换发生

   1. class Circle 
   2. { 
   3. public: 
   4.     explicit Circle(double r) : R(r) {} 
   5.     explicit Circle(int x, int y = 0) : X(x), Y(y) {} 
   6.     explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} 
   7. private: 
   8.     double R; 
   9.     int    X; 
  10.     int    Y; 
  11. }; 
  12.  
  13. int _tmain(int argc, _TCHAR* argv[]) 
  14. { 
  15. //一下3句,都会报错 
  16.     //Circle A = 1.23;  
  17.     //Circle B = 123; 
  18.     //Circle C = A; 
  19.      
  20. //只能用显示的方式调用了 
  21. //未给拷贝构造函数加explicit之前可以这样 
  22.          Circle A = Circle(1.23); 
  23.         Circle B = Circle(123); 
  24.         Circle C = A; 
  25.  
  26. //给拷贝构造函数加了explicit后只能这样了 
  27.          Circle A(1.23); 
  28.         Circle B(123); 
  29.         Circle C(A); 
  30.     return 0; 
  31. } 

https://www.cnblogs.com/this-543273659/archive/2011/08/02/2124596.html

单引号与双引号

'a'表示是一个字符,"a"表示一个字符串相当于'a'+'\0';
''里面只能放一个字符;
""里面表示是字符串系统自动会在串末尾补一个 0。
https://www.runoob.com/note/41014

c++中单引号表示一个字符,双引号表示字符串
python中单引号与双引号是一样的
https://www.cnblogs.com/ymjyqsx/p/6655485.html

this指针

this指针的用处:

一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。   例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .

this指针的使用:

一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时,如this->n = n (不能写成n = n)。

this指针程序示例:

this指针存在于类的成员函数中,指向被调用函数所在的类实例的地址。   根据以下程序来说明this指针

#include   

class Point   {   int x, y;   

public:   

Point(int a, int b) { x=a; y=b;}   

void MovePoint( int a, int b){ x+=a; y+=b;}   

void print(){ cout<<"x="<  

};   

void main( )   {   

Point point1( 10,10);   

point1.MovePoint(2,2);   

point1.print( );   

}   

当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。

MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:

void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}   即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。   即该函数过程可写成 point1.x+= a; point1. y + = b;

关于this指针的一个经典回答:

当你进入一个房子后,

你可以看见桌子、椅子、地板等,

但是房子你是看不到全貌了。

对于一个类的实例来说,

你可以看到它的成员函数、成员变量,

但是实例本身呢?

this是一个指针,它时时刻刻指向你这个实例本身

类的this指针有以下特点:

  1. this只能在成员函数中使用。

全局函数、静态函数都不能使用this.

实际上,成员函数默认第一个参数为T * const this。

如:

 class A

 {

  public:

     int func(int p)

     {

     }

 };

其中,func的原型在编译器看来应该是:

int func(A * const this,int p);

  1. 由此可见,this在成员函数的开始前构造,在成员函数的结束后清除。

这个生命周期同任何一个函数的参数是一样的,没有任何区别。

当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传递进去。如:

A a;

a.func(10);

此处,编译器将会编译成:

A::func(&a,10);

看起来和静态函数没差别,对吗?不过,区别还是有的。编译器通常会对this指针做一些优化,因此,this指针的传递效率比较高–如VC通常是通过ecx寄存器传递this参数的。

  1. 几个this指针的易混问题。

A. this指针是什么时候创建的?

this在成员函数的开始执行前构造,在成员的执行结束后清除。

但是如果class或者struct里面没有方法的话,它们是没有构造函数的,只能当做C的struct使用。采用 TYPE xx的方式定义的话,在栈里分配内存,这时候this指针的值就是这块内存的地址。采用new的方式 创建对象的话,在堆里分配内存,new操作符通过eax返回分配 的地址,然后设置给指针变量。之后去调 用构造函数(如果有构造函数的话),这时将这个内存块的地址传给ecx,之后构造函数里面怎么处理请 看上面的回答。

B. this指针存放在何处?堆、栈、全局变量,还是其他?

this指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全局变量。在汇编级 别里面,一个值只会以3种形式出现:立即数、寄存器值和内存变量值。不是存放在寄存器就是存放在内 存中,它们并不是和高级语言变量对应的。

C. this指针是如何传递类中的函数的?绑定?还是在函数参数的首参数就是this指针?那么,this指针 又是如何找到“类实例后函数的”?

大多数编译器通过ecx寄存器传递this指针。事实上,这也是一个潜规则。一般来说,不同编译器都会遵从一致的传参规则,否则不同编译器产生的obj就无法匹配了。

在call之前,编译器会把对应的对象地址放到eax中。this是通过函数参数的首参来传递的。this指针在调用之前生成,至于“类实例后函数”,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。

D. this指针是如何访问类中的变量的?

如果不是类,而是结构体的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,就很容易理解这个问题了。

在C++中 ,类和结构是只有一个区别的:类的成员默认是private,而结构是public。

this是类的指针,如果换成结构,那this就是结构的指针了。

E. 我们只有获得一个对象后,才能通过对象使用this指针。如果我们知道一个对象this指针的位置,可以直接使用吗?

this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以通过&this获得),也可以直接使用它。

F. 每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?

普通的类函数(不论是成员函数,还是静态函数)都不会创建一个函数表来保存函数指针。只有虚函数才会被放到函数表中。但是,即使是虚函数,如果编译器能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是会直接调用该函数。
参考:http://blog.chinaunix.net/uid-21411227-id-1826942.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值