一、引用
1、概念
引用不是定义一个变量,而是给以恶搞已有成员起一个别名,编译器不会为引用变量开辟内存空间,应用变量与被引用变量共用同一块空间。
int main()
{
int a=10;
int &b=a;
cout<<&a<<endl;
cout<<&b<<endl;
return 0;
}
这段代码执行的结果是相同的,说明引用的地址不会改变。
2、引用特性
1、引用在定义时必须初始化
2、一个变量可以有多个引用
3、一个引用只能对应一个变量,不可以作为其他变量的别名
int main()
{
int a=10;
int& b=a;
int& c=a;
int d=0;
//int& c=d;由于c已经是a的引用了,c不可以再作为d的引用
//int& e;引用必须要初始化
return 0;
}
三、常引用
在使用引用的过程中,我们只允许权限的平移和缩小,权限放大就会报错
例如一些写使用const修饰的变量
下面是一些例子
int main()
{
//1、权限的放大
const int a = 10;
//int& ra = a;
//a是只读的,而ra的权限是可读可写的,如果让ra作为a的别名,就会使对a的权限放大
const int& ra = a;//权限的平移
int p = a;//注意:这里并不是权限的放大,只是将m的值拷贝给p,并没有涉及到引用,只是把a赋值给了p,p和a的地址是不一样的
//权限的缩小
int b = 0;
const int& rb = b;//权限的缩小,此时不能修改rb,但是可以通过修改b达到修改rb的目的
//权限的放大(这一条与引用无关)
const int c = 0;
const int* p1 = &c;
//int* p2 = p1;这一句是把p2的值赋给p1,但是由于p2是可读可写的,而p1是只读的,权限放大了,所以会报错。
//权限的缩小(这一条与引用无关)
int d = 10;
int* p3 = &d;
const int* p4 = p3;
//常量的引用
double e = 12.34;
//double& re1 = 12.34;该语句编译时会出错,对于非常量引用的初始化必须是可修改的变量
const double& re2 = 12.34;//如果加入const修饰的话,就可以引用初始化为常量
//当类型不同时,涉及到临时变量的问题
//int& re3 = e;//该语句编译时会出错
//涉及类型转换时,e不是直接传给re3,而是通过一个临时变量
//e先把值传给临时变量,临时变量取出整形部分给re3
//严格来说re3引用的不是e,而是中间产生的临时变量
//!但是临时变量具有常性,就会产生权限的放大,因此会编译报错
//注意:double& re1 = e;不会编译报错,因为两者是同类型的,不会产生中间变量
const int& re4 = e;//这里就不会编译出错
//临时变量具有常性,会产生权限的放大
//因此我们可以在前面加上const变成常引用,就不会产生报错
//其他涉及到临时变量的问题
int x = 0, y = 1;
//int& r = x + y;//该语句编译报错
//与上面类似,除了类型转换会产生临时变量
//如果一个表达式的计算结果没有用变量来接收,这个结果就会放在临时变量中
//如果计算结果非常小,也可能会直接存放在寄存器中
//因此r实际上引用的是存放x+y计算结果的临时变量
//但是临时变量具有常性,就会产生权限的放大,因此会编译报错
const int& r = x + y;//在前面加上const变成常引用,就不会产生报错
int sum = x + y;
int& s = sum;
//通过sum接收表达式的结果,且引用s和sum是同类型的
//这样就不会产生临时变量,因此也不会报错
}
引用和指针的区别
1、引用是一个变量的别名,指针存储一个变量地址
2、引用在定义时必须初始化,指针没有要求
3、初始化时,引用有了初始化对象后,就不可以更改,而指针则可以修改其指向的对象(同类型的)
4、引用不能为空,但是空(NULL)有指针。
5、sizeof(引用)=引用类型的大小,sizeof(指针)=4/8字节
6、引用自加是引用的对象加一,而指针自加是向后偏移一个类型的大小
7、引用没有多级引用,指针有多级指针。但是引用可以有很多个同级引用,,即一个变量可以有多个别名
8、访问对象的方式不同,指针 需要显式解引用,而引用编译器自己会处理
9、引用比指针使用起来相对安全。(因为没有空引用)
内联函数
概念
inline修饰的函数叫做内联函数,编译时c++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。内联函数自在减少函数调用的开销,适用于那些函数体少,调用频繁的函数。
当编译器处理到函数调用时,如果该函数被声明为内联函数,则编译器会尝试将该函数调用替换为函数体内部的代码。当程序运行到函数调用处,会直接执行了函数体内部的代码,就减少了函数调用的消耗。但是并不一定会替换,内联说明只是想编译器发出的一个请求,编译器可以选择忽略这个请求,如果函数体规模过大,就可能会失败。
特性
inline是一种以空间换取时间的做法。在编译阶段,编译器会用函数体替换函数的调用,这样会使生成的可执行程序变大,但是会减少调用开销,提高程序的运行效率。一般来说,可以将函数体小,不是递归,调用频繁的函数使用inline修饰。
inline不可以声明和定义分离,否则会导致链接错误。因为内联函数没有地址,在函数调用处会直接展开为函数体内部的代码,因此无法链接。
auto关键字
在早期c/c++中,auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,而在c++11中,标准委员会赋予了auto新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
可以试一下这段代码感受一下效果。
int Fun()
{
return 0;
}
int main()
{
int a=10;
auto b=a;
auto c='a';
auto d=Fun();
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
cout<<typeid(c).name()<<endl;
cout<<typeid(d).name()<<endl;
}
要注意的是,使用auto定义变量的时候必须进行初始化。
在编译阶段,编译器需要根据初始化的表达式来推到auto的实际类型,因此auto并不是一种类型的声明,而是一个类型声明时的占位符。
用auto声明指针类型的时候,auto与auto*没有区别,用auto声明引用类型的时候必须要加&
int main()
{
int a = 0;
auto pa = &a;
auto* pa1 = &a;
autp& b = a;
return 0;
}
基于范围的for循环
用法
for(declaration : range)
{
//...
}
declaration时范围内用于迭代的变量
range时被迭代的范围
int main()
{
int array[]={1,2,3,4,5,6};
for(auto& e : array)
{
e *= 2;
}
for(auto e : array)
{
cout << e <<" ";
}
}
上面代码中,可以直接用对应的类型声明,也可以用auto。
指针空值nullptr
在c++11以前,指针空值为NULL,但是在传统的c头文件中,NULL实际上是一个宏,被定义为0,因此会出现一些问题。比如我们想要用NULL的类型来作为一些判断条件时,他会被认为时整形,而不是指针类型。
在c++11中,我们改用nullptr表示指针空值,他是作为新关键字引入的,因此不需要包含其他头文件。为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。注意:在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。