C语言重难点 “指针”。

前言

指针是c语言学习的重点,让我们由浅入深,一步步进行了解。


 1. int a = 10;
 2. char b = 'b';
 3. float c = 1.0f;

   如上面的代码,每个变量在创建时都会在内存上开辟一个空间,不同类型的变量有不同的大小,每个变量都有自己的起始位置,起始位置就是该变量的地址,C语言中给地址起了新的名字叫做:指针
   这就好比我们出去住酒店,酒店的每一个房间都有一个号码,当我们将房间号告诉朋友并让他储存起来,下一次朋友来找我们,就可以通过储存的房间号,来找到我们,指针也是这样的一个道理,我们通过使用指针将变量的地址储存,在有需要的情况下,我们就可以通过访问指针储存的地址,来找到我们的目标变量,并进行下一步操作。
   所以我们可以理解为:内存单元编号 == 地址 == 指针

一、指针的类型

(1)、指针初始化


 1. int* p = NULL;
 2. char* p = NULL;
 3. int** p = NULL;
 4. char**p = NULL;
 5. int*(*p)[10] = NULL;
 6. int(*p)[5] = NULL;

这是几个常见的指针的初始化,下面我们来区分他们。

(2)、指针的类型

 将指针变量p去掉,就是指针的类型


 1. int* p = NULL;//指针类型为int*
 2. char* p = NULL;//指针类型为char*
 3. int** p = NULL;//指针类型为int**
 4. char**p = NULL;//指针类型为char**
 5. int*(*p)[10] = NULL;//指针类型为int*(*)[10]
 6. int(*p)[5] = NULL;//指针类型为int(*)[5]

(3)指针指向的类型

 同理,去掉指针变量p和与其最近的 ’*’ 号,就可以得到指针所指向的内容


 1. int* p = NULL;//指针指向的类型为int
 2. char* p = NULL;//指针指向的类型为char
 3. int** p = NULL;//指针指向的类型为int*
 4. char**p = NULL;//指针指向的类型为char*
 5. int*(*p)[10] = NULL;//指针指向的类型为int*()[10]
 6. int(*p)[5] = NULL;//指针指向的类型为int()[5]

(4)解引用

代码中的 ‘*’ 号,就是解引用操作符


 1. int main()
 2. {
 3.    int a = 100;
 4.    int* pa = &a;
 5.    *pa = 0;
 6.    return 0;
 7. }

  上⾯代码中第5⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间,pa其实就是a变量了;所以pa = 0,这个操作符是把a改成了0。

二、指针的使用

(1)解引用操作符

代码中的 ‘*’ 号,就是解引用操作符


 1. int main()
 2. {
 3.    int a = 100;
 4.    int* pa = &a;
 5.    *pa = 0;
 6.    return 0;
 7. }

  上⾯代码中第5⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间,pa其实就是a变量了;所以pa = 0,这个操作符是把a改成了0。

(2)、取地址操作符

取地址操作符 —— &


 1. int main()
 2. {
 3.    int a = 10;
 4.    int*p = &a;
 5.    *p = a;
 6.    p = &a;
 7.    printf("%p\n", p);
 8.    printf("%p\n", p+1);
 9.    return 0;
 10.} 


 1. 005BF984
 2. 005BF988

  上述代码中,我们通过取地址操作符 ‘ & ’ 将a的地址存到指针p里面,p+1的地址比p的地址多4,因为&a取出的是a所占4个字节中地址较⼩的字节的地址。在访问时会通过其变量类型来向后访问相应的字节。( int 占四字节 )

三、指针变量的大小

  32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。
  如果指针变量是⽤来存放地址的,那么指针变的⼤⼩就得是4个字节的空间才可以。
  同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节

 1. int main()
 2. {
 3.  printf("%zd\n", sizeof(char *));
 4.  printf("%zd\n", sizeof(short *));
 5.  printf("%zd\n", sizeof(int *));
 6.  printf("%zd\n", sizeof(double *));
 7.  return 0;
 8. }

X86环境下输出


 1. 4
 2. 4
 3. 4
 4. 4

X64环境下输出


 1. 8
 2. 8
 3. 8
 4. 8

结论:

  • 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
  • 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
  • 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

四、指针的运算

