静态分析-RIPS-源码解析记录-01

token流扫描重构部分,这一部分主要利用php的token解析api解析出来的token流,对其中的特定token进行删除、替换、对于特定的语法结构进行重构,保持php语法结构上的一致性

解析主要在lib/scanner.php中通过Tokenizer这个类来实现,也就是在main.php中开始调用new scanner的对象,准备开始扫描漏洞,此时在scanner的构造函数中完成token流的解析

这里主要在scanner这个类的构造函数里面完整待扫描的文件的token解析,主要由以下四个步骤完成

接着到tokenize中进行具体的token解析,这里要借助php zend引擎自带的一个词法分析的函数,token_get_all();

 

 对于上面这个文件,解析以后如下图所示:

array(11) {[0] =>array(3) {[0] =>int(379)[1] =>string(6) "<?php
"[2] =>int(1)}[1] =>array(3) {[0] =>int(320)[1] =>string(2) "$a"[2] =>int(2)}[2] =>string(1) "="[3] =>string(1) "["[4] =>array(3) {[0] =>int(323)[1] =>string(3) ""a""[2] =>int(2)}[5] =>string(1) ","[6] =>array(3) {[0] =>int(323)[1] =>string(3) ""b""[2] =>int(2)}[7] =>string(1) "]"[8] =>string(1) ";"[9] =>array(3) {[0] =>int(382)[1] =>string(1) "
"[2] =>int(2)}[10] =>array(3) {[0] =>int(381)[1] =>string(3) "?>
"[2] =>int(3)}
}

解析出来的每个token以数组形式存在,默认一个token数组包括三个元素,0代表token常量,1代表token值,2代表行号,此时得到的是最初的token组,接下来要经过一系列的token处理,以为php中实际上变量表示方法多样,要做到统一才方便后面分析。

第一部分: 

prepare_tokens:

 function prepare_tokens(){    // delete whitespaces and other unimportant tokens, rewrite some special tokensfor($i=0, $max=count($this->tokens); $i<$max; $i++) //遍历token数组{if( is_array($this->tokens[$i]) )  //a. unset掉可忽略token,比如php的开始标签<?php以及一些空格,比如if   ()变为if(),方便后面进行条件语句的解析,以及像if(): xxx endif 中间的文本unset掉b. 闭合标签变分号;  c.<?= 标签表echo{if( in_array($this->tokens[$i][0], Tokens::$T_IGNORE) )unset($this->tokens[$i]);else if( $this->tokens[$i][0] === T_CLOSE_TAG )$this->tokens[$i] = ';';    else if( $this->tokens[$i][0] === T_OPEN_TAG_WITH_ECHO )$this->tokens[$i][1] = 'echo';} // @ (depress errors) disturbs connected token handlingelse if($this->tokens[$i] === '@')  //unset掉@符号{unset($this->tokens[$i]);}    // rewrite $array{index} to $array[index]  //对于数组处理如果当前是花括号并且前一个token是变量else if( $this->tokens[$i] === '{'&& isset($this->tokens[$i-1]) && ((is_array($this->tokens[$i-1]) && $this->tokens[$i-1][0] === T_VARIABLE)|| $this->tokens[$i-1] === ']') ) //或者上一个token是[,当前是{,则肯定是数组变量(主要是多维数组){$this->tokens[$i] = '['; //则令当前token为左方括号$f=1;while($this->tokens[$i+$f] !== '}') //此时while循环找下一个与当前花括号对应的右花括号{$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find closing brace of '.$this->tokens[$i-1][1].'{}.', array_slice($this->tokens, $i-1, 2), $this->tokens[$i-1][2], $this->filename);break;    //没找到则退出,说明语法有问题}}$this->tokens[$i+$f] = ']';  //否则令右花括号为]}    }// rearranged key index of tokens$this->tokens = array_values($this->tokens);}    

第二部分:

接着是对多维数组的重构:

// rewrite $arrays[] to $variables and save keys in $tokens[$i][3]

从代码注释可以看出这个函数将多维数组的所有键名保存在当前解析为变量token的数组第四个元素中

function array_reconstruct_tokens(){    for($i=0,$max=count($this->tokens); $i<$max; $i++) //遍历所有token数组{// check for arraysif( is_array($this->tokens[$i]) && $this->tokens[$i][0] === T_VARIABLE && $this->tokens[$i+1] === '[' ) //当前token是个变量,并且下一个token是[,则最少即为一维数组{    $this->tokens[$i][3] = array(); //初始化第四个元素为数组$has_more_keys = true;  $index = -1;$c=2;// loop until no more index found: array[1][2][3]while($has_more_keys && $index < MAX_ARRAY_KEYS) //while循环遍历多维数组,max默认为10(这个数已经够了){$index++;// save constant index as constant   //找到当前变量对应的右括号,主要是针对常量if(($this->tokens[$i+$c][0] === T_CONSTANT_ENCAPSED_STRING || $this->tokens[$i+$c][0] === T_LNUMBER || $this->tokens[$i+$c][0] === T_NUM_STRING || $this->tokens[$i+$c][0] === T_STRING) && $this->tokens[$i+$c+1] === ']'){         unset($this->tokens[$i+$c-1]); //unset掉左括号$this->tokens[$i][3][$index] = str_replace(array('"', "'"), '', $this->tokens[$i+$c][1]); //把键名放到第四个数组元素中unset($this->tokens[$i+$c]); //unset掉键名unset($this->tokens[$i+$c+1]); //unset掉右括号$c+=2; #c+2尝试找到下一维数组// save tokens of non-constant index as token-array for backtrace later  //$a[$b][][]对于这种非常量索引的情况  } else{$this->tokens[$i][3][$index] = array(); $newbraceopen = 1; //就当作是左括号的个数unset($this->tokens[$i+$c-1]); //unset掉左括号while($newbraceopen !== 0) {    if( $this->tokens[$i+$c] === '[' ){$newbraceopen++;  //哇,又遇到新的一个左括号}else if( $this->tokens[$i+$c] === ']' ) {$newbraceopen--; //此时说明一组左右括号遍历完}else{$this->tokens[$i][3][$index][] = $this->tokens[$i+$c]; //此时将变量索引对应的数组保存在第四个元素中 }    unset($this->tokens[$i+$c]); //unset掉该变量索引或unset掉右括号或左括号$c++; //就把它当作游标吧,游标不断滑动if(!isset($this->tokens[$i+$c])) //尝试找到=或者分号,实际就是结束当前数组的符号,没有找到则break退出{addError('Could not find closing bracket of '.$this->tokens[$i][1].'[].', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}unset($this->tokens[$i+$c-1]); //这一处unset为了处理特殊情况}if($this->tokens[$i+$c] !== '[')$has_more_keys = false;$c++;    }    $i+=$c-1;}}// return tokens with rearranged key index$this->tokens = array_values($this->tokens);        }

 比如对于下面这种索引为变量的数组:

 解析以后将所有维度的键名存储在$a这个token的第四个数组元素中

 

 这个算法设计的还是挺巧妙的,每一处unset都设计的很恰当

 

 这个unset就刚刚假设当前游标为数组索引或右括号,-1直接unset掉左括号

 

 ①处当遍历数组变量完成后则置为true,此时因为游标在=或者;处,因此c++完后$i应该+c-1回到=或者;处

对于$a[$b][][]处理完后就是:

 

第三部分:

fix_tokens:

这一部分主要是重构一些token信息

function fix_tokens(){    for($i=0; $i<($max=count($this->tokens)); $i++){// convert `backticks` to backticks()  #处理反引号if( $this->tokens[$i] === '`' ){        $f=1;while( $this->tokens[$i+$f] !== '`' )  #通过while循环来,将`xxx` 转换成backticks标识的token{    // get line_nr of any near tokenif( is_array($this->tokens[$i+$f]) )$line_nr = $this->tokens[$i+$f][2];  #此时反引号中间内容的行号$f++; #f++走到右反引号if(!isset($this->tokens[$i+$f]) || $this->tokens[$i+$f] === ';')  #无闭合则报错{addError('Could not find closing backtick `.', array_slice($this->tokens, $i, 5), $this->tokens[$i+1][2], $this->filename);break;    }}if(!empty($line_nr)) #若反引号中间内容不为空,则进行重构{ $this->tokens[$i+$f] = ')'; #将右引号变为圆括号)$this->tokens[$i] = array(T_STRING, 'backticks', $line_nr);  #将左反引号声明一个backticks的token// add element backticks() to array             $this->tokens = array_merge(   #对当前token进行重构array_slice($this->tokens, 0, $i+1), array('('),  #因为刚才将左反引号替换了,所以此时需要再添加一个左圆括号array_slice($this->tokens, $i+1) #拼接上后面从右圆括号开始的token,所以就是`xxx`  变为 backtricks(xxx));    }}#接下来要对一些条件语句、循环语句进行解析,主要为IF,else if,for,foreach,while// real tokenelse if( is_array($this->tokens[$i]) )  {    // rebuild if-clauses, for(), foreach(), while() without { } #首先重构没有花括号的,即只有方法体只有单条语句if ( ($this->tokens[$i][0] === T_IF || $this->tokens[$i][0] === T_ELSEIF || $this->tokens[$i][0] === T_FOR || $this->tokens[$i][0] === T_FOREACH || $this->tokens[$i][0] === T_WHILE) && $this->tokens[$i+1] === '(' ){        // skip condition in ( ) #这个while主要是跳过条件判断(),继续扫描后面的token,对token数组并不做处理$f=2;$braceopen = 1;while($braceopen !== 0 ) {if($this->tokens[$i+$f] === '(')$braceopen++;else if($this->tokens[$i+$f] === ')')$braceopen--;$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find closing parenthesis of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}    // alternate syntax while(): endwhile; #这个if条件主要是为了给php的替代语法结构加上左右花括号,对于每一种条件或者循环关键字,都对应了相应的结束标记,因此结合一个c变量,通过其自增来找到相应关键字的闭合token,然后再将:和endif都放到花括号内if($this->tokens[$i+$f] === ':'){switch($this->tokens[$i][0]){case T_IF:case T_ELSEIF: $endtoken = T_ENDIF; break;case T_FOR: $endtoken = T_ENDFOR; break;case T_FOREACH: $endtoken = T_ENDFOREACH; break;case T_WHILE: $endtoken = T_ENDWHILE; break;default: $endtoken = ';';}$c=1;while( $this->tokens[$i+$f+$c][0] !== $endtoken){$c++;if(!isset($this->tokens[$i+$f+$c])){addError('Could not find end'.$this->tokens[$i][1].'; of alternate '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, $f+1), $this->tokens[$i][2], $this->filename);break;    }}$this->wrapbraces($i+$f+1, $c+1, $i+$f+$c+2);}#这个if条件主要是为了针对 if() echo "1";只有一条指令,将其放到花括号内// if body not in { (and not a do ... while();) wrap next instruction in braceselse if($this->tokens[$i+$f] !== '{' && $this->tokens[$i+$f] !== ';'){$c=1;while($this->tokens[$i+$f+$c] !== ';' && $c<$max){$c++;}$this->wrapbraces($i+$f, $c+1, $i+$f+$c+1);}} #为else if 添加花括号// rebuild else without { }    else if( $this->tokens[$i][0] === T_ELSE && $this->tokens[$i+1][0] !== T_IF&& $this->tokens[$i+1] !== '{'){    $f=2;while( $this->tokens[$i+$f] !== ';' && $f<$max){        $f++;}$this->wrapbraces($i+1, $f, $i+$f+1);}// rebuild switch (): endswitch;   #switch语句的处理,和if while等差不多,先扫描跳过判断语句     else if( $this->tokens[$i][0] === T_SWITCH && $this->tokens[$i+1] === '('){$newbraceopen = 1;$c=2;while( $newbraceopen !== 0 ){// watch function calls in function callif( $this->tokens[$i + $c] === '(' ){$newbraceopen++;}else if( $this->tokens[$i + $c] === ')' ){$newbraceopen--;}                    else if(!isset($this->tokens[$i+$c]) || $this->tokens[$i + $c] === ';'){addError('Could not find closing parenthesis of switch-statement.', array_slice($this->tokens, $i, 10), $this->tokens[$i][2], $this->filename);break;    }$c++;}#此时达到switch的方法体,因为switch一般来说是带花括号的,但对于endswitch的情况需要特殊处理一下,变为花括号形式// switch(): ... endswitch;if($this->tokens[$i + $c] === ':'){$f=1;while( $this->tokens[$i+$c+$f][0] !== T_ENDSWITCH) #这里是通过f来找endswitch,c找:{$f++;if(!isset($this->tokens[$i+$c+$f])){addError('Could not find endswitch; of alternate switch-statement.', array_slice($this->tokens, $i, $c+1), $this->tokens[$i][2], $this->filename);break;    }}$this->wrapbraces($i+$c+1, $f+1, $i+$c+$f+2);}}// rebuild switch case: without { }    #switch处理完了,此时处理switch内部的case这一部分主要是将每一条case后面的全部补全花括号else if( $this->tokens[$i][0] === T_CASE ){$e=1;while($this->tokens[$i+$e] !== ':' && $this->tokens[$i+$e] !== ';') #找到分号{$e++;if(!isset($this->tokens[$i+$e])){addError('Could not find : or ; after '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}$f=$e+1;if(($this->tokens[$i+$e] === ':' || $this->tokens[$i+$e] === ';')&& $this->tokens[$i+$f] !== '{' && $this->tokens[$i+$f][0] !== T_CASE && $this->tokens[$i+$f][0] !== T_DEFAULT){$newbraceopen = 0;while($newbraceopen || (isset($this->tokens[$i+$f]) && $this->tokens[$i+$f] !== '}' && !(is_array($this->tokens[$i+$f]) && ($this->tokens[$i+$f][0] === T_BREAK || $this->tokens[$i+$f][0] === T_CASE || $this->tokens[$i+$f][0] === T_DEFAULT || $this->tokens[$i+$f][0] === T_ENDSWITCH) ) )){        if($this->tokens[$i+$f] === '{')$newbraceopen++;else if($this->tokens[$i+$f] === '}')    $newbraceopen--;$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find ending of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, $e+5), $this->tokens[$i][2], $this->filename);break;    }}if($this->tokens[$i+$f][0] === T_BREAK){if($this->tokens[$i+$f+1] === ';')$this->wrapbraces($i+$e+1, $f-$e+1, $i+$f+2);// break 3;    else$this->wrapbraces($i+$e+1, $f-$e+2, $i+$f+3);}    else{   # 无break的情况$this->wrapbraces($i+$e+1, $f-$e-1, $i+$f);}    $i++;}}// rebuild switch default: without { }  #针对default的情况,如果没有花括号,则添加花括号  else if( $this->tokens[$i][0] === T_DEFAULT&& $this->tokens[$i+2] !== '{' ){$f=2;$newbraceopen = 0;while( $this->tokens[$i+$f] !== ';' && $this->tokens[$i+$f] !== '}' || $newbraceopen ){        if($this->tokens[$i+$f] === '{')$newbraceopen++;else if($this->tokens[$i+$f] === '}')    $newbraceopen--;$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find ending of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}$this->wrapbraces($i+2, $f-1, $i+$f+1);}#函数名小写// lowercase all function names because PHP doesn't care    else if( $this->tokens[$i][0] === T_FUNCTION ){$this->tokens[$i+1][1] = strtolower($this->tokens[$i+1][1]);} #函数调用小写   else if( $this->tokens[$i][0] === T_STRING && $this->tokens[$i+1] === '('){$this->tokens[$i][1] = strtolower($this->tokens[$i][1]);}    // switch a do while with a while (the difference in loop rounds doesnt matter// and we need the condition to be parsed before the loop tokens)else if( $this->tokens[$i][0] === T_DO ){$f=2;$otherDOs = 0;// f = T_WHILE token position relative to i#此时去找到对应该DO的while的tokenwhile( $this->tokens[$i+$f][0] !== T_WHILE || $otherDOs ){        #忽略内层的DO while体if($this->tokens[$i+$f][0] === T_DO)$otherDOs++;else if($this->tokens[$i+$f][0] === T_WHILE)$otherDOs--;$f++; #用f来标志找到的while位置if(!isset($this->tokens[$i+$f])){addError('Could not find WHILE of DO-WHILE-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}// rebuild do while without {} (should never happen but we want to be sure)#对于do后不带花括号的情况,带上花括号if($this->tokens[$i+1] !== '{'){$this->wrapbraces($i+1, $f-1, $i+$f);// by adding braces we added two new tokens$f+=2; #因为在最外层的while前加了两个花括号占位,因此f+2才代表while的位置}#d代表while后的分号位置,这样不改变f所指的位置方便后面替换结构$d=1;// d = END of T_WHILE condition relative to iwhile( $this->tokens[$i+$f+$d] !== ';' && $d<$max ){$d++;}#对do while语句进行重构,变成while结构// reorder tokens and replace DO WHILE with WHILE$this->tokens = array_merge(array_slice($this->tokens, 0, $i), // before DO  array_slice($this->tokens, $i+$f, $d), // WHILE condition f指向while d指向while结束array_slice($this->tokens, $i+1, $f-1), // DO WHILE loop tokens i指向do循环体,f-1即内容结束array_slice($this->tokens, $i+$f+$d+1, count($this->tokens)) // rest of tokens without while condition  while之后的token数组);    }}    }// return tokens with rearranged key index$this->tokens = array_values($this->tokens);}

上面函数名小写要注意一点,php是弱类型语言,这里其本身不支持函数重载,即没有java的类的多态特性,但是同为解释型语言的python是支持函数重载的

第四部分:

token解析的最后一部分为:

function fix_ternary(){for($i=0,$max=count($this->tokens); $i<$max; $i++){if( $this->tokens[$i] === '?' ){unset($this->tokens[$i]);// condition in brackets: fine, delete conditionif($this->tokens[$i-1] === ')'){   #先找到)右括号,然后减f一直找到左括号,一直unset到左括号(unset($this->tokens[$i-1]);// delete tokens till ( $newbraceopen = 1;$f = 2;while( $newbraceopen !== 0 && $this->tokens[$i - $f] !== ';'){if( $this->tokens[$i - $f] === '(' ){$newbraceopen--;}else if( $this->tokens[$i - $f] === ')' ){$newbraceopen++;}unset($this->tokens[$i - $f]);    $f++;if(($i-$f)<0){addError('Could not find opening parenthesis in ternary operator (1).', array_slice($this->tokens, $i-5, 10), $this->tokens[$i+1][2], $this->filename);break;    }}#判断左括号左边是否是!或是自定义函数调用或者是isset、empty函数调用//delete token before, if T_STRINGif($this->tokens[$i-$f] === '!' || (is_array($this->tokens[$i-$f]) && ($this->tokens[$i-$f][0] === T_STRING || $this->tokens[$i-$f][0] === T_EMPTY || $this->tokens[$i-$f][0] === T_ISSET))){unset($this->tokens[$i-$f]);}}// condition is a check or assignment#判断问号之前是不是逻辑比较,是的话肯定有操作数,需要unset掉else if(in_array($this->tokens[$i-2][0], Tokens::$T_ASSIGNMENT) || in_array($this->tokens[$i-2][0], Tokens::$T_OPERATOR) ){// remove both operandsunset($this->tokens[$i-1]); #右操作数删除unset($this->tokens[$i-2]); #删除运算符// if operand is in bracesif($this->tokens[$i-3] === ')')  #判断左边是否是函数调用,跟上面unset过程差不多,理想情况下是a()==1,但是对于1==a()没有考虑进去,因此对于这种unset并不能完全消除token,就直接走上面第一种a()这种形式的token解析{// delete tokens till ( $newbraceopen = 1;$f = 4;while( $newbraceopen !== 0 ){if( $this->tokens[$i - $f] === '(' ){$newbraceopen--;}else if( $this->tokens[$i - $f] === ')' ){$newbraceopen++;}unset($this->tokens[$i - $f]);    $f++;if(($i-$f)<0 || $this->tokens[$i - $f] === ';'){addError('Could not find opening parenthesis in ternary operator (2).', array_slice($this->tokens, $i-8, 6), $this->tokens[$i+1][2], $this->filename);break;    }}#删除函数调用//delete token before, if T_STRINGif(is_array($this->tokens[$i-$f]) && ($this->tokens[$i-$f][0] === T_STRING || $this->tokens[$i-$f][0] === T_EMPTY || $this->tokens[$i-$f][0] === T_ISSET)){unset($this->tokens[$i-$f]);}}unset($this->tokens[$i-3]);}// condition is a single variable, delete#对于单变量  $a? unset掉else if(is_array($this->tokens[$i-1]) && $this->tokens[$i-1][0] === T_VARIABLE){unset($this->tokens[$i-1]);}}    }// return tokens with rearranged key index$this->tokens = array_values($this->toknes);    }

这一部分主要就是对于三元操作符删除掉?前面的判断条件,此时只保留?后面的两种取值情况

参考:

https://xz.aliyun.com/t/2605#toc-6  

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

欢乐钓鱼大师辅助,2024年攻略大全!

在探索欢乐钓鱼大师的世界时&#xff0c;成功的关键在于全面考虑各种影响钓鱼效果的因素。以下是五大关键要素&#xff0c;掌握它们&#xff0c;你也能成为一名钓鱼高手&#xff01; 一、黄金钓点&#xff1a;位置决定一切 选择正确的钓点至关重要。考虑湖泊、河流和小溪的水深…

[算法][单调栈] [leetcode]316. 去除重复字母

去除重复字母 给你一个字符串 s &#xff0c;请你去除字符串中重复的字母&#xff0c;使得每个字母只出现一次。需保证 返回结果的 字典序最小&#xff08;要求不能打乱其他字符的相对位置&#xff09;。 字典序最小&#xff1a; 考虑字符串 a 与 字符串 b&#xff0c;如果字…

luceda ipkiss教程 71:统计线路中器件的个数

**案例分享&#xff1a;**统计线路中某一器件的个数 如&#xff0c;统计SplitterTree中mmi的个数&#xff1a; 所有代码如下&#xff1a; # Copyright (C) 2020 Luceda Photonicsfrom si_fab import all as pdk from ipkiss3 import all as i3class GeneralizedSplitterTree…

Codeforces Round 605 (Div. 3) A~D

本人水平不高&#xff0c;开这个专栏主要是督促自己补题&#xff0c;有些题对目前的我来说还比较难&#xff0c;还补不动&#xff0c;等以后能力上来了再补。。。 原题链接&#xff1a;Dashboard - Codeforces Round 605 (Div. 3) - Codeforces 目录 A. Three Friends B. Sn…

国产银河麒麟V10SP1系统下搭建TiDB数据库操作步骤图文

开发目的&#xff1a;在国产银河麒麟系统中搭建TiDB数据库运行环境。 开发工具&#xff1a;银河麒麟系统V10SP1TiDBMySql数据库8.0。 具体步骤&#xff1a; 1、在VmWare虚拟机中安装好国产银河麒麟V10Sp1操作系统。 2、打开终端命令&#xff0c;安装TiDB相关软件&#xff1…

Abiotic Factor 非生物因素开服教程

1、购买后登录服务器&#xff08;百度搜索莱卡云&#xff09;game.lcayun.com 进入控制面板后会出现正在安装的界面&#xff0c;安装时长约5分钟左右&#xff08;如长时间处于安装中请联系我们的客服人员&#xff09; 2、复制查询端口 点击网络可以看到两个端口&#xff0c;首…

【Python基础】装饰器(3848字)

文章目录 [toc]闭包什么是装饰器装饰器示例不使用装饰器语法使用装饰器语法 装饰器传参带参数的装饰器类装饰器魔术方法\__call__()类装饰器示例带参数类装饰器property装饰器分页操作商品价格操作 个人主页&#xff1a;丷从心 系列专栏&#xff1a;Python基础 学习指南&…

信创应用软件之办公流版签

信创应用软件之办公流版签 文章目录 信创应用软件之办公流版签概述流式文件版式文件电子签章厂商金山办公永中-永中Office中标-中标普华Office福昕科技e签宝法大大 概述 办公流版签软件主要包括办公中常用到的流式软件、版式软件以及电子签章。 版式文件和流式文件都是文书类…

飞利浦|西圣开放式耳机怎么选?爆款机型深度对比!

现在&#xff0c;开放式耳机以其独特的非入耳设计&#xff0c;成为了市场上的新宠。开放式耳机在佩戴上不仅能让我们长时间享受舒适的体验&#xff0c;更告别了入耳式耳机会导致的耳闷头昏的烦恼。但是现在&#xff0c;面对市场上琳琅满目的品牌和型号&#xff0c;许多消费者感…

LabVIEW学习记录4-局部变量、全局变量、共享变量

【LabVIEW】局部变量、全局变量、共享变量 一、变量定义二、内存分配三、竞争状态四、变量创建及简单使用示例4.1 局部变量4.1.1 局部变量的创建4.1.2 局部变量的编程实例 4.2 全局变量4.2.1 创建4.2.2 调用4.2.3 编程实例 4.3 共享变量 一、变量定义 LabVIEW&#xff08;Labor…

C++:二叉搜索树的底层模拟实现

概念&#xff1a; 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 搜索二叉树的操作&#xff1a; int a[] {8, 3, 1, 10, 6, 4, 7, 14, 13};二叉搜索树需要满足左子树比根小&#xff0c;右子树比根大&#xff0c;…

2024第十六届“中国电机工程学会杯”数学建模A题B题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…