C语言总结十三:程序环境和预处理详细总结

       了解程序的运行环境可以让我们更加清楚的程序的底层运行的每一个步骤和过程,做到心中有数,预处理阶段是在预编译阶段完成,掌握常用的预处理命令语法,可以让我们正确的使用预处理命令,从而提高代码的开发能力和阅读别人代码的能力,本篇博客详细总结C语言中的程序环境和预处理,达到理解并运用的目的!

一、程序的翻译环境和执行环境

       在ANSI C的任何一种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。

第2种是执行环境,它用于实际执行代码。

二、详细介绍编译+链接

2.1 翻译环境

  1. 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
  2. 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
  3. 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库,将其需要的函数也链接到程序中。

2.2 编译本身划分的阶段

看代码: sum.c文件

int g_val = 2016;
void print(const char *str)
{
    printf("%s\n", str);
}

test.c文件

#include <stdio.h>
 extern void print(char *str);
 extern int g_val;

int main()
{

   printf("%d\n", g_val);
   print("hello bit.\n");
   return 0;
}

VIM学习资料:

  1.   简明VIM练级攻略: https://coolshell.cn/articles/5426.html
  2.   给程序员的VIM速查卡 https://coolshell.cn/articles/5479.html   

2.3 运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2.  程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

三、预处理详细介绍

3.1 预定义符号

        顾名思义,预定义宏就是已经预先定义好的宏,我们可以直接使用,无需再重新定义。

举例
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    printf("Date : %s\n", __DATE__);
    printf("Time : %s\n", __TIME__);
    printf("File : %s\n", __FILE__);
    printf("Line : %d\n", __LINE__);

    system("pause");
    return 0;
}


3.2  #define

        #define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。

     这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串等同,它不需要双引号。 

3.2.1  #define 定义标识

语法格式:#define  宏名  字符串

解释#表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。

注意两点:

1.字符串可以是数字、表达式、if 语句、函数等。

2.宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。建议不要加分号。

#include <stdio.h>

#define N 100

int main()
{
    int sum = 20 + N;
    printf("%d\n", sum);
    return 0;
}

注意第 6 行代码int sum = 20 + NN100代替了。#define N 100就是宏定义,N为宏名,100是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。


3.2.2  #define 定义宏(带参数的宏定义)

       程序中反复使用的表达式就可以使用宏定义,#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

     下面是宏的申明方式:  #define name( parament-list )   stuff     

      其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。注意: 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

如:#define SQUARE( x ) x * x
这个宏接收一个参数 x,如果在上述声明之后,你把SQUARE( 5 );
置于程序中,预处理器就会用下面这个表达式替换上面的表达式:5 * 5

警告!!!上面这个宏存在一个问题!

观察下面的代码段:
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
乍一看,你可能觉得这段代码将打印36这个值。
事实上,它将打印11.
为什么?
替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );
这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。
在宏定义上加上两个括号,这个问题便轻松的解决了:#define SQUARE(x) (x) * (x)
这样预处理之后就产生了预期的效果:printf ("%d\n",(a + 1) * (a + 1) );

还有另一个宏定义:

#define DOUBLE(x) (x) + (x)
定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));这将打印什么值呢?
看上去,好像打印100,但事实上打印的是55.
我们发现替换之后:printf ("%d\n",10 * (5) + (5));
乘法运算先于宏定义的加法,所以出现了55
这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。
#define DOUBLE(x)   ( ( x ) + ( x ) )

总结:提示: 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中 的操作符或邻近操作符之间不可预料的相互作用。


3.2.3  #define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。

注意事项:

1.  宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。

2.  宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令

3.  代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,而作为字符串处理。

4.  宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。

5.  习惯上宏名用大写字母表示,以便于与变量区别。

6.  可用宏定义表示数据类型,使书写方便。但是需要注意用宏定义表示数据类型和用typedef定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。(易出错点!)

#define UINT unsigned int
在程序中可用 UINT 作变量说明:
UINT a, b;
请看下面表示整型指针类型的两种方式:
#define PIN1 int *
typedef int *PIN2;  //也可以写作typedef int (*PIN2);

下面用 PIN1,PIN2 说明变量时就可以看出它们的区别:
PIN1 a, b;   在宏代换后变成:int * a, b;表示 a 是指向整型的指针变量,而 b 是整型变量。
然而:
PIN2 a,b;
表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。

