C语言——指针初阶

简介: C语言——指针初阶

1. 指针是什么

指针是什么?

在 C 语言中,指针是一种特殊的变量,能够存储另一个变量的内存地址。指针变量可以用来访问、修改存储在内存中的数据。

将一个变量的地址存储在指针变量中,可以通过解引用操作符(*)来访问指针所指向的变量的值。例如,可以通过以下方式声明和使用一个整型变量和一个指向该变量的指针:

在这个例子中,我们定义了一个名为 a 的整型变量,它的值为 10。然后,我们定义了一个指向 a 的指针变量 p,使用地址运算符 & 来获取 a 的地址,并将该地址存储在指针 p 中。最后,我们通过解引用操作符 * 来输出指针 p 所指向的变量的值,即输出了变量 a 的值 10。


指针理解的2个要点:


1. 指针是内存中一个最小单元的编号,也就是地址


2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量


我们可以用下图来理解内存:  

  • 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
  • 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。

指针的大小:

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

在32位机器下指针大小都是4字节

在64位机器下指针大小都是8字节

总结

指针变量是用来存放地址的,地址是唯一标示一个内存单元的。

指针的大小在32位平台是4个字节,在64位平台是8个字节

2. 指针和指针类型

这里我们在讨论一下:

指针的类型

我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?

准确的说:有的。

char* 类型的指针是为了存放 char 类型变量的地址。


short* 类型的指针是为了存放 short 类型变量的地址。


int* 类型的指针是为了存放 int 类型变量的地址。


那指针类型的意义是什么?


作用一:


如果用int*类型的指针pa来接收a的地址,当改变*pa,a的4个字节中的内容都发生了改变:


如果用char类型的指针pa来接收a的地址,当改变*pa,只有一个字节中的内容发生了改变:

int*的指针解引用访问4个字节,char*类型指针解引用访问1个字节

总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

左用二:

观察下面代码:

我们发现:

int*类型的pa加1,地址向后增加了4位

char*类型的pb加1,地址向后增加了1位

总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。


2.1 指针+-整数

在C语言中,指针可以进行加减整数的操作,这个操作的含义是在指针所指向的地址上加上或减去一个整数量,从而实现指针位置的移动。

以下是一些示例代码:

在这个例子中,我们定义了一个整型数组a和一个字符型指针s。

对于整型指针p,它指向数组a的第一个元素,并且可以使用指针加法将p加1,指向数组a的第二个元素。

对于字符指针s,它指向一个字符串的第一个字符,并且通过指针加法将s加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++)
  {
    *(p + i) = i;
  }
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(p + i));
  }
}

这段代码中,使用指针变量p直接访问数组元素,相当于p + i所指向的内存位置就是数组arr[i]的地址。在这个地址上进行赋值操作,实现了对数组值的修改。同时,使用指针加法能够更方便地对数组元素进行遍历。


需要注意的是,指针加减整数的操作有时会越过数组的边界,访问到不合法的内存位置。因此,在进行指针加减整数的操作时,应该保证指针不会越界。


2.2 指针的解引用

这段代码演示了如何对一个整型变量n的高/低位进行操作,并观察内存的变化。代码中定义了一个整型变量n,并给它赋予十六进制值0x11223344,然后分别使用指向char类型的指针pc和指向int类型的指针pi对n进行操作。


首先,将指针pc指向n,并将它转换为指向char类型的指针。这样,pc能够对n高/低位进行逐一操作,因为char类型只占用一个字节。接着,将pc指向的第一个字节赋值为0,这将会将n的最低字节设为0,其他字节不变。


然后,通过指针pi对整型变量n进行操作。因为pi是指向整型变量n的指针,所以它可以对整个n进行操作。将pi指向的值赋值为0,这将会将整个n的值设为0。


需要注意的是,在对整型变量n的高/低位进行操作时,必须要考虑机器的大小端问题,否则可能会导致数据的读取和写入出现问题。


总结:


指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。


3. 野指针

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

野指针是指没有被初始化或已经被释放的指针,它指向内存中未知的地址或者不可访问的地址。野指针的出现可能会导致程序的崩溃、内存泄漏等问题。


3.1 野指针成因

1.指针未赋初值,或者被赋了一个未知的值,导致指针指向一个不可知的地址。

2.指针已经被释放,但是程序还尝试去访问该指针所指向的内存空间。

代码一:

代码二:

此程序存在返回局部变量的指针问题,即将一个指向局部变量的指针返回给调用者。具体问题在 test() 函数中,函数中定义的 a 变量是一个局部变量,函数执行完毕后,系统会收回这个变量的内存空间,在该函数中申请的内存空间随即被释放。所以 a 变量所处的内存空间在函数执行完毕后已经被释放了,返回的指针 &a 指向的内存空间已经不再是该变量的内存空间,因此这个指针是无效的,称为“悬挂指针”。


