【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 = 100
        
        z = SQUARE1(2);//z = 100 / (2*2) = 25
        
        z = 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';          //在此处放一个空字符
                else
                    while (getchar() != '\n')
                        continue;          //处理输入行中的剩余字符
            }
            return ret_val;
        }
        
        
      3. useheader.c————使用names_st结构

        #include <stdio.h>
        #include "names_st.h"
        //记得链接names_st.c
        
        int 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 4
    
    int main(void)
    {
        int i;
        int total = 0;
    
        for (i = 1; i <= LIMIT; i++)
        {
            total += 2*i*i + 1;
    #ifdef JUST_CHECKING
            printf("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类型,输出int
        printf("%s\n", MYTYPE(2.0*d)); //2.0*d是double类型,输出double
        printf("%s\n", MYTYPE(3L));    //3L是long类型,输出other
        printf("%s\n", MYTYPE(&d));    //&d是int * 类型输出other
        return 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.mfbz.cn/a/285445.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构基本概念

数据结构 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。它包括数据的逻辑结构、数据的存储结构和数据的基本运算。 数据的逻辑结构 数据的逻辑结构是指数据元素之间的逻辑关系。所谓逻辑关系是指数据元素之间的关联方式或“邻接关系”。 逻辑结构与数据元素本…

[④Meson]: Unit Tests

前言 Meson构建系统支持uni-tests&#xff0c;使用run()命令可以非常方便进行uni-test测试。 Syntax 基本语法&#xff1a; e executable(prog, source.c) test(name of test, e)下面是创建两个可执行程序&#xff0c;并且将它们在test中使用的例子&#xff1a; test0 e…

【Unity引擎技术整合】 Unity学习路线 | 知识汇总 | 持续更新 | 保持乐趣 | 共同成长

前言 本文对Unity引擎的知识进行了一个整理总结&#xff0c;基本包含了Unity中大部分的知识介绍。网上也有很多Unity相关的学习资料&#xff0c;但大多数都不成体系&#xff0c;学起来的时候难免会东奔西走的摸不着头脑。本文整理的多数文章都是有对应的系列性文章专栏&#x…

在 Oracle 数据库表中加载多个数据文件

在本文中&#xff0c;我将展示 SQL 加载器 Unix 脚本实用程序的强大功能&#xff0c;其中 SQL 加载器可以使用自动 shell 脚本加载多个数据文件。这在处理大量数据以及需要将数据从一个系统移动到另一个系统时非常有用。 它适合涉及大量历史数据的迁移项目。那么就不可能为每…

Laya3D常见报错信息汇总

1.Cannot read property isTrigger of undefined&#xff1a;貌似是Laya引擎的bug 解决方法&#xff1a; 在初次加载带有刚体的3D游戏对象组件的时候&#xff0c;使用代码获取刚体组件&#xff0c;设置刚体组件的isTrigger属性&#xff1a; let rigid this.obj.getComponent(L…

SELinux 安全模型——MLS

首发公号&#xff1a;Rand_cs BLP 模型&#xff1a;于1973年被提出&#xff0c;是一种模拟军事安全策略的计算机访问控制模型&#xff0c;它是最早也是最常用的一种多级访问控制模型&#xff0c;主要用于保证系统信息的机密性&#xff0c;是第一个严格形式化的安全模型 暂时无…

盾构机数据可视化监控平台 | 图扑数字孪生

2002 年,中国 863 计划把盾构机列为国家关键技术&#xff0c;以国家力量为主导&#xff0c;集中力量进行盾构机专项研究。在 2008 年&#xff0c;中国成功研制出属于自己的国产盾构机——中国中铁一号&#xff0c;同时还打通了天津地铁 1500m 的隧道。此举更彻底地打破了国内盾…

【Java基础篇】While(true) 和 for(;;)哪个性能更好呢

两个无限循环的性能分析 ✔️两者反编译比较 ✔️两者反编译比较 While(true) 和 for(; &#x1f609; 都是做无限循环的代码&#xff0c;他们两个有什么区别呢&#xff1f; 关于这个问题&#xff0c;网上有很多的讨论&#xff0c;今天我收到私信&#xff0c;所以凑着假期&…

【C++】Ubuntu编译filezilla client

在新版Ubuntu 22.04.3 LTS上编译filezilla client成功&#xff0c;shell命令如下&#xff1a; sudo apt-get install libfilezilla-dev libwxbase3.0-dev gnutls-dev libdbus-1-dev sudo apt-get install libwxgtk3.0-gtk3-dev sudo apt-get install libgtk-3-dev sudo apt-ge…

【力扣100】78.子集

添加链接描述 class Solution:def subsets(self, nums: List[int]) -> List[List[int]]:# 思路是回溯&#xff0c;这道题和【全排列】不一样的地方是出递归&#xff08;收获&#xff09;的判断条件不一样def dfs(path,index,res):res.append(path[:])for i in range(index,…

【C++杂货铺】C++11新特性——可变参数模板

文章目录 一、可变模板参数相关概念的引入二、获取参数包中参数的个数三、递归函数方式展开参数包四、逗号表达式展开参数包五、可变模板参数的实际应用——emplace相关接口5.1 回顾一下 push_back 的三种用法5.2 emplace_back 使用方法介绍5.3 听说 emplace_back 可以提高效率…

三菱人机交互GT Designer的使用(三,指示灯,数值显示与输入,字符串显示与输入,日期|时间的显示)

今天继续对GT进行学习&#xff0c;如有不妥&#xff0c;欢迎指正&#xff01;&#xff01;&#xff01; 目录 指示灯设置 设置指示灯 位指示灯 字指示灯 数值输入&#xff0c;输出&#xff08;二者差距不大&#xff09; 数值显示与输出 数值显示&#xff08;只能显示&…

Spring-JdbcTemplate

1.什么是JdbcTemplate (1)spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作 2.准备工作 (1) 引入相关jar druid.jar ,mysql.jar , spring-jdbc.jar,spring-tx.jar,spring-orm.jar (2)在spring配置 连接池 <!--数据源--><bean id"ds" class&q…

【GitHub】ssh: connect to host github.com port 22: Connection refused

本地使用git上传GitHub仓库时发现的一个报错&#xff0c;以为是本机连不上github了&#xff0c;ping过后发现能够正常访问&#xff0c;于是上网找到了一个很完美的解决方案 原因&#xff1a;22端口被占用或被防火墙屏蔽 解决方法&#xff1a;切换GitHub的443端口 1.首先找到…

普中STM32-PZ6806L开发板(HAL库函数实现-PWM呼吸灯)

简介 实现PWM呼吸灯。 主芯片 STM32F103ZET6呼吸灯引脚 : PC7电路原理图 LED8 电路图 LED8 与 主芯片连接图 其他知识 公式 PWM周期公式: Tpwm ( (ARR 1) * (PSC 1) ) / Tclk Tclk为定时器的输入时钟频率 Tout则为定时器溢出时间 ARR为计数周期 PSC为预分频器的值…

undefined reference to `pthread_create‘的另外一种解法

背景 编译带有thread的程序人&#xff0c;如果忘记-lpthread&#xff0c;那么就会报错 解决办法一&#xff1a;添加-lpthread 很简单添加-lpthread就行了 解决办法二&#xff1a;升级glibc 在高版本的glibc上&#xff0c;可能无需增加-lpthread Why glibc 2.34 removed li…

数字图像处理(3)——频域图像增强

&#x1f525;博客主页&#xff1a;是dream &#x1f680;系列专栏&#xff1a;深度学习环境搭建、环境配置问题解决、自然语言处理、语音信号处理、项目开发 &#x1f498;每日语录&#xff1a;贤才&#xff0c;难进易出&#xff1b;庸才&#xff0c;易进易初出&#xff1b;…

区块链复习

文章目录 考试重点哈希碰撞哈希函数的应用哈希算法对称加密密钥解决方法 椭圆曲线加密算法数字签名国密算法第一章第二章比特币比特币公钥 区块比特币的信息查询去中心化与分布式BIP治理结构 第三章 比特币区块结构头哈希值的作用&#xff1a;防篡改自然分叉&#xff1a; 交易数…

MySQL数据库学习一

1 什么是数据库的事务&#xff1f; 1.1 事务的典型场景 在项目里面&#xff0c;什么地方会开启事务&#xff0c;或者配置了事务&#xff1f;无论是在方法上加注解&#xff0c;还 是配置切面。 <tx:advice id"txAdvice" transaction-manager"transactionMa…

CodeWave 3.4版本新特性AI智能助手功能的革新与实践

目录 1 前言2 CodeWave 3.4版本&#xff1a;AI智能助手功能的新特性2.1 逻辑生成2.2 逻辑解读 3 CodeWave提供了全方位的逻辑组件4 AI智能助手功能的实践案例4.1 生成逻辑的实践4.2 解读逻辑的实践4.3 CodeWave的解读描述和逻辑的对比 5 结语 1 前言 在数字化时代&#xff0c;…