C语言精华 - 指针初识篇

简介: C语言精华 - 指针初识篇

文章目录

前言

在之前的文章中有简单介绍指针,但在C语言初识这个专栏里也不会深入,在未来C语言进阶这个专栏会详细了解

一、指针是什么

官方来说:在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(Points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”,意思是通过它能找到以它为地址的内存单元

#include<stdio.h>
int main()
{
  int a = 10;//a占4个字节,1个字节对应1个编号,a有4个地址,如果用4个地址去访问a比较麻烦
  int* pa = &a;//&a拿到的是a的4个字节中每一个字节的地址。通过int*类型的pa存储a的地址
  *pa = 20;//再通过*解引用操作去访问a
  return 0;
}

一个小的单元是多大?以及如何编址?

经过仔细计算和权衡我们发现一个字节对应一个地址是比较合适的

对于32位的机器,假设有32根地址线,那么假设每根地址线寻址产生一个电信号正电/负电(1或0)


假设一个内存单元是1bit - 2^32bit:

如果给每个bit都有一个地址 - 太浪费了。经过平衡后,以一个字节为内存单元,然后分配地址

这里就有2的32次方个地址。 每个地址标识一个字节,那么:

同理64位也是一样

在32位机器上,地址是32个0或者1组成的二进制序列,那地址就得用4个字节的空间来存储所以一个指针变量的大小就是4个字节

在64位机器上,如果有64根地址线,只有一个指针变量的大小是8个字节,才能存放一个地址

小结:1.每个地址标识一个字节 &emsp;&emsp;&emsp;2.指针的大小在32位平台是4个字节,在64位平台是8个字节

二、指针和指针类型

1、指针类型

不同类型的数据交给指针时也要使用不同的指针类型去存储

由以下代码发现:不同的指针类型都是4个字节或8个字节,那么指针类型是否有存在的必要?

#include<stdio.h>
int main()
{
  int* pa;
  char* pc;
  float* pf;
  printf("%d\n", sizeof(pa));//4
  printf("%d\n", sizeof(pc));//4
  printf("%d\n", sizeof(pf));//4
  return 0;
}

2、指针类型的意义

先了解一下:1个十六进制位是4个二进制位

0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f  -> 十六进制位

1 1 1 1 1 1 1 1 -> 二进制位

     8 4 2 1 = 15 = f

所以说4个二进制位是1个十六进制位,1个字节是2个十六进制位

#include<stdio.h>
int main01()
{
  int a = 0x11223344;
  int* pa = &a;
  *pa = 0;
  return 0;
}
//--------------------------------------------------
int main02()
{
  int a = 0x11223344;
  char* pc = &a;
  *pc = 0;
  return 0;
}

这里使用int和char类型的指针存储a的值。调试发现:同一个值,被不同类型指针存储后,在解引用操作时,它们的访问权限不一样


为了能够更直观的认识指针类型的意义,再看以下代码:

#include<stdio.h>
int main()
{
  int arr[10] = {0};
  int* p1 = arr;
  char* p2 = arr;
  printf("%p\n", p1);
  printf("%p\n", p1 + 1);
  printf("----------分割线---------\n");
  printf("%p\n", p2);
  printf("%p\n", p2 + 1);
  return 0;
}

这里使用int和char类型的指针存储arr数组首元素的地址。运行发现:被不同类型指针存储后,统一加1后的结果不同

小结:1、指针类型决定了指针解引用的权限有多大 &emsp;&emsp;&emsp;2、指针类型决定了指针走一步,能走多远(步长)** &emsp;&emsp;&emsp;**3、int类型指针+1跳过4个字节;char类型指针+1跳过1个字节;数组类型指针+1跳过过1个数组(暂时不了解)

3、简单应用

/***********************************************************************

目的:借助指针输出将数组里的元素1 - 10输出

分析:使用int*类型的指针存储数组首地址,并利用解引用操作符

平台:Visual studio 2017 && windows

***********************************************************************/

#include<stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  int i = 0;
  int* p = arr;
  for(i = 0; i < 10; i++)
  {
    printf("%d ", *(p++));
  }
  return 0;
}

输出结果:


相反,如果使用char*类型的指针去存储:每次加1后所走的步长不同

#include<stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  int i = 0;
  char* p = arr;
  for(i = 0; i < 10; i++) 
  {
    printf("%d ", *(p++));
  }
  return 0;
}

输出结果:

三、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。以下代码列举几个野指针

1、野指针的产生

#include<stdio.h>
//1、
int main01()
{  
  int* p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值 
  *p = 20;//非法访问内存
  return 0;
}
//2、
int main02()
{
  int arr[10] = {0};
  int* p = arr;
  int i = 0;
  for(i = 0; i <= 10; i++)//当i = 10时,此时的p就是野指针,再去解引用的话就是非法访问内存
  {
    *p = i;
    p++;
  }
}
//3、
int* test()
{
  int a = 10;
  return &a;
}
int main03()
{
  int* p = test(); //a是局部变量,一旦test函数执行完成,test函数被销毁,变量a被释放。p虽然拿到了a的地址,但是指向的空间就是未知的 
  *p = 20;//非法访问内存
  return 0;
}

小结: 1、指针变量未初始化的情况是野指针
   2、指针指向的数组越界后是野指针
   3、指针去接收一个函数的局部变量的返回地址时,这个程序结束,函数销毁,指针指向的空间被释放,也会导致野指针

2、如何规避野指针

