bison flex 实现tiny语言的编译器

bison flex 实现tiny语言的编译器

项目地址:tiny-compiler
完成了词法分析,语法分析,中间代码生成,虚拟机执行,没有进行类型检查、错误处理和中间代码优化。

词法分析

%{
#include <iostream>
#include "tiny-parser.tab.h"
using namespace std;
/* lexeme of identifier or reserved word */

extern FILE *fin; /* we read from this file */

extern int current_lineno ;

#undef YY_INPUT
#define YY_INPUT(buf,result,max_size) \
	if ( (result = fread( (char*)buf, sizeof(char), max_size, fin)) < 0) \
		YY_FATAL_ERROR( "read() in flex scanner failed");
%}

digit       [0-9]
number      {digit}+
letter      [a-zA-Z]
identifier  {letter}+
whitespace  [ \t]+

%%

"if"            {return IF_T;}
"then"          {return THEN;}
"else"          {return ELSE;}
"end"           {return END;}
"repeat"        {return REPEAT;}
"until"         {return UNTIL;}
"read"          {return READ;}
"write"         {return WRITE;}
":="            {return ASSIGN;}
"="             {return EQ;}
"<"             {return LT;}
"+"             {return PLUS;}
"-"             {return MINUS;}
"*"             {return TIMES;}
"/"             {return OVER;}
"("             {return LPAREN;}
")"             {return RPAREN;}
";"             {return SEMI;}
{number}        {return NUM;}
{identifier}    {return ID;}
\n             {current_lineno++;}
{whitespace}    {/* skip whitespace */}
\{[^\}]*\}          { 
                    if(yytext[0] == '\n') {
                        current_lineno++;
                    }
                }
"$"             {return EOF;}
"}"             {return ERROR;}
.               {return ERROR;}

%%

这里需要注意两个点,

  1. 定义了current_lineno变量,用于记录当前行号。每次碰到\n都是需要加一的
  2. YY_INPUT 宏,用于指定从哪里读入数据,具体的File * fin在main函数中设置。

语法分析

代码

%{
#include "TreeNode.h"
#define YYSTYPE TreeNode *
#include <string>

using namespace std;

extern int current_lineno ;
extern char *yytext;

int yylex();
int yyerror(const char *s);

static char saved_name[256]; /* for use in assignments */
static int saved_lineNo;  /* ditto */
extern TreeNode * root; /* stores syntax tree for later return */

%}

%token IF_T THEN ELSE END REPEAT UNTIL READ WRITE
%token ID NUM 
%token ASSIGN EQ LT PLUS MINUS TIMES OVER LPAREN RPAREN SEMI
%token ERROR 

%% /* Grammar for TINY */

program     : stmt_seq {root = $1; }
            ;
stmt_seq    : stmt_seq SEMI stmt {$1->children.push_back( $3);
                                $$ = $1;
                                   }
            | stmt  { $$=new_stmt_node();
                     $$->children.push_back( $1);
             }
            ;
stmt        : if_stmt { $$ = $1; }
            | repeat_stmt { $$ = $1; }
            | assign_stmt { $$ = $1; }
            | read_stmt { $$ = $1; }
            | write_stmt { $$ = $1; }
            | error  { $$ = NULL; }
            ;
if_stmt     : IF_T exp THEN stmt_seq END     
                 { 
                  $$ = new_if_stmt(current_lineno, $2, $4);
                 }
            | IF_T exp THEN stmt_seq ELSE stmt_seq END
                 { $$ = new_if_stmt(current_lineno, $2, $4, $6);
                 }
            ;
repeat_stmt : REPEAT stmt_seq UNTIL exp
                 { $$ = new_repeat_stmt(current_lineno, $2, $4);
                 }
            ;
assign_stmt : ID { $1=new_id(current_lineno, yytext);
               }
              ASSIGN exp
                 { 
                    $$ = new_assign_stmt(current_lineno, $1, $4);                
                 }
            ;
read_stmt   : READ ID
                 {
                  $$=new_read_stmt(current_lineno, new_id(current_lineno, yytext));
                 }
            ;
