C语言带你进入指针的大门

141 阅读7分钟

前言,经过了前面简单的C语言学习,下面我们就来到了指针的学习,当然只是初阶指针的学习,指针的内容还有很多,我们会后面在介绍。

[toc]

1 指针是什么?

首先我们知到,我们的电脑内存分为一个一个的字节,为了方便快速的访问到内存空间,于是我们给了这些内存一个编号,每个字节都会有一个编号,这个编号实际上就是内存的地址,就是指针。

可以举一个例子加深理解,我们的教学楼每个教室都有门牌号,如果你要去某个教室找人,他只告诉你自己在这座楼中,你只能一个一个检查每个教室看一下他是不是在里面,这样效率就会很低,如果他告诉你他在404,那么你就可以直接去找到。这样就大大加快了效率。

指针就是地址严格的说,但是我们口头上常说的指针,其实是指针变量。

#include <stdio.h>
int main()
{
 int a = 10;//在内存中开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
 return 0; 
}

2 指针和指针的类型有什么

c语言的指针呢就和C语言的内置类型一样是有很多类型的。比如:

int * pa;
char * pa;
short* pa;
long* pa;
long long*pa;
float* pa;
double* pa;
void* pa;

那么C语言的这些指针类型的意义是什么呢,这里可以给大家介绍一下, 1.指针的类型决定了指针解引用的时候可以访问多少字节,也就是这个指针有多大的权限。

比如int类型在内存中占用四个字节,那我解引用int*的指针的时候(格式是*pa)就可以访问到四个字节,也就是刚好将int的数据完整拿出来。

2.指针的类型决定了计算机如何看待内存中的数据,比如float和int在内存中都是占用四个字节,但是int类型和float类型在内存中的存储方式是不一样的。这样就说明我们这些相同字节类型的指针式不可以混用的。

3.还决定了指针加减整数的时候会跳过几个字节

我们知到,内存在划分的时候式一个一个字节划分的,每个字节都有他自己的地址,但是我们的指针保存的只是这个数据的最低一个字节的地址,比如int类型,我们在&p的时候只是拿到了p第一个字节的地址,但是p剩下的3个字节也是属于p的数据,如果我通过指针+1来访问下一个数据,我就需要跳过四个字节,就是跳过p指向下一个元素。因此,指针的类型还决定了指针加见整数的时候能跳过几个字节。

3 野指针

野指针就是指向未知空间的指针变量,变量中的地址是随机的或者是不可控的。没有限制的。

野指针是非常危险的,如果对野指针进行解引用就会非法访问内存,造成程序崩溃。

野指针的成因有: 1.创建指针变量未初始化,这时候里面的地址是随机的。(解决方法,不知道赋啥值的时候可以先赋成空指针。) 2.指针越界访问 关于指针越界访问这里介绍一个例题

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0; }

3.指针指向的内存被释放了(涉及到动态内存开辟) 了解了野指针是如何形成的那我们就要避免写出野指针。

1.指针变量的创建的时候记得初始化 2.数组使用指针访问的时候要注意越界 3.指针指向的内存被释放后要置为NULL 4.指针在使用前要检查其有效性 5.避免在函数中返回局部变量的地址(因为函数返回后局部变量就会被销毁,这块空间就会还给操作系统,用来分配给别的地方使用)

4 指针的运算

指针运算的类型和意义有: 1.指针加整数(是指针向后跳(具体跳几个字节由指针类型决定),访问下一个元素) 2.指针 - 指针,这个差是两个指针间的元素个数,一定要是大地址减小地址,不然的话没有意义,同时如果两个指针指向的不是同一块连续空间,那这个差也是没有意义的。 3.指针的关系运算也就是比大小

这个C语言标准规定,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) 
{
    *vp = 0; 
}

例如这种写法并不是在所有编译器都是可行的,因为这是标准未定义的。

5 指针和数组

指针和数组的关系,我们之前在数组那部分提过,数组名就是首元素地址(两种情况除外1.&数组名,这时候数组名代表整个数组的地址 2.sizeof(数组名),这时候数组名代表整个数组,计算的也是整个数组的大小)

那这样我们就可以把数组名(也就是首元素的地址)用一个指针来维护起来,通过指针来访问数组的元素,我们来看一段代码演示

int main()
{
    int arr[10] = {0,1,2,3,4,5,6,7,8,9};
    int* pa = arr;
    //这里要注意,不可以写成&arr,不然取出的是整个数组的地址,虽然存在变量中的地址没有区别,但是地址的性质不一样,一但我们给这个地址加1,那么指针就会指向数组的后面, 一次性就跳过了整个数组。
    for(int i =0;i<10;i++)
    {
        //pa[i] = 0;
        *(pa+i) = 0;//这两种写法都是可以的
    }
}

6 二级指针

二级指针就是用来存放一级指针变量的地址的指针变量。

int a = 0;
int* pa = &a;
int** ppa = &pa;//这就是二级指针,有两个*号

这里解释一下,靠近ppa的*与变量名结合,意义是表示这个变量是一个指针变量。而前面的 int * 则代表,这个ppa指针变量所指向的对象是int * 类型的。

7 指针数组

顾名思义就是用来存放指针的数组

int* parr[3] = {0};
//这就是一个指针数组,可以存放三个指针。

这我们可以用一个指针数组来模拟一个二维数组(涉及到动态内存开辟)

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *parr[3] = {0};
    for(int i =0;i<3;i++)
    {
        parr[i] = (int*)malloc(sizeof(int)*5);
    }
    return 0;
}

注意上面模拟出来的就是 int arr[3][5],虽然正常使用来说是一样的,但是我们要注意,malloc出来的空间是在堆区的,并且这三个一维数组并不是连续的。而我们直接创建的二维数组在内存中是连续的,也就是说如果我们用一个指针,访问数组的第6个元素,我们会拿到数组第二行第一个元素,但如果是malloc的数组这样子访问就会报错,或程序崩溃,因为发生了越界访问。