c++new时赋初值_优质 quot;嵌入式C编程quot; 必备指南

来自公众号 : 嵌入式ARM

整理:bug菌

1、聊一聊

本文是bug菌为大家整理的好文,C语言其实是非常简洁的语言,语法相比那些高级语言可以说非常小巧了,然而C语言在嵌入式中却有着其独特的魅力,本文为大家展示了C语言在嵌入式中的特色,大家参考学习一下。文章中也融入了bug菌的一些观点,仅供大家学习参考!

2、前言

本文从语法上来说C语言并不复杂, 但编写优质可靠的嵌入式C程序并非易事,不仅需要熟知硬件特性和缺陷,还需要对编译原理和计算机技术知识有着一定的了解。本文以嵌入式实践为基础,再结合相关资料, 阐述嵌入式需要了解的C语言知识和重点,希望每个读到这篇文章的人都能有所收获。

3、关键字

关键字是C语言中具有特殊功能的保留标示符,按照功能可分为1). 数据类型(常用char, short, int, long, unsigned, float, double)2). 运算和表达式( =, +, -, *, while, do-while, if, goto, switch-case)3). 数据存储(auto, static, extern,const, register,volatile,restricted),4). 结构(struct, enum, union,typedef),5). 位操作和逻辑运算(<>, &, |, ~,^, &&),6). 预处理(#define, #include, #error,#if...#elif...#else...#endif等),7). 平台扩展关键字(__asm, __inline,__syscall)这些关键字共同构成了嵌入式平台的C语法。(bugPS:平台扩展关键字大家可以到各个平台手册中查找,往往这些扩展能够帮助大家更好的调试程序等,不过也会存在跨平台可移植性问题)嵌入式的应用从逻辑上可以抽象为三个部分:1). 数据的输入(如传感器,信号,接口输入),2). 数据的处理(如协议的解码和封包,AD采样值的转换等)3). 数据的输出(GUI的显示,输出的引脚状态,DA的输出控制电压,PWM波的占空比等),对于数据的管理就贯穿着整个嵌入式应用的开发,它包含数据类型,存储空间管理,位和逻辑操作,以及数据结构,C语言从语法上支撑上述功能的实现,并提供相应的优化机制,以应对嵌入式下更受限的资源环境。

4、数据类型

C语言支持常用的字符型,整型,浮点型变量,有些编译器如keil还扩展支持bit(位)和sfr(寄存器)等数据类型来满足特殊的地址操作。C语言只规定了每种基本数据类型的最小取值范围,因此在不同芯片平台上相同类型可能占用不同长度的存储空间,这就需要在代码实现时考虑后续移植的兼容性,而C语言提供的typedef就是用于处理这种情况的关键字,在大部分支持跨平台的软件项目中被采用,典型的如下:
typedef unsigned char uint8_t;typedef unsigned short uint16_t;typedef unsigned int uint32_t;......typedef signed int int32_t;
既然不同平台的基本数据宽度不同,那么如何确定当前平台的基础数据类型如int的宽度,这就需要C语言提供的接口sizeof,实现如下。
printf("int size:%d, short size:%d, char size:%d\n", sizeof(int), sizeof(char), sizeof(short));
这里还有重要的知识点,就是指针的宽度,如
char *p;printf("point p size:%d\n", sizeof(p));
其实这就和芯片的可寻址宽度有关,如32位MCU的宽度就是4,64位MCU的宽度就是8,在有些时候这也是查看MCU位宽比较简单的方式。(bugPS:在熟悉一款新的芯片时候,直接把这些参数打印出来,不然这些注意点比较零碎在文档上查找比较麻烦)

5、内存管理与存储架构

C语言允许程序变量在定义时就确定内存地址,通过作用域,以及关键字extern,static,实现了精细的处理机制,按照在硬件的区域不同,内存分配有三种方式(节选自C++高质量编程):1). 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。2). 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中 ,效率很高,但是分配的内存容量有限。(bugPS:不过还是存在入栈和出栈的时间消耗)3). 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但同时遇到问题也最多。(bugPS:这一块也是最容易出问题的,分配了不释放,释放了又访问都是不正确的,也是非常考验程序员的编程素质)这里先看个简单的C语言实例。
//main.c#include #include static int st_val;                   //静态全局变量 -- 静态存储区int ex_val;                           //全局变量 -- 静态存储区int main(void){   int a = 0;                         //局部变量 -- 栈上申请   int *ptr = NULL;                   //指针变量   static int local_st_val = 0;       //静态变量   local_st_val += 1;   a = local_st_val;   ptr = (int *)malloc(sizeof(int)); //从堆上申请空间   if(ptr != NULL)   {          printf("*p value:%d", *ptr);    free(ptr);          ptr = NULL;          //free后需要将ptr置空,否则会导致后续ptr的校验失效,出现野指针       }            }    
C语言的作用域不仅描述了标识符的可访问的区域,其实也规定了变量的存储区域,在文件作用域的变量st_val和ex_val被分配到静态存储区,其中static关键字主要限定变量能否被其它文件访问,而代码块作用域中的变量a, ptr和local_st_val则要根据类型的不同,分配到不同的区域,其中a是局部变量,被分配到栈中,ptr作为指针,由malloc分配空间,因此定义在堆中,而local_st_val则被关键字限定,表示分配到静态存储区。这里就涉及到重要知识点,static在文件作用域和代码块作用域的意义是不同的:在文件作用域用于限定函数和变量的外部链接性(能否被其它文件访问), 在代码块作用域则用于将变量分配到静态存储区。(bugPS:这里的代码块一般指的是用大括号括起来的区域,当然文件中的static同样也是分配到静态全局存储区)对于C语言,如果理解上述知识对于内存管理基本就足够,但对于嵌入式C来说,定义一个变量,它不一定在内存(SRAM)中,也有可能在FLASH空间,或直接由寄存器存储(register定义变量或者高优化等级下的部分局部变量),如:定义为const的全局变量定义在Flash中(BugPS:对于MCU一般都会放在Flash,不过具体还是需要看对应的映射文件,参考:【MCU】一种单片机节省内存的方法(补充));定义为register的局部变量会被优化到直接放在通用寄存器中(bugPS:register修饰的仅仅是可能分配到:具体可以参考: 顿悟,神秘的register关键字(C语言篇)),在优化运行速度,或者存储受限时,理解这部分知识对于代码的维护就很有意义。此外,嵌入式C语言的编译器中会扩展内存管理机制,如支持分散加载机制和__attribute__((section("用户定义区域"))),允许指定变量存储在特殊的区域如(SDRAM, SQI FLASH), 这强化了对内存的管理,以适应复杂的应用环境场景和需求。
LD_ROM 0x00800000 0x10000 { ;load region size_region    EX_ROM 0x00800000 0x10000 { ;load address = execution address  *.o (RESET, +First)  *(InRoot$$Sections)  .ANY (+RO)  }  EX_RAM 0x20000000 0xC000 { ;rw Data    .ANY (+RW +ZI)  }  EX_RAM1 0x2000C000 0x2000 {    .ANY(MySection)   }  EX_RAM2 0x40000000 0x20000{    .ANY(Sdram)  }}int a[10] __attribute__((section("Mysection")));int b[100] __attribute__((section("Sdram")));
采用这种方式,我们就可以将变量指定到需要的区域,这在某些情况下是必须的,如做GUI或者网页时因为要存储大量图片和文档,内部FLASH空间可能不足,这时就可以将变量声明到外部区域,另外内存中某些部分的数据比较重要,为了避免被其它内容覆盖,可能需要单独划分SRAM区域,避免被误修改导致致命性的错误,这些经验在实际的产品开发中是常用且重要,不过因为篇幅原因,这里只简略的提供例子,如果工作中遇到这种需求,建议详细去了解下。至于堆的使用,对于嵌入式Linux来说,使用起来和标准C语言一致,注意malloc后的检查,释放后记得置空,避免"野指针“,不过对于资源受限的单片机来说,使用malloc的场景一般较少,如果需要频繁申请内存块的场景,都会构建基于静态存储区和内存块分割的一套内存管理机制,一方面效率会更高(用固定大小的块提前分割,在使用时直接查找编号处理),另一方面对于内存块的使用可控,可以有效避免内存碎片的问题,常见的如RTOS和网络LWIP都是采用这种机制,我个人习惯也采用这种方式,所以关于堆的细节不在描述,如果希望了解,可以参考中关于存储相关的说明。

