当前位置: 首页 > news >正文

计算机是怎么读懂C语言的?

文章目录

  • 前言
  • 程序环境
  • 翻译环境
    • 翻译环境分类
      • 编译
        • 预处理
          • 预处理符号
            • 预定义符号
            • #define
            • #undef
          • 命令行定义
          • 条件编译
          • 文件包含
            • 头文件包含查找规则
            • 嵌套文件包含
          • 其他预处理指令
        • 编译阶段
        • 汇编
      • 链接

🎉welcome🎉
✒️博主介绍:博主大一智能制造在读,热爱C/C++,会不定期更新系统、语法、算法、硬件的相关博客,浅浅期待下一次更新吧!
😘博客制作不易,👍点赞+⭐收藏+➕关注

前言

  • 在我之前的一篇文章中,写到了目前主流语言的优缺点,那其实对于语言来说,剖析到最底层,都是二进制,只是语法不同,那计算机是怎么区分语言,在程序写好到结束中间,发生了哪些事情?本篇文章从C语言角度出发,剖析一下从写好程序到运行发生了哪些事情。

程序环境

  • 在ANSI C下的任何程序当中,都有两种不同的环境:
  1. 翻译环境:这个环境当中可以将程序的源代码转换成可执行的机器指令。
  2. 执行环境:它用于实际执行代码。

翻译环境

  • 翻译环境可以将程序翻译成可执行的机器指令,对于其他语言也是这样,只有翻译成可执行的机器指令,计算机才可以识别,那C语言的翻译阶段是这样的:

在这里插入图片描述

  • 在C语言中,翻译的大致过程就如上图所示,在程序写好进行编译的时候,会编译器(集成开发环境)会对每个程序文件(.c)单独进行翻译,翻译成一个目标文件(在windows环境下面后缀是.obj),每个目标文件都进过链接器,链接器外部接链接库,通过链接库和链接器生成一个可执行程序(.exe)。

翻译环境分类

  • 翻译环境大致分为编译和链接两个阶段

编译

  • 编译时翻译环境最开始的阶段,他分成三个步骤:
  1. 预编译(预处理)
  2. 编译(处理)
  3. 汇编

预处理

  • 预处理阶段会进行对头文件的包含,对于用#define定义的符号进行替换和删除,还有注释的删除,和文本操作,其中#define定义和头文件的包含都用到了预处理符号。
预处理符号
预定义符号
  • 预定义符号是语言内置的符号,有以下几种:
__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
#define
  • #define在前面学习常量的时候是有进行简略的介绍的,用#define定义的标识符常量,但是#define是不仅仅可以定义标识符常量的,还可以定义一些宏,那这些宏具体可以干什么呢?可以把他理解为另类的函数,宏的定义方法如下
#define name(parament-list) stuff;
  • 其中name如何函数命一样,parament-list是一个符号表,可以理解为函数的参数,stuff可以理解为要实际做的事情,符号表内的符号会出现在stuff里面,对于宏来说,他实际是把name(parament-list)进行替换,替换成后面的是stuff,可以用代码进行验证一下:
#include<stdio.h>#define ADD(x,y) x+yint main()
{printf("%d", 3 * ADD(3, 4));return 0;
}

在这里插入图片描述

  • 表达式的结果式13,但是按照猜想得到的结果应该是21,3+4=7,在和3相乘,那13怎么得到的?上面说到,宏是进行的替换,将后面的3+4替换下来,那这个表达式实际上是3*3+4,就是9+4,那就是13,那可不可以让3+4先算在乘3呢?只需要加括号,对于宏来说,不要吝啬括号,那对上面代码进行修改:
#include<stdio.h>#define ADD(x,y) (x+y)int main()
{printf("%d", 3 * ADD(3, 4));return 0;
}

在这里插入图片描述

  • 那如果现在是一个乘法的宏呢?
#include<stdio.h>#define MUL(x,y) (x*y)int main()
{printf("%d", 3 * MUL(3+3, 4+4));return 0;
}

在这里插入图片描述

  • 和我们要得到的结果是不一样的,我们想要得到的是144,但是得到的是57,那我将内容替换到程序当中3+34+4,也就是3+12+4,
    得出19,19
    3,得到57,但是我们想要的是先相加在相乘的,那就还需要加括号,如下所示:
#include<stdio.h>#define MUL(x,y) ((x)*(y))int main()
{printf("%d", 3 * MUL(3+3, 4+4));return 0;
}