write_stmt  : WRITE exp
                 { 
                  $$=new_write_stmt(current_lineno, $2);
                 }
            ;
exp         : simple_exp LT simple_exp 
                 { 
                  $$ = new_exp(current_lineno, $1,LT,$3);
                 }
            | simple_exp EQ simple_exp
                 { 
                  $$=new_exp(current_lineno, $1,EQ,$3);
                 }
            | simple_exp { $$ =$1; }
            ;
simple_exp  : simple_exp PLUS term 
                 { 
                  $$=new_simple_exp(current_lineno, $1, PLUS, $3);
                 }
            | simple_exp MINUS term
                 { 
                  $$=new_simple_exp(current_lineno, $1, MINUS, $3);
                 } 
            | term { $$ = $1; }
            ;
term        : term TIMES factor 
                 { 
                  $$=new_term(current_lineno, $1, TIMES, $3);
                 }
            | term OVER factor
                 { 
                  $$=new_term(current_lineno, $1, OVER, $3);
                 }
            | factor { $$ = $1; }
            ;
factor      : LPAREN exp RPAREN
                 { $$ = $2; }
            | NUM
                 { 
                  $$=new_num(current_lineno, atoi(yytext));
                 }
            | ID {
                  $$=new_id(current_lineno, yytext);
                 }
            | error { $$ = NULL; }
            ;

%%

int yyerror(const char *s) {
     fprintf(stderr, "Error: %s at line %d near %s\n", s, current_lineno, yytext);
     return 0;
}

tiny语言的表达式还挺难写的

  1. 这里需要先定义token,生成tiny-parser.tab.h文件,然后让tiny-lexer.l包含,这样可以解决重定义token的问题。
  2. 对于每一个表达式的值,如果没有指定的话,就是int型,指定就是你自己定义的类型
    1. 一定要是指针类型,因为局部变量在过了这个函数之后,他就会自动销毁
    2. 不要用c++的智能指针,bison是c语言写的,听说,不支持那么高级的语法
    3. 示例,就是这个宏
#define YYSTYPE TreeNode *
  1. 注意这里是**$4不是$3**,前面的SDT也算是一个$,这个bug特别难找,莫名其妙的空指针错误
assign_stmt : ID { $1=new_id(current_lineno, yytext);
               }
              ASSIGN exp
                 { 
                    $$ = new_assign_stmt(current_lineno, $1, $4);                
                 }
            ;
  1. 要声明yylex()函数,这个是从词法解析里定义的入口函数。

接口

#ifndef TREE_NODE_H
#define TREE_NODE_H
#include <string.h>
#include <vector>
#include "tiny-parser.tab.h"

using namespace std;

extern int lineno ;

enum NodeKind{StmtK=1,ExpK} ;
enum StmtKind{Stmt_K=3,IfK,RepeatK,AssignK,ReadK,WriteK} ;
enum ExpKind {OpK=9,ConstK,IdK} ;
/* ExpType is used for type checking */
enum ExpType{Void_type=9,Integer,Boolean} ;


#define Token_Kind int

class TreeNode{
private:
    void show_info(int depth,bool is_last_child);
public:
    NodeKind kind;
    int lineno;
    union { StmtKind stmt; ExpKind exp;} production_kind;
    union { Token_Kind op;
            int val;   
            char *name; } attr;
    ExpType type; /* for type checking of exps */
    vector<TreeNode *> children;


    void copy_name(const char *name){
        this->attr.name=new char(strlen(name));
        strcpy(this->attr.name,name);
    };
    void show_all_info();


    TreeNode(int line){
        this->lineno = line;
    }

    template<typename... Args>
    TreeNode(int line,TreeNode * first_child, Args... args){
        this->lineno = line;
        this->children.push_back(first_child);
        (this->children.push_back(args),...);
    };
    ~TreeNode(){
        if(this->production_kind.exp==IdK){
            delete this->attr.name;
        }
    }

    virtual void generate_code()=0;
};


TreeNode * new_stmt_node();