由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。
在使用时要格外小心,以避出错。


3.2.4  #和##

在宏定义中,有时还会用到###两个符号,它们能够对宏参数进行操作。

1. #的用法

#用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。

#define STR(s) #s
那么:
printf("%s", STR(c.biancheng.net));
printf("%s", STR("c.biancheng.net"));
分别被展开为:
printf("%s", "c.biancheng.net");
printf("%s", "\"c.biancheng.net\"");
可以发现,即使给宏参数“传递”的数据中包含引号,
使用#仍然会在两头添加新的引号,而原来的引号会被转义。
#include <stdio.h>
#define STR(s) #s
int main()
 {
    printf("%s\n", STR(c.biancheng.net));
    printf("%s\n", STR("c.biancheng.net"));
    return 0;
}

运行结果:
c.biancheng.net
"c.biancheng.net"

2. ##用法

##称为连接符,用来将宏参数或其他的串连接起来。

#define CON1(a, b) a##e##b
#define CON2(a, b) a##b##00

那么:
printf("%f\n", CON1(8.5, 2));
printf("%d\n", CON2(12, 34));
将被展开为:
printf("%f\n", 8.5e2);
printf("%d\n", 123400);
#include <stdio.h>
#define CON1(a, b) a##e##b
#define CON2(a, b) a##b##00
int main()
{
    printf("%f\n", CON1(8.5, 2));
    printf("%d\n", CON2(12, 34));
    return 0;
}

运行结果:
850.000000
123400


3.2.5 带副作用的宏参数

      当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。


3.2.6 宏和函数对比

       宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))    那为什么不用函数来完成这个任务?

原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹。

2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以 用于>来比较的类型。 宏是类型无关的。

宏的缺点:当然和函数相比宏也有劣势的地方:

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。


3.2.7 命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是: 把宏名全部大写 函数名不要全部大写


3.3 #undef

       这条指令用于移除一个宏定义。

#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。


3.4 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个 程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器 内存大些,我们需要一个数组能够大些。)

#include <stdio.h>
int main()
{
    int array [ARRAY_SIZE];
    int i = 0;
    for(i = 0; i< ARRAY_SIZE; i ++)
   {
        array[i] = i;
   }
    for(i = 0; i< ARRAY_SIZE; i ++)
   {
        printf("%d " ,array[i]);
   }
    printf("\n" );
    return 0;
}

编译指令:

//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c


3.5 条件编译

       在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。 比如说: 调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

      这些操作都是在预处理阶段完成的,多余的代码以及所有的宏都不会参与编译,不仅保证了代码的正确性,还减小了编译后文件的体积。这种能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。

常见的条件编译指令:

1. 单分支条件编译 :

#if    整型常量表达式
#endif

如进行注释掉:#if  0

                         #endif

2.多分支条件编译

#if 整型常量表达式1
    程序段1
#elif 整型常量表达式2
    程序段2
#elif 整型常量表达式3
    程序段3
#else
    程序段4

#endif

它的意思是:如常“表达式1”的值为真(非0),就对“程序段1”进行编译,否则就计算“表达式2”,结果为真的话就对“程序段2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。
     需要注意的是,#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。

3.判断是否被定义

#ifdef  宏名
    程序段1
#else
    程序段2
#endif

也可以省略 #else:

#ifdef  宏名
    程序段

#endif

它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。

4.判断是否未被定义

#ifndef 宏名
    程序段1 
#else 
    程序段2 

#endif

与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。

总结:#if、#ifdef、#ifndef的用法区别在哪里?

     #if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。

3.6文件包含

3.6.1头文件被包含的方式

  #include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include 也是C语言预处理命令的一种。#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次。头文件分为两种:标准头文件和自己编写的头文件。

#include 的用法有两种:

#include <stdHeader.h>
#include "myHeader.h"

关于 #include 用法的注意事项:

  • 一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令。
  • 同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制。
  • 文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。

编程习惯:习惯是使用尖括号来引入标准头文件,使用双引号来引入自定义头文件(自己编写的头文件),这样一眼就能看出头文件的区别。 


3.6.2 嵌套文件包含

如果出现这样的场景:

comm.h和comm.c是公共模块。

test1.h和test1.c使用了公共模块。

test2.h和test2.c使用了公共模块。

test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。

如何解决这个问题?—条件编译

每个头文件的开头写:

第一种:

