修仙之指针初阶-创新互联-成都快上网建站

修仙之指针初阶-创新互联

1.指针是什么❓
引言:变量的内存地址:先前我们了解到,C程序中变量的值都是存储在计算机内存中特定的存储单元中。而内存中的每个单元都有唯一的地址。
1.1理解指针是什么?🔻
1.指针是内存中一个最小的单元编号,也就是地址。
2.口语中的指针,指的是指针变量,是用来存放变量的地址。

我们可以进一步将指针理解为“内存”

创新互联从2013年成立,先为道外等服务建站,道外等地企业,进行企业商务咨询服务。为道外企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

图片描述

1.2指针变量😁
如何获得变量的地址呢?通过&(取地址操作符)取出变量的其实地址,然后放在一个变量中,这个变量就是指针变量。 通过指针变量间接存取它指向的变量的访问方式称为间接寻址。
#includeint main()
{
    int a = 5;//在内存中开辟一块空间
    int* pa = &a;//取出变量a的地址,存放到pa这个变量中。pa就是指针变量。
    printf("%p\n",&a);//需要用到取地址操作符
    printf("%p\n", pa);
    //打印地址格式用%p,表示输出变量的地址值
    //地址值时用一个十六进制(以16为基数)的无符号整数表示。
    return 0;
}

图片描述

//如何定义两个相同基类型的指针变量呢?
int*pa,*pb;
//注意不可以用int*pa,pb。这表示定义了可以指向整型数据的指针变量pa和整型变量pb。

总结:❗

1. 指针变量--->用来存放地址的变量。(存放在指针中的值都被当成地址来处理)
2.那么最小的一个单元有多大呢?---- 一个字节
3.深刻剖析内存单元如何进行编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电
平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==
2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。
也就是说:
1.在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
2.那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地

总结:❗

1.指针是用来存放地址的,地址是唯一标识一块地址空间的。
2.在32位平台上,指针的大小占4个字节;在64位平台上,指针的大小占8个字节。
接下来我们观察一下指针变量在内存中的存储情况
int main()
{
    int num = 0;//地址编号为:0x00effcf0,存放的值为00000000
    int* pa = #//地址编号为:0x00EFFCE4,存放的值为0x00effcf0
    char* pc = (char*)#//地址编号为:0x00EFFCD8,存放的值为0x00effcf0
    return 0;
}

注意:

本例第5行:指针变量只能指向同一基类型的变量,否则会引起warning。所以本例使用了强制类型转换。

通过上述代码和图片解释,我们发现:
指向某变量的指针变量,虽然指针变量中存放的是变量的地址值,二者在数值上相等,但在概念上变量的指针并不等同于变量的地址。变量的地址是一个常量,不能对其进行赋值。而变量的指针是一个变量,其值是可以改变的。

🌜2.指针和指针类型🌛

我们都知道,变量有不同的类型,如整型、浮点型等等。那么指针也有对应的指针类型。

int num=10;
int* p=#//pa是一个指针变量,它指向一个整型变量
变量p是一个指针变量,用来存放num的地址。因为num的类型是整型,那么指针存放的数据类型是整型,对应p是一个整型指针,同时需要在int和p之间加一个*确保它是一个指针变量。
2.1指针类型:
指针类型定义的方式:类型关键字+*+指针变量名
char* p=NULL;(存放char类型变量的地址)
short* p=NULL;(存放short类型变量的地址)
long* p=NULL;(存放long类型变量的地址)
int* p=NULL;(存放int类型变量的地址)
float* p=NULL;(存放float类型变量的地址)
double* p=NULL;(存放double类型变量的地址)
介绍一下 指针运算符, 也称 间接寻址运算符 或 解引用运算符:(*)间接寻址运算符*用来访问指针变量指向变量的值。 运算时,要求指针已经被正确初始化或者已经指向内存中某个确定的存储单元,防止野指针的出现。
2.2指针+-整数
int main()
{
    int num = 10;
    int* pn = #
    char* pc = (char*)#
    printf("&num=%p\n", &num);
    printf("pn=%p\n", pn);
    printf("pn+1=%p\n",pn+1);
    printf("pc=%p\n",pc);
    printf("pc+1=%p\n",pc+1);
    return 0;
}

内存中的地址是由十六进制显示的:pn是整型指针,pn+1与pn相差4个字节;而pc是字符型指针,pc+1与pc相差1个字节

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