TreeNode * new_if_stmt(int line,TreeNode * cond, TreeNode * then_stmt, TreeNode * else_stmt);
TreeNode * new_if_stmt(int line,TreeNode * cond, TreeNode * then_stmt);

TreeNode * new_repeat_stmt(int line, TreeNode * body,TreeNode * cond);

TreeNode * new_assign_stmt(int line,TreeNode * var, TreeNode * exp);

TreeNode * new_read_stmt(int line,TreeNode * var);

TreeNode * new_write_stmt(int line,TreeNode * exp);

TreeNode * new_exp(int line,TreeNode * left, Token_Kind op, TreeNode * right);
TreeNode * new_exp(int line,TreeNode * simple_exp);

TreeNode* new_simple_exp(int line,TreeNode * left, Token_Kind op, TreeNode * right);

TreeNode * new_term(int line,TreeNode * left, Token_Kind op, TreeNode * right);
TreeNode * new_term(int line,TreeNode * factor);


TreeNode * new_num(int line,int val);
TreeNode * new_id(int line,const char *name);

#endif

就是使用TreeNode*节点,记录当前的需要的信息,比方说 表达式的符号,变量的id,当前节点的行号,每次进行到规约的时候,就执行这个,把节点增加到根节点里去。

接口实现

#ifndef TREE_NODE_EXTERN_H
#define TREE_NODE_EXTERN_H
#include <iostream>
#include "TreeNode.h"
using namespace std;

extern FILE *fout;

class StmtNode : public TreeNode {

public:
    StmtNode(int line):TreeNode(line){

    };

    void generate_code() override{
    
        for(auto &child:this->children){
            child->generate_code();
        }
    
    }
};



class IfNode : public TreeNode {
public:
    static int if_count ;
    IfNode(int line, TreeNode* condition, TreeNode* true_stmt, TreeNode* false_stmt):TreeNode(line){};

    IfNode(int line, TreeNode* condition, TreeNode* true_stmt):TreeNode(line,condition,true_stmt){};

    void generate_code() override{
        if_count++;
        this->children[0]->generate_code();
        if(this->children.size()==3){
            //if else
            fprintf(fout,"if_else_%d:\n",if_count);
            this->children[1]->generate_code();
            fprintf(fout,"jmp if_end_%d\n",if_count);

            fprintf(fout,"label if_else_%d\n",if_count);
            this->children[2]->generate_code();

            fprintf(fout,"label if_end_%d\n",if_count);
        }else{
            //if
            fprintf(fout,"if_end_%d:\n",if_count);
            this->children[1]->generate_code();
            fprintf(fout,"label if_end_%d\n",if_count);
        }
          fflush(fout);
    }
};
int IfNode::if_count = 0;

class RepeatNode : public TreeNode {
public:
    static int repeat_count ;
    RepeatNode(int line, TreeNode* condition, TreeNode* stmt):TreeNode(line,condition,stmt){};

    void generate_code() override{
        repeat_count++;
        fprintf(fout,"label repeat_%d\n",repeat_count);
        this->children[0]->generate_code();
        this->children[1]->generate_code();
        fprintf(fout,"repeat_%d\n",repeat_count);
        fflush(fout);
    }
};
int RepeatNode::repeat_count = 0;

class AssignNode : public TreeNode {
public:
    AssignNode(int line, TreeNode* left, TreeNode* right):TreeNode(line,left,right){};

    void generate_code() override{
        this->children[1]->generate_code();
        fprintf(fout,"pop %s\n",this->children[0]->attr.name);
          fflush(fout);
    }
};

class ReadNode : public TreeNode {
public:
    ReadNode(int line, TreeNode* left):TreeNode(line,left){};

    void generate_code() override{
        fprintf(fout,"read %s\n",children[0]->attr.name);
        fflush(fout);
    }
};

class WriteNode : public TreeNode {
public:
    WriteNode(int line, TreeNode* right):TreeNode(line,right){};

    void generate_code() override{
        fprintf(fout,"write %s\n",children[0]->attr.name);
    }
};