#ifndef __TEST_H__

#define __TEST_H__

//头文件的内容

#endif   //__TEST_H__

第二种:

#pragma once

就可以避免头文件的重复引入。

四、其他预处理指令

      以上便是程序环境和预处理全部内容,认真理解消化,一定会有极大的收获,至此,C语言所有理论技术已经全部总结完,认真复习消化练习,一定会取得不错的效果。可以留下你们点赞、关注、评论,您的支持是对我极大的鼓励,下期再见!

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

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

相关文章

在线扒站网PHP源码-在线扒站工具网站源码

源码介绍 这是一款在线的网站模板下载程序&#xff0c;也就是我们常说的扒站工具&#xff0c;利用它我们可以很轻松的将别人的网站模板样式下载下来&#xff0c;这样就可以大大提高我们编写前端的速度了&#xff01;注&#xff1a;扒取的任何站点不得用于商业、违法用途&#…

如何在CentOS 7 中基于OpenSSL 3.0 搭建Python 3.0 环境

1、OpenSSL 1.1 原因 [rootlocalhost ~]# openssl version OpenSSL 1.0.2k-fips 26 Jan 2017 [rootlocalhost ~]#通过执行openssl version可知Linux系统已经安装了OpenSSL&#xff0c;但该版本较低&#xff1b;Python 3 要求 OpenSSL版本不能低于1.1.1&#xff0c;否则安装P…

【02】mapbox js api加载arcgis切片服务

需求&#xff1a; 第三方的mapbox js api加载arcgis切片服务&#xff0c;同时叠加在mapbox自带底图上 效果图&#xff1a; 形如这种地址去加载&#xff1a; http://zjq2022.gis.com:8080/demo/loadmapbox.html arcgis切片服务参考链接思路&#xff1a;【01】mapbox js api加…

【Copula】最可能场景详解

基于Copula联合分布的最可能场景详解 最可能场景&#xff08;The most-likely scenario&#xff09;实例探讨参考 最可能场景&#xff08;The most-likely scenario&#xff09; 相应英文介绍原理介绍如下&#xff1a;&#xff08;出自论文J2020-Drought hazard transferabilit…

RTC讲解

RTC&#xff08;Real Time Clock&#xff09;实时时钟 RTC实时时钟本质上是一个独立的定时器。RTC模块拥有一组连续计数的32位无符号计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。 RTC模块和时钟配…

基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备功能开发(BLE+HID+FreeRTOS+Gecko SDK)

目录 项目介绍硬件介绍项目设计开发环境及工程参考总体流程图硬件基本配置应用初始化按键中断回调定时器回调按键响应任务蓝牙事件回调BLE HIDReport Map及报文键盘设备鼠标设备复合设备 发送字符串上/下滚动 功能展示项目总结 &#x1f449; 【Funpack3-1】基于XG24-EK2703A的…

网络端口映射和端口转发的区别和联系

目 录 一、端口映射技术 1.1 原理 1.2 应用场景 1、远程访问 2、游戏主机 3、文件共享 4、监控视频共享 二、端口转发技术 2.1 原理 2.2 应用场景 1、网络负载均衡 2、网络安全 3、网络代理 三、端口映射和转发的实现方法 3.1 路由器配置 3.2 网络防火墙 …

C Primer Plus 第6版 编程练习 chapter 16

文章目录 1. 第1题1.1 题目描述1.2 编程源码1.3 结果显示 2. 第2题2.1 题目描述2.2 编程源码2.3 结果显示 3. 第3题3.1 题目描述3.2 编程源码3.3 结果显示 4. 第4题4.1 题目描述4.2 编程源码4.3 结果显示 5. 第5题5.1 题目描述5.2 编程源码5.3 结果显示 6. 第6题6.1 题目描述6.…

linux|操作系统|centos7物理机安装网卡驱动8188gu(内核升级,firmware固件,USB设备管理,module管理)

前言&#xff1a; 目前服务器领域centos7基本是主流的操作系统&#xff0c;而linux相对于Windows来说&#xff0c;软硬件方面的支持是差很多的&#xff0c;在硬件方面来说&#xff0c;以一个免驱的网卡为例&#xff0c;window xp可能不会自动识别到&#xff0c;但Windows10基本…

问题:Feem无法发送信息OR无法连接(手机端无法发给电脑端)