6、指针与数组

数组和指针往往是引起程序bug的主要原因,如数组越界,指针越界,非法地址访问,非对齐访问,这些问题背后往往都有指针和数组的影子,因此理解和掌握指针和数组,是成为合格C语言开发者的必经之路。数组是由相同类型元素构成,当它被声明时,编译器就根据内部元素的特性在内存中分配一段空间,另外C语言也提供多维数组,以应对特殊场景的需求,而指针则是提供使用地址的符号方法,只有指向具体的地址才有意义,C语言的指针具有最大的灵活性,在被访问前,可以指向任何地址,这大大方便了对硬件的操作,但同时也对开发者有了更高的要求。参考如下代码。
int main(void){  char cval[] = "hello";  int i;  int ival[] = {1, 2, 3, 4};  int arr_val[][2] = {{1, 2}, {3, 4}};  const char *pconst = "hello";  char *p;  int *pi;  int *pa;  int **par;  p = cval;  p++;            //addr增加1  pi = ival;  pi+=1;          //addr增加4  pa = arr_val[0];  pa+=1;          //addr增加4  par = arr_val;  par++;         //addr增加8  for(i=0; i  {      printf("%d ", cval[i]);  }  printf("\n");  printf("pconst:%s\n", pconst);  printf("addr:%d, %d\n", cval, p);  printf("addr:%d, %d\n", icval, pi);  printf("addr:%d, %d\n", arr_val, pa);  printf("addr:%d, %d\n", arr_val, par);}/* PC端64位系统下运行结果0x68 0x65 0x6c 0x6c 0x6f 0x0pconst:helloaddr:6421994, 6421995addr:6421968, 6421972addr:6421936, 6421940addr:6421936, 6421944 */
对于数组来说,一般从0开始获取值,以length-1作为结束,通过[0, length)半开半闭区间访问,这一般不会出问题,但是某些时候,我们需要倒着读取数组时,有可能错误的将length作为起始点,从而导致访问越界,另外在操作数组时,有时为了节省空间,将访问的下标变量i定义为unsigned char类型,而C语言中unsigned char类型的范围是0~255,如果数组较大,会导致数组超过时无法截止,从而陷入死循环,这种在最初代码构建时很容易避免,但后期如果更改需求,在加大数组后,在使用数组的其它地方都会有隐患,需要特别注意。在前面提到过,指针占有的空间与芯片的寻址宽度有关,32位平台为4字节,64位为8字节,而指针的加减运算中的长度又与它的类型相关,如char类型为1,int类型为4,如果你仔细观察上面的代码就会发现par的值增加了8,这是因为指向指针的指针,对应的变量是指针,也就是长度就是指针类型的长度,在64位平台下为8,如果在32位平台则为4,这些知识理解起来并不困难,但是这些特性在工程运用中稍有不慎,就会埋下不易察觉的问题。另外指针还支持强制转换,这在某些情况下相当有用,参考如下代码:
#include typedef struct{  int b;  int a;}STRUCT_VAL;static __align(4) char arr[8] = {0x12, 0x23, 0x34, 0x45, 0x56, 0x12, 0x24, 0x53};int main(void){    STRUCT_VAL *pval;    int *ptr;    pval = (STRUCT_VAL *)arr;    ptr = (int *)&arr[4];    printf("val:%d, %d", pval->a, pval->b);    printf("val:%d,", *ptr);}//0x45342312 0x53241256//0x53241256
基于指针的强制转换,在协议解析,数据存储管理中高效快捷的解决了数据解析的问题(bugPS:bug菌之前的文章也有提到过,强转结构体直接索引解析,还是比较好用的),但是在处理过程中涉及的数据对齐,大小端,是常见且十分易错的问题,如上面arr字符数组,通过__align(4)强制定义为4字节对齐是必要的,这里可以保证后续转换成int指针访问时,不会触发非对齐访问异常,如果没有强制定义,char默认是1字节对齐的,当然这并不就是一定触发异常(由整个内存的布局决定arr的地址,也与实际使用的空间是否支持非对齐访问有关,如部分SDRAM使用非对齐访问时,会触发异常), 这就导致可能增减其它变量,就可能触发这种异常,而出异常的地方往往和添加的变量毫无关系,而且代码在某些平台运行正常,切换平台后触发异常,这种隐蔽的现象是嵌入式中很难查找解决的问题。另外,C语言指针还有特殊的用法就是通过强制转换给特定的物理地址访问,通过函数指针实现回调,如下:
#include typedef int (*pfunc)(int, int);int func_add(int a, int b){ return a+b;}int main(void){    pfunc *func_ptr;    *(volatile uint32_t *)0x20001000 = 0x01a23131;    func_ptr = func_add;    printf("%d\n", func_ptr(1, 2));}
这里说明下,volatile易变的,可变的,一般用于以下几种状况:1)并行设备的硬件寄存器(如:状态寄存器)2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)3)多线程应用中被几个任务共享的变量volatile可以解决用户模式和异常中断访问同一个变量时,出现的不同步问题,另外在访问硬件地址时,volatile也阻止对地址访问的优化,从而确保访问的实际的地址,精通volatile的运用,在嵌入式底层中十分重要,也是嵌入式C从业者的基本要求之一。函数指针在一般嵌入式软件的开发中并不常见,但对许多重要的实现如异步回调,驱动模块,使用函数指针就可以利用简单的方式实现很多应用,当然我这里只能说是抛砖引玉,许多细节知识是值得详细去了解掌握的。