class ExpNode : public TreeNode {
public:
    ExpNode(int line, TreeNode* left, TreeNode* right):TreeNode(line,left,right){};
    ExpNode(int line, TreeNode* simple_exp):TreeNode(line,simple_exp){};
    ExpNode(int line):TreeNode(line){};
    void generate_code() override{
        if(this->children.size()==1){
            this->children[0]->generate_code();
        }
        else if(this->children.size()==2){
            //this->show_all_info();
            //cout<<endl;
            this->children[0]->generate_code();
            this->children[1]->generate_code();
            fprintf(fout,"sub\n");
            switch (this->attr.op)
            {
            case EQ:
                fprintf(fout,"jnz ");
                break;
            case LT:
                fprintf(fout,"jeg ");
                break;
            default:
                cerr<<"wrong operator in exp node"<<endl;
                break;
            }
        }
        fflush(fout);
    
    }
};

class SimpleExpNode : public TreeNode {
public:
    SimpleExpNode(int line, TreeNode* left, TreeNode* right,Token_Kind op):TreeNode(line,left,right){
        this->attr.op = op;
        this->kind = ExpK;
        this->type = Integer;
        this->production_kind.exp=OpK;
    };
    SimpleExpNode(int line, TreeNode* factor):TreeNode(line,factor){
        this->kind = ExpK;
        this->type = Integer;
        this->production_kind.exp=OpK;
    };

    void generate_code() override{
        if(this->children.size()==1){
            this->children[0]->generate_code();
        }else{
            switch (this->attr.op)
            {
            case PLUS:
                this->children[0]->generate_code();
                this->children[1]->generate_code();
                fprintf(fout,"add\n");
                break;
            case MINUS:
                this->children[0]->generate_code();
                this->children[1]->generate_code();
                fprintf(fout,"sub\n");
                break;
            default:
                cerr<<"wrong operator in simple exp node"<<endl;
                break;
            }
        }
        fflush(fout);
    }
};

class TermNode : public TreeNode {
public:
    TermNode(int line, TreeNode* left, TreeNode* right):TreeNode(line,left,right){};
    TermNode(int line, TreeNode* factor):TreeNode(line,factor){};
    void generate_code() override{
        if(this->children.size()==0){
            this->children[0]->generate_code();
        }else{
            this->children[0]->generate_code();
            this->children[1]->generate_code();
            switch (this->attr.op)
            {
            case TIMES:
                fprintf(fout,"mul\n");
                break;
            case OVER:
                fprintf(fout,"div\n");
                break;
            default:
                cerr<<"wrong operator in term node"<<endl;
                break;
            }
        }
        fflush(fout);
    }
};

class IdNode : public TreeNode {
public:
    IdNode(int line,const char *name):TreeNode(line){
        this->kind = ExpK;
        this->type = Integer;
        this->attr.name = new char[strlen(name)+1];
        strcpy(this->attr.name,name);
        this->production_kind.exp=IdK;
    }
    void generate_code() override{
        fprintf(fout,"push %s\n",this->attr.name);
        fflush(fout);
    }
};

class NumNode : public TreeNode {
    public:
    NumNode(int line,int value):TreeNode(line){
        this->kind = ExpK;
        this->type = Integer;
        this->attr.val = value;
        this->production_kind.exp=ConstK;
    }
    void generate_code() override{
        fprintf(fout,"push $%d\n",this->attr.val);
        fflush(fout);
    }
};
#endif

对于每一个不同类型的节点,都有子类和他对应,生成不同的汇编代码。

本来应该是像后面的那样,更加简洁一点的,但我懒得改了,哈哈,先做个垃圾出来再说。

抽象语法树的生成

program     : stmt_seq {root = $1; }

这里执行成功了之后,就可以对root调用get_info的接口了,输出所有节点的信息
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
这是最后的结果,这个就是树的前序遍历,没啥好说的,最后一行可以不用| 的。


