C++学习第二篇

[本节内容]

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。

猜你喜欢

转载自blog.csdn.net/ly_6699/article/details/86553105