泰裤辣!这是什么操作,自动埋点,还能传参?

news2024/11/28 16:43:05

目录

前言

参数放在注释中

准备入口文件

编写插件

运行代码

完整代码

参数放在局部作用域中

准备源代码

编写插件

运行代码

完整代码

总结


前言

        在上篇文章讲了如何通过手写babel插件自动给函数埋点之后,就有同学问我,自动插入埋点的函数怎么给它传参呢?这篇文章就来解决这个问题
        我讲了通过babel来实现自动化埋点,也讲过读取注释给特定函数插入埋点代码,感兴趣的同学可以来主页。

效果是这样的
源代码:

//##箭头函数 
//_tracker 
const test1 = () => {}; 
const test1_2 = () => {};

转译之后:

import _tracker from "tracker"; 

//##箭头函数 
//_tracker 
const test1 = () => { _tracker(); }; 
const test1_2 = () => {};

        代码中有两个函数,其中一个//_tracker的注释,另一个没有。转译之后只给有注释的函数添加埋点函数。
        要达到这个效果就需要读取函数上面的注释,如果注释中有//_tracker,我们就给函数添加埋点。这样做避免了僵硬的给每个函数都添加埋点的情况,让埋点更加灵活。


那想要给插入的埋点函数传入参数应该怎么做呢?传入参数可以有两个思路,

  • 一个是将参数也放在注释里面,在babel插入代码的时候读取下注释里的内容就好了;
  • 另一个是将参数以局部变量的形式放在当前作用域中,在babel插入代码时读取下当前作用域的变量就好;

下面我们来实现这两个思路,大家挑个自己喜欢的方法就好 

参数放在注释中

整理下源代码

import "./index.css"; 

//##箭头函数 
//_tracker,_trackerParam={name:'gongfu', age:18} 
const test1 = () => {}; 

//_tracker 
const test1_2 = () => {};

        代码中,有两个函数,每个函数上都有_tracker的注释,其中一个注释携带了埋点函数的参数,待会我们就要将这个参数放到埋点函数里

关于如何读取函数上方的注释,大家可以这篇文章:,我就不赘述了

准备入口文件

index.js

const { transformFileSync } = require("@babel/core"); 
const path = require("path"); 
const tracker = require("./babel-plugin-tracker-comment.js"); 
const pathFile = path.resolve(__dirname, "./sourceCode.js"); 

//transform ast and generate code 
const { code } = transformFileSync(pathFile, { 
    plugins: [
        [tracker, { 
            trackerPath: "tracker", 
            commentsTrack: "_tracker", 
            commentParam: "_trackerParam" 
        }]
    ], 
}); 

console.log(code);

        和上篇文章的入口文件类似,使用了transformFileSyncAPI转译源代码,并将转译之后的代码打印出来。过程中,将手写的插件作为参数传入

plugins: [[tracker, { trackerPath: "tracker", commentsTrack: "_tracker"}]]

除此之外,还有插件的参数。

  • trackerPath表示埋点函数的路径,插件在插入埋点函数之前会检查是否已经引入了该函数,如果没有引入就需要额外引入。
  • commentsTrack标识埋点,如果函数前的注释有这个,就说明函数需要埋点。判断的标识是动态传入的,这样比较灵活
  • commentParam标识埋点函数的参数,如果注释中有这个字符串,那后面跟着的就是参数了。就像上面源代码所写的那样。这个标识不是固定的,是可以配置化的,所以放在插件参数的位置上传进去 

编写插件

插件的功能有:

  • 查看埋点函数是否已经引入
  • 查看函数的注释是否含有_tracker
  • 将埋点函数插入函数中
  • 读取注释中的参数

前三个功能在上篇文章中已经实现了,下面实现第四个功能

const paramCommentPath = hasTrackerComments(leadingComments, options.commentsTrack);
if (paramCommentPath) {
  const param = getParamsFromComment(paramCommentPath, options);
  insertTracker(path, param, state);
}

//函数实现
const getParamsFromComment = (commentNode, options) => {
  const commentStr = commentNode.node.value;
  if (commentStr.indexOf(options.commentParam) === -1) {
    return null;
  }

  try {
    return commentStr.slice(commentStr.indexOf("{"), commentStr.indexOf("}") + 1);
  } catch {
    return null;
  }
};

