C++ Primer 数组

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

在这里插入图片描述
在这里插入图片描述

目录

  • 3.5数组
    • 定义和初始化内置数组
    • 显式初始化数组元素
    • 字符数组的特殊性
    • 理解复杂的数组声明
    • 访问数组元素
    • 检查下标的值
    • 指针和数组
    • 指针也是迭代器
    • 指针运算
    • 解引用和指针运算的交互
    • 下标和指针
    • C风格字符串
    • C标准库String函数
    • 比较字符串
    • 目标字符串的大小由调用者指定
    • 与旧代码的接口
    • 使用数组初始化vector对象

3.5数组

数组是一种类似于标准库类型vector的数据结构,但是在性能和灵活性的权衡上又与vector有所不同。与vector相似的地方是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对这些特殊的应用来说程序的运行时性能较好,但是相应地也损失了一些灵活性。

如果不清楚元素的确切个数,请使用vector

定义和初始化内置数组

数组是一种复合类型。数组的声明形如a[d],其中a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。也就是说,维度必须是一个常量表达式:

unsigned cnt=42//不是常量表达式
constexpr unsigned sz=42;//常量表达式
int arr[10];//含有10个整数的数组
int*parr[sz];//含有42个整型指针的数组
string bad[cnt];//错误:cnt不是常量表达式
string strs[get_size()];//当get_size是constexpr时正确;否则错误

默认情况下,数组的元素被默认初始化。

和内置类型的变量一桦,如果在函数内部定义了柏种内置类型的数组,那么默会初始化会令数组合有未定义的值。

定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和vector一样,数组的元素应为对象,因此不存在引用的数组。

显式初始化数组元素

可以对数组的元素进行列表初始化,此时允许忽略数组的维度。如果在声明时没有指明维度,编译器会根据初始值的数量计算并推测出来;相反,如果指明了维度,那么初始值的总数量不应该超出指定的大小。如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化成默认值:

const unsigned sz=3;
int ial[sz] = {0,1,2}; //含有3个元素的数组,元素值分别是0,1,2
int a2[]={0,1,2};//维度是3的数组
int a3[5]={0,1,2};//等价于a3[]={0,1,2,0,01}
string a4[3] = {"hi", "bye"}; //等价于a4[]={"hi", "bye",""}
int a5[2] = {0,1,2}; //错误:初始值过多

字符数组的特殊性

字符数组有一种额外的初始化形式,我们可以用字符串字面值对此类数组初始化。当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个宇字符也会像字符串的其他字符一样被拷贝到字符数组中去:

char a1[]={'C', '+', '+'};    //列表初始化,没有空字符
char a2 ={'C', '+', '+', ' '}; //列表初始化,含有显式的空字符
char a3[]="C++";//自动添加表示字符串结束的空字符
const char a4[6]="Daniel";//错误:没有空间可存放空字符!a1的维度是3,a2和a3的维度都是4,a4的定义是错误的。尽管字符串字面值"Daniel"看起来只有6个字符,但是数组的大小必须至少是7,其中6个位置存放字面值的内容,另外1个存放结尾处的空字符。不允许拷贝和赋值不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:```cpp
int a[]= {0,1,2}; //含有3个整数的数组
int a2[]=a;//错误:不允许使用一个数组初始化另一个数组
a2=a//错误:不能把一个数组直接赋值给另一个数组

一些编译器支持数组的赋值,这就是所谓的编译器扩属(compiler extension)。但一般来说,最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。

理解复杂的数组声明

和vector一样,数组能存放大多数类型的对象。例如,可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。在这几种情况中,定义存放指针的数组比较简单和直接,但是定义数组的指针或数组的引用就稍微复杂一点了:

