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

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风格的基于数组的字符串。

相关文章:

C++ Primer 数组

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…...

Linux(Centos)安装allnnlp失败,jsonnet报错

Linux安装allnnlp失败,jsonnet报错 问题分析并解决1. 什么是 Software Collection (SCL)&#xff1f;2. 安装步骤2.1 安装 SCL 仓库支持2.2 安装 Devtoolset-7 提供的 C 编译器2.3 启用 Devtoolset-7 环境2.4 验证安装 3. 永久启用 Devtoolset-7 环境 问题 执行pip install al…...

小程序设计和开发:要如何明确目标和探索用户需求?

一、明确小程序的目标 确定业务目标 首先&#xff0c;需要明确小程序所服务的业务领域和目标。例如&#xff0c;是一个电商小程序&#xff0c;旨在促进商品销售&#xff1b;还是一个服务预约小程序&#xff0c;方便用户预订各类服务。明确业务目标有助于确定小程序的核心功能和…...

C#面试常考随笔12:游戏开发中常用的设计模式【C#面试题(中级篇)补充】

C#面试题&#xff08;中级篇&#xff09;&#xff0c;详细讲解&#xff0c;帮助你深刻理解&#xff0c;拒绝背话术&#xff01;-CSDN博客 简单工厂模式 优点&#xff1a; 根据条件有工厂类直接创建具体的产品 客户端无需知道具体的对象名字&#xff0c;可以通过配置文件创建…...

napalm ‘NXOSDriver‘ object has no attribute ‘port‘ 解决方案(随手记)

解决方案&#xff08;仍然使用ssh作为访问方式&#xff09; 使用napalm时&#xff0c;对于Cisco Nexus设备&#xff0c;默认采用的是443的api去访问获取数据&#xff0c;如果需要使用ssh的方式获取&#xff0c;需要特别指定get_network_driver(nxos_ssh) 使用443 https api的…...

顺序表与vector

一、顺序表的模拟实现 1.顺序表的实现方式 按照数组的申请方式&#xff0c;有以下两种实现方式&#xff1a; &#xff08;1&#xff09;数组采用静态分配&#xff0c;此时的顺序表称为静态顺序表。 &#xff08;2&#xff09;数组采用动态分配&#xff0c;此时的顺序表称为动…...

Spring 面试题【每日20道】【其三】

1、Spring 中的 Profile 注解的作用是什么&#xff1f; 中等 Profile 注解在Spring框架中用于根据不同的环境配置文件&#xff08;profiles&#xff09;来激活或忽略某些Bean的注册。它允许开发者定义逻辑以区分不同环境下的bean定义&#xff0c;例如开发、测试和生产环境。 …...

作业day7

请使用条件变量实现2生产者2消费者模型&#xff0c;注意1个生产者在生q产的时候&#xff0c;另外一个生产者不能生产 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE]…...

后盾人JS--继承

继承是原型的继承 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </hea…...

基于CY8CKIT-149 BLE HID设备实现及PC控制功能开发(BLE HID+CapSense)

目录 项目介绍硬件介绍项目设计开发环境总体流程图BLE HID触摸按键与滑条 功能展示项目总结 &#x1f449; 【Funpack4-1】基于CY8CKIT-149 BLE HID设备实现及PC控制功能开发 &#x1f449; Github: EmbeddedCamerata/CY8CKIT-149_ble_hid_keyboard 项目介绍 基于英飞凌 CY8CK…...

工业级 激光测距 飞行时间法TOF 相位法 多频调制的本质!

激光测距仪中使用的相位法飞行时间&#xff08;Phase-based Time-of-Flight, ToF&#xff09;是一种通过测量调制光信号的相位差来计算距离的高精度方法。以下是详细解释&#xff1a; 一、核心原理&#xff1a;用“波的延迟”测距离 想象你向一堵墙拍手&#xff0c;通过回声的…...

014-STM32单片机实现矩阵薄膜键盘设计

1.功能说明 本设计主要是利用STM32驱动矩阵薄膜键盘&#xff0c;当按下按键后OLED显示屏上会对应显示当前的按键键值&#xff0c;可以将此设计扩展做成电子秤、超市收银机、计算器等需要多个按键操作的单片机应用。 2.硬件接线 模块管脚STM32单片机管脚矩阵键盘行1PA0矩阵键盘…...

flowable expression和json字符串中的双引号内容

前言 最近做项目&#xff0c;发现了一批特殊的数据&#xff0c;即特殊字符"&#xff0c;本身输入双引号也不是什么特殊的字符&#xff0c;毕竟在存储时就是正常字符&#xff0c;只不过在编码的时候需要转义&#xff0c;转义符是\&#xff0c;然而转义符\也是特殊字符&…...

