news 2026/4/17 21:13:11

复合类型(指针、数组和指针算术)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
复合类型(指针、数组和指针算术)

指针、数组和指针算术

指针和数组基本等价的原因在于指针算术(pointer afithmetic)和C++内部处理数组的方式。首先,我
们来看一看算术。将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型
的字节数。将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8:将指向
short的指针加1后,如果系统对short使用2个字节存储,则指针值将增加2。程序清单4.19演示了这种
令人吃惊的现象,它还说明了另一点:C++将数组名解释为地址。

// 指针、数组和指针算术.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> int main() { using namespace std; double wages[3] = { 10000.0,20000.0,30000.0 }; short stacks[3] = { 3,2,1 }; double* pw = wages; short* ps = &stacks[0]; cout << "pw = " << pw << ",*pw =" << *pw << endl; pw = pw + 1; cout << "pw =" << pw << ",*pw=" << *pw << "\n\n"; cout << "ps=" << ps << ",*ps=" << *ps << endl; ps = ps + 1; cout << "add 1 to the ps pointer:\n"; cout << "ps =" << ps << ",*ps=" << *ps << "\n\n"; cout << "access two elements with array notation\n"; cout << "stacks[0] =" << stacks[0] << ",stacks[1] = " << stacks[1] << endl; cout << "access two elements with pointer notation\n"; cout << "*stacks =" << *stacks << ",*(stacks+1)=" << *(stacks + 1) << endl; cout << sizeof(wages) << " = size of wages array\n"; cout << sizeof(pw) << " = size of pw pointer\n"; return 0; }

运行结果

pw = 00000001000FF738,*pw =10000 pw =00000001000FF740,*pw=20000 ps=00000001000FF764,*ps=3 add 1 to the ps pointer: ps =00000001000FF766,*ps=2 access two elements with array notation stacks[0] =3,stacks[1] = 2 access two elements with pointer notation *stacks =3,*(stacks+1)=2 24 = size of wages array 8 = size of pw pointer

程序说明

在多数情况下,C++将数组名解释为数组第1个元素的地址。因此,下面的语句将pw声明为指向double类型的指针,然后将它初始化为wages—wages数组中第1个元素的地址:

double *pw=wages;

和所有数组一样,wages 也存在下面的等式:

wages=&wages[0]=address of first element of array

为表明情况确实如此,该程序在表达式&stacks[0]中显式地使用地址运算符来将ps 指针初始化为stacks
数组的第1 个元素。

接下来,程序查看pw 和pw 的值。前者是地址,后者是存储在该地址中的值。由于pw 指向第1 个元
素,因此
pw 显示的值为第1 个元素的值,即10000。接着,程序将pw 加1。正如前面指出的,这样数字
地址值将增加8,这使得pw 的值为第2 个元素的地址。因此,*pw 现在的值是20000—第2 个元素的值
(参见图4.10,为使改图更为清晰,对其中的地址值做了调整)。

此后,程序对ps执行相同的操作。这一次由于ps指向的是short类型,而short占用2个字节,因此
将指针加1时,其值将增加2。结果是,指针也指向数组中下一个元素。

注意:将指针变量加1后,其增加的值等于指向的类型占用的字节数。

现在来看一看数组表达式stacks[1]。C++编译器将该表达式看作是*(stacks+1),这意味着先计算数
组第2个元素的地址,然后找到存储在那里的值。最后的结果便是stacks[1]的含义(运算符优先级要求使
用括号,如果不使用括号,将给*stacks加1,而不是给stacks加1)。

从该程序的输出可知,(stacks+1)和stacks[1]是等价的。同样,(stacks+2)和stacks[2]也是等
价的。通常,使用数组表示法时,C++都执行下面的转换:

arrayname[i] becomes *(arrayname + i)

如果使用的是指针,而不是数组名,则C++也将执行同样的转换:

pointername[i] becomes *(pointername +i)

因此,在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,
也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,
而数组名是常量:

pointername=pointername+1;//valid arrayname=arrayname+1; //not allowed

另一个区别是,对数组应用sizeof 运算符得到的是数组的长度,而对指针应用sizeof 得到的是指针的
长度,即使指针指向的是一个数组。例如,在程序清单4.19 中,pw 和wages 指的是同一个数组,但对它
们应用sizeof 运算符得到的结果如下:

24=size of wages array<<displaying sizeof wages 4=size of pw pointer<<displaying sizeof pw

