02-C语言的指针
目标
- C语言指针释义
- 指针用法
- 指针与数组
- 指针与函数的参数
- 二级指针
- 函数指针
指针在C中很重要,它可以直接操作内存,是C效率的体现之一。
C语言指针概述
什么是指针
C语言中,指针是一个存放地址的变量。
指针能做什么
- 更直接和效率的在内存中修改值。
- 写游戏外挂(解决跨进程问题和注入问题即可达成)。
指针定义
先看一个指针定义的例子:
int a = 10;
int *p = &a; // 将a的地址赋值给给指针p
// 也可这么写,这种方式更容易理解
int a1 = 10;
int *p; // 定义一个指针p
p = &a1; // 将a1的地址值(十六进制)赋值给p
这里有两个新概念
- *符号: *符号代表是一个“指针”定义,这里定义一个叫p的指针。
- &符号: &符号是一个“取地址符”,&a代表取a的内存地址值。
再看一个例子
int a = 10;
int *p;
p = &a; // 将a的地址赋值给给指针p
printf("p的地址为:%#x\n", &p);
printf("p存储的值为:%#x\n",p);
printf("p指向的地址存储的值为:%d \n",*p);
返回值如下:
p的地址为:0x3bfed4
p存储的值为:0x3bfee0
p指向的地址存储的值为:10
第三行的打印,引出了另一个知识点:
- 在程式的运算中,*p的*号将指定获取p的存储地址值指向目标内存获取。也相当于拿着存储的地址值后,跳转到这个地址指向的值。简单来讲可以说是一种地址解包。
指针占用大小
指针指向的是地址,地址占用4个字节。
在指针的角度看数据的占用
指针是指向变量占用的首地址的
int占用为4字节,若定义一个int变量i,那么指针指向的是i地址的第一个字节地址,接下来3个字节都是int字节。这总共4个字节都不会被其他对象所占用。
long long占用8个字节,若定义一个long long变量LL,那么指针指向了LL地址的首字节地址,接下来7个地址都是这个LL的(共占8个)。
C语言指针的使用
指针的基本运算
我们这里主要讨论值运算和地址运算,需要有N目运算的基础。如果不清楚请看上一篇文章的相关描述。
先看一个例子:
int a = 10;
int *p = &a;
*p = *p + 10;
printf("%#x,%d,%d\n",p,*p,a);
在这里,控制台打印出来的为:
0x28f9a4,20,20
第一个参数为16进制地址输出(每次分配的地址一般都不一样),第二个是取指针值显示,第三个值为a内存值。
再看看第二个例子:
int a = 10;
int *p = &a;
(*p)++;
printf("%#x,%d,%d\n",p,*p,a);
*p++;
printf("%#x,%d,%d\n",p,*p,a);
运行结果为:
0x2dfb34,11,11
0x2dfb38,-858993460,11
第一个打印行,运行是 ( *p)++,根据N目运算优先级,先计算的 ( *p),然后再说++,也就是先算出值,再在值上+1。
第二个打印行,运行是 *p++,根据N木运算优先级,先算++,再算 *,这就变成了一个地址++位移,再进行地址值获取的动作。我们可以看到,这里的第二个参数,取值已经不知道取到个什么值了,这也就是指针位移带来的不可控问题。
这里有个有意思的点:++进行地址自增过后,会根据数据的类型进行占用字节计算,再位移。比如int占用为4字节,++就会位移4个字节。
指针数组与数组指针
数组和指针
int array[5];
int *p = array; // 没有&符号
printf("array地址为%#x \n",array);
printf("p地址为%#x \n",p);
控制台打印为:
array地址为0x25f7e0
p地址为0x25f7e0
可以运行,可以看到,在指针p赋值的时候,array没有写&符号。其实这里传达的概念就是:C中,数组名就是数组的首地址
如果强制给array用&符号,将会编译出错。
定义数组array,那么array就是一个数组的首地址,那么可以对指针做地址位移操作,如下:
int array[5] = { 1, 2, 3, 4, 5 };
int *p = array + 4; // 偏移4个位置
printf("array地址为%#x \n", array);
printf("x值为:%d,地址为:%#x \n", *p, &p);
int *p2 = array + 5;
printf("x2值为:%d,地址为:%#x \n", *p2, &p2); // 越界
控制台打印为:
array地址为0x54fb6c
x值为:5,地址为:0x54fb60
x2值为:-858993460,地址为:0x54fb54
array的首地址为0x54fb6c,在首位偏移4个位置(4*4=16)到了0x54fb60,其中存的值为数组5,最后我们对array偏移5个地址,但是数组上没有那么多,所以越界了,指到一个不知道什么值的地址上了。
根据以上分析,我们可以得出结论:
int array[5] = { 1, 2, 3, 4, 5 };
int *p = array;
int i = 2; // 定义数组的偏移量
// p + i = &array[i] = array + i; // 我们可以认为这三个是一个等式
p + i = &array[i] = array + i; 我们可以认为这三个是一个等式
数组的内存值改变:
int array[5]; // 未定义数组值,默认每个item都为0
int x = 0;
for (int *p = array; p < array + 5; p++){
*p = ++x; // 修改指针的值,直接给内存地址赋值
}
for (int i = 0; i < 5; i++){
printf("%d \n", array[i]);
}
控制台输出:
1
2
3
4
5
我们利用p指针不停的循环自增,给数组的内存地址重新赋值,内存地址的值改变,改变了数组指向的内容。
指针数组
指针指向数组的首地址,如下就是一个指针数组:
char *name[] = {"leon","tony","cherry","mango"};
根据N目运算符优先级说明,[]为1级,*为2级,那么先进行name[]操作,再进行了指针 *操作,最后进行了赋值
定义一个char类型的数组,指针指向了这个数组的首地址。
其实这就是与如下代码一个道理,我们在数组与指针小节也见过这个定义:
char name[];
char *p = name;
指针p指向了name数组的首地址。
数组指针
用指针组成的数组。
char (*name)[] = {"leon","tony","cherry","mango"};
根据N目云算法优先级说明,[]与()都是1级运算符,1级运算符遵守从左到右的计算规则,那么是先算( *name),再操作( *name)[],再进行赋值。
定义一个name指针,再把这个name指针定义成数组,每一个item指向一个item内存地址。
指针数组与数组指针的区别
指针数组是一个指向数组的首地址,数组指针是一个由指针构成的数组。
指针与函数的参数
函数的参数
java的值传递和引用传递
- java中,将对象当做参数传给方法,如果方法中对对象做了点啥的话,会改变这个对象的值,这会影响到方法外的对象。——这就是引用。
- Java中,将基本变量当做参数传给方法,在方法中怎么折腾都不会影响方法外的、作为参数传递的变量。——这就是值传递。
C中是这样么?
- C中,给函数(方法)传递的参数是值大多情况下都是值传递。
这么说来,C在这方面处理的比Java好啊。
如果要达到跟Java一样的引用效果,如何做呢?
看看以下例子能否做到:
void swap(int *a, int *b){
int *temp;
temp = a;
a = b; // 指针交换
b = temp;
}
int _tmain(int argc, _TCHAR* argv[]){
int a = 10;
int b = 20;
swap(&a, &b);
printf("a:%d,b:%d \n", a, b);
system("pause");
return 0;
}
控制台输出:
a:10,b:20
没有改变,这说明:形参的修改,无法带来实参的修改。
- 形参:方法定义的参数
- 实参:调用方法时,注入进方法的参数。
楼上的代码,只是将a,b的地址copy给了形参(形参叫a,b但是不是实参的a,b,只是一个copy),可以理解成copy为a1,b1,虽然他们指向的地址也是a,b的地址,但是将这份a,b的copy指针a1,b1地址进行切换,是不会影响真正a,b的。
真的没有办法了么?再看看下面的方法:
void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b; // 指针指向值交换
*b = temp;
}
int _tmain(int argc, _TCHAR* argv[]){
int a = 10;
int b = 20;
swap(&a, &b);
printf("a:%d,b:%d \n", a, b);
system("pause");
return 0;
}
控制台返回值为:
a:20,b:10
看起来成功了,这次又发生了什么呢?
在新的swap方法中,a,b形参是copy实参a,b的地址的,这个地址值是不会有差别的,所以他用了一个 *号对地址指向的内存值进行直接修改。 =。=
二级指针
什么是二级指针?
一个指向一级指针的指针。它存储的内容是一个一级指针的地址。
简单来讲,就是在一个指针的基础上,再包一层指针。
举个例子,下例中p2就是一个二级指针。
int a = 10;
int *p = &a; // 一级指针,p存放的是a的地址
int **p2 = &p; // 二级指针,在p指针基础上再包一层指针
printf("a的地址:%#x \n", &a); // 打印p存储的值(也就是a的地址)
printf("p存储的值:%#x \n", p); // 打印p存储的值(也就是a的地址)
printf("p2存储的值:%#x \n", p2); // 打印p2存储的地址
printf("p2存储的值,再用*解一次指针包:%#x \n", *p2); // 打印*p2,也就是p存储的值(也就是16进制地址)
printf("p2存储的值,用*解2次指针包:%#d \n", **p2); // 打印a存储的值(10进制int)
控制台打印为:
a的地址:0x3efc54
p存储的值:0x3efc54
p2存储的值:0x3efc48
p2存储的值,再用解一次指针包:0x3efc54
p2存储的值,用解2次指针包:10
同理,三级指针也来了
……接上例代码
int ***p3 = &p2; // 三级指针来了
printf("p2存储的值,用*解3次指针包:%#d \n", ***p3); // 打印a存储的值(10进制int)
这多级指针概念复杂感觉一点都不简洁,有什么用?
在NDK开发中,大量使用多级指针,所以一定要介绍。
函数指针
什么是函数指针?一个指向函数的指针。
函数指针定义是什么样子的?如下:
// 语法: 返回类型 (*指针)(参数1,参数2,……);
int (*calc)(int a, int b);
上例中,根据注释的语法说明,我们就这么定义了一个函数指针。
C中的函数,在C平台是一种什么样的存在呢?看看下面的例子:
/*完整代码*/
int impl(int a, int b){
return a + b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int (*calc)(int a, int b);
calc = impl;
int x = calc(1,2);
printf("calc(1,2)得到值为:%d \n",x);
printf("impl函数打印为:%#x \n", impl);
printf("calc函数打印为:%#x \n", calc);
system("pause");
return 0;
}
控制台输出:
calc(1,2)得到值为:3
impl函数打印为:0xa61028
calc函数打印为:0xa61028
定义calc是一个函数指针,打印impl函数名,也是一个地址!这说明什么?函数名跟数组名一样都是一个指针!
这就是我们的函数指针。
目标小结
- [x] C语言指针释义
- [x] 指针用法
- [x] 指针与数组
- [x] 指针与函数的参数
- [x] 二级指针
- [x] 函数指针