2.3指针的解引用
int main()
{
    int num = 10;
    int* pn = #
    char* pc = (char*)#
    printf("%d\n", *pn);
    printf("%d\n", *pc);
    return 0;
}

这样看对指针的解引用不深刻,我们换一种方法:

int main()
{
    int n = 0x11223344;
    char* pc = (char*)&n;
    int* pn = &n;
    *pc = 0; //重点在调试的过程中观察内存的变化。
    *pn = 0; //重点在调试的过程中观察内存的变化。
    return 0;
}
//接下来我们通过调试观察在内存中的变化
通过对pc进行解引用操作,按ctrl+F10进行逐语句调试过程,按F10到25行,再按一次调试25行代码跳到26行,可以观察25行程序运行的情况:观察内存变化发现内存由0x11223344变成了0x11223300。*pc对pc进行解引用操作并赋值为0,因为pc是Char类型指针,访问一个字节的内存空间,所以将内存地址的低位44改成了00。
当我们再按F10的时候,语句调试第26行,跳到27行,观察第26行程序的运行:因为pn是整型指针,对pn解引用操作会对内存访问四个字节的内存空间,将*pn赋值为0,也就是将0x11223344变成了0x00000000。

总结:💗

  1. 指针的类型决定了指针解引用操作对内存能访问多大的权限(能操作几个字节)

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

同样,我们可以通过解引用修改变量的值,看下面程序运行结果: 📄
int main()
{
    int num = 0;
    int* pa = #//定义指针变量的同时对其初始化
    *pa = 20;//修改指针变量pa所指向的变量的值
    //引用指针所指向的变量的值,也称为指针的解引用。
    printf("%d\n", num);
    return 0;
}

那么:既然*pa和变量num的值一样,我们原本可以很容易的将变量a的值打印出来,为什么还要舍近求远地使用指针变量来得到a的值呢?通过”标题4展现指针的强大功能“😍

👺3.野指针👺

概念:

➡野指针就是指指向的内存是不可知的。(随机的、不正确的、没有明确限制的)