总之,使用new 来创建数组以及使用指针来访问不同的元素很简单。只要把指针当作数组名对待即可。
然而,要理解为何可以这样做,将是一种挑战。要想真正了解数组和指针,应认真复习它们的相互关系。

指针小结

刚才已经介绍了大量指针的知识,下面对指针和数组做一总结。

1.声明指针

要声明指向特定类型的指针,请使用下面的格式:

typeName * pointerName;


面是一些示例:

double *pn; char *pc;

其中,pn 和pc 都是指针,而double *和char *是指向double 的指针和指向char 的指针。

2.给指针赋值

应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址,new运算符返回
未命名的内存的地址。
下而是一些示例:

double *pn; //pn can point to a double value double *pa; //so can pa char *pc; //pc can point to a char value double buddle=3.2; pn=&bubble; //assign address of bubble to pn pc=new char; //assign address of newly allocated char memory to pc pa=new double[30]; //assign address of lst element of array of 30 double to pa

3.对指针解除引用

对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符()来解除引用。因
此,如果像上面的例子中那样,pn 是指向bubble 的指针,则
pn 是指向的值,即3.2。
下面是一些示例:

cout<<*pn; //print the value of bubble *pc ='S'; //place 'S' into the memory location whose address is pc

另一种对指针解除引用的方法是使用数组表示法,例如,pn[0]与*pn是一样的。决不要对未被初始化
为适当地址的指针解除引用。

4.区分指针和指针所指向的值

如果pt是指向int的指针,则*pt不是指向int的指针,而是完全等同于一个int类型的变量。pt才是指针。
下面是一些示例:

int *pt=new int; //assigns an address to thhe pointer pt *pt =5; //stores the value 5 at that address

5.数组名

在多数情况下,C++将数组名视为数组的第一个元素的地址。
下面是一个示例:

int tacos[10]; //now tacos is the same as &tacos[0]

一种例外情况是,将sizeof 运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。

6.指针算术

C++允许将指针和整数相加。加1 的结果等于原来的地址值加上指向的对象占用的总字节数。还可以
将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个
数组(也可以指向超出结尾的一个位置)时,这种运算才有意义;这将得到两个元素的间隔。

下面是一些示例:

int tacos[10]={5,2,8,4,1,2,2,4,6,8}; int *pt=tacos; //suppose pf and tacos are the address 3000 pt=pt+1; //now pt is 3004 if a int is 4 bytes int *pe=&tacos[9]; //pe is 3036 if an int is 4 bytes pe=pe-1; //now pe is 3032,the address of tacos[8] int diff=pe - pt; //diff is 7,the separation between tacos[8] and tacos[1]

7.数组的动态联编和静态联编

使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:

int tacos[10]; //static binding,size fixed at compile time

使用new[ ]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度
也将在运行时设置。使用完这种数组后,应使用delete [ ]释放其占用的内存:

int size; cin>>size; int *pt=new int[size]; //dynamic binding,size set at run time ... delete[] pz; //free memory when finished

8 数组表示法和指针表示法

使用方括号数组表示法等同于对指针解除引用:

tacos[0] means *tacos means the value at address tacos tacos[3] means *(tacos +3) means the value at address tacos + 3

数组名和指针变量都是如此,因此对于指针和数组名,既可以使用指针表示法,也可以使用数组
表示法。
下面是一些示例:

int *pt=new int[10]; //pt points to block of 10 ints *pt=5; //set element number 0 to 5 pt[0]=6; //reset element number 0 to 6 pt[9]=44; //set tenth element(element number 9) to 44 int coats[10]; *(coats+4)=12; //set coats[4] to 12

指针和字符串

数组和指针的特殊关系可以扩展到C-风格字符串。请看下面的代码:

char flower[10]="rose"; cout<<flower<<"s are red\n";

数组名是第一个元素的地址,因此cout语句中的flower是包含字符r的char元素的地址。cout对象认
为char的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符
(\0)为止。总之,如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。

这里的关键不在于flower是数组名,而在于flower是一个char的地址。这意味着可以将指向char的
指针变量作为cout的参数,因为它也是char的地址。当然,该指针指向字符串的开头,稍后将核实这一点。

前面的cout语句中最后一部分的情况如何呢?如果flower是字符串第一个字符的地址,则表达式“s are red\n”是什么呢?为了与cout对字符串输出的处理保持一致,这个用引号括起的字符串也应当是一个地址。
在C++中,用引号括起的字符串像数组名一样,也是第一个元素的地址。上述代码不会将整个字符串发送
给cout,而只是发送该字符串的地址。这意味着对于数组中的字符串、用引号括起的字符串常量以及指针
所描述的字符串,处理的方式是一样的,都将传递它们的地址。与逐个传递字符串中的所有字符相比,这
样做的工作量确实要少。

