源码学习初章-基础知识储备


文章目录

  • 学前准备
    • 源码地址
    • 引言
    • extern "C"
  • 宏定义
    • 平台宏
      • 跨平台宏
      • vstdio平台禁用警告宏
    • 连接、双层宏定义
    • 函数宏
      • 系统函数宏
      • 自定义函数宏
      • 多语句执行宏do while0
    • 普通宏定义
  • C的一些必备函数知识
    • 回调函数和函数指针
      • 回调函数wireshark-4.0.7源码例子
      • 函数指针wireshark4.0.7源码例子
  • 结构体和关键字
    • 结构体和联合体
      • 结构体 struct
      • 联合体 union
    • 关键字
      • static
      • extern
      • C++关键字 explicit
  • 标志位与位操作
    • & 、 |、<<、>>
    • 标志位(|加标志)(&减/检标志)
  • 代码习惯
    • if else
    • if else break
    • 函数定义
  • 其他内容后续补充


学前准备

源码地址

cJSON库地址
sqlite-github源码地址
sqlite官方网址
wireshark源码

引言

  • 引言参考的是sqlite和wireshark的部分代码,第二篇开始进入cJSON正式篇
  • 第一篇暂时不涉及github的源码,参考的是sqlite官网发布的shell.c sqlite3.h .c等文件进行简单的知识储备
  • 另外由于工作中多是linux环境所以把vstdio卸载了,但是为了在windows下学习sqlite源码所以我采用的是Qt5.14.1 MinGW32进行开发
  • 本文只会使用cpp的new和delete,其他语法将均为c的语法
  • 面向对象只是一种设计模式,如果你愿意,你可以用汇编来实现面向对象

extern “C”

  • 声明:
    这么做的目的是把c作为底层开发库,这样任何一个编程语言都可以调用c写的库了
  • 区别
    1、因为在 C 语言中,默认的函数调用约定是 C 调用约定(C calling convention),参数通过栈传递,由调用者清理栈。
    2、在 C++ 中,默认的函数调用约定是 C++ 调用约定(C++ calling convention),其实质是通过名称修饰(Name Mangling)来支持函数重载,参数传递方式和 C 调用约定类似,但由于函数名被修饰,因此 C++ 编译器能够识别重载的函数
  • 做法
    在头文件中 所有的 函数 结构体 变量 等能想到的所有内容都放在两个 "{}"中
#ifdef __cplusplus
extern "C" {
#endif
void fun1();
void fun2();
static struct Stu{
	int a;
	char b;
}stu{0,0};
int a=10;
#ifdef __cplusplus
}
#endif

宏定义

平台宏

跨平台宏

#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <windows.h>
#elif define(__linux__)
#include <socket.h>
#endif

总结:
- !defined(_CRT_SECURE_NO_WARNINGS)是为了防止C4996报错
- defined(_WIN32)和defined(WIN32)用来判断是否是windows,defined(WIN64)是在win系统基础上判断是不是win64位

vstdio平台禁用警告宏

#if defined(_MSC_VER)
#pragma warning(disable : 4054)// 禁用类型转换警告。此警告提示可能存在的类型转换问题。
#pragma warning(disable : 4055)//禁用类型转换警告。此警告提示可能存在的类型转换问题。
#pragma warning(disable : 4100)//禁用未使用的形参警告。此警告提示声明了但未在函数体内使用的形式参数。
//等
#endif /* defined(_MSC_VER) */

连接、双层宏定义

#include <stdio.h>

// 定义连接运算符宏
#define CONCAT(x, y) x##y

//双层宏定义
/*
 * 双重宏的第一个宏用于接收参数,并进行预处理操作。
 * 第二个宏则负责将经过预处理的参数传递给其他宏或函数,或者用于宏展开后的标识符构造
 * */
// 第一个宏,用于将宏参数转换为字符串常量
#define SHELL_STRINGIFY_(f) #f
// 第二个宏,用于传递参数给第一个宏进行处理
#define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f)