3.1野指针的成因😺 3.1.1指针的未初始化
int main()
{
    int*p;//此时指针变量p指向的内存是不可知的
    *p=20;//对指针进行解引用修改值可能会对内存发生不可逆的影响
    return 0;
}
3.1.2指针的越界访问
int main()
{
    int arr[10] = { 0 };
    int* p=&arr;//数组的第一个元素的首地址
    int i = 0;
    for (i = 0; i< 11; i++)
    {
        *(p++) = i;//先对p赋值,后p++(详看++的运算规则)
        //指针指向的范围超出了数组的范围,此时p就是野指针
    }
    for (i = 0; i< 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}
3.1.3指针指向的空间释放
与动态内存相关内容,后续发布文章讲解
3.2如何规避野指针
1.指针初始化
2.防止指针越界的情况发生
3.指针指向空间释放即使置NULL
4.避免返回局部变量的指针
5.指针使用之前检查有效性
int main()
{
    int* p = NULL;
    int num = 10;
    p = #
    if (p != NULL)
    {
        *p = 10;
    }
    printf("%d\n", num);
    //10
}
恪守准则:
1.永远清楚每个指针指向了哪里,指针必须只想一块有意义的内存;
2.永远清楚每个指针指向的对象的内容是什么;
3.永远不要使用未初始化的指针变量。
🌜4.指针运算🌛 4.1指针+-整数😇
#define N_VALUES 5
float values[N_VALUES];//浮点型数组
float* vp;//定义一个浮点型指针
//指针+-整数;指针的关系运算
int main()
{
    for (vp = &values[0]; vp< &values[N_VALUES];)
    {
        *vp++ = 0;//数组从下标为0的元素开始赋值为0;
    }
    return 0;
}
4.2指针-指针😇
//函数功能:模拟实现strlen
//函数形参:字符型指针
//函数返回值:整型
int my_strlen(char* ch)
{
    char* p = ch;
    //数组名是数组首地址
    while (*p!='\0')
    {
        p++;
    }
    return p - ch;
    //指针-指针:实现指针之间的距离(相差的字节数)
}
int main()
{
    char ch[20] = { 0 };
    fgets(ch, sizeof(ch), stdin);
    printf("%d\n", my_strlen(ch));
    return 0;
}
4.3指针的关系运算😇
#define N_VALUES 5
float values[N_VALUES];//浮点型数组
float* vp;//定义一个浮点型指针
//指针+-整数;指针的关系运算
int main()
{
    for(vp = &values[N_VALUES]; vp >&values[0];)//vp指向下标为5的内存空间
    {
        *--vp = 0;//从下标为4的位置开始
    }
    return 0;
}
当我们对代码进行简化时:
#define N_VALUES 5
float values[N_VALUES];//浮点型数组
float* vp;//定义一个浮点型指针
//指针+-整数;指针的关系运算
int main()
{    
    for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
    {
        *vp = 0;
        //当vp=&values[0]时,*vp=0,此时会进行vp--,导致指针指向第一个元素之前的内存空间,应避免这样          //的问题
    }
    return 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证
它可行

❗标准规定❗

允许指向数组元素的指针与 指向数组最后一个元素后面的那个内存位置的指针比较,但是 不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
4.4按值调用和模拟按引用调用 ⬇引例1⬇修改实参的值
void Change(int num)
{
    num = 20;
}
int main()
{
    int num = 10;
    printf("%d\n", num);
    Change(num);
    printf("%d\n", num);
    return 0;
}

程序在函数Change中改变了num的值,但是再次输出实参的值却发生没有任何变化,说明函数的形参值的改变并未影响到实参值的改变。这是因为形参是实参的一份临时拷贝,通过按值调用不能在被调函数中改变其调用语句中的实参值。这时我们就要用到指针这个秘密武器了。指针变量的一个重要作用就是用作函数参数,由于传给被调函数的这个值不是变量的值,而是变量的地址,通过向被调函数传递变量的地址可以在被调函数中改变主调函数中变量的值,模拟C++中的按引用调用。

void Change(int* num)
{
    *num = 20;
}
int main()
{
    int num = 10;
    printf("%d\n", num);
    Change(&num);
    printf("%d\n", num);
    return 0;
}

实际上是通过实参的地址对实参进行修改

⬇引例2⬇实现两个整数的交换
void Swap(int*a,int*b)//形参是两个整型指针
{
    int tmp = 0;//交换两个数的值
    tmp = *a;
    *a = *b;
    *b = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    Swap(&a, &b);//传址
    printf("%d\n%d\n", a, b);
    return 0;
}

如果修改为Swap(a,b),void Swap(int a,int b)结果是什么呢?原理和引例1相同,我就不过多解释啦😁

🌜5.指针和数组🌛
先看一个例子:
int main()
{
    int arr[10] = { 0 };
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

结论:数组名表示的是数组首元素的地址

//也就是说我们可以这样写:
int arr[10]={0};
int*pa=&arr;//pa存放的是数组首元素的地址

例如:

int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    int *p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr)/sizeof(arr[0]);
    for(i=0; ip+%d = %p\n", i, &arr[i], i, p+i);
    }
    return 0;
}

进一步可以表示为以下这种方式:

int main()
{
    int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    int* p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    for (i = 0; i< sz; i++)
    {
        printf("%d ", *(p + i));
    }
    return 0;
}
🌜6.二级指针🌛

指针变量也是变量,那么指针变量的地址存放在哪里呢?-----二级指针

int main()
{
    int a = 10;
    int* pa = &a;
    printf("对pa进行解引用=%d\n", *pa);
    printf("变量a的地址=%p\n", &a);
    printf("变量a的地址存储在pa中=%p\n", pa);
    int** ppa = &pa;
    printf("指针变量pa的地址=%p\n", &pa);
    printf("对ppa解引用得到pa的值=%p\n", *ppa);
    printf("对ppa解引用得到pa再解引用得到a=%d\n", **ppa);
    printf("变量pa的地址存储在ppa中=%p\n",ppa);
    **ppa = 30;
    //**ppa相当于*pa,*pa相当于a
    printf("a=%d\n", a);
    return 0;
}

程序运行结果如上

🌜7.指针数组🌛

指针数组是指针还是数组呢?答案是数组,是存放指针变量的数组。

我们知道存在整型数组、浮点型数组、字符数组,那指针数组是什么样子的呢?

int* arr[5]={0};

如图片所示

简单介绍即可,进阶版跟后续笔记😭

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前文章:修仙之指针初阶-创新互联
文章起源:http://kswjz.com/article/cdghcp.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流