注意:在cout 和多数C++表达式中,char 数组名、char 指针以及用引号括起的字符串常量都被解释为
字符串第一个字符的地址。

程序清单4.20 演示了如何使用不同形式的字符串。它使用了两个字符串库中的函数。函数strlen( )我
们以前用过,它返回字符串的长度。函数strcpy( )将字符串从一个位置复制到另一个位置。这两个函数的
原型都位于头文件cstring(在不太新的实现中,为string.h)中。该程序还通过注释指出了应尽量避免的错
误使用指针的方式。

#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> int main() { using namespace std; char animal[20] = "bear"; // animal holds bear const char* bird = "wren"; // bird holds address of string char* ps; // uninitialized cout << animal << " and"; // display bear cout << bird << "\n"; // display wren cout << "Enter a kind of animal: "; cin >> animal; ps = animal; cout << ps << "!\n"; cout << "Before using strcpy():\n"; cout << animal << " at " << (int*)animal << endl; cout << ps << " at " << (int*)ps << endl; ps = new char[strlen(animal) + 1]; // get new storage strcpy(ps, animal); // copy string to new storage cout << "After using strcpy():\n"; cout << animal << " at " << (int*)animal << endl; cout << ps << " at " << (int*)ps << endl; delete[] ps; return 0; }

运行结果

bear andwren Enter a kind of animal: fox fox! Before using strcpy(): fox at 0000000D382FF9C8 fox at 0000000D382FF9C8 After using strcpy(): fox at 0000000D382FF9C8 fox at 000001F897C3E020

程序说明
程序清单4.20中的程序创建了一个char数组(animal)和两个指向char的指针变量(bird和ps)。该
程序首先将animal数组初始化为字符串"bear”,就像初始化数组一样。然后,程序执行了一些新的操作,
将char指针初始化为指向一个字符串:

const char *bird="wren"; //bird holds address of string

记住,"wren“实际表示的是字符串的地址,因此这条语句将"wren”的地址赋给了指针。(一般
来说,编译器在内存留出一些空间,以存储程序源代码中所有用引号括起的字符串,并将每个被存储的字
符串与其地址关联起来。)这意味着可以像使用字符串"wren”那样使用指针bird,如下面的示例所示:

cout<<" A concerned "<<bird<<" speaks\n";

字符串字面值是常量,这就是为什么代码在声明中使用关键字const 的原因。以这种方式使用const 意
味着可以用bird 来访问字符串,但不能修改它。第7 章将详细介绍const 指针。最后,指针ps 未被初始化,
因此不指向任何字符串(正如您知道的,这通常是个坏主意,这里也不例外)。

接下来,程序说明了这样一点,即对于cout 来说,使用数组名animal 和指针bird 是一样的。毕竟,
它们都是字符串的地址,cout 将显示存储在这两个地址上的两个字符串(“bear”和“wren”)。如果激活错
误地显示ps 的代码,则将可能显示一个空行、一堆乱码,或者程序将崩溃。创建未初始化的指针有点像签
发空头支票:无法控制它将被如何使用。

对于输入,情况有点不同。只要输入比较短,能够被存储在数组中,则使用数组animal 进行输入将是安全的。然而,使用bird 来进行输入并不合适:

  • 有些编译器将字符串字面值视为只读常量,如果试图修改它们,将导致运行阶段错误。在C++中,
    字符串字面值都将被视为常量,但并不是所有的编译器都对以前的行为做了这样的修改。
  • 有些编译器只使用字符串字面值的一个副本来表示程序中所有的该字面值。

下面讨论一下第二点。C++不能保证字符串字面值被唯一地存储。也就是说,如果在程序中多次使用
了字符串字面值“wren”,则编译器将可能存储该字符串的多个副本,也可能只存储一个副本。如果是后面
一种情况,则将bird 设置为指向一个“wren”,将使它只是指向该字符串的唯一一个副本。将值读入一个
字符串可能会影响被认为是独立的、位于其他地方的字符串。无论如何,由于bird 指针被声明为const,因
此编译器将禁止改变bird 指向的位置中的内容。