void TreeNode::show_info(int depth,bool is_last_child){
        // 输出当前节点的信息
    for (int i = 0; i < depth; i++) {
        if (i == depth-1) {
            if (is_last_child) {
                cout << "└── ";
            } else {
                cout << "├── ";
            }
        } else {
            cout << "│   ";
        }
    }

    cout<<"line: "<<this->lineno<<", type: "<<getExpTypeName(type);
    cout<<", ";
    if(this->kind==StmtK){
        cout<<"stmt: "<<getStmtKindName(production_kind.stmt)<<endl;
    }else{
        cout<<"exp: "<<getExpKindName(production_kind.exp);
        switch (production_kind.exp)
        {
        case OpK:
            cout<<", op: "<<Token_Kind_to_string(attr.op)<<endl;
            break;
        case ConstK:
            cout<<", val: "<<attr.val<<endl;
            break;
        case IdK:
            cout<<", name: "<<attr.name<<endl;
            break;
        default:
            cout<<endl;
            break;
        }
    }
    
  // 递归输出子节点
    for (size_t i = 0; i < children.size(); i++) {
        if (children[i] != nullptr) {
            children[i]->show_info(depth + 1, (i == (children.size() - 1)));
        } else {
            if (i == children.size() - 1) {
                for (int j = 0; j < depth + 1; j++) {
                    if (j == depth) {
                        cout << "└── ";
                    } else {
                        cout << "│   ";
                    }
                }
            } else {
                for (int j = 0; j < depth + 1; j++) {
                    if (j == depth) {
                        cout << "├── ";
                    } else {
                        cout << "│   ";
                    }
                }
            }
        }
    }
};

虚拟机的实现

见VM文件夹的虚拟机指令,模拟的是一个栈型计算机
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
这里用label代替的行号,因为我懒得回填行号了,大不了先遍历一遍,记录所有的label

#include <iostream>
#include <fstream>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>
#include <functional>

using namespace std;

class VirtualMachine {
public:
    VirtualMachine(const string& filename) {
        // 从文件中读取指令
        ifstream file(filename);
        if (file.is_open()) {
            string line;
            while (getline(file, line)) {
                instructions.push_back(line);
            }
            file.close();
        } else {
            cout << "Unable to open file" << endl;
            exit(1);
        }

        // 初始化指令映射表
        instructionMap.emplace("halt", [this]() { this->halt(); });
        instructionMap.emplace("add", [this]() { this->add(); });
        instructionMap.emplace("sub", [this]() { this->sub(); });
        instructionMap.emplace("mul", [this]() { this->mul(); });
        instructionMap.emplace("div", [this]() { this->div(); });

        instructionMapTwo.emplace("read", [this](string & tokens) { this->read(tokens); });
        instructionMapTwo.emplace("write", [this](string& tokens) { this->write(tokens); });
        instructionMapTwo.emplace("push", [this](string& tokens) { this->push(tokens); });
        instructionMapTwo.emplace("pop", [this](string& tokens) { this->pop(tokens); });
        instructionMapTwo.emplace("label", [this](string& tokens) { this->label(tokens); });
        instructionMapTwo.emplace("jmp", [this](string& tokens) { this->jmp(tokens); });
        instructionMapTwo.emplace("jnz", [this](string &tokens) { this->jnz(tokens); });
        instructionMapTwo.emplace("jeg", [this](string &tokens) { this->jeg(tokens); });
        instructionMapTwo.emplace("jel", [this](string &tokens) { this->jel(tokens); });
    }

    void run() {
        // 初始化label
        for(int i=0;i<instructions.size();i++){
            if(instructions[i].find("label")!=string::npos){
                labels[instructions[i].substr(6)]=i;
            }
        }
        // 运行虚拟机
        while (pc < instructions.size()) {
            
            execute(instructions[pc]);
            pc++;
        }
    }

private:
    stack<int> stack_;
    unordered_map<string, function<void()>> instructionMap;
    unordered_map<string,function<void(string &)>> instructionMapTwo;
    unordered_map<string, int> labels;
    unordered_map<string,int> variables;
    vector<string> instructions;
    int pc = 0; // 程序计数器

    void halt() {
        // 停止运行
        exit(0);
    }

