指针
通常来说对于一个变量我们不仅需要知道它的取值还需要知道其在内存中所放的位置。在C语言中每一个内存位置都定义了可使用 &
运算符访问变量的地址。所以对于一些涉及内存地址变化的问题,如动态内存分配,我们需要有一个可以运算内存位置的量来做处理。这个量就是指针。
当在计算机的内存中进行数据存储时,不同类型的数据占用的字节数不同,为正确的访问数据,必须为每个字节都编上编号,每个字节拥有唯一的编号,我们将内存中字节的编号成为地址或指针。指针是一种保存变量地址的变量,其值就是该变量的内存存储地址。
指针的概念
指针变量是用来存放内存地址的变量。
个人理解: 指针变量就如同数学中的矢量(向量),一个矢量可以表示物体位移的两个方面:一是物体运动的方向,(矢量在书写时所写的箭头代表了其可以表示方向)二是物体运动的速率大小。而一个指针变量从表示的角度来说,也可以表示两个方面:一个是常规变量(这里的常规指非指针变量的变量。如相对于矢量标量可以作为常规变量)的地址,一个是常规变量的取值。
同常规变量一样,我们要使用指针变量也需要先声明,指针变量声明的一般形式为:
type *var_name; (即:数据类型 *指针变量名称)
如 int *p;
在声明变量的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值是一个良好的编程习惯。赋为NULL值的指针被称为空指针。NULL 指针是一个定义在标准库中的值为零的常量。按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
个人理解的指针变量几点说明:
-
指针变量是一个同变量地址相关联的量,它本身的值就表示取另一个变量的地址,假设一个指针变量取名为 p p p, 指向变量 a a a 的地址,则可以表示为
p = &a
,即将变量
a a a的地址取出来赋值给名为
p p p的指针变量
。 -
关于指针变量名前的星号 ∗ * ∗。 一个作用是在定义时用于表明这是指针变量;另一个作用是取出自身所指向地址的变量的数值,即进行
解引用
----取地址的逆操作,即已知变量的地址,获取变量的取值
。解引用用*
运算符来实现。 -
指针变量同自身所取地址的常规变量在取值变化方面具有共同捆绑的效果,无论是后期变换指针变量的取值还是常规变量的取值发生变化,两者共同发生变化。两者的赋值发生变化时,内存地址并不会发生变化。
指针为 const
(1) int * const 变量名=&变量。
表示一旦得到了某个变量的地址,不能再指向其他变量。对应于常量和变量来说,标注const的指针变量为常量,为固定不变的一个地址量
,后续无法再指向其他的地址。但后续可更改该常量的值
。如
int * const q = &i; 即指针常量q取了变量i的地址,不允许q++。
(2) const 在数据类型前。const 数据类型 *变量名 = &变量名
表示不能通过这个指针去修改所指地址的变量的值。指针p的取值即为所取地址的变量的当前值,后续不再变化。但不影响该指针变量指向其他的变量。同时取址操作并不影响所取地址变量的属性,即不能使得所取地址变量成为const。如
const int *p=&i;
*p=26 (操作非法,取址后不能通过指针变量修改i的取值)
i=26;(操作允许。因为i本身为变量,可进行赋值操作)
p=&j;(操作允许。仅取址固定,取地址可变)
注: 判断哪个被const了的标志是const在*前还是后面。const 在星号前表示不能通过赋值改变所指变量的取值;const在星号后表示所取地址固定,不能再指向其他地址。如下(1)(2)等价。
int i;
const int* p1 = &i;(1)
int const* p2 = &i;(2)
int *const p3 = &i;(3)
非const转换成const
void f(const int* x);
int a = 15;
f(&a);
const int b = a;
f(&b);
当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量修改。
【代码示例】
#include <stdio.h>
int main ()
{
int var1 = 10;
int *p;
char c = 'a';
char *m;
p = &var1;
m = &c;
printf("1.1: var1变量的地址为: %p\n", &var1);
printf("1.2: p存储的地址为: %p\n", p);
printf("1.3: 指针变量*p的取值为: %d\n\n\n", *p);
printf("2.1: 字符变量c的地址为: %p\n", &c);
printf("2.2: 字符指针变量m存储的地址为: %p\n", m);
printf("2.3: 字符指针变量m的取值为: %c\n\n\n", *m);
*p = 3;
printf("3.1: 指针变量p存储的新地址为: %p\n", p);
printf("3.2: 指针变量p的新取值为: %d\n", *p);
printf("3.3: var1的新取值为: %d\n\n\n", var1);
var1 = 4;
printf("4.1: 指针变量p存储的新地址为: %p\n", p);
printf("4.2: *p的新取值为: %d\n", *p);
printf("4.3: var1的新取值为: %d\n\n\n", var1);
return 0;
}
输出结果为:
1.1: var1变量的地址为: 0061fec4
1.2: p存储的地址为: 0061fec4
1.3: 指针变量*p的取值为: 10
2.1: 字符变量c的地址为: 0061fec3
2.2: 字符指针变量m存储的地址为: 0061fec3
2.3: 字符指针变量m的取值为: a
3.1: 指针变量p存储的新地址为: 0061fec4
3.2: 指针变量p的新取值为: 3
3.3: var1的新取值为: 3
4.1: 指针变量p存储的新地址为: 0061fec4
4.2: *p的新取值为: 4
4.3: var1的新取值为: 4
指针的指针
指针变量本身是一个变量,所以在声明后也占用一定的内存,故而其自身也有内存地址。指针的指针即为存储指针变量的内存地址的指针变量,声明时用两个*,表示为指针的指针。
声明语法为:
type **var_name;
【代码示例】
#include <stdio.h>
int main ()
{
int var1 = 10;
int *p;
int **q;//指向指针的指针
p = &var1;
q = &p;//取地址时仅写取地址符与指针变量的名称,不带星号。
printf("1.1: var1变量的内存地址为: %p\n", &var1);
printf("1.2: p存储的地址为: %p\n", p);
printf("1.3: p的内存地址为: %p\n", &p);
printf("1.4: q存储的地址为: %p\n", q);
printf("1.5: 指针变量*p的取值为: %d\n", *p);
printf("1.6: 指针变量**q的取值为: %d\n\n\n", **q);
return 0;
}
输出结果为:
1.1: var1变量的内存地址为: 0061fec8
1.2: p存储的地址为: 0061fec8
1.3: p的内存地址为: 0061fec4
1.4: q存储的地址为: 0061fec4
1.5: 指针变量*p的取值为: 10
1.6: 指针变量**q的取值为: 10
指针运算
用于表示当前指针变量位置的变化。 可以对指针进行四种算术运算:++、–、+、-。其中++、–操作是对内存地址进行变化,每执行一次++或–操作,内存地址变化相应类型数据所占的字节数。例如一个整型变量占4个字节,做加加运算后,内存位置后移四个字节。对指针变量做+或-操作,也是内存地址发生变化,即从当前数据类型的一个数据单元移到另一个数据单元。对指针变量的取值进行+或-操作,是取值发生变化,内存地址不变。
【代码示例】
#include <stdio.h>
int main(){
int a = 4;
int *pa;
int b[] = {1,2,100};
pa = &a;
int *pb;//int *pb; pb=b可直接写为*pb = b; 数组的变量名在C语言中即代表数组首元素的地址,所以*pb=b即可直接表示为指针变量pb取了数组b的首地址,且将数组首元素的值赋给*pb。
pb = b;
printf("1.1: 变量a的地址为: %p\n", &a);
printf("1.2: 指针变量pa存储的地址为: %p\n", pa);
printf("1.3: 指针变量pa的取值为: %d\n\n\n", *pa);
/*指针运算*/
/*单一元素变量的指针运算*/
*pa += 1;
printf("2.1: *pa += 1运算后指针变量pa存储的新地址为: %p\n", pa);
printf("2.2: *pa += 1运算后*pa的新取值为: %d\n", *pa);
printf("2.3: *pa += 1运算后a的新取值为: %d\n\n\n", a);
*pa -= 1;
printf("3.1: *pa -= 1运算后指针变量pa存储的新地址为: %p\n", pa);
printf("3.2: *pa -= 1运算后*pa的新取值为: %d\n", *pa);
printf("3.3: *pa -= 1运算后a的新取值为: %d\n\n\n", a);
pa += 1;
printf("4.1:pa += 1运算后指针变量pa存储的新地址为: %p\n", pa);
printf("4.1:pa += 1运算后*pa的新取值为: %d\n", *pa);
printf("4.1:pa += 1运算后a的新地址: %p\n", &a);
printf("4.1:pa += 1运算后a的新取值为: %d\n\n\n", a);
pa++;//-=与+=运算支持数值的运算,内存地址不会发生改变。但--与++的运算使得内存地址发生变化
printf("5.1: pa++运算后指针变量pa存储的新地址为: %p\n", pa);
printf("5.2: pa++运算后*pa的新取值为: %d\n", *pa);
printf("5.3: pa++运算后a的新取值为: %d\n\n\n", a);
pa -= 1;
printf("6.1: pa -= 1运算后指针变量pa存储的新地址为: %p\n", pa);
printf("6.1: pa -= 1运算后*pa的新取值为: %d\n", *pa);
printf("6.1: pa -= 1运算后a的新地址: %p\n", &a);
printf("6.1: pa -= 1运算后a的新取值为: %d\n\n\n", a);
*pa++;//-=与+=运算支持数值的运算,内存地址不会发生改变。但--与++的运算使得内存地址发生变化
printf("7.1: *pa++运算后指针变量pa存储的新地址为: %p\n", pa);
printf("7.2: *pa++运算后*pa的新取值为: %d\n", *pa);
printf("7.3: *pa++运算后a的新取值为: %d\n\n\n", a);
/*数组元素变量的指针运算*/
printf("数组b的首地址为: %p\n", b);
printf("指针变量pb所存储的地址为: %p\n", pb);
printf("指针变量pb的取值为: %d\n\n\n", *pb);
*pb += 1;
printf("数组指针运算1.1:*pb += 1运算后指针变量pb存储的新地址为: %p\n", pb);
printf("数组指针运算1.2: *pb += 1运算后*pb的新取值为: %d\n", *pb);
printf("数组指针运算1.3: *pb += 1运算后b的新取值为: %d\n\n\n", b[0]);
*pb -= 1;
printf("数组指针运算2.1: *pb -= 1运算后指针变量pb存储的新地址为: %p\n", pb);
printf("数组指针运算2.2: *pb -= 1运算后*pb的新取值为: %d\n", *pb);
printf("数组指针运算2.3: *pb -= 1运算后b的新取值为: %d\n\n\n", b[0]);
pb++;//-=与+=运算支持数值的运算,内存地址不会发生改变。但--与++的运算使得内存地址发生变化
printf("数组指针运算3.1: pb++运算后指针变量pb存储的新地址为: %p\n", pb);
printf("数组指针运算3.2: b[1]的地址为: %p\n", &b[1]);
printf("数组指针运算3.3: pb++运算后*pb的新取值为: %d\n", *pb);
printf("数组指针运算3.4: pb++运算后b的新取值为: %d\n\n\n", b[0]);
*pb++;//-=与+=运算支持数值的运算,内存地址不会发生改变。但--与++的运算使得内存地址发生变化
printf("数组指针运算4.1: *pb++运算后指针变量pb存储的新地址为: %p\n", pb);
printf("数组指针运算4.2: b[2]的地址为: %p\n", &b[2]);
printf("数组指针运算4.3: *pb++运算后*pb的新取值为: %d\n", *pb);
printf("数组指针运算4.4: *pb++运算后b的新取值为: %d\n", b[0]);
return 0;
}
输出结果为:
1.1: 变量a的地址为: 0061fec4
1.2: 指针变量pa存储的地址为: 0061fec4
1.3: 指针变量pa的取值为: 4
2.1: *pa += 1运算后指针变量pa存储的新地址为: 0061fec4
2.2: *pa += 1运算后*pa的新取值为: 5
2.3: *pa += 1运算后a的新取值为: 5
3.1: *pa -= 1运算后指针变量pa存储的新地址为: 0061fec4
3.2: *pa -= 1运算后*pa的新取值为: 4
3.3: *pa -= 1运算后a的新取值为: 4
4.1:pa += 1运算后指针变量pa存储的新地址为: 0061fec8
4.1:pa += 1运算后*pa的新取值为: 6422200
4.1:pa += 1运算后a的新地址: 0061fec4
4.1:pa += 1运算后a的新取值为: 4
5.1: pa++运算后指针变量pa存储的新地址为: 0061fecc
5.2: pa++运算后*pa的新取值为: 6422220
5.3: pa++运算后a的新取值为: 4
6.1: pa -= 1运算后指针变量pa存储的新地址为: 0061fec8
6.1: pa -= 1运算后*pa的新取值为: 6422200
6.1: pa -= 1运算后a的新地址: 0061fec4
6.1: pa -= 1运算后a的新取值为: 4
7.1: *pa++运算后指针变量pa存储的新地址为: 0061fecc
7.2: *pa++运算后*pa的新取值为: 6422220
7.3: *pa++运算后a的新取值为: 4
数组b的首地址为: 0061feb8
指针变量pb所存储的地址为: 0061feb8
指针变量pb的取值为: 1
数组指针运算1.1:*pb += 1运算后指针变量pb存储的新地址为: 0061feb8
数组指针运算1.2: *pb += 1运算后*pb的新取值为: 2
数组指针运算1.3: *pb += 1运算后b的新取值为: 2
数组指针运算2.1: *pb -= 1运算后指针变量pb存储的新地址为: 0061feb8
数组指针运算2.2: *pb -= 1运算后*pb的新取值为: 1
数组指针运算2.3: *pb -= 1运算后b的新取值为: 1
数组指针运算3.1: pb++运算后指针变量pb存储的新地址为: 0061febc
数组指针运算3.2: b[1]的地址为: 0061febc
数组指针运算3.3: pb++运算后*pb的新取值为: 2
数组指针运算3.4: pb++运算后b的新取值为: 1
数组指针运算4.1: *pb++运算后指针变量pb存储的新地址为: 0061fec0
数组指针运算4.2: b[2]的地址为: 0061fec0
数组指针运算4.3: *pb++运算后*pb的新取值为: 100
数组指针运算4.4: *pb++运算后b的新取值为: 1
0地址
- Null 是一个预定定义的符号,表示0地址
通常可以用0地址表示两种特殊的事情
- 返回的指针是无效的
- 指针没有没真正初始化,可以先初始化为0.
- 通常指针变量未有初始变量与之关联时,先将该指针变量赋为空,即如
void *p=NULL
malloc函数
动态内存分配,来实现数组的大小可变的情况。其返回结果类型为void类型, 需要类型转换为自己需要的类型
。
- 该函数使用需要包含头文件
#include<stdlib.h>
- 函数声明形式
void* malloc(size_t, size)
; - 动态分配的空间,用完后需要用free()函数释放。申请空间的首地址需被释放。
从函数返回指针
C语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为static变量。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int *getRandom(){
static int r[10];//声明静态局部变量
int i;
srand((unsigned)time(0));//设置随机种子,随机数从时间获取。
for(i=0;i<10;i++){
r[i] = rand();
}
return r;
}
int main(){
int *p;//声明指针变量
int i;
p = getRandom();//调用返回指针的函数
for(i=0;i<10;i++){
printf("*(p+%d): %d\n", i, *(p+i));
}
return 0;
}
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int getRandom(){
int r[10];//常规函数无须强制定义为静态变量
int i;
srand((unsigned)time(0));//设置随机种子,随机数从时间获取
for(i=0;i<10;i++){
r[i] = rand();
}
return r;
}
int main(){
int p[10];//定义数组
int i;
p[10] = getRandom();
for(i=0;i<10;i++){
printf("p[%d]: %d\n", i, p[i]);
}
return 0;
}
总结
指针概念太过高深,要想掌握还需多练多写。
参考文献
- C|菜鸟教程。链接
- https://blog.csdn.net/WhereIsHeroFrom/article/details/121551694