第四章 复合类型(下篇)
如何使用const关键字来保护指针和其指向的数据?
如何比较两个指针的值?
如何处理new失败的情况?
如何检查new是否成功分配了内存?
4.7 指针和自由存储空间
- 指针是一种变量,指针本身也是一种复合类型(例如,类型名为int *),它存储的是值的地址,而不是值本身。可以使用*运算符来解除引用指针,得到指向的值。
- &运算符可以用来获取变量的地址。例如,如果home是一个变量,则&home是它的地址。
- 声明指针时,需要指定指针所指向的值的类型。例如,int * p_updates;声明了一个指向int类型的指针。
- 赋值指针时,需要使用&运算符来获取目标变量的地址。例如,p_updates = &updates;将updates的地址赋给p_updates。
- 使用指针时,可以像使用普通变量一样,对其进行赋值、比较和运算。例如,*p_updates = *p_updates + 1;将更新p_updates所指向的值。
示例:
// pointer.cpp -- our first pointer variable
#include <iostream>
int main()
{
using namespace std;
int updates = 6; // declare a variable
int * p_updates; // declare pointer to an int
p_updates = &updates; // assign address of int to pointer
// express values two ways
cout << "Values: updates = " << updates;
cout << ", *p_updates = " << *p_updates << endl;
// express address two ways
cout << "Addresses: &updates = " << &updates;
cout << ", p_updates = " << p_updates << endl;
// use pointer to change value
*p_updates = *p_updates + 1;
cout << "Now updates = " << updates << endl;
return 0;
}
输出:
Values: updates = 6, *p_updates = 6
Addresses: &updates = 0x0065fd48, p_updates = 0x0065fd48
Now updates = 7
从中可知,int变量updates和指针变量p_updates只不过是同一枚硬币的两面。变量updates表示值,并使用&运算符来获得地址;而变量p_updates表示地址,并使用运算符来获得值(参见下图 )。由于p_updates指向updates,因此p_updates和updates完全等价。可以像使用int变量那样使用p_updates。正如程序清单表明的,甚至可以将值赋给p_updates。这样做将修改指向的值,即updates。
4.7.1 声明和初始化指针
- 声明指针时,需要指定指针所指向的值的类型,并在变量名前加上*号。例如,int * pt;声明了一个指向int类型的指针。
- 初始化指针时,需要使用&运算符来获取目标变量的地址,并将其赋给指针。例如,int * pt = &higgens;将higgens的地址赋给pt。
- 使用指针时,可以使用*运算符来解除引用指针,得到指向的值。也可以使用指针本身来获取地址。例如,*pt和higgens是等价的,pt和&higgens也是等价的。
示例:
// init_ptr.cpp -- initialize a pointer
#include <iostream>
int main()
{
using namespace std;
int higgens = 5;
int * pt = &higgens;
cout << "Value of higgens = " << higgens
<< "; Address of higgens = " << &higgens << endl;
cout << "Value of *pt = " << *pt
<< "; Value of pt = " << pt << endl;
return 0;
}
输出:
Value of higgens = 5; Address of higgens = 0012FED4
Value of *pt = 5; Value of pt = 0012FED4
4.7.2 指针的危险
- 指针的危险:如果创建指针时没有初始化,或者没有为指针所指向的数据分配内存,就可能导致程序错误或崩溃。例如,long * fellow; *fellow = 223323;这样的代码是不安全的,因为fellow没有被赋值为一个有效的地址,而223323可能被存储在任意的内存位置上。
- 金科玉律:一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。这样可以避免指针指向不可预知的地方,或者覆盖重要的数据。
如何使用const关键字来保护指针和其指向的数据?我们继续往下走。
- 初始化指针:可以使用&运算符来获取目标变量的地址,并将其赋给指针。也可以使用new运算符来在堆中动态分配内存,并返回其地址。例如,int * pt = &higgens;或者int * pn = new int;都是有效的初始化指针的方法。
4.7.3 指针和数字
- 指针和数字:指针和整数是不同的类型,不能直接将整数赋给指针,除非进行强制类型转换。例如,int * pt = (int *) 0xB8000000;将一个整数值作为地址赋给指针。注意,pt是int值的地址并不意味着pt本身的类型是int。例如,在有些平台中,int类型是个2字节值,而地址是个4字节值。
- 指针的操作:指针可以进行一些有限的运算,如递增、递减、加减一个整数值等。这些运算会根据指针所指向的类型的大小来改变指针的值。例如,如果pt是一个指向int的指针,则pt++会使pt增加sizeof(int)(四)个字节。
- 指针和内存管理:指针可以用来管理运行阶段的内存空间分配,使用new运算符来在堆中动态分配内存,并返回其地址。使用delete运算符来释放动态分配的内存,并防止内存泄漏。
所以,如何比较两个指针的值?
我们继续往下走。
4.7.4 使用new来分配内存
- 使用new来分配内存:可以使用new运算符来在运行阶段为任意类型的数据对象分配内存,并返回其地址。例如,
int * pn = new int;
将在堆中为一个int类型的值分配内存,并将其地址赋给pn。可以使用*pn来访问或修改该值。但是。如何处理new失败的情况?我们继续往下走。
如何检查new是否成功分配了内存?我们继续往下走。
- 指针和数据对象:指针可以指向变量或者未命名的数据对象。变量是在编译时分配的有名称的内存,而未命名的数据对象是在运行时分配的没有名称的内存。只能通过指针来访问未命名的数据对象,不能通过名称来访问。
- 指针的类型:指针的类型必须与其指向的数据对象的类型相同,这样才能正确地解释内存中的值。指针本身的大小通常与地址的大小相同,而与其指向的类型无关。
// use_new.cpp -- using the new operator
#include <iostream>
int main()
{
using namespace std;
int nights = 1001;
int * pt = new int; // allocate space for an int
*pt = 1001; // store a value there
cout << "nights value = ";
cout << nights << ": location " << &nights << endl;
cout << "int ";
cout << "value = " << *pt << ": location = " << pt << endl;
double * pd = new double; // allocate space for a double
*pd = 10000001.0; // store a double there
cout << "double ";
cout << "value = " << *pd << ": location = " << pd << endl;
cout << "location of pointer pd: " << &pd << endl;
cout << "size of pt = " << sizeof(pt);
cout << ": size of *pt = " << sizeof(*pt) << endl;
cout << "size of pd = " << sizeof pd;
cout << ": size of *pd = " << sizeof(*pd) << endl;
return 0;
}
输出:
nights value = 1001: location 0028F7F8
int value = 1001: location = 00033A98
double value = 1e+007: location = 000339B8
location of pointer pd: 0028F7FC
size of pt = 4: size of *pt = 4
size of pd = 4: size of *pd = 8
new分配的内存块通常与常规变量声明分配的内存块不同。变量nights和pd的值都存储在被称为栈(stack)的内存区域中,而new从被称为堆(heap)或自由存储区(free store)的内存区域分配内存。
4.7.5 使用 delete 释放内存
- 使用delete释放内存:可以使用delete运算符来释放使用new分配的内存,并防止内存泄漏。例如,delete ps;将释放ps所指向的内存,但不会删除指针ps本身。为了避免使用悬空指针,并使ps成为一个悬空指针。可以将其赋值为nullptr。
nt * ps = new int; // allocate memory with new
. . . // use the memory
delete ps; // free memory with delete when done
- 释放内存的规则:只能用delete来释放使用new分配的内存,不能用delete来释放声明变量所获得的内存。也不能尝试释放已经释放的内存块,否则会导致不确定的结果。对空指针使用delete是安全的。
int * ps = new int; // ok
delete ps; // ok
delete ps; // not ok now
int jugs = 5; // ok
int * pi = &jugs; // ok
delete pi; // not allowed, memory not allocated by new
- 指针和地址:可以使用不同的指针来指向同一个内存块,并用其中任何一个指针来释放该内存块。但一般来说,不要创建两个指向同一个内存块的指针,因为这会增加错误地删除同一个内存块两次的可能性。
- 注意,使用delete的关键在于,将它用于new分配的内存。这并不意味着要使用用于new的指针,而是用于new的地址:
int * ps = new int; // allocate memory
int * pq = ps; // set second pointer to same block
delete pq; // delete with second pointer
4.7.6 使用 new来创建动态数组
- 使用new来创建动态数组:可以在new运算符后面加上方括号和数组长度来创建动态数组,并返回其首元素的地址。例如,
double * p3 = new double [3];
将在堆中为3个double类型的值分配内存,并将其首元素的地址赋给p3。可以使用p3[i]或*(p3 + i)来访问或修改第i个元素。 - 使用delete释放动态数组:要释放动态数组,需要在delete运算符后面也加上方括号。例如,
delete [] p3;
将释放p3所指向的整个数组。这样,可以防止内存泄漏和悬空指针。 - 指针和数组的关系:指针和数组基本等价,可以将指针当作数组名来使用,也可以将数组名当作指针来使用。但指针是变量,可以修改它的值,而数组名是常量,不能修改它的值。指针算术会根据指针所指向的类型的大小来改变指针的值。
// arraynew.cpp -- using the new operator for arrays
#include <iostream>
int main()
{
using namespace std;
double * p3 = new double [3]; // space for 3 doubles
p3[0] = 0.2; // treat p3 like an array name
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 + 1; // increment the pointer
cout << "Now p3[0] is " << p3[0] << " and ";
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 - 1; // point back to beginning
delete [] p3; // free the memory
return 0;
}
下面是该程序的输出:
p3[1] is 0.5.
Now p3[0] is 0.5 and p3[1] is 0.8.
从中可知,arraynew.cpp将指针p3当作数组名来使用,p3[0]为第1个元素,依次类推。下面的代码行指出了数组名和指针之间的根本差别:
p3 = p3 + 1; // okay for pointers, wrong for array names
不能修改数组名的值。但指针是变量,因此可以修改它的值。请注意将p3加1的效果。表达式p3[0]现在指的是数组的第2个值。因此,将p3加1导致它指向第2个元素而不是第1个。将它减1后,指针将指向原来的值,这样程序便可以给delete[ ]提供正确的地址。
相邻的int地址通常相差2个字节或4个字节,而将p3加1后,它将指向下一个元素的地址,这表明指针算术有一些特别的地方。情况确实如此。
4.8 指针、数组和指针算术
- 指针、数组和指针算术:指针和数组基本等价,可以将指针当作数组名来使用,也可以将数组名当作指针来使用。但指针是变量,可以修改它的值,而数组名是常量,不能修改它的值。指针算术会根据指针所指向的类型的大小来改变指针的值。
- 使用new创建动态数组:可以在new运算符后面加上方括号和数组长度来创建动态数组,并返回其首元素的地址。例如,double * p3 = new double [3];将在堆中为3个double类型的值分配内存,并将其首元素的地址赋给p3。可以使用p3[i]或*(p3 + i)来访问或修改第i个元素。
- 使用delete释放动态数组:要释放动态数组,需要在delete运算符后面也加上方括号。例如,delete [] p3;将释放p3所指向的整个数组。这样,可以防止内存泄漏和悬空指针。
示例:
// addpntrs.cpp -- pointer addition
#include <iostream>
int main()
{
using namespace std;
double wages[3] = {10000.0, 20000.0, 30000.0};
short stacks[3] = {3, 2, 1};
// Here are two ways to get the address of an array
double * pw = wages; // name of an array = address
short * ps = &stacks[0]; // or use address operator
// with array element
cout << "pw = " << pw << ", *pw = " << *pw << endl;
pw = pw + 1;
cout << "add 1 to the pw pointer:\n";
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 = 0x28ccf0, *pw = 10000
add 1 to the pw pointer:
pw = 0x28ccf8, *pw = 20000
ps = 0x28ccea, *ps = 3
add 1 to the ps pointer:
ps = 0x28ccec, *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
4 = size of pw pointer
4.8.1 程序说明
- C++将数组名解释为数组第一个元素的地址,因此可以用指针来访问数组元素。
double * pw = wages;
wages = &wages[0] = address of first element of array
- 将指针加1时,其增加的值等于指向的类型占用的字节数,因此指针也指向数组中下一个元素。
- 以用数组方括号表示法或解除引用运算符来访问指针或数组名所指向的元素,它们是等价的。
- 可以修改指针的值,但不能修改数组名的值,因为数组名是常量。
- 对数组名应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度。
- 对数组名取地址时,得到的是整个数组的地址,而不是第一个元素的地址。这种地址可以用一个指向包含n个元素的类型数组的指针来表示。
4.8.2 指针小结
1.声明指针
要声明指向特定类型的指针,请使用下面的格式:
typeName * pointerName;
下面是一些示例:
double * pn; // pn can point to a double value
char * pc; // pc can point to a char value
其中,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 bubble = 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 1st 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 the pointer pt
*pt = 5; // stores the value 5 at that address
5.数组名
在多数情况下,C++将数组名视为数组的第1个元素的地址。
下面是一个示例:
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 * pz = 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
4.8.3 指针和字符串
- 指针和字符串的关系:指针可以指向字符串的首地址,也可以通过指针访问和修改字符串中的字符。用引号括起的字符串常量是只读的,不能被修改。
- 指针数组的用法:指针数组是一个数组,它的元素都是指针,可以用来存放不同类型或长度的数据的地址。特别适合用来存放不同长度的字符串。
- 字符串和数组的区别:字符串可以用字符数组或字符指针来表示,但它们有本质的区别。字符数组可以修改其中的内容,而字符指针不能。字符数组在初始化时可以用等号赋值,而后只能用strcpy()或strncpy()函数来赋值。
- 字符串输入和输出的注意事项:在将字符串读入程序时,应使用已分配的内存地址,如数组名或用new初始化过的指针。在将字符串输出到屏幕时,如果给cout提供一个char指针,它将显示指向的字符串;如果要显示字符串的地址,则必须将指针强制转换为另一种类型。
4.8.4 使用 new创建动态结构
- 动态结构是在运行时分配内存的结构,而不是在编译时确定的。这样可以根据需要灵活地创建和销毁结构,节省内存空间。
- 要创建动态结构,需要使用new运算符和结构类型。例如,要创建一个名为inflatable的结构类型,并将其地址赋给一个指针ps,可以这样写:
inflatable * ps = new inflatable;
- 要访问动态结构的成员,有两种方法:
使用箭头运算符(->)和指向结构的指针。例如,要访问ps指向的结构的price成员,可以这样写:
ps->price
使用句点运算符(.)和解引用指针得到的结构。例如,要访问ps指向的结构的price成员,也可以这样写:
(*ps).price
- 要释放动态结构占用的内存,需要使用delete运算符和指向结构的指针。例如,要释放ps指向的结构的内存,可以这样写:
delete ps;
4.8.5 自动存储、静态存储和动态存储
- 自动存储是在函数内部定义的常规变量使用的存储方式,它们在函数被调用时自动创建,在函数结束时自动销毁。自动变量通常存储在栈中,它们的生命周期受函数的作用域限制。
- 静态存储是在整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static。静态变量通常存储在数据段中,它们的生命周期与程序相同。
- 动态存储是使用new和delete运算符管理的一种存储方式,它们操作一个内存池,称为自由存储空间或堆。动态变量通常存储在堆中,它们的生命周期由程序员控制,可以在任何时候创建和销毁。
4.9 类型组合
- 数组是一种存储多个相同类型的值的数据结构,它们可以是基本类型,如int或char,也可以是复合类型,如结构或类。
- 结构是一种存储多个不同类型的值的数据结构,它们可以是基本类型,如int或char,也可以是复合类型,如数组或指针。
- 指针是一种存储地址的变量,它们可以指向任何类型的数据,包括数组、结构或函数。
- 可以用各种方式组合数组、结构和指针,例如:
创建一个结构数组,其中每个元素都是一个结构。例如:
struct inflatable // structure definition
{
char name[20];
float volume;
double price;
};
inflatable trio[3]; // array of 3 structures
创建一个指针数组,其中每个元素都是一个指针。例如:
int * arr[5]; // array of 5 pointers to int
创建一个指向数组的指针,它可以用来访问数组的元素。例如:
int arr[5] = {1, 2, 3, 4, 5};
int * p = arr; // pointer to the first element of arr
创建一个指向结构的指针,它可以用来访问结构的成员。例如:
inflatable balloon; // a structure
inflatable * p = &balloon; // pointer to the structure
创建一个指向指针的指针,它可以用来访问被指向的指针所指向的数据。例如:
int x = 10; // an int variable
int * p = &x; // pointer to x
int ** q = &p; // pointer to p
- 要访问组合类型的数据,有两种常用的运算符:
句点运算符(.)用于访问结构名或解引用指针得到的结构的成员。例如:
balloon.name; // access the name member of balloon
(*p).name; // access the name member of the structure pointed by p
箭头运算符(->)用于访问指向结构的指针的成员。例如:
p->name; // same as (*p).name
4.10 数组的替代品
模板类vector和array是数组的替代品。下面简要地介绍它们的用法以及使用它们带来的一些好处。
4.10.1 模板类 vector
- 模板类vector是一种动态数组,它可以在运行时调整大小,可以在末尾或中间添加或删除元素,可以存储任何类型的数据。
- 要使用vector对象,需要包含头文件vector,并使用名称空间std。例如:
#include <vector>
using namespace std;
- 要创建一个vector对象,需要使用模板语法来指定元素的类型和数量。例如,要创建一个名为vi的vector对象,它可以存储int类型的元素,可以这样写:
vector<int> vi; // create an empty vector of int
- 要访问vector对象的元素,可以使用下标运算符([])或at()方法。例如,要访问vi的第一个元素,可以这样写:
vi[0]; // use the subscript operator
vi.at(0); // use the at() method
- 要修改vector对象的大小,可以使用resize()方法或push_back()方法。例如,要给vi添加一个元素,可以这样写:
vi.resize(vi.size() + 1); // use the resize() method
vi.push_back(10); // use the push_back() method to append 10
- 要获取vector对象的大小,可以使用size()方法。例如,要打印vi的元素个数,可以这样写:
cout << vi.size() << endl; // use the size() method
4.10.2 模板类 array(C++11)
- 模板类array是一种固定大小的数组,它可以存储任何类型的数据,与普通数组相比,它更方便,更安全,效率也相同。
- 要使用array对象,需要包含头文件array,并使用名称空间std。例如:
#include <array>
using namespace std;
- 要创建一个array对象,需要使用模板语法来指定元素的类型和数量。例如,要创建一个名为ai的array对象,它可以存储5个int类型的元素,可以这样写:
array<int, 5> ai; // create an array of 5 ints
- 要访问array对象的元素,可以使用下标运算符([])或at()方法。例如,要访问ai的第一个元素,可以这样写:
ai[0]; // use the subscript operator
ai.at(0); // use the at() method
- 要修改array对象的元素,可以直接赋值或使用fill()方法。例如,要给ai的所有元素赋值为10,可以这样写:
ai.fill(10); // use the fill() method
- 要获取array对象的大小,可以使用size()方法。例如,要打印ai的元素个数,可以这样写:
cout << ai.size() << endl; // use the size() method
4.10.3 比较数组、vector对象和 array 对象
要了解数组、vector对象和array对象的相似和不同之处,最简单的方式可能是看一个使用它们的简单示例,如程序清单所示。
// choices.cpp -- array variations
#include <iostream>
#include <vector> // STL C++98
#include <array> // C++11
int main()
{
using namespace std;
// C, original C++
double a1[4] = {1.2, 2.4, 3.6, 4.8};
// C++98 STL
vector<double> a2(4); // create vector with 4 elements
// no simple way to initialize in C98
a2[0] = 1.0/3.0;
a2[1] = 1.0/5.0;
a2[2] = 1.0/7.0;
a2[3] = 1.0/9.0;
// C++11 -- create and initialize array object
array<double, 4> a3 = {3.14, 2.72, 1.62, 1.41};
array<double, 4> a4;
a4 = a3; // valid for array objects of same size
// use array notation
cout << "a1[2]: " << a1[2] << " at " << &a1[2] << endl;
cout << "a2[2]: " << a2[2] << " at " << &a2[2] << endl;
cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;
// misdeed
a1[-2] = 20.2;
cout << "a1[-2]: " << a1[-2] <<" at " << &a1[-2] << endl;
cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;
return 0;
}
下面是该程序的输出示例:
a1[2]: 3.6 at 0x28cce8
a2[2]: 0.142857 at 0xca0328
a3[2]: 1.62 at 0x28ccc8
a4[2]: 1.62 at 0x28cca8
a1[-2]: 20.2 at 0x28ccc8
a3[2]: 1.62 at 0x28ccc8
a4[2]: 1.62 at 0x28cca8
- 数组、vector对象和array对象都是一种存储多个相同类型的值的数据结构,它们都可以使用下标运算符([])或at()方法来访问元素。
- 数组的大小在编译时确定,不能在运行时改变,它们使用栈(静态内存分配)来存储数据,效率较高,但不安全,不方便,不能直接赋值或复制。
- vector对象的大小在运行时确定,可以动态地增加或减少,它们使用堆(动态内存分配)来存储数据,效率较低,但安全,方便,可以直接赋值或复制。
- array对象的大小在编译时确定,不能在运行时改变,它们使用栈(静态内存分配)来存储数据,效率与数组相同,但安全,方便,可以直接赋值或复制。
小思考🤔️
如何使用const关键字来保护指针和其指向的数据?
答:
可以使用const关键字来限制指针和其指向的数据的修改。例如,const int * pt;声明了一个指向const int类型的指针,它不能通过*pt来修改其值;而int * const finger;声明了一个常量指针,它不能改变其指向;而const int * const pip;声明了一个既不能修改值也不能修改指向的常量指针。
如何比较两个指针的值?
答:
可以使用关系运算符(如==、!=、<、>等)来比较两个指针的值,它们会被转换为整数进行比较。但要注意,只有当两个指针都指向同一个数组或者同一个对象时,比较才有意义。否则,比较结果是不确定的,因为不同的内存区域可能有不同的排序方式。
如何处理new失败的情况?
答:
如果没有足够的内存满足new的请求,new通常会引发异常,这是一种错误处理技术。也可以使用nothrow参数来让new返回一个空指针,而不是引发异常。例如,int * pt = new (nothrow) int;如果分配失败,pt将被赋值为nullptr。这样,可以通过检查pt是否为空来判断是否成功分配了内存。
如何检查new是否成功分配了内存?
答:
如果没有足够的内存满足new的请求,new通常会引发异常,这是一种错误处理技术。也可以使用nothrow参数来让new返回一个空指针,而不是引发异常。例如,int * pt = new (nothrow) int;如果分配失败,pt将被赋值为nullptr。这样,可以通过检查pt是否为空来判断是否成功分配了内存。