当前位置: 首页 > news >正文

【C缺陷与陷阱】----语义“陷阱”

💯💯💯

本篇处理的是有关语义误解的问题:即程序员的本意是希望表示某种事物,而实际表示的却是另外一种事物。在本篇我们假定程序员对词法细节和语法细节的理解没有问题,因此着重讨论语义细节。

  • 导言:
  • ①.指针与数组
    • 1.1数组的两个注意点:
      • 1.1.1确定数组的大小
      • 1.1.2获得指向该数组下标为0的元素的指针。
    • 1.2指针与数组之间的真正关系
      • 1.2.1数组名表示首元素的地址(指针)
      • 1.2.2“二维数组”
  • ②.非指针的数组
  • ③.作为参数的数组声明
    • 3.1一维数组的传参
    • 3.2二维数组的传参
    • 3.3一级指针传参
    • 3.4二级级指针传参
  • ④.避免"举隅法"
  • ⑤.空指针并非空字符串
    • 注意
  • ⑥.边界计算与不对称边界
    • 6.1 死循环问题
      • 6.1.1死循环的原因是什么呢?
      • 6.1.2解决方法
      • 6.1.3总结
    • 6.2 边界问题
      • 6.2.1两个原则和一个编程技巧
    • 6.3 边界访问
  • ⑦.求值顺序
    • 7.1短路求值:&&和||
  • ⑧.整数溢出
  • ⑨.main函数的返回值

导言:

由于一个程序错误可以从不同层面采用不同方式进行考察,而根据程序错误与考察程序的方式之间的相关性,可以将程序错误进行划分为各种陷阱与缺陷:
①.词法“陷阱”
②.语法“陷阱”
③.语义“陷阱”
④.连接问题
⑤.库函数问题
⑥.预处理器问题
⑦.可移植性缺陷
本篇重点讲解语义“陷阱”

①.指针与数组

1.1数组的两个注意点:

1.1.1确定数组的大小

数组的声明必须确定数组的大小是多少

int a[3];/

声明a是一个拥有3个整形的数组

struct arr
{int p[4];double x;
}b[10];

声明了b是一个拥有10个元素的数组,每个数组元素是个结构体,该结构体中包含了一个拥有4个整形的数组和一个浮点型的变量。

1.1.2获得指向该数组下标为0的元素的指针。

这句话什么意思呢?
其实有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行运算的
换句话说,任何一个数组下标的运算都等同于一个对应的指针运算,所以我们完全可以依据指针行为定义数组下标的行为。

那获得该下标为0的元素的指针,如果给这个指针加1,就能得到指向该数组中下一个元素的指针。
也就是指针+一个整数得到的还是指针,只不过指针的位置发生改变,
注意可不是指针指向的内容发生改变了!

int a[10]={0,1,2,3,4,5,6,7,8,9};
int  *p=a;
p=p+1;
//或者写成p++;

1.2指针与数组之间的真正关系

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
    sizeof(a)的结果是整个数组a的大小,而不是指向数组a首元素地址的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
  3. 除此之外所有的数组名都表示首元素的地址(指针)
    这里重点讲解第三个关系

1.2.1数组名表示首元素的地址(指针)

数组名表示首元素的地址
除了a被用作运算符sizeof的参数和&a的情况,在其他所有的情况中数组名a都代表着指向数组a中下标为0的元素的指针。
也就是数组名a代表着数组a中首元素的地址。

所以我们可以这样理解*a也就是数组a中下标为0的元素的引用。

*a=10;