目录 前言 问题分析 资源、链接 其他问题 前言 需要在小米手机、华为平板、Dell电脑之间传输文件&#xff0c;试过安装破解的华为电脑管家、小米的MIUI文件传输等&#xff0c;均无果。&#xff08;小米“远程管理”ftp传输倒是可以&#xff0c;但速度太慢了&#xff0c;且…

【Ant Design of Vue】Modal.confirm无法关闭的bug

一、问题 在使用 Ant Design Vue 的 Modal.confirm 确认框时&#xff0c;出现了点击取消和确定后 Modal.confirm 确认框无法关闭的问题 二、代码 代码完全是 copy 的官网的代码&#xff0c;但是 copy 到本地后就会出现上述问题 <template><a-button click"sho…

鸿蒙开发(五)鸿蒙UI开发概览

从用户角度来讲&#xff0c;一个软件拥有好看的UI&#xff0c;那是锦上添花的事情。再精确的算法&#xff0c;再厉害的策略&#xff0c;最终都得通过UI展现给用户并且跟用户交互。那么&#xff0c;本篇一起学习下鸿蒙开发UI基础知识&#xff0c;认识下各种基本控件以及使用方式…

UE5 C++ 学习笔记 UBT UHT 和 一些头文件

总结一些似懂非懂的知识点&#xff0c;从头慢慢梳理。 任何一个项目都有创建这些三个.cs。 这个是蓝图转C 这个是本身就是C项目,应该就是多了一个GameModeBase类 Build.cs包含了每个模块的信息&#xff0c;表明了这个项目用到了哪一些模块。该文件里的using UnrealBuilTool 是…

为数字取证和 OSINT 调查定制用户体验

Tsurugi Linux 是一个高度定制的开源发行版&#xff0c;专注于支持 DFIR 调查。 该项目主要侧重于实时取证分析、事后分析和数字证据获取。用户还可以执行恶意软件分析、OSINT(开源情报)和计算机视觉活动。 我们精心打造了用户友好的体验&#xff0c;按照逻辑取证分析顺序组织…

【JS逆向学习】36kr登陆逆向案例(webpack)

在开始讲解实际案例之前&#xff0c;大家先了解下webpack的相关知识 WebPack打包 webpack是一个基于模块化的打包&#xff08;构建&#xff09;工具, 它把一切都视作模块 webpack数组形式&#xff0c;通过下标取值 !function(e) {var t {};// 加载器 所有的模块都是从这个…

Ps:使用钢笔工具快速精准抠图的技巧

众所周知&#xff0c;钢笔工具是 Photoshop 中最精准的、适用于硬边缘&#xff08;清晰轮廓&#xff09;对象的抠图工具。但是&#xff0c;如果从头开始一个锚点一个锚点的勾勒&#xff0c;既费时又费眼。 我们可以先用选区工具或选区命令做一个基础选区&#xff0c;然后将选区…

IPv6自动隧道---6to4中继

6to4中继 普通IPv6网络需要与6to4网络通过IPv4网络互通,这可以通过6to4中继路由器方式实现。所谓6to4中继,就是通过6to4隧道转发的IPv6报文的目的地址不是6to4地址,但转发的下一跳是6to4地址,该下一跳为路由器我们称之为6to4中继。隧道的IPv4目的地址依然从下一跳的6to4地…

力扣每日一练(24-1-19)

我的思路&#xff1a; def maxProfit(self, prices: List[int]) -> int:if not len(prices):return 0 max_profit 0for p in range(len(prices) - 1):prices[p] prices[p 1] - prices[p]prices[p] max(0, prices[p])max_profit sum(prices[:-1])return max_profit 其实…

百度云网盘下载速度如何提升到正常速度

引入问题 我们在下载代码学习资料的时候大多数都是百度云网盘&#xff0c;但是限速&#xff01;下载的十分的慢&#xff0c;有什么办法能让我们不开通会员就能享受正常速度呢&#xff1f; 当然有&#xff01; 解决百度云网盘下载速度过慢&#xff0c;提高到正常速度 点击右…

vue3使用vue-masonry插件实现瀑布流

《Vue插件》瀑布流插件vue-masonry的使用与踩坑记录 参数:item-selector transition-duration column-width origin-left origin-top gutter 前言: 之前其实有分享过一篇纯CSS实现瀑布流的方法: https://oliver.blog.csdn.net/article/details/126450691&#xff0c;但纯CSS实现…