#include<stdio.h>
//1、
int main01()
{
  //`1.明确知道要初始化的值时:
  int a1 = 10;
  int* p1 = &a1;
  //2.不明确知道要初始化的值时
  //养成好的习惯,定义变量的时候对变量初始化为0
  int a = 0;
  //而指针可以初始化为NULL(空)
  int* p = NULL;
  return 0;
}
//2、
int main02()
{
  int arr[10] = {0};
  int* p = arr;
  int i = 0;
  for(i = 0; i < 10; i++)//C语言本身是不会检查数组越界的,要保证数组不越界
  {
    *p = i;
    p++;
  }
}
//3、
int* test()
{
  int a = 10;
  return &a;
}
int main03()
{
  int* p = test(); 
  p = NULL;//将p置为空指针
  *p = 20;//err
  return 0;
}
//4、
int main04()
{
  int* p = NULL;
  if(p != NULL)
    *P = 20;
  return 0;
}

总结: 1、初始化变量
   2、注意不要数组越界
   3、指针指向的空间释放后及时置为NULL
   4、指针使用之前检查有效性

四、指针运算

1、利用指针进行简单运算

#define N_VALUES 5
#include<stdio.h>
//1、使用指针运算将数组从前往后被初始化为0
int main01()
{
  float values[N_VALUES];
  float* vp;
  for(vp = &values[0]; vp < &values[N_VALUES];)//指针的关系运算
  {
    *vp++ = 0;//指针加减运算
  }
  return 0;
}
//2、使用指针运算将数组从后往前被初始化为0
int main02()
{
  float values[N_VALUES];
  float* vp;
  for(vp = &values[N_VALUES]; vp > &values[0];)
  {
    *--vp = 0;
  }
  return 0;
}
//3、将main02代码简化一点
int main03()
{
  float values[N_VALUES];
  float* vp;
  for(vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
  {
    *vp = 0;
  }
  return 0;
}

观察main02和main03

main02和main03的功能是一样的,都能初始化数组为0,且main03相对来说更容易理解

实际上main03在绝大部分的编译器上是可以完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行

标准规定:允许指向数组元素的指针与指向数组最后1个元素后面的那个内存位置的指针比较,但是不允许与指向第1个元素之前的那个内存位置的指针进行比较


#include<stdio.h>
//1、利用指针运算打印数组元素
int main01()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  int* p = arr;//将数组的第1个元素的地址交给p
  int* pend = arr + 9;//将数组的最后1个元素的地址交给pend
  while(p <= pend)//利用数组的地址 -> 从低到高
  {
    printf("%d ", *p);//1 2 3 4 5 6 7 8 9 10
    p++;
  }
  return 0;
}
//2、指针相减
int main02()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  //指针相减运算 -> 指针-指针 = 2个指针之间的元素个数 -> 但前提是2个指针指向同一块空间
  printf("%d\n", &arr[9] - &arr[0]);//9
  //细想一下,其实指针+指针其实是没有意义的
  return 0;
}

2、简单应用

/***********************************************************************

目的:使用指针与指针的运算模拟strlen

分析:找到目标字符串\0的位置和首元素地址相减即可

平台:Visual studio 2017 && windows

*************************************************************************/

#include<stdio.h>
int my_strlen(char* str)
{
  //备份1份首地址
  char* start = str;
  while(*str)
  {
    str++;
  }
  return str - start;
}
int main()
{
  int len = my_strlen("abc");//传过去的仅仅是a的地址
  printf("%d\n", len);
  return 0;
}

五、指针和数组

1、指针和数组的关联

#include<stdio.h>
int main()
{
  int arr[10] = {0};
  int* p = arr;
  int i = 0;
  for(i = 0; i < 10; i++)
  {
    //printf("%p <==> %p\n", &arr[i], p + i);//&arr[i] <==> p + i
    *(p+i) = i;
  }
  for(i = 0; i < 10; i++)
  {
    printf("%d ", *(p + i));
  }
  return 0;
}

2、更深层次的了解指针和数组

#include<stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10}; 
  int* p = arr;
  //打印第3个元素:
  printf("%d\n", arr[2]);
  printf("%d\n", 2[arr]);
  printf("%d\n", p[2]);
  //arr[2] --> *(arr + 2) --> *(2 + arr) --> 2[arr]
  //p[2] --> *(p + 2)
  //可推出:
  //arr[2] <==> *(arr + 2) <==> *(p + 2) <==> *(2 + p) <==> *(2 + arr) <==> 2[arr]
  return 0;
}

六、二级指针

#include<stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;//此时pa指向变量a的地址时,pa为一级指针变量
  int** ppa = &pa;//同时pa也是个变量||地址,此时ppa去指向pa的地址时,ppa为二级指针变量
  int*** pppa = &ppa;//此时pppa为三级指针变量
  //当然还有四级指针、五级指针...。语法是支持的,但是并不常用(三级指针也很少用到)
  //怎么通过ppa找到a -> *ppa <=> pa, *pa <=> a, **ppa <=> a
  printf("%d\n", **(ppa));
  return 0;
}

图解:

七、指针数组

#include<stdio. h>
int main()
{
  int arr[10];//整型数组 - 存放整型的数组就是整型数组
  char ch[5];//字符数组 - 存放字符的数组就是字符数组
  //指针数组 - 存放指针的数组就是指针数组
  int* parr1[5];//整型指针数组
  char* parr2[5];//字符指针数组
  return 0;
}

八、指针进阶

这里将附上对于指针更深层次的文章

指针进阶篇


相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
56 0
|
20天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
73 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
20天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
44 9
|
20天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
40 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
101 13
|
23天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
23天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
74 3
|
24天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
52 11
|
23天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
35 1
下一篇
DataWorks