[本节内容]
6.引用
7.内联函数
8.auto关键字(C++11)
9.基于范围的for循环(C++11)
10.指针空值—nullptr(C++11)
6.引用 (简单来说就是给变量“取别名”)
6.1 概念:
引用不是新定义一个变量,而是给已存在的变量取个别名。编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。例如:
int a=10;
int & ra=a; //此时ra是a的别名,即a与ra的地址相同
int ra=a; //仅把a的值赋给ra,二者地址不同
1)类型& 引用变量名(对象名)=引用实体;
2)引用类型必须和引用实体是同种类型的
6.2 引用特性
*引用在定义是必须初始化。 例如:int& ra;是错误的
*一个变量可以有多个引用。 例如:int& ra =a;int& rr=a;int& rra=a;
*引用一旦用于一个实体,再不能引用其他实体。 例如:
int& ra=a;int& ra=b;是错误的
6.3 常引用
常引用的使用 例如:
void Test()
{
const int a = 10;
const int &ra = a; //int &ra=a错误,因为a为常量
const int &rb = 10; //int &b=10错误,因为b为常量
double d = 12.34;
const int &rd = d; //int &rd=d错误,因为类型不同
}
6.4 使用场景
*做参数:不用传地址,&是别名,相当于拿原数进行操作
void Swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
*做返回值:返回值的生命周期必须不受函数的限制。即若返回值是当前函数的局部变量,就不能用引用进行返回。
int& Test(int &a)
{
a += 10;
return a;
}
// 注:引用做返回值无需产生新变量
6.5 传值,传引用效率比较
*传值的效率不如传引用,而引用和指针在作为传参及返回值类型上效率几乎相同。
8.引用和指针的比较
1)引用和指针在作为传参以及返回值类型上效率几乎一样。
2)在语法概念上,引用只是一个别名,没有独立的空间,和其引用实体共用一块空间。
3)而在底层实现上,引用是按照指针方式来实现的,即是会分配空间的(通过指针和引用的汇编代码对比可得出结论)。
注意:
1)引用在定义是必须初始化,指针无要求
2)一个引用只能用于一个实体,指针可以指向任意变量
3)没有NULL引用但是有NULL指针;有多级指针但没有多级引用
4)在sizeof中含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数
5)引用自加即引用的实体增加一,指针自加是指针向后偏移一个类型的大小
6)访问实体方式不同,指针需要显式解引用,引用则由编译器自己处理
7)引用比指针使用起来相对更安全
7.内联函数inline(与C语言中的宏函数作用相同)
7.1 概念:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
7.2 特性
1)inline是一种以时间换空间的做法,省去调用函数额外开销,所以代码很长或者有循环和递归时不宜使用。
2)inline对于编译器只是一个建议,编译器会自动优化,若定义为inline函数体内有循环或递归,编译器会忽略掉内联。
3)inline不建议声明和定义分开,分离会导致链接错误。因为内联函数被展开时就没有了函数地址,链接也就找不到了。
7.3 宏
优点:增强代码的复用性;提高性能。
缺点:不方便调试;代码可读性差,可维护性差,容易误用;没有对类型安全性的检查;
C++中可以替代宏的技术有:常量可用comst定义,函数可用inline
8.auto关键字(C++11)
auto的作用:简化代码
1. auto不是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量类型必须由编译器在编译时期推导而得。
2.使用auto定义变量时必须对其进行初始化,再由初始化表达式推导auto的类型。
3.auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
4.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
5.auto 不能推导的场景主要有:
不能作为函数参数;
不能直接用来声明数组;
只能作为类型只是符;
可搭配新式for循环和lambda表达式使用;
不能定义非静态成员变量;
不能用auto做实例化模板的参数
9.基于范围的for循环(C++11)
9.1 范围for的语法
在c++98中,如果要遍历一个数组,可以按照如下方式进行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
array[i] *= 2;
for (int *p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
cout << *p << endl;
}
对于一个有范围的集合来说,由程序员来说明循环的范围是很多余的,有时候反而会出错。因此在c++11中引入了基于范围的for循环:(由 :分为下面两部分组成)
<一 > 范围内用于迭代的变量
<二 > 被迭代的范围
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " ";
}
注意: 与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
9.2范围for的使用条件
1.for循环迭代的范围必须是明确的
对于数组而言,就是数组的第一个元素到最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意: 以下代码就有问题,因为for的范围不确定
void TestFor(int array[]) //此时的array是指针,无法用于范围
{
for (auto&e : array)
cout << e << endl;
}
2.迭代的对象要实现++和==的操作
10.指针空值 nullptr(C++11)
10.1 C++98中的指针空值
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误。如果一个指针没有合法的指向,我们就会按如下方式对其进行初始化:
void TestPtr()
{
int *p1 = NULL;
int *p2 = 0;
....
}
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码;
#ifndef NULL
#ifndef NULL 0;
#else
#define NULL ((void*)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。但无论怎样定义,在使用空值的指针时,都不可避免的会遇到麻烦。比如:
void f(int)
{
cout << "f(int)" << endl;
}
void f(int *)
{
cout << "f(int *)" << endl;
}
int main()
{
f(0); //调用第一个
f(NULL); //调用第一个
f((int *)NULL); //调用第二个
system("pause");
return 0;
}
程序本意是想通过f(NULL)调用f(int*)函数,但由于NULL被定义成0,因此与程序初衷相悖。在c++98中,0即可以是整形数字也可以是(void*)常量,但编译器默认为整形数字,只有(void*)0才能按指针方式使用。
10.2nullptr与nullptr_t
为了考虑兼容性,c++11并没有消除常量0的二义性,而是给出了全新的nullptr表示空值指针:用nullptr代表一个指针空值常量,类型为nullptr_t。
typedef decltype(nullptr) nullptr_t;
注:
1.在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引用的。
2.在C++11中,sizeof(nullptr)与sizeof(void*)所占字节数相同。
3.为了提高代码的健壮性,指针空值最好用nullptr。