const insertTracker = (path, param, state) => {
  const bodyPath = path.get("body");
  if (bodyPath.isBlockStatement()) {
    let ast = template.statement(`${state.importTackerId}(${param});`)();
    if (param === null) {
      ast = template.statement(`${state.importTackerId}();`)();
    }
    bodyPath.node.body.unshift(ast);
  } else {
    const ast = template.statement(`{
			${state.importTackerId}(${param});
              return BODY;
            }`)({ BODY: bodyPath.node });
    bodyPath.replaceWith(ast);
  }
};

        上述代码的逻辑是检查代码是否含有注释_tracker,如果有的话,再检查这一行注释中是否含有参数,最后再将埋点插入函数。
        在检查是否含有参数的过程中,用到了插件参数commentParam。表示如果注释中含有该字符串,那后面的内容就是参数了。获取参数的方法也是简单的字符串切割。如果没有获取到参数,就一律返回null

获取参数的复杂程度,取决于。先前是否就有一个规范,并且编写代码时严格按照规范执行。 像我这里的规范是埋点参数commentParam和埋点标识符_tracker必须放在一行,并且参数需要是对象的形式。即然是对象的形式,那这一行注释中就不允许就其他的大括号符号"{}"。 遵从了这个规范,获取参数的过程就变的很简单了。当然你也可以有自己的规范

        在执行插入逻辑的函数中,会校验参数param是否为null,如果是null,生成ast的时候,就不传入param了。

当然你也可以一股脑地传入param,不会影响结果,顶多是生成的埋点函数会收到一个null的参数,像这样_tracker(null)

第四个功能也实现了,来看下完整代码 

const { declare } = require("@babel/helper-plugin-utils");
const { addDefault } = require("@babel/helper-module-imports");
const { template } = require("@babel/core");
//judge if there are trackComments in leadingComments
const hasTrackerComments = (leadingComments, comments) => {
  if (!leadingComments) {
    return false;
  }
  if (Array.isArray(leadingComments)) {
    const res = leadingComments.filter((item) => {
      return item.node.value.includes(comments);
    });
    return res[0] || null;
  }
  return null;
};

const getParamsFromComment = (commentNode, options) => {
  const commentStr = commentNode.node.value;
  if (commentStr.indexOf(options.commentParam) === -1) {
    return null;
  }

  try {
    return commentStr.slice(commentStr.indexOf("{"), commentStr.indexOf("}") + 1);
  } catch {
    return null;
  }
};

const insertTracker = (path, param, state) => {
  const bodyPath = path.get("body");
  if (bodyPath.isBlockStatement()) {
    let ast = template.statement(`${state.importTackerId}(${param});`)();
    if (param === null) {
      ast = template.statement(`${state.importTackerId}();`)();
    }
    bodyPath.node.body.unshift(ast);
  } else {
    const ast = template.statement(`{
			${state.importTackerId}(${param});
              return BODY;
            }`)({ BODY: bodyPath.node });
    bodyPath.replaceWith(ast);
  }
};


const checkImport = (programPath, trackPath) => {
  let importTrackerId = "";
  programPath.traverse({
    ImportDeclaration(path) {
      const sourceValue = path.get("source").node.value;
      if (sourceValue === trackPath) {
        const specifiers = path.get("specifiers.0");
        importTrackerId = specifiers.get("local").toString();
        path.stop();
      }
    },
  });

  if (!importTrackerId) {
    importTrackerId = addDefault(programPath, trackPath, {
      nameHint: programPath.scope.generateUid("tracker"),
    }).name;
  }

  return importTrackerId;
};

module.exports = declare((api, options) => {

  return {
    visitor: {
      "ArrowFunctionExpression|FunctionDeclaration|FunctionExpression|ClassMethod": {
        enter(path, state) {
          let nodeComments = path;
          if (path.isExpression()) {
            nodeComments = path.parentPath.parentPath;
          }
          // 获取leadingComments
          const leadingComments = nodeComments.get("leadingComments");
          const paramCommentPath = hasTrackerComments(leadingComments,options.commentsTrack);

          // 如果有注释,就插入函数
          if (paramCommentPath) {
            //add Import
            const programPath = path.hub.file.path;
            const importId = checkImport(programPath, options.trackerPath);
            state.importTackerId = importId;

            const param = getParamsFromComment(paramCommentPath, options);
            insertTracker(path, param, state);
          }
        },
      },
    },
  };
});

运行代码

现在可以用入口文件来使用这个插件代码了