试图将信息读入ps 指向的位置将更糟。由于ps 没有被初始化,因此并不知道信息将被存储在哪里,这甚至
可能改写内存中的信息。幸运的是,要避免这种问题很容易—只要使用足够大的char 数组来接收输入即可。请
不要使用字符串常量或未被初始化的指针来接收输入。为避免这些问题,也可以使用std::string 对象,而不是数组。

警告:在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是使用new
初始化过的指针。

接下来,请注意下述代码完成的工作:

ps=animal; ... cout<<animal<<" at"<<(int *)animal<<endl; cout<<ps<<" at"<<(int *)ps<<endl;

一般来说,如果给cout 提供一个指针,它将打印地址。但如果指针的类型为char *,则cout 将显示指
向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int *(上
面的代码就是这样做的)。因此,ps 显示为字符串“fox”,而(int *)ps 显示为该字符串的地址。注意,将
anim 赋给ps 并不会复制字符串,而只是复制地址。这样,这两个指针将指向相同的内存单元和字符串。

要获得字符串的副本,还需要做其他工作。首先,需要分配内存来存储该字符串,这可以通过声明另
一个数组或使用new来完成。后一种方法使得能够根据字符串的长度来指定所需的空间:

ps=new char[strlen(animal)+1];

字符串“fox”不能填满整个animal 数组,因此这样做浪费了空间。上述代码使用strlen( )来确定字符
串的长度,并将它加1 来获得包含空字符时该字符串的长度。随后,程序使用new 来分配刚好足够存储该
字符
串的空间。

接下来,需要将animal 数组中的字符串复制到新分配的空间中。将animal 赋给ps 是不可行的,因为
这样只能修改存储在ps 中的地址,从而失去程序访问新分配内存的唯一途径。需要使用库函数strcpy( ):

strcpy(ps,animal);

strcpy( )函数接受2 个参数。第一个是目标地址,第二个是要复制的字符串的地址。您应确定,分配了
目标空间,并有足够的空间来存储副本。在这里,我们用strlen( )来确定所需的空间,并使用new 获得可
用的内存。

通过使用strcpy( )和new,将获得“fox”的两个独立副本:

另外,new 在离animal 数组很远的地方找到了所需的内存空间。
经常需要将字符串放到数组中。初始化数组时,请使用=运算符;否则应使用strcpy( )或strncpy( )。
strcp
y( )在前面已经介绍过,其工作原理如下:

char food[20]='carrots'; //initialization strcpy(food,"flan"); //otherwise

注意,类似下面这样的代码可能导致问题,因为food数组比字符串小:

strcpy(food,"a picnic basket filled with many goodies");

在这种情况下,函数将字符串中剩余的部分复制到数组后面的内存字节中,这可能会覆盖程序正在使
用的其他内存。要避免这种问题,请使用strncpy( )。该函数还接受第3 个参数—要复制的最大字符数。
然而,要注意的是,如果该函数在到达字符串结尾之前,目标内存已经用完,则它将不会添加空字符。因
此,应该这样使用该函数:

strncpy(food,"a picnic basket filled with many goodies",19); food[19]='\0';

这样最多将19 个字符复制到数组中,然后将最后一个元素设置成空字符。如果该字符串少于19 个字
符,则strncpy( )将在复制完该字符串之后加上空字符,以标记该字符串的结尾。

警告:应使用strcpy( )或strncpy( ),而不是赋值运算符来将字符串赋给数组。
您对使用C-风格字符串和cstring 库的一些方面有了了解后,便可以理解为何使用C++ string 类型更为
简单了:您不用担心字符串会导致数组越界,并可以使用赋值运算符而不是函数strcpy( )和strncpy( )。

使用new 创建动态结构

在运行时创建数组优于在编译时创建数组,对于结构也是如此。需要在程序运行时为结构分配所需的
空间,这也可以使用new运算符来完成。通过使用new,可以创建动态结构。同样,“动态”意味着内存
是在运行时,而不是编译时分配的。由于类与结构非常相似,因此本节介绍的有关结构的技术也适用于类。

将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。
例如,要创建一个未命名的inflatable类型,并将其地址赋给一个指针,可以这样做:

inflatable *ps=new inflatable;

这将把足以存储inflatable 结构的一块可用内存的地址赋给ps。这种句法和C++的内置类型完全相同。

比较棘手的一步是访问成员。创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没
有名称,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(−>)。该运算符由
连字符和大于号组成,可用于指向结构的指针,就像点运算符可用于结构名一样。例如,如果ps 指向一个
inflatable 结构,则ps−>price 是被指向的结构的price 成员(参见图4.11)。

