写在前面:本文将对指针的基本应用以及使用指针时常见的错误做出讲解。由于博主现在刚刚学到指针,对指针的理解有不正确的地方,望读者指正。
1. 指针概念介绍
1.1 何为指针?为什么会出现指针?
先抛出结论:指针就是地址,地址就是指针。在口语中,指针也是指针变量。
我们知道,计算机有很大的内存,当一个变量创建时,计算机首先得把它存起来,于是计算机在内存中开辟一块空间来存放创建的变量。但是把变量存起来后,计算机怎么使用它?或者说怎么找到它?直接找的话岂不是大海捞针,效率太低。为了更快的找到它,计算机把内存分成一个个小的空间(最小的内存空间大小为一个字节),并且对每个空间进行编号。
1.2 计算机是怎样对内存单元编号的?
我们常说32位系统,64位系统。在32位系统中有32根地址线,64位同理。每根地址线能产生高电平与低电平,计算机把高电平记成1,低电平记成0,那32根地址线能产生多少个二进制序列?总共产生2的32次方个序列,计算机把一个二进制序列作为一个内存空间的编号,我们给这个编号起了个形象的名字:地址,通过地址我们就能找到变量并且使用它。但我们看到的地址不是一串长长的二进制序列,计算机把4个二进制数转化为一个十六进制数,用8个十六进制数来表示地址。总结一下,计算机根据物理电信号产生内存的地址。
1.3 指针的两种概念
回到刚才的结论,可以这样理解指针,计算机通过指针指向的地址,找到地址中存放的变量,一个指针是指向一个变量的,所以我们说指针就是地址,地址就是指针。当然,指针是一串二进制序列,我们把指针存储到一个变量中,这个变量就叫做指针变量,这是口语中常说的指针变量。
2. 指针和指针类型
指针在口语中被叫做指针变量,既然是个变量,那这个变量的类型是什么?定义它的语法是怎样的?不同类型的大小一样吗?
刚才说指针是一串二进制序列,在32位系统中指针有32个比特位,也就是4个字节,因此我们要存储指针只需要用4个字节大小的变量(在64位系统中,指针变量的大小为8个字节)。
int n = 10;
int* p = &p;
先来讲讲要怎么初始化指针变量,首先要先有一个变量(指针得指向一个变量),创建相应类型的指针变量,把变量的地址取出,将地址存到指针变量中。通过打印p我们可以看的n的地址。 地址与打印结果相同,很好的说明指针变量p中存放的是n的地址。
指针变量还有其他类型,但指针变量的类型必须根据指针所指向变量的类型决定。
char*
short*
float*
double*
...
指针变量的类型有什么意义?指针变量的类型与其所指向的变量类型不一致会发生什么?
2.1 不同指针+ -整数的结果是否相同?
指针+ -整数是什么意思?指针就是地址,对地址加1不是对地址中的二进制序列加1,我们希望加1能使指针能指向下一个变量,那地址也就要对应着下一个变量,所以加1应该跨过一个变量的字节大小,使指针指向下一个变量。当创建一个整形变量时,计算机在内存中开辟了四个字节的空间。那指针变量加1就应该跨过这个变量指向下一个变量,所以整形指针加1是对地址加4。但非要让字符指针去指向一个整形变量,字符指针加1只会对地址加1(一个字符的大小)。在使用指针时,指针加1不能正确指向下一个变量的地址,我们还怎么使用指针,指针就没意义了嘛不是,,所以指针类型一定要与变量类型对应。
总结:不同指针类型加1减1的步长不同。
2.2 不同指针的解引用
我们先得明白指针的解引用操作。指针指向一个地址,通过地址我们能访问到地址所对应的变量,*解引用操作符就能通过地址访问变量。
*pi = 0,表示通过pi找到n变量,把n置成0,整形指针就能很好地完成这个操作。 但如果我们创建的是字符指针呢? 只有两个数字被置成了0,这里的两个十六进制数字代表着8个二进制数字,也就是1个字节。我们发现字符指针只访问了一个字节大小的空间,把8个二进制数置成0。我们可以说整形指针的访问权限有4个字节,字符指针的访问权限有1个字节。
综上我们得出结论:不同指针类型解引用的访问权限不同。
3. 野指针
之前我说,在创建指针变量之前要先创建一个变量,让指针指向它,如果指针没有指向一个变量又对指针解引用会出现什么呢?这时的解引用就算作非法访问了,因为没有初始化指针,指针里的地址是随机的,去解引用一个你没申请过的内存,当然是不合法的。
总的来说野指针就是:一个指向地址是随机的,不可知的指针。
3.1 野指针是怎样产生的?
刚才我说的指针变量未初始化就是野指针产生的一种方式。
当指针越界访问时,也会产生野指针
在变量内存释放之后使用指针也会导致野指针 显然,解引用指针出现了错误,释放变量的内存后是不能使用之前指向它的指针的。
3.2 如果避免野指针的产生?
- 首先,对指针初始化
- 使用数组时注意不要越界访问
- 指针指向空间释放时及时置空指针
- 避免返回局部变量的地址(因为局部变量销毁时,再访问这个空间就是非法的)
- 在使用指针前检查
int main()
{
int *p = NULL;
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0; }
4. 指针与数组
之前的博客中我说过,数组名是数组首元素地址,在创建数组指针时我们就能这样
int arr[5] = { 0 };
int* p = arr;
写代码验证一下这种写法是否正确。 由于数组名是数组首元素地址,我们就能通过指针访问数组
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("p + i = %p == arr[i] = %p\n", p, &arr[i]);
}
return 0;
}
可以看到,p + i 就是数组中下标为i的元素地址。我们能运用指针初始化数组,打印数组。
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
这是指针最基本的运用。
5. 指针运算
5.1 指针+ -整数
之前说过,指针+ -整数使其指向下一块空间,步长由指针变量的类型决定。
5.2 指针-指针
指针-指针得到的是两个指针之间的变量个数,准确的说是指针移动的距离,因为指针-指针可能是负数,注意得到的结果可不是地址之间相差的字节数。
5.3 指针之间的关系比较
先看下这串代码
int arr[5] = { 1,2,3,4,5 };
int* p = &arr[5];
for ( p = &arr[5]; p > &arr[0]; )
{
*(--p) = 0;
}
p在循环开始时指向数组下标为5的元素,但数组元素最大下标为4,p就指向下标为4的元素后面那一块空间,虽然数组没有申请这块空间,但是不对指针解引用是不会造成越界访问的。p的地址大小大于arr[0]的地址大小,进入循环,p先自减再将所指向的变量置成0。 …
最后p指向数组的第一个元素,p > arr[0]的地址,p自减,指向arr[0],把数组第0个元素置成0。p指向arr[0],等于arr[0]的地址,不满足循环条件,结束循环。
我们还能将代码优化,增加它的可读性
int arr[5] = { 1,2,3,4,5 };
int* p = &arr[4];
for ( p = &arr[4]; p >= &arr[0]; p-- )
{
*p = 0;
}
刚开始指针指向数组第四个元素,并且满足大于等于数组第0个元素的地址,将p所指向的元素置成0。 …
之后p会将数组第0个元素置成0。再判断时,由于p自减,p指向数组前一个元素,不满足大于等于arr[0]的地址,循环结束。
优化后的代码看起来更容易读懂,但在c语言标准中这样写是错误的。
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向数组第一个元素前面那个内存位置的指针比较。
这个标准读起来非常拗口,所说的意思就是我们优化前的代码在语法中是支持的,优化后的代码是标准不支持的。但这个规定有点过于严谨,其实优化后的代码在大多数编译器上是能运行的。之所以说这个标准,是为了如果在使用数组指针时,程序出错,要先检查是不是你的代码不符合标准规定。
6.指针数组
我们知道,数组有整形数组,字符数组,浮点型数组…那有没有指针数组呢?答案是有的。我们可以把变量的地址存储在指针数组中
int main()
{
int a = 0;
int b = 1;
int c = 2;
int* arr[3] = { &a,&b,&c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
通过指针数组解引用数组中的地址,接着怎么使用就看具体场景了。这里只将指针数组的创建与初始化,更复杂的运用在指针进阶中我会再说的。
7.二级指针
我们知道,指针是一串二进制序列,在32位系统中大小为4个字节,既然指针是一个变量,我们就能把指针再存进一个变量中,这个存放指针的变量我们叫做二级指针。
总结:二级指针是存放指针的变量。
说两个简单的应用。
int a = 0;
int b = 1;
int* pa = &a;
int** paa = &pa;
*paa = &b;
对二级指针解引用找到一级指针pa,我们可以更改pa存放的地址,上面代码将pa存放地址改成了b的地址。
int a = 0;
int b = 1;
int* pa = &a;
int** paa = &pa;
**paa = 10;
printf("%d",a);
对二级指针paa解引用两次等于将一级指针pa解引用一次,都能找到a变量并访问其内存。
这次对指针的讲解只是初阶,对指针的应用都还十分简单。关于指针的更深入的内容在博主学完指针进阶后会整理的。博主刚学c语言,对指针的理解相对粗浅,如果文章有讲解不对的地方还望读者们谅解。最后,如果觉得这篇文章对你有帮助的话,不妨动动小手点个赞吧。
|