小智学长嵌入式入门学习路线_1 C语言基础

news2024/9/27 21:22:11

原课程链接: 嵌入式开发系统学习路线 从基础到项目 精品教程 单片机工程师必备课程 物联网开发 c语言 2022追更

前言

在学习过程中,老师提到了一个很重要的思想:主要从学习嵌入式的角度学习各项技能。比如c语言,语法有很多,但是其中可能有的在嵌入式开发中并不常用。如果是抱着高中备考的态度去学,搞懂每一个知识点,会浪费大量的精力。而且适用相关方面有基础的人。因此,如果读者把其中某一部分拿出来单独学习(比如为了学习c语言而学习C语言章节)并不合适。

像以往一样,我只是整理了供自己学习的学习笔记,希望读者多多支持原作者~ 也希望我的理解对你有帮助。

C 语言开发基础

本章节主要学习与嵌入式开发相关的 C语言基础知识。最终成果期望能做出 Zlog 的日志框架和 google test 的自测框架。

基本数据类型

char short int。

unsigned char: 0x00~0xff

unsigned short: 0x0000~0xffff

unsigned int: 0x00~0xffff ffff.

要熟悉一些类型的转换。

应用:比如Unix时间戳,可以转换为8位的16进制数(如:AAAA AAAA)。服务器可能发送一个一维长度为4的 unsigned char 数组给我们,我们要通过移位运算合并。

unsigned char time_stamp_arr[4]=[0XAA,0XAA,0XAA,0XAA];
unsigned int time_stamp=(((((time_stamp_arr[0]<<8)|time_stamp_arr[1])<<8)|time_stamp[2])<<8)|time_stamp[3];

判断

判断当然非常常用。点灯可能就用的到判断。

主要就是 if 和 switch 的语法。多判断当然更推荐 switch。

循环

遍历当然用途也很广。比如一直循环查看有无新订单。

for 和 while 的语法。

static

首先,static 可以限制作用域。被 static 限制的函数不能被其他文件使用。除非这个函数在同一个文件中被非 static 的函数引用了,然后别的文件引用那个非 static 的函数来间接引用这个函数。

vscode 怎么运行多个源代码文件?比如要运行 Main.c,Main.c 中引用了 PrintMessage.c 中的内容,就可以输入gcc Main.c PrintMessage.c -o Main来链接多个源代码文件并生成可执行文件Main.exe. 注意这里没有 -c,-c 后跟多个文件会报错。

然后进行本例的测试。我在 PrintMessage.c 中这样写:

#include<stdio.h>
#include"PrintMessage.h"
void printMessage(){
    printMessage1();
}

static void printMessage1(){
    printf("hello world");
}

在 PrintMessage.h 中这样声明:

#ifndef _PRINTMESSAGE_H
#define _PRINTMESSAGE_H
#include<stdio.h>
void printMessage();
void printMessage1();
#endif

注意这里如果 printMessage1() 声明也是 static 类型,Main 就会报错直接找不到这个函数。如果 .h 声明中没有 static 关键字,Main 会正常提示:这个函数是 static 的,你别的文件用不了。但是通过 printMessage() 间接调用 printMessage1() 是可以的。

static 还可以声明变量,被 static 修饰的变量指挥被定义一次,下次访问时仍然继承上次的值。比如我们要统计一台坏掉的打印机被访问了几次:

void printMessage(){
    static int i=0;
    printf("i=%d\n",i++);
}

当这个函数被 Main.c 访问5次,输出并不是00000每次重新定义,而是01234值一直继承。

define typedef

就是同内容替换。比如区分测试和发布版本,或者开启关闭日志功能可能有用。

比如:

#define LOG_OPEN 1//这一句没有被注释,才能输出当前版本信息。注释掉了就说明关闭日志了,不会开启选择结构
#define VERSION_PRE 1
//#define VERSION_POST 1
//#define VERSION GLOD 1

