指针概念及应用

指针的相关概念

1.指针是什么?

指针是内存中的一个最小单元的编号,其实就是指地址,对于我们平时口中所讲述的指针,通常指的是指针变量,指针变量是用来存放内存地址的变量。

2.地址与指针

一个32位机器在一个进程中可以一次操控4G的内存空间,并对这4GB的连续内存空间进行编码,得到2^32种编号,这个编号就是内存地址,一个编号(地址)对应一个内存单元。

指针,是内存中最小单元的编号,而内存中最小单元指的就是内存单元,内存单元的编号指的就是上述的2^32种编号,所以我们可以这样说:编号-地址-指针,这三种其实所指相同。

如图8.3地址1000是变量i的指针。

例如,将变量i的地址存放在指针变量p中,p就指向i。如图8.4

如图8.5

在地址2000上的变量是指向地址2005上的那个变量,在地质2000上该变量的内容是2005.同理,在地质2001上的变量是指向地址2004上的那个变量,在地址2001上该变量的内容是2004.

3.指针变量

一般格式:类型声明*变量名;

其中,“*”表示这是一个指针变量;“变量名”即为定义的指针变量名;“类型声明”表示本指针变量所指向的变量的数据类型。

在前面,我们学过许多种操作符,其中有个取地址操作符(&)以及解引用操作符(*)(又称为间接访问操作符)。

我们知道在创建一个变量时,当程序执行到创建变量,那么相应的会在内存空间内分配一个内存空间给这个变量,用于存储这个变量的值。

如果对一个变量使用取地址操作符,也那么就是取出这个变量的地址,通过之前的章节我们知道,例如一个int类型的变量,在创建变量时,内存会分给这个变量4个字节的空间大小,而一个字节对应着一个内存空间,对应一个内存地址,而四个字节就对应四个内存地址,而通常来说,我们口中的变量的地址指的是这个变量所占内存空间的低地址,也就是分配给这个变量的由低到高的地址中的第一个地址。

而当我们对一个变量使用取地址操作符时,取出的就是这个对应内存空间的第一个内存单元的地址。
如果我们取出这个变量的地址之后把它存放在另一个变量中,那么这个变量就是指针变量。

4.指针类型

指针变量,它也是一种变量,那么指针变量也是有类型的。

在代码中取出a的地址存放在pa中,pa是一个指针变量,这个指针变量的类型如普通变量一样,变量名的前面就是其类型,所以指针变量pa的类型是int*。
相对于普通的变量类型,指针变量的类型只是在类型后加上了*号,这个星号可和我们之前了解的解引用操作符不是一个作用(含义),这个星做作用是,与星号前面的类型进行结合,表明这个类型是一个指针类型,这个指针类型所定义的变量是指针变量。*的个数表示这个指针的级数。

5.定义指针

而定义一个指针变量的语法格式是:

type*name=NULL;

其中type位类型,name是你要创建的指针变量的变量名。

要注意的是,当你创建一个指针变量时,你需要给其进行初始化,如果你不知道或者说暂时不需要赋值,你可以先赋值为NULL。绝对不允许不进行初始化创建指针变量,不初始化会出现野指针,这是很危险的行为。

NULL在C语言中你可以理解为空指针,是计算机内存中保留的值。虽然C语言标准没有明确指出NULL空指针与指向内存地址为0x00000000的指针相同,但是在实际情况中,基本就是这样。

另外虽然在初始化时可以赋给指针变量NULL,但是解引用空指针,这种操作是不允许存在的,是C语言为定义的行为

6.指针类型的意义

  1. 指针类型决定了,在对指针进行解引用时,可以访问的内存大小,可以访问多少个字节大小的内存空间。或者说可以对多大的内存空间进行操作。
  2. 指针类型决定了指针的步长。

7.指针的赋值

一般格式:&变量名;

指针变量同普通变量一样,使用之前不仅要定义,而且必须赋予具体的值。未经赋予的值不能使用

给一个指针变量赋值可以有以下两种方法:

1.定义指针变量的同时就进行赋值。

int a;

int*p=&a;

2.先定义指针变量,然后再赋值。

int a;

int*p;

p=&a;

注意:如果在定义完指针变量之后再赋值,注意不要加*”。

p中存储的是a的地址,*p就是通过p中存放的地址,找到内存中对应这个地址编号的一块空间,并直接对空间进行操作。这就是解引用操作符。

8.大小端字节序

字节序

字节序又称端序或尾序,在计算机领域中,指电脑内存中或在数字通信链路中,占用多个字节的数据的字节排列顺序。

字节的排列方式有两个通用规则:

大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。

小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。

为什么会出现大小端呢?

当往内存中存放数据的时候,我们有很多种存放的顺序,正着放,反着放,随机存放等等等等,例如我们往内存中存放0x11223344,这只是演示。