这句代码也就是把首元素的值改成10. 同理,*(a+1_是数组a中下标为1的元素的引用,以此类推,可以推出,*(a+i)即数组a中下标为i的元素的引用;而这种写法是如此的常用,因为它被简记为a[i];

所以我们可以这样理解数组下标与指针之间的关系

a[0] 等同于*(a+0) //数组名a表示首元素的地址,加0还是首元素,对首元素的解引用,访问的就是首元素,而a[0]就是首元素a[1]等同于*(a+1) //首元素的地址+1表示指向了第二个元素的地址,解引用就等于访问第二个元素,而a[1]就是第二个元素a[2]等同于*(a+2)//首元素的的地址+2表示指向了第三个元素的地址,解引用等于访问第三个元素,而a[2]就是第三个元素
…… …… ……
a[i]等同于*(a+i)//就相当于数组a中下标为i的元素的访问    

也正是这一概念让许多C语言新手对数组与指针之间的关系搞得迷迷糊糊的。
实际上,由于a+ii+a的含义是相同的,因此a[i]i[a]也具有相同的含义,但不要写成这样,因为不好理解。

1.2.2“二维数组”

我们可以利用"二维数组"来揭示指针与数组之间的关系

int arr[10][20];

这个代码声明arr是一个数组。该数组是拥有10个数组类型的元素。其中每个元素都是一个拥有20个整形元素的数组(而不是一个拥有20个数组类型的元素,每个元素都是一个拥有10个整形的数组)。

根据我们对一维数组的理解,arr总是被转换成一个指向arr数组的起始元素的的指针。也就是首元素的地址。

这里还是提醒一下,该数组仍然遵循上面的第1条
sizeof(数组名)表示的是整个数组的大小,而这个二维数组的整个大小是10 * 20*sizeof(int)

而这个"二维数组",它实际上是以数组为元素的数组。它是一维数组里面又套了一个一维数组。
在一维数组里面,我们可以比较轻松的利用指针来编写操纵一维数组的程序,但是对于二维数组从记法上的便利性来说采用下标的形式才是更好的方式。
如果我们仅仅使用指针来操纵二维数组,那我们就必须深刻的理解指针相关的知识,不然会常常遇到意想不到的bug。

来我们看一下,下面的声明

int a[10];
int arr[10][20];
int *p;
int i;

想一想arr[3]代表的是什么?
a[3]表示一维数组a中第4个元素。

那arr[3],对于二维数组arr,是什么意思呢?
我读这个二维数组的方式是这个二维数组有10个数组类型。
10个数组元素,每个数组元素有20个整形元素。
所以arr[3],就代表着这10个数组元素的第4个数组元素。

其实我们还可以这样理解:将二维数组第一个[ ]里看成是行数
第二个[ ]看成列数。
那么有该二维数组有10行20列
arr[0]表示第1行的数组元素
arr[1]表示第2行的数组元素
arr[2]表示第3行的数组元素
arr[3]表示第4行的数组元素
arr[4]表示第5行的数组元素
arr[5]表示第6行的数组元素
arr[6]表示第7行的数组元素
arr[7]表示第8行的数组元素
arr[8]表示第9行的数组元素
arr[9]表示第10行的数组元素

那arr[3]表示的是第4行数组元素。

我们知道每一行的数组元素里面,都有着20个整形元素
所以一个arr[3]的大小就是sizeof(arr[3])==20*sizeof(int)

//这段代码表示什么意思?
p=arr[3];

这个语句使得指针p指向了数组arr[3]中下标为0的元素。
为什么是这样呢?
因为你看,arr[3]表示的是数组,该数组里面还有20个元素呢
所以arr[3]表示这个拥有20个整形元素的数组的数组名
数组名代表着什么?
代表着首元素的地址!所以该语句将arr[3]数组的首元素的地址传给了
p。
*arr[3]就是对arr[3]这个数组的首元素的地址的访问了,也就是它那20个元素的首元素。

*(arr[3]+1)这就表示访问第4个数组元素里面的第2个元素 也就是arr[3][1]
*(arr[3]+3)这就表示访问第4个数组元素里面的第3个元素==arr[3][2]
*(arr[3]+4)这就表示访问第4个数组元素里面的第4个元素==arr[3][3]
*(arr[3]+5)这就表示访问第4个数组元素里面的第5个元素==arr[3][4]
…… ……
*(arr[3]+i)这就表示访问第4个数组元素里面的第i个元素==arr[3][i]

这个语句根据前面的类似的道理,还可以写成下面的这样

*(arr[3]+3)这就表示访问第4个数组元素里面的第3个元素==arr[3][2]
进一步写成这样---也就是将arr[3]下标形式写成指针形式
*(*(arr+3)+3)

我们不难发现,用带方括号的下标形式很明显的要比完全用指针来表达方便多了。

不过还有人经常犯错误,写成下面这样

p=arr;

这个语句是非法,因为arr是一个二维数组,即”数组的数组“
arr表示的是首元素的地址对吧
你想一想,arr首元素是个啥?还是个数组呀!
所以arr首元素的地址是数组的地址,使用arr时,会转化为一个指向数组的指针,而p是一个指向整形变量的指针,可不能将一种类型的指针,赋给另一种类型的指针,这是非法的。

很显然,我们需要一个指向数组的指针来保存arr,上一篇博客我已经较为详细的介绍了该如声明一个变量:按照使用的方式来声明
我们需要的是一个指针,该指针是指向一个数组的,该数组的大小是20

int(*ph)[20];
//我们首先构造出(*ph)这个指针
//这个指针指向的是什么类型的呢?--是int [20]类型的

该语句实现的效果就是*ph是一个拥有20个整形元素的数组
所以ph就是一个指向这样的数组的指针。

所以可以这样写

int arr[10][20];
int(*ph)[20];
ph=arr;

ph也就指向数组arr的第一个元素的地址了,也就是数组arr的10个中有着20个元素的数组的元素之一。

利用上面的"二维数组"可以很好的揭示C语言中数组与指针之间独特的关系,从而更清楚的明白理解这两个概念。

②.非指针的数组

1.在C语言中,字符串常量代表的是一块包括字符串中所有字符还有一个’\0’的内存区域的地址。
2.字符串常量是以空字符’\0’作为结束标志。
3.字符串如果不用数组存储,那必须要有指针来存储。并且该指针指向的是字符串首字符的地址。

我们如果想让两个字符串合并成一个字符串

给定一个想法:
先计算出两个字符串长度,计算总长度多少
利用malloc函数,开辟一个大小为总长度的空间
将两个字符拷贝过去

注意事项:
1.如果利用stren计算字符串长度,请记住最后的结果要加上1,因为strlen遇到’\0’就停止。最后并没有将’\0’计算进去。
2.malloc申请的空间可能失败,需要判断
3.malloc申请的空间,在程序结束之前需要释放

③.作为参数的数组声明

在C语言中,我们虽然没有办法将一个数组作为函数参数之间参过去,因为不知道数组有多大,如果超级大,那操作系统可能无法提供足够的空间。
但如果我们使用数组名作为参数,那么数组名会立刻被转化为指向该数组第一个元素的指针。
数组传参和指针传参
写代码时难免要把数组或者指针传给函数,那函数的参数该怎么设计呢?

3.1一维数组的传参

void test(int arr[])//数组传参,数组接收
{}
void test(int arr[10])//跟上面一样
{}
void test(int* arr)//数组传参,指针接收
{}
void test2(int* arr[20])//数组传参,指针接收
{}
void test2(int** arr)//数组传参,指针接收
{}
因为数组名就是首元素的地址,所以数组名传参,可以用数组来接收,也可以用地址来接收,只不过要注意接收的是一级指针还是二级指针。
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);return 0;
}