#ifdef LOG_OPEN

#ifdef VERSION_PRE 
	printf("This is a pre version!\n");
#ifdef VERSION_POST 
	printf("This is a post version!\n");
#elif VERSION_GLOD//正式版
	printf("This is a glod version!\n");
#endif

通过这种方式首先选择开启还是关闭日志。如果开启,区分是测试版,发布版还是正式版,并执行相应的代码。发布的时候我们就可以直接修改 define 发布。

define 和 typedef 的区别:define 就直接在原文中替换了,typedef 是别名的意思。比如:

#define PCHAR1 char *
PCHAR1 c1,c2;//相当于:char *c1, c2; 实际上 c1 是指针,c2 是 char 类型。

typedef char * PCHAR2
PCHAR2 c3,c4;//相当于:char *c1;char *c2;

typedef 常用于类型定义,比如比较经典的单片机定义:unsigned char => u8, unsigned short => u16, unsigned int =>u32

enum

比如我们要确认当前网络连接状态,可能有四种:初始化,连接中,连接成功,连接失败。我们可以给这四种状态定义值=1 2 3 4。

int net_status=2;
switch(net_status){
    case 1:
        printf("NETWORK INITING");
        break;
    case 2:
        printf("NETWORK CONNECTING");
        break;
    case 3:
        printf("NETWORK CONNECT SUCCESS");
        break;
    case 4:
        printf("NETWORK CONNECT FAIL");
        break;
    default:
        printf("ERROR OCCURRED!\n");
        break;
}

1 2 3 4 挺难理解的。我们就可以用上刚刚学到的 #define,赋予 1 2 3 4 意义。

#define NET_CONNECT_INIT 1
#define NET_CONNECT_ING 2
#define NET_CONNECT_SUCCESS 3
#define NET_CONNECT_FAIL 4
int net_status=NET_CONNECT_ING;
switch(net_status){
    case NET_CONNECT_INIT:
        printf("NETWORK INITING");
        break;
    case NET_CONNECT_ING:
        printf("NETWORK CONNECTING");
        break;
    case NET_CONNECT_SUCCESS:
        printf("NETWORK CONNECT SUCCESS");
        break;
    case NET_CONNECT_FAIL:
        printf("NETWORK CONNECT FAIL");
        break;
    default:
        printf("ERROR OCCURRED!\n");
        break;
}

这样可读性高多了。但是如果能给 status 加些限制就更好了,让其值只能从4种 define 中选择。

typedef enum{
    NET_CONNECT_INIT,
	NET_CONNECT_ING,
	NET_CONNECT_SUCCESS,
	NET_CONNECT_FAIL,
}E_NET_STATUS;

struct

自己定义一种结构类型。比如一个学生有姓名年龄性别等属性。一台售货机有各种传感器、各种设备的属性。定义为一个结构体方便管理。

typedef struct {
    /* data */
    char *name;
    int age;
    enum{f,m,} sex;
}Student;
void main(){
    Student s1;
    s1.age=19;
    s1.sex=f;
    s1.name="girl";
    printf("s1.name=%s",s1.name);
}

指针

普通变量是为了表示数据而生,指针变量是为了传递数据而生。指针变量指明了一个变量数据的地址,所有人拿到那个地址之后都可以去那个地址找那个变量读写,而不像普通的函数传参,得到的变量只是复制了一份,修改也不是修改的原来的变量。

比如最经典的 swap 函数,直接传普通的变量原变量值并不会被修改。

void swap(int *a, int *b){
    int temp=*a;
    *a=*b;
    *b=temp;
}
int main(){
    int a=10;
    int b=20;
    swap(&a,&b);
    printf("a=%d\nb=%d\n",a,b);
}

同类型的多个数据可以用数组存储。

回调函数

菜鸟教程上的使用例:

#include<stdio.h>

int Callback_1(int x) // Callback Function 1
{
    printf("Hello, this is Callback_1: x = %d ", x);
    return 0;
}

