【C++初阶】第一站:C++入门基础(下)-1

简介: 【C++初阶】第一站:C++入门基础(下)-1

6.引用

6.6 引用和指针的区别

来看下面代码,右击鼠标转到反汇编:




60e2b38f1c5b43ba8c29f443995b6f9c.png

可以看到汇编代码,大家都是一样的,解析下面的汇编代码:


dword         双字 就是四个字节 
ptr               pointer缩写 即指针
[]里的数据是一个地址值,这个地址指向一个双字型数据
比如mov eax, dword ptr [a]         把内存地址a中的双字型(32位)数据赋给eax


1359fc54cbec4c0d9554f3c7eb2e67dc.png

       lea表示"load effective address",表示对某个变量的地址进行加载,相当于取地址的操作。所以lea eax, [a]就是将a的地址赋给eax寄存器。


接着分别进行以下操作:


dca7e8a3791f4c2a81598f8390129a29.png

一样的转到反汇编:


3883b471f3774a54a7511b33733e4d47.png


引用和指针的不同点:(建议别背,从使用的角度区分)


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);//特别注意一下这一条为什么错误。

解析上面需要注意那条 :

df7c0ec179214e0b911de3452fbdcbbe.png



从以上可以简单看出宏函数的优缺点:


缺点:


1、容易出错,语法坑很多
2、不能调试
3、没有类型安全的检查


优点:


1、没有的类型的严格限制
2、针对频繁调用小函数,不需要再建立栈帧,提高了效率


引出我们的内联函数,也是不需要建立栈帧.


7.1概念

 

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

9f1e649737e142bca55e6add95302cdf.png


       如果在上述函数前增加inline关键字将其改成内联函数, 在编译期间编译器会用函数体替换函数的调用。

查看方式:

1. 在release模式下,查看编译器生成的汇编代码中是否存在 call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开 ( 因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式 )

在debug设置下默认是不会展开的:


0599dacf1253454a910638fdef827e4d.png


在使用内联函数之前,需要进行以下设置:



e4425c67e9f4478688ab1fdc199fcc02.png

展开是什么意思呢,就是不用调用函数,直接把函数里的功能直接拷贝放到main函数内实现:


       而且解决了符号优先级的问题,因为函数传参,是把表达式算出结果再传进去的。

bad55dbdad8646a0be36f5af4789b983.png



7.2 特性

       inline是一种 以空间换时间的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率      

        inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建

议:将 函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、 不

是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为

《C++prime》第五版关于inline的建议:



b00271840b434dd1b0d924899307a122.png


381d575195d0423b8c51e8b377343c97.png

编译器视为内联函数

f0e43511b4124a3fae09ed404e175e3a.png


因代码膨胀,编译器不视为内联函数


2e816169ab8743538b9f1e82484db25c.png

add是内联函数,func被编译器视为不是内联函数。


adeb1ed7055d472a8190945b38e19429.png


把func函数类冗余的代码删除:


8920233008c14624b60bb8aa6511698a.png


这样就被视作为内联了。


测试代码:


#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 中用了该符号


说明:


       在一个源文件形成可执行程序的阶段,预编译阶段的作用其中之一就是头文件的包含,实际上是展开:


812ae58d47634f00a823f63d5b630ebd.png


所以说预编译之后,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()函数,就去查这个符号表,发现地址是无效的,就会报链接型错误。


(编译链接知识点欠缺的,传送🚪:编译链接基础知识(上))


图解:

249578d052bd42cba699390e0ec57b12.png


改正:


如何避免这个链接型错误?


       那就让内联函数声明和定义不分离:这样的话在预编译阶段,就把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();
}

以上代码执行:



d27b4ceb5a8e4f619d3d549d862dd7cb.png

C++有哪些技术替代宏


1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数



【C++初阶】第一站:C++入门基础(下)-2

https://developer.aliyun.com/article/1456997

相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
26 0
|
1月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
1月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
1月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
30 0
|
1月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
32 0
|
1月前
|
存储 算法 C++
C++入门10——stack与queue的使用
C++入门10——stack与queue的使用
42 0
|
1月前
|
存储 C++ 容器
C++入门9——list的使用
C++入门9——list的使用
19 0