int main() {
    // 使用连接运算符宏
    int CONCAT(number, 1) = 42; // 等同于 int number1 = 42;
    printf("Value: %d\n", number1);
    // 注意:下面这个例子在预处理阶段将连接运算符宏展开为"Hello",而不是一个新的标识符
    printf("%s\n", "Hello" "World");
    printf("The value is: %s\n", SHELL_STRINGIFY(number1));//使用双层宏定义
    return 0;
}

在这里插入图片描述

函数宏

系统函数宏

将系统函数变成我们自己的宏即可省去后期我们大量调用时的麻烦
当然也可以自己写个函数通过宏实现

# define GETPID (int)GetCurrentProcessId//获取当前进程函数
#include <stdio.h>
#include <windows.h>

// 定义宏,用于获取当前进程ID
#define GETPID (int)GetCurrentProcessId

// 定义宏,用于检查给定的字符是否为空白字符(包括空格、制表符、换行符等)
#define IsSpace(X)  isspace((unsigned char)X)

// 定义宏,用于检查给定的字符是否为十进制数字字符(0-9)
#define IsDigit(X)  isdigit((unsigned char)X)

// 定义宏,用于将给定的字符转换为小写字符
#define ToLower(X)  (char)tolower((unsigned char)X)

// 定义宏,用于将给定的字符转换为大写字符
#define ToUpper(X)  (char)toupper((unsigned char)X)

int main() {
    // 使用宏来获取当前进程ID
    int pid = GETPID();

    // 输出当前进程ID
    printf("Current process ID: %d\n", pid);

    // 另外,我们也可以直接调用 GetCurrentProcessId 函数来获取进程ID
    int pid_direct = GetCurrentProcessId();
    printf("Current process ID (direct call): %d\n", pid_direct);

    // 测试字符处理的宏
    char ch = 'a';

    // 使用 IsSpace 宏来检查字符 ch 是否为空白字符
    if (IsSpace(ch)) {
        printf("The character is a whitespace character.\n");
    } else {
        printf("The character is not a whitespace character.\n");
    }

    // 使用 IsDigit 宏来检查字符 ch 是否为数字字符
    if (IsDigit(ch)) {
        printf("The character is a digit.\n");
    } else {
        printf("The character is not a digit.\n");
    }

    // 使用 ToLower 宏将字符 ch 转换为小写形式,并输出转换后的字符
    printf("The lowercase of the character is: %c\n", ToLower(ch));

    // 使用 ToUpper 宏将字符 ch 转换为大写形式,并输出转换后的字符
    printf("The uppercase of the character is: %c\n", ToUpper(ch));

    return 0;
}

在这里插入图片描述

自定义函数宏

//宏定义自己的函数
//注意显示转换不是必须的,但写上更加直观
#include <iostream>
using namespace std;

#define MIN(x,y) (int)min((int)x, (int)y)
int min(int a,int b) {
    int ret = a<b?a:b;
    cout<<"min is "<<ret<<endl;
    return ret;
}

int main()
{
   cout<<MIN(10,5);

   return 0;
}

在这里插入图片描述

多语句执行宏do while0

# define CONTINUE_PROMPT_RESET \
  do {setLexemeOpen(&dynPrompt,0,0); trackParenLevel(&dynPrompt,0);} while(0)
  • 当有多条语句要执行时需要用{}将函数括起来,而用{}需要用do while循环好处:
    1、使宏的用法更像函数调用,增加了代码的可读性和可维护性
    2、可以避免一些编译器警告,因为使用了多条语句,编译器会认为这是一个复合语句
    3、do-while(0) 循环体会执行一次,因为条件永远为真。这并不会引起运行时额外的开销,因为优化后的编译器会将其优化掉
  • 重点:宏中需要一行写完,如果分行需要用 \ 进行换行
