1. 指针的概念
1.1 内存
1.2 指针是什么
1.3 指针变量的大小
2. 指针类型
2.1 指针类型的意义
2.2 指针类型意义的疑问
3. 野指针
3.1 野指针的概念
3.2 野指针的成因
3.3 如何规避野指针
4. 指针运算
4.1 指针 + 或 - 整数
4.2 指针 - 指针
4.3 指针 - 指针的用途
在前面的文章中我详细的讲解了各种操作符的知识,不知道小伙伴们在了解完后有没有自己去练习呀!那么今天这篇文章小编就带大家来了解指针的相关知识。包括什么是指针、指针类型、指针运算等知识点。
1. 指针的概念
在学习指针之前,我们首先得理解内存的概念,因为指针是用来访问内存的。
1.1 内存
那么什么是内存呢?内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
而为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址,且每一个内存单元都有一个唯一的编号。如下图:
1.2 指针是什么
那么指针到底是什么呢?在C语言中,把 这些编号 或 这些地址 也叫做 指针。因此 编号 == 地址 == 指针。
但我们口头语中常说的指针,基本上都是指针变量。(指针变量 —> 变量 —> 存放地址的变量)
需要注意的是:我们在写C语言程序的时候,创建的变量、数组等都是创建在内存中的(即在内存中分配空间的),而每个内存单元都有地址,所以变量也是有地址的。
1.3 指针变量的大小
在谈及指针变量的大小之前,小编先来给大家讲讲 地址或者编号是如何产生的呢? ——> 在计算机上,有地址线(物理的电线),通过产生高低电平的信号,转换为数字信号:1/0。在32位的机器中,上面就会有32根地址线;在64位的机器中,上面就会有64根地址线。
因此在32位的机器上,地址是32个0或者1组成二进制序列,那内存中就得用32个bit位(即4个字节)的空间来存储,所以一个指针变量的大小就应该是4个字节。
如果在64位机器上,地址是64个0或者1组成二进制序列,那内存中就得用64个bit位的空间来存储,那一个指针变量的大小是8个字节,才能存放一个地址。
大家可以这样记忆:地址可以类比为身份证,并不会因为身份的不同(存储不同的数据类型时)而改变。而指针变量的大小可以类比为身份证的位数,也不会因为一个人身份的改变(数据类型的不同)而改变大小。
总结:
1. 指针是内存中一个最小单元的编号,也就是地址。
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
3. 指针变量是用来存放地址的,地址是唯一标识一个内存单元的。
4. 指针变量的大小在32位平台是4个字节,在64位平台是8个字节。
2. 指针类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?答案当然是有的!
指针的类型:type* p
其中:
* 说明p是指向的 type类型 的指针变量
type说明:1. p指向的对象的类型
2. p解引用的时候访问的对象大小是sizeof(type)
例如:
char* pc = NULL;
short* ps = NULL;
int* pi = NULL;
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
2.1 指针类型的意义
那指针的类型究竟有什么意义呢?
#include<stdio.h>
int main()
{
char a = 'x';
char* pc = &a;
int i = 10;
int* pi = &i;
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("\n");
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
如上所示例子,当我们对不同类型的指针进行 +1 操作时,其在内存中跳过的字节数是不一样的,char类型的指针pc 进行 +1 操作只跳过了一个字节,而int型的指针进行 +1 操作跳过了4个字节。
也就是说,指针类型决定了指针 +/- 1 操作时的步长,整型指针+1跳过4个字节,字符指针+1跳过1个字节。 +/- n 对于type* p 来说跳过的是:n*sizeof(type)这么多个字节。
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
*pc = 0;
printf("0x%x\n", n);
return 0;
}
在上述的这个例子中,"0x11223344" 是16进制的数字,我们将整型n的地址 强制类型转换 并赋值给了字符类型的指针pc,当指针pc解引用并修改值时,可以看出 n 的值并没有完全被修改,而是只改变了一个字节的值。
即指针的类型决定了,对指针解引用的时候有多大的权限,或者说能操作几个字节。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
总结指针类型的意义:
1. 指针的类型决定了指针向前或者向后走一步有多大(距离)
2. 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
2.2 指针类型意义的疑问
这里也许有一些小伙伴会对第二点有疑问:不是说指针不是4个字节就是8个字节吗?怎么这里又能根据类型来操作字节的个数了? 下面举例来说明:
如上例子,我们前面说的指针不是4字节就是8字节指的是指针本身自己的大小,如上无论是字符指针pc还是整型指针pi,它们在64位的机器上大小都是8字节。
而我们所说的指针解引用能操作几个字节,可以这么理解:对于整型指针pi来说,它解引用后,即 *pi 等同于 变量 i,而变量 i 是整型类型的,整型(int)的大小是4个字节,因此变量 i 能操作4个字节,即 *pi也只能操作4个字节。
3. 野指针
3.1 野指针的概念
何为野指针呢?野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.2 野指针的成因
那为什么会有野指针呢?野指针是怎么来的呢?主要有以下三点原因!
1. 指针未初始化。
如上例子,当局部变量不初始化时,内容会是随机值,此时 p 就称为野指针。
2. 指针越界访问。
我们设定数组的长度为10,但当我们用指针访问数组时,访问时却超出了数组大小的范围。换句话说,当指针p指向的范围超出数组arr的范围时,p就是野指针。
3. 指针指向的空间释放 (这点小编会在后续讲解动态内存开辟的时候跟老铁们探讨,这里就不先进行讲解了)
3.3 如何规避野指针
既然有野指针,那么我们该如何规避野指针的生成呢?小编在这里给大家总结了几点:
1. 指针初始化。
1.1 明确知道指针应该初始化为谁的地址,就直接初始化。
1.2 不知道初始化为什么值,暂时初始化为NULL。
2. 小心指针越界。
3. 指针指向的空间释放,及时置为NULL。
4. 避免返回局部变量的地址。
5. 指针使用之前检查有效性。
4. 指针运算
指针运算又可分为:1. 指针 +或- 整数 2. 指针 - 指针。
4.1 指针 + 或 - 整数
假定这样一个场景:我们设置一个长度大小为10的整型数组arr,要求不使用数组下标的方式访问数组,即不采用arr[0]、arr[1]...这种形式来访问数组。
那该采用什么样的方式呢?毫无疑问肯定是 指针 + 或 - 整数 的方式。代码如下:
#include<stdio.h>
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;
p++;
}
for (i = 0; i < sz; i++)
printf("%d ", arr[i]);
printf("\n\n");
p = arr;
for (i = 0; i < sz; i++)
printf("%d ", *(p + i));
printf("\n");
return 0;
}
如结果所示,采用 指针 + 或 - 整数 的方式也能得到正确的结果。
需要注意的是:
1. arr是数组名,数组名是首元素的地址,所以可以把arr理解为指针,指向数组首元素,故可以写成p = arr。
2. 因为p = arr,所以arr[0] = *p = *(p+0) = p[0]。类推:若p = arr,则arr[i] = *(p+i) = p[i]。
3. arr[i] == *(arr+i) == *(i+arr) == i[arr] (注:[]仅仅是个操作符)
4.2 指针 - 指针
大家先来看看下面两个例子:
上面这个例子说明指针 - 指针结果的绝对值是指针与指针之间的元素个数。
而这第二个例子出错了,是因为数组ch和数组arr并没有执行同一块内存空间,所以两者之间不能相减。
总结:
1. 指针 - 指针得到的数值的绝对值:是指针和指针之间的元素个数。
2. 指针 - 指针运算的前提条件是:指针和指针指向了同一块空间。
4.3 指针 - 指针的用途
我们都知道strlen库函数是用来求字符串的长度的。那当我们自己模拟实现strlen函数时,大多数的小伙伴可能都是设置一个int的变量count来统计字符个数,最终求得字符串的长度。
下面小编就来教大家怎么用指针 - 指针的方式来模拟实现strlen函数,代码如下:
#include<stdio.h>
int my_strlen(char* s)
{
char* start = s;
while (*s) // *s != '\0'
{
s++;
}
return s - start;
}
int main()
{
char ch[] = "abcdef";
int len = my_strlen(ch);
printf("%d\n", len);
return 0;
}
我们只要找到字符串的末尾的地址,再减去字符串的首地址,即可得到字符串的长度。
好了,小编今天就先讲解到这里啦,各位老铁们我们下期再见吧!