C语言精华 - 指针初识篇
Posted 跳动的bit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言精华 - 指针初识篇相关的知识,希望对你有一定的参考价值。
文章目录
前言
在之前的文章中有简单介绍指针,但在C语言初识这个专栏里也不会深入,在未来C语言进阶这个专栏会详细了解
一、指针是什么
官方来说:在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(Points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”,意思是通过它能找到以它为地址的内存单元
#include<stdio.h>
int main()
{
int a = 10;//a占4个字节,1个字节对应1个编号,a有4个地址,如果用4个地址去访问a比较麻烦
int* pa = &a;//&a拿到的是a的4个字节中每一个字节的地址。通过int*类型的pa存储a的地址
*pa = 20;//再通过*解引用操作去访问a
return 0;
}
一个小的单元是多大?以及如何编址?
经过仔细计算和权衡我们发现一个字节对应一个地址是比较合适的
对于32位的机器,假设有32根地址线,那么假设每根地址线寻址产生一个电信号正电/负电(1或0)
假设一个内存单元是1bit - 2^32bit:
如果给每个bit都有一个地址 - 太浪费了。经过平衡后,以一个字节为内存单元,然后分配地址
这里就有2的32次方个地址。 每个地址标识一个字节,那么:
同理64位也是一样
在32位机器上,地址是32个0或者1组成的二进制序列,那地址就得用4个字节的空间来存储所以一个指针变量的大小就是4个字节
在64位机器上,如果有64根地址线,只有一个指针变量的大小是8个字节,才能存放一个地址
小结:1.每个地址标识一个字节    2.指针的大小在32位平台是4个字节,在64位平台是8个字节
二、指针和指针类型
1、指针类型
不同类型的数据交给指针时也要使用不同的指针类型去存储
由以下代码发现:不同的指针类型都是4个字节或8个字节,那么指针类型是否有存在的必要?
#include<stdio.h>
int main()
{
int* pa;
char* pc;
float* pf;
printf("%d\\n", sizeof(pa));//4
printf("%d\\n", sizeof(pc));//4
printf("%d\\n", sizeof(pf));//4
return 0;
}
2、指针类型的意义
先了解一下:1个十六进制位是4个二进制位
0 1 2 3 4 5 6 7 8 9 a b c d e f -> 十六进制位
1 1 1 1 1 1 1 1 -> 二进制位
8 4 2 1 = 15 = f
所以说4个二进制位是1个十六进制位,1个字节是2个十六进制位
#include<stdio.h>
int main01()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
//--------------------------------------------------
int main02()
{
int a = 0x11223344;
char* pc = &a;
*pc = 0;
return 0;
}
这里使用int和char类型的指针存储a的值。调试发现:同一个值,被不同类型指针存储后,在解引用操作时,它们的访问权限不一样
为了能够更直观的认识指针类型的意义,再看以下代码:
#include<stdio.h>
int main()
{
int arr[10] = {0};
int* p1 = arr;
char* p2 = arr;
printf("%p\\n", p1);
printf("%p\\n", p1 + 1);
printf("----------分割线---------\\n");
printf("%p\\n", p2);
printf("%p\\n", p2 + 1);
return 0;
}
这里使用int和char类型的指针存储arr数组首元素的地址。运行发现:被不同类型指针存储后,统一加1后的结果不同
小结:1、指针类型决定了指针解引用的权限有多大    2、指针类型决定了指针走一步,能走多远(步长)**    **3、int类型指针+1跳过4个字节;char类型指针+1跳过1个字节;数组类型指针+1跳过过1个数组(暂时不了解)
3、简单应用
/***********************************************************************
目的:借助指针输出将数组里的元素1 - 10输出
分析:使用int*类型的指针存储数组首地址,并利用解引用操作符
平台:Visual studio 2017 && windows
***********************************************************************/
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
int* p = arr;
for(i = 0; i < 10; i++)
{
printf("%d ", *(p++));
}
return 0;
}
输出结果:
相反,如果使用char*类型的指针去存储:每次加1后所走的步长不同
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
char* p = arr;
for(i = 0; i < 10; i++)
{
printf("%d ", *(p++));
}
return 0;
}
输出结果:
三、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。以下代码列举几个野指针
:
1、野指针的产生
#include<stdio.h>
//1、
int main01()
{
int* p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p = 20;//非法访问内存
return 0;
}
//2、
int main02()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for(i = 0; i <= 10; i++)//当i = 10时,此时的p就是野指针,再去解引用的话就是非法访问内存
{
*p = i;
p++;
}
}
//3、
int* test()
{
int a = 10;
return &a;
}
int main03()
{
int* p = test(); //a是局部变量,一旦test函数执行完成,test函数被销毁,变量a被释放。p虽然拿到了a的地址,但是指向的空间就是未知的
*p = 20;//非法访问内存
return 0;
}
小结: 1、指针变量未初始化的情况是野指针
2、指针指向的数组越界后是野指针
3、指针去接收一个函数的局部变量的返回地址时,这个程序结束,函数销毁,指针指向的空间被释放,也会导致野指针
2、如何规避野指针
#include<stdio.h>
//1、
int main01()
{
//`1.明确知道要初始化的值时:
int a1 = 10;
int* p1 = &a1;
//2.不明确知道要初始化的值时
//养成好的习惯,定义变量的时候对变量初始化为0
int a = 0;
//而指针可以初始化为NULL(空)
int* p = NULL;
return 0;
}
//2、
int main02()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for(i = 0; i < 10; i++)//C语言本身是不会检查数组越界的,要保证数组不越界
{
*p = i;
p++;
}
}
//3、
int* test()
{
int a = 10;
return &a;
}
int main03()
{
int* p = test();
p = NULL;//将p置为空指针
*p = 20;//err
return 0;
}
//4、
int main04()
{
int* p = NULL;
if(p != NULL)
*P = 20;
return 0;
}
总结: 1、初始化变量
2、注意不要数组越界
3、指针指向的空间释放后及时置为NULL
4、指针使用之前检查有效性
四、指针运算
1、利用指针进行简单运算
#define N_VALUES 5
#include<stdio.h>
//1、使用指针运算将数组从前往后被初始化为0
int main01()
{
float values[N_VALUES];
float* vp;
for(vp = &values[0]; vp < &values[N_VALUES];)//指针的关系运算
{
*vp++ = 0;//指针加减运算
}
return 0;
}
//2、使用指针运算将数组从后往前被初始化为0
int main02()
{
float values[N_VALUES];
float* vp;
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
return 0;
}
//3、将main02代码简化一点
int main03()
{
float values[N_VALUES];
float* vp;
for(vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
{
*vp = 0;
}
return 0;
}
观察main02和main03
main02和main03的功能是一样的,都能初始化数组为0,且main03相对来说更容易理解
实际上main03在绝大部分的编译器上是可以完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行
标准规定:允许指向数组元素的指针与指向数组最后1个元素后面的那个内存位置的指针比较,但是不允许与指向第1个元素之前的那个内存位置的指针进行比较
#include<stdio.h>
//1、利用指针运算打印数组元素
int main01()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;//将数组的第1个元素的地址交给p
int* pend = arr + 9;//将数组的最后1个元素的地址交给pend
while(p <= pend)//利用数组的地址 -> 从低到高
{
printf("%d ", *p);//1 2 3 4 5 6 7 8 9 10
p++;
}
return 0;
}
//2、指针相减
int main02()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//指针相减运算 -> 指针-指针 = 2个指针之间的元素个数 -> 但前提是2个指针指向同一块空间
printf("%d\\n", &arr[9] - &arr[0]);//9
//细想一下,其实指针+指针其实是没有意义的
return 0;
}
2、简单应用
/***********************************************************************
目的:使用指针与指针的运算模拟strlen
分析:找到目标字符串\\0的位置和首元素地址相减即可
平台:Visual studio 2017 && windows
*************************************************************************/
#include<stdio.h>
int my_strlen(char* str)
{
//备份1份首地址
char* start = str;
while(*str)
{
str++;
}
return str - start;
}
int main()
{
int len = my_strlen("abc");//传过去的仅仅是a的地址
printf("%d\\n", len);
return 0;
}
五、指针和数组
1、指针和数组的关联
#include<stdio.h>
int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for(i = 0; i < 10; i++)
{
//printf("%p <==> %p\\n", &arr[i], p + i);//&arr[i] <==> p + i
*(p+i) = i;
}
for(i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
2、更深层次的了解指针和数组
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
//打印第3个元素:
printf("%d\\n", arr[2]);
printf("%d\\n", 2[arr]);
printf("%d\\n", p[2]);
//arr[2] --> *(arr + 2) --> *(2 + arr) --> 2[arr]
//p[2] --> *(p + 2)
//可推出:
//arr[2] <==> *(arr + 2) <==> *(p + 2) <==> *(2 + p) <==> *(2 + arr) <==> 2[arr]
return 0;
}
六、二级指针
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;//此时pa指向变量a的地址时,pa为一级指针变量
int** ppa = &pa;//同时pa也是个变量||地址,此时ppa去指向pa的地址时,ppa为二级指针变量
int*** pppa = &ppa;//此时pppa为三级指针变量
//当然还有四级指针、五级指针...。语法是支持的,但是并不常用(三级指针也很少用到)
//怎么通过ppa找到a -> *ppa <=> pa, *pa <=> a, **ppa <=> a
printf("%d\\n", **(ppa));
return 0;
}
图解:
七、指针数组
#include<stdio. h>
int main()
{
int arr[10];//整型数组 - 存放整型的数组就是整型数组
char ch[5];//字符数组 - 存放字符的数组就是字符数组
//指针数组 - 存放指针的数组就是指针数组
int* parr1[5];//整型指针数组
char* parr2[5];//字符指针数组
return 0;
}
八、指针进阶
这里将附上对于指针更深层次的文章
指针进阶篇
以上是关于C语言精华 - 指针初识篇的主要内容,如果未能解决你的问题,请参考以下文章