#include <stdio.h>
// 假设这是全局变量,实际情况下可能是其他类型的全局变量
int dynPrompt;

// 假设这是虚拟的函数,实际情况下可能是其他具体的函数
void setLexemeOpen(int* prompt, int arg1, int arg2) {
    // 这里省略函数的具体实现
    dynPrompt = -1;
    printf("setLexemeOpen called with arguments: %d, %d\n", arg1, arg2);
}

void trackParenLevel(int* prompt, int arg) {
    // 这里省略函数的具体实现
    printf("trackParenLevel called with argument: %d\n", arg);
}

// 定义宏
#define CONTINUE_PROMPT_RESET \
    do { \
        setLexemeOpen(&dynPrompt, 0, 0); \
        trackParenLevel(&dynPrompt, 0); \
    } while(0)

int main() {
    printf("Before calling CONTINUE_PROMPT_RESET:\n");
    printf("dynPrompt = %d\n", dynPrompt);

    // 使用宏 CONTINUE_PROMPT_RESET
    CONTINUE_PROMPT_RESET;

    printf("\nAfter calling CONTINUE_PROMPT_RESET:\n");
    printf("dynPrompt = %d\n", dynPrompt);

    return 0;
}

在这里插入图片描述

# define CONTINUE_PROMPT_AWAITS(p,s) \
  if(p && stdin_is_interactive) setLexemeOpen(p, s, 0)
  //这个 if 是条件语句(单个条件语句),所以不用 do while0

普通宏定义

  • 宏代表的是指针
    #define CONTINUE_PROMPT_PSTATE_PTR (&dynPrompt)
  • 宏代表的是变量
    #define CONTINUE_PROMPT_PSTATE_VALUE dynPrompt
  • 代表的是具体的常数
    #define CONTINUE_PROMPT_PSTATE_CONSTANT 42
#include <stdio.h>

// 假设这是全局变量,实际情况下可能是其他类型的全局变量
int dynPrompt = 42;

// 定义三个不同的宏
#define CONTINUE_PROMPT_PSTATE_PTR (&dynPrompt)
#define CONTINUE_PROMPT_PSTATE_VALUE dynPrompt
#define CONTINUE_PROMPT_PSTATE_CONSTANT 42

int main() {
    int *ptr = CONTINUE_PROMPT_PSTATE_PTR; // ptr 指向 dynPrompt 的地址
    int value = CONTINUE_PROMPT_PSTATE_VALUE; // value 的值是 dynPrompt 的值,即 42
    int constant = CONTINUE_PROMPT_PSTATE_CONSTANT; // constant 的值是 42

    printf("dynPrompt: %d\n", dynPrompt);
    printf("Address of dynPrompt: %p\n", &dynPrompt);
    printf("ptr: %p\n", ptr);
    printf("value: %d\n", value);
    printf("constant: %d\n", constant);

    return 0;
}

在这里插入图片描述

C的一些必备函数知识

回调函数和函数指针

#include <iostream>
using namespace std;
//函数指针
void (*funPtr)(int a,int b);
//下面的 max和min会赋值给函数指针
void max(int a,int b){
    int ret = a>b?a:b;
    cout<<"max is "<<ret<<endl;
}
void min(int a,int b) {
    int ret = a<b?a:b;
    cout<<"min is "<<ret<<endl;
}
//回调函数
typedef void (*callBack)(int a,int b);//回调函数比函数指针多了一个typedef
void sameFun(int a, int b , callBack fun){
    fun(a,b);
}
int main()
{
   cout << "Hello World" << endl;
   funPtr = max;
   funPtr(10,5);
   funPtr = min;
   funPtr(10,5);

    sameFun(10,5,max);
    sameFun(10,5,min);
   return 0;
}

在这里插入图片描述

回调函数wireshark-4.0.7源码例子

packet.h:76
typedef int (*dissector_t)(tvbuff_t *, packet_info *, proto_tree *, void *);//回调函数声明

