资讯专栏INFORMATION COLUMN

C++11 新特性详解 1一览表

NSFish / 979人阅读

摘要:不能进行隐式转换跟它相对应的另一个关键字是,意思是隐藏的,类构造函数默认情况下即声明为隐式。通过排他性来定义,每个表达式不是就是。返回容器中倒数第一个元素的常量迭代器。这种转换的安全性也要开发人员来保证。

目录

1.explicit关键字

2.左值和右值概念

3.函数返回当引用

4.C++11 _array容器用法

5.C++类型转换简介

5.1.static_cas转换

5.2.reinterpreter_cast 转换

5.3.dynamic_cast转换

5.4.const_cast 转换


1.explicit关键字

 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关键字的作用在于:在实际开发中 等号会产生一些歧义,等号本身又有赋值的作用,无法在一瞬间辨别等号是用于赋值还是调用隐式构造,不便于阅读。


2.左值和右值概念

我们在日常编译程序时,有时可能会遇到如下报错提示:

源代码

#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是在寄存器   中


3.函数返回当引用

 上面我们讲了左值与右值,当我们把函数返回值作为左值时,编译器给出报错提示,而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;}

执行结果

其实,主要还是看返回值的生命周期,如果是立刻销毁的,则应注意使用~


4.C++11 _array容器用法

 array容器概念 

  1. array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。
  1. array是将元素置于一个固定数组中加以管理的容器。
  2. array可以随机存取元素,支持索引值直接存取, 用[]操作符或at()方法对元素进行操作,也可以使用迭代器访问
  3. 不支持动态的新增删除操作
  4. array可以完全替代C语言中的数组,使操作数组元素更加安全!
  5. #include

 array特点

  1. array 容器的大小是固定的,无法动态的扩展或收缩,这也就意味着,在使用该容器的过程无法增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。
  2. STL 还提供有可动态扩展或收缩存储空间的 vector 容器

 array对象的构造

 array采用模板类实现,array对象的默认构造形式

array arrayT;    //T为存储的类型, 为数值型模板参数

//构造函数

#includearray 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 迭代器访问 

#includeusing namespace std;array.begin();         返回容器中第一个数据的迭代器。array.end();          返回容器中最后一个数据之后的迭代器。array.rbegin();        返回容器中倒数第一个元素的迭代器。array.rend();         返回容器中倒数最后一个元素的后面的迭代器。array.cbegin();        返回容器中第一个数据的常量迭代器。array.cend();          返回容器中最后一个数据之后的常量迭代器。array.crbegin();        返回容器中倒数第一个元素的常量迭代器。array.crend();         返回容器中倒数最后一个元素的后面的常量迭代器。
#include#includeusing 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()。略。

5.C++类型转换简介

旧式转型  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

 


5.1.static_cas转换

静态类型转换斯文的劝导,温柔的转换)(编译器会检查转换是否合理)。如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可以使编译器做一些合法性检查,不写也没关系。

 


5.2.reinterpreter_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语言的转换

 


5.3.dynamic_cast转换

 动态类型转换

  • 将一个基类对象指针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;}

运行结果

 


5.4.const_cast 转换

去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

相关文章

  • 近几个月Github上最热门的Java项目一览

    摘要:今天逛了逛,顺手精选出了一下近几个月以来上最热门的个项目。相关阅读正式开源,帮助应用快速容器化未来可能会上热门的项目地址介绍哈哈,皮一下很开心。这是我自己开源的一份文档,目前仍在完善中,欢迎各位英雄好汉一起完善。 showImg(https://segmentfault.com/img/remote/1460000015766827?w=391&h=220);今天逛了逛Github,顺...

    cyqian 评论0 收藏0
  • Apache Flink 1.9 重大特性提前解读

    showImg(https://segmentfault.com/img/remote/1460000019961426); 今天在 Apache Flink meetup ·北京站进行 Flink 1.9 重大新特性进行了讲解,两位讲师分别是 戴资力/杨克特,zhisheng 我也从看完了整个 1.9 特性解读的直播,预计 Flink 1.9 版本正式发布时间大概是 7 月底 8 月初左右正式发...

    wall2flower 评论0 收藏0
  • 详解css媒体查询

    摘要:媒体查询,添加自,允许内容的呈现针对一个特定范围的输出设备而进行裁剪,而不必改变内容本身。而且浏览器也不会根据媒体查询来动态的加载样式,它只是一股脑的将所有的样式引入。 简介 媒体查询(Media Queries)早在在css2时代就存在,经过css3的洗礼后变得更加强大bootstrap的响应式特性就是从此而来的. 简单的来讲媒体查询是一种用于修饰css何时起作用的语法. Media...

    leone 评论0 收藏0
  • 详解css媒体查询

    摘要:媒体查询,添加自,允许内容的呈现针对一个特定范围的输出设备而进行裁剪,而不必改变内容本身。而且浏览器也不会根据媒体查询来动态的加载样式,它只是一股脑的将所有的样式引入。 简介 媒体查询(Media Queries)早在在css2时代就存在,经过css3的洗礼后变得更加强大bootstrap的响应式特性就是从此而来的. 简单的来讲媒体查询是一种用于修饰css何时起作用的语法. Media...

    ymyang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<