C语言初阶指针

简介: 指针是内存中一个最小的单元编号,也就是地址,我们平时说的指针通常是指指针变量,用来存放内存地址的变量。我们就可以把大内存比作一个公寓,每个小内存相当于一个小房子,每个小房子都有一个自己的门牌号,这就相当于地址。

 在c语言的学习中,我们都跳不过去一个东西——指针,那什么是指针呢?我们先来了解一下。


       指针是内存中一个最小的单元编号,也就是地址,我们平时说的指针通常是指指针变量,用来存放内存地址的变量。我们就可以把大内存比作一个公寓,每个小内存相当于一个小房子,每个小房子都有一个自己的门牌号,这就相当于地址。一个小的内存单元只有一个字节,而在32位机器中就会有2的32次方种不同的编号,相当于2的32次方字节的内存,在64位机器中就有2的64次方的内存,每个内存单元都有一个编号,这个编号也被称为地址。


       当我们在C语言中创建一个变量、数组时,都要在内存上开辟空间。那我们怎么找到变量在计算机中的位置呢,我们可以用取地址操作符&来找到对应的地址。


int main(void)
{
  int a = 0;
  int arr[12];
  printf("%p\n", &a);
  printf("%p\n", arr);
  return 0;
}

2dc6ba21abaf49dfb744188384d8cfa5.png


指针是用来存放地址的,所以我们也可以使用指针变量将对应的地址打印出来。


int main(void)
{
  int a = 0;
  int* p = &a;
  printf("%p\n", p);
  printf("%p\n", &a);
  return 0;
}

ca5cfef421ae46449af79152f31077cc.png


机器的地址都是由二级制数进行编号的,计算机中就有32个电压线分别代表0或1,那一个地址由32位不同的电压线进行自由组合,那指针{地址}的大小就是32bit也就是4个字节。同样64位机器的指针大小就是8个字节。


指针和指针类型


在变量类型中,我们就有不同的变量类型,有整型、浮点型、字符型……那我们的指针类型也会有这么多的类型。


char*  (字符型指针)


int*      (整型指针)


float*   (单精度浮点型指针)


double(双精度浮点型指针)  


short*  (短整型指针)


long*   (长整型指针)


……


无论在程序中使用哪种类型的指针,在32位机器下都是4个字节大小。


int main(void)
{
  printf("%d\n", sizeof(int*));
  printf("%d\n", sizeof(char*));
  printf("%d\n", sizeof(float*));
  printf("%d\n", sizeof(long*));
  printf("%d\n", sizeof(short*));
  printf("%d\n", sizeof(double*));
}

90985ec362cd46e38d2aaa4a15006f7f.png


当在64位机器下就是8个字节大小。


64a00371f6104c9497b33e66f6a646e1.png


在创建变量的时候,我们就是考虑要存放数据的大小才考虑使用不同的变量类型的,那创建指针变量时因为指针的大小都相等,我们是不是不用考虑指针类型,随便一个就可以使用呢?


int main(void)
{
  int a = 0X11223344;//0X开头是16进制的数
  int* p = &a;
  *p = 0;
}

8adf1402f9eb46ffac4041415927265d.png


我们通过监视内存可以看到a的内存已经存储到机器中去了,当我们使用*p = 0时将改变a的内容。


6a79a75903424c4eadd6f69b5551eb3a.png


成功将a中的值改成0。


但是当我们没有正确对应的使用变量时,将会怎样呢?


int main(void)
{
  int a = 0X11223344;//0X开头是16进制的数
  char* p = &a;
  *p = 0;
}


现在我们使用char类型的指针改变a的值,会不会成功呢?

e36f25935e294bda9514faf43b03c9c7.png

d65863e921da4fbd8f1fbbcca6d49539.png


当我们使用char*指针时只改变了a中的一个字节的值 。我们就可以推出来:int*的指针解引用可以访问4个字节,char*访问可以访问1个字节。


我们就可以总结出:指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)。


int main(void)
{
  int a = 0X11223344;
  int* p = &a;
  char* pa = &a;
  printf("%p\n", p);
  printf("%p\n", p + 1);
  printf("%p\n", p);
  printf("%p\n", p + 1);
  return 0;
}

c021635a8dc4464794f3dbf0375cc89f.png


a 的地址是007AF8FC,当int*加1后a的地址跳过了四个字节,当char*加1后a的地址跳过了1个字节。


我们可以总结出:指针类型决定指针+/-操作时的步长。


野指针


当我们在创建一个指针变量时或者在使用一个指针变量时,未初始化指针或丢失了指针指向的内容时,我们把这种指针叫做野指针。


int main(void)
{
  int* p;
  *p = 20;
  printf("%d\n", *p);
  return 0;
}