int*ptrs[10];//ptrs是含有10个整型指针的数组
int&refs[10] = /* ? */ //错误:不存在引用的数组
int(*parray)[10] = &arr;//parray指向一个含有10个整数的数组
int(&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组

默认情况下,类型修饰符从右向左依次绑定。对于ptrs来说,从右向左理解其含义比较简单:首先知道我们定义的是一个大小为10的数组,它的名宇是ptrs,然后知道数组中存放的是指向int的指针。

但是对于parray来说,从右向左理解就不太合理了。因为数组的维度是紧跟着被声明的名字的,所以就数组而言,由内向外阅读要比从右向左好多了。由内向外的顺序可帮助我们更好地理解parray的含义:首先是圆括号括起来的部分,*parray意味着parray是个指针,接下来观察右边,可知道parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int。这样最终的含义就明白无误了,parray是一个指针,它指向一个int数组,数组中包含10个元素。同理,(&arrRef)表示arrRef是一个引用,它引用的对象是一个大小为10的数组,数组中元素的类型是int。

当然,对修饰符的数量并没有特殊限制:

int*(&arry)[10]=ptrs;//arry是数组的引用,该数组含有10个指针

按照由内向外的顺序阅读上述语句,首先知道arry是一个引用,然后观察右边知道,arry引用的对象是一个大小为10的数组,最后观察左边知道,数组的元素类型是指向int的指针。这样,arry就是一个含有10个int型指针的数组的引用。

要想理解数组声明的含义,最好的办法是从数组的名字开始按照由外向内的顺序阅读。

访问数组元素

与标准库类型vector和string一样,数组的元素也能使用范围for语句或下标运算符来访问。数组的索引从0开始,以一个包含10个元素的数组为例,它的索引从0到9,而非从1到10。

在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。在cstddef头文件中定义了size_t类型,这个文件是C标准库stddef.h头文件的C++语言版本。

数组除了大小固定这一特点外,其他用法与vector基本类似。例如,可以用数组来记录各分数段的成绩个数:

//以10分为一个分数段统计成绩的数量:0~9,10~19,…,90~99,100
unsigned scores[11]={}; //11个分数段,全部初始化为0
unsigned grade;
while(ctn>>grade) {
if(grade<=100)++scores[grade/10];//将当前分数殴的计数值加1
}

与vector和string一样,当需要遍历数组的所有元素时,最好的办法也是使用范围for语句。例如,下面的程序输出所有的scores:

{for(auto:scores)cout << i << " ";//输出当前的计数值cout<<endl;
}

因为维度是数组类型的一部分,所以系统知道数组scores中有多少个元素,使用范图for语句可以减轻人为控制遍历过程的负担。

检查下标的值

与vector和string一样,数组的下标是否在合理范围之内由程序员负责检查,所谓合理就是说下标应该大于等于0而且小于数组的大小。要想防止数组下标越界,除了小心谨慎注意细节以及对代码进行彻底的测试之外,没有其他好办法。对于一个程序来说,即使顺利通过编译并执行,也不能肯定它不包含此类致命的错误。大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。

WARNING

指针和数组

在C++语言中,指针和数组有非常紧密的联系。就如即将介绍的,使用数组的时候编译器一般会把它转换成指针。通常情况下,使用取地址来获取指向某个对象的指针,取地址符可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针:

string nums[]={“one“,“two“,“three“);//数组的元素是string对象
string*p=&nums[0];//P指向nums的第一个元素

然而,数组还有一个特性:在很多用到数组名字的地方,编译器都会自动地将其换为一个指向数组首元素的指针:

string *p2=nums;//等价于p2=&nums[0]

在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。

由上可知,在一些情况下数组的操作实际上是指针的操作,这一结论有很多隐含的意思。其中一层意思是当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:

int ia[]={0,1,2,3,4,5,6,7,8,9}; //ia是一个含有10个整数的数组
auto ia2(ia);//ia2是一个整型指针,指向ia的第一个元素
ia2 = 42;//错误:ia2是一个指针,不能用int值给指针赋值

尽管ia是由10个整数构成的数组,但当使用ia作为初始值时,编译器实际执行的初始化过程类似于下面的形式:

auto ia2(&ia[0]);//显然ia2的类型是int*

必须指出的是,当使用decltype关键字时上述转换不会发生,dectype(ia)返回的类型是由10个整数构成的数组:

//ia3是一个含有10个整数的数组
decltype(ia)ia3={0,1,2,3,4,5,6,7,8,9};
ia3=p;//错误:不能用整型指针给数组赋值

指针也是迭代器

指向数组元素的指针拥有更多功能。vector和string的迭代器支持的运算,数组的指针全都支持。例如,允许使用递增运算符将指向数组元素的指针向前移动到下一个位置上:

int arr[]={0,1,2,3,4,5,6,7,8,9};
int*p = arr;//p指向arr的第一个元素
++p;//p指向arr[1]

就像使用迭代器遍历vector对象中的元素一样,使用指针也能遍历数组中的元素。当然,这样做的前提是先得获取到指向数组第一个元素的指针和指向数组尾元素的下一位置的指针。之前己经介绍过,通过数组名字或者数组中首元素的地址都能得到指向首元素的指针;不过获取尾后指针就要用到数组的另外一个特别性质了。我们可以设法获取数组尾元素之后的那个并不存在的元素的地址:

int*e=&arr[10];//指向arr尾元素的下一位置的指针

这里显然使用下标运算符索引了一个不存在的元素,arr有10个元素,尾元素所在位置的索引是9,接下来那个不存在的元素唯一的用处就是提供其地址用于初始化e。就像尾后迭代器一样,尾后指针也不指向具体的元素。因此,不能对尾后指针执行解引用或递增的操作。

利用上面得到的指针能重写之前的循环,令其输出arr的全部元素:

for(int*b= arr; b!=e; ++b)cout<<*b<<endl;//输出arr的元素

标准库函数begin和end

尽管能计算得到尾后指针,但这种用法极易出错。为了让指针的使用更简单、更安全,C++11新标准引入了两个名为begin和end的函数。这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参数:

int ia[]={0,1,2,3,4,5,6,7,8,9};//ia是一个含有10个整数的数组
int* beg=begin(ia);//指向a首元素的指针
int* last = end(ia);//指向arr尾元素的下一位置的指针

begin函数返回指向ia首元素的指针,end函数返回指向ia尾元素下一位置的指针,这两个函数定义在iterator头文件中。使用begin和end可以很容易地写出一个循环并处理数组中的元素。例如,假设arr是一个整型数组,下面的程序负责找到arr中的第一个负数:

//pbeg指向arr的首元素,pend指向arr尾元素的下一位置
int*pbeg=begin(arr),xpend=end(arr);
//寻找第一个负值元素,如果巳经检查完全部元素则结束循环
while(pbeg!=pend&&*pbeg>=0)++pbeg

首先定义了两个名为pbeg和pend的整型指针,其中pbeg指向arr的第一个元素,pend指向arr尾元素的下一位置。while语句的条件部分通过比较pbeg和pend来确保可以安全地对pbeg解引用,如果pbeg确实指向了一个元素,将其解引用并检查元素值是否为负值。如果是,条件失效、退出循环;如果不是,将指针向前移动一位继续考查下一个元素。

指针运算

指向数组元素的指针可以执行所有迭代器运算。这些运算,包括解引用、递增、比较、与整数相加、两个指针相减等,用在指针和用在迭代器上意义完全一致。

给(从)一个指针加上(减去)某整数值,结果仍是指针。新指针指向的元素与原来的指针相比前进了(后退了)该整数值个位置:

constexpr size_t sz = 5;
int arr[sz]={1,2,3,4,5};
int*ip= arr;  //等价于int*ip=&arr[0]
int*ip2=ip+4; //ip2指向arr的尾元素arr[4]ip加上4所得的结果仍是一个指针,该指针所指的元素与ip原来所指的元素相比前进了4个位置。给指针加上一个整数,得到的新指针仍需指向同一数组的其他元素,或者指向同一数组的尾元素的下一位置:```cpp
//正确:arr转换成指向它首元素的指针;p指向arr尾元素的下一位置
int*p = arr+8;//使用警告:不要解引用!
int*p2 = arr+105;//错误;arr只有5个元素,p2的值未定义

当给arr加上sz时,编译器自动地将arr转换成指向数组arr中首元素的指针。执行加法后,指针从首元素开始向前移动了sz(这里是5个位置,指向新位置的元素。也就是说,它指向了数组arr尾元素的下一位置。如果计算所得的指针超出了上述范围就将产生错误,而且这种错误编译器一般发现不了。

和迭代器一样,两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素:

auto n=end(arr)-begin(arr);//n的值是5,也就是arr中元素的数量

两个指针相减的结果的类型是一种名为 ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstdef头文件中的机器相关的类型。因为差值可能为负值,所以ptrdifft是一种带符号类型。

只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。例如,可以按照如下的方式遍历数组中的元素:

int*b = arr,*e=arr+sz;
while(b<e){//使用*b++b;
}如果两个指针分别指向不相关的对象,则不能比较它们:```cpp
int i = 0,sz = 42;
int*p = &i,*e=&sz;
//未定义的;p和e无关,因此比较毫无意义!
while(p<e)

尽管作用可能不是特别明显,但必须说明的是,上述指针运算同样适用于宇指针和所指对象并非数组的指针。在后一种情况下,两个指针必须指向同一个对象或该对象的下一位置。如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果当然是0。

解引用和指针运算的交互

指针加上一个整数所得的结果还是一个指针。假设结果指针指向了一个元素,则允许解引用该结果指针:

int ia[]={0,2,4,6,8}; //含有5个整数的数组
int last = *(ia+4);//正确:把last初始化成8,也就是ia[4]的值

表达式*(ia+4)计算ia前进4个元素后的新地址,解引用该结果指针的效果等价于表达式ia[4]。

回忆一下在中介绍过如果表达式含有解引用运算符和点运算符,最好在必要的地方加上圆括号。类似的,此例中指针加法的圆括号也不可缺少。如果写成下面的形式:

last = *ta+4;//正确;last=4等价于ia[0]+4

下标和指针

如前所述,在很多情况下使用数组的名字其实用的是一个指向数组首元素的指针。一个典型的例子是当对数组使用下标运算符时,编诙器会自动执行上述转换操作。给定

int ia[]={0,2,4,6,8};//含有5个整数的数组

此时,ia[0]是一个使用了数组名字的表达式,对数组执行下标运算其实是对指向数组元素的指针执行下标运算:

int i = ia[2]; //ia转换成指向数组首元素的指针
//ia[2]得到(ia+2)所指的元素
int*p = ia;//p指向ia的首元素
i = *(p+2); // 等价于i= ia[2];

只要指针指向的是数组中的元素(或者数组中尾元素的下一位置),都可以执行下标运算:

int*p = &ia[2];//p指向索引为2的元素
int j=p[1];    //p[1]等价于*(p+1),就是ia[3]表示的那个元素
int k = p[-2];//p[-2]是ia[0]表示的那个元素

虽然标准库类型string和vector也能执行下标运算,但是数组与它们相比还是有所不同。标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,上面的最后一个例子很好地说明了这一点。内置的下标运算符可以处理负值,当然,结果地址必须指向原来的指针所指同一数组中的元素(或是同一数组尾元素的下一位置)。

内置的下标运算符所用的索引值不是无符号类型,这一点与vector和int不一样。

C风格字符串

WARNNG:尽管C++支特C风格字符宣,但在C++程序中最好还是不要使用它们。这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是众多安全问题的根本原因。

字符串字面值是一种通用结构的实例,这种结构即是C++由C继承而来的C风格字符串(C-stylecharacterstring)。C风格字符串不是一种类型,而是为了表达和使用字符串而形成
的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符结束(null terminated)。以空字符结束的意思是在字符串最后一个字符后面跟着一个宇字符(’\0’)。一般利用指针来操作这些字符串。

C标准库String函数

表3.8列举了C语言标准库提供的一组函数,这些函数可用于操作C风格宇符串,它们定义在cstring头文件中,cstring是C语言头文件string-h的C++版本。

表3.8:C风格字符串的函数

strlen§返回p的长度,空字符不计算在内
strcmp(pl,p2)比较pl和p2的相等性。如果pl==p2,返回0;如果p1>p2,返回一个正值;如果p1<p2,返回一个负值
strcat(pl,P2)将p2附加到p1之后,返回p1
strcPy(P1,p2)将p2拷贝给pl,返回p

传入此类函数的指针必须指向以空字符作为结束的数组:

char ca[] = {'C', '+','+'};//不以空字符结来
cout<<strlen(ca) << endl;

此例中,ca虽然也是一个字符数组但它不是以空字符作为结束的,因此上述程序将产生未定义的结果。strlen函数将有可能沿着ca在内存中的位置不断向前寻找,直到遇到空字符才停下来。

比较字符串

比较两个C风格字符串的方法和之前学习过的比较标准库string对象的方法大相径庭。比较标准库string对象的时候,用的是普通的关系运算符和相等性运算符:

string s1 = “A string example“;
string s2=“different string“;
if(sl<s2)//false:s2小于s1

如果把这些运算符用在两个C风格字符串上,实际比较的将是指针而非字符串本身:

const char cal[]=“A string example“;
const char ca2[]=“different string“;
if(cal<ca2)//未定义的:试图比较两个无关地址

谨记之前介绍过的,当使用数组的时候其实真正用的是指向数组首元素的指针。因此,上面的if条件实际上比较的是两个const char*的值。这两个指针指向的并非同一对象,所以将得到未定义的结果。

要想比较两个C风格宇符串需要调用strcmp函数,此时比较的就不再是指针了。如果两个字符串相等,strcmp返回0:如果前面的字符串较大,返回正值;如果后面的字符串较大,返回负值:

if(strcmp(cal,ca2)<0)//和两个string对象的比较sl<s2效果一样

目标字符串的大小由调用者指定

连接或拷贝C风格字符串也与标准库string对象的同类操作差别很大。例如,要想把刚刚定义的那两个string对象s1和s2连接起来,可以直接写成下面的形式:

//将largeStr初始化成s1、一个空格和s2的连接
string largeStr=s1+" "+s2;

同样的操作如果放到ca1和ca2这两个数组身上就会产生错误了。表达式ca1+ca2试图将两个指针相加,显然这样的操作没什么意义,也肯定是非法的。

正确的方法是使用strcat函数和strcpy函数。不过要想使用这两个函数,还必须提供一个用于存放结果字符串的数组,该数组必须足够大以便容纳下结果字符串及末尾的宇字符。下面的代码虽然很常见,但是充满了安全风险,极易引发严重错误:

//如果我们计算错了largeStr的大小将引发严重错误
strcpy(largeStr,cal);//把cal拷贝给largeStr
strcat(largeStrf,)//在largeStr的末尾加上一个空格
strcat(largeStr,ca2);//把ca2连接到largeStr后面

一个潜在的问题是,我们在估算largeStr所需的宇间时不容易估准,而且largeStr所存的内容一旦改变,就必须重新检查其空间是否足够。不幸的是,这样的代码到处都是,
程序员根本没法照顾周全。这类代码充满了风险而且经常导致严重的安全泄漏。

对大多数应用来说使用标准库string要比使用C风格字符串更安全、更高。

与旧代码的接口

很多C++程序在标准库出现之前就已经写成了,它们肯定没用到string和vector类型。而且,有一些C++程序实际上是与C语言或其他语言的接口程序,当然也无法使用
C++标准库。因此,现代的C++程序不得不与那些充满了数组和/或C风格守符串的代码衔接,为了使这一工作简单易行,C++专门提供了一组功能。

混用string对象和C风格字符串员

使用字符串字面值来初始化string对象:

string s(“Hello World“);//s的内容是Hello World

更一般的情况是,任何出现字符串字面值的地方都可以用以空字符结束的字符数组替代:

  • 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
  • 在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);,在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。

上述性质反过来就不成立了:如果程序的某处需要一个C风格字符串,无法直接用string对象代替它。例如,不能用string对象直接初始化指向字符的指针。为了完成该功能,string专门提供了一个名为c_str的成员函数:

char*str=s;//错误:不能用string对象初始化char*
const char*str=s.c_str();//正确

顾名思义,c_str函数的返回值是一个C风格的字符串。也就是说,函数的返回结果是一个指针,该指针指向一个以定字符结束的字符数组,而这个数组所存的数据总好与那个string对象的一样。结果指针的类型是const char*,从而确保我们不会改变字符数组的内容。

我们无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。

WARNING: 如果执行完c_str()函数后程序想一直都能使用其返回的数组,最好该数组重新拷贝一份。

使用数组初始化vector对象

不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。相反的,允许使用数组来初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了:

int int_arr = {0,1,2,3,4,5};
//ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr),end(int_arr));

在上述代码中,用于创建ivec的两个指针实际上指明了用来初始化的值在数组int_arr中的位置,其中第二个指针应指向待拷贝区域尾元素的下一位置。此例中,使用标准库函数begin和end来分别计算int_arr的首指针和尾后指针。在最终的结果中,ivec将包含6个元素,它们的次序和值都与数组int_arr完全-样。
用于初始化vectoz对象的值也可能仪是数组的一部分:

//拷贝三个元素:int_arr[1]、int_arr[2]、int_arr[3]
vector<int>subVec(int_arr+1,int_arr+4);

这条初始化语句用3个元素创建了对象subVec,3个元素的值分别来自int_arr[1]、int_arr[2]和int_arr[3]。

建议:尽量使用标准库类型而非数组

使用指针和数组很容易出错。一部分原因是概念上的问题:指针常用于底层操作,因此容易引发一些与烦琐细节有关的错误。其他问题则源于语法错误,特别是声明指针
时的语法错误。

现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。

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

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

相关文章

CPU的基本结构

基本结构 控制器&#xff08;Control Unit&#xff09;&#xff1a;负责控制&#xff0c;如指令计数器&#xff0c;指令跳转。 算术逻辑控制器&#xff08;Arithmetic/Logic Unit&#xff09;&#xff1a;负责计算&#xff0c;如算术运算加减&#xff0c;逻辑比较大小等。 南北…

git SourceTree 使用

Source Tree 使用原理 文件的状态 创建仓库和提交 验证 再克隆的时候发发现一个问题&#xff0c;就是有一个 这个验证&#xff0c;起始很简单 就是 gitee 的账号和密码&#xff0c;但是要搞清楚的是账号不是名称&#xff0c;我之前一直再使用名称登录老是出问题 这个很简单的…

BFS算法篇——广度优先搜索,探索未知的旅程(上)

文章目录 前言一、BFS的思路二、BFS的C语言实现1. 图的表示2. BFS的实现 三、代码解析四、输出结果五、总结 前言 广度优先搜索&#xff08;BFS&#xff09;是一种广泛应用于图论中的算法&#xff0c;常用于寻找最短路径、图的遍历等问题。与深度优先搜索&#xff08;DFS&…

hot100(9)

81.104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 后序遍历&#xff0c;从下往上&#xff0c;需要用到下面返回的结果。 public int maxDepth(TreeNode root) {if(root null){return 0;}int left maxDepth(root.left);int right maxDepth(root.right);re…

Elasticsearch:向量搜索的快速介绍

作者&#xff1a;来自 Elastic Valentin Crettaz 本文是三篇系列文章中的第一篇&#xff0c;将深入探讨向量搜索&#xff08;也称为语义搜索&#xff09;的复杂性&#xff0c;以及它在 Elasticsearch 中的实现方式。 本文是三篇系列文章中的第一篇&#xff0c;将深入探讨向量搜…

U9成品入库单有提示 组织+单号已经存在

2025年首个问题出来了&#xff01;也是U9上线以来首次碰到的问题。看到这样的提示&#xff0c;头皮发麻了。深感不妙。看过all.log之后&#xff0c;果然是重复行的问题&#xff01; 怎么会有重复行的错误发生呢&#xff1f;百思不得其解。 无奈之下&#xff0c;只能将单据类型…

为什么要设计DTO类/什么时候设置DTO类?

为什么设计DTO类&#xff1f; 例如&#xff1a;根据新增员工接口设计对应的DTO 前端传递参数列表&#xff1a; 思考&#xff1a;是否可以使用对应的实体类来接收呢&#xff1f; 注意&#xff1a;前端提交的数据和实体类中对应的属性差别比较大&#xff0c;所以自定义DTO类。 …

【C++篇】C++11新特性总结1

目录 1&#xff0c;C11的发展历史 2&#xff0c;列表初始化 2.1C98传统的{} 2.2&#xff0c;C11中的{} 2.3&#xff0c;C11中的std::initializer_list 3&#xff0c;右值引用和移动语义 3.1&#xff0c;左值和右值 3.2&#xff0c;左值引用和右值引用 3.3&#xff0c;…

大语言模型遇上自动驾驶:AsyncDriver如何巧妙解决推理瓶颈?

导读 这篇论文提出了AsyncDriver框架&#xff0c;致力于解决大语言模型在自动驾驶领域应用中的关键挑战。论文的主要创新点在于提出了大语言模型和实时规划器的异步推理机制&#xff0c;实现了在保持性能的同时显著降低计算开销。通过设计场景关联指令特征提取模块和自适应注入…

【iOS自动化】Xcode配置WebDriverAgent

WebDriverAgent 是 iOS 端自动化测试的工具&#xff0c;这里记录下 MacOS 环境 Xcode 如何配置 WebDriverAgent。 【重要】环境准备 ‼️ 注意&#xff1a;Xcode 版本需要支持对应的 iOS 版本&#xff0c;而 Xcode 版本又依赖 MacOS 版本&#xff1b;在开始部署前&#xff0c…

洛谷题目: P8774 [蓝桥杯 2022 省 A] 爬树的甲壳虫 题解 (本题较简)

题目传送门&#xff1a; P8774 [蓝桥杯 2022 省 A] 爬树的甲壳虫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前言&#xff1a; 这是一道关于概率和期望的动态规划问题&#xff0c;解题的核心思路是通过建立状态转移方程来计算甲壳虫从树根爬到树顶所需时间的期望值。题…

力扣题库第495题目解析

文章目录 1.题目再现2.思路分析&&示例说明2.1第一个示例2.2第二个示例 3.代码解释 1.题目再现 这个题目的名字叫做提莫攻击&#xff0c;如果是玩游戏的小伙伴对于这个场景就很熟悉了&#xff1b; 这个实际上是说&#xff1a;已知的条件会给我们一个数组&#xff0c;在…

leetcode刷题日记 1

https://leetcode.cn/problems/decode-ways/description/ 题目分析 分析了一下题目&#xff0c;我的第一想法&#xff1a;和之前的上楼梯问题很像 为什么这么说呢&#xff0c;感觉他们的值和他们之前元素都有千丝万缕的联系 就像上楼梯问题 就是我们的dp问题 怎么解释呢&a…

matlab simulink 汽车四分之一模型轮胎带阻尼

1、内容简介 略 matlab simulink121-汽车四分之一模型轮胎带阻尼 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略

广度优先搜索(BFS)算法详解——以走迷宫问题为例

引言&#xff1a;当算法遇见迷宫 想象你置身于一个复杂的迷宫&#xff0c;如何在最短时间内找到出口&#xff1f;这个问题不仅存在于童话故事中&#xff0c;更是计算机科学中经典的路径搜索问题。本文将带你通过走迷宫问题&#xff0c;深入理解广度优先搜索&#xff08;BFS&am…

网工_以太网MAC层

2025.02.05&#xff1a;网工老姜学习笔记 第12节 以太网MAC层 2.1 MAC层的硬件地址2.2 MAC地址特殊位含义2.3 终端适配器&#xff08;网卡&#xff09;具有过滤功能2.4 MAC帧的格式2.4.1 DIX Ethernet V2标准&#xff08;先私有&#xff0c;后开放&#xff0c;用得比较多&#…

解锁高效 Web 开发新姿势:Open WebUI 安装指南

在 Web 开发的浩瀚宇宙里&#xff0c;找到一款强大又好用的框架&#xff0c;就如同拥有了超级外挂&#xff0c;能让开发效率直线飙升。 今天要给大家介绍的 Open WebUI&#xff0c;便是这样一款神器&#xff0c;它作为开源框架&#xff0c;助力开发者轻松搭建现代感十足、交互性…

485网关数据收发测试

目录 1.UDP SERVER数据收发测试 使用产品&#xff1a; || ZQWL-GW1600NM 产品||【智嵌物联】智能网关型串口服务器 1.UDP SERVER数据收发测试 A&#xff08;TX&#xff09;连接RX B&#xff08;RX&#xff09;连接TX 打开1个网络调试助手&#xff0c;模拟用户的UDP客户端设…

软考高级-软件系统架构师-02-软件工程(重点)

用工程化的思想做软件 一、软件开发方法&#xff08;/原则&#xff09; 软件开发方法&#xff08;重点&#xff09; 结构化法&#xff08;面向过程/函数&#xff09; C 概念 用户至上严格区分工作阶段&#xff0c;每个阶段有各自的任务和成果强调系统开发的整体性和全局性系统开…

STM32的HAL库开发---通用定时器(TIMER)---定时器脉冲计数

一、脉冲计数实验原理 1、 外部时钟模式1&#xff1a;核心为蓝色部分的时基单元&#xff0c;时基单元的时钟源可以来自四种&#xff0c;分别是内部时钟PCLK、外部时钟模式1&#xff0c;外部时钟模式2、内部定时器触发&#xff08;级联&#xff09;。而脉冲计数就是使用外部时钟…