前言
这期会把上一期C++入门没有讲完的引用,内联函数跟nullptr给讲完,下期将会讲解类和对象
关于 C++的文章通通收录在了这个专栏里面:
引用
概念:引用是给已存在变量取了一个别名,它和它引用的变量共用同一块内存空间。
规则:
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用一个实体,再不能引用其他实体
4.引用过程中,权限不可以扩大,只能平移和缩小(权限指的是eg:以后可以既读又改,现在只能读了)
使用场景:
1.作为函数的形参
1.做输出型参数–想里面改变,对应外面也改变的那种
2.减少拷贝,提高效率–对大对象(参数很多的那种,比如:一万个形参都用引用)或者深拷贝对象有用
2.作为函数的返回值
1.减少拷贝,提高效率–对大对象(参数很多的那种,比如:一万个形参都用引用)或者深拷贝对象有用(传值返回用拷贝,传引用返回不要拷贝)
2.修改返回值/获取返回值
使用举例:
int a = 10;
int& ra = a;
int* x = &a;
int*& p = x;
引用一旦引用一个实体,再不能引用其他实体:举例:
int a = 10;
int& ra = a;
int b = 11;
ra = b;//这句话是改变ra的值,不是让ra成为b的引用
一个变量可以有多个引用,举例:
int a =0;
int& b = a;
int& c= b;
引用过程中,权限不可以扩大,只能平移和缩小,举例:(这个点属于常引用的)
const int a = 0;
int& b = a;//这个是不行的,扩大了
int x = 0;
int& y = x;
const int& z = x;//这里是权限缩小
x++;//这里会间接导致z++,但是z不能直接自己++
const int& m = 10;这样也是允许的
但是int& m =10;就不行
引用作函数返回值:
先举个例:
非引用:
int Count()
{
int n = 0;
n++;
return n;//这个的话是靠创建临时变量(变量占字节小的时候才是用寄存器)去过渡
}
引用:
int& Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int ret = Count();
}
这样的话,分两种情况:
1.如果Count函数结束,栈帧销毁,没有清理栈帧的话,那么ret的结果侥幸是正确的
2.如果Count函数结束,栈帧销毁,清理栈帧,那么ret的结果是随机值
(特别是Count用了之后还进行了其他操作去用栈帧)
总结:引用做返回值时,出了函数作用域,对象不在了,就不能用引用返回,还在就可以用引用返回
eg:
int& Count()
{
static int n = 0;
n++;
return n;//这里返回的相当于是n的别名
}
int& SLAt(SeqList*ps,int pos)
{
return ps->a[pos];
}
易错点:
int& Count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int ret = Count();//这里的ret是n的拷贝,不是引用,要int& ret = Count();才是
return 0;
}
临时变量的一个小知识点
编译器为了执行代码搞的大部分临时变量都具有常性
举例: double dd = 12.21; int& i = dd;//这时就会报错 但是 double dd = 12.21; int i1 = dd; int& i = i1;//这就不会报错 int func() { static int x = 0; return x; } main函数里面int& ret = func();//这样就不行 要const int& ret = func();//这样才行
常见情况:
1.发生类型转换时会产生临时变量来辅助,这时的就具有常性
2.传值引用返回值返回时也会产生临时变量,也有常性
3.隐式转换
(临时变量没有常性的情况以后才会提及)
引用和指针的区别
语法层面:
引用是不开空间,是对a取别名 指针是开空间,存储a的地址
从底层汇编指令实现的角度来看:引用是类似指针的方式实现的–也就是引用也会开空间
平时理解:引用的语法理解当不开空间,要是谈到底层的话,那就是会开空间
引用和指针的不同点:
1.引用概念上定义一个变量的别名,指针存储一个变量地址。
2.引用在定义时必须初始化,指针没有要求
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4.没有NULL引用,但有NULL指针
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7.有多级指针,但是没有多级引用
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理(底层)
9.引用比指针使用起来相对更安全
内联函数
内联函数
概念:以inline修饰的函数叫做内联函数
作用:在编译器编译时,会在调用内联函数的地方把内联函数在那展开(不会像自定义函数一样是通过call搞得),这样可以避免函数调用建立栈帧的开销
适用范围:适用于短小并且频繁调用的函数。
原因:如果不频繁的话,用的效果不明显;函数比较长的话,在编译过程中的指令会非常长,导致可执行程序的占用内存会很大(现在编译器可以主动避免这个了)
inline对于编译器只是一个建议,最终是否成为inline,是由编译器自己决定的
编译器会ban掉的一些函数(不让他成为内联,让他就是普通的函数):
1.比较长的函数
2.大多数递归函数
注意:深度深的递归跟频繁调用的函数是有差别的,不能等同:深度深的递归eg:1000*999一直乘到1这样一下算下来的话会导致文件变得很大,频繁调用函数则不会
默认debug模式下,inline在编译过程中不会起作用,否则就不方便调试了
在release版本下才会起作用,但是release版本下又看不了编译,这个需要自己去设置(不同软件的设置方法不一样)
注意:inline不建议声明和定义分离(要在同一文件下),分离会导致链接错误–原因:inline被展开之后,定义就没有函数地址了,链接就找不到那个函数的地址,就会报错
内联函数展示:
inline f(int a)
{
return a*10;
}//也就在前面加个inline就行了
遗忘的补充:宏函数
优点:不需要建立栈帧,提高调用效率
缺点:复杂,容易出错;可读性差;不能调试(因为预编译阶段进行了替换)
C++中替代宏函数的方法:内联函数
举例:
正确写法:#define f(x,y) ((x)+(y))
易错点:1.后面加了;号和return
2.f(x,y)写成了f(int x, int y)
3.x和y没有加()以及大整体没有加括号--出错原因:万一是f(a|b,a&b)
隐藏知识点:+比&和|的优先级高
指针空值—nullptr
在后续表示指针空值时建议最好使用nullptr。
跟用0或者NULL表示指针空值的区别:
void f(int) { } void f(int*) { } f(0);//会调用第一个--默认情况下0被看作是一个整形常量,不行的话才是(void*)0 f(NULL); //会调用第一个--NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量(具体看编译器是哪种) f(nullptr);//会调用第二个
补充知识:像这种形参只给了类型的,没有给名字的也行,如果函数里面没有使用这个形参的话
作业部分
引用传值,指针传地址(X)
原因:引用表面好像是传值,其本质也是传地址
删除空指针是无害的,但是不能删除引用(对)
原因:空指针没有任何指向,删除无害,引用是别名,删除引用就删除真实对象
关于c++的inline关键字,以下说法正确的是(BC)
A.使用inline关键字的函数会被编译器在调用处展开
B.头文件中可以包含inline函数的声明
C.可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数
D.递归函数也都可以成为inline函数
A:要看编译器有没有把他处理成内联函数
B:如果头文件里有内联函数的声明和定义的话就行