第4讲——指针
指针知识,我们在C语言学习的已足够多,然而现在我们仍需学习。我特地花了一讲的篇幅来详细讲述指针。
我将在C语言学过的指针知识都一带而过,而将一些重要的点认真讲解。
首先,什么是指针?
指针是一个变量,这个变量存储的是值的地址,而不是值本身。
常规变量的地址是怎么找到的呢?如home是一个变量,那么&home就是它的地址。
int a = 6; cout<<"a value = "<<a<<endl; //输出a的值 cout<<"a address = "<<&a<<endl; //a的地址
我们仿佛可以这样总结:使用常规变量时,值是指定的量,而地址为派生量。
但是,指针变量却将地址视为指定的量,而将值视为派生量。
于是,指针名表示的是地址,*运算符被称为间接值或解除引用运算符,将其应用于指针,可以得到该地址处存储的值。
但我们要知道,*a是指“a指向的变量”,而不仅仅是“a指向的变量所拥有的值”。即*a与常规int变量等效。
#include <iostream> using namespace std; int main() { int x = 6; int *a; a = &x; //输出值 cout<<"Values: x = "<<x; cout<<", *a = "<<*a<<endl; //输出地址 cout<<"Addresses: &x = "<<&x; cout<<", a = "<<a<<endl; //使用指针改变值 *a = *a + 1; //将修改a指向的变量的值 cout<<"Now x = "<<x<<endl; }
执行程序:
我们知道,指针伴随着风险,那么是什么风险呢?
在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。我们应该记住,为数据提供空间是一个独立的步骤。
int *a; *a = 100;
a确实是一个指针,但它指向哪里呢?上述代码没有将地址赋给a,那么100将被放在哪里呢?我们不知道,由于a没有被初始化,它可能有任何值。不管值是什么,程序都将它解释为存储100的地址。
所以我们一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。
我们刚开始学指针的时候,有这样一道题,让你编写一个交换两个变量的值的程序。
#include <iostream> using namespace std; //值调用 void swap1(int a,int b) { int t = a; a = b; b = t; } //地址调用 void swap2(int *a,int *b) { int t = *a; *a = *b; *b = t; } int main() { int a = 3,b = 4; swap1(a,b); cout<<a<<" "<<b<<endl; //输出"3 4" swap2(&a,&b); cout<<a<<" "<<b<<endl; //输出"4 3" }
除了值调用之外,我们还要提防下面这个错误:
#include <iostream> using namespace std; void swap(int *a,int *b) { int *t; *t = *a; *a = *b; *b = *t; } int main() { int a = 3,b = 4; swap(a,b); cout<<a<<" "<<b<<endl; //输出"4 3" }
这个程序错在哪呢?t是一个指向int型的指针,因此*t是一个整数。用一个整数作为辅助变量去交换两个整数有何不妥?而且,我们也得到了正确结果。
问题就是,t存储的地址是什么?也就是说t指向哪里?因为t是一个变量,根据规则,它在赋值之前是不确定的如果这个“不确定的值”所代表的内存单元恰好是能写入的,那么这段程序将正常工作;如果它是只读的,程序可能会崩溃。
=================================================我是分界线===============================================
上面一部分我们了解了指针的基本定义,接下来我们来谈谈与指针密切相关的动态内存空间。
哈哈,我们要谈新知识点了,想想还有点小激动嘞(毕竟指针在C语言就啃烂了)。。。
你们知道指针如何实现在程序运行时分配内存吗??
前面的指针用法都是对指针的大材小用,指针真正的用武之处——在运行阶段分配未命名的内存以存储值。请谨记,在这种情况下,只能通过指针来访问内存。
在C++中,我们会使用new运算符来分配内存。
例如,我们试试在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。
这里的关键所在是C++的new运算符。程序员要告诉new:需要为哪种数据类型分配内存。
于是,new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
下面是一个这样的示例:
int *p1 = new int;
new int 告诉程序,需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给p1,p1被声明为指向int的指针。现在,p1是地址,而*p1是存储在那里的值。
如果将上述方法与下述方法(将变量的地址赋给指针)进行比较:
int n; int *p2 = &n;
在这两种情况(p1和p2)下,都是将一个int变量的地址赋给了指针。
我们要知道,第二种情况下,可以通过名称n来访问该int,而在第一种情况下,则只能通过该指针进行访问。
这引出了一个问题:p1指向的内存没有名称,如何称呼它呢?
我们说p1指向一个数据对象,这里的“对象”不是“面向对象编程”中的对象,而是一样“东西”。术语“数据对象”比“变量”更通用,它指的是为数据项分配的内存块。因此,变量也是数据对象,但p1指向的内存不是变量。
为一个数据对象(可以是基本类型,也可以是结构)获得并指定分配内存的通用格式如下:
typeName *pointer_name = new typeName;
对于指针,我们还需知道:new分配的内存块通常与常规变量声明分配的内存块不同。常规变量的值都存储在被称为栈的内存区域中,而new从被称为堆或自由存储区的内存区域分配内存。
我们要做个有始有终的人,既然我们借用完内存,我们就应该将其归还给内存池。
int *ps = new int; ... delete ps;
这将释放ps指向的内存,但不会删除指针ps本身。例如,我们可以将ps重新指向另一个新分配的内存块。
如果我们不使用delete来释放使用new分配的内存,那么将会发生内存泄漏。
然后我们来谈一谈使用new创建动态结构
将new用于结构由两步组成:创建结构和访问其成员
例如,我们创建一个未命名的student类型,并将其地址赋给一个指针:
student *ps = new student;
这将把足以存储student结构的一块可用内存的地址赋给ps。
而对于这种没有名称、只知道它的地址的结构,C++提供了->运算符来访问成员。
什么??那何时用句点运算符?何时用箭头运算符?
如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。
凡事无绝对,对于上面的指针ps,我们知道它指向结构,那么*ps就是被指向的值——结构本身。故可用*ps接句点运算符访问结构成员。
【补充一点点】
- 将指针变量加1,其增加后的值等于指向的类型占用的字节数;
- 对指针解除引用的两种方法:①使用解除引用运算符(*),②使用数组表示法(pn[1]与*(pn+1)等价);
- 数组名和指针名常常可以进行相同的操作,但只可以修改指针的值,而数组名是常量;
- 对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。