02-C语言的指针

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] 函数指针

你可能感兴趣的:(02-C语言的指针)