摘要:不能进行隐式转换跟它相对应的另一个关键字是,意思是隐藏的,类构造函数默认情况下即声明为隐式。通过排他性来定义,每个表达式不是就是。返回容器中倒数第一个元素的常量迭代器。这种转换的安全性也要开发人员来保证。
目录
explicit /ɪkˈsplɪsɪt/ 明确的;清楚的;直率的;详述的
作用是表明该构造函数是显示的, 而非隐式的。不能进行隐式转换!跟它相对应的另一个关键字是implicit,意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。
例如我们定义一个类
class student {public: student(int age) { this->age = age; cout << "age=" << age << endl; } student(int age, string name) { this->age = age; this->name = name; cout << "age=" << age << " name=" << name << endl; } ~student() { } int getAge() { return this->age; } string getName() { return this->name; }private: string name; int age;};
在主函数中分别使用显示构造和隐式构造
int main() { //显示构造,直接在括号里面写 student xiaoM(18); //显示构造 student xiaoH(19, "小花"); //隐式构造 student xiaoW = 18; //隐式构造(C++11前编译器无法通过) student xiaoZ = { 19,"小张" }; system("pause"); return 0;}
执行结果
当给其中一个构造函数加上explicit关键字后
class student {public: explicit student(int age) { this->age = age; cout << "age=" << age << endl; }private: string name; int age;};
这种声明就表示:这个构造函数只能显示构造,无法进行隐式转换
explicit关键字的作用在于:在实际开发中 等号会产生一些歧义,等号本身又有赋值的作用,无法在一瞬间辨别等号是用于赋值还是调用隐式构造,不便于阅读。
我们在日常编译程序时,有时可能会遇到如下报错提示:
源代码
#includeusing namespace std;int demo() { int i = 0; return i;}int main() { //将函数返回作为左值 demo() = 888; return 0;}
这里将函数返回赋予888,编译器给出“表达式必须是可修改的左值”的报错提示 ,如果我们将函数返回作为右值,则不会有此报错~
关于左值和右值的问题,这里就涉及到计算机的存储层次结构
存储层次结构
例如我们计算 c = a + b ;
a 和 b 的值会保存在内存中,当程序遇到c = a + b,会将a和b的值拿到cpu的寄存器中,再将计算结果返回给内存中的c,当我们Ctrl + S后,内存中的数据就可以永久保存到磁盘中。
左值和右值的概念
按字面意思,通俗地说。以赋值符号 = 为界,等号(=)左边的就是左值(lvalue),等号(=) 右边就是右值(rvalue)。
lvalue —— 代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。
rvalue —— 通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的 lvalue的定义,rvalue是在不在内存中占有确定位置的表达式,而是存在在寄存器中。
所有的左值(无论是数组,函数或不完全类型)都可以转换成右值。
我们将下面程序转为汇编码观察
#includeusing namespace std;int main() { int a = 1; int b = 2; int c = 3; c = a + b; printf("%d", c); return 0;}
汇编代码
程序将a、b、c的值都存储在栈上,当执行 c= a + b 时,程序会将 a 和 b 的值拿到寄存器中计算,再将计算结果返回给内存中 c 的位置上去。
也就是说,程序执行的方式,是从内存中取值放到寄存器中,再将计算结果返回给内存中的某个位置!
a + b的结果保存在寄存器中,再将寄存器的值返回给内存
因此,对于 c = a + b;是合法的
而 a + b = c;也就是eax = c就是非法,左值需要在内存中有位置,而a + b是在寄存器 中
上面我们讲了左值与右值,当我们把函数返回值作为左值时,编译器给出报错提示,而C++11的新增特性“函数返回引用”又可以实现函数返回值作为左值的操作
C++引用使用时的难点:
1.当函数返回参数是引用时
若返回栈变量(自动变量/临时变量),不能成为其它引用的初始值,不能作为左值使用。
2. 若返回静态变量或全局变量
·可以成为其他引用的初始值
·即可作为右值使用,也可作为左值使用
3. 返回形参当引用
(注:C++链式编程中,经常用到引用,运算符重载专题) 例如在重载“<<"左移运算符后,我们想要链式输出,就是返回的cout引用
下面,对第一种种情况分别通过代码举例 ?
1. 函数返回值为引用,返回临时变量作为其他引用的初始值
#includeusing namespace std;int& demo() { int i = 600; return i; //返回局部变量 }int main() { int &a =demo(); //函数引用成为其他引用的初始值 cout << a << endl; system("pause"); return 0;}
编译结果
编译器并未报错,也只是给出警告 “ 返回局部变量 ” 。好像目测也没啥问题 ,下面通过一段代码示例,来解释这种操作的不合理性。?看操作。
我们知道,通过引用可以将两个变量都有同一块地址
因此,对于上面的代码,a 的地址和局部变量 i 有相同的地址
我们现在更改a的值,然后再调用demo函数
int main() { int &a = demo(); cout << "a的地址为:" <<&a<
运行结果:
第一次demo函数执行,i 的地址为00D3F854,a的地址也为00D3F854。
然后demo函数执行完毕后,在栈内的空间释放。
第二次demo函数也是在栈的相同位置存储,所以 i 的位置没有改变,因此 i =600后也影响了a的值。
如果我们在栈中使用两个函数,会是什么结果呢?
#include#includeusing namespace std;int demo1() { int i = 100; cout << "demo1()函数中,i的地址为:" << &i << endl; return 0;}int& demo() { int i = 600; cout << "i的地址为:" << &i << endl; return i; //返回局部变量}int main() { int &a = demo(); cout << "a的地址为:" <<&a<
执行结果
也就是说,如果用函数返回引用,返回局部变量和引用初始化,程序会继续认为这个地址还是属于计算机的,并非是程序员自己申请的。
2.返回局部变量作为左值
情况与上面类似
#include#includeusing namespace std;int demo1() { int i = 0; cout << "demo1()函数中,i的地址为:" << &i << endl; return 0;}int& demo(int **p) { int i = 600; *p = &i; cout << "i的地址为:" << &i << " i的值为:" << i << endl; return i; //返回局部变量}int main() { int* p = NULL; demo(&p) = 888; cout << "p的地址为:" << p << " p的值为:" << *p << endl; demo1(); cout << "p的地址为:" << p << " p的值为:" << *p << endl; system("pause"); return 0;}
执行结果
总结:若返回栈变量(自动变量/临时变量),不能成为其它引用的初始值,不能作为左值使用,虽然编译器只会警告,但在实际开发中是非常危险的操作!
3.例如返回static或者全局变量就不出出现这种情况
#include#includeusing namespace std;int& demo() { static int i = 600; cout << "i的地址为:" << &i << endl; return i; //返回静态变量}int main() { int a = demo(); a = 888; cout << "调用了demo(),a的值为:" << a << endl; demo(); cout << "再次调用了demo(),a的值为:" << a << endl; system("pause"); return 0;}
执行结果
其实,主要还是看返回值的生命周期,如果是立刻销毁的,则应注意使用~
array容器概念
- array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。
- array是将元素置于一个固定数组中加以管理的容器。
- array可以随机存取元素,支持索引值直接存取, 用[]操作符或at()方法对元素进行操作,也可以使用迭代器访问
- 不支持动态的新增删除操作
- array可以完全替代C语言中的数组,使操作数组元素更加安全!
- #include
array特点
- array 容器的大小是固定的,无法动态的扩展或收缩,这也就意味着,在使用该容器的过程无法增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。
- STL 还提供有可动态扩展或收缩存储空间的 vector 容器
array对象的构造
array采用模板类实现,array对象的默认构造形式
array
arrayT; //T为存储的类型, 为数值型模板参数 //构造函数
#include
array a1; //一个存放5个int的array容器array a2; //一个存放6个float的array容器array a3; //一个存放7个student的array容器
array的赋值
array 的赋值
a1.assign(0); //玩法一 改变array中所有元素(注:将被废弃,不推荐使用)
在目前版本,编译器会报错
如果非要使用assign的话可以根据报错提示加一个宏
#define _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING 1
a1.fill(666); //玩法二 用特定值填充array中所有元素
array
test={1, 2, 3, 4};// 玩法三 定义时使用初始化列表,列表内的数量不能超过定义的大小
array
test; test={1,2,3,4}; //玩法四 定义后使用列表重新赋值
array
a1,a2; a1={1,2,3,4};
a2 = a1;//玩法五,赋值运算
a1.swap(a2); //玩法六 和其它array进行交换
array的大小
array.size(); 返回容器中元素的个数array.empty(); 判断容器是否为空,逗你玩的,因为容器是固定大小,永远为 falsearray.max_size(); 返回容器中最大元素的个数,数组是固定大小,这也是逗你玩的同size()。
array的数据存取
第一 使用下标操作 a1[0] = 100;
第二 使用at 方法 如: a1.at(2) = 100;
第三 接口返回的引用 a1.front() 和 a1.back()
注意: 第一和第二种方式必须注意越界
array 迭代器访问
#include
using namespace std;array.begin(); 返回容器中第一个数据的迭代器。array.end(); 返回容器中最后一个数据之后的迭代器。array.rbegin(); 返回容器中倒数第一个元素的迭代器。array.rend(); 返回容器中倒数最后一个元素的后面的迭代器。array.cbegin(); 返回容器中第一个数据的常量迭代器。array.cend(); 返回容器中最后一个数据之后的常量迭代器。array.crbegin(); 返回容器中倒数第一个元素的常量迭代器。array.crend(); 返回容器中倒数最后一个元素的后面的常量迭代器。 #include
#include using namespace std;int main() { array arrayInt = { 1,2,3,4,5 }; 普通迭代器 for (array ::iterator ite = arrayInt.begin(); ite != arrayInt.end(); ite++) cout << *ite << " "; 常量迭代器,常量迭代器无法修改 array ::const_iterator ite = arrayInt.cbegin(); 逆向迭代器,逆向迭代器也是++ for (array ::reverse_iterator ite = arrayInt.rbegin(); ite != arrayInt.rend(); ite++) cout << *ite << " ";} set.rbegin()与set.rend()。略。
旧式转型 C风格的强制类型
type b = ( type ) a
例如:
int i = 48;
char c = (char ) i;
double PI = 3.1415926;int i = PI; 隐式转换int i1 = (int) PI ; 强制类型转换强制类型转换,整数直接变指针int * addr = (int*) 0x888888; void *p;int * int_arg = (int*) p; 强制转换
新式转型C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。
格式:
type b = 类型操作符<type> ( a )
类型操作符= static_cast | reinterpreter_cast | dynamic_cast | const_cast
静态类型转换(斯文的劝导,温柔的转换)(编译器会检查转换是否合理)。如int转换成char
char a = "A";int b = static_cast
(a); 主要用法:
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。上行指针或引用(派生类到基类)转换安全,下行不安全(上行可以,下行不安全)
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型
1.用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
class Animal {public: virtual void cry() = 0;};class dog :public Animal {public: void cry() { cout << "小狗汪汪汪" << endl; }};int main() { dog* d = new dog(); Animal* animal = d;//子类对象赋给基类 使用static_cast指针转换 Animal* al = static_cast(d); 引用转换 dog d2; Animal& a2 = static_cast(d2); system("pause"); return 0;}
那为什么说上行转换可以,下行转换不安全呢?
我们再增加一个Animal的派生类
class cat :public Animal {public: void cry() { cout << "小猫喵喵喵" << endl; }};
int main() { dog* d = new dog(); //上行转换安全 Animal* animal = static_cast(d); //下行转换,这种也是安全的 d = static_cast(animal); //animal已经是dog类转过来的,再转成cat的就不安全的 cat* c; c = static_cast(animal); system("pause"); return 0;}
因此,并不是说下行转换不可用,例如上面的将从dog类转过来的animal再转到cat就是不安全的,还是取决于程序员的使用 。
2.基本数据类型之间的转换
int a = 10;char b = static_cast(a);
3.把空指针转换成目标类型的空指针。
int* p = static_cast
(NULL); NULL在C++里定义的是void*0
4.把任何类型的表达式转换成void类型
int* p = new int[10];void* vp = static_cast(p);
总结:使用static_cast可以使编译器做一些合法性检查,不写也没关系。
重新解释类型(挂羊头,卖狗肉) 不同类型间的互转,数值与指针间的互转
用法:
type b = reinterpret_cast
( a )type 必须是一个指针、引用、算术类型、函数指针。
例如强制将整数转为指针int* addr = reinterpret_cast
(0x88888);如果使用static_cast则会报错int* addr1 = static_cast (0x88888);
忠告:滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。
用法1,数值与指针之间转换
int* p = reinterpret_cast(0x88888);int val = reinterpret_cast(p);
用法2, 不同类型指针和引用之间的转换
定义一个父类和两个派生类
class Animal {public: void cry() { cout << "动物叫" << endl; }};class cat :public Animal {public: void cry() { cout << "小猫喵喵喵" << endl; }};class dog :public Animal {public: void cry() { cout << "小狗汪汪汪" << endl; }};
1.隐式上行 转换 与强制上行 转换
dog d1;Animal* animal = &d1;animal->cry();//如果能用static_cast强转,static_cast优先dog* d2 = reinterpret_cast(animal);dog* d3 = static_cast(animal);
2.不同类型指针转换不能用static_cast
3.但是可以使用reinterpre_cast转换
int main() { dog d1; cat* c1 = reinterpret_cast(&d1); c1->cry(); system("pause"); return 0;}
编译器不管程序是将什么类型转的,只晓得最后要获得一个cat类型,然后按cat的存储类型,访问里面的方法。如果滥用reinterpreter_cast,类型之间转来转去,可能会导致程序快速崩溃。
4.引用的强制类型转换
dog d1;Animal& animal = d1;dog& d2 = reinterpret_cast(animal);
reinterpreter_cast 再加一个 static_cast 就已经可以替换C语言的转换
动态类型转换
- 将一个基类对象指针cast到继承类指针,dynamic_cast 会根据基类指针是否真正指向继承类指针来做相应处理。失败返回null,成功返回正常cast后的对象指针;
- 将一个基类对象引用cast 继承类对象,dynamic_cast 会根据基类对象是否真正属于继承类来做相应处理。失败抛出异常bad_cast
注意:dynamic_cast在将父类cast到子类时,父类必须要有虚函数一起玩。
第一种情况 示例代码
#includeusing namespace std;class Animal {public: //父类必须有虚函数 virtual void cry() = 0;};class cat :public Animal {public: void cry() { cout << "小猫喵喵喵" << endl; }};class dog :public Animal {public: void cry() { cout << "小狗汪汪汪" << endl; }};int main() { dog d1; Animal* animal = &d1; cat* ca= dynamic_cast(animal); if (ca==NULL) { cout << "这是只狗!" << endl; } else { cout << "这是只猫" << endl; } system("pause"); return 0;}
运行结果
第二种情况示例代码
int main() { dog d1; Animal& animal = d1; dog& d2 = dynamic_cast(animal); system("pause"); return 0;}
这种是没有任何问题,因为dog与animal是子类与父类的关系,并且animal指针真正指向dog。
但是,如果我们用其他类型接受又会是什么情况呢?
int main() { dog d1; Animal& animal = d1; cat& d2 = dynamic_cast(animal); system("pause"); return 0;}
基类指针并没有指向cat,这种情况编译是没有任何问题,因为编译器会认为这是父类与子类之间的转换,但是当我们运行时,编译器就会抛出异常
对于抛出异常,我们就可以利用try—catch语句捕获异常,保证程序能继续运行
int main() { dog d1; Animal& animal = d1; try { cat& d2 = dynamic_cast(animal); } catch (std::bad_cast bc) { cout << "不是猫,应该是狗" << endl; } system("pause"); return 0;}
运行结果
去const属性。(仅针对于指针和引用)
例如
#includeusing namespace std;void demo(const char* p) { char* p1 = const_cast(p); p1[0] = "A";}int main() { //字符数组 char p[] = "1234567"; demo(p); cout << p << endl; system("pause"); return 0;}
运行结果
或者直接使用const_cast,运行结果也是一样
int main() { char p[] = "1234567"; const_cast(p)[0] = "A"; cout << p << endl; system("pause"); return 0;}
但是,对于常量字符串不能去掉const修改
例如
#includeusing namespace std;void demo(const char* p) { char* p1 = const_cast(p); p1[0] = "A";}int main() { const char *p = "1234567"; demo(p); cout << p << endl; system("pause"); return 0;}
可以编译,但是运行会报错
因为常量所处位置是常量区,内存无法访问常量区。
5.2 类型转换使用建议
1)static_cast静态类型转换,编译的时c++编译器会做编译时的类型检查;隐式转换;
基本类型转换,父子类之间合理转换
2)若不同类型之间,进行强制类型转换,用reinterpret_cast<>() 进行重新解释
建 议:
C语言中 能隐式类型转换的,在c++中可用 static_cast<>()进行类型转换。因C++编译器在编译检查一般都能通过;C语言中不能隐式类型转换的,在c++中可以用 reinterpret_cast<>() 进行强制类型解释。
总结:static_cast<>()和reinterpret_cast<>() 基本上把C语言中的 强制类型转换给覆盖,注意reinterpret_cast<>()很难保证移植性。
3)dynamic_cast<>(),动态类型转换,安全的虚基类和子类之间转换;运行时类型检查
4)const_cast<>(),去除变量的只读属性
最后的忠告:程序员必须清楚的知道: 要转的变量,类型转换前是什么类型,类型转换 后是什么类型,转换后有什么后果。
C++大牛建议:一般情况下,不建议进行类型转换;避免进行类型转换。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/125067.html
摘要:今天逛了逛,顺手精选出了一下近几个月以来上最热门的个项目。相关阅读正式开源,帮助应用快速容器化未来可能会上热门的项目地址介绍哈哈,皮一下很开心。这是我自己开源的一份文档,目前仍在完善中,欢迎各位英雄好汉一起完善。 showImg(https://segmentfault.com/img/remote/1460000015766827?w=391&h=220);今天逛了逛Github,顺...
showImg(https://segmentfault.com/img/remote/1460000019961426); 今天在 Apache Flink meetup ·北京站进行 Flink 1.9 重大新特性进行了讲解,两位讲师分别是 戴资力/杨克特,zhisheng 我也从看完了整个 1.9 特性解读的直播,预计 Flink 1.9 版本正式发布时间大概是 7 月底 8 月初左右正式发...
阅读 980·2021-11-25 09:43
阅读 1383·2021-11-18 10:02
阅读 1716·2021-11-02 14:41
阅读 2255·2019-08-30 15:55
阅读 1037·2019-08-29 16:18
阅读 2523·2019-08-29 14:15
阅读 1366·2019-08-26 18:13
阅读 693·2019-08-26 10:27