【C语言】第六章-指针

第六章 指针

第1节

什么是指针

  在计算机科学中,指针时编程语言中的一个对象,利用地址,它的值直接指向存在在电脑存储器当中的另一个地方的值。简而言之,指针是一个存储变量地址的变量。在32位系统上,地址是32个0或1组成的序列,所以要求用4个字节来存储,所以指针在32位机器上是4个字节的大小。 因此推而广之,在64位机器上,指针是8个字节大小的。

指针和指针类型

指针的基本使用

  我们可以使用int* num;的语句来定义一个指针类型的变量,并直接在其后进行赋值,但是要注意我们要赋给指针变量的一定是一个变量的地址,此时我们就需要用到&取地址符。

  而如果我们我们想要取到指针中的值时则需要用到*解引用操作符。以下的例子展示了指针的基本使用。

1
2
3
4
5
6
7
8
9
10
11
12
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int num = 10;
int*p = &num;//创建指针变量并让其指向num变量
printf("%d\n", p);//打印指针p所指向的内存地址
printf("%d\n", *p);//打印p所指向变量的值
system("pause");
}

  但是我们不能将非法的内存地址赋给指针变量。那么什么是非法的内存地址呢?所谓非法的内存地址即我们并未向系统申请过的内存地址。我们每定义一个变量系统都会分给我们一块内存空间以便我们存放数据,而如果我们对指针直接进行赋值,那么我们就无法保证赋给指针的变量是合法的,此时如果再访问指针则是未定义行为(在C标准中并未明确说明的操作行为),这是十分危险的。比如说我们使用这样的语句int* p = 0x100;则是十分不安全的写法。因此对于指针我们不要对其进行乱赋值,防止其指向非法的内存

  为了防止防止指针指向不合法的内存,对于我们定义了但暂时不用的指针变量,出于安全起见,我们可以将其暂时赋值为NULL。如int* p = NULL

指针常见注意事项

  1、不可将字面值常量的地址赋给指针。如int* p = &10这样的写法就是错的。

  2、不可给指针直接复制内存地址,以免造成访问非法内存的错误。

  3、对于暂时不使用的指针将其值赋为NULL防止造成野指针,发生意想不到的麻烦。

  4、在32位机器下指针的大小为32位,4个字节,指针具体大小视系统而变。

指针和数组名

  之前数组讲解时有提到过数组名就是元素的首地址,那么我们是否可以理解为数组名就是一个指向首元素的指针呢?其实这么理解实在欠妥,虽然数组和指针十分相似,有着千丝万缕的联系,但数组可千万不能和指针一概而论。
  在C语言中,数组和指针可以进行转换,这为我们很多操作行了方便,但数组和指针永远属于两个不同的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int arr[] = { 1,2,3,4 };
int* p = arr;
printf("%d\n",sizeof(arr));//数组的大小
printf("%d\n", sizeof(arr + 0));//数组隐式转换为指针,参与运算
printf("%d\n", sizeof(p));//一个和数组指向相同元素的指针
system("pause");
}

  在这个例子中我们很明显就能看出指针和数组的区别,当我们取数组的大小时,会进行计算得出数组所有元素大小之和,而我们取指针的大小时,永远都只会是4,尤其是我们在打印arr + 0的大小的时候由于数组名不能参与运算,于是数组名隐式转换为指针后才参与运算,从结果是4也能看得出来此时的arr + 0已经是一个指针了,哪怕它所指向的内存地址并没有发生改变。

  初次之外我们可以利用数组名和指针可以相互转换的特点和指针可以参与运算的特点利用指针来访问数组中的元素。

1
2
3
4
5
6
7
8
9
10
11
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int arr[] = { 1,2,3,4 };
printf("%d\n", *(arr + 1));
printf("%d\n", arr[1]);
system("pause");
}

  在这个例子中我们可以看到两种取数组中元素的方式都可得到数组中的第二个元素,因此我们不难得出结论*(arr + 1) <=> arr[1],即这两种方式是等价的。因此我们很多情况下可以利用指针灵活使用数组。

1
2
3
4
5
6
7
8
9
10
11
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int arr[] = { 1,2,3,4 };
int* p = arr + 1;
printf("%d\n", p[-1]);//等价于*(p - 1);
system("pause");
}

  尤其是对于一个指针,下标的方式依然使用,并且不同于数组,在指针的下表中甚至可以使用负数作为下标。

指针运算

指针 +- 整数

  指针加减一个整数等同于跳过了几个元素,而绝对不是内存地址加减整数,这点我们之前的例子中已经深有体会了,这里不再详细说明了。

指针 - 指针

  指针虽然不能相加,但是可以相减,指针相减计算的是两个指针间所偏移的元素个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int arr[] = { 1,2,3,4 };
int* p1 = arr + 1;
int* p2 = arr + 2;
printf("%d\n", p2 - p1);//得出p2与p1间相偏移了一个元素
printf("%d\n", p1 - p2);//向左偏移则为负数
system("pause");
}

指针的关系运算

  指针间也可也进行普通的关系比较,比如指针相等,指针大小等等,其中指针大小比较时,指针相比于另一个指针向右便宜则为大,向左则为小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int arr[] = { 1,2,3,4 };
int* p1 = arr + 1;
int* p2 = arr + 1;
int* p3 = arr + 2;
printf("%d\n", p1 == p2);//得出p2与p1间相偏移了一个元素
printf("%d\n", p3 > p2 ? 1 : -1);//向左偏移则为负数
system("pause");
}

  不过在此值得说明的是:只有在连续的一段内存上指针的相减以及指针的关系运算才有意义,否则都是无意义的

指针微进阶

  C语言指针博大精深,有着很多的门路,在此我们挑其中最好理解的两点进行讲解。

数组指针

1
2
3
4
5
6
7
8
9
10
11
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>

int main()//主函数,函数入口
{
int arr[] = { 1,2,3,4 };
printf("%p\n", arr);
printf("%p\n", &arr + 1);
system("pause");
}

  从运行结果大家不难发现,&arr + 1arr的结果要多了16个字节,也就正好是整个数组的长度。也就是说&arr我们取到了整个数组的指针,在我们对这个指针进行+1时,指针跳过了整个数组,于是我们称指向整个数组的指针为数组指针。

常量指针和指向常量的指针

  常量我相信大家都不陌生,也就是在初始化后无法再进行改变的量,但是在指针中常量也分为常量指针和指向常量的指针两种。

  指向常量的指针:对指针所指向的变量无法进行更改的指针。

         const int* p = &num

         int const* p = &num(等价)

  这样的指针在我们不想让指针修改一个值却又方便传入的时候经常使用。

  常量指针:指针一旦指向某个内存地址无法再更改其指向。
         int* const p = &num
  这样的指针与数组名十分类似,自身的值都无法进行改变,但是在强调一遍数组和指针是两种数据类型,不可搞混。

  剩下指针更为高阶的知识和用法将在C语言进阶篇进行讲解。

-------------本文结束感谢您的阅读!-------------
记录学习每一分,感谢您的赞助