packet.c:3310
/* Register a new dissector by name. */
dissector_handle_t
register_dissector(const char *name, dissector_t dissector, const int proto)
{
	struct dissector_handle *handle;

	handle = new_dissector_handle(DISSECTOR_TYPE_SIMPLE, dissector, proto, name, NULL, NULL);

	return register_dissector_handle(name, handle);
}

packet-ssh.c:4799
 ssh_handle = register_dissector("ssh", dissect_ssh, proto_ssh);

packet-ssh.c:676
 static int
dissect_ssh(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
/*这里面是ssh解析的具体代码,不做展示*/
}

函数指针wireshark4.0.7源码例子

packet-ssh.c:185
truct ssh_flow_data {
    guint   version;

    gchar*  kex;
    int   (*kex_specific_dissector)(guint8 msg_code, tvbuff_t *tvb,
            packet_info *pinfo, int offset, proto_tree *tree,
            struct ssh_flow_data *global_data, guint *seq_num);//函数指针声明

    /* [0] is client's, [1] is server's */
#define CLIENT_PEER_DATA 0
#define SERVER_PEER_DATA 1
/*后续代码略*/
}
//---------------------------------------------------------------------------------
packet-ssh.c:1702
static void ssh_set_kex_specific_dissector(struct ssh_flow_data *global_data)
{
    const char *kex_name = global_data->kex;

    if (!kex_name) return;

    if (strcmp(kex_name, "diffie-hellman-group-exchange-sha1") == 0 ||
        strcmp(kex_name, "diffie-hellman-group-exchange-sha256") == 0)
    {
        global_data->kex_specific_dissector = ssh_dissect_kex_dh_gex;//函数指针赋值
    }
    /*dh group 和 ecdh 略*/
}
//---------------------------------------------------------------------------------
packet-ssh.c:1361
static int ssh_dissect_kex_dh_gex(guint8 msg_code, tvbuff_t *tvb,
        packet_info *pinfo, int offset, proto_tree *tree,
        struct ssh_flow_data *global_data, guint *seq_num)

结构体和关键字

结构体和联合体

结构体 struct

static struct DynaPrompt {
  char dynamicPrompt[PROMPT_LEN_MAX];
  char acAwait[2];
  int inParenLevel;
  char *zScannerAwaits;
} dynPrompt = { {0}, {0}, 0, 0 };
  • 因为C没有构造函数,不能像cpp一样在构造函数对结构体进行赋初值,因此它赋初值的方法是在结构体定义的尾部赋值

  • 每个成员变量含义:
    dynamicPrompt 数组被初始化为 {0}:即所有元素都是空字符 ‘\0’。
    acAwait 数组被初始化为{0}:即设置为两个空字符 ‘\0’。
    inParenLevel 被初始化为 0:表示括号的层级初始值为 0。
    zScannerAwaits被初始化为 0:它被初始化为 0,即指向空地址。

联合体 union

  • 联合体的所有成员共用同一块内存空间,不同成员在内存中的位置是重叠的。
  • 联合体的大小等于所有成员中占用内存最大的那个成员的大小
  • 在同一时间,只能使用一个成员,存储一个成员的值会覆盖其他成员的值。
#include <stdio.h>
#include <string.h>

union Data {
    int integerData;
    float floatData;
    char stringData[20];
} data;

int main() {
    // 存储整数数据
    data.integerData = 42;
    printf("Integer Data: %d\n", data.integerData);

    // 存储浮点数数据
    data.floatData = 3.14;
    printf("Float Data: %.2f\n", data.floatData);

    // 存储字符串数据
    strcpy(data.stringData, "Hello, Union!");
    printf("String Data: %s\n", data.stringData);

    // 访问整数数据(此时整数数据会被覆盖)
    printf("Integer Data: %d\n", data.integerData);

    return 0;
}

在这里插入图片描述

关键字

  • static和extern的作用是相反的

static