当我们没有初始化指针时,指针指向的内容是随机的,我们在用指针赋值或改变时,会对计算机中未知的东西改变,引起程序的bug。像形成野指针的原因有很多:


1.指针未初始化


int main(void)
{
  int* p;
  *p = 100;
  return 0;
}


2. 指针越界访问


int main(void)
{
  int arr[10] = { 0 };
  int* p = arr;
  int i = 0;
  for (i = 0; i <= 11; i++)
  {
    *(p++) = i;
  }
  return 0;
}


指针指向数组的第一个元素,随着for循环指针依次向后指向数组中的元素,当最后一次移动时已经超过数组的长度,指针就变成野指针。


3.指针指向的空间释放


int* test()
{
  int a = 10;
  return &a;
}
int main()
{
  int *p = test();
  printf("%d\n", *p);
  return 0;
}


指针指向函数中a的地址,但是在出函数时a的空间将被销毁,所以指针也变成野指针。


所以我们一定不要让指针变成野指针,当我们在不知道怎么用指针的时候,可以使用类似int*p = NULL来定义或重赋值。


那如何避免野指针呢?


1.指针的初始化


2.小心指针越界


3.指针指向空间释放,及时用NULL


4.避免返回局部变量的地址


5指针使用之前检查有效性


指针运算


指针的运算分为三种:


1.指针+/-整数


2.指针-指针


3.指针的关系运算


指针+/-整数一般运用在数组中,我们使用数组时会运用下标,我们也可以用指针代替


int main()
{
  int arr[10] = { 0 };
  int* p = &arr[0];
  int i = 0;
  int sz = sizeof(arr) / sizeof(arr[0]);
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *p);
    p++;
  }
  return 0;
}


当p+1时指针就会指向数组中的下一个元素。


指针-指针


int main(void)
{
  int arr[10] = { 0 };
  printf("%d\n", &arr[9] - &arr[0]);
  return 0;
}

ed049f9c6d5642029600e7167f36d3f8.png


这个数组的大小为9,当第一个数组元素地址减去最后一个数组元素的地址就是9。


说明指针-指针得到的绝对值就是指针和指针之间的元素个数。前提是两个指针指向同一个空间。


指针的关系运算实际就是比较指针的大小,但是指针的比较是有规律的。第一个代码是正确的,第二个代码是错误的。


#define N_VALUES = 5
for (vp = &values[N_VALUES]; vp > &values[0];)
{
  *--vp = 0;
}


#define N_VALUES = 5
for (vp = &values[N_VALUES - 1]; vp >= &value[0]; vp--)
{
  *vp = 0;
}


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


指针和数组


指针和数组之间是什么关系呢?指针变量就是指针变量,不是数组,指针变量的大小就是4/8个字节,专门是用来存放地址的。数组不是指针!!!


联系:数组名其实就是数组首元素的地址,数组名==地址==指针。


先看一个例子:


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


数组元素的地址都是连续的,之间差一个数组类型的大小。


b458d7fcd53f4a25815ce2374055462b.png


当int *p = arr时,arr[i] = *p + i,指针就可以代替其中的数组


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

93f4f934576d445b991f57ed7a3a4a2d.png


数组名是数组首元素的地址,但是有两个例外:sizeof(数组名)和 &数组名。


二级指针


我们刚才说的都属于一级指针,一级指针可以存放地址,当创建指针时指针也会占用4/8字节的内存,所以指针也会有地址,这时候我们如果需要这个一级指针的地址就需要一个指针类型的指针进行接收(二级指针),所以二级指针可以理解为指针的指针。


int main(void)
{
  int a = 10;
  int* p = &a;
  int* pp = &p;
  printf("%d\n", a);
  return 0;
}


p是一级指针变量,指针变量也是变量,变量是在内存中开辟空间的,是变量就有地址。pp就是二级指针变量,二级指针变量就是用来存放一级指针变量的地址。


指针数组


我们之前学过一些类型的数组,比如整型数组,字符数组等等,这些数组就是存放一些数据类型的。那顾名思义指针数组就是存放指针的数组,数组元素都是指针变量。


#include<stdio.h>
int main(void)
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
  int* parr[] = { arr1,arr2,arr3 };
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
    {
      printf("%d ", *(*(parr + i)+j));
    }
    printf("\n");
  }
  return 0;
}

1794efffcc5d47a0bc7e87b0cd06bd0d.png


因为数组名就是数组首元素的地址,如果有多个数组,我们就可以将多个数组放入指针数组中进行处理,将这些数组一一打印出来,也可以使用parr[i][j]来打印数据。

目录
相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
51 0
|
5天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
20 2
|
2月前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
2月前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
2月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
2月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
2月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
2月前
|
C语言
C语言指针(3)
C语言指针(3)
14 1
|
2月前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
20 0