    void read(string & tokens) {
        // 从输入流中读取一个值并存入寄存器
        cout << "Enter a value for variable " << tokens << ": ";
        int value;
        cin >> value;
        variables[tokens] = value;
    }

    void write(string &tokens) {
        // 将寄存器的值输出到输出流
        cout <<tokens << " = " << variables[tokens] << endl;
    }

    void push(string& tokens) {
        // 将寄存器或常数压入栈顶
        if (tokens.front() == '$') {
            int reg = stoi(tokens.substr(1));
            stack_.push(reg);
        } else if(variables.count(tokens)==1){
            stack_.push(variables[tokens]);
        }else{
            cerr<<"Undefined variable: "<<tokens<<endl;
        }
    }

    void pop(string& tokens) {
        // 将栈顶的值弹出并存入寄存器
        int reg = stack_.top();
        variables[tokens] = reg;
        stack_.pop();
    }

    void label(string& tokens) {
        // 啥也不用作,前面记录过了
  
    }

    void jmp(string &tokens) {
        // 跳转到标签
        if(labels.count(tokens)==0){
            cerr << "Undefined label: " << tokens << endl;
            exit(1);
        }
        pc = labels[tokens];
    }

    void jnz(string &tokens) {
        // 如果栈顶的值为0,则跳转到标签
        if (stack_.top() != 0) {
            if(labels.count(tokens)==0){
                cerr << "Undefined label: " << tokens << endl;
                exit(1);
            }
            pc = labels[tokens];
        }
        stack_.pop();
    }

    void jeg(string &tokens){
        // 如果栈顶的值大于等于0,则跳转到标签
        if (stack_.top() >= 0) {
            if(labels.count(tokens)==0){
                cerr << "Undefined label: " << tokens << endl;
            }
            pc=labels[tokens];
        }
        stack_.pop();
    }

    void jel(string &tokens){
        // 如果栈顶的值小于等于0,则跳转到标签
        if (stack_.top() <= 0) {
            if(labels.count(tokens)==0){
                cerr << "Undefined label: " << tokens << endl;
            }
            pc=labels[tokens];
        }
        stack_.pop();
    }

    void add() {
        // 栈顶两个元素相加
        int a = stack_.top();
        stack_.pop();
        int b = stack_.top();
        stack_.pop();
        stack_.push(b + a);
    }

    void sub() {
        // 栈顶两个元素相减
        int a = stack_.top();
        stack_.pop();
        int b = stack_.top();
        stack_.pop();
        stack_.push(b - a);
    }

    void mul() {
        // 栈顶两个元素相乘
        int a = stack_.top();
        stack_.pop();
        int b = stack_.top();
        stack_.pop();
        stack_.push(a * b);
    }

    void div() {
        // 栈顶两个元素相除
        int a = stack_.top();
        stack_.pop();
        int b = stack_.top();
        stack_.pop();
        stack_.push(b / a);
    }

    void execute(string& tokens) {
        if(tokens.front()=='#'){
            return ; // 注释
        }
        // 执行指令
        if (instructionMap.count(tokens)) {
            instructionMap[tokens]();
        } else {
            auto posi= tokens.find(' ');
            if(posi != string::npos){
                string instr= tokens.substr(0,posi);
                string arg= tokens.substr(posi+1);
                if(instructionMapTwo.count(instr)){
                    instructionMapTwo[instr](arg);
                }else{

                    cout << "Invalid instruction: " << tokens << endl;
                }
            }else{
                cout << "Invalid instruction: " << tokens << endl;
            }
        }
    }
};

int main(int argc, char* argv[]) {
    if (argc != 2) {
        cout << "Usage: " << argv[0] << " <input_file>" << endl;
        return 1;
    }

    VirtualMachine vm(argv[1]);
    vm.run();
    return 0;
}

这里写的非常好的地方在于,他把指令放到了一个map里,不用if判断了,耦合很低。

感想

  1. 最难的还是flex和bison的使用,资料和demo太少了,yyerror yylval yytexts啥的宏都要自己查官网文档。
  2. 虚拟机的实现很快,寄存器型的会难一些,需要把if while 这些翻译的逻辑搞懂。

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

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