因为源码中有大量的static函数、结构体等。
静态函数的作用是:

  • 函数隐藏在当前源文件中,对其他部分不可见,实现隐藏实现细节的目的
  • 将函数声明为 static 可以限制其作用域,避免与其他文件中的同名函数冲突
  • 这是一种常见的编程技巧,用于实现模块化的代码结构和增强程序的安全性。

extern

  • 当在一个源文件中使用 extern 来声明函数时,它告诉编译器该函数是在另一个源文件中定义的,而不是在当前文件中定义的
  • 通常,这样的声明会放在头文件中,然后在其他源文件中包含该头文件,以便其他源文件可以访问和调用声明的函数。

C++关键字 explicit

  • 这个关键字加在构造函数前,保证代码安全性
class MyClass {
public:
    explicit MyClass(int value) {
        // 构造函数的实现
    }
};
//如果没有explicit关键字那么构造函数可以隐式调用
int num = 42;
MyClass obj = num; // 隐式调用构造函数,将整数转换为 MyClass 类型
//加上explicit关键字必须显式创建对象
int num = 42;
MyClass obj(num); // 显式调用构造函数,将整数转换为 MyClass 类型

标志位与位操作

& 、 |、<<、>>

1000&1010 = 1000
1000|1010 = 1010
1<<8 = 1*2的八次方(10000000)
256>>8 = 256/2的八次方(
10000000>>8)
可以简单地理解为加减法的关系,本处不做具体解释,可以参考网上其他信息

标志位(|加标志)(&减/检标志)

#include <stdio.h>

// 定义标志位的常量
#define FLAG_A (1 << 0) // 00000001
#define FLAG_B (1 << 1) // 00000010
#define FLAG_C (1 << 2) // 00000100

int main() {
    int flags = 0; // 初始标志位为 0

    // 设置标志位
    flags |= FLAG_A; // 将 FLAG_A 设置为 1
    flags |= FLAG_C; // 将 FLAG_C 设置为 1

    // 检查标志位
    if (flags & FLAG_A) {
        printf("标志位 A 已设置\n");
    } else {
        printf("标志位 A 未设置\n");
    }

    if (flags & FLAG_B) {
        printf("标志位 B 已设置\n");
    } else {
        printf("标志位 B 未设置\n");
    }

    if (flags & FLAG_C) {
        printf("标志位 C 已设置\n");
    } else {
        printf("标志位 C 未设置\n");
    }

    return 0;
}

过程

  • flags = 00000000
  • FLAG_A = 00000001
    flags = 00000000 | 00000001 = 00000001
  • FLAG_C = 00000100
    flags = 00000001 | 00000100 = 00000101
  • FLAG_A = 00000001
    flags = 00000101 & 00000001 = 00000001 (非零,表示标志位 A 已设置)
  • FLAG_B = 00000010
    flags = 00000101 & 00000010 = 00000000 (为零,表示标志位 B 未设置)
  • FLAG_C = 00000100
    flags = 00000101 & 00000100 = 00000100 (非零,表示标志位 C 已设置)
  • 这样我们通过 | 加标志 再用 & 检查标志,我们就可以通过3bit的数据获得8种标志位情况
  • 实际上我们不必用&检查标志位,因为8种情况我们都是知道的所以直接 == 0111(0x07) 或 0011(0x03)(等6种情况)即可
    注:各种源码这种标志位过于普遍,本处不再贴此类代码

代码习惯

if else

if( dynPrompt.inParenLevel>9 ){
  shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
}else if( dynPrompt.inParenLevel<0 ){
  shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
}else{
  shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
  dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
}

if else break

if( nmb !=0 && noc+nmb <= ncmax ){
          //sss
}else break; 

函数定义

这种函数参数列表是第一次看到
工作中没有遇到过这种写法,但是这种注释确实比较方便,易读性很好
后续会对这种格式进行讨论

