您的位置:首页 > 编程语言 > C语言/C++

【C++】学习笔记十——指针

2016-11-28 22:08 204 查看

指针

计算机程序在存储数据时必须有3中基本属性:

信息存储在何处

存储的值为多少

存储的信息是什么类型

定义一个简单变量可以达到上述目的。声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。

指针是一个变量,存储的是值的地址,而不是值本身。

程序4.14演示了用地址运算符(&)获取常规变量的地址。

程序4.14

#include<iostream>
int main()
{
using namespace std;
int donuts = 6;
double cups = 4.5;

cout << "donuts value = " << donuts;
cout << " and donuts address = " << &donuts << endl;    //可能需要使用unsigned(&donuts)
cout << "cups value = " << cups;
cout << " and cups address = " << &cups << endl;

cin.get();
return 0;
}




使用常规变量时,值是指定的量,地址为派生量;

处理存储数据的新策略刚好相反,将地址视为指定的量,而将值视为派生的量。

一种特殊类型的变量——指针用于存储值的地址。因此,指针名表示的是地址。

*运算符被称为间接值(indirect value)或解除引用(dereferencing)运算符,将其用于指针,可以得到该地址处存储的值(这和乘法的符号相同,C++根据上下文来确定所指的是乘法还是解除引用)。例如,假设manly是一个指针,则manly表示的是一个地址,而 *manly表示存储在该地址处的值。 *manly与常规int变量等效。

程序4.15

#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 << "Addresses: &updates = " << &updates;
cout << ", p_updates = " << p_updates << endl;

//使用指针改变值
*p_updates = *p_updates + 1;
cout << "Now updates = " << updates << endl;

cin.get();
return 0;
}




从程序4.15可知,int变量updates和指针变量p_updates只不过是同一枚硬币的两面。

变量updates表示值,并使用&运算符来获得地址;

变量p_updates表示地址,并使用*运算符来获得值。

由于p_updates指向updates,因此*p_updates和updates完全等价。可以像使用int变量那样使用*p_updates。甚至可以将值赋给 *p_updates,这样将修改指向的值updates。

1. 声明和初始化指针

计算机需要跟踪指针指向的值的类型,因此,指针声明必须指定指针指向的数据的类型:

int * p_updates;


这表明,*p_updates的类型为int。由于*运算符被用于指针,因此p_updates变量本身必须是指针。

p_updates指向int类型,p_updates的类型时指向int的指针,即int*。

p_updates是指针,而*p_updates是int。

*两边的空格是可选的。

一般C程序使用这种格式:
int *ptr;
,这强调*ptr是一个int类型的值。

C++一般使用:
int* ptr;
,这强调int*是一种类型——指向int的指针。

甚至可以这样做:
int*ptr;


但是,下面的声明将创建一个指针(p1)和一个int变量(p2):

int* p1, p2;


对每个指针变量名,都需要使用一个*。

注意:在C++中,int *是一种复合类型,是指向int的指针。

可以用同样的句法来声明指向其他类型的指针:

double * tax_ptr;
char * str;


可以在声明语句中初始化指针。在这种情况下,被初始化的是指针,而不是它指向的值。也就是说,下面的语句将pt(而不是*pt)的值设置为&higgens:

int higgens = 5;
int * pt = &higgens;


程序4.16

#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;
cin.get();
return 0;
}




从程序4.16可知,程序将pt(而不是*pt)初始化为higgens的地址。

2. 指针的危险

在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向数据的内存。为数据提供空间是一个独立的步骤,忽略这一步会发生危险。如下所示:

long * fellow;
*fellow = 223323;


fellow是一个指针,但它没有明确的指向。上述代码没有将地址赋给fellow。

由于fellow没有被初始化,他可能有任何值。不管值是什么,程序都将它解释为存储223323的地址。如果fellow的值为1200,计算机将把数据放在地址1200上,即使这恰巧是程序代码的地址。fellow指向的地方很可能并不是要存储223323的地方。这种错误可能会导致一些隐匿难以跟踪的bug。

警告:一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。

3. 指针和数字

指针不是整型,虽然计算机通常把地址当做整数来处理。从概念上看,指针与整数是截然不同的类型。

不能简单地将整数赋给指针:

int * pt;
pt = 0xB8000000;     //类型不匹配


在这里,左边是指向int的指针,因此可以把地址赋给它,但右边是一个整数。

要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:

int * pt;
pt = (int *) 0xB0000000;    //赋值语句两边都是整数的地址,因此这样的赋值有效

e6d1

注意:pt是int值的地址,并不意味着pt本身是int类型。

4. 使用new来分配内存

前面都将指针初始化为变量的地址;变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。

指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存;在C++中仍可以这样做,但C++还有更好的方法——new运算符。

int * pn = new int;           //在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值


new int告诉程序,需要合适存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给pn,pn是被声明为指向int的指针。pn是地址,而*pn是存储在那里的值。