node index.js

执行结果

image.png

运行结果符合预期 

        可以看到我们设置的埋点参数确实被放到函数里面了,而且注释里面写了什么,函数的参数就会放什么,那么既然如此,可以传递变量吗?我们来试试看

import "./index.css";

//##箭头函数
//_tracker,_trackerParam={name, age:18}
const test1 = () => {
	const name = "gongfu2";
};

const test1_2 = () => {};

        在需要插入的代码中,声明了一个变量,然后注释的参数刚好用到了这个变量。
运行代码看看效果

image.png


        可以看到,插入的参数确实用了变量,但是引用变量却在变量声明之前,这肯定不行。得改改。
        需要将埋点函数插入到函数体的后面,并且是returnStatement的前面,这样就不会有问题了

const insertTrackerBeforeReturn = (path, param, state) => {
	//blockStatement
	const bodyPath = path.get("body");
	let ast = template.statement(`${state.importTackerId}(${param});`)();
	if (param === null) {
		ast = template.statement(`${state.importTackerId}();`)();
	}
	if (bodyPath.isBlockStatement()) {
		//get returnStatement, by body of blockStatement
		const returnPath = bodyPath.get("body").slice(-1)[0];
		if (returnPath && returnPath.isReturnStatement()) {
			returnPath.insertBefore(ast);
		} else {
			bodyPath.node.body.push(ast);
		}
	} else {
		ast = template.statement(`{ 
            ${state.importTackerId}(${param}); 
            return BODY; 
        }`)({ BODY: bodyPath.node });
		bodyPath.replaceWith(ast);
	}
};

        这里将insertTracker改成了insertTrackerBeforeReturn。其中关键的逻辑是判断是否是一个函数体,

  • 如果是一个函数体,就判断有没有return语句,
    • 如果有return,就放在return前面
    • 如果没有return,就放在整个函数体的后面
  • 如果不是一个函数体,就直接生成一个函数体,然后将埋点函数放在return的前面

再来运行插件:

image.png

很棒,这就是我们要的效果😃

完整代码

const { declare } = require("@babel/helper-plugin-utils");
const { addDefault } = require("@babel/helper-module-imports");
const { template } = require("@babel/core");
//judge if there are trackComments in leadingComments
const hasTrackerComments = (leadingComments, comments) => {
  if (!leadingComments) {
    return false;
  }
  if (Array.isArray(leadingComments)) {
    const res = leadingComments.filter((item) => {
      return item.node.value.includes(comments);
    });
    return res[0] || null;
  }
  return null;
};

const getParamsFromComment = (commentNode, options) => {
  const commentStr = commentNode.node.value;
  if (commentStr.indexOf(options.commentParam) === -1) {
    return null;
  }

  try {
    return commentStr.slice(commentStr.indexOf("{"), commentStr.indexOf("}") + 1);
  } catch {
    return null;
  }
};

const insertTrackerBeforeReturn = (path, param, state) => {
  //blockStatement
  const bodyPath = path.get("body");
  let ast = template.statement(`${state.importTackerId}(${param});`)();
  if (param === null) {
    ast = template.statement(`${state.importTackerId}();`)();
  }
  if (bodyPath.isBlockStatement()) {
    //get returnStatement, by body of blockStatement
    const returnPath = bodyPath.get("body").slice(-1)[0];
    if (returnPath && returnPath.isReturnStatement()) {
      returnPath.insertBefore(ast);
    } else {
      bodyPath.node.body.push(ast);
    }
  } else {
    ast = template.statement(`{ ${state.importTackerId}(${param}); return BODY; }`)({BODY: bodyPath.node });
    bodyPath.replaceWith(ast);
  }
};


const checkImport = (programPath, trackPath) => {
  let importTrackerId = "";
  programPath.traverse({
    ImportDeclaration(path) {
      const sourceValue = path.get("source").node.value;
      if (sourceValue === trackPath) {
        const specifiers = path.get("specifiers.0");
        importTrackerId = specifiers.get("local").toString();
        path.stop();
      }
    },
  });

  if (!importTrackerId) {
    importTrackerId = addDefault(programPath, trackPath, {
      nameHint: programPath.scope.generateUid("tracker"),
    }).name;
  }

  return importTrackerId;
};