在这里插入图片描述

  • 这样替换下来的就是(3+3)*(4+4),结果是48,和我们想要得到的结果是一样, 所以对于宏而言,不需要吝啬括号。

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

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

#和##的作用

  • u在正常使用printf打印的时候可以将不用%s打印两个字符串吗?是可以的:
#include<stdio.h>
int main()
{printf("这个数字是:""%d", 10);return 0;
}

在这里插入图片描述

  • 那在宏中可以吗?也是可以的:
#include<stdio.h>
#define PRINT(A ,B) printf("数字是"A"\n",B);
int main()
{PRINT("%d",10);return 0;
}

在这里插入图片描述

  • 但是这样只是对于字符串是参数的时候才能将字符串放进去,还有一种方法,就是利用#,它的作用是将一个宏参数变成字符串,如果现在要计算一个加法表达式的结果,就可以用这个来更直观的表达:
#include<stdio.h>
#define PRINT(A ,B) printf(#B"的结果是"A"\n",B);int main()
{PRINT("%d",1+2);return 0;
}

在这里插入图片描述

  • ##的作用
    ##可以把位于它两边的符号合成一个符号。
    它允许宏定义从分离的文本片段创建标识符。
#include<stdio.h>
#define _ADD(A ,B) num##A+=B;int main()
{int num5 = 5;_ADD(5, 10);printf("%d", num5);return 0;
}

在这里插入图片描述
宏的副作用

  • 对于宏来说,有些是有副作用的,比如++操作符,看下面的代码:
#include<stdio.h>#define MAX(A,B) ((A)>(B)?(A):(B))int main()
{int a = 1;int b = 2;int c = MAX(a++, b++);printf("a= %d b= %d c=%d", a, b, c);return 0;
}
  • 结果是什么?按照猜想的结果,a++是2,b++是3,然后比大小赋给c,3比2大,所以b++在执行一次,是4,那现在a是2,b是4,c也是4,结果是正确的吗?运行起来看看:

在这里插入图片描述

  • c是3,这就是因为,宏本质上还是替换,赋值给c的是((a++)>(b++)?(a++):(b++));这个表达式的结果是b++,而b++是先进行了一次++,得到3,表达式结果还是b++,但是是后置++,那就是先使用在++,那就是先赋值3,在进行++。

宏对比函数的优缺点

  • 那宏和函数都可以实现某种功能,那他们有什么区别吗?就是单纯的书写格式不一样吗?不仅仅是这样,宏的优点在于宏的速度是优于函数的,并且对于宏,是不需要去定义类型的,那宏就没有缺点吗?有,当我们使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度,而且宏是没法调试的,我们是不能直接进入宏调试的,因为宏是替换到程序当中,编译器是认得,但是我们是不知道内部有无问题的,而且上面说到宏没有定义类型,也就不够严谨,并且宏可能会带来运算符优先级的问题,导致我们想的和实际跑出来的内容不一样。

宏的命名

  • 对于宏的命名而言,通常是全部大写的,这也是一个约定俗成的东西,而函数的命名就通常不是大写的,这也可以让其他程序员在看程序的时候,一眼看出来哪个是宏哪个是函数。
#undef
  • 如果在写程序的时候,宏目前的功能不满足当前的程序或者不满足当前我们想要得到的效果,但是我们知道,直接修改宏内代码是个不太好的习惯,那有没有办法可以不动我们程序内本身就有的东西,然后修改掉宏实现的内容呢?这里就有一个新的操作符——#undef,它的作用并不是修改一个宏,而是移除,那怎么去使用的?它的语法格式是这样的:
  • #undef NAME
  • 那现在可以使用一下,看看#undef的功能是不是和我说的是一样的:
#include<stdio.h>#define MAX(A,B) ((A)>(B)?(A):(B))
#undef MAXint main()
{int a = 1;int b = 2;int c = MAX(a++, b++);printf("a= %d b= %d c=%d", a, b, c);return 0;
}

在这里插入图片描述

  • 可以看到是有报错的,那现在在编译器眼中,就没有了MAX这个宏,这个时候就可以在写一个MAX的宏来实现我们现在想要实现的内容了。
#include<stdio.h>#define MAX(A,B) ((A)>(B)?(A):(B))
#undef MAX
#define MAX(A,B) ((A)<(B)?(B):(A))int main()
{int a = 1;int b = 2;int c = MAX(a++, b++);printf("a= %d b= %d c=%d", a, b, c);return 0;
}

在这里插入图片描述

