一 二级指针的概念:
A(即B的地址)是指向指针的指针,称为二级指针,用于存放二级指针的变量称为二级指针变量。根据B的不同情况,二级指针又分为指向指针变量的指针和指向数组的指针。
首先任何值都有地址 ,一级指针的值虽然是地址,但这个地址做为一个值亦需要空间来存放,是空间就具有地址 ,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址。
一级指针所关联的是其值(一个地址)名下空间里的数据,这个数据可以是任意类型并做任意用途,但二级指针所关联的数据只有一个类型一个用途,就是地址,指针就是两个用途提供目标的读取或改写, 那么二级指针就是为了提供对于内存地址的读取或改写。
指针的表现形式是地址,核心是指向关系指针,运算符“*”的作用是按照指向关系访问所指向的对象.如果存在A指向B的指向关系,则A是B的地址,“*A”表示通过这个指向关系间接访问B.如果B的值也是一个指针,它指向C,则B是C的地址,“*B”表示间接访问C如果C是整型、实型或者结构体等类型的变量或者是存放这些类型的数据的数组元素,则B(即C的地址)是普通的指针,称为一级指针,用于存放一级指针的变量称为一级指针变量。
二 二级指针的使用
2.1 一级指针的实现
二级指针作为函数参数的作用:在函数外部定义一个指针p,在函数内给指针赋值,函数结束后对指针p生效,那么我们就需要二级指针。
#include<iostream>
using namespace std;
int a = 10;
int b = 100;
int *q;
void func(int *p)
{
//p自身的地址发生了改变,而p指向的地址没有发生改变
cout << "func:&p=" << &p << ",p=" << p << endl; //note:3
//p重定向指向b,但这里的p只是q的一个拷贝,改变p并不会改变q
//而且改变的是p自身的指针,并没有改变p所指向的指针
p = &b;
//*p = b;//这就不一样了,这样的话p指向的地址就会发生改变
//p自身的地址没有发生改变,而p指向的地址发生了改变,由a指向b
cout << "func:&p=" << &p << ",p=" << p << endl; //note:4
}
int main()
{
//&a, &b, &q分别为a、b、q自身的地址,而q为q所指向的地址
cout << "&a=" << &a << ",\t&b=" << &b << ",\tq=" << q << ",\t&q=" << &q << endl; //note:1
q = &a; //改变q所指向的地址,而自己本身的地址并没有发生改变
cout << "*q=" << *q << ",\tq=" << q << ",\t&q=" << &q << endl; //note:2
//参数传递为q制作一个副本,所以在func中p自身的地址发生了改变,但是其指向的地址并没有变化
func(q);
//q自身的指针并没有变化,q指向的内容也没有变化
cout << "*q=" << *q << ",\tq=" << q << ",\t&q=" << &q << endl; //note:5
system("pause");
return 0;
}
运行结果:
&a=01250000, &b=01250004, q=00000000, &q=01250330
*q=10, q=01250000, &q=01250330
func:&p=0041F904,p=01250000
func:&p=0041F904,p=01250004
*q=10, q=01250000, &q=01250330
请按任意键继续. . .
我们看输出:
- note:1->a,b,q都有一个地址;
- note:2->q指向a;
- note:3->我们发现参数p的地址变了,跟q不一样了,是的参数传递是制作了一个副本,也就是p和q不是同一个指针,但是指向的地址0x01250330(a的地址)还是不变的;
- note:4->p重新指向b;
- note:5->退出函数,p的修改并不会对q造成影响。
编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 p,编译器使 p = q(但是&p != &q,也就是他们并不在同一块内存地址,只是他们的内容一样,都是a的地址)。如果函数体内的程序修改了p的内容(比如在这里它指向b)。在本例中,p申请了新的内存,只是把 p所指的内存地址改变了(变成了b的地址,但是q指向的内存地址没有影响),所以在这里并不影响函数外的指针q。
这就需要二级指针操作:
2.2 二级指针操作
#include<iostream>
using namespace std;
int a = 10;
int b = 100;
int *q;
void func(int **p) //2
{
cout << "func:&p=" << &p << "\t*p=" << *p << ",\tp=" << p << endl;
*p = &b; //3
cout << "func:&p=" << &p <<"\t*p=" << *p << ",\tp=" << p << endl;
}
int main()
{
cout << "&a=" << &a << ",\t&b=" << &b << ",\tq=" << q << ",\t&q=" << &q << endl;
q = &a;
cout << "*q=" << *q << ",\tq=" << q << ",\t&q=" << &q << endl;
func(&q); //1
cout << "*q=" << *q << ",\tq=" << q << ",&\tq=" << &q << endl;
system("pause");
return 0;
}
运行结果:
&a=00160000, &b=00160004, q=00000000, &q=00160330
*q=10, q=00160000, &q=00160330
func:&p=004CFA84 *p=00160000, p=00160330
func:&p=004CFA84 *p=00160004, p=00160330
*q=100, q=00160004,& q=00160330
请按任意键继续. . .
这里只改了三个地方,变成传二级指针。我们再看:
因为传了 指针q的地址( 二级指针**p)到函数,所以二级指针拷贝(拷贝的是p,一级指针中拷贝的是q所以才有问题),( 拷贝了指针但是指针内容也就是指针所指向的地址是不变的)所以它还是指向一级指针q(*p = q)。在这里无论拷贝多少次,它依然指向q,那么*p = &b;自然的就是 q = &b;了。
2.3 一个示例
下面是一个例子:
void bug(int **a)
{
(*a) = (int *)malloc(sizeof(int));
**a = 1;
cout << **a << endl;
}
void main()
{
int *a;
bug(&a);
cout << *a << endl;
system("pause");
}
运行结果:
1
1
2.4 另外一个示例:
#include<stdio.h>
#include<stdlib.h>
void main()
{
int a = 5;
int b = 10;
int *p = &a;
int **pp = &p; //*pp指向p的地址,改变**pp的值就会改变p的地址,也就是说p不再指向a了
printf("%d\n", *p);
printf("变量p的地址:%x\n", p);
*pp = &b; //*pp指向b的地址,改变**pp的值就会改变b的值
**pp = 1; //这样赋值会改变p的地址,使得p不再指向变量a
printf("%d\n", *p);
printf("变量p的地址:%x\n", p);//此时p的地址和前边的不再一样,而是指向*pp
printf("变量*pp的地址:%x\n", *pp);
printf("%d %d\n", a, b);
system("pause");
}
运行结果:
5
变量p的地址:3ff728
1
变量p的地址:3ff71c
变量*pp的地址:3ff71c
5 1
请按任意键继续. . .
2.5 二级指针的另外一个强大的作用:动态申请二维数组
void main()
{
<span style="white-space:pre"> </span>int m , n , **p;
<span style="white-space:pre"> </span>scanf("%d%d" , &m , &n);
<span style="white-space:pre"> </span>p = (int **)malloc(m * sizeof(int *))
//C++中建议使用:p = new int* [m];
<span style="white-space:pre"> </span>for(i = 0 ; i < m ; i++)
<span style="white-space:pre"> </span>p[i] = (int *)malloc(n * sizeof(int));
//C++:p[i] = new int[n];
}
这样就实现了二维数组的动态申请,因为一般数组声明时,不允许下标是变量,所以如果想动态决定数组各维的大小,最好这样做。
三 一道机试题
#include "stdafx.h"
#include <iostream>
using namespace std;
void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
//p = new char[num]; //C++当中
}
int _tmain(int argc, _TCHAR* argv[])
{
char *str = NULL;
GetMeory(str, 100);
strcpy(str, "Hello");
cout << str << endl;
return 0;
}
问:程序能否达到目的:在GetMemory()中为main函数中的开辟空间,并将str指向这段空间?
分析: str是一个指针,指向NULL,形参p也是一个指针,初始也指向NULL,在GetMemory函数中,这个指针又指向了新开辟的空间。但是只是形参的指向改变了,实参str仍然指向NULL,并没有改变。因此,程序达不到题目的要求,而且运行时会出现错误,由于str一直指向NULL,执行strcop时,会出现错误,提示某某内存不能写入。
#include "stdafx.h"
#include <iostream>
using namespace std;
void GetMeory(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
//*p = new char[num]; //C++当中
}
int _tmain(int argc, _TCHAR* argv[])
{
char *str = NULL;
GetMeory(&str, 100);
strcpy(str,"Hello");
cout << str << endl;
return 0;
}
分析:str是一个指针,指向NULL。而调用GetMemory函数时,传递的是str的地址,p是一个二级指针,*p是一个指针。因此,将str的地址赋给临时变量p,则*p就是指针str的值,改变*p的值就相当于改变str的值。因此这种方法能够得到题目要求的效果。另外还有一种方法,采用一级指针,让函数返回一个指针变量,指向新分配的内存,程序如下:
#include "stdafx.h"
#include <iostream>
using namespace std;
char * GetMeory2(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
//p = new char[num]; //C++当中
return p;
}
int _tmain(int argc, _TCHAR* argv[])
{
char *str = NULL;
str = GetMeory2(str, 100);
strcpy(str,"Hello");
cout << str << endl;
return 0;
}
完美!
四 二级指针外挂
源程序:
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
void main()
{
char a = 'A';
char b = 'B';
char c = 'C';
char d = 'D';
char *p = &a;
printf("p = %x, &a = %x, &b = %x, &c = %x, &d = %x\n", p, &a, &b, &c, &d);
while (1)
{
printf("等级为%c\n", *p);
//_sleep(2000); //所有平台都是通用的
Sleep(2000);
}
}
外挂程序:
_declspec(dllexport) void go()
{
int **p = (int **)0x44fc17;
*p = (int *)0x44fbff;
}
_declspec(dllexport) void go1()
{
int *p = (int *)0x40f7b7;
*p = 'D';
}