module.exports = declare((api, options) => {

  return {
    visitor: {
      "ArrowFunctionExpression|FunctionDeclaration|FunctionExpression|ClassMethod": {
        enter(path, state) {
          let nodeComments = path;
          if (path.isExpression()) {
            nodeComments = path.parentPath.parentPath;
          }
          // 获取leadingComments
          const leadingComments = nodeComments.get("leadingComments");
          const paramCommentPath = hasTrackerComments(leadingComments,options.commentsTrack);

          // 如果有注释,就插入函数
          if (paramCommentPath) {
            //add Import
            const programPath = path.hub.file.path;
            const importId = checkImport(programPath, options.trackerPath);
            state.importTackerId = importId;

            const param = getParamsFromComment(paramCommentPath, options);
            insertTrackerBeforeReturn(path, param, state);
          }
        },
      },
    },
  };
});

参数放在局部作用域中

        这个功能的关键就是读取当前作用域中的变量。在写代码之前,来定一个前提:当前作用域的变量名也和注释中参数标识符一致,也是_trackerParam

准备源代码

import "./index.css";

//##箭头函数
//_tracker,_trackerParam={name, age:18}
const test1 = () => {
	const name = "gongfu2";
};

const test1_2 = () => {};

//函数表达式
//_tracker
const test2 = function () {
	const age = 1;
	_trackerParam = {
		name: "gongfu3",
		age,
	};
};

const test2_1 = function () {
	const age = 2;
	_trackerParam = {
		name: "gongfu4",
		age,
	};
};

        代码中,准备了函数test2test2_1。其中都有_trackerParam作为局部变量,但test2_1没有注释//_tracker

编写插件

if (paramCommentPath) {
  //add Import
  const programPath = path.hub.file.path;
  const importId = checkImport(programPath, options.trackerPath);
  state.importTackerId = importId;

  //check if have tackerParam
  const hasTrackParam = path.scope.hasBinding(options.commentParam);
  if (hasTrackParam) {
    insertTrackerBeforeReturn(path, options.commentParam, state);
    return;
  }

  const param = getParamsFromComment(paramCommentPath, options);
  insertTrackerBeforeReturn(path, param, state);
}

        这个函数的逻辑是先判断当前作用域中是否有变量_trackerParam,有的话,就获取该声明变量的初始值。然后将该变量名作为insertTrackerBeforeReturn的参数传入其中。

运行下代码

我们运行下代码看看

image.png

运行结果符合预期,很好

完整代码

const { declare } = require("@babel/helper-plugin-utils");
const { addDefault } = require("@babel/helper-module-imports");
const { template } = require("@babel/core");
//judge if there are trackComments in leadingComments
const hasTrackerComments = (leadingComments, comments) => {
  if (!leadingComments) {
    return false;
  }
  if (Array.isArray(leadingComments)) {
    const res = leadingComments.filter((item) => {
      return item.node.value.includes(comments);
    });
    return res[0] || null;
  }
  return null;
};

const getParamsFromComment = (commentNode, options) => {
  const commentStr = commentNode.node.value;
  if (commentStr.indexOf(options.commentParam) === -1) {
    return null;
  }

  try {
    return commentStr.slice(commentStr.indexOf("{"), commentStr.indexOf("}") + 1);
  } catch {
    return null;
  }
};

const insertTrackerBeforeReturn = (path, param, state) => {
  //blockStatement
  const bodyPath = path.get("body");
  let ast = template.statement(`${state.importTackerId}(${param});`)();
  if (param === null) {
    ast = template.statement(`${state.importTackerId}();`)();
  }
  if (bodyPath.isBlockStatement()) {
    //get returnStatement, by body of blockStatement
    const returnPath = bodyPath.get("body").slice(-1)[0];
    if (returnPath && returnPath.isReturnStatement()) {
      returnPath.insertBefore(ast);
    } else {
      bodyPath.node.body.push(ast);
    }
  } else {
    ast = template.statement(`{${state.importTackerId}(${param}); return BODY;}`)({BODY: bodyPath.node });
    bodyPath.replaceWith(ast);
  }
};


const checkImport = (programPath, trackPath) => {
  let importTrackerId = "";
  programPath.traverse({
    ImportDeclaration(path) {
      const sourceValue = path.get("source").node.value;
      if (sourceValue === trackPath) {
        const specifiers = path.get("specifiers.0");
        importTrackerId = specifiers.get("local").toString();
        path.stop();
      }
    },
  });

  if (!importTrackerId) {
    importTrackerId = addDefault(programPath, trackPath, {
      nameHint: programPath.scope.generateUid("tracker"),
    }).name;
  }

  return importTrackerId;
};

