引用
介绍
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
注意:引用类型必须和引用实体是同种类型的
使用
引用入门使用
int main() { int a = 10; int& ra = a;//定义引用类型 a *= 6; return 0; }
调试显示
引用变量也可以作为被引用的对象
int main() { int a = 8; int& b = a; int& c = b; return 0; }
调试显示
引用还可以代替指针的传址调用
函数需要指针传地址,现在可以换成引用,形参是实参的别名
以下是引用和指针关于swap函数的使用
指针引用
int main() { int a = 0; int* p = &a; int*& p1 = p; return 0; }
调试显示
引用的特性
1、 引用在定义时必须初始化
2.、一个变量可以有多个引用
3、 引用一旦引用一个实体,再不能引用其他实体
可以看出,现在学的引用很有效果,但不能完全替代指针,像在链表这些地方我们还是选择使用指针,而在一些输出型参数,也就是形参的改变要影响实参的地方我们可以选择使用引用
常引用
如果被引用的对象被constant修饰,而引用变量没有被const修饰会报错
(看作小权力不能转为大权力)
int main() { const int a = 10; const int& b = a; int& c = a; //err:“初始化”: 无法从“const int”转换为“int &” return 0; }
引用变量如果没有被const修饰,不能将常量作为引用对象
int main() { const int& a = 10; int& b = 10; //err:“初始化”: 无法从“int”转换为“int &” return 0; }
(看作大权力能转为小权力)且引用变量和被引用对象要同一类型
int main() { double a = 13.14; const double& b = a; int& c = a; //err:“初始化”: 无法从“double”转换为“int &” return 0; }
传值,传引用效率比较
简述传值:
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,
效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
引用和指针的不同点:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。引入inline修饰符解决一些频繁调用的小函数大量消耗栈内存的问题
如果在下面Add函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2013的设置方式)
//没有inline修饰 int Add(int x, int y) { return x + y; } int main() { int a = 3, b = 6; Add(a, b); return 0; } //有inline修饰 int Add(int x, int y) { return x + y; } int main() { int a = 3, b = 6; Add(a, b); return 0; }
可以看到,已经没有call指令了。
inline是一种以空间换时间的做法,这里的空间指编译出来的可执行文件的大小,内联函数会直接在程序中展开,越长的函数调用越多次就会使文件大小暴增。
inline只是向编译器发出的一个请求,编译器可以选择忽略该请求。
建议将函数规模较小、非递归且调用次数较多的用inline修饰
总结:
1.inline实际上没有call,展开了减少空间的消耗;反汇编中有call就是没展开
2.内联函数不建议声明和定义分离
auto关键字(C++11)
介绍
auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
类型别名思考
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1、类型难于拼写
2、含义不明确导致容易出错
除了typedef还有一种类型是auto的作用是自动推导类型
auto的作用:
auto不是一种类型的声明,而是一个占位符,编译器在编译时会将auto替换为变量实际的类型。
auto的使用
#include<iostream> using namespace std; int main() { auto a = 8; auto b = 9.9; auto c = 'd'; auto d = 10.8f; cout << typeid(a).name() << endl; cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; cout << typeid(d).name() << endl; return 0; }
使用typedef确实可以简化代码,但是(请看下面代码)
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的
类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义。
使用auto的注意事项
使用auto定义变量时必须对其进行初始化
在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型。
因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto与指针和引用结合起来使用
①用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
②auto后面加上*限定赋值的对象必须是指针
#include<iostream> using namespace std; int main() { auto a = 10; auto b = &a; auto* c = &a; auto* d = a; //err:无法推导“auto *”的类型(依据“int”) //err:“初始化”: 无法从“int”转换为“int *” return 0; }
在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,
因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能推导的场景
1、不能在函数的形参里使用
2、不能使用数组定义
auto声明引用类型是要加上&
4、为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
5、auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
范围for(C++11)
介绍
对于一个有范围的集合而言,由程序员来说明(循环的范围是多余的,有时候还会容易犯错误。)
因此C++11中引入了基于范围的for循环。
作用:可以简洁的遍历数组,也方便我们使用
for循环后的括号由冒号“ :”分为两部分:
第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
范围for在数组中实现了自动取数组里的数赋给另一个值;自动++;
使用
int main() { int arr[] = { 1,2,3,4,5 }; ///*原始遍历数组*/ //int sz = sizeof(arr) / sizeof(arr[0]); //for (int i = 0; i < sz; i++) //{ // cout << arr[i] << " "; //} //cout << endl; /*范围for遍历数组*/ for (auto i : arr) { cout << i << " "; } cout << endl; return 0; }
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
我想在原数组的每个数据都乘以2,该怎么执行呢?看看我下面的做法对吗?
#include<iostream> using namespace std; int main() { int arr[] = { 1,2,3,4,5 }; for (auto i : arr) { i *= 2; } /*auto遍历数组*/ for (auto i : arr) { cout << i << " "; } cout << endl; return 0; }
这样子写有问题吗?
我们观察发现,值没有变,我们相对范围内的元素修改,需要用到引用&
#include<iostream> using namespace std; int main() { int arr[] = { 1,2,3,4,5 }; for (auto& i : arr) { i *= 2; } /*auto遍历数组*/ for (auto i : arr) { cout << i << " "; } cout << endl; return 0; }
范围for的使用条件
范围for迭代的范围必须是确定的
对于数组:数组首元素到最后一个元素
对于后面所学的类,使用begin和end的方法,(begin和end是for循环迭代范围)
指针空值nullptr(C++11)
介绍
在C语言中,NULL是一个宏定义,表示一个空指针常量。
它通常被定义为一个整数常量0或者(void *)0,用于表示空指针或者无效的指针。
实际上, NULL是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
简述NULL应用场景:
变量初始化、条件判断、动态内存分配
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); return 0; }
可以看到,传递的参数是NULL,看到调用int类型的f,而不是int*的f,与我们的目的/原理相违背
注意:
1、在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2、在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3、为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。