将变量的地址赋给指针

int higgens;
int * pt = &higgens;


这两种情况(pn和pt),都是将一个int变量的地址赋给了指针。在第二种情况下,可以通过名称higgens来访问该int,在第一种情况下,则只能通过该指针进行访问。

pn指向的内容没有名称,此时,我们说pn指向一个数据对象(为数据分配的内存块)。

为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下

typeName * pointer_name = new typeName;


程序4.17

//使用new
#include<iostream>
int main()
{
using namespace std;
int nights = 1001;
int * pt = new int;
*pt = 1001;

cout << "nights values = ";
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;
cin.get();
return 0;
}




5. 使用delete释放内存

当需要内存时,可以使用new来请求;使用完内存后,可以使用delete运算符将内存归还给内存池。

使用delete时,后面要加上指向内存块的指针(这些内存块最初是用new分配的):

int * ps = new int;                  //分配内存
...                                  //使用内存
delete ps;                           //释放内存


这将释放ps指向的内存,但不会删除指针ps本身。可以将ps重新指向一个新分配的内存。

一定要配对的使用new和delete,否则会发生内存泄露(memory leak),也就是说,被分配的内存再也无法使用了。如果内存泄露严重,则程序将由于不断寻找更多内存而终止。

不要尝试释放已经释放的内存,这样做的结果是不确定的,什么情况都有可能发生。

另外,不能使用delete来释放声明变量所获得的内存:

int * ps = new int;     //ok
delete ps;              //ok
delete ps;              //not ok now
int jugs = 5;
int * pi = &jugs;       //ok
delete pi;              //不允许,内存不是由new分配的


注意:只能使用delete来释放使用new分配的内存。然而,对空指针使用delete是安全的。

使用delete的关键在于,将它用于new分配的内存。但delete并不一定要用于new的指针,而是用于new 的地址:

int * ps =new int;      //分配内存
int * pq = ps;          //指针pq也指向相同的内存块
delete pq;              //释放内存


一般来讲,不要创建两个指向同一个内存快的指针,因为这将增加错误的删除同一个内存块两次的可能性。

6. 使用new来创建动态数组

静态联编(static binding):通过声明来创建数组,数组是在编译时加入到程序中的,必须在编写程序时指定数组的长度;

动态联编(dynamic binding):使用new,如果在运行阶段需要数组,则创建它,如果不需要,则不创建,还可以在程序运行时选择数组的长度。数组是在程序运行时创建的。

6.1使用new创建动态数组

将数组的元素类型和元素数目告诉new即可。必须在类型名后加上方括号[],其中包含元素数目。

例如,要创建一个包含10个int元素的数组:

int * psome = new int [10];


new运算符返回第一个元素的地址,该地址被赋给指针psome。

当程序使用完new分配的内存块是,应使用delete释放它们。然而,对于使用new创建的数组,应使用另一种格式的delete来释放:

delete [] psome;


方括号[]告诉程序,应释放整个数组,而不仅仅是指针指向的元素。

如果使用new时,不带方括号,则使用delete时,也不应带方括号。如果使用new时带方括号,则使用delete时也应带方括号。

使用new和delete时,应遵循以下规则:

不要使用delete来释放不是new分配的内存

不要使用delete释放同一个内存块两次

如果使用new []为数组分配内存,则应使用delete [] 来释放

如果使用new []为一个实体分配内存,则应使用delete(没有方括号)来释放

对空指针应用delete是安全的

指针psome只指向数组的第一个元素,因此编写程序时,应给定元素数目。

为数组分配内存的通用格式如下

type_name * pointer_name = new type_name [元素数目];


使用new运算符可以确保内存块足以存储指定数目个类型为type_name的元素,而pointer_name将指向第一个元素。

6.2 使用动态数组

int * psome = new int [10];


访问数组元素时,只要把指针当作数组名使用即可。

也就是说,对第1个元素可以使用psome[0],而不是*psome;对第2个元素可以使用psome[1],以此类推。

可以这样做的原因是,C和C++内部都使用指针来处理数组。数组和指针基本等价。

程序4.18

//对数组使用new
#include<iostream>
int main()
{
using namespace std;
double * p3 = new double [3];      //分配3个double的内存空间
p3[0] = 0.2;
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 + 1;                       //指针加1
cout << "Now p3[0] is " << p3[0] << " and ";
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 - 1;                       //指针恢复为最初
delete [] p3;
cin.get();
return 0;
}




从中可知,程序将指针p3当作数组名来使用,p3[0]为第一个元素,以此类推。

下面的代码指出了数组名和指针之间的根本差别

p3 = p3 + 1;   //对指针可以,对数组名不可以


不能修改数组名的值。但指针是变量,因此可以修改它的值。

p3+1将导致它指向第二个元素而不是第一个。将它减一后,指针将指向原来的值,这样程序便可以给delete[]提供正确的地址。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 指针