module.exports = declare((api, options) => {

  return {
    visitor: {
      "ArrowFunctionExpression|FunctionDeclaration|FunctionExpression|ClassMethod": {
        enter(path, state) {
          let nodeComments = path;
          if (path.isExpression()) {
            nodeComments = path.parentPath.parentPath;
          }
          // 获取leadingComments
          const leadingComments = nodeComments.get("leadingComments");
          const paramCommentPath = hasTrackerComments(leadingComments,options.commentsTrack);

          // 如果有注释,就插入函数
          if (paramCommentPath) {
            //add Import
            const programPath = path.hub.file.path;
            const importId = checkImport(programPath, options.trackerPath);
            state.importTackerId = importId;

            //check if have tackerParam
            const hasTrackParam = path.scope.hasBinding(options.commentParam);
            if (hasTrackParam) {
              insertTrackerBeforeReturn(path, options.commentParam, state);
              return;
            }

            const param = getParamsFromComment(paramCommentPath, options);
            insertTrackerBeforeReturn(path, param, state);
          }
        },
      },
    },
  };
});

总结

        这篇文章讲了如何给埋点的函数添加参数,参数可以写在注释里,也可以写在布局作用域中。支持动态传递,非常灵活。感兴趣的同学们可以拷一份代码下来跑一跑,相信你们会很有成就感的。

下篇文章来讲讲如何在create-reate-app中使用我们手写的babel插件。

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

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

相关文章

基于IMX6ULL的AP3216C的QT动态数据曲线图显示

前言:本文为手把手教学 LinuxQT 的典型基础项目 AP3216C 的数据折线图显示,项目使用正点原子的 IMX6ULL 阿尔法( Cortex-A7 系列)开发板。项目需要实现 AP3216C 在 Linux 系统下的驱动,使用 QT 设计 AP3216C 的数据显示页面作为项目的应用层。…

消息中间件RabbitMQ简介

1.1消息队列中间件简介 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题实现高性能,高可用,可伸缩和最终一致性[架构] 使用较多的消息队列有ActiveMQ,RabbitMQ&#xff…

人工智能安全风险:零信任的作用

人工智能(AI)和机器学习技术飞速发展,我们所处的时代正在经历前所未有的创新。但是,技术飞速发展的同时也带来了各种挑战。人工智能技术越来越复杂,与之相关的网络安全风险也越来越棘手,随之产生了一个新的…

TortoiseGit 入门指南10:贮藏

有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, 而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是贮藏 …

【Linux指令集】---unzip指令(超详细)

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【Linux专栏】🎈 本专栏旨在分享学习Linux的一点学习心得,欢迎大家在评论区讨论💌 演示环境&#xff1…

JVM系统优化实践(19):GC生产环境案例(二)

您好,这里是「码农镖局」CSDN博客,欢迎您来,欢迎您再来~ 接昨天的问题继续来说,在高并发场景中,对象过多容易导致OOM。由于高并发导致Young GC存活对象过多,因此会有太多对象进入老年代&#xf…

关于unity Content Size Fitter 套 Content Size Fitter

首先:最好不要unity Content Size Fitter 套 Content Size Fitter 这样最后得到的变化可能会错误 unity也提示了,父物体如果有了,那么子物体就不要再加了。 但是你们要的需求: 一级父物体 ➡自适应大小➡二级父物体&#xff08…

经典目标检测R-CNN系列(2)Fast R-CNN

经典目标检测R-CNN系列(2)Fast R-CNN Fast R-CNN是作者Ross Girshick继R-CNN后的又一力作。 同样使用VGG16作为网络的backbone,与R-CNN相比训练时间快9倍,测试推理时间快213倍,准确率从62%提升至66%(Pascal VOC数据集上)。 1 Fast R-CNN的…

Java List 与数组互转

前言 略 数组转 List 方法1 Employee[] array new Employee[]{emp1, emp2, emp3}; List<Employee> list Arrays.asList(array);Arrays.asList 返回的是固定长度的数组&#xff0c;扩大或缩小列表的操作将返回UnsupportedOperationException。 数组转 List 方法2 E…

【C++STL】“vector“容器的模拟实现