命令行定义
  • C语言的大部分编译器都提供了一种功能,允许在命令行当中定义符号,用于编译过程的程序,通常是在Linux环境下使用,在windows环境下一般是不可以的,那这个是什么意思呢?举个例子:
#include<stdio.h>
int main()
{int arr[SIZE];int i=0;for(i=0;i<SIZE;i++){arr[i]=i;}for(i=0;i<SIZE;i++){printf("%d" ,arr[i]);}printf("\n");return 0;
}
  • 可以看到上面的程序当中有一个没有初始化和定义的变量SIZE,那程序正常情况下是跑不起来的,是会报错的,那应该怎么在没有修改程序的情况下,让它跑起来,在Linux环境下面,可以在命令行规定,规定格式是这样的:
gcc -D SIZE=10 test.c
  • 这样编译器就会知道SIZE是10,程序就可以正常运行起来。
条件编译
  • 在编译一个程序的时候,当我们有部分语句不需要的时候,可以进行注释,那有没有其他方法呢?有,可以用条件编译指令,那条件编译指令具体怎么使用,往下看:
  • 在我们的程序当中,会有一些调试性的代码,这个时候,这些代码就和鸡翅一样,食之无味,弃之可惜,那现在就可以使用选择性的编译:
#include<stdio.h>#define _ DEBUG _int main()
{int i = 0;int arr[10] = { 0 };for(i = 0; i<10;i++){arr[i] = i;printf("%d", arr[i]);//为了知道上面的数是否放到数组里面}return 0;
}
  • 可以看到上面代码中,for循环内的printf是用于验证我们的数字成功放到了数组当中没有,那现在我们知道放成功了,不需要它了,但是又不想删除和注释,这个时候使用条件编译指令当中的一个:
#include<stdio.h>#define _ DEBUG _int main()
{int i = 0;int arr[10] = { 0 };for(i = 0; i<10;i++){arr[i] = i;#ifdef _ DEBUF _//如果_ DEBUF _为真,进入到里面执行语句printf("%d", arr[i]);//为了知道上面的数是否放到数组里面#endif//结束标志}return 0;
}
  • 那这样写,对于_ DEBUF _,它的值是为假的,那就不会走ifdef,那就不会运行printf,这样就实现了我们不想删除也不想注释,但是让程序不跑某些代码的功能了,常见条件编译指令还有很有:
//1
#if  //常量表达式
//...
#endif
//这里的常量表达式由预处理求值
//比如上面程序当中的_ DEBUG _,编译器会知道是什么,但是必须要提前声明
//2.多个分支的条件编译
#if //常量表达式
//...
#elif //常量表达式
//...
#else
//...
#endif
//3.判断是否被定义
#if defuned(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol
//4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPION2msdos_version_option2();#ednif
#endif
文件包含
  • 我们知道#include是用于包含头文件的,在进入这个头文件的时候,就对头文件指向的文件进行了编译,这个时候在编译器眼里#include的地方就是包含的头文件的内容,所以,我们包含了多少次头文件,就会替换多少次,所以我们包含了多少次头文件,就编译了多少次头文件,所以最好不要重复包含同一个头文件很多次
头文件包含查找规则
  • 在包含头文件的时候,我们发现,有两种包含方式,一种是<>,另一种是"",那它们两个包含方式有什么区别呢?
  • 对于""而言,它会先在源文件所在目录下查找,如果没有找到这个头文件,编译器就像查找库函数头文件一样在标准位置查找头文件,如果没有在标准位置查找到,就提示编译错误,而标准位置就在编译器的下载目录当中,比如我用的vs2020,那标准位置就在我的vs2020的默认下载路径。
  • 而<>,会直接在标准位置查找,找不到就报错,对于库函数的头文件,也可以用""来引,但是就会降低效率,因为本来就存放在标准位置,直接就可以查,但是用“”先查本地,相比较就会慢一点,而且也不容易区分本地的头文件和库函数的头文件,所以对于头文件用什么引,要根据情况而定。
嵌套文件包含

在这里插入图片描述

  • 如上图所示,comn.h和comn.c是公共模板,test1.h和test1.c使用了公共模块,test2.h和test2.c也使用了公共模块,test.h和test.c使用了test1模块和test2的模块。
  • 那这样test是不是同时包含了两份comn.h的内容,那就重复包含了,那有没有什么方法解决这个问题呢?使用条件编译,在每个头文件最前面加上条件编译指令,如下所示:
#ifdef _TEST_H_
#define 
//头文件的包含
#endif
  • 或者:
#pragma once
  • 这样可以避免掉我们的头文件重复包含
其他预处理指令
  • 还有一些预处理指令:
#error
#pragma
#line
...

编译阶段

  • 预处理结束后,就要开始我们的编译阶段,编译阶段会生成一个test.s的文件,这个文件里面会放我们程序转成的汇编代码,编译器通常会在这个阶段进行语法分析、词法分析、语义分析,符号汇总,那前面三个分析的作用可以猜到,是将我们的C语言程序进行分析然后转换成汇编代码,那符号汇总是什么?它会干些什么事?符号汇总其实就是将我们的全局变量和全局函数进行汇总,然后对其进行汇编,然后就会到汇编这一步。

汇编

  • 汇编阶段会在编译阶段之后进行,它会生成一个.obj,.obj是在windows环境下生成的,在Linux环境下生成.o文件,那这些文件内容是什么?里面全是二进制指令,还会生成符号表,符号表就和编译阶段的符号汇总有关,那我们的.obj或者.o文件是二进制指令,能不能打开看看呢?我们是直接打不开的,可以使用一个工具readelf,可以打开文件看到这些二进制指令;那符号表是什么呢?符号表就是用于判断在编译过程中是否有使用符号。

链接

  • 链接阶段是链接库和我们的生成的.obj或者.o文件生成一个.exe文件,这个文件可以执行,能看到我们程序实际跑出的结果是什么。

相关文章:

计算机是怎么读懂C语言的?

文章目录前言程序环境翻译环境翻译环境分类编译预处理预处理符号预定义符号#define#undef命令行定义条件编译文件包含头文件包含查找规则嵌套文件包含其他预处理指令编译阶段汇编链接&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;博主大一智能制造在读&#xff…...

hadoop入门介绍及各组件功能运行关系

文章目录Hadoop 组成部分1.HDFS2.MapReduce 架构概述3. yarn 架构概述4.HDFS、YARN、MapReduce三者关系Hadoop 组成部分 1.HDFS Hadoop Distributed File System&#xff0c;简称 HDFS&#xff0c;是一个分布式文件系统。 HDFS 架构概述 主要分为 NameNode (mn):存储文件的元…...

(HP)新手引导使用react-shepherd

1&#xff0c;官方参数文档&#xff1a;https://shepherdjs.dev/docs/tutorial-02-usage.html 2&#xff0c;基本代码 import { ShepherdTour } from react-shepherd; import ./index.less; // 自己的样式文件&#xff0c;用来修改样式 import ./shepherd.less; // 将shephe…...

数据结构:栈和队列(Leetcode20. 有效的括号+225. 用队列实现栈+232. 用栈实现队列)

目录 一.数据结构--栈 1.栈的基本介绍 2.栈的实现 二.数据结构--队列 1.队列的基本介绍 2.队列的实现 三.栈的运用(Leetcode20. 有效的括号225) 1.问题描述 2.问题分析 题解代码&#xff1a; 四.用两个队列实现栈(225. 用队列实现栈 - 力扣&#xff08;Leetcode&a…...

22.2.19周赛双周赛(贪心、记忆化搜索...)

文章目录双周赛98[6359. 替换一个数字后的最大差值](https://leetcode.cn/problems/maximum-difference-by-remapping-a-digit/)[6361. 修改两个元素的最小分数](https://leetcode.cn/problems/minimum-score-by-changing-two-elements/)贪心排序[6360. 最小无法得到的或值](ht…...

2023最新软件测试面试题(带答案)

1. 请自我介绍一下(需简单清楚的表述自已的基本情况&#xff0c;在这过程中要展现出自信&#xff0c;对工作有激情&#xff0c;上进&#xff0c;好学) 面试官您好&#xff0c;我叫###&#xff0c;今年26岁&#xff0c;来自江西九江&#xff0c;就读专业是电子商务&#xff0c;毕…...

【C++】类型转换方法

本篇博客让我们来见识一下C中新增的类型转换方法 文章目录1.C语言中类型转换2.C中的强制类型转换2.1 static_cast2.2 reinterpret_cast2.3 const_castvolatile关键字2.4 dynamic_cast3.C强制类型转换的作用4.RTTI1.C语言中类型转换 在C语言中&#xff0c;类型转换有下面两种形…...

100亿级订单怎么调度,来一个大厂的极品方案

背景 超时处理&#xff0c;是一个很有技术难度的问题。 所以很多的小伙伴&#xff0c;在写简历的时候&#xff0c;喜欢把这个技术难题写在简历里边&#xff0c; 体现自己高超的技术水平。 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;尼恩经常指导大家 优化简历。 最…...

C++性能白皮书

最近看完了《C性能白皮书》&#xff0c;这本书列出了一些性能优化的思路&#xff0c;不过只是一些指引&#xff0c;没有讲具体细节&#xff0c;我整理出了其中的关键点分享给大家&#xff1a; 硬件篇 作为一个程序员&#xff0c;想要性能优化&#xff0c;最好要了解些硬件&…...

华为OD机试 - 黑板上色 | 机试题算法思路 【2023】

最近更新的博客 华为OD机试 - 简易压缩算法(Python) | 机试题算法思路 【2023】 华为OD机试题 - 获取最大软件版本号(JavaScript) 华为OD机试 - 猜字谜(Python) | 机试题+算法思路 【2023】 华为OD机试 - 删除指定目录(Python) | 机试题算法思路 【2023】 华为OD机试 …...

如何在六秒内吸引观众的注意力

根据《2022国民专注力洞察报告》显示&#xff0c;当代人的连续专注时长&#xff0c;已经从2000年的12秒&#xff0c;下降到了现在的8秒。对于这个事实你可能难以相信&#xff0c;实际上这意味着&#xff0c;大多数互联网用户跳到一些页面上时&#xff0c;可能眼皮都不眨一下就离…...

FreeRTOS与UCOSIII任务状态对比

FreeRTOS任务状态 1、运行态 正在运行的任务&#xff0c;正在使用处理器的任务。 单核处理器中任何时候都有且只有一个任务处于运行态。 2、就绪态 已经准备就绪&#xff08;非阻塞或挂起&#xff09;&#xff0c;可以立即运行但还没有运行的任务。 正在等待比自己高优先级…...

小程序 npm sill idealTree buildDeps 安装一直没反应

目录 一、问题 二、解决 1、删除.npmsrc 、清除缓存 2、更换镜像源 3、最终检测 一、问题 记录&#xff1a;今天npm 一直安装不成功 显示&#xff1a;sill idealTree buildDeps 我的版本&#xff1a; 我百度到换镜像源安装方法&#xff0c;但我尝试后&#xff0c;依然…...

GPT系列详解:初代GPT

本文详细解读了OpenAI公司在2018年6月发布的论文《Improving Language Understanding by Generative Pre-Training》&#xff0c;它其中介绍的算法也就是后来人们说的GPT。本文借鉴了李沐的这个视频&#xff0c;感兴趣的同学可以移步观看大神的讲解。 目录引言GPT方法无监督预训…...

为什么要使用数据库

数据保存在内存优点&#xff1a;存取速度快缺点&#xff1a;数据不能永久保存数据保存在文件优点&#xff1a;数据永久保存缺点&#xff1a;1&#xff09;速度比内存操作慢&#xff0c;频繁的IO操作。2&#xff09;查询数据不方便数据保存在数据库1&#xff09;数据永久保存2&a…...

【单目标优化算法】海鸥优化算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

筑基六层 —— 整型提升及实用调式技巧

目录 一.修炼必备 二. 整型提升 三.实用调式技巧 一.修炼必备 1.入门必备&#xff1a;VS2019社区版&#xff0c;下载地址&#xff1a;Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com) 2.趁手武器&#xff1a;印象笔记/有道云笔记 3.修炼秘籍&…...

后端前端文件传输2中传出模式

base64文件传输 app.route(/download, methods[get]) def hello_as(): # 数据 id request.args.get("id") cur g.db.cursor() cur.execute(fselect name,grade,commentNum,cityName,sceneryThemeName from dataList where cityId? , (id,)) …...

【ZOJ 1067】Color Me Less 题解(vector+开方)

问题 颜色缩减是从一组离散颜色到较小颜色的映射。这个问题的解决方案需要在标准的24位RGB颜色空间中执行这样的映射。输入由十六个RGB颜色值的目标集合和要映射到目标集合中最接近的颜色的任意RGB颜色集合组成。为了我们的目的&#xff0c;RGB颜色被定义为有序三元组&#xff…...

凌恩生物经典文章:孟德尔诞辰200周年,Nature Genetics礼献豌豆高质量精细图谱

本期为大家分享的文章是2022年发表在《Nature Genetics》上的一篇文章“Improved pea reference genome and pan-genome highlight genomic features and evolutionary characteristics”&#xff0c;作者通过结合三代pacbio测序、染色体构象捕获&#xff08;Hi-C&#xff09;测…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...