7、结构体类型与对齐

C语言提供自定义数据类型来描述一类具有相同特征点的事务,主要支持的有结构体,枚举和联合体。其中枚举通过别名限制数据的访问,可以让数据更直观,易读,实现如下:
typedef enum {spring=1, summer, autumn, winter }season;season s1 = summer;
联合体的是能在同一个存储空间里存储不同类型数据的数据类型,对于联合体的占用空间,则是以其中占用空间最大的变量为准,如下:
typedef union{       char c;       short s;       int i; }UNION_VAL;UNION_VAL val; int main(void) {       printf("addr:0x%x, 0x%x, 0x%x\n",                     (int)(&(val.c)), (int)(&(val.s)), (int)(&(val.i)));       val.i = 0x12345678;       if(val.s == 0x5678)             printf("小端模式\n");         else             printf("大端模式\n");     } /*addr:0x407970, 0x407970, 0x407970 小端模式*/
联合体的用途主要通过共享内存地址的方式,实现对数据内部段的访问,这在解析某些变量时,提供了更为简便的方式,此外测试芯片的大小端模式也是联合体的常见应用,当然利用指针强制转换,也能实现该目的,实现如下:
int data = 0x12345678; short *pdata = (short *)&data; if(*pdata = 0x5678)       printf("%s\n", "小端模式"); else     printf("%s\n", "大端模式");
可以看出使用联合体在某些情况下可以避免对指针的滥用。结构体则是将具有共通特征的变量组成的集合,比起C++的类来说,它没有安全访问的限制,不支持直接内部带函数,但通过自定义数据类型,函数指针,仍然能够实现很多类似于类的操作,对于大部分嵌入式项目来说,结构化处理数据对于优化整体架构以及后期维护大有便利,下面举例说明:
typedef int (*pfunc)(int, int); typedef struct{       int num;       int profit;       pfunc get_total; }STRUCT_VAL;int GetTotalProfit(int a, int b){       return a*b; }  int main(void){       STRUCT_VAL Val;       STRUCT_VAL *pVal;        Val.get_total = GetTotalProfit;       Val.num = 1;       Val.profit = 10;       printf("Total:%d\n",  Val.get_total(Val.num, Val.profit));  //变量访问      pVal = &Val;       printf("Total:%d\n",  pVal->get_total(pVal->num, pVal->profit)); //指针访问 } /* Total:10 Total:10 */
C语言的结构体支持指针和变量的方式访问,通过转换可以解析任意内存的数据(如我们之前提到的通过指针强制转换解析协议),另外通过将数据和函数指针打包,在通过指针传递,是实现驱动层实接口切换的重要基础,有着重要的实践意义,另外基于位域,联合体,结构体,可以实现另一种位操作,这对于封装底层硬件寄存器具有重要意义,实践如下:
typedef unsigned char uint8_t;   union reg{         struct{             uint8_t bit0:1;             uint8_t bit1:1;             uint8_t bit2_6:5;             uint8_t bit7:1;       }bit;       uint8_t all; }; int main(void){       union reg RegData;       RegData.all = 0;        RegData.bit.bit0 = 1;       RegData.bit.bit7 = 1;       printf("0x%x\n", RegData.all);        RegData.bit.bit2_6 = 0x3;       printf("0x%x\n", RegData.all); } /* 0x81 0x8d*/
通过联合体和位域操作,可以实现对数据内bit的访问,这在寄存器以及内存受限的平台(bugPS:省内存的办法),提供了简便且直观的处理方式,另外对于结构体的另一个重要知识点就是对齐了,通过对齐访问,可以大幅度提高运行效率,但是因为对齐引入的存储长度问题,也是容易出错的问题,对于对齐的理解,可以分类为如下说明。基础数据类型:以默认的的长度对齐,如char以1字节对齐,short以2字节对齐等数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。联合体 :按其包含的长度最大的数据类型对齐。结构体:结构体中每个数据类型都要对齐,结构体本身以内部最大数据类型长度对齐
union DATA{       int a;       char b; };  struct BUFFER0{       union DATA data;       char a;       //reserved[3]       int b;       short s;       //reserved[2] }; //16字节  struct BUFFER1{       char a;                //reserved[0]       short s;      union DATA data;       int b; };//12字节  int main(void) {       struct BUFFER0 buf0;       struct BUFFER1 buf1;            printf("size:%d, %d\n", sizeof(buf0), sizeof(buf1));       printf("addr:0x%x, 0x%x, 0x%x, 0x%x\n",                     (int)&(buf0.data), (int)&(buf0.a), (int)&(buf0.b), (int)&(buf0.s));            printf("addr:0x%x, 0x%x, 0x%x, 0x%x\n",                     (int)&(buf1.a), (int)&(buf1.s), (int)&(buf1.data), (int)&(buf1.b)); } /* size:16, 12 addr:0x61fe10, 0x61fe14, 0x61fe18, 0x61fe1c addr:0x61fe04, 0x61fe06, 0x61fe08, 0x61fe0c */
其中union联合体的大小与内部最大的变量int一致,为4字节,根据读取的值,就知道实际内存布局和填充的位置是一致,事实上学会通过填充来理解C语言的对齐机制,是有效且快捷的方式。

8、预处理机制

C语言提供了丰富的预处理机制,方便了跨平台的代码的实现,此外C语言通过宏机制实现的数据和代码块替换,字符串格式化,代码段切换,对于工程应用具有重要意义,下面按照功能需求,描述在C语言运用中的常用预处理机制。#include 包含文件命令,在C语言中,它执行的效果是将包含文件中的所有内容插入到当前位置,这不只包含头文件,一些参数文件,配置文件,也可以使用该文件插入到当前代码的指定位置。其中<>和""分别表示从标准库路径还是用户自定义路径开始检索。#define宏定义,常见的用法包含定义常量或者代码段别名,当然某些情况下配合##格式化字符串,可以实现接口的统一化处理,实例如下:
#define MAX_SIZE  10#define MODULE_ON  1#define ERROR_LOOP() do{\                     printf("error loop\n");\                   }while(0);#define global(val) g_##valint global(v) = 10;int global(add)(int a, int b){    return a+b;
#if..#elif...#else...#endif, #ifdef..#endif, #ifndef...#endif条件选择判断,条件选择主要用于切换代码块,这种综合性项目和跨平台项目中为了满足多种情况下的需求往往会被使用。#undef 取消定义的参数,避免重定义问题。#error,#warning用于用户自定义的告警信息,配合#if,#ifdef使用,可以限制错误的预定义配置。#pragma 带参数的预定义处理,常见的#pragma pack(1), 不过使用后会导致后续的整个文件都以设置的字节对齐,配合push和pop可以解决这种问题,代码如下:
#pragma pack(push)#pragma pack(1)struct TestA{   char i;   int b;}A;#pragma pack(pop); //注意要调用pop,否则会导致后续文件都以pack定义值对齐,执行不符合预期等同于 struct _TestB{     char i;   int b; }__attribute__((packed))A;

9、总结

如果你看到了这里,那么应该对C语言有了比较清晰的认识,嵌入式C语言在处理硬件物理地址,位操作,内存访问,都给予开发者了充分的自由,通过数组,指针以及强制转换的技巧,可以有效减少数据处理中的复制过程,这对于底层是必要的,也方便了整个架构的开发。但是由这种自由带来的非法访问,溢出,越界,以及不同硬件平台对齐,数据宽度,大小端问题,在功能设计人员手里一般还能够处理,对于后续接手项目的人来说,如果本身的设计没有考虑清楚这些问题,往往代表着问题和麻烦,所以对于任何嵌入式C的从业者,清晰的掌握这些基础的知识和必要的。讲到这里,关于嵌入式C语言的初步总结就到此为止,但C语言在嵌入式运用的中的重点和难点并不仅仅只有这些,如嵌入式C语言支持的内联汇编,通讯间的可靠性实现,存储数据校验和完整性保证,这些工程上的运用和技巧,都很难用简单的言语说清楚,另外有关异常触发后的查找和解决的技巧,也值得详细的说明,这里因为篇幅以及自己还未整理清晰,就先到此为止。-END-

免责声明:整理文章为传播相关技术,版权归原作者所有如有侵权,请联系删除,非常感谢!


●输入m获取文章目录

C语言与C++编程

99e266dd119aa3920a5ba6de1b2038e4.png

分享C/C++技术文章

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

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

相关文章

二手交易平台html代码,二手物品交易HTML5模板

二手物品交易HTML5模板资源下载此资源下载价格为4D币&#xff0c;请先登录资源文件列表codedown123-0820-18/about.html , 9117codedown123-0820-18/contact.html , 8364codedown123-0820-18/css/app.css , 115913codedown123-0820-18/img/accountcover.jpg , 74024codedown123…

浏览器安装Axure插件与配置

Axure发布到本地的html包&#xff0c;打开需要Axure插件。 chrome浏览器安装插件需要翻墙或者导入下载好的插件文件&#xff0c;不是太方便。国内的360极速&#xff0c;qq浏览器的应用扩展不需要翻墙&#xff0c;可以直接搜索安装。下面演示360极速浏览器的Axure插件安装与配置…

matlab 滤波器设计 coe_一种半带滤波器的低功耗实现方法

在如今数字技术中&#xff0c;半带滤波器因其通带阻带对称&#xff0c;系数具有偶对称性且滤波器阶数为奇数&#xff0c;有效系数少等特点广泛应用于通信、视频处理、语音识别等数字信号处理应用中&#xff0c;尤其常用于实现信号的2倍抽取。对于一个阶数为N(N为偶数)&#xff…

产品设计:APP个人信息保护指引

需求分析 2019年11月4日&#xff0c;工业和信息化部展开APP侵犯用户权益专项整治行动。即日起各安卓应用市场根据最新的规则审核市场里的各应用&#xff0c;审核不通过将下架处理。 调研了“手机qq”、“抖音”、“快手”、“今日头条”、“澎湃新闻”等APP&#xff0…

说说GIL

上一篇&#xff1a;线程深入篇引入 Code&#xff1a;https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Thread/3.GIL 说说GIL 尽管Python完全支持多线程编程&#xff0c; 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的&#xff0c;所以这时候…

2021重庆高考成绩名次排名查询,重庆高考排名对应大学-重庆高考位次大学(2021年理科)...

选择科目测一测我能上哪些大学选择科目领取你的专属报告>选择省份关闭请选择科目确定v>每年高考结束后&#xff0c;报大学、选专业、填志愿就成了考生与家长十分关心的一件事情。本期&#xff0c;圆梦志愿为大家整理了重庆高考理科2020年位次排名对应的大学&#xff0c;供…

Project项目信息的日程排定方法区别

日程排定方法分&#xff1a;项目开始日期&#xff0c;项目结束日期。 项目开始日期 设置如下 在“工期”单元格输入任意数字&#xff0c;任务开始日期会从项目开始日期2020年3月1日开始 给项目任务设置工期的时候&#xff0c;从任务的第一个开始设置&#xff0c;按正序进行&a…

gis里创建要素面板怎么打开_周末技术流 | GIS三维热力图分析

周末技术流&#xff2e;&#xff2f;&#xff37;现在行动&#xff01;我们的技术流是一个系列&#xff0c;最终带大家出一套完整图纸哦~(未经允许严禁盗用&#x1f6ab;)Rhino日照分析1.前期回顾本期内容一直关注我们的朋友到这期可能会有点熟悉&#xff0c;确实&#xff0c;我…

project提醒:无法链接这些任务,因为它们已通过另一个任务链链接

给45任务指定前置任务111时&#xff0c;提示“无法链接这些任务&#xff0c;因为它们已通过另一个任务链链接” 查了好久没找到原因&#xff0c;后来无意在46任务前置任务输入111&#xff0c;没有提示。 解决方法&#xff1a; 删除了提示的45任务&#xff0c;新建任务&#x…

企业网站 源码 e-mail_天津seo优化套餐服务收费_天津网站优化关键词价格

天津华阳在线专注于SEO关键词排名优化&#xff0c;品牌网站建设&#xff0c;营销型网站建设&#xff0c;App、小程序开发&#xff0c;搜索引擎seo优化&#xff0c;竞价托管sem&#xff0c;品牌口碑建设与代运营等服务。企业通过引进前BAT产品经理不断丰富产品线优化技术实力&am…

必须Mark下,2019 年度中国质量协会质量技术优秀奖

曾经和一群可爱的人儿做的项目&#xff0c;获得了2019 年度中国质量协会质量技术优秀奖&#xff0c;无心插柳柳成荫。 那几年工作得很快乐&#xff0c;工作与家庭都兼顾&#xff0c;是同事也是朋友。2019年末去过一次移动宁波分公司&#xff0c;特意去看了原来驻场的办公室&am…

python文件编码及执行

兼容中文编码 由于Python源代码也是一个文本文件&#xff0c;所以&#xff0c;当你的源代码中包含中文的时候&#xff0c;在保存源代码时&#xff0c;就需要务必指定保存为UTF-8编码。 当Python解释器读取源代码时&#xff0c;为了让它按UTF-8编码读取&#xff0c;我们通常在文…

这些Windows 10隐藏秘技,你知道几个?

1. 虚拟桌面 玩电脑的老鸟&#xff0c;估计都听说过虚拟桌面。简言之&#xff0c;平时要做的工作太多&#xff0c;又没有第二个显示器&#xff0c;那么“虚拟桌面”也就成了不二之选。微软Windows 10的虚拟桌面隐藏在WinTAB中&#xff0c;也就是所谓的时间线视图&#xff08;T…

vant toast loading 倒计时_日期倒计时软件哪个好 苹果日期倒计时软件推荐

日期倒计时软件哪个好&#xff0c;相信大家也是经常会查看日期&#xff0c;来保证一些重要的事情能够按时进行&#xff0c;那么哪一款日期倒计时软件比较好用&#xff0c;能够提醒用户们日期将至呢。这里就为大家推荐几款。日期倒计时软件哪个好1.Days Matter Air作为Days Matt…

业务应用系统的业务操作日志设计

目的&#xff1a;记录业务的访问活动 操作时间&#xff1a;精确到秒 服务器IP&#xff1a;可能部署多台服务器&#xff0c;记录当前线程服务器IP地址 访问者IP&#xff1a;访问者ip地址 访问者账号&#xff1a;系统通过手机号登录&#xff0c;记录手机号 业务名称&#xf…

c char转int_c/c++基础之sizeof用法

在 C/C 中&#xff0c;sizeof() 是一个判断数据类型或者表达式长度的运算符。1 sizeof 定义 sizeof 是 C/C 中的一个操作符&#xff08;operator&#xff09;&#xff0c;返回一个对象或者类型所占的内存字节数。The sizeof keyword gives the amount of storage, in bytes, as…

非结构化数据与结构化数据提取---- BeautifulSoup4 解析器

CSS 选择器&#xff1a;BeautifulSoup4 和 lxml 一样&#xff0c;Beautiful Soup 也是一个HTML/XML的解析器&#xff0c;主要的功能也是如何解析和提取 HTML/XML 数据。 lxml 只会局部遍历&#xff0c;而Beautiful Soup 是基于HTML DOM的&#xff0c;会载入整个文档&#xff0c…

pyaudio usb playback_5.5寸触控屏IP电话会议USB全向麦克风NK-OAM600U_影视工业网

寸触控屏视频会议USB全向麦克风(拾音器)NK-OAM600U概述&#xff1a;派尼珂NK-OAM600U视频会议USB全向麦克风&#xff0c;是一款配置多点手势触控FHD屏的高清会议电话&#xff0c;便捷的连接方式&#xff1a;支持USB/以太网/WIFI/蓝牙Bluetooth连接&#xff0c;同时支持外接视频…

微软发布Azure Storage不可变存储功能的正式版本

随着新的不可变存储功能的发布&#xff0c;blob特性在特定的保留期间内将是不可以擦除和修改的。该特性从今年6月份开始进行预览使用&#xff0c;现在&#xff0c;微软宣布它在所有公开Azure region中正式发布。\\微软添加对不可变存储的支持&#xff0c;以便于帮助客户遵循像S…

红米note5手机插u盘没反应_认真分析下:荣耀智慧屏x1和红米x50对比哪个好?用后一个月告诉大家实情...

这二款电视荣耀智慧屏x1和红米x50区别还是有的哈&#xff0c;外观和款式是不一样的&#xff0c;不过家用都是可以的&#xff0c;看个人吧&#xff0c;我自己用的是荣耀智慧屏x1&#xff0c;款式是我喜欢的&#xff0c;多时尚的&#xff0c;整体质感不错的&#xff0c;看上去很大…