int Callback_2(int x) // Callback Function 2
{
    printf("Hello, this is Callback_2: x = %d ", x);
    return 0;
}

int Callback_3(int x) // Callback Function 3
{
    printf("Hello, this is Callback_3: x = %d ", x);
    return 0;
}

int Handle(int y, int (*Callback)(int))
{
    printf("Entering Handle Function. ");
    Callback(y);
    printf("Leaving Handle Function. ");
}

int main()
{
    int a = 2;
    int b = 4;
    int c = 6;
    printf("Entering Main Function. ");
    Handle(a, Callback_1);
    Handle(b, Callback_2);
    Handle(c, Callback_3);
    printf("Leaving Main Function. ");
    return 0;
}

img

可见,回调函数的一个特点是耦合性低。针对不同的回调函数我们使用同一个函数接口。

另一个特点:类似中断处理,可以在发生某事件时主动调用该函数。

比如,我们想让自动售货机货空的时候发消息提示我们:没有货物啦。要补货。怎样实现?

第一种实现方式:我们写一个循环 while(1) 函数,在里面一直一直判断if(good_num==0),变为0的时候输出信息“要补货”。

死循环好吗,不好。浪费系统资源。

第二种实现方式:我们不必被动的一遍遍检查当前货物有没有=0,我们可以在货物=0时主动调用回调函数输出信息“要补货”。在货物=0时调用 Handle 函数,传入回调函数指针和货物数:0,主动访问该函数。

数据结构基本概念

有时候我们可能需要做数据拷贝。

malloc 函数裸机不建议使用。因为无法利用碎片化空间,只能直接申请一整块空间。带操作系统的计算机可以。

为了实现数据拷贝,我们还是有必要了解一些数据结构的。

数组:批量的同类型数据。

栈:比如程序执行了一半,一个中断进来了,我们要把原来执行了一半的程序存储到栈里。栈最主要的特点就是先进后出。

队列:先入先出。比如批量处理用户的订单。

链表:可以插队,比如有vip用户要先处理,插入队首。并且只要知道链表首地址就知道整个链表所有元素的地址。

树:树的排列一般是由特征的,可以找到相应特征的元素,比如二叉树找最大的。

实战1:Zlog 日志框架

下面做一个简单的项目,一个低配版 Zlog 日志框架。

首先,日志会输出一些信息,这自然不用多说。但是并不是所有情况下我们都希望日志信息输出的,比如发布版和正式版我们不希望用户看到日志信息。

解决方法很简单,之前在 define 部分也有学习到。就是在文件开头定义当前文件所处的模式,下面通过ifdef来判断现在所处的模式,进而决定要不要输出日志文件。

#ifdef OPEN_LOG
printf();
printf();
printf();
#endif

我们可以把这一部分封装到函数里。C 语言有一种变参函数,支持传入不同长度的参数。是 stdarg.h 下的 type va_arg(va_list ap, type) 函数。

菜鸟教程上的实例程序如下:

#include <stdarg.h>
#include <stdio.h>

int sum(int, ...);

int main()
{
   printf("15 和 56 的和 = %d\n",  sum(2, 15, 56) );
   return 0;
}

int sum(int num_args, ...)
{
   int val = 0;
   va_list ap;
   int i;

   va_start(ap, num_args);
   for(i = 0; i < num_args; i++)
   {
      val += va_arg(ap, int);//va_arg返回的是不固定的参数部分,固定的部分就是num_args,不用通过这个函数获取了。
   }
   va_end(ap);
 
   return val;//因此最后获取到的 val 值只是不固定的参数的求和,即15+56
}

传入的参数们 num_args 通过 va_start函数初始化成一个va_list类型的对象 ap,ap 通过va_arg(va_list, 数据类型)遍历。