相关文章

RocketMq源码解析五:生产者Producer发送消息

上一章我们把生产者启动的流程和大家一起跟着源码走了一遍,现在我们来看发送消息的流程。上一章我们已经把核心接口和类关系梳理了一遍。如下图 我们今天重点看MQProducer中的send方法最终的实现。DefaultMQProducer中,send的实现最终还是调用了 defaultMQProducerIm…

ingress规则

一 k8s 对外服务之 Ingress LB ingress 1 Ingress 简介 service的作用体现在两个方面 ? ① 对集群内部&#xff0c;它不断跟踪pod的变化&#xff0c;更新endpoint中对应pod的对象&#xff0c;提供了ip不断变化的pod的服务发现机制&#xff1b; ② 对集群外部&#xff0c…

MySQL—多表查询—自连接

一、引言 自连接&#xff0c;顾名思义就是自己连接自己。 自连接的语法结构&#xff1a; 表 A 别名 A join 表 A 别名 B ON 条件 ...; 注意&#xff1a; 1、这种语法有一个关键字&#xff1a;join 2、自连接查询可以是内连接的语法&#xff0c;可以是外连接的语法&#xff08…

embedding层的理解

一文读懂Embedding的概念&#xff0c;以及它和深度学习的关系 - 知乎 (zhihu.com) 感觉这篇知乎真的大道至简。个人感觉embedding层和普通的线性层没有什么区别~就是为了降维和升维用的。也就是向量的维度变化&#xff01;

hutool工具实践-验证码

简介 验证码功能位于cn.hutool.captcha包中&#xff0c;核心接口为ICaptcha&#xff0c;此接口定义了以下方法&#xff1a; createCode 创建验证码&#xff0c;实现类需同时生成随机验证码字符串和验证码图片getCode 获取验证码的文字内容verify 验证验证码是否正确&#x…

客户端被攻击怎么办,为什么应用加速这么适合

随着科技的进步和互联网的普及&#xff0c;游戏行业也正在经历前所未有的变革。玩家们不再满足于传统的线下游戏&#xff0c;而是转向了线上游戏。然而&#xff0c;随着游戏的线上化&#xff0c;游戏安全问题也日益凸显。游戏受到攻击是游戏开发者永远的痛点&#xff0c;谈“D“…

QtCharts使用

1.基础配置 1.QGraphicsView提升为QChartView#include <QtCharts> QT_CHARTS_USE_NAMESPACE #include "ui_widget.h"2. QT charts 2.柱状图 2.1QBarSeries //1.创建Qchart对象QChart *chart new QChart();chart->setTitle("直方图演示");//设…

回炉重造java----JUC(第二天)

Monitor---监视器/管程 对象头&#xff1a; 操作系统提供的Monitor对象 Synchronized底层实现原理&#xff1a; ①锁对象在加了synchronized之后&#xff0c;对象头中的Mark Word中就存了一个Monitor的地址指针。 ②当一个线程获取到锁之后&#xff0c;Monitor中的Owner属性指…

C++青少年简明教程:字符类型、字符数组和字符串

C青少年简明教程&#xff1a;字符类型、字符数组和字符串 在 C 语言中&#xff0c;处理文本数据的基础是字符类型 char&#xff0c;字符数组&#xff0c;以及标凌库中的字符串类 std::string。 C中的char类型占用 1 字节的内存空间&#xff0c;用于存储单个ASCII字符。例如&a…

ChatTTS web应用;基于文本指导的图像生成;使用Groq和Llama3在几秒内生成整本书;协作机器人画家,可以根据语言描述或图像在画布上作画

✨ 1: ChatTTS-Nuxt3 Webui ChatTTS-Nuxt3 WebUI是基于ChatTTS开发的文本转语音Web应用&#xff0c;支持详细参数调整和移动视图。 ChatTTS-Nuxt3 Webui 基于 ChatTTS 项目开发&#xff0c;由 2noise 创建&#xff0c;WebUI 开发则由 Gouryella 完成。你可以在 这里 免费试用…