vector的模拟实现 模拟实现成员变量构造函数无参构造函数初始化n个val的构造函数迭代器区间构造函数 拷贝构造析构函数begin&#xff08;&#xff09;end&#xff08;&#xff09;swap&#xff08;&#xff09;reserve()resize()capacity()size()重载[]运算符重载赋值运算符ins…

自学网络安全(黑客)为什么火了?

网安专业从始至终都是需要学习的&#xff0c;大学是无法培养出合格的网安人才的。这就是为啥每年网安专业毕业生并不少&#xff0c;而真正从事网安岗位的人&#xff0c;寥寥无几的根本原因。 如果将来打算从事网安岗位&#xff0c;那么不断学习是你唯一的途径。 网络安全为什…

应对 618、双十一等大促期间的高负载,API 性能测试应该怎么做?负载测试、基线测试、冒烟测试、浸泡测试、峰值测试和尖峰测试详解

随着应用程序和服务交付速度的不断提高&#xff0c;在按时交付应用程序的竞赛中&#xff0c;性能测试往往会退居其次。但是&#xff0c;在节假日期间&#xff0c;购物额都会大幅增长。在这种一年中的成败时刻&#xff0c;公司是无法接受他们的应用程序在高负载下变得不可靠的。…

第四章 云原生架构之Kubernetes基础知识

1、K8S整体架构 1.1、概述 ​ Kubernetes是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;简称 K8S。K8S的本质是一组服务器集群&#xff0c;可以在对应服务器集群的每个节点上运行程序&#xff0c;来对节点中的容器进行管理。类似Mas…

Pytest+Jenkins+Allure的接口自动化测试

目录 生成Allure 两种形式 一 项目内直接生成不依赖Jenkins 1.先安装好allure 将allure\bin配置到环境变量中 cmd 命令行输入&#xff1a;allure 校验是否安装成功 2. 将json文件生成html文件 执行 allure generate report/ -o report/html其中的report/ 为生成的json路径&a…

OCPM和CPM有什么区别?

CPM和OCPM这两种收费模式的对比 Cpm&#xff1a;表示千次展示费用&#xff0c;是数据指标&#xff0c;也是一种出价方式。代表展现一千次的消费&#xff0c;也就是你展现1000次要给媒体多少钱 例如某企业广告曝光量是50万&#xff0c;总广告价格为10000元&#xff0c;那么千人…

matplotlib 笔记:marker 款式

1 ec 边缘颜色 marker 边缘的颜色 import numpy as np import matplotlib.pyplot as pltxnp.linspace(0,10) ynp.sin(x)1.5 plt.figure(figsize(10,10)) plt.scatter(x,y,ecC9) plt.show() 2 fc 填充颜色 face color 填充颜色 3 lw 边缘宽度 4 s 点的大小 5 marker 点款式 i…

设计模式 ~ 工厂模式

工厂模式 工厂模式是一种设计模式&#xff0c;指在通过使用工厂函数或构造函数来创建对象&#xff1b; 它提供了一种灵活的方式来创建对象实例&#xff0c;而无需直接调用构造函数或使用new关键字&#xff1b; 可以分类&#xff0c;解耦&#xff1b; 可以扩展多个类&#xff0…

Baidu——基于大模型的优质Prompt开发课-写代码

软件开发产业趋势与技术革新 大模型驱动的软件开发 代码辅助开发模型 实际操作 你是一名非常专业的产品经理&#xff0c;请问如果我要做一个图片字符画的工具的调研&#xff0c;需要哪些步骤 你是一名编程大牛&#xff0c;目前我想做一个图像字符画的工具&#xff0c;这个工具要…

前缀、中缀、后缀表达式及简易运算实现总结

title: 前缀、中缀、后缀表达式及简易运算实现总结 date: 2023-06-30 10:25:50 tags: 表达式 categories:开发知识及其他 cover: https://cover.png feature: false 1. 概念 1.1 什么是前缀、中缀、后缀表达式&#xff1f; 前缀表达式&#xff1a;又称波兰式&#xff08;Pol…

代码随想录day5 | 242.有效的字母异位词 349. 两个数组的交集 202.快乐数

文章目录 一、有效的字母异位词二、两个数组的交集三、快乐数 一、有效的字母异位词 242.有效的字母异位词 代码随想录知识点 哈西法可以选取的三种数据结构&#xff1a; 数组setmap class Solution { public:bool isAnagram(string s, string t){int hash[26] {0};// 1f…