因此我们可以如法炮制,写一个print_log函数,传入一串不固定长度的要输出的参数。首先要在函数里用ifdef判断一下当前是否是日志模式。

如果是日志模式,则可以继续输出,先利用va_start初始化 va_list 列表,再逐个获取参数并输出。

#include<stdio.h>
#include<stdarg.h>
#define OPEN_LOG 1

void printLog(int num_args,...){//这里的第一个参数也不一定非要是 num_args,这里只是一种用法,方便知道传入了几个参数。
    #ifdef OPEN_LOG
    va_list ap;
    int i=0;
    va_start(ap,num_args);
    for(;i<num_args;i++){
        char * str=va_arg(ap,char *);
        printf("\t%s\n",str);
    }
    va_end(ap);
    #endif
}

int main(){
    printf("Log:\n");
    printLog(3,"123","123","789");
    return 0;
}

实际上,我们学习C语言的第一个程序 hello world 里面就有一个变参函数: pintf。其中第一个参数是固定的 char * 字符串,后面的参数都是替换字符串中的类型符的参数。

printf 是直接把内容输出到屏幕,sprintf(char *str, const char * format, ...) 是将 format 字符串输出到 str 字符串中,后面的可变参数是用来替换 format 字符串中的类型符的。而 snprintf(char *str, size_t size, const char *format, ...) 是限定输出最大 size 长度。而 vsnprintf 功能与 snprintf 完全一致,只是把参数列表替换为 va_list 类型。

因此利用这两种函数,我们可以不直接输出日志字符串,而是先将要打印的信息输出到变参函数中,在函数中利用 vsnprintf 将要打印的信息合并成一整个字符串,再进行输出。

#include<stdio.h>
#include<stdarg.h>
#define OPEN_LOG 1

void printLog(char *fmt,...){
    #ifdef OPEN_LOG
    va_list arg;
    va_start(arg,fmt);
    char str[1+vsnprintf(NULL,0,fmt,arg)];//通过这条语句获取 fmt 的长度并定义相应长度的字符数组
    vsnprintf(str,sizeof(str),fmt,arg);//vsprintf 自然也可以,vsnprintf 保险一些
    printf("%s",str);
    va_end(arg);
    #endif
}

int main(){
    printf("Log:\n");
    printLog("\t%s\n\t%s\n\t%s\n","123","123","789");
    return 0;
}

接下来是限制不同版本的日志输出信息。比如我们设置四种日志输出等级:debug, info, warn. error。我们希望输出等级是层层递增的,比如调试版本四种信息都会输出,正常版本只会输出 info, warn, error 信息,发布版本只会输出 error 信息。用之前 define 章节学到的版本定义非常容易实现。

#define OPEN_LOG 1//设置开启日志

//设置当前日志版本
#define LOG_LEVEL LOG_DEBUG
// #define LOG_LEVEL LOG_INFO
// #define LOG_LEVEL LOG_WARN
// #define LOG_LEVEL LOG_ERROR

typedef enum{
    LOG_DEBUG=0,
    LOG_INFO,
    LOG_WARN,
    LOG_ERROR,
}E_LOGLEVEL;//四个值分别是0 1 2 3 4

//printLog 函数中做一些改动。首先开头不只是一个固定参数,而是两个固定参数,第一个是版本号,比如是 LOG_ERROR 就代表这条信息是 ERROR 等级下要输出的。第二个代表 fmt 原字符串。
//然后要 printf 输出之前先判断一下当前版本与要输出的信息的版本。比如当前版本是 INFO 版本,要输出的信息是 LOG_DEBUG 的信息,那么这条信息就不用输出。
void printLog(const int level, const char *fmt,...){
    #ifdef OPEN_LOG
    va_list arg;
    va_start(arg,fmt);
    char str[1+vsnprintf(NULL,0,fmt,arg)];
    vsnprintf(str,sizeof(str),fmt,arg);
    if(level>=LOG_LEVEL)//注意看,这句是关键
        printf("%s",str);
    va_end(arg);
    #endif
}