3.指针越界,指向了不属于该指针所指向内存空间的位置。


代码一:


代码二:

这段程序中存在指针p越界的问题,导致arr数组发生了内存越界的现象,它的具体问题在 “for” 循环中的语句 *(p++) = i;循环次数超过了数组的长度,因此当指针 p 指向数组 arr 区域外时就会出现非法访问,是一种常见的野指针错误。


更改方法可以是检查循环范围,修改为循环10次即可.


3.2 如何规避野指针

1. 指针初始化

在定义指针变量时,最好将其初始化为空指针,防止指针未初始化就被使用,造成意料之外的行为.

2. 小心指针越界


要避免指针越界,应该注意以下几点:


1.确认数组长度:在定义数组时,要确保数组长度足够,避免出现数组访问越界的情况。


2.检查数组下标:在使用数组下标时,要确保数组下标不会越界,比如当下标小于 0 或大于等于数组长度时,就可能出现数组越界的情况。


3.指针的作用域:由于指针变量的生命周期很长,要确保指针变量所指向的内存空间还没有被释放,避免因为指针变量所指向的内存空间已经被释放导致的指针越界的问题。


4.指针的使用范围:在使用指针时,要确保指针变量所指向的内存空间的有效范围,避免访问指针所指向的内存空间以外的内存,造成指针越界的情况。


5.指针的类型:当使用指针访问一个内存块时,要确保指针的类型和所指向的内存块的类型匹配,避免出现类型不匹配导致的访问越界问题。


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


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


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


在使用指针变量之前,最好先检查指针是否为空,以避免空指针引发的异常或错误


4. 指针运算

4.1 指针+-整数

这部分内容和2.1一样,补充新的例子:

通过将vp指向数组第一个元素的地址(&values[0]),可以使用for循环对整个数组进行遍历。


for循环中的语句使用了指针加法,每次使vp指向下一个数组元素。在数组元素前加上*,可以取得vp所指向的数组元素的值,并将这个值赋为0。通过这样的方式,vp指向了数组中的每一个元素,并将它们都设为了0。注意,在循环中没有定义循环变量,条件检查和迭代都在了循环语句中,这也是一种常见的for循环写法。


需要注意的是,循环中的条件判断是vp < &values[N_VALUES],在指针比较中,指针实际上是对应一个内存地址,比较的是这个地址的大小,它的判断依据是地址升序排列,所以vp指向的地址在比较时要小于&values[N_VALUES],否则就越界了。


4.2 指针-指针

分析下面代码的结果:

在这段代码中,计算了数组中两个元素的地址差,通过指针相减的方式得到了它们之间的元素个数。由于数组元素在内存中是连续存储的,它们之间的地址差就是它们之间的元素个数。


指针-指针运算的前提条件:

只有当指针指向同一数组的元素时,才能进行指针相减运算。

4.3 指针的关系运算

代码简化, 这将代码修改如下:

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

标准规定:

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


5. 指针和数组

指针和数组之间的联系:

数组中,数组名其实是数组首元素的地址,数组名 == 地址 == 指针

当我们知道数组首元素的地址的时候,因为数组是连续存放的,所以通过指针就可以遍历访问数组

6. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?

这就是二级指针 。

二级指针的地址存放在三级指针里:


7. 指针数组

指针数组是一个包含指针的数组,即数组的每个元素都是指向某种类型的指针。指针数组可以很方便地表示和操作一组指针,用于存储和访问多个数据结构。

int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "hello world";
  char arr3[] = "cuihua";
  //指针数组
  char* parr[] = { arr1,arr2,arr3 };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%s\n", parr[i]);
  }
  return 0;
}


下面是int型指针数组使用例子:

int main()
{
  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 };
  int i = 0, j = 0;
  for (i = 0; i < 3; i++)
  {
    for (j = 0; j < 5; j++)
    {
      //printf("%d ", parr[i][j]);
      printf("%d ", *(parr[i] + j));
    }
    printf("\n");
  }
  return 0;
}

这段代码演示了一个包含指针数组的简单例子。在这个例子中,我们定义了三个整型数组 arr1、arr2 和 arr3,然后创建了一个指针数组 parr。parr中的每个元素都是一个指向整型数组的指针,即 arr1、arr2 或 arr3。


在这个例子中,我们可以通过 *(parr[i] + j) 来访问第 i 个指针元素指向的整型数组中的第 j 个元素。


这种方式与使用 parr[i][j] 的效果是一样的,只是表达方式略有不同。这里使用指针和数组下标的方式是指针运算的一种形式,即先使用 parr[i] 获取指向整型数组的指针,然后使用 *(parr[i] + j) 访问指针所指向的数组。


目录
相关文章
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
2月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
179 9
|
2月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
64 7
|
3月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
267 13
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
3月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
213 3
|
3月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
3月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
72 1
|
3月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
3月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。

热门文章

最新文章