C语言:初阶指针

内存和地址

在计算机中,一切的运算数据都会暂存在内存中,当我们需要调用内存中某个部分的时候,就会用到指针。我们可以把计算机的内存比作一条街上的一排房屋,每个房屋都会容纳一定的数据,并可以用房号来标识;首先这个比喻是有一定的局限性的,不过对于初学者来理解,是足够的;这里就不必展开了;那这个房屋得多大呢?在计算机中内存中由数亿上计的位(bit)组成,所以,房屋储存以字节为单位;
在这里插入图片描述
假设把上面一个格子当作一个字节,那么每个格子就可存8个位;内存中每个位置都包含一些值;每个字节的位置用地址来标识;在这里,地址是十六进制的;为了存储更大的值,我们把两个或更多个字节合在一起作为一个更大的内存单位。我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节;在这里,内存中的每个位置都是由独一无二的地址标识的;当我们要引用某个数据时,就要调用到某个内存,我们通过地址来找到该位置,而指针,就是来记住这个地址的;

指针

所以,指针是内存中一个最小单元的编号,也就是地址,口语中说的指针通常指的是指针变量。
如:

#include <stdio.h>
int main()
{
    
    
 int a = 10;
 int *p = &a;
  //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。
 return 0;
}

对于变量a来说,会在内存中开辟一块空间,对于变量a,我们要取到它的地址,可以用到&操作符。

在这里插入图片描述
一般来说,在32位机器上,一个指针变量大小为4个字节,64位机器上,8个字节。

值与类型

先看下面代码:

#include <stdio.h>
int main()
{
    
    
 int a=112,b=1;
 float c=3.14;
 int* d=&a;
 int* e=&c;
 return 0;
}

变量表示:
在这里插入图片描述

我们知道a是整型存储类型的,对于a取地址,对他解引用是它本身,但是,c是浮点型类型的,对他取地址后解引用,却是一个整数;答案是该变量包含了一系列0或1的位,对于它是整型类型或者是浮点型类型,取决于它的算术指令;所以,不能简单的通过检查一个值的位来判断它的类型;

接着看以下代码:

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL

指针定义类型为 type+*;

对于不同类型的指针,是为了存储相应类型的地址;如char类型指针存储char变量的地址。那不同类型的指针又有什么不同呢?

#include <stdio.h>
int main()
{
    
    
 int n = 10;
 char *pc = (char*)&n;//括号是强制转换
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return  0;
}

结果:
在这里插入图片描述
我们可以看到,对于char类型和int类型取地址,是位于n内存上的地址,
在这里插入图片描述
地址加1后,
在这里插入图片描述
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)

最后:

//演示实例
#include <stdio.h>
int main()
{
    
    
 int n = 0x11223344;//表示地址位置
 char *pc = (char *)&n;
 int *pi = &n;
 *pc = 0;  
 *pi = 0; 
 return 0;
}

一开始的地址:

0x11223344

pc解引用后:

0x11223300

pi解引用后:

0x00000000

在这里,电脑采用了小端的存储方式。在电脑中,数据一定是从内存的低地址依次向高地址读取和写入,小端存储,会将低位存储到低地址处,高地址存储到高地址处,也即存储时是0x44332211.所以,pc解引用后,由于它是char类型的,大小只有一个字节,所以只对一个字节内存大小赋值为0,即为44;而pi为int类型,大小4个字节,所以当它解引用后,就会使整个地址为0。

总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

指针运算

指针±整数

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
int main()
{
    
    
    for (vp = &values[0]; vp < &values[N_VALUES];)
{
    
    
     *vp++ = 0;
}
 }

在这里,values表示一个数组,vp是一个浮点型指针,在for循环中,vp赋值为value首元素地址,每当它+1后,地址就会不断先后移动4个字节,直至与values[5]地址相同,循环停止;

在这里插入图片描述

指针-指针

int my_strlen(char *s)
{
    
    
       char *p = s;
       while(*p != '\0' )
              p++;
       return p-s;
}

这是一个求字符串长度的函数,假设有一个字符串为“abcdef" ,指针位于初始位置a地址上,p指针表示初始位置地址,当循环之后,p走到了’\0’的位置,最后相减返回,即为字符串长度6。

在这里插入图片描述
当然,这里给出结论:指针-指针是返回的是之间有多少个元素,也即地址差/整型字节。一般可以运用到字符串和数组中。上面例子由于是char类型,看不出来。下面给出一个例子:

#include<stdio.h>
int main()
{
    
    
    int arr[10];
    printf("%d",&arr[10]-&arr[0]);
  
    return 0;
}

结果:10
当然指针-指针的前提条件是指向同一块空间。

指针与数组

先看例子

#include <stdio.h>
int main()
{
    
    
 int arr[10] = {
    
    1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

在这里插入图片描述
可以看出arr地址即为首元素地址,当然两种情况除外。(数组章节有说明哦).
既然arr可以当作指针,那么就有以下的操作:

#include <stdio.h>
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; i<sz; i++)
   {
    
    
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
   }
    return 0;
}

在这里插入图片描述
可以看到, p+i 其实计算的是数组 arr 下标为i的地址,那么我们可以通过指针来访问数组元素。

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;
}

在这里插入图片描述

二级指针

指针变量也是变量,是变量就有地址。如int *p是一个指针变量,那么它的地址就由二级指针来存储

#include<stdio.h>
int main()
{
    
    
    int a=10;
    int* p=&a;
    int** pp=&p;
    return 0;
}

在这里插入图片描述

可以看到,a是整型变量,p是一级指针,解引用后为10,pp是二级指针,*pp表示一级指针的地址,**pp表示解一级指针的指向的内容。

在这里插入图片描述

野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
所以,我们要规避野指针的情况。
1.创建指针记得初始化,当不知道指向哪里时,直接指向NULL,不能不管它;
2.不能使访问位置越界,如一个数组有10个元素,你访问到下标为10的位置,超过数组长度范围,这就是访问越界。
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址,局部变量的指针指向的地址会在函数走完后销毁,当返回到主函数时,主函数指针接收不到任何地址。
5. 指针使用之前检查有效性

猜你喜欢

转载自blog.csdn.net/m0_74068921/article/details/130874325
今日推荐