int main(){
    printf("Log:\n");
    printLog(LOG_DEBUG,"\t%s VERSION MESSAGE: %s\n","LOG_DEBUG","123");
    printLog(LOG_INFO,"\t%s VERSION MESSAGE: %s\n","LOG_INFO","456");
    printLog(LOG_WARN,"\t%s VERSION MESSAGE: %s\n","LOG_WARN","789");
    printLog(LOG_ERROR,"\t%s VERSION MESSAGE: %s\n","LOG_ERROR","012");
    return 0;
}

这样执行输出的会是四条语句。也可以切换当前日志等级。

在调试过程中,输出一些代码相关信息如当前所处函数、当前所处行数等。

void printLog(const int level, const char * function, const int line, const char *fmt,...){
    #ifdef OPEN_LOG
    va_list arg;
    va_start(arg,fmt);
    char str[1+vsnprintf(NULL,0,fmt,arg)];
    vsnprintf(str,sizeof(str),fmt,arg);
    if(level>=LOG_LEVEL)
        printf("Function: %s; Line: %d\n%s",function, line, str);
    va_end(arg);
    #endif
}
#define PRINT_LOG(level, fmt...) printLog(level, __FUNCTION__, __LINE__, fmt)

image-20230121005858828

在整个的 printLog 函数结尾还可以增加一个状态:日志已保存 isSaved,代表整个字符串已经顺利写入 str 缓冲数组中。

最后一步就是把所有的函数声明、define、typedef 提出来放到 .h 文件中,方便管理。

实战2:google 测试框架

随着需求不断变化,版本也会不断更迭,难免会对一些函数做修改。

如果对函数做的操作使得函数违背了最初期望实现的功能呢?应该增加一些限制,比如修改完函数后对函数进行测试。如果结果符合预期,再保留修改。

比如写一个最简单的 sum 函数进行两数求和,我们可以通过测试函数比较输入 a b 后函数的返回值和 a+b 的值是否相等。

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

typedef struct{
    int a;
    int b;
    int output;
    int (*TestEMFunc)(int,int);//这里代表一个有两个 int 参数的 返回值为 int 类型的函数
}GTest;

GTest *addFunc(int (*TestEMFunc)(int,int),int a,int b, int output){//这里传入的 output 是程序员自己给的,期望的结果。
    GTest *m_EMTest=(GTest *)malloc(sizeof(GTest));
    m_EMTest->a=a;
    m_EMTest->b=b;
    m_EMTest->TestEMFunc=TestEMFunc;
    m_EMTest->output=output;
    return m_EMTest;
}

int add(int a, int b){return a+b;}

void runGTest(GTest *gTest){
    if(gTest!=NULL){
        int temp=gTest->TestEMFunc(gTest->a, gTest->b);//这里 temp 计算得出的结果是函数的返回值。
        if(temp==gTest->output)printf("测试成功!\n");
        else printf("测试失败!建议重新修改函数\n");
    }
}

void main(){
    GTest *gTest=addFunc(add, 2, 3, 5);
    runGTest(gTest);
    return;
}

当然这只是最基础的一个形式。github 上有 google 测试框架,把此例弄懂后去理解 google 测试框架会更加容易。

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

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

相关文章

【Java|golang】1807. 替换字符串中的括号内容

给你一个字符串 s &#xff0c;它包含一些括号对&#xff0c;每个括号中包含一个 非空 的键。 比方说&#xff0c;字符串 “(name)is(age)yearsold” 中&#xff0c;有 两个 括号对&#xff0c;分别包含键 “name” 和 “age” 。 你知道许多键对应的值&#xff0c;这些关系由…

Linux基本功系列之chmod命令实战

