6.引用
6.6 引用和指针的区别
来看下面代码,右击鼠标转到反汇编:
可以看到汇编代码,大家都是一样的,解析下面的汇编代码:
dword 双字 就是四个字节 ptr pointer缩写 即指针 []里的数据是一个地址值,这个地址指向一个双字型数据 比如mov eax, dword ptr [a] 把内存地址a中的双字型(32位)数据赋给eax
lea表示"load effective address",表示对某个变量的地址进行加载,相当于取地址的操作。所以lea eax, [a]就是将a的地址赋给eax寄存器。
接着分别进行以下操作:
一样的转到反汇编:
引用和指针的不同点:(建议别背,从使用的角度区分)
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。 2. 引用在定义时必须初始化,指针没有要求 3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体 4. 没有NULL引用,但有NULL指针 5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节) 6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小 7. 有多级指针,但是没有多级引用 8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理 9. 引用比指针使用起来相对更安全
7.内联函数
复习一下:实现一个ADD的宏函数
#define N 10
以下均为错误案例:
#define ADD(int x,int y) {return x+y;} #define ADD( x, y) {return x+y;} #define ADD( x, y) return x+y; #define ADD(x,y) x+y; #define ADD(x,y) x+y; #define ADD(x,y) (x+y);//特别注意一下这一条为什么错误。
解析上面需要注意那条 :
从以上可以简单看出宏函数的优缺点:
缺点:
1、容易出错,语法坑很多 2、不能调试 3、没有类型安全的检查
优点:
1、没有的类型的严格限制 2、针对频繁调用小函数,不需要再建立栈帧,提高了效率
引出我们的内联函数,也是不需要建立栈帧.
7.1概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
如果在上述函数前增加inline关键字将其改成内联函数, 在编译期间编译器会用函数体替换函数的调用。
查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在 call Add 2. 在debug模式下,需要对编译器进行设置,否则不会展开 ( 因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式 )
在debug设置下默认是不会展开的:
在使用内联函数之前,需要进行以下设置:
展开是什么意思呢,就是不用调用函数,直接把函数里的功能直接拷贝放到main函数内实现:
而且解决了符号优先级的问题,因为函数传参,是把表达式算出结果再传进去的。
7.2 特性
inline是一种 以空间换时间的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率
inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将 函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、 不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为
《C++prime》第五版关于inline的建议:
编译器视为内联函数
因代码膨胀,编译器不视为内联函数
add是内联函数,func被编译器视为不是内联函数。
把func函数类冗余的代码删除:
这样就被视作为内联了。
测试代码:
#include<iostream> #include<vector> #include<string.h> #define ADD(x,y) ((x)+(y)) inline int add(int x, int y) { return x + y; } inline int func() { int x1 = 0; int x2 = 0; int x3 = 0; int x4 = 0; int ret = 0; ret += x1; //以下代码全注释掉 /*ret -= x2; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3; ret -= x3;*/ return 0; } int main() { ADD(1, 2); printf("%d\n",ADD(1,2)); printf("%d\n",ADD(1,2)*3); int a = 1, b = 2; //ADD(a | b, a & b);//(a | b + a&b) //int ret = add(1, 2); int ret = add(a | b, a & b); printf("%d\n",ret); ret = func(); }
声明和定义分离
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到
测试代码:
Func.h
#pragma once #include<iostream> using namespace std; inline void f(int i); void fx();
Func.cpp
#include"Func.h" void f(int i) { cout << "f(int i)" << i << endl; } void fx() { //既有声明,又有定义,是直接展开 f(1); }
main.cpp
#include "F.h" int main() { f(1); fx(); return 0; }
以上代码执行:
无法解析的外部符号"void_cdecl f(int)"(?f@@YAXH@Z),函数 main 中用了该符号
说明:
在一个源文件形成可执行程序的阶段,预编译阶段的作用其中之一就是头文件的包含,实际上是展开:
所以说预编译之后,Func.cpp的代码其实是这个样子的:
#include <iostream> using namespace std; inline void f(int i); void f(int i) { cout << i << endl; } void fx() { f(1); }
编译器一看,f()函数是一个内联(inline),在调用的地方直接展开了,也就不会建立函数栈帧(不会有那一堆的汇编指令),所以就没有函数地址了,也就不会进入符号表。
即Func.cpp的符号表中,f()函数的地址是无效的。
在链接阶段,在这个main.cpp内调用f()函数,就去查这个符号表,发现地址是无效的,就会报链接型错误。
(编译链接知识点欠缺的,传送🚪:编译链接基础知识(上))
图解:
改正:
如何避免这个链接型错误?
那就让内联函数声明和定义不分离:这样的话在预编译阶段,就把Func.h内的头文件展开了,这样的话呢,在编译阶段就展开了,那么在main.cpp的符号表里面就有了f()函数的地址,在链接阶段通过main函数里面的f()函数调用,一查符号表就找到了f()函数。
Func.h
#pragma once #include<iostream> using namespace std; inline void f(int i) { cout << "f(int i)" << i << endl; } void fx();
Func.cpp
#include"Func.h" void fx() { //既有声明,又有定义,直接展开 f(1); }
main.cpp
#include"Func.h" #include<stdio.h> int main() { //只有声明 cout << "内联函数:" << endl; f(1); cout << "调用内联函数的函数:" << endl; fx(); }
以上代码执行:
C++有哪些技术替代宏
1. 常量定义 换用const enum 2. 短小函数定义 换用内联函数
【C++初阶】第一站:C++入门基础(下)-2