【C Primer Plus第六版 学习笔记】 第十六章 C预处理器和C库

有基础,进阶用,个人查漏补缺

第十五章的内容之前学过,跳过

  1. 预处理之前,编译器必须对该程序进行一些翻译处理

    1. 首先把源代码中出现的字符映射到原字符集

    2. 其次编译器定位每个反斜杠后面跟着换行符的实例,并删除它们(把由于写代码时,一行太长,会用反斜杠\把一行逻辑行变成两个物理行)

    3. 然后编译器把文本划分为预处理记号序列、空白序列和注释序列。此处需要注意的是,编译器将用一个空格字符替换每一条注释,如

      int/* 注释*/fox;
      //将变成
      int fox;//中间的注释变成了一个空格
      
  2. C预处理器在程序执行之前查看程序,故称之为预处理器。根据程序中的预处理器指令,预处理器把符号缩写替换成其表达的内容。

  3. 明示常量:#define

    1. 指令从#开始运行,到第1个换行符结束,针对的是一个逻辑行(可以用\进行物理换行)

    2. 类对象宏定义的组成:宏的名称中不允许有空格,需要遵循C变量的命名规则

      #define PX printf("x is %d\n", x)
      //预处理指令 宏 替换体
      
    3. 预处理器不做计算,不对表达式求值,只进行替换

    4. 记号:可以把宏的替换体看作是记号(token)型字符串

      #define FOUR 2*2    //有一个记号2*2,但对于C编译器来说是3个记号
      #define six 2 * 3   //有三个记号2、*、3,额外的空格也是替换体的一部分
      
    5. 重定义常量:假设先把LIMIT定义为20,稍后在该文件中又把它定义为25。

      //ANSI标准在新定义和旧定义完全相同时才允许重定义
      #define six 2 * 3
      #define six 2 * 3
      
    6. 在#define中使用参数,即类函数宏:

      1. 为保证运算顺序,要多使用圆括号

        #define SQUARE(X) X*X
        #define SQUARE1(X) (X*X)
        #define SQUARE2(X) (X)*(X)
        int x = 5;
        z = SQUARE(x);//z=25
        z = SQUARE(x+2);//z= x+2*x+2 = 5+2*5+2 = 17
        z = 100 / SQUARE(2);//z = 100/2*2 = 100/2*2 = 100z = SQUARE1(2);//z = 100 / (2*2) = 25z = SQUARE2(x+2);//z = (x+2)*(x+2)
      2. 避免使用++x等这种递增或者递减

      3. 用宏参数创建字符串:#运算符

        #define PSQR(X) printf("The square of X is %d.\n", ((X)*(X)))
        PSQR(8);//输出The square of X is 64.**注意双引号中的X被视为普通文本,不是记号**#define PSQR(x) printf("The square of " #x " is %d.\n", ((x)*(x)))
        int y = 5;
        PSQR(y);//输出The square of y is 25
        PSQR(2 + 4);//输出The square of  2 + 4 is 36
        
      4. 预处理黏合剂:##运算符

        #define XNAME(n) x ## n
        int XNAME(1) = 14;//变成int x1 = 14
        
      5. 变宏参:…和__VA_ARGS__

        #define PR(...) prinf(__VA_ARGS__)
        pr("Hoedy");//等于 prinf("Hoedy")#define PR(X, ...) prinf("Message " #X ": " __VA_ARGS__)
        int x = 2;
        PR(1, "x = %d\n", x);//即prinf("Message " "1" ": " "x = %d\n", x)
        //输出Message 1: x = 2
        
  4. 宏和函数的选择

    1. 宏生成内联代码,即在程序中生成语句,调用20次宏就生成20行代码。而调用函数20次,在程序中只有一份函数语句的副本,节省空间

    2. 调用函数时,程序控制必须跳转到函数内,再返回主调程序,比内联代码更费时间

    3. 宏不用担心变量类型

    4. 在嵌套循环中使用宏更有助于提高效率

    5. 对于简单的函数,通常使用宏

      #define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
      #define ABS(X,Y) ((X) < 0 ? -(X) : (X))
      #DEFINE ISSIGN(X) ((X)  == '+' || (X) == '-' ? 1 : 0)
      
  5. 文件包含:#include

    1. 当预处理器发现#include指令时会查看后面的文件名并把文件的内容包含到当前文件中,即替换源文件中的#include指令。这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。

    2. #include指令有两种形式:文件名在尖括号或者双引号中

      //在unix系统中
      #include <stdio.h>              //查找系统目录
      #include "mystuff.h"            //查找当前工作目录
      #include "/usr/biff/mystuff.h"  //查找/usr/biff/目录
      
    3. 头文件实例

      1. names_st.h————names_st结构的头文件

        // 常量
        #include <string.h>
        #define SLEN 32// 结构声明
        struct names_st
        {char first[SLEN];char last[SLEN];
        };// 类型定义
        typedef struct names_st names;// 函数原型
        void get_names(names *);
        void show_names(const names *);
        char * s_gets(char * st, int n);
        
      2. names_st.c————定义names_st.h中的函数

        #include <stdio.h>
        #include "names_st.h"     //包含头文件//函数定义
        void get_names(names * pn)
        {printf("Please enter your first name: ");s_gets(pn->first, SLEN);printf("Please enter your last name: ");s_gets(pn->last, SLEN);}void show_names(const names * pn)
        {printf("%s %s", pn->first, pn->last);
        }char * s_gets(char * st, int n)
        {char * ret_val;char * find;ret_val = fgets(st, n, stdin);if (ret_val){find = strchr(st, '\n');   //查找换行符if (find)                  //如果地址不是NULL,*find = '\0';          //在此处放一个空字符elsewhile (getchar() != '\n')continue;          //处理输入行中的剩余字符}return ret_val;
        }
      3. useheader.c————使用names_st结构

        #include <stdio.h>
        #include "names_st.h"
        //记得链接names_st.cint main(void)
        {names candidate;get_names(&candidate);printf("Let's welcome ");show_names(&candidate);printf(" to this program!\n");return 0;
        }
        
      4. 注意:

        1. 两个源文件.c都使用names_st类型结构,所以它们都必须包含names_st.h头文件
        2. 必须编译和链接names_st.c和useheader.c源代码文件
        3. 声明和指令放在names_st.h头文件中,函数定义放在names_st.c源代码文件中
    4. 使用头文件

      头文件中最常用的形式如下:

      1. 明示常量
      2. 宏函数
      3. 函数声明
      4. 类型定义
      5. 使用头文件声明外部变量供其他文件共享
  6. #undef 指令

    用于取消已定义的 #define 指令

    #define LIMIT 40
    #undef LIMIT      //可以移除上面的定义
    

    现在可以将LIMIT重新定义为一个新值,即使原来没有定义LIMIT,该取消也依旧有效;

    如果想使用一个名称,又不确定是否之前已经用过,为安全起见,可以使用 #undef 取消该名称的定义

  7. 条件编译——#ifdef、#else、#endif

    预处理器不识别用于标记块的花括号{}

    缩进与否看个人风格

    #ifdef MAVIS          //如果已经用#define定义了MAVIS,则执行下面的指令#include "horse.h"#define STABLE 5
    #else                 //如果没有用#define定义了MAVIS,则执行下面的指令#include "cow.h"#define STABLE 5
    #endif                //必须存在
    

    也可以用这些指令标记C语句块

    #include <stdio.h>
    #define JUST_CHECKING
    #define LIMIT 4int main(void)
    {int i;int total = 0;for (i = 1; i <= LIMIT; i++){total += 2*i*i + 1;
    #ifdef JUST_CHECKINGprintf("i=%d, running total = %d\n", i, total);
    #endif}printf("Grand total = %d\n", total);return 0;
    }
    

    输出:

    i=1, running total = 3
    i=2, running total = 12
    i=3, running total = 31
    i=4, running total = 64
    Grand total = 64
    

    如果省略JUST_CHECKING定义(把#define JUST_CHECKING放在注释中,或者使用#undef指令取消它的定义),并重新编译该程序,只会输出最后一行。该方法可用来调试程序。

  8. 条件编译——#ifndef 指令

    #ifndef 和#ifdef 用法类似,也是和#else、#endif一起使用,只是它们的逻辑相反

    有arrays.h

    #ifndef SIZE#define SIZE 100
    #endif
    

    有代码

    #define SIZE 10
    #include "arrays.h" //当执行到该行时,跳过了#define SIZE 100,故SIZE为10
    

    故可以使用#ifndef 技巧避免重复包含

    #ifndef NAMES_H_
    #define NAMES_H_// constants
    #define SLEN 32// structure declarations
    struct names_st
    {char first[SLEN];char last[SLEN];
    };// typedefs
    typedef struct names_st names;// function prototypes
    void get_names(names *);
    void show_names(const names *);
    char * s_gets(char * st, int n);#endif
    

    用以下代码进行测试,是没有问题的。但是如果把上面的.h中的#ifndef 删除,程序会无法通过编译

    #include <stdio.h>
    #include "names.h"
    #include "names.h"   //不小心第2次包含头文件int main()
    {names winner = {"Less", "Ismoor"};printf("The winner is %s %s.\n", winner.first, winner.last);return 0;
    }
    
  9. 条件编译——#if 和 #elif

    类似于if语句。#if 后面跟着整型常量表达式

    #if SYS == 1#include "a.h"
    #elif SYS == 2#include "b.h"
    #elif SYS == 3#include "c.h"
    #else#include "d.h"
    #endif
    

    另一个新的用法测试名称是否已经定义

    #if defined (ABC)#include "a.h"
    #elif defined (DE)#include "b.h"
    #elif defined (FG)#include "c.h"
    #else#include "d.h"
    #endif
    
  10. 预定义宏

    在这里插入图片描述

  11. #line 和 #error

    #1ine 指令重置__LINE__和__FILE__宏报告的行号和文件名。可以这样使用Iine:

    #line 1000 11      //把当前行号重置为1000
    #line 10 "cool.c"  //把行号重置为10,把文件名重置为 cool.c
    

    #error 指令让预处理器发出一条错误消息,该消息包含指令中的文本。如果可能的话,编译过程应该中断。可以这样使用#error 指令:

    #if __STDC_VERSION__!= 201112L
    #error Not C11
    #endif//编译以上代码生成后,输出如下:
    $ gcc newish.c
    newish.c:14:2: error: #error Not C11
    $ gcc -std=c11 zewish.c
    $
    

    如果编译器只支持旧标准,则会编译失败,如果支持 CI1 标准,就能成功编译。

  12. #pragma

    在现在的编译器中,可以通过命令行参数或 IDE 菜单修改编译器的一些设置。#pragma 把编译器指令放入源代码中。例如,在开发 C99 时,标准被称为 C9X,可以使用下面的编译指示 (pragma)让编译器支持 C9X:

    #pragma c9x On
    
  13. 泛型选择(C11)

    在程序设计中,泛型编程(generic programming)指那些没有特定类型,但是一旦指定一种类型,就可以转换成指定类型的代码。

    例如,C++在模板中可以创建泛型算法,然后编译器根据指定的类型自动使用实例化代码。C没有这种功能。然而,C11 新增了一种表达式,叫作泛型选择表达式(generic selection expression),可根据表达式的类型(即表达式的类型是int、double 还是其他类型)选择一个值。泛型选择表达式不是预处理器指令,但是在一些泛型编程中它常用作#define 宏定义的一部分。

    下面是一个泛型选择表达式的示例:

    _Generic (x, int: 0, float: 1, double: 2, default: 3)
    

    _Generic 是C11 的关键宇。_Generic 后面的國括号中包含多个用逗号分隔的项。

    1. 第1个项是一个表达式,后面的每个项都由一个类型、一个冒号和一个值组成,如float:1
    2. 第1个项的类型匹配哪个标签,整个表达式的值是该标签后面的值。例如,假设上面表达式中× 是int 类型的变量,× 的类型匹配int :标签,那么整个表达式的值就是0。
    3. 如果没有与类型匹配的标签,表达式的值就是 default:标签后面的值。
    4. 泛型选择语句与 switch 语句类似,只是前者用表达式的类型匹配标签,而后者用表达式的值匹配标签。
    #include <stdio.h>
    #define MYTYPE(X) _Generic((X),\
    int: "int",\
    float : "float",\
    double: "double",\
    default: "other"\
    )int main(void)
    {int d = 5;printf("%s\n", MYTYPE(d));     //d是int类型,输出intprintf("%s\n", MYTYPE(2.0*d)); //2.0*d是double类型,输出doubleprintf("%s\n", MYTYPE(3L));    //3L是long类型,输出otherprintf("%s\n", MYTYPE(&d));    //&d是int * 类型输出otherreturn 0;
    }
    
  14. 内联函数(C99)

    通常,函数调用都有一定的开销,因为函数的调用过程包括建立调用、传递参数、跳转到函数代码并返回。使用宏使代码内联,可以避免这样的开销。

    C99还提供另一种方法:内联函数(inline function)。把函数变成内联函数,编译器可能会用内联代码替换函数调用,并(或)执行一些其他的优化,但是也可能不起作用。

    标准规定具有内部链接的函数可以成为内联函数,还规定了内联函数的定义与调用该函数的代码必须在同一个文件中。

    因此,最简单的方法是使用函数说明符inline和存储类别说明符static。通常,内联函数应定义在首次使用它的文件中,所以内联函数也相当于函数原型。如下所示:

    #include <stdio.h>
    inline static void eatline ()   // 内联函数定义/原型
    {while (getchar() != '\n')continue;
    }
    int main()
    {...eatline();              //函数调用...
    }
    

    编译器查看内联函数的定义(也是原型),可能会用函数体中的代码替换 eatline)函数调用。也就是说,效果相当于在西数调用的位置输入函数体中的代码:

    #include <stdio.h>
    inline static void eatline ()   // 内联函数定义/原型
    {while (getchar() != 'In')continue;
    }
    int main()
    {...
    //函数调用之处相当于插入代码块while (getchar() != "\n')continue;              ...
    }
    

    由于并未给内联函数预留单独的代码块,所以无法获得内联函数的地址(实际上可以获得地址,不过这样做之后,编译器会生成一个非内联函数)。另外,内联函数无法在调试器中显示。

    内联函数应该比较短小。把较长的函数变成内联并未节约多少时间,因为执行函数体的时间比调用函数的时间长得多。

    编译器优化内联函数必须知道该函数定义的内容。这意味着内联函数定义与函数调用必须在同一个文件中。鉴于此,一般情况下内联函数都具有内部链接。

    因此,如果程序有多个文件都要使用某个内联函数,那么这些文件中都必须包含该内联函数的定义。最简单的做法是,把内联函数定义放入头文件,并在使用该内联函数的文件中包含该头文件即可。

    一般都不在头文件中放置可执行代码,内联函数是个特例。因为内联两数具有内部链接,所以在多个文件中定义同一个内联函数不会产生什么问题。

    与C++不同的是,C还允许混合使用内联函数定义和外部函数定义(具有外部链接的两数定义)。例如,一个程序中使用下面了个文件:

    //  file1.c
    #include <stdio.h>
    inline static double square(double x) { return x*x; }
    void spam(double);
    void masp(double);
    int main()
    {double q = square(1.3);printf("%.2f\n", q);spam(12.6);masp(1.6);return 0;
    }
    
    //  file2.c
    #include <stdio.h>
    double square(double x) { return (int) (x*x); }
    void spam(double v)
    {double kv = square(v);printf("%.2f\n", kv);return;
    }
    
    //  file3.c
    #include <stdio.h>
    inline double square(double x) { return (int) (x * x + 0.5); }
    void masp(double w)
    {double kw = square(w);printf("%.2f\n", kw);return;
    }
    

    如上述代码所示,3 个文件中都定义了square() 函数。

    1. file1.c文件中是 inline static定义
    2. file2.c 文件中是普通的函数定义(因此具有外部链接)
    3. file3.c 文件中是 inline 定义,省路了static
    • 3个文件中的函数都调用了 sguare() 函数,这会发生什么情况?
      1. file1.c文件中的main()使用square()的局部static 定义。由于该定义也是inline 定义,所以编译器有可能优化代码,也许会内联该函数。
      2. file2.c文件中spam函数使用该文件中square()函数的定义,该定义具有外部链接,其他文件也可见。
      3. file3.c 文件中,编译器既可以使用该文件中square()函数的内联定义,也可以使用file2.c文件中的外部链接定义。如果像 file3.c 那样,省路file1.c 文件 inline 定中的 static,那么该 inline 定义被视为可替换的外部定义。
  15. 关于库以及一些函数的使用跳过

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/591113.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一种安防场景下融合注意力机制和时空图卷积神经网络的人体动作识别方法与流程

本发明涉及模式识别与计算机视觉领域&#xff0c;尤其涉及一种安防场景下融合注意力机制和时空图卷积神经网络的人体动作识别方法。 背景技术&#xff1a; 视觉一直是人类获取外界信息的最重要、最直观的途径&#xff0c;据有关统计&#xff0c;人类获取信息的80&#xff05;都…

nginx 多端口部署多站点

目录 1.进行nginx.conf 2.复制粘贴 3.修改端口及站点根目录 4. 网站上传 1.进行nginx.conf 在 nginx 主要配置文件 nginx.conf 中&#xff0c;server 是负责一个网站配置的&#xff0c;我们想要多个端口访问的话&#xff0c;可以复制多个 server 先进入到 nginx.conf 中 …

「微服务」Saga 模式 如何使用微服务实现业务事务-第二部分

在上一篇文章中&#xff0c;我们看到了实现分布式事务的一些挑战&#xff0c;以及如何使用Event / Choreography方法实现Saga的模式。在本文中&#xff0c;我们将讨论如何通过使用另一种类型的Saga实现&#xff08;称为Command或Orchestration&#xff09;来解决一些问题&#…

vue3+echarts可视化——记录我的2023编程之旅

文章目录 ⭐前言⭐2023我在csdn的旅途痕迹&#x1f496;node系列文章&#x1f496;vue3系列文章&#x1f496;python系列文章&#x1f496;react系列文章&#x1f496;js拖拽相关文章&#x1f496;小程序系列文章&#x1f496;uniapp系列文章 ⭐可视化布局&#x1f496; git 数…

【VRTK】【VR开发】【Unity】18-VRTK与Unity UI控制的融合使用

课程配套学习项目源码资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【背景】 VRTK和Unity自身的UI控制包可以配合使用发挥效果。本篇就讨论这方面的实战内容。 之前可以互动的立体UI并不是传统的2D UI对象,在实际使用中…

(self-supervised learning)Event Camera Data Pre-training

Publisher: ICCV 2023 MOTIVATION OF READING: 自监督学习、稀疏事件 NILM link: https://arxiv.org/pdf/2301.01928.pdf Code: GitHub - Yan98/Event-Camera-Data-Pre-training 1. Overview Contributions are summarized as follows: 1. A self-supervised framework f…

如何下载LANDSAT数据

LANDSAT&#xff08;Land Remote Sensing Satellite&#xff09;是美国国家航空航天局&#xff08;NASA&#xff09;与美国地质调查局&#xff08;USGS&#xff09;合作推出的一系列卫星&#xff0c;旨在提供地球表面的高分辨率遥感数据。LANDSAT卫星系列始于1972年&#xff0c…

win10连上了wifi热点但是无法上网

我的情况是能正常连接wifi热点&#xff08;手机连接这个热点能上网&#xff0c;说明这个wifi热点是正常的&#xff09; 但是没法上网 打开cmd窗口发现能ping通百度&#xff0c;掘金&#xff0c;csdn这些网址。这就更奇怪了&#xff01;于是根据上面的提示&#xff0c;检查了代…

【数据结构和算法】 相等行列对

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 三层循环 2.2 哈希 二层循环 三、代码 3.1 三层循环 3.2 哈希 二层循环 四、复杂度分析 4.1 …

LeetCode74二分搜索优化:二维矩阵中的高效查找策略

题目描述 力扣地址 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&…

抖店和商品橱窗有什么区别?新手应该选哪个?

我是电商珠珠 临近年底了&#xff0c;有的人已经开始为下一年筹谋&#xff0c;有的去抖音做账号做直播带货&#xff0c;不会直播带货的就想尝试做下抖店&#xff0c;来为以后的经济打基础。 刚想要接触却对这类有些迷糊&#xff0c;发现商品橱窗和抖店都可以卖货&#xff0c;…

jumpServer-01-跳板机与堡垒机

jumpServer-01-跳板机与堡垒机 文章目录 jumpServer-01-跳板机与堡垒机一、为什么需要跳板机&#xff1f;二、堡垒机的核心价值三、跳板机与堡垒机的区别四、堡垒机的核心作用与价值 一、为什么需要跳板机&#xff1f; 跳板机&#xff08;Jump Server&#xff09;是一种安全设备…

C# WPF上位机开发(以始为终,寻找真实的上位机需求)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 c# wpf、qt、mfc这些上位机的需求是真实存在的&#xff0c;在现实中有很多应用的地方&#xff0c;这一点大家都很清楚。而程序员本身呢&#xff0c…

iOS问题记录 - iOS 17通过NSUserDefaults设置UserAgent无效(续)

文章目录 前言开发环境问题描述问题分析1. 准备源码2. 定位源码3. 对比源码4. 分析总结 解决方案补充内容1. UserAgent的组成2. UserAgent的设置优先级 最后 前言 在上篇文章中对该问题做了一些判断和猜测&#xff0c;并给出了解决方案。不过&#xff0c;美中不足的是没有进一…

117基于matlab的短时傅里叶变换(STFT)、小波变换(WT)、同步压缩变换(SST)、瞬态提取变换(TET)进行时频分析

基于matlab的短时傅里叶变换&#xff08;STFT&#xff09;、小波变换&#xff08;WT&#xff09;、同步压缩变换&#xff08;SST&#xff09;、瞬态提取变换&#xff08;TET&#xff09;进行时频分析。程序已调通&#xff0c;可直接运行。 117时频分析短时傅里叶变换 (xiaohong…

酷开系统 | 重磅!酷开科技荣获第十届广东专利优秀奖!

2023年12月16日广东省市场监督管理局公布了第十届广东专利奖获奖名单并进行了公示。其中&#xff0c;深圳市酷开网络科技股份有限公司申报的专利“一种智能电视的交互系统及方法”&#xff08;专利号ZL201310038386.0&#xff09;&#xff0c;荣获第十届广东专利优秀奖。 广东…

CSS 纵向底部往上动画

<template><div class"container" mouseenter"startAnimation" mouseleave"stopAnimation"><!-- 旋方块 --><div class"box" :class"{ scale-up-ver-bottom: isAnimating }"><!-- 元素内容 --&g…

【图像拼接】源码精读:Seam-guided local alignment and stitching for large parallax images

第一次来请先看这篇文章&#xff1a;【图像拼接&#xff08;Image Stitching&#xff09;】关于【图像拼接论文源码精读】专栏的相关说明&#xff0c;包含专栏内文章结构说明、源码阅读顺序、培养代码能力、如何创新等&#xff08;不定期更新&#xff09; 【图像拼接论文源码精…

在Adobe Acrobat上如何做PDF文档签名

Adobe Acrobat如何做PDF文档签名&#xff1f;PDF文档签名是指对PDF文档进行基于证书的数字签名&#xff0c;类似于传统的手写签名&#xff0c;可标识签名文档的人员。与手写签名不同&#xff0c;数字签名难以伪造&#xff0c;因为其包含签名者唯一的加密信息。为PDF文档进行基于…

网络四元组

文章目录 网络四元组 今天我们来聊聊 网络四元组 网络四元组 四元组&#xff0c;简单理解就是在 TCP 协议中&#xff0c;去确定一个客户端连接的组成要素&#xff0c;它包括源 IP 地址、目标 IP 地址、源端口号、目标端口号。 正常情况下&#xff0c;我们对于网络通信的认识可…