static char *shellFakeSchema(
  sqlite3 *db,            /* The database connection containing the vtab */
  const char *zSchema,    /* Schema of the database holding the vtab */
  const char *zName       /* The name of the virtual table */
){
//。。。
}

其他内容后续补充

这个是为了cJSON库源码学习先初步学习的一些知识储备

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

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

相关文章

kafka集群搭建(Linux环境)

zookeeper搭建&#xff0c;可以搭建集群&#xff0c;也可以单机&#xff08;本地学习&#xff0c;没必要搭建zookeeper集群&#xff0c;单机完全够用了&#xff0c;主要学习的是kafka&#xff09; 1. 首先官网下载zookeeper&#xff1a;Apache ZooKeeper 2. 下载好之后上传到…

自动化测试框架unittest与pytest的区别!

引言 前面文章已经介绍了python单元测试框架&#xff0c;大家平时经常使用的是unittest&#xff0c;因为它比较基础&#xff0c;并且可以进行二次开发&#xff0c;如果你的开发水平很高&#xff0c;集成开发自动化测试平台也是可以的。而这篇文章主要讲unittest与pytest的区别&…

Grafana - TDEngine搭建数据监测报警系统

TDengine 与开源数据可视化系统 Grafana 快速集成搭建数据监测报警系统 一、介绍二、前置条件三、Grafana 安装及配置3.1 下载3.2 安装3.2.1 windows安装 - 图形界面3.2.2 linux安装 - 安装脚本 四、Grafana的TDEngine配置及使用4.1 登录4.2 安装 Grafana Plugin 并配置数据源4…

流数据湖平台Apache Paimon(一)概述

文章目录 第1章 概述1.1 简介1.2 核心特性1.3 基本概念1.3.1 Snapshot1.3.2 Partition1.3.3 Bucket1.3.4 Consistency Guarantees一致性保证 1.4 文件布局1.4.1 Snapshot Files1.4.2 Manifest Files1.4.3 Data Files1.4.4 LSM Trees 第1章 概述 1.1 简介 Flink 社区希望能够将…

10.python设计模式【代理模式】

内容&#xff1a;为其他对象提供一种代理一控制对这个对象的访问 应用场景&#xff1a; 远程代理&#xff1a; 为远程的对象提供代理虚代理&#xff1a;根据需要创建很大的对象保护代理&#xff1a;控制对原始对象的访问&#xff0c;用于对象有不同访问权限时 UML图 举个例…

新零售行业如何做会员管理和会员营销

蚓链数字化营销系统全渠道会员管理解决方案&#xff0c;线上线下统一管理&#xff0c;打造私域流量&#xff0c;微信、门店会员全渠道管理&#xff0c;打通私域流量池&#xff0c;实现裂变营销&#xff1a; 开启新零售之路&#xff0c;必然要摒弃原有的管理模式&#xff0c;大…

实训笔记7.27

实训笔记7.27 7.27笔记一、Hive数据仓库基本概念&#xff08;处理结构化数据&#xff09;1.1 Hive的组成架构1.1.1 Hive的客户端1.1.2 Hive的驱动程序1.1.3 Hive的元数据库 1.2 Hive和数据库的区别 二、Hive的安装配置三、Hive的相关配置项四、Hive的基本使用方式4.1 使用Hive的…

DMA传输原理与实现详解(超详细)

DMA&#xff08;Direct Memory Access&#xff0c;直接内存访问&#xff09;是一种计算机数据传输方式&#xff0c;允许外围设备直接访问系统内存&#xff0c;而无需CPU的干预。 文章目录 Part 1: DMA的工作原理配置阶段&#xff1a;数据传输阶段&#xff1a; Part 2: DMA数据…

Jmeter+MySQL链接+JDBC Connection配置元件+使用

