一.引用
定义:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。是C++基于C语言指针的概念新增加的概念,底层实现是依靠指针。
接下来通过代码来见识下引用
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. int a = 2; 6. int& b = a;//b就是是a的引用 7. return 0; 8. }
从引用的定义来看,引用是变量的一个别名,和它所引用的变量共用一块内存空间。
因此,当b++时,a的也将发生改变。
1. b++; 2. cout << a << endl;
a的值为以下运行结果
我们可以总结:变量的引用可以直接访问这个变量在内存中的空间。进而通过引用直接改变这个变量的值。而在C语言中,要想改变变量的值,就是使用指针,变量的指针就是变量的地址,可以通过这个变量的地址解引用改变这个变量。
1. #include<stdio.h> 2. int main() 3. { 4. int a = 2; 5. int* b = &a;//指针存储a的地址 6. (*b)++; 7. printf("%d", a); 8. return 0; 9. }
由此可知,引用和一级指针都可以改变变量的值。
那我们就可以举一反三的知道:一级指针的引用和二级指针都可以改变一级指针。
C++引用特性
1. 引用在定义时必须初始化
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. int& a;//a未初始化,不合法 6. return 0; 7. }
2. 一个变量可以有多个引用,引用的引用指向的还是同一内存空间。
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. int a = 2; 6. int& b = a;//a的三个引用b、c、d 7. int& c = a; 8. int& d = b;//引用的引用指向的还是同一内存空间 9. cout << &a << endl; 10. cout << &b << endl; 11. cout << &c << endl; 12. cout << &d << endl; 13. return 0; 14. }
3. 引用一旦引用一个实体,再不能引用其他实体
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. int a = 2; 6. int x = 3; 7. int& b = a;//a的引用 8. b = x;//a的引用无法修改,这句代码含义是赋值:a=3 9. return 0; 10. }
引用的使用场景
一.传参
当我们要写两个整型的交换函数时,只能将形参写成一级指针的形式,通过指针来交换两变量的值,当有了引用后,也可以将引用作为形参,通过引用交换两个变量。这里体现了一级指针和引用的相同作用。只不过引用的使用更加简单明了。
1. void Swap(int& left, int& right) 2. { 3. int temp = left; 4. left = right; 5. right = temp; 6. }
在数据结构阶段,单链表的头插函数是这样写的
1. void PushHead(struct ListNode** pphead,int n) 2. { 3. //创建新节点 4. struct ListNode* newnode=(struct ListNode*)malloc(sizeof(struct ListNode)); 5. newnode->date=n; 6. //链接新节点 7. newnode->next=*pphead; 8. *pphead=newnode; 9. }
在这里,要想改变链表的头节点指针,就需要用到二级指针。用引用同样可以做到
1. void PushHead(struct ListNode*& phead,int n) 2. { 3. //创建新节点 4. struct ListNode* newnode=(struct ListNode*)malloc(sizeof(struct ListNode)); 5. newnode->date=n; 6. //链接新节点 7. newnode->next=phead; 8. phead=newnode; 9. }
体现了二级指针和一级指针的引用相同的作用。
二.做返回值
引用做返回值,可以通过函数返回函数内对象的引用来在函数外修改这个对象。
1. #include<iostream> 2. #include<assert.h> 3. using namespace std; 4. struct sequence 5. { 6. int* a; 7. int size; 8. int capcity; 9. }; 10. int& SearchModify(sequence* seq,int pos)//查找pos下标位置的数据并修改 11. { 12. assert(pos < seq->size); 13. return seq->a[pos]; 14. }
可以查找
1. struct sequence s; 2. int x=SearchModify(&s, 2);//查找下标为2位置的值
可以修改
SearchModify(&s, 2) = 3;//修改下标为2位置的值为3
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
1. #include<iostream> 2. using namespace std; 3. int& func() 4. { 5. int n = 2;//n是局部变量,出函数将销毁 6. n++; 7. return n;//但是返回了n的引用,让函数外可以找到已经销毁的空间 8. } 9. int main() 10. { 11. int& a = func();//a这块空间是非法的 12. return 0; 13. }
可以修改代码使正确。
stactic int n=2;//静态变量,出作用域不销毁
二.auto关键字
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto会自动识别类型:
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. auto a = 3;//int 6. auto b = 2.7;//double 7. auto c = 'm';//char 8. auto s = "ssacs";//string 9. auto a = &a;//注意:auto与auto*没有区别 10. auto* b = &a; 11. auto& c = a;//引用必须auto& 12. return 0; 13. }
注意:auto类型不能用来做形参,不能用来声明数组。
三.内联函数
概念 :以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。
特性:
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽视inline特性。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。建议将inline声明定义都放到.h文件中。
1. // F.h 2. #include <iostream> 3. using namespace std; 4. inline void f(int i); 5. // F.cpp 6. #include "F.h" 7. void f(int i) 8. { 9. cout << i << endl; 10. } 11. // main.cpp 12. #include "F.h" 13. int main() 14. { 15. f(10); 16. return 0; 17. } 18. // 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl 19. //f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用
四.范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. int arr [] = {1,2,3,4,5}; 6. //遍历数组 7. for (int n : arr) 8. { 9. cout << n << " "; 10. } 11. return 0; 12. }
由于范围for的第一个参数是用于迭代的变量,要想改变数组内的值就不能直接改变这个变量的值。例如
1. #include<iostream> 2. using namespace std; 3. int main() 4. { 5. int arr [] = {1,2,3,4,5}; 6. //遍历数组 7. for (int n : arr)//打印数组 8. { 9. cout << n << " "; 10. } 11. cout << endl; 12. for (int n : arr)//数组元素全部*2 13. { 14. n*=2; 15. } 16. 17. for (int n : arr)//打印数组 18. { 19. cout << n << " "; 20. } 21. return 0; 22. }
正确的方法是:使用引用做用于迭代的变量,因为数组的值会依次赋值给引用,就顺便得到了数组全部元素的引用。进而用每个元素的引用改变数组元素。
1. for (int& n : arr)//数组元素全部*2 2. { 3. n*=2; 4. }