第四点 :
int a = 10 ; int & b = a;
int & d= b ;
return 0 ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
1.4 引用权限(常引用)
对于权限可以缩小,但是不能放大
void TestConstRef ( )
{
int a= 0 ;
int & b= a;
const int & c= a;
const int x= 10 ;
int & y= x;
const int & y= x;
int & n= a+ x = 临时对象
const int & n = a+ x = 临时对象;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
1.5 引用使用场景
1.5.1 引用作为输出型参数(减少拷贝,提高效率)
说明 :如果参数是指针类型,使用时需要对其解引用操作,但是使用引用可以避免解引用操作
1.5.2 引用作为返回值(减少拷贝,提高效率)
int & func ( )
{
int a = 0 ;
return a;
}
int main ( )
{
int ret = func ( ) ;
int & ret = func ( ) ;
cout << ret << endl;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
说明 :
第一种情况,当函数运行结束,函数内存被系统收回 。引用作为返回值类型,返回临时局部变量a被引用接收。那么对象ret
接收到这块空间是什么?这里需要根据编译器是否对这块函数栈帧清空,但是操作操作是不具备安全性 第二种情况,使用引用接收这块已经被回收的空间 。会导致野引用,访问未知名空间可以看作租房子,房子合同到期,如果非法进入会出现不安全的影响。
【有趣的现象 】:
关于编译器是否对该函数栈帧清空,可以看一个有意思的东西
说明 :对于两次ret
结果值为0,表示编译器没有对函数栈帧清空;如果当调用func()
函数后,ret
值为随机值表示函数栈帧已经清空。间接说明引用做返回值尽量不要返回局部变量
引用做返回值使用场景:
void SLModity ( Seqlist& s1, int pos, int x)
{
assert ( pos >= 0 ) ;
assert ( pos < s1. size) ;
s1. a[ pos] = x;
}
int SLGet ( Seqlist& s1, int pos)
{
assert ( pos >= 0 ) ;
assert ( pos < s1. size) ;
return s1. a[ pos] ;
}
int & Get ( int pos)
{
assert ( pos >= 0 ) ;
assert ( pos < s1. size) ;
return a[ pos] ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
1.6 引用做参数与返回值效率
说明:由于一般在实参传递或返回值传递过程需要创建临时对象,而对于引用与被引用对象共用同块空间,减少了拷贝,提高效率 。通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。
1.7 引用和指针的区别
在语法概念上,引用是一个别名,没有独立空间,同其引用实体共用同一块空间,但是在底层实现上,实际引用是有开辟空间的,由于引用是按照指针方式实现的 。
int main ( )
{
int a= 10 ;
int & ra= a;
cout<< "&a" << & a<< endl;
cout<< "&ra" << & ra<< endl;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
1.7.1 引用和指针的汇编代码对比
引用相较于指针来说,更加适合使用比较复杂的场景进行一些替换,使得代码更简单易懂(不代表引用完全替代指针),比如:引用定义后不能改变指向,而指针可以改变指向。
1.7.2 引用与指针不同点
引用概念上定义一个变量的别名,指针存储一个变量地址
引用在定义时必须初始化,指针没有要求
引用在初始化引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
没有NULL引用,但是有NULL指针
在sizeof
中含义不同:引用结果为引用类型的大小,但是指针始终是地址空间所占字节个数(32位平台下占4个字节)
引用自加既引用的实体增加1,指针自加即指针后偏移一个类型的大小
有多级指针,但是没有多级引用
访问实体方式不同,指针需要显示解引用,引用编译器自己处理
引用比指针使用起来相对更安全
二、内敛函数
2.1 内敛函数概念
内敛函数是以关键字inline
修饰的函数,编译时C++编译器会在调用内敛函数位置展开函数体(函数体替换函数的调用) 。函数调用建立栈帧会有开销,内敛函数虽然可以提升程序运行的效率,但是内敛函数也是需要付出代价。
【查看方式 】:
内敛函数本身是一种代码优化 手段
在release模式下,查看编译器生成的汇编代码中是否存在call Add 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2013
的设置方式
2.2 内敛函数特性
第一点 :
inline
是一种以空间换时间的做法,如果编译器将函数当成内敛函数处理,在编译阶段,会用函数体代替函数调用.
说明 :内敛函数展开,编译程序空间变大(指令变多),空间是编译好的可执行程序,并不是我们理解的内存 。虽然避免了函数调用开销提高程序运行效率,但是可能会使目标文件变大
【给出场景 】:当1000行需要调用swap函数,函数体假设10行代码
如果swap不是内敛函数,合计需要10 + 1000 如果swap是内敛函数,合计需要10 * 1000,导致目标文件变大
第二点 :
inline
对于编译器而言只是一个建议,不同编译器关于inline
实现机制可能不同一般建议 :将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline
修饰,否则编译器会忽略inline
特性
第三点 :
inline
不建议声明和定义分离,分离会导致链接错误 。因为inline
被展开,就没有函数地址了,链接就会找不到
2.3 关于头或源文件同时定义函数措施
假设这里所指函数为Add函数。如果想要头文件中定义Add函数会导致预处理阶段出现命名冲突。
【使得头文件定义Add函数两种方法 】:
第一种 :
使用static
关键字将函数的外部链接属性转为内部链接属性 ,只在当前文件可见,简单来说就是不进符号表,不会产生没有命名冲突。
第二种 : 使用内敛函数,假设在.cpp
文件定义内敛函数,由于内敛函数没有函数地址,意味着不会进去符号表 。对于其他文件中需要该函数,会发生链接错误。推荐内敛函数的声明和定义放在一块,在头文件展开并且链接时不会出现命名冲突
【推荐 】:
如果是大函数,可以使用声明和定义分离,static
修饰; 如果是小函数,可以使用内敛。
三、auto关键字(C++11)
3.1 类型别名思考
由于程序中使用到的类型也越来越复杂,导致了类型难于拼写,含义不明确导致容易出错。
比如 :std::map::iterator
类型但是该类型太长了,特别容易写错。这里可以考虑使用typedef为类型取别名typedef std::map::iterator Map
。但是typedef本身存在不足。
3.2 typedef陷阱
typedef char * pstring;
int main ( )
{
const pstring p1
const pstring* p2
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
过程解析 :这里const pstring != const char *
。由于const pstring
中const
赋予了整个指针本身常性,形成了常量指针char * const
。对于p2来说,const pstring* p2
转化后char* const p2
,对于p2二级指针可以不初始化。
虽然 typedef 并不真正影响对象的存储特性,但在语法上它还是一个存储类的关键字,就像 auto、extern、static 和 register 等关键字一样。
typedef static int INT_STATIC;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
由于typedef已经占据存储类关键字位置,因此typedef声明中就不能再使用static或任何其他存储类关键字,导致不能声明多个存储类关键字
3.3 auto简历
在早期C/C++中auto的含义是使用auto修饰的变量,该变量具有自动存储器的局部变量 ,但是没有人使用它,因为意义不大,这里指向的是局部变量,那么当函数结束,局部变量出了作用域,生命周期结束,变量会自动销毁,对此使用没有意义。
C++11中,auto被赋予了新的含义auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译中推导而得
int TestAuto ( )
{
return 10 ;
}
int main ( )
{
int a = 10 ;
auto b = a;
auto c = 'a' ;
auto d = TestAuto ( ) ;
cout << typeid ( b) . name ( ) << endl;
cout << typeid ( c) . name ( ) << endl;
cout << typeid ( d) . name ( ) << endl;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
打印变量类型:typeid(对象名).name()
3.4 auto使用(自动识别类型)
1. auto定义变量
使用auto定义变量时必须对其进行初始化 ,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型
2.auto与指针和引用结合起来使用
auto与指针和引用结合起来使用。当auto声明指针类型时,用auto和auto*没有任何区别(*如果使用auto ,则表示指向的变量是指针类型 )但用auto声明引用类型时,则必须加&
int main ( )
{
int x = 10 ;
auto a = & x;
auto * b = & x;
auto & c = x;
cout << typeid ( a) . name ( ) << endl;
cout << typeid ( b) . name ( ) << endl;
cout << typeid ( c) . name ( ) << endl;
* a = 20 ;
* b = 30 ;
c = 40 ;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3.auto在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际上只会对第一个类型进行推导,然后用推导出来的类型定义其他变量
void TestAuto ( )
{
auto a = 1 , b = 2 ;
auto c = 3 , d = 4.0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3.5 auto不能推导场景
1.auto不能作为函数参数
void TestAuto ( auto a)
{ }
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
auto虽然不能作为函数参数,但是可以做函数返回值 。对于auto作为函数返回值的建议是慎用,可能使得可读性下降。
如果多层嵌套函数返回值类型是auto,想要得到具体的返回值的类型,需要一个个函数去检查。
auto TestAuto ( int a)
{
return a;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.auto不能直接用来声明数组
void TestAuto ( )
{
int a[ ] = { 1 , 2 , 3 } ;
auto b[ ] = { 4 ,5 ,6 } ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
三、基于范围的for循环(C++)
3.1 范围for语法
对于一个有范围的集合而言,交给程序员来说明循环范围是多余的,可能还会导致犯错误。对此C++11中引入了基于范围for循环。
for循环后的括号由冒号:
分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
void TestFor ( )
{
int array[ ] = { 1 , 2 , 3 , 4 , 5 } ;
for ( auto & e : array)
e *= 2 ;
for ( auto e : array)
cout << e << " " ;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
【注意 】:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环
3.2 范围for的使用条件
1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意 :以下代码就有问题,因为for的范围不确定
void TestFor ( int array[ ] )
{
for ( auto & e : array)
cout<< e << endl;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2. 迭代的对象需要要实现++和==的操作(本质还是迭代器)
四、指针空值
在C/C+良好的编程习惯中,对于未初始化的指针,一个没有合法的指向的指针,基本会进行初始化。int *p=NULL\0
。对此在C头文件
中,可知NULL实际是一个宏。
# ifndef NULL
# ifdef __cplusplus
# define NULL 0
# else
# define NULL ( ( void * ) 0 )
# endif
# endif
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对此NULL可能定义有两种(字面常量0,无类型指针(void )的常量) *,C++中使用NULL指针空值,可能会遇到一些问题。
void f ( int )
{
cout << "haha" << endl;
}
void f ( int * )
{
cout << "hehe" << endl;
}
int main ( )
{
f ( 0 ) ;
f ( ( int * ) NULL ) ;
f ( NULL ) ;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
按照预期,传NULL应该调用int *的函数,但是NULL被定义成0.对此预期和结果不匹配。对此为了区分C++和C,C++引出了nullptr
关键字代替NULL的使用。
在C++98中,字面常量0既可以是一个整型数字,也可以是无类型的指针(void *)常量,但是编译器默认情况下将其看成是一个整型常量 ,如果要将其按照指针方式来使用,必须强制类型转换。
小结 :
nullptr
是C++11作为新关键字引入的,在使用过程中,不需要包含头文件
在C++11中,sizeof(nullptr)
与sizeof((void *)0)
所占的字节数相同
为了提高代码的健壮性,在后续C++使用中最好使用nullptr
表示指针空值
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/2302_79177254/article/details/140729961","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: