不知不觉就第11篇了QWQ
11.1、指针和自由存储空间
之前提到了计算机程序在存储数据时必须跟踪的3个基本属性:
(1)信息存储在何处;
(2)存储的值为多少;
(3)存储的信息时什么类型。
之前我们通过定义一个简单变量,让声明语句指出了值的类型和符号名,让程序为值分配内存,还在内部跟踪该内存单元。
下面来看另一种方法:以指针为基础,指针是一个变量,其存储的是值的地址,而不是值本身。在讨论指针之前,我们只需对变量应用地址运算符(&),就可以获得它的位置。(这个学过C语言的肯定不陌生)下面给一个程序:
#include <iostream>
int main()
{
using namespace std;
int donuts = 6;
double cups = 4.5;
cout << "donuts value= " << donuts;
cout << " and donuts address = " << &donuts << endl;
cout << "cups value = " << cups;
cout << " and cups address = " << &cups << endl;
return 0;
}
显示地址时,该实现的cout使用十六进制表示法,因为这是常用于描述内存的表示法。在该实现中,donuts的存储位置比cups要低。两个地址的差为0000006FCE94F8C8-0000006FCE94F8C4=4。因为donuts的类型为int,而这种类型使用4个字节。
处理存储数据的新策略刚好相反,将地址视为指定的值,而将值视为派生量。一种特殊类型的变量——指针用于存储值的地址,因此指针名表示的是地址。*运算符被称为间接值或解除引用运算符,将其应用于指针,可以得到该地址处存储的值(这和乘法使用的符号相同;C++会根据上下文确定指的是什么)。
#include <iostream>
int main()
{
using namespace std;
int updates = 6;
int* p_updates;
p_updates = &updates;
cout << "Values :updates= " << updates;
cout << ", *p_updates= " << *p_updates << endl;
cout << "Adresses:&updates= " << &updates;
cout << ",p_updates= " << p_updates << endl;
*p_updates += 1;
cout << "Now updates= " << updates << endl;
return 0;
}
int变量updates和指针变量p_updates只不过是同一枚硬币的两面。变量updates表示值,并使用&运算符来获取地址;而p_updates表示地址,并使用*运算符来获得值。由于p_updates指向updates,因此*p_updates和updates完全等价,可以使用int变量来使用。
11.1.1、声明和初始化指针
int *p_updates;
这个声明表明*p_updates的类型为int。由于*运算符被用于指针,因此p_updates变量本身必须是指针。我们可以认为p_updates指向int类型,或者该类型是指向int的指针。
一般来说,C程序员使用这种格式:
int *ptr;
这强调*ptr是一个int类型的值。而很多C++程序员使用这种格式:
int* ptr;
这强调的是:int*是一种类型——指向int的指针。
(其实在哪添加空格是自己喜好,对编译器没啥影响,甚至不加也行)
但是如果要声明多个变量,每个指针变量名都需要使用一个*。
可以在声明语句中初始化指针。被初始化的是指针而不是它指向的值。也就是说,下面的语句将pt的值设置为&higgens:
int higgens=6;
int* pt=&higgens;
11.1.2、指针的危险
在C++中创建指针时,计算机将分配用来存放地址的内存,但不会分配用来存储指针所指向数据的内存。为数据提供空间是一定要的步骤。一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。
11.1.3、指针和数字
指针不是整型,虽然计算机通常把地址当作整数看待,但从概念上,整数可以进行运算,而指针运算没有意义,因此不能将整数赋给指针:(那前面的地址举例)
int* pt;
*pt=0000006FCE94F8C8;
在这里,左边是指向int的指针,因此可以赋给地址。但右边是一个整数,这段语句没有告诉程序这个整数是一个地址,因此不能赋值。如果要使用,应通过强制类型转换将数字转换为适当的地址类型:
int* pt;
*pt= (int *)0000006FCE94F8C8;
这样两边都是地址,赋值才有效。
11.1.4、使用new来分配内存
前面我们将指针初始化为变量的地址;变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供的一个别名。指针真正的作用时在运行阶段分配未命名的内存以存储值。在C语言中,可以用库函数malloc()来分配内存;在C++中依然可以这么做,但C++有更好的方法——new运算符。
在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值,这里的关键所在是C++的new运算符。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。例子:
int* pn=new int;
new int告诉程序,需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后它找到这样的内存,并返回其地址。接下来将地址赋给pn,pn是被声明为指向int的指针。现在pn是地址,而*pn是存储在那里的值。为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:
typeName * pointer_name=new typeName;
#include <iostream>
int main()
{
using namespace std;
int nights = 1001;
int* pt = new int;
*pt = 1001;
cout << "nights value = ";
cout << nights << ":location = " << &nights << endl;
cout << "int ";
cout << "value = " << *pt << ":location = " << pt << endl;
double* pd = new double;
*pd = 10000001.0;
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;
}
程序说明:该程序使用new分别为int类型和double类型的数据对象分配内存。指针pt和pd指向这两个数据对象,如果没有它们,将无法访问这些内存单元。现在就可以像使用变量那样使用*pt和*pd了。
该程序还指出了必须声明指针所指向的类型的原因之一:地址本身只指出了对象存储地址的开始,而没有指出其类型(使用的字节)。从两个值的地址可以知道,它们都只是数字,并没有提供类型或长度信息。另外指向int的指针长度和double的指针相同,因为它们都是地址。
11.1.5、使用delete释放内存
当需要内存时,可以使用new来请求,而有请求,就会有删除。delete运算符使得在使用内存后,能够将其归还给内存池,能更有效地使用内存。使用delete时,后面要加上指向内存块的指针:
int* pt=new int;
delete pt;
这将释放pt指向的内存,而不会删除指针pt本身。可以将pt重新指向另一个新分配的内存块。一定要配对地使用new和delete;否则将会发生内存泄漏。
注:只能用delete来释放使用new分配的内存,然而对空指针使用delete是安全的。一般来说不能创建两个指向同一个内存块的指针,因为这将增加错误的删除同一个内存块两次的可能性。
11.1.6、使用new来创建动态数组
11.1.6.1、使用new创建动态数组
在C++中,创建动态数组很容易,只要将数组的元素类型和元素数目告诉new即可。必须在类型名后面加上方括号,其中包含元素数目。例如:
int* psome=new int [10];
new运算符返回第一个元素的地址,这个地址被赋给指针psome。当程序使用完new分配的内存块时,应使用delete释放它们。对于使用new创建的数组,应使用另一种格式的delete来释放:
delete [] psome;
方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。
使用new和delete时,应遵循下面的规则:
(1)不要使用delete释放不是new分配的内存。
(2)不要使用delete释放同一块内存块两次。
(3)如果使用new[ ]为数组分配内存,应使用delete[ ]来释放。
(4)对空指针应用delete是安全的。
11.1.6.2、使用动态数组
我们使用上面的声明,可以将它看作是一跟指向该元素的手指。假设int占4个字节,则将手指沿正确的方向移动4个字节,手指指向第二个元素。下面做法对学过C语言的会很熟悉:可以使用psome[0]访问第一个元素,第二个元素则可以使用psome[1],以此类推。
#include <iostream>
int main()
{
using namespace std;
double* p3 = new double[3];
p3[0] = 0.2;
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 + 1;
cout << "Now p3[0] is " << p3[0] << " and ";
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 - 1;
delete[] p3;
return 0;
}
p3=p3+1;
我们不能修改数组名的值。但指针是变量,因此可以修改它的值。上面这个作用是导致p3指向第二个元素而不是第一个,减去1则指向第一个值,便能给delete[ ]提供正确的地址。