C基础指针的使用
Posted mChenys
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C基础指针的使用相关的知识,希望对你有一定的参考价值。
目录
一、一级指针
指针也是一个变量,指针存放的内容是一个内存地址,该地址指向一块内存空间。
1.1 指针变量的定义
一级指针变量的数据类型会在基本数据了类型之后多了一个*
号,指针变量只能存放内存地址(一个16进制的数),不能将一个基本数据类型直接赋值给一个指针变量。
如果要取出一级指针变量指向的内存地址所对应的值的话,可以通过在指针变量前加一个*
号来获取
int *p;//表示定义一个指针变量p, 类型是int *;
*p;//代表获取该指针指变量指向的内存块对应的实际数据
int *p = 100; //错误, 只能指向内存地址, 是一个16进制的数
*p =100 ; //正确, 因为*p操作的是变量的值.
1.2 &取地址运算符
通过&
符号可以取得一个变量在内存当中的地址,然后就可以赋值给指针变量了。
#include<stdio.h>
int main()
int a = 100;
int *p;// 定义了一个int *类型的指针变量,名字叫做p
p = &a;// 给指针变量赋值,把a的内存地址赋值给指针变量p
*p = 200;//修改a的值,效果等于直接给a赋值, *p代表指针指向变量的值,而p代表指向变量的内存地址
printf("p=%p,*p=%d,a=%d\\n",p,*p,a);
// 通常为了书写方便,可以直接这样给指针变量赋值
// int *p1 = &a;
// 直接修改a变量的值,内存地址并不会修改
a = 50;
// 查看*p的值
printf("p=%p,*p=%d,a=%d\\n",p,*p,a);
return 0;
两次输出结果如下:
可以看到*p和a变量的值是一样的,并且修改值并不会影响p指针变量指向的内存地址。
1.3 无类型指针
定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void *转化为其他类型指针,也可以用(void *)将其他类型指针强制转化为void类型指针。
void *p 他可以指向任意类型的内存地址
1.4 空指针与野指针
NULL在C语言中的定义为(void *)0
,它是一个宏常量, #define NULL 0 .如果一个指针变量没有明确指向一块内存地址, 那么就把这个变量指向NULL,这个指针就是空指针,空指针是合法的,例如:
int *p = NULL;
// 或者
int *p;
p = NULL;
指向NULL的指针叫空指针,没有具体指向任何变量地址的指针(也就是没有初始化值的指针)叫野指针。
#include<stdio.h>
int main()
// 定义了一个int *类型的指针变量p
int *p;
// 如果指针变量没有初始化就使用,那就是野指针,
// 由于它不知道指向的内存地址是啥,所以无法修改这块内存地址所对应的数值, 程序执行的时候会报错
*p = 100;
return 0 ;
1.5 指针的兼容性
指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *
赋值给int *
,它是不会自动类型转换的.
原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。
1.6 常量指针与指针常量
1、指向常量的指针(常量指针)
常量指针的const书写在指针类型之前,例如:
const char *p;
定义一个指向常量的指针, 但是这个指针不能通过*p
的方式取修改变量的值了。但是可以通过*p的方式读取变量的值。
#include<stdio.h>
int main()
int a = 10; //定义一个变量
// 定义一个指向int类型的地址的指针变量,指向a变量的内存地址
const int *p = &a; // 指针类型是 const int *
*p = 100; // 编译会通过不了,常量指针的常量不能改变, 但是直接给a变量赋值还是可以的.只是不允许通过*p的方式
printf("a=%d\\n",*p);
return 0;
2、指针常量
指针常量的const书写在指针类型之后,例如:
char * const p;
定义一个指针常量,一旦初始化之后其内容不可改变,也就是说不能再指向其他变量的地址了。但是可以通过*p的方式改变变量的值。
#include<stdio.h>
int main()
int a = 10; //定义一个变量
//定义一个指针常量,指向a变量的内存地址
int *const p = &a; //指针类型是 int *const
int b = 20; // 允许修改变量
p = (int *)&b;//编译通不过,因为p是一个常量,不能被修改,指针常量的指针不能修改
printf("%d\\n",*p);
return 0;
原则上在C语言中常量指针的常量不能修改、指针常量的指针不能修改;但是C语言中通过const定义的常量是有问题的,因为可以通过指针变量来间接的修改常量的值,所以在C语言中用#define常量比较多,而C++的const是没办法改的。
1.7 指针与数组的关系
一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址,而且是连续的内存地址.也就是说如果数组是char类型的,那么每个元素的内存地址就是相差1, 如果是int类型的数组, 那么元素间的内存地址是相差4,例如:
int a[10];
int *p = a;
p和&a[0]的地址,以及a数组名的地址都是一样的。 也就是说数组名的地址等于数组首元素的内存地址。
#include<stdio.h>
int main()
char a[10];
printf("a=%p\\na[0]=%p\\na[1]=%p\\na[2]=%p\\n",a,&a[0],&a[1],&a[2]);
printf("========================\\n");
int b[10];
printf("b=%p\\nb[0]=%p\\nb[1]=%p\\nb[2]=%p\\n",b,&b[0],&b[1],&b[2]);
return 0;
输出结果如下:
a=0x16efa760e
a[0]=0x16efa760e
a[1]=0x16efa760f
a[2]=0x16efa7610
========================
b=0x16efa75e4
b[0]=0x16efa75e4
b[1]=0x16efa75e8
b[2]=0x16efa75ec
从结果也可以看出char数组的每个元素内存地址相差1, int数组的元素内存地址相差4.
注意:当指针变量指向一个数组名的时候,C语言语法规定指针变量名可以当做数组名使用.但是指针变量的sizeof不是数组的sizeof, 指针变量的sizeof在64位系统是8个字节, 32位系统是4个字节.
#include<stdio.h>
int main()
int a[] = 1, 2, 3, 4, 5;
int *p; // 指针变量
// 数组可以直接赋值给指针变量,不用&取地址,因为变量名a的地址就是数组首元素的内存地址
p = a;
// 如果指针指向的是数组,那么变量名可以当做数组用
p[3] = 100;
// 数组的长度和指针变量的长度是不一样的
printf("数组长度=%lu字节,指针变量长度=%lu字节\\n", sizeof(a), sizeof(p));
// 操作指针变量来操作数组元素
int i;
for (i = 0; i < 5; i++)
printf("a[%d]=%d\\n", i, p[i]);// 和操作数组一样操作元素
return 0;
输出结果如下:
数组长度=20字节,指针变量长度=8字节
a[0]=1
a[1]=2
a[2]=3
a[3]=100
a[4]=5
注意:指针变量如果指向的不是数组名或者数组的首元素的内存地址,那么用指针去取数组的元素的时候是有区别的,假设指针指向的是数组下标为2的元素的内存地址, 那么p[0] 就不再是等于a[0]了, 而是等于a[2]了, 同理p[1]就应该等于a[3],其他依次类推。
1.8 指针位移运算
指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数做为倍数的运算。
例如:char *p;
当执行p++;
后,移动的字节数是sizeof(char)
这么多的字节数;如果是int *p1;
那么p1++
后移动的字节数就是sizeof(int)
。
#include<stdio.h>
int main()
int a[5] = 0;
int *p = a;//指向数组名的指针,就是数组首元素的地址
*p = 100;// 给指针变量对应地址的变量赋值100,相当于p[0] = 100;
printf("移动前指针位置:%p\\n",p);
//移动指针
//移动2,表示移动了sizeof(int) *2 个字节,对应就是数组下标=2的元素内存地址
p += 2;
*p = 20;//修改a[2]的值为20
printf("移动后指针位置:%p\\n",p);
int i;
for(i = 0 ;i < 5; i++)
printf("a[%d]=%d\\n",i,a[i]);
return 0;
结果如下:
移动前指针位置:0x16efbb600
移动后指针位置:0x16efbb608
a[0]=100
a[1]=0
a[2]=20
a[3]=0
a[4]=0
可以看到指针+2后,移动前后的内存地址刚好相差8个Byte,也就是2个int元素的占用的内存大小.
1.9 指针与char数组
在C语言中所有的数据类型都可以当做是一个char的数组.
#include<stdio.h>
int main()
int a = 0x12345678; //定义一个16进制数,int占4个字节,相当于char数组长度是4个BYTE
char *p = (char *)&a;//定义char * 的指针,指向a的地址
int i = 0 ;
for(i =0;i < sizeof(a); i++)
printf("a[%d]=%x\\n",i,p[i]);//由于是小端对齐,所以输出结果是78,56,34,12倒着输出的.内存地址从左->右表示从高->底
return 0;
练习-将一个int数转成ip地址
我们知道一个int占4个字节,刚好对应4个char, 对于unsigned char的取值范围就是0~255, 所以刚好和ip地址每段的取值符合.所以可以将int数转成ip地址。
#include<stdio.h>
int main()
int a = 987654321;
unsigned char *p = (unsigned char *)&a; //定义unsigned char *类型的指针变量p 对应a变量的地址,这里其实是指针强转
//在C语言中基本数据类型可以当做char数组对待,所以可以这样操作
printf("%u.%u.%u.%u\\n",p[3],p[2],p[1],p[0]); // 结果为: 58.222.104.177
return 0;
直接ping 987654321 对应的ip地址就是上面用程序求出的ip: 58.222.104.177
练习-将一个字符串IP转成unsigned int类型.
这个案例就是上面案例的反过程, 先将字符串中每一段的数据取出来,然后操作指针来赋值.
#include<stdio.h>
int main()
char a[] = "192.168.1.1";
unsigned int ip = 0;
unsigned char * p = (unsigned char *) &ip;
//先从字符串中取值
int a1,a2,a3,a4;
sscanf(a,"%d.%d.%d.%d",&a1,&a2,&a3,&a4);
//小端对齐赋值
p[0] = a4;
p[1] = a3;
p[2] = a2;
p[3] = a1;
printf("ip=%u\\n",ip); // ip=3232235777
return 0;
如下所示,对数字3232235777进行ping时,查看的ip刚好就是192.168.1.1
练习-使用指针对二维数组进行排序
#include<stdio.h>
int main()
int a[2][3] = 10,21,13,42,5,9; //二维数组
int *p = (int *)a; // 定义一个指针, 指向数组a
int i,j;
//下面通过操作指针的方式来排序, 由于内存是连续的,所以可以当做一维数组看待
int size = sizeof(a) / sizeof(a[0][0]); // 6
for(i = 0 ; i < size; i++)
for(j=0 ; j< size - i -1 ; j++)
if(p[j] > p[j+1])
// 直接通过指针操作内存地址对应的值
int temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
//输出排序后的结果
for( i =0 ; i <sizeof(a) /sizeof(a[0]) ; i++)
for( j =0; j<sizeof(a[0]) / sizeof(a[0][0]); j++)
printf("%d,",a[i][j]); // 5,9,10,13,21,42,
return 0;
练习-使用指针对字符串取反操作
int main()
char str[20] = "hello world";
char *start = str;//&(str[0]); //创建首指针
char *end = start + strlen(str) - 1; //创建尾指针, 通过指针偏移到尾部
while (start < end)
// 通过指针操作值
char tmp = *start;
*start = *end;
*end = tmp;
// 偏移首位指针
start++;
end--;
printf("%s\\n", str); // dlrow olleh
return 0;
1.10 指针数组
由于指针是一个变量,所以也可以以数组的形式出现。 指针数组的字节长度不受数据类型的影响,而是受到操作系统的影响, 在32位系统中,一个指针的长度是4个BYTE, 64位系统是8个BYTE, 所以32位系统中指针数组的长度等于 元素个数*4
; 64位系统中指针数组的长度等于元素个数*8
.
#include<stdio.h>
int main()
//定义一个int *类型的10个元素的指针数组
int *a[10] = NULL;
//定义一个char *类型的10个元素的指针数组
char *b[10] = NULL;
printf("%lu,%lu\\n",sizeof(a),sizeof(b)); // 80,80
return 0;
指针数组元素的赋值
#include<stdio.h>
int main()
int *a[10] = NULL;
int i = 5;
a[0] = &i; //取i的地址给a[0]指针
*a[0] = 100; //修改i的值
printf("i=%d\\n",i); // i=100
return 0;
二、二级和多级指针
指针就是一个变量,既然是变量就也就存在内存地址,所以可以定义一个指向指针的指针变量。二级指针的定义会比一级指针多一个*
;
二级指针指向的是一级指针的地址, 一级指针指向的是变量的地址, 如下图所示:
换成代码就是:
int a = 10;
int *p = &a; //一级指针指向变量a
int **pp = &p; //二级指针指向一级指针变量p
//反过来就是修改变量的值
*pp = NULL; // 表示将1级指针变量的值赋值为NULL;
**pp = 100; //表示把变量a的值赋值为100;
2.1 二级指针与一级指针数组的关系
一级指针数组里面存放的元素都是一级指针变量, 而指针数组的名字就是数组首元素的内存地址, 也就是一级指针的内存地址, 所以可以用二级指针的来指向.当指针指向数组的时候就可以当做数组本身来使用了.
int main()
int *a[3] = NULL;//一级指针数组
int x,y,z;
a[0] = &x; //给数组元素赋值
a[1] = &y;
a[2] = &z;
int **p = a;//二级指针指向数组名,数组名等效于指向&a[0],而a[0]是个一级指针.所以指向一级指针地址的指针那就是二级指针了.
//指针指向数组就可以当做数组用
int i;
for(i=0;i<3;i++)
printf("p[%d]=%p\\n",i,p[i]); //打印数组元素,数组元素是一级指针
*p[0] = 100;//等效于 *a[0] =100; 就是将0下标的指针指向的变量的值修改为100,因为p[0]=a[0]=一级指针,然后加*就是修改值
*p[1] = 200;
*a[2] = 300;
printf("x=%d,y=%d,z=%d\\n",x,y,z);
return 0;
结果如下:
p[0]=0x16b1735f8
p[1]=0x16b1735f4
p[2]=0x16b17以上是关于C基础指针的使用的主要内容,如果未能解决你的问题,请参考以下文章