(1)、指针±整数

  数组在内存中是连续存放的


 1. int main()
 2. {
 3. 	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 4. 	int *p = &arr[0];
 5. 	int sz = sizeof(arr) / sizeof(arr[0]);
 6. 	for(int i = 0; i < sz; i++)
 7. 	{
 8. 		printf("%d ", *(p + i));
 9. 	}
 10.	return 0;
 11.}	 


 1. 1 2 3 4 5 6 7 8 9 10

  *p取出数组首元素的地址,i为整数,(p+i)就会依次取出数组的元素。 同理,也可以计算指针减去一个整数。

(2)指针-指针

  指针 - 指针的操作,其本质是计算两个指针之间有多少个元素,虽然在数值上是字节的大小,但默认会根据指针指向内容的类型大小进行计算,即指针指向内容若为 int 类型 ,两个指针相减数值上位40,那么默认会进行 40 ÷ 4 的操作,即指针之间有10个元素。


 1. int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
 2.int* p1 = &arr[0];
 3.int* p2 = &arr[0] + 10;
 4.int num = p2 - p1;
 5.printf("%d - %d = (数值大小)\n", p2, p1);
 6.printf("p2 - p1 = %d\n", num);


 1.19922500 - 19922460 = (数值大小)//40
 2.p2 - p1 = 10

(3)、指针的运算关系

指针和指针可以用来进行大小的比较,通过比较可以实现一系列功能,例如打印数组。

 1.int arr[10] = {0,1,2,3,4,5,6,7,8,9};
 2.int* p1 = &arr[0];
 3.int* p2 = &arr[0]+10;
 4.while(p1 < p2)
 5.{
 6.	printf("%d ",*p1);
 7.	p1++;
 8.}
 1.0 1 2 3 4 5 6 7 8 9

五、野指针

  概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

(1)、野指针成因

  a、指针未进行初始化


 1. int main()
 2. {
 3. 	int *p;//局部变量指针未初始化,默认为随机值
 4. 	*p = 10;
 5. 	return 0;
 6. }


  b、指针越界访问


 1. int main()
 2. {
 3. 	int arr[10] = {0};
 4. 	int *p = &arr[0];
 5. 	int i = 0;
 6. 	for(i = 0; i <= 11; i++)
 7. 	{
 8. 		*(p++) = i;//指针指向的范围超出数组arr的范围时,p就是野指针
 9. 	}
 10.	return 0;
 11.} 

  c、指针所指向的空间被释放


 1. int* test()
 2.{
 3.		int n = 100;
 4.		return &n;
 5.}
 6.int main()
 7.{
 8.		int* p = test();
 9.		printf("%d\n", *p);
 10.	return 0;
 11.}

(2)、如何避免野指针

   a、指针初始化
   b、小心指针越界
   ⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
   c、指针变量不在使用时,及时置为NULL,指针使用之前检查有效性
   d、避免返回局部变量的地址

六、二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
这就是 ⼆级指针
  对于二级指针的运算有:

  • *ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa .

 1. int b = 20;
 2. *ppa = &b;//等价于pa =&b

  • **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a .

 1. **p = 30;
 2. //等价于*pa = 30;
 3. //等价于a = 30;

七、指针的具体类型

(1)、空指针


 1. void* p;

  空指针,也称无具体类型的指针(或叫泛型指针),通常可以用作函数的参数,它可以接收任意类型的地址,但空指针不可以直接进行指针的加减整数和解引用的运算

(2)、指针数组


 1. char (*p)[];
 2.int (*p)[];
 3.float (*p)[];
 4.double (*p)[];

  指针数组的每个元素都是⽤来存放地址(指针)的。(用来存放指针的数组)

(3)、数组指针


 1. char (*p)[];
 2.int (*p)[];
 3.float (*p)[];
 4.double (*p)[];

  那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

(4)、函数指针


 1. int (*p)(int,int);
 2.//    int            (*p)                     (int,int);
 3.//     ↑               ↑                        ↑   ↑
 4.//函数返回类型   函数指针变量名   p所指向的的函数的参数类型和个数说明

  函数指针是用来变量是用来存放函数地址的,未来通过地址可以调用函数

(5)、函数指针数组


 1.int (*p[4])(int,int)
 2.//    int              (*p                  [4])                    (int,int);
 3.//     ↑                 ↑                   ↑                        ↑   ↑
 4.//函数返回类型   函数指针数组变量名   存放函数的个数   p所指向的的函数数组中函数的参数类型和个数说明

总结 :

  本文主要用于个人学习和知识分享,学习路漫漫,如有错误,感谢指正。
  如需引用,注明地址。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值