文章目录一. chmod命令介绍二. 语法格式及常用选项三. 参考案例3.1 对全部用户增加写的权限3.2 所有用户减去读的权限3.3 给文件的所有者和所有组加上读写权限3.4 设置所有用户为读写执行的权限3.5 文件拥有着为rwx&#xff0c;所属组为rw&#xff0c;其它为r3.6 去掉所有者的r…

高级Spring之ApplicationContext功能

第一步&#xff0c;我们先来看这个接口的内部结构&#xff0c;了解别人的内部&#xff0c;知己知彼&#xff0c;百战不殆&#xff1a; 这个接口的扩展功能主要体现在它继承的四个接口上&#xff1a; MessageSource&#xff1a;国际化功能 ResourcePatternResolver: 资源访问功…

第23章_Tomcat和JavaEE入门

一、JavaEE简介什么是JavaEEJavaEE&#xff08;Java Enterprise Edition&#xff09;&#xff0c;Java企业版&#xff0c;是一个用于企业级web开发平台。最早由Sun公司定制并发布&#xff0c;后由Oracle负责维护。JavaEE平台规范了在开发企业级web应用中的技术标准.在JavaEE平台…

Acwing——第 87 场周赛

题目链接 4797. 移动棋子 4798. 打怪兽 4799. 最远距离 题目描述 4797. 移动棋子 给定一个 5 行 5 列的方格矩阵&#xff0c;其中一个方格中有一个棋子。 现在&#xff0c;我们希望将棋子移动至矩阵的最中心方格中&#xff0c;即将其移动至矩阵的第 3行第 3列方格中。 每次…

8种时间序列分类方法总结

对时间序列进行分类是应用机器和深度学习模型的常见任务之一。本篇文章将涵盖 8 种类型的时间序列分类方法。这包括从简单的基于距离或间隔的方法到使用深度神经网络的方法。这篇文章旨在作为所有时间序列分类算法的参考文章。 时间序列定义 在涵盖各种类型的时间序列 (TS) 分…

分布式锁与实现(一)-为什么需要分布式锁

1 在开发中的锁是什么 在计算机科学中&#xff0c;锁是在执行多线程时用于强行限制资源访问的同步机制&#xff0c;即用于在并发控制中保证对互斥要求的满足。 在java中我们有两种资源控制方式Synchronized与AQS 1.2 基于Synchronized实现的锁控制 Synchronized是java提供的一…

JDK 8新特性之Lambda表达式

目录 一&#xff1a;使用匿名内部类存在的问题 Lambda表达式写法,代码如下&#xff1a; 二&#xff1a;Lambda的标准格式 三&#xff1a;Lambda的实现原理 四&#xff1a;Lambda省略格式 五&#xff1a;Lambda的前提条件 六&#xff1a;函数式接口 七&#xff1a;Lambd…

05回溯法

文章目录装载问题回溯算法优化算法构造最优解0-1背包问题批处理作业调度问题图的M着色问题N皇后问题最大团问题回溯算法实际上一个类似枚举的搜索尝试过程&#xff0c;主要是在搜索尝试过程中寻找问题的解&#xff0c;当发现已不满足求解条件时&#xff0c;就“回溯”返回&…

12. 字典dict类型详解

1. 基础知识 (1) 字典(dictionary)是Python中另一个非常有用的内置数据类型。 (2) 列表是有序的对象集合&#xff0c;字典是无序的对象集合。两者之间的区别在于&#xff1a;字典当中的元素是通过键来存取的&#xff0c;而不是通过偏移存取。 (3) 字典是一种映射类型&#xff…

Flowable进阶学习(三)流程、流程实例挂起与激活;启动、处理、结束流程的原理以及相关表结构与变动

文章目录流程挂起与激活流程实例挂起与激活启动、处理、结束流程的原理一、启动流程的原理启动一个流程实例时涉及到的表及表结构:ACT_RU_EXECUTION 运行时流程执行实例ACT_RU_IDENTITYLINK 运行时用户关系信息ACT_RU_TASK 运行时任务表ACT_RU_VARIABLE 运行时变量表二、处理流…