提示:有时,C++新手在指定结构成员时,搞不清楚何时应使用句点运算符,何时应使用箭头运算符。
规则非常简单。如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭
头运算符。

另一种访问结构成员的方法是,如果ps 是指向结构的指针,则ps 就是被指向的值—结构本身。由
ps 是一个结构,因此(*ps).price 是该结构的price 成员。C++的运算符优先规则要求使用括号。
程序清单4.21 使用new 创建一个未命名的结构,并演示了两种访问结构成员的指针表示法。

#include <iostream> struct inflatable //structure definition { char name[20]; float volume; double price; }; int main() { using namespace std; inflatable* ps = new inflatable; cout << "Enter name of inflatable item:"; cin.get(ps->name, 20); cout << "Enter volume in cubic feet:"; cin >> (*ps).volume; cout << "Enter price:$"; cin >> ps->price; cout << "Name:" << (*ps).name << endl; cout << "Volume:" << ps->volume << " cubic feet\n"; cout << "Price:$" << ps->price << endl; delete ps; return 0; }

运行结果

Enter name of inflatable item:Fabulous Frodo Enter volume in cubic feet:1.4 Enter price:$27.99 Name:Fabulous Frodo Volume:1.4 cubic feet Price:$27.99

1.一个使用new 和delete 的示例

下面介绍一个使用new 和delete 来存储通过键盘输入的字符串的示例。程序清单4.22 定义了一个函数
getname( ),该函数返回一个指向输入字符串的指针。该函数将输入读入到一个大型的临时数组中,然后使
用new [ ]创建一个刚好能够存储该输入字符串的内存块,并返回一个指向该内存块的指针。对于读取大量
字符串的程序,这种方法可以节省大量内存(实际编写程序时,使用string 类将更容易,因为这样可以使
用内置的new 和delete)。

假设程序要读取100 个字符串,其中最大的字符串包含79 个字符,而大多数字符串都短得多。如果用
char 数组来存储这些字符串,则需要1000 个数组,其中每个数组的长度为80 个字符。这总共需要80000
个字节,而其中的很多内存没有被使用。另一种方法是,创建一个数组,它包含1000个指向char 的指针,然后使用new根据每个字符串的需要分配相应数量的内存。这将节省几万个字节。是根据输入来分配内存,
而不是为每个字符串使用一个大型数组。另外,还可以使用new根据需要的指针数量来分配空间。就目前
而言,这有点不切实际,即使是使用1000个指针的数组也是这样,不过程序清单4.22还是演示了一些技
巧。另外,为演示delete是如何工作的,该程序还用它来释放内存以便能够重新使用。

#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> using namespace std; char* getname(void); int main() { char* name; name = getname(); cout << name << " at" << (int*)name << "\n"; delete[] name; name = getname(); cout << name << " at" << (int*)name << "\n"; delete[] name; return 0; } char* getname() { char temp[80]; cout << "Enter last name:"; cin >> temp; char* pn = new char[strlen(temp) + 1]; strcpy(pn, temp); return pn; }

运行结果

Enter last name:Fredeldumpkin Fredeldumpkin at0000024F3C6AEE80 Enter last name:Pook Pook at0000024F3C6AE5C0

2.程序说明

来看一下程序清单4.22 中的函数getname( )。它使用cin 将输入的单词放到temp 数组中,然后使用new
分配新内存,以存储该单词。程序需要strle(temp)+ 1 个字符(包括空字符)来存储该字符串,因此将
这个值提供给new。获得空间后,getname( )使用标准库函数strcpy( )将temp 中的字符串复制到新的内存块
中。该函数并不检查内存块是否能够容纳字符串,但getname( )通过使用new 请求合适的字节数来完成了
这样的工作。最后,函数返回pn,这是字符串副本的地址。

在main( )中,返回值(地址)被赋给指针name。该指针是在main( )中定义的,但它指向getname( )
函数中分配的内存块。然后,程序打印该字符串及其地址。

接下来,在释放name 指向的内存块后,main( )再次调用getname( )。C++不保证新释放的内存就是下一次使用new 时选择的内存,从程序运行结果可知,确实不是。

在这个例子中,getname( )分配内存,而main( )释放内存。将new 和delete 放在不同的函数中通常并不
是个好办法,因为这样很容易忘记使用delete。不过这个例子确实把new 和delete 分开放置了,只是为了
说明这样做也是可以的。