参考大大的博客学习&#xff1a;怎么用JMeter操作MySQL数据库&#xff1f;看完秒懂&#xff01;_jmeter mysql_程序员馨馨的博客-CSDN博客 注&#xff1a;里面所有没打码的都是假数据&#xff0c;麻烦大家自行修改正确的信息。 一、背景 需要取数据库中的值&#xff0c;作为…

分布式操作系统会不会是操作系统的终端形态?

昨天一位网友私信我&#xff0c;提出一个问题&#xff1a;“Laxcus分布式操作系统会不会是操作系统发展的终极形态&#xff1f;”。今天觉得有必要把这件事说一说&#xff0c;所以就忙里偷闲写下这篇文章。 咱们先说结论&#xff1a;是也不是&#xff0c;需要具体情况具…

C++--菱形继承

1.什么是菱形继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继承关系为单继承 多继承&#xff1a;一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承的问题&#xff1a;菱形继承有数据冗余和二义性的问题&#xff0c;数据冗余是由于创建多个相同类型的…

Gradle和Maven的区别

Gradle和Maven 当涉及到构建和管理项目时&#xff0c;Gradle和Maven是两个非常流行的选项。本文将讨论Gradle和Maven之间的区别以及它们的配置信息差异。 1. Gradle和Maven的区别 1.1 构建脚本语言 Maven使用XML作为构建脚本语言&#xff0c;而Gradle使用基于Groovy的DSL&…

❤️创意网页:创意动态画布~缤纷移动涂鸦~图片彩色打码

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

【Vue】在el-table的el-table-column中,如何控制单行、单列、以及根据内容单独设置样式。例如:修改文字颜色、背景颜色

用cell-style表属性来实现。在官网中是这样表述这个属性的。 在el-table中用v-bind绑定此属性。&#xff08;v-bind的简写是&#xff1a;&#xff09; <el-table:data"options":cell-style"cell"><el-table-column prop"id" label"…

设计模式-建造者模式

在前面几篇文章中&#xff0c;已经讲解了单例模式、工厂方法模式、抽象工厂模式&#xff0c;创建型还剩下一个比较重要的模式-建造者模式。在理解该模式之前&#xff0c;我还是希望重申设计模式的初衷&#xff0c;即为解决一些问题而提供的优良方案。学习设计模式遗忘其初衷&am…

图文教程:使用 Photoshop、3ds Max 和 After Effects 创建被风暴摧毁的小屋

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. 在 Photoshop 中设置图像 步骤 1 打开 Photoshop。 打开 Photoshop 步骤 2 我已经将小屋的图像导入到Photoshop中以演示 影响。如果您愿意&#xff0c;可以使用其他图像。 图片导入 步骤 3 由于小…

css实现渐变边框动画

渐变边框动画 1、实现效果2、实现代码 1、实现效果 2、实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&…

Jenkins搭建最简教程

纠结了一小会儿&#xff0c;到底要不要写这个&#xff0c;最终还是决定简单记录一下&#xff0c;因为Jenkins搭建实在是太简单了&#xff0c;虽然也有坑&#xff0c;但是坑主要在找稳定的版本上。 先学一个简称&#xff0c;LTS (Long Term Support) 属实是长见识了&#xff0c…

【高级数据结构】并查集

目录 修复公路&#xff08;带扩展域的并查集&#xff09; 食物链&#xff08;带边权的并查集&#xff09; 修复公路&#xff08;带扩展域的并查集&#xff09; 洛谷&#xff1a;修复公路https://www.luogu.com.cn/problem/P1111 题目背景 A 地区在地震过后&#xff0c;连接…

Qt —— Vs2017编译hiredis源码并测试调用(附调用hiredis库源码)

下载hiredis源码 编译hiredis源码 1、解压下载的hiredis源码包,如图使用Vs2017打开hiredis_win.sln 2、如下两图,Vs2017打开.sln后点击升级。 分别对两个工程的debug、release进行配置。Debug配置为多线程调试DLL(MDd)、Release配置为多线程DLL(/MD),这样做是为了配合被调用…