3.2二维数组的传参

void test(int arr[3][5])//ok?二维数组传参,二维数组接收
{}
void test(int arr[][])//ok?这个可不行喔
{}
void test(int arr[][5])//ok?跟第一个一样

注意点:二维数组传参,函数参数的设计只能省略第一个[ ]的数字。
因为对于一个二维数组,可以不知道到多少行,但不能不知道一行多少个元素。这样才方便运算。

void test(int *arr)//ok?还记得上面说的吗,二维数组的数组名相当于第一行的数组地址所以是类型是数组指针类型的,应该用数组指针来接收
{}
void test(int* arr[5])//ok?不行这是个指针
{}
void test(int (*arr)[5])//ok?这个可以是数组指针,指针指向一个五个整形的数组
{}
void test(int **arr)//ok?不行,这个是二级指针
{}
int main()
{int arr[3][5] = {0};test(arr);}

3.3一级指针传参

//用一级指针来接收
void print(int *p, int sz)
{int i = 0;for(i=0; i<sz; i++){printf("%d\n", *(p+i));}
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9};int *p = arr;int sz = sizeof(arr)/sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;}

想一想当函数参数为一级指针时,可以接收上面参数呢?

void test1(int * p)
{ }
//test1函数能接收什么参数?
void test2(char* p)
{ }
//test2函数能接收什么参数?

3.4二级级指针传参