为了解该程序的一些更为微妙的方面,需要知道一些有关c++是如何处理内存的知识。下面介绍一些
这样的知识,这些知识将在第9章做全面介绍。

自动存储、静态存储和动态存储

根据用于分配内存的方法,C++有3 种管理数据内存的方式:自动存储、静态存储和动态存储(有时
也叫作自由存储空间或堆)。在存在时间的长短方面,以这3 种方式分配的数据对象各不相同。下面简要地
介绍每种类型(C++11 新增了第四种类型—线程存储,这将在第9 章简要地讨论)。

1.自动存储

在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们
在所属的函数被调用时自动产生,在该函数结束时消亡。例如,程序清单4.22 中的temp 数组仅当getname( )
函数活动时存在。当程序控制权回到main( )时,temp 使用的内存将自动被释放。如果getname( )返回temp
的地址,则main( )中的name 指针指向的内存将很快得到重新使用。这就是在getname( )中使用new 的原
因之一。

实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段
代码。到目前为止,我们使用的所有代码块都是整个函数。然而,在下一章将会看到,函数内也可以有代
码块。如果在其中的某个代码块定义了一个变量,则该变量仅在程序执行该代码块中的代码时存在。

自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块
时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在程序执行过程中,栈将不断地
增大和缩小。

2.静态存储

静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面
定义它;另一种是在声明变量时使用关键字static:

static double fee=56.50;

在K&RC中,只能初始化静态数组和静态结构,而C++Release2.0(及后续版本)和ANSIC中,也
可以初始化自动数组和自动结构。然而,一些您可能己经发现,有些C++实现还不支持对自动数组和自动
结构的初始化。

第9 章将详细介绍静态存储。自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。
变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。

动态存储

new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++
中被称为自由存储空间store)或堆(heap)。该内存池同用于静态变量和自动变量的内存是分开的。程序
清单4.22表明,new和delete让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命
周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用
内存有更大的控制权。然而,内存管理也更复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的,
但和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 3:29:19

Elastic 在 AWS re:Invent:总结一年在 agentic AI 创新中的合作

作者&#xff1a;来自 Elastic Brian BergholmJenn MichelUdayasimha Theepireddy (Uday) 又一年以客户为中心的卓越合作亮点。 Elastic 的能力&#xff08;包括向量数据库和上下文工程&#xff09;与 AWS 服务的集成&#xff0c;帮助客户更快、更灵活地构建智能、可扩展且安全…

作者头像 李华
网站建设 2026/4/16 21:20:34

n1n:从替代LiteLLM Proxy自建网关到企业级统一架构的进阶之路

摘要&#xff1a;在 2025 年的大模型应用开发中&#xff0c;如何统一管理 GPT-4、Claude 3.5、Gemini 1.5 等异构 API 成为企业的核心痛点。本文将深度解析开源网关 LiteLLM 的技术原理与实施路径&#xff0c;剖析自建网关在生产环境中的“隐形深坑”&#xff0c;并探讨如何通过…

作者头像 李华
网站建设 2026/4/16 20:54:58

数控机床床身机构优化设计(任务书)

毕业论文(设计)任务书 题目 数控机床床身机构优化设计 学院 专业班级 学生姓名 年级 学号 指导教师 课题来源 (【√】) 【】科研项目 【】实践教育基地项目 【】自拟课题 课题内容及目标 一、课题内容 1、数控机床床身结构调研与分析 收集现有数控机床床身结构类型、材…

作者头像 李华
网站建设 2026/4/15 19:04:49

保姆级教程---在 Windows 上安装运维神器——宝塔面板,告别繁琐命令行!

摘要:对于习惯了图形化界面的 Windows Server 管理员或开发者来说,手动配置 IIS、PHP、MySQL 环境简直是噩梦。今天为大家介绍一款“运维神器”——宝塔面板(Windows 版)。本文将手把手教你如何在 Windows 环境下从零安装并配置宝塔面板,实现服务器管理的“可视化”与“一…

作者头像 李华
网站建设 2026/4/17 12:15:23

Python 的开发效率真的比 Java 高吗

的开发效率通常高于 Java&#xff0c;主要体现在&#xff1a;语法简洁、开发周期短、动态类型提升灵活性、生态丰富快速上手、适合原型迭代。**其中&#xff0c;语法简洁带来的代码量减少最为显著——根据多项行业统计&#xff0c;同样功能&#xff0c;Python 编写所需代码行数…

作者头像 李华