如果是你让你从上述片段中快速的按顺序取出数据,你会选择哪种方法呢?所以就出现了大小端排序。

过这个大小端,为什么叫大小端而不是叫别的,据说是因为发明者当时在看格列夫游记,看到其中两个国家因为争论吃鸡蛋应该先从大头剥还是小投剥,发动了战争,而获得启发,起名大小端。

9.步长

10.野指针

野指针就是指针指向的位置是不可知的、随机的、甚至没有访问权限的。

为什么会存在野指针?
1.指针未初始化
#include <stdio.h>
int main()
{
    int* p;     //局部变量指针未初始化。默认为随机值
    *p = 10;

    return 0;
}

2.指针越界访问
就比如,对于一个数组,只有十个元素,而我非要去访问这个数组的第十一个元素,这就是数组越界访问。
指针也是,当指针指向的地址不在数组所拥有的内存空间范围时,指针就成为了野指针。
#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int i = 0;
    
    for (i = 0; i <= 10; i++)
    {
        printf("%d ", *(p+i));
    }

    return 0;
}
3.指针指向的空间释放
局部变量的作用域是有限的,当出了变量所在的局部范围,变量就自动销毁了,其所分配的空间就还给内存了。这个时候,如果主函数内部有指针指向这个局部变量销毁之前所指向的地址,那么局部变量自动销毁之后,这个指针就变成了野指针。
#include<stdio.h>
int* test()
{
    int a = 10;
    return &a;          //&a=0x0012ff40
}
int main()
{
    int* p = test();
    return 0;
}
规避野指针
1.指针初始化
2.小心指针越界访问
3.指针指向空间释放,及时置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性

11.二级指针

一级指针,创建一个指针变量用于存放一个普通类型变量的地址。例如

int a = 10;
int* pa = &a;  // 其中pa是一级指针变量。
而指针变量也是变量,是变量在创建是就会分配内存空间,所以一个指针变量在内存中也是有一块内存空间的。那么也就存在相应的地址编号。

int a = 10;
int* pa = &a;  // 其中pa是一级指针变量。

int** ppa = &pa;  //ppa是二级指针变量

a的地址存放在一级指针变量pa中,pa的地址存放在二级指针变量ppa中。
解引用ppa通过存储在ppa内的地址,找到pa,再对pa进行解引用,找到a。

int a; 变量a的类型是int类型

int * pa ,这个*表示这个pa是一个指针变量,int表示pa存储的地址指向的内存空间中存储的是int类型的值

int* *pa ,第二个*表示,pa是一个指针变量,int*表示ppa存储的地址指向的内存空间中存储的是int*类型的。

指针运算关系

对于指向地址的指针,可以进行下列的运算:

1.指针 +/- 整数

通过一个小例题来理解
使用指针,将一个数组内的所有元素赋值为0

*p++;与(*p)++;*++p;的区别

*p++  ===  *(p++)----->>先p++再*p

(*p)++;---->>先对p解引用,再++。

*++p;----->>先++,再解引用。

一元运算符*和++的优先级相同,但结合律是从右往左(其他大部分是从左往右)。

2.指针 - 指针

使用指针-指针,运算时,前提是两个指针需要指向同一块内存空间。例如同时指向一个数组中的不同元素。
指针-指针得到的值的绝对值是两个指针之间的元素的个数。

3 比较两个指针大小

指针与数组

1.int a[] = {1,2}; int *p = a

数组类似于指向位置固定的指针
即a除了不能 进行a++,其他用法和指针差不多

2. *(p+i) = p[i] = a[i] = *(a+i)

p+i = &p[i] = a+i = &a[i]

假设 p=200 则 p+n =200+n*size
size = p所指向的类型的大小

3.数组作为参数传入函数

数组作为参数传入函数时,函数只会生成一个指针来保存传入数组的首地址
即 fun(int a[ ]) = fun(int* a)
数组作为参数传入函数被修改后,main中也会被修改。

4.字符串数组

字符串必须以\0结束,计算字符串长度计算到 \0 为止

sizeof

用于计算变量本身所占用的字节数,而不是变量指向的地址所占用的空间大小

strlen

用于计算以 NULL (\0)结尾的字符串中字符的个数

5.两种定义方式

char w[] = “hi” 被定义在栈区,可以被修改
char *w = “hi” 被定义在常量区,不能修改

6.int a[ ][3]={1,2,3,4,5,6}

int a[2][3]; int (* p)[3] = a;
不能写成int **p= a, 指针类型不同

7.a[i][j] = *(a[i]+j) = *( *(a+i)+j)

8.c[i][j][k] = *( *( *(c+i)+j)+k)

c[i][j][k] = *((c[i][j]+k) = *(*(c[i]+j)+k)

原理与上一条相似。

感谢:https://blog.csdn.net/2201_75314884/article/details/127992565

感谢:https://blog.csdn.net/m0_60292931/article/details/123981539

  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值