void test(int** ptr)
{printf("num = %d\n", **ptr); 
}
int main()
{int n = 10;int*p = &n;int **pp = &p;test(pp);//pp是二级指针test(&p);//p是一级指针,&p就应该用二级指针来接收了return 0;}

当函数的参数为二级指针的时候,可以接收什么参数?

void  test(char**p)
{}
int mian()
{char c='b';char *pc=&c;char**pcc=&pc;char *arr[10];test(&pc);//指针的地址,需要二级指针接收test(ppc)//二级指针,二级指针接收test(arr);//数组名表示首元素地址,每个元素的类型是char类型的指针,然后再取地址,当然可以用二级指针接收。
}

④.避免"举隅法"

什么叫"举隅法",就是以整体代表部分,或者以部分代替整体。
在C语言,我们会遇到常见的"陷阱":混淆指针与指针所指向的数据。
对于字符串的情形,我们更是经常犯错误。
比如:

char *p,*q;
p="abc";

我们可能一开始初学时认为,上面的赋值语句将字符串"abc"赋给了p,
然而实际上并不是这样,要记住字符串的不同之处。
实际上p的值是一个指向由’a’,‘b’,‘c’,'\0’四个字符组成的数组的起始字符的指针。

如果我们执行下面的语句

q=p;

让p和q同时指向用一块空间,但这个赋值语句并没有将p的数据 赋值给q
只是改变了p原来的指向而已。
所以我们要记住,复制指针并不同时复制指针所指向的数据。

⑤.空指针并非空字符串

在C语言中将一个整数转化为一个指针,最后得到的结果都取决与具体的C编译器实现,。但有个特殊情况,那就是常数0,编译器保证由0转化而来的指针,不等于任何有效的指针。
所以0通常被写成NULL

#define NULL 0;

无论是用常数0还是用符号NULL,效果是一样的。
但是要记住的重点是:

注意

当常数0被转化为指针使用时,这个给指针就不能再被访问,解引用了。
也就是当使用NULL时,就不能再去企图访问这个指针指向的内容了,一旦访问,就会造成非法。
比如下面这样:

char *p=NULL;
char *l="abc";
if(strcmp(l,p)==0);

这样就非法访问了,因为库函数,strcmo的使用会查看它的指针参数所指向内存中的内容的操作,因为p为空指针,如果访问它就会非法。

printf(p);
printf("%s",p);

以上两个写法也是未定义的。在不同的环境会出现不同的效果。

⑥.边界计算与不对称边界

a[10];在C语言中,数组下标的范围,是0-9,大家应该都不陌生。
而在有些语言中数组下标是1-11,有的是0-10等等。
今天我们就来探究一下C语言这样设计的动机是什么。

6.1 死循环问题

我们先来看一段代码:

int main()
{int i;int arr[] = { 1,2,3,4,5,6,7,8,9,10 };for (i = 0; i < 12; i++){printf("xiao tao\n");arr[i] = 0;}return 0;
}

这段代码的本意是要将数组a中所有的元素置为0,但却产生了一个出乎意料的"副作用",在for语句中比较部分本来是i<10,却写成了12,因此实际上并不存在的a[10],a[11]都被设置为0,也就是内存中在在数组后面的数据被设置为0。然后最终该程序变成了死循环,下面将会详细的讲解为什么会出现这样的情况,但该部分不是本篇重点,如需知道下面的知识,请跳过。

你觉得该代码有什么问题呢?

1.越界访问
2.死循环

第一个问题越界访问非常容易看出来,数组arr只有10个元素,下标从0-9,而循环12次,肯定越界访问了
每次都会打印一个xiao tao,并且把对应的arr[i]置为0。
所以最后答案应该就是打印了12次xiao tao,然后越界访问出现错误,使arr[10]=0,arr[11]=0了
但最后答案却不是这样。
答案变成了死循环了,死循环打印xiao tao

在这里插入图片描述

6.1.1死循环的原因是什么呢?

这里涉及有关栈空间的知识:

1.
内存空间分为栈区,堆区和静态区栈区一般存放局部变量,函数参数,函数返回值等
堆区是用来分配动态开辟的空间的
静态区是存放全局变量,static修饰的静态变量等

在这里插入图片描述


2.局部变量是在栈区存放的
3.栈区的使用习惯:
先使用高地址处的空间
再使用低地址处的空间
4.数组的地址随着下标的增长,地址是由低到高变化的

在这里插入图片描述
注意:
该测试是在VS2019 X86环境下进行,其他环境可能不一样,结果也就不一样,不能一概而论。
在变量i与数组之间一定有两个整形空间吗?
答案:不一定。
在VS2019 X86环境下,变量i与数组之间确实空了两个整形变量空间大小。
而在VC6.0环境下,变量i与数组之间没有剩余空间。
在gcc环境下,变量i与数组之间有一个整形空间大小
在这里插入图片描述

6.1.2解决方法

可能有的人会这样想将变量i定义在数组的下面这样就不会发生死循环了
我们从栈空间使用方面来看,这样当然可以避免死循环,但是难道我们以后写数组都要把i写在数组的后面吗?
这样只能解决当前的问题,而不能解决根本。不过现在的编译器大多数会自己修改这个死循环问题,比如将变量的i的地址放在数组的下面,在release版本就是这样进行优化不会死循环。

我们一般可能想不到这样本质原因,但我们可以通过调试来解决这个问题
当让i不断的++,当i等于10时将arr[10]置0,当i等于11时将arr[11]置0,然后我们通过调试监视发现arr[12]与i的值相等,这时我们就要想到为什么会死循环了,arr[12]的地址就等于i的地址,将arr[12]修改成0,就等于将i改为0了。
在这里插入图片描述
将arr[12]置0,发现i也变成0了
在这里插入图片描述

6.1.3总结

其实这道题是在特殊环境下才能实现的,但我们还是要注意的是其中的知识点:
1.栈区的使用习惯
先使用高地址的空间
再使用低地址的空间

2.数组随着下标的增长,地址不断增大

6.2 边界问题

在所有常见的程序设计错误中,最难于察觉的一类是"栏杆错误",也常被称为"差一错误",其实总结起来都是边界问题。
比如100米长的围栏每隔10米就需要一根支撑用的栏杆,那一共需要多少根栏杆呢?
”显而易见“答案是10,不就是100处以10嘛,得到结果10,需要10根栏杆。
这个答案是错误的,正确答案是11根。

仔细想一想:一开始支持这10米长的围栏需要两个跟,两端各一根,然后从第二根开始往后计数,每个10米要一根,最后加起来就是10+1
这个1是一开始没有计算的那根。

6.2.1两个原则和一个编程技巧

为了避免”栏杆错误“,总结以下两个原则:

  • 首先考虑最简单情况下的特例,然后将得到的结果往外推。
  • 仔细计算边界,绝不能掉以轻心

一个编程技巧:

  • 用一个入界点和第一个出界点来表示一个数组范围。
    比方说:整数x满足x>=16且x<=48;
    求满足整数x的个数是多少呢?
    我们可以利用编程技巧将它转变以下
    写成x>=16且x<49;
    这里的下界就是”入界点“,即包含在取值范围之中,而上界是”出结点“,即不包含在取值范围内。
    虽然这样形成了两个不对称(左边带有等于,右边不带有等于),但编程效果是极佳的,因为最后的答案就是上界-下界。

对于像C这样的数组下标从0开始的语言,不对称边界给程序设计代来了许多便利。
为什么呢?
因为这样数组的上界恰是数组元素的个数!
因此如果我们要在C语言中定义一个拥有10各元素的数组,那么0就是数组下标的第一个入界点,而10就是下标中的第一个出界点。

我们经常这样写

int a[10],i;
for(i=0;i<10;i++)
{
a[i]=10;
}

而不是这样写:

int a[10],i;
for(i=1;i<=10;i++)
{
a[i]=10;
}

是有道理的。所以我们在控制循环时,设置变量的范围是通常是设置为不对称形式,而不设置称对称形式。

6.3 边界访问

ANSIC标准明确允许这样的用法:
数组中实际不存在的”溢界“元素的地址位于数组所占内存之后,这个地址是可以用于进行赋值和比较,但是如果要引用该元素,那就是非法的了。
什么意思呢?
就是数组最后一个元素的后面那块空间地址是允许拿来进行赋值和比较的,但不允许访问。
该准则与上面的”不对称边界“原则是一致的,空间上也形成不对称,但是要记住不能访问!!!.一旦访问就是非法访问了。

⑦.求值顺序

上篇博客我写了关系运算符优先级之间的问题,但本篇讲的是求值顺序,与它并不相同。

C语言只有4各运算符(&& 和|| 和?:;和,)存在着规定的求值顺序。其他运算符对其操作数求值的顺序是未定义的。特别的,赋值运算符并不保证任何求值顺序

1.条件运算符?:;有三个操作数:在a?b:c中,操作数a首先被求值,然后根据a的值再去求操作数b或c的值。
2.而逗号运算符,首先对左侧操作数求值,然后该值被”丢弃“,再对右侧操作数求值

7.1短路求值:&&和||

逻辑与&&逻辑或||存在着短路求值
什么叫短路求值呢?就是首先对左侧操作数求值,只有当需要时才会对右侧操作数进行求值。

3.逻辑与&&: 表达式1&&表达式2 表达式1为假则右边不再进行。
4.逻辑或|| : 表达式1||表达式2 表达式1为真则右边不再进行。

int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;
}
因为 i = a++ && ++b && d++;   a++是后置++,先使用后++,所以a=0,先使用与&&进行配对,然后是假,
所以后面++b, d++都不再进行,但a++,这个还是进行的,所以a用完后还要给a+1,
所以a=1,b=2,c=3,d=4
int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++||++b||d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;
}
这里a++(a=0,先使用与||配对为假,再+1++b,b自增为3||配对为真,后面的d++不再进行了所以
a=1,b=3,c=3,d=4;

⑧.整数溢出

C语言中存在着两类整数算术运算,有符号运算与无符号运算。在无符号算术中,没有所谓的”溢出“一说:所有的无符号数运算都是以2的n次方为模,这里的n是结果中的位数。
如果算术运算符中一位是有符号,一个是无符号的,有符号的会先转化成无符号数,然后进行运算。
当两个操作数都是有符号数时,才可能发现”溢出“,而且溢出的结果是未定义的。当一个运算的结果发生”溢出“时,做出任何假设都是不安全的。
那如何进行检查两个操作数进行运算时是否”溢出“呢?

一种正确的方式是将a和b都强制转化为无符号整数:

if( (unsigned)a+(unsigned)b>INT_MAX )
exit(1);

这里的INT_MAX是表示最大整数值。

不需要用到无符号算术运算的另一种可行方法是:

if(a>INT_MAX-b)
exit(1);

⑨.main函数的返回值


main()
{
}

函数main什么都没写,与其他任何函数一样,如果没有显示声明返回类型,那么函数返回类型就默认为整形。但是这个程序也没有给出任何返回值。

按理说,这样不会造成什么危害。一个返回值为整形的函数,如果返回失败,实际上是返回了某个”垃圾“整数。只要不用到该整数,就问题不大。

但严格来说,大多数C语言实现是通过函数main的返回值来告知操作系统该函数的执行是否成功。
典型的处理就是main函数返回值为0表示执行成功,返回非0表示执行失败。

#include <stdio.h>
int main()
{printf("hello world\n");return 0;
}

在这里插入图片描述

#include <stdio.h>
int main()
{int arr[10], i;for (i = 0; i < 11; i++){arr[i] = 0;}return 0;
}

在这里插入图片描述
如果一个程序的main函数并不返回任何值,那么有可能看上去执行失败。
所以即使是最简单的C程序也应该像这样编写代码:

#include <stdio.h>
int main()
{printf("hello world\n");return 0;
}

相关文章:

【C缺陷与陷阱】----语义“陷阱”

&#x1f4af;&#x1f4af;&#x1f4af; 本篇处理的是有关语义误解的问题&#xff1a;即程序员的本意是希望表示某种事物&#xff0c;而实际表示的却是另外一种事物。在本篇我们假定程序员对词法细节和语法细节的理解没有问题&#xff0c;因此着重讨论语义细节。导言&#xf…...

JavaWeb--VUE

VUE1 概述2 快速入门3 Vue 指令3.1 v-bind & v-model 指令3.2 v-on 指令3.3 条件判断指令3.4 v-for 指令4 生命周期5 案例5.1 需求5.2 查询所有功能5.3 添加功能目标 能够使用VUE中常用指令和插值表达式能够使用VUE生命周期函数 mounted 1 概述 接下来我们学习一款前端的框…...

2分钟彻底搞懂“高内聚,低耦合”

&#x1f497;推荐阅读文章&#x1f497; &#x1f338;JavaSE系列&#x1f338;&#x1f449;1️⃣《JavaSE系列教程》&#x1f33a;MySQL系列&#x1f33a;&#x1f449;2️⃣《MySQL系列教程》&#x1f340;JavaWeb系列&#x1f340;&#x1f449;3️⃣《JavaWeb系列教程》…...

网络编程UDP TCP

定义:关注底层数据的传输 区分网页编程:关注上层应用 端口号:区分软件 2个字节 0~65535表示端口号 同一协议下端口号不能冲突 8000以下称为预留端口号,建议之间设置端口号为8000以上 常见的端口号: 80:http 8080:tomcat 3306:mysql 1521:oracle InetSocketAddress:此类实现IP套…...

【2023-Pytorch-检测教程】手把手教你使用YOLOV5做电线绝缘子缺陷检测

随着社会和经济的持续发展&#xff0c;电力系统的投资与建设也日益加速。在电力系统中&#xff0c;输电线路作为电能传输的载体&#xff0c;是最为关键的环节之一。而绝缘子作为输电环节中的重要设备&#xff0c;在支撑固定导线&#xff0c;保障绝缘距离的方面有着重要作用。大…...

交叉编译(NDK)

文章目录前言Android-NDK使用NDK目录结构主流的Android NDK交叉编译前言 交叉编译是指在一种计算机体系结构上编译和构建应用程序&#xff0c;但是生成的可执行文件和库是针对另一种不同的体系结构&#xff0c;比如ARM、MIPS、PowerPC、x86 等。 常见的交叉编译工具集&#x…...

【数据库】MySQL 解读事务的意义及原则

目录 1.事务的概念 2.为什么要用事物 3.使用 4.事务的原则&#xff08;ACID&#xff09; 4.1原子性&#xff08;Atomicity&#xff09; 4.2一致性&#xff08;Consistency&#xff09; 4.3持久性&#xff08;Durability&#xff09; 4.4隔离性&#xff08;Isolation…...

【Linux】冯诺依曼体系结构

冯诺依曼体系结构一、计算机结构体系来源二、冯诺依曼体系结构三、冯诺依曼体系结构中的数据流动一、计算机结构体系来源 研制电子计算机的想法产生于第二次世界大战期间&#xff0c;主要用来进行弹道计算&#xff0c;在"时间就是胜利"的战争年代&#xff0c;迫切需…...

【小白】git是什么?gitee和git和github的关系?

gitee问题一、git是什么&#xff1f;gitee和git和github的关系&#xff1f;问题二、能不能通俗易懂的说&#xff1f;问题一、git是什么&#xff1f;gitee和git和github的关系&#xff1f; Git是一种版本控制系统&#xff0c;用于管理文件的版本、记录文件的修改历史以及协同开…...

UDS 14229 -1 刷写34,36,37服务,标准加Trace讲解,没理由搞不明白

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…...

【Android -- 软技能】聊聊程序员的软技能

什么是软技能&#xff1f; 所谓软技能&#xff0c;就是相对于「硬技能」而言的技能&#xff0c;对于程序员来说&#xff0c;「硬技能」就是计算机专业技术能力&#xff0c;软技能则是专业之外的所有技能&#xff0c;包括职业规划能力、处理人际关系能力、专业态度、做事的方式…...

【Java学习笔记】27.Java 抽象类

Java 抽象类 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 抽象类除了不能实例化对象…...

Vite4 + Vue3 + vue-router4 动态路由

动态路由&#xff0c;基本上每一个项目都能接触到这个东西&#xff0c;通俗一点就是我们的菜单是根据后端接口返回的数据进行动态生成的。表面上是对菜单的一个展现处理&#xff0c;其实内部就是对router的一个数据处理。这样就可以根据角色权限或者一些业务上的需求&#xff0…...

MS(mbed l432KC)-->速通9个lab详细解析[5]

Exercise5 这次实验我们将正式接触到一个相对来说有点意思并且有点牌面的传感器了----->数码管。数码管是我们生活中非常常见的一种传感器,比如计时器,秒表,以及数字显示大屏幕,其实原理都跟数码管差不多。如果是没有单片机基础的同学,突然一下接触到相对来说比较常见…...

XXE漏洞复现

目录XML基础概念XML数据格式DTD基础定义DTD作用分类DTD实体实体的分类DTD元素XXE漏洞介绍实操如何探测xxe漏洞XML基础 概念 什么是XML 是一种可扩展标记语言 (Extensible Markup Language, XML) &#xff0c;标准通用标记语言的子集&#xff0c;可以用来标记数据、定义数据类型…...

初识C++需要了解的一些东西(2)

&#x1f601;关注博主&#xff1a;翻斗花园第一代码手牛爷爷 &#x1f603;Gitee仓库&#xff1a;牛爷爷爱写代码 目录&#x1f30d;内联函数&#x1f315;内联函数概念&#x1f316;内联函数特性&#x1f313;auto关键字(C11)&#x1f31e;类型别名⭐️auto简介☀️auto的使…...

全国程序员薪酬大曝光!看完我酸了····

2023年&#xff0c;随着互联网产业的蓬勃发展&#xff0c;程序员作为一个自带“高薪多金”标签的热门群体&#xff0c;被越来越多的人所关注。在过去充满未知的一年中&#xff0c;他们的职场现状发生了一定的改变。那么&#xff0c;程序员岗位的整体薪资水平、婚恋现状、职业方…...

改进YOLO系列 | CVPR2023最新Backbone | FasterNet 远超 ShuffleNet、MobileNet、MobileViT 等模型

论文地址:https://export.arxiv.org/pdf/2303.03667v1.pdf 为了设计快速神经网络,许多工作都集中在减少浮点运算(FLOPs)的数量上。然而,作者观察到FLOPs的这种减少不一定会带来延迟的类似程度的减少。这主要源于每秒低浮点运算(FLOPS)效率低下。并且,如此低的FLOPS主要…...

Nginx常见用法

一、Niginx是什么&#xff1f; Nginx是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Niginx是一款轻量级的web服务器/反向代理器&#xff0c;Nginx是高性能的HTTP和 反向代理的web服务器&#xff0c;处理高并发的能力十分强大&#xff0c…...

MySQL存储引擎和日志管理

MySQL存储引擎和日志管理一、存储引擎的概念1、Myisam的特点2、Myisam表支持 3 种不同的存储格式3、innodb4、死锁5、查看系统支持的存储引擎6、查看表使用的存储引擎7、修改存储引擎8、通过 create table 创建表时指定存储引擎二、日志管理1、错误日志2、通用查询日志3、慢查询…...

Arduino 驱动DS1307时钟模块使用介绍

Arduino 驱动DS1307时钟模块使用介绍 🔖DS1307时钟模块 📍DS1307时钟模块资料 https://pan.baidu.com/s/1mtXruLAktZj7UDhFD54BNw 提取码:d9xh 📋模块简介:DS1307 I2C实时时钟芯片(RTC) 24C32 32K I2C EEPROM存储器 解决DS1307带备用电池不能读写的问题。 充满电后,…...

为什么 Python 没有 main 函数?

众所周知&#xff0c;Python中没有所谓的main函数&#xff0c;但是网上经常有文章提到“ Python的main函数”和“建议编写main函数”。 其实&#xff0c;可能他们是想模仿真正的main函数&#xff0c;但是许多人都被误导&#xff08;或误解&#xff09;&#xff0c;然后编写了非…...

【无标题】使用Bibtex4word 整理毕业论文参考文献

Bibtex4Word使用一览需要安装和配置的软件texLiveBibtex4word需要安装和配置的软件 texLive 这个就不多说啦&#xff0c;大家自己找教程安装吧&#xff0c;因为我之前装过这一系列所以就不再来一遍了&#xff0c;但是有两条配置需要注意&#xff01; 添加环境变量 key: BIB…...

19--网络API(java版)

什么是网络API在一个网站里面发起https请求是很常见的&#xff0c;但是&#xff0c;由于微信小程序是腾讯内部的产品&#xff0c;不能直接打开一个外部的链接&#xff0c;例如&#xff0c;您在微信小程序里面无法直接打开网站&#xff0c;但是&#xff0c;在做小程序开发的时候…...

ElasticSearch - 分片内部原理之动态更新索引、近实时搜索、持久化变更、段合并

文章目录01. ElasticSearch 倒排索引是什么&#xff1f;02. ElasticSearch 倒排索引为什么是不可变的&#xff1f;03. ElasticSearch 索引文档原理&#xff1f;04. ElasticSearch 如何动态更新索引&#xff1f;05. ElasticSearch 文档的新增、删除、更新&#xff1f;06. Elasti…...

模拟数据采集卡之ADCTDC 模拟时间/数字转换器组合应用选型指南

简介 产品组合包括一系列多功能时间到数字转换器(TDC)和模数转换器(ADC)。我们的许多客户的应用场景依赖于对飞行时 间(TOF)的测量。该系列产品非常适合用于质谱系统(TOF-MS)&#xff0c;光学相干断层扫描(OCT)&#xff0c;荧光寿命成像显微镜(FLIM)&#xff0c; 时间相关单光子…...

R语言编程基础

文章目录安装运算符判断函数递归安装 根据自己的操作系统&#xff0c;下载R语言环境后&#xff0c;安装&#xff0c;并将安装路径加入到环境变量&#xff0c;即可从命令行进入R环境 >rR version 4.2.2 (2022-10-31 ucrt) -- "Innocent and Trusting" Copyright …...

2023-03-15:屏幕录制并且显示视频,不要用命令。代码用go语言编写。

2023-03-15&#xff1a;屏幕录制并且显示视频&#xff0c;不要用命令。代码用go语言编写。 答案2023-03-15&#xff1a; 使用moonfdd/ffmpeg-go和moonfdd/sdl2-go库来实现屏幕录制并显示视频&#xff0c;大体流程如下&#xff1a; 1.使用libavdevice库中的AVInputFormat&…...

STM32外设-DMA

1. 简介 DMA(Direct Memory Access)—直接存储器存取&#xff0c;是单片机的一个外设&#xff0c;它的主要功能是用来搬数据&#xff0c;但是不需要占用 CPU&#xff0c;即在传输数据的时候&#xff0c; CPU 可以干其他的事情&#xff0c;好像是多线程一样。数据传输支持从外设…...

【面试题】面试官:如果后端给你 1w 条数据,你如何做展示?

最近一位朋友参加阿b的面试&#xff0c;然后面试官问了她这个问题&#xff0c;我问她咋写的&#xff0c;她一脸淡定的说&#xff1a;“虚拟列表。”大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面…...

团购模板网站/邯郸seo营销

信息存储大多数计算机使用八位的块&#xff0c;或者字节&#xff0c;作为最小的可寻址的内存单位&#xff0c;而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组&#xff0c;称为虚拟内存。内存的每个字节都由一个唯一的数字来标识&#xff0c;称为它的地址…...

哪个网站做螺丝生意好/小红书推广价目表

总算是把卡方检验的思想看懂了 用来判断概率分布X与Y是否有关。 所以在文本特征选择中&#xff0c;有如下的计算公式&#xff1a; 转自Hackerzer的博客&#xff1a;http://blog.csdn.net/hackerzer/article/details/76713829...

做网站 单页数量/seo实战密码在线阅读

最近研究康盛的Unhome&#xff0c;一个SNS平台软件 要用到Mysql这个软件&#xff0c;唉&#xff0c;开源的东西就是麻烦&#xff0c;开启日志都搞了半天 刚开始只搞懂了开启更新之类的sql [mysqld] log-update "文件位置。log" 但是里面只有insert&#xff0c;updat…...

最新猪价格今日猪价格表/关键词排名优化江苏的团队

...

能自己做效果图的网站/百度seo优化排名如何

题目传送门 题目大意&#xff1a; 同这题&#xff0c;但是对于 111 ~ mmm 内的每个 nnn 都要求解&#xff0c;且 m≤5105m\leq 5\times 10^5m≤5105。 题解 先不考虑顺序&#xff0c;最后乘 n!n!n! 即可。 每个元素之多选一个&#xff0c;要选出 nnn 个来&#xff0c;且贡献…...

服务网站备案/seo发包排名软件

1.两种思维方式在求职面试中&#xff0c;经常会考察这种问题&#xff1a;北京有多少量特斯拉汽车&#xff1f; 某胡同口的煎饼摊一年能卖出多少个煎饼&#xff1f; 深圳有多少个产品经理&#xff1f; 一辆公交车里能装下多少个乒乓球&#xff1f; 一个正常成年人有多少根头发&a…...