过滤器Filter总结

过滤器Filter1. 简介2. 快速入门3. 执行流程4. 使用细节4.1 拦截路径4.2 过滤器链5. 案例5.1 需求5.2 LoginFilter1. 简介 过滤器是JavaWeb三大组件之一&#xff08;Servlet、Filter&#xff0c;Listner&#xff09;&#xff1b; 作用&#xff1a; 把对资源&#xff08;servl…

Ubuntu22.04 安装 ssh

文章目录Ubuntu22.04 安装 ssh一、 环境配置二、 启动远程连接三、 开放端口四、 远程连接Ubuntu22.04 安装 ssh 一、 环境配置 安装 Ubuntu 系统后&#xff0c;我们首先需要配置管理员 root 用户&#xff1a; sudo passwd root然后&#xff0c;进行软件源的更换&#xff1a…

14 Java集合(Map集合+HashMap+泛型使用+集合面试题)

集合14.11 Map集合14.11.1 Map集合特点14.11.2 Map集合体系结构14.12 HashMap14.12.1 HashMap基本使用14.12.2 HashMap实际应用14.12.3 HashMap练习14.12.4 HashMap底层实现原理14.12.5 put的过程原码14.12.6 resize过程原码14.12.7 get的过程原码14.13 HashTable14.14 泛型高级…

5-1中央处理器-CPU的功能和基本结构

文章目录一.CPU的功能二.CPU的基本结构&#xff08;一&#xff09;运算器1.运算器的基本组成2.专用数据通路方式3.CPU内部单总线方式&#xff08;二&#xff09;控制器1.基本组成2.实现过程&#xff08;三&#xff09;寄存器一.CPU的功能 中央处理器&#xff08;CPU&#xff0…

并查集的入门与应用

目录 一、前言 二、并查集概念 1、并查集的初始化 2、并查集的合并 3、并查集的查找 4、初始化、查找、合并代码 5、复杂度 二、路径压缩 三、例题 1、蓝桥幼儿园&#xff08;lanqiaoOJ题号1135&#xff09; 2、合根植物&#xff08;2017年决赛&#xff0c;lanqiaoO…

SQL注入篇 - 布尔盲注及延时注入

数据来源 盲注 什么是盲注&#xff1a; 布尔盲注原理 布尔盲注流程 手工盲注思路&#xff08;以下的文章参考&#xff1a;DVWA-sql注入&#xff08;盲注&#xff09; - N0r4h - 博客园&#xff09; 手工盲注的过程&#xff0c;就像你与一个机器人聊天&#xff0c;这个机器人知…

DGSEA | GSEA做完了不要停,再继续比较一下有意义的通路吧!~

1写在前面 GSEA大家都会用了&#xff0c;但GSEA也有它自己的缺点&#xff0c;就是不能比较两个基因集或通路的富集情况。&#x1f912; 今天介绍一个Differential Gene Set Enrichment Analysis (DGSEA)&#xff0c;可以量化两个基因集的相对富集程度。&#x1f609; 2用到的包…

Java中的位运算及其常见的应用

文章目录1、位运算1.1 原码、反码、补码1.2 位运算符2、位运算的应用2.1 取模运算2.2 奇偶性判断2.3 交换变量的值2.4 加法运算1、位运算 1.1 原码、反码、补码 计算机中所有数据的存储和运算都是以二进制补码的形式进行的。a —> 97&#xff0c;A —> 65&#xff0c;‘…

深入学习Vue.js(十二)编译器

模板DSL的编译器 1.编译器概述 编译器实际上是一段程序&#xff0c;他用来将一种语言A翻译为另一种语言B。其中&#xff0c;A被称为源代码&#xff0c;B被称为目标代码。编译器将源代码翻译为目标代码的过程被称为编译。完整的编译过程通常包含词法分析、语法分析、语义分析、…