关于Internet Download Manager(IDM)强制下载合并相关二次开发

目录 前言 强制下载视频 强制合并 迁移下载列表 免责声明 附录 前言 那个下载工具IDM不说了&#xff0c;确实有很多便捷的功能&#xff0c;不过也有一些限制 常见的包括但不限于&#xff1a; 1.无法下载有版权保护的视频&#xff08;不管真假&#xff09; 2.有时候下载…...

在Vue3 + Vite 项目中使用 Tailwind CSS 4.0

文章目录 首先是我的package.json根据官网步骤VS Code安装插件验证是否引入成功参考资料 首先是我的package.json {"name": "aplumweb","private": true,"version": "0.0.0","type": "module","s…...

Notepad++消除生成bak文件

设置(T) ⇒ 首选项... ⇒ 备份 ⇒ 勾选 "禁用" 勾选禁用 就不会再生成bak文件了 notepad怎么修改字符集编码格式为gbk 如图所示...

[mmdetection]fast-rcnn模型训练自己的数据集的详细教程

本篇博客是由本人亲自调试成功后的学习笔记。使用了mmdetection项目包进行fast-rcnn模型的训练&#xff0c;数据集是自制图像数据。废话不多说&#xff0c;下面进入训练步骤教程。 注&#xff1a;本人使用linux服务器进行展示&#xff0c;Windows环境大差不差。另外&#xff0…...

汽车自动驾驶AI

汽车自动驾驶AI是当前汽车技术领域的前沿方向&#xff0c;以下是关于汽车自动驾驶AI的详细介绍&#xff1a; 技术原理 感知系统&#xff1a;自动驾驶汽车通过多种传感器&#xff08;如激光雷达、摄像头、雷达、超声波传感器等&#xff09;收集周围环境的信息。AI算法对这些传感…...

信息学奥赛一本通 2101:【23CSPJ普及组】旅游巴士(bus) | 洛谷 P9751 [CSP-J 2023] 旅游巴士

【题目链接】 ybt 2101&#xff1a;【23CSPJ普及组】旅游巴士(bus) 洛谷 P9751 [CSP-J 2023] 旅游巴士 【题目考点】 1. 图论&#xff1a;求最短路Dijkstra, SPFA 2. 动态规划 3. 二分答案 4. 图论&#xff1a;广搜BFS 【解题思路】 解法1&#xff1a;Dijkstra堆优化 …...

Day25 洛谷 提高- 1007

零基础洛谷刷题记录 Day01 2024.11.18 Day02 2024.11.25 Day03 2024.11.26 Day04 2024.11.28 Day05 2024.11.29 Day06 2024 12.02 Day07 2024.12.03 Day08 2024 12 05 Day09 2024.12.07 Day10 2024.12.09 Day11 2024.12.10 Day12 2024.12.12 Day13 2024.12.16 Day14 2024.12.1…...

minikube 的 Kubernetes 入门教程--Ollama

让我们在本地 Kubernetes 中部署 Ollama&#xff08;llama3.2&#xff09; Ollama 安装方式有多样&#xff0c;是可选的&#xff0c;因为有minikube环境&#xff0c;我在 Kubernetes 中Deployment它。调用Ollama镜像&#xff0c;也可以用 Ollama CLI 去运行&#xff08;run&am…...

【字节青训营-7】:初探 Kitex 字节微服务框架(使用ETCD进行服务注册与发现)

本文目录 一、Kitex概述二、第一个Kitex应用三、IDL四、服务注册与发现 一、Kitex概述 长话短说&#xff0c;就是字节跳动内部的 Golang 微服务 RPC 框架&#xff0c;具有高性能、强可扩展的特点&#xff0c;在字节内部已广泛使用。 如果对微服务性能有要求&#xff0c;又希望…...

Docker技术相关学习三

一、Docker镜像仓库管理 1.docker仓库&#xff1a;用于存储和分发docker镜像的集中式存储库&#xff0c;开发者可以将自己创建的镜像推送到仓库中也可以从仓库中拉取所需要的镜像。 2.docker仓库&#xff1a; 公有仓库&#xff08;docker hub&#xff09;&#xff1a;任何人都可…...

浏览器查询所有的存储信息,以及清除的语法

要在浏览器的控制台中查看所有的存储&#xff08;例如 localStorage、sessionStorage 和 cookies&#xff09;&#xff0c;你可以使用浏览器开发者工具的 "Application" 标签页。以下是操作步骤&#xff1a; 1. 打开开发者工具 在 Chrome 或 Edge 浏览器中&#xf…...

生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (上)

今天小李哥将开启全新的技术分享系列&#xff0c;为大家介绍生成式AI的安全解决方案设计方法和最佳实践。近年来&#xff0c;生成式 AI 安全市场正迅速发展。据 IDC 预测&#xff0c;到 2025 年全球 AI 安全解决方案市场规模将突破 200 亿美元&#xff0c;年复合增长率超过 30%…...

mysql 学习8 函数,字符串函数,数值函数,日期函数,流程函数

函数 一 字符串函数 二 数值函数 三 日期函数 四 流程函数...

WSL2中安装的ubuntu搭建tftp服务器uboot通过tftp下载

Windows中安装wsl2&#xff0c;wsl2里安装ubuntu。 1. Wsl启动后 1&#xff09;Windows下ip ipconfig 以太网适配器 vEthernet (WSL (Hyper-V firewall)): 连接特定的 DNS 后缀 . . . . . . . : IPv4 地址 . . . . . . . . . . . . : 172.19.32.1 子网掩码 . . . . . . . .…...

7.2.背包DP

背包DP 在C中&#xff0c;背包动态规划&#xff08;Knapsack DP&#xff09; 是解决资源分配类问题的核心算法范式&#xff0c;尤其在处理物品选择与容量限制的组合优化问题时表现优异。以下是针对不同背包类型的详细解析与代码实现&#xff1a; 一、背包DP问题分类 类型特点…...

芝法酱学习笔记(2.6)——flink-cdc监听mysql binlog并同步数据至elastic-search和更新redis缓存

一、需求背景 在有的项目中&#xff0c;尤其是进销存类的saas软件&#xff0c;一开始为了快速把产品做出来&#xff0c;并没有考虑缓存问题。而这类软件&#xff0c;有着复杂的业务逻辑。如果想在原先的代码中&#xff0c;添加redis缓存&#xff0c;改动面将非常大&#xff0c…...

pandas习题 071:字典元素列表构造 DataFrame

(编码题)以下有一个列表嵌套字典 data,列表中的每个字典 fields 中的列表为每行数据的值,另有一个 col 为列名,利用这两个数据构造一个 DataFrame。 data = [{fields: [2024-10-07T21:22:01, USER-A, 21, 0,...

FFmpeg rtmp推流直播

文章目录 rtmp协议RTMP协议组成RTMP的握手过程RTMP流的创建RTMP消息格式Chunking(Message 分块) rtmp服务器搭建Nginx服务器配置Nginx服务器 librtmp库编译推流 rtmp协议 RTMP&#xff08;Real Time Messaging Protocol&#xff09;是由Adobe公司基于Flash Player播放器对应的…...

北京门头沟区房屋轮廓shp的arcgis数据建筑物轮廓无偏移坐标测评

在IT行业中&#xff0c;地理信息系统&#xff08;GIS&#xff09;是用于处理、分析和展示地理空间数据的重要工具&#xff0c;而ArcGIS则是GIS领域中的一款知名软件。本文将详细解析标题和描述中提及的知识点&#xff0c;并结合“门头沟区建筑物数据”这一标签&#xff0c;深入…...

Verilog基础(一):基础元素

verilog基础 我先说&#xff0c;看了肯定会忘&#xff0c;但是重要的是这个过程&#xff0c;我们知道了概念&#xff0c;知道了以后在哪里查询。语法都是术&#xff0c;通用的概念是术。所以如果你有相关的软件编程经验&#xff0c;那么其实开启这个学习之旅&#xff0c;你会感…...

Games104——游戏引擎Gameplay玩法系统:基础AI

这里写目录标题 寻路/导航系统NavigationWalkable AreaWaypoint NetworkGridNavigation Mesh&#xff08;寻路网格&#xff09;Sparse Voxel Octree Path FindingDijkstra Algorithm迪杰斯特拉算法A Star&#xff08;A*算法&#xff09; Path Smoothing Steering系统Crowd Simu…...

vue生命周期及其作用

vue生命周期及其作用 1. 生命周期总览 2. beforeCreate 我们在new Vue()时&#xff0c;初始化一个Vue空的实例对象&#xff0c;此时对象身上只有默认的声明周期函数和事件&#xff0c;此时data,methods都未被初始化 3. created 此时&#xff0c;已经完成数据观测&#xff0…...

数据分析师使用Kutools for Excel 插件

数据分析师使用Kutools for Excel 插件 Kutools for Excel 是一款功能强大的 Excel 插件&#xff0c;旨在提高 Excel 用户的工作效率&#xff0c;简化复杂的操作。它提供了超过 300 个增强功能&#xff0c;帮助用户快速完成数据管理、格式化、排序、分析等任务&#xff0c;特别…...

毫秒级响应的VoIP中的系统组合推荐

在高并发、低延迟、毫秒级响应的 VoIP 场景中&#xff0c;选择合适的操作系统组合至关重要。以下是针对 Ubuntu linux-lowlatency、CentOS Stream kernel-rt 和 Debian 自定义 PREEMPT_RT 的详细对比及推荐&#xff1a; 1. 系统组合对比 特性Ubuntu linux-lowlatencyCentO…...

unordered_map/set的哈希封装

【C笔记】unordered_map/set的哈希封装 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】unordered_map/set的哈希封装前言一. 源码及框架分析二.迭代器三.operator[]四.使用哈希表封装unordered_map/set后言 前言 哈…...

表格标签的使用

一.表格标签 1.1表格标签的作用 用来显示和展示数据&#xff0c;不是用来布局页面的。 1.2表格的基本语法 <table> //用于定义表格标签 <tr> // table row 用于定义表格中的行&#xff0c;必须嵌套在<table> </table>标签中 <td>单元格内的文…...

python算法和数据结构刷题[5]:动态规划

动态规划&#xff08;Dynamic Programming, DP&#xff09;是一种算法思想&#xff0c;用于解决具有最优子结构的问题。它通过将大问题分解为小问题&#xff0c;并找到这些小问题的最优解&#xff0c;从而得到整个问题的最优解。动态规划与分治法相似&#xff0c;但区别在于动态…...

【cocos creator】【模拟经营】餐厅经营demo

下载&#xff1a;【cocos creator】模拟经营餐厅经营...

编程AI深度实战:给vim装上AI

系列文章&#xff1a; 编程AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 编程AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 编程AI深度实战&#xff1a;给vim装上AI-CSDN博客 编程AI深度实战&#xff1a;火的编程AI&…...

信息学奥赛一本通 2088:【22CSPJ普及组】逻辑表达式(expr) | 洛谷 P8815 [CSP-J 2022] 逻辑表达式

【题目链接】 ybt 2088&#xff1a;【22CSPJ普及组】逻辑表达式(expr) 洛谷 P8815 [CSP-J 2022] 逻辑表达式 【题目考点】 1. 表达式树&#xff1a;中缀表达式建树 可以看该问题信息学奥赛一本通 1356&#xff1a;计算(calc) 了解中缀表达式建树过程。 【解题思路】 解法…...

Linux系统管理

文章目录 一、进程与服务二、systemctl基本语法操作 三、系统运行级别Linux进程运行级别查看当前运行级别修改当前运行级别 四、关机重启命令 一、进程与服务 守护进程与服务是一个东西。 二、systemctl 基本语法 systemctl start|stop|restart|status 服务名查看服务的方法…...

CTFSHOW-WEB入门-命令执行71-77

题目&#xff1a;web 71 题目&#xff1a;解题思路&#xff1a;分析可知highlight_file() 函数被禁了&#xff0c;先想办法看看根目录&#xff1a;cvar_export(scandir(dirname(‘/’))); 尝试一下发现很惊奇&#xff1a;&#xff08;全是&#xff1f;&#xff09;这种情况我也…...

[MRCTF2020]Ez_bypass1(md5绕过)

[MRCTF2020]Ez_bypass1(md5绕过) ​​ 这道题就是要绕过md5强类型比较&#xff0c;但是本身又不相等&#xff1a; md5无法处理数组&#xff0c;如果传入的是数组进行md5加密&#xff0c;会直接放回NULL&#xff0c;两个NuLL相比较会等于true&#xff1b; 所以?id[]1&gg…...

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算四、使用宏设置设置页面切换和音频同步一、 插入音频及音频设置 1.插入音频:点击菜单栏插入-音频-选择PC上的音频(已存在的音频)或者录制音频(现场录制) …...

Modbus Slave RTU 在 AVP28335(兼容德州仪器TMS 320 28335) 上实现含源码及注释。

今天先把题目先给出来&#xff0c; 在近两天会把源码 &#xff08;含详细注释 &#xff09;及部署、测试结果给出来&#xff0c; 希望能给大家帮助。&#xff08;原来这个程序在CSDN中&#xff0c;有小伙伴已经写了一些&#xff0c;但是发现里面埋了很多坑&#xff0c;例如&…...

git-secret 使用教程

以下是一份详细的 git-secret 使用教程&#xff0c;包含常见场景的 Bash 代码示例&#xff1a; 1. 安装 git-secret # Ubuntu/Debian sudo apt-get install git-secret# macOS (Homebrew) brew install git-secret# 其他 Linux (Snap) sudo snap install git-secret# 验证安装…...

防火墙安全策略

目录 一.拓扑及需求 二.需求分析 三.配置详细信息 防火墙&#xff1a; OA server&#xff1a; Web Server&#xff1a; PC1&#xff1a; ​编辑PC2&#xff1a; PC3&#xff1a; 配置安全区域&#xff1a; 交换机&#xff1a; 四.需求实现以及测试&#xff1a; 1.…...