C语言数字全排列生成器

前言 从0开始记录我的学习历程&#xff0c;我会尽我所能&#xff0c;写出最最大白话的文章&#xff0c;希望能够帮到你&#xff0c;谢谢。 提示&#xff1a;文章作者为初学者&#xff0c;有问题请评论指正&#xff0c;感谢。 这个代码的功能是生成并打印出从1到N的所有整数的…

zdppy_amauth 实现给角色批量绑定权限

新增接口 api.resp.post("/auth/role_auth", amauth.role.add_auths)如何测试 如何测试能不能给指定的角色批量的添加权限呢&#xff1f; 1、需要新建一个角色2、需要拿到这个角色的ID3、需要新增三个权限4、需要拿到新增的三个权限的ID5、拿着角色ID和权限ID列表…

通信技术振幅键控(ASK)调制与解调硬件实验

一、实验目的 1. 掌握用键控法产生ASK信号的方法&#xff1b; 2. 掌握ASK非相干解调的原理。 二、实验内容 1. 观察ASK已调信号的波形&#xff1b; 2. 观察ASK解调信号的波形。 三、实验器材 1. 双踪示波器&#xff1b; 2. 通信原理实验箱信号源模块、③、④、⑦号模块。…

输出流--6.6

代码以及解释&#xff1a; package java2;import java.io.File; import java.io.FileWriter; import java.io.IOException;public class Test02 {public static void main(String[] args) throws IOException {String str "flag{hello_ctf}";//1.封装File fnew Fil…

Locality-aware subgraphs for inductive link prediction in knowledge graphs

Locality-aware subgraphs for inductive link prediction in knowledge graphs a b s t r a c t 最近的知识图&#xff08;KG&#xff09;归纳推理方法将链接预测问题转化为图分类任务。 他们首先根据目标实体的 k 跳邻域提取每个目标链接周围的子图&#xff0c;使用图神经网…

鸿蒙轻内核M核源码分析系列六 任务及任务调度(2)任务模块

任务是操作系统一个重要的概念&#xff0c;是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它任务运行。鸿蒙轻内核的任务模块可以给用户提供多个任务&#xff0c;实现任务间的切换&#xff0c;帮助用户管理业务程序流程。…

鸿蒙北向开发 IDE DevEco Studio 3.1 傻瓜式安装闭坑指南

首先下载 安装IDE 本体程序 DevEco Studio 下载链接 当前最新版本是3.1.1,下载windows版本的 下载下来后是一个压缩包, 解压解锁包后会出现一个exe安装程序 双击运行安装程序 一路 next ( 这里涉及安装文件目录,我因为C盘够大所以全部默认了,各位根据自己情况选择自己的文件…

我的创作纪念日--我和CSDN一起走过的1825天

机缘 第一次在CSDN写文章&#xff0c;是自己在记录学习Java8新特性中Lambda表达式的内容过程中收获的学习心得。之前也有记录工作和生活中的心得体会、难点的解决办法、bug的排查处理过程等等。一直都用的有道笔记&#xff0c;没有去和大家区分享的想法&#xff0c;是一起的朋…

MotionEditor_ 通过内容感知扩散编辑视频运动

图1. MotionEditor&#xff1a;一种基于扩散的视频编辑方法&#xff0c;旨在将参考视频的运动转移到源视频中。 摘要 现有的基于扩散的视频编辑模型在随时间编辑源视频的属性方面取得了显著进展&#xff0c;但在修改运动信息的同时保持原始主角的外观和背景方面存在困难。为…

WordPress 插件推荐:菜单缓存插件——Menu Caching

今天在缙哥哥博客上发现了一个 WordPress 速度优化插件的优化感觉很不错&#xff0c;明月自己装上也体验了一番&#xff0c; WordPress 菜单的载入速度无论是 PC 端和移动端都非常不错&#xff0c;并且这个叫 Menu Caching 的菜单缓存插件还完美的兼容 WPRocket&#xff0c;W3 …