c语言指针

一、指针的介绍

1.1 c语言的优点

c语言之所以这么强大的原因有两个:

          1、c语言是编译型语言

          2、c语言有指针!!!!

TIPS(TIPS的都是科普,看着玩)

TIPS.1、字节码:

我们的计算机是不能理解高级语言的,计算机只能理解的是机器语言(听名字就是机器才能明白的语言),我举个简单的例子(大概是这个意思但是并不严谨):

        我们知道在计算机中都是存储的二进制数字,因为计算机实际上就是由一个个开关(后来是二极管)组成的,反正就是计算机的基本元件只能表示两个状态,即使是现在也是(当然现在有生物计算机了,4个碱基就是4进制,还有量子计算机,可以表示叠加态,但是原理都是一样的,想通过一个基本元件表示的多个状态来表示数据)。从二进制出发那么计算机是如何执行这些复杂的程序的呢,从上面的描述来看计算机只能表示简单的二进制数,那么像 int a;这句话是怎么表示的呢,我下面提供一种思路(并不严谨,但是是这么一个道理!):

把int a;这句话的所有字母转化成对应的ASCII吗为:

i--------105------二进制表示是------>0110 1001

n-------110-------二进制表示是------>0110 1110

t-------116--------二进制表示是----->0111 0100

a-------97--------二进制表示是------>0110 0001

那么在计算机内部就可以存成:

0110 1001 0110 1110 0110 1110 0110 0001 (这个叫做字节码)

那么计算机里面会存一个指令表,遇见了特定的字节码就会执行特定的操作,比如遇见了int的字节码是:

0110 1001 0110 1110 0110 1110

TIPS.2、计算机语言的发展历程

计算机语言的发展历程:

机器语言------------>汇编语言-------------->高级语言

机器语言:简单来说就是上面的那种字节码,就你写int a;直接写成上面的字节码,这个对人来说太难了,正经人谁记得住这么长的字节码呢!!!!(但是这是计算机唯一能认识和执行的语言)

汇编语言:在机器语言的基础上发展了一下,将对应操作的字节码都编程了单词,也就是在计算机里面弄了个映射的表(下面的表是那个意思,但是机器语言那个对应的字节码是不对的,我记不住哈,给你随便来了个示意图,感兴趣可以自己百度),我们写汇编语言就直接可以用 2 add 3,这样就会好记很多!!就会比直接写字节码轻松很多!

高级语言:就是在汇编语言的基础上发展而来的,更加人性化的语言(仅此而已!!!)

        因此从上面的逻辑可以看出来,我们的计算机不管是什么语言都得通过查表将相应的语句转化成机器语言的字节码!!所以我们的高级语言都需要通过两次转化变成机器语言(第一步从高级语言变成汇编语言,第二步从汇编语言变成机器语言),我们来看看都有些啥语言呢:

上面的从第三层开始后面的都是高级语言哈,语言分类就3种!

1.2 编译型语言和解释型语言

编译型语言:源程序编译后即可在该平台运行(快!c语言就是),意思就是我们的程序在写好了之后,点击编译然后就形成了一个字节码的文件,下次要用或者给别人用直接把这个字节码的文件发给别人或者我们直接运行就可以用了(一个写好了的机器语言程序)。但是缺点是跨平台型差,简单一点的例子就是你苹果电脑和windows电脑的底层实现是不一样的,那么那个从汇编语言到机器语言的映射表是长得不一样的!!!那么你提前翻译好了,别人就看不懂你的字节码文件,就运行不了!这就是跨平台型差的原因,但是快!

解释型语言:(python语言就是)在程序运行期间编译,也就是边运行边编译。意思就是我的程序写好了就写好了,只是转换成了汇编语言,但是并没有变成机器语言,在每次执行的时候动态的根据你是什么计算机然后翻译成相应的字节码(机器语言),然后执行,因此你写的程序就可以即在windows又在苹果的mac OS上面运行!因为每次都需要转换然后再执行因此速度慢,但是跨平台性好!

c语言的强大之一就是速度快,你以后做其他的刷题网站会发现,你用c语言的时间限制是其他语言的一半,因为你语言本身就运行得更快,所以限制更加严格。

二、指针

        2.1 指针是什么

        指针简单来说就是访问计算机内存空间的一个工具,首先我们要明白计算机怎么执行程序的,上面知识介绍了如何从高级语言到机器语言,但是怎么执行程序的呢?当然是从上往下执行的,这个我也知道!实际上我们的一个程序在要运行的时候会加载进入计算机的内存(计算机的存储空间分为内存和外存),内存就是你平时买手机或者电脑的那个运行内存,比如8G运行内存,16G运行内存(所以为啥运行内存大手机越贵,因为能同时加载进来的程序越多,也就是你能同时打开的app越多!!一个app就是一段程序嘛,你可以先这样简单的理解)。那么内存那么大,而且可能里面同时加载了10个程序,我的拼多多程序咋知道我的代码在哪呢???我肯定不能去运行淘宝的程序吧!

内存的简单示意图,实际上内存就是这样的一个一个小格子,比如一个字节一个格子,那么1M = 1024byte那么久会有1024个格子,1G就会有1024*1024个格子

        实际上从上面的你应该也能想到了我们给每个格子弄上一个门牌号!!实际上计算机也就是这么设计的,这个门牌号就叫做内存地址,那么我们的一个程序你只要告诉我没行代码的门牌号(内存地址)我去相应的房间里面取代码就行了!从上面的门牌号(内存地址的概念)就应运而生了指针了,对没错,指针就是指向的门牌号,简单来说就是个记门牌号的!并且在计算机中我们的门牌号就是直接用整数表示的,比如第一个门牌号就是0,然后一次递增,下一个房间的门牌号就是1,然后就是2。用上面的示意图举例:

QQ程序的门牌号(内存地址)就可以是 :3

微信程序的门牌号(内存地址)就可以是:6

。。。

那么我们就可以用个指针来记录这个整数,下次要用这个程序的时候就可以直接到这个指针记录的房间去取程序!

2.2 c语言里面的指针

c语言的指针和*号分不开,我们一般定义指针如下:

int *p;
char *key;
float *f;

// 这个其实很好理解,指针的指针的指针
int ***pp;

有了上面的知识我们其实能够知道应该可以直接这么写:

#include<stdio.h>
int main()
{
    int *a = 1024;
    printf("%d",a);
}

        讲道理上面的操作是没问题的,我们直接把内存的门牌号给指针a,但是c语言觉得你这样不行,你不能直接把门牌号给这个变量,万一这个门牌号里面的内容是非常非常重要的其他程序使用的内存地址呢,你要是能直接赋值获取到,那岂不是你能为所欲为之终极为所欲为?????所以C语言只让你这样:

#include<stdio.h>
int main()
{
    int a = 3;
    int *p = &a;
    printf("%d",(*p));
}

我们知道我们每定义一个变量,那么系统会给我们的变量分配一个空的内存地址,然后该房间中存入我们的数据,然后你自己想干嘛干嘛,反正这个空间不重要,系统随便给你的一个没用的空间,随便你折腾。那么我们怎么获取一个变量的内存地址呢?答案就是通过&符号,你可以试试下面的程序:

#include<stdio.h>
int main()
{
    int a = 3;
    char ch = 'k';
    printf("%d,%d",&a,&ch);
}

你多执行几次,你会发现每次输出的结果应该都是不一样的,因为程序每次运行都给变量随机分配一个没用的空间,那么我们每次获取的地址都是不一样的。因此对于任何一个普通的变量获取地址就是用&符号

#include<stdio.h>
int main()
{
    float data[5];
    printf("%d",data);
}

注意数组的地址就直接是数组的名字,把这两个记住就行了,正常的变量就用&,数组就是数组名。

指针就是使用的*,定义很简单,直接:

变量类型 * 指针名字 ;

一定注意指针名字就是指针的,如果你用的(*指针名字)就是获取的指针指向的门牌号里面的内容

#include<stdio.h>
int main()
{
    int a = 3;
    
    char ch[5]={'1','2','3','A','d'};
    
    int *pa = &a;
    char *pch = ch;
    
    printf("这个获取的是普通变量的地址:%d\n",&a);
    printf("这个获取的是数组的地址:%d\n",ch);
    printf("这个获取的是指针指向的房间的内容:%d\n",(*pa));
    printf("这个获取的是指向的数组房间的内容:%d\n",(*pch));
    
}

请自己尝试最后一个输出的到底是什么?(应该是字符1,因为我这里输出的是%d,所以输出的是ASCII码49)

总结:

          1、申请指针: 变量类型 * 指针名字

          2、获取普通变量的地址: &普通变量名

          3、获取指针对应的内存地址中的内容:(*指针名字)

实际上就是单是一个指针那么就是代表的地址,(*指针)就是获取的内存地址的内容

或者有的同学说把指针理解成一种变量类型,这也是可以的,int *,float *,char *,long *这些新的变量类型

2.3 指针的基本应用----值传递和引用传递

        前面我们讲到了我想写一个具有交换两个数据功能的函数,那么你肯定会觉得很简单:

#include<stdio.h>

void exchange(int a,int b){
    int t = a;
    a = b;
    b = t;
    
}

int main()
{
    int a = 1;
    int b = 2;
    exchange(a,b);
    printf("%d,%d",a,b);
    
}

        但是你运行会发现并没有交换过来(在函数里面是交换过来了的),这是因为这种方式称为值传递,意思就是调用函数的时候,外面的main函数只是给了函数两个值,一个是1,一个是2,函数持有的1和2与原来的a、b没有啥关系,就是一个复印的版本(在内存空间中另外开辟的两个空间),见下面的图:

也就是你一顿操作猛如虎,结果一看全是操作的复印件,原件完全没影响到,这就叫做参数的《值传递》,所有的语言都区分这个

没错基于上面的问题诞生了引用传递,说白了就是把外面变量的地址传递给函数,那么函数修改的就是原件的内容就会在外面体现出来:

#include<stdio.h>

void exchange(int *a,int *b){
    int t = *a;
    *a = *b;
    *b = t;
    
}

int main()
{
    int a = 1;
    int b = 2;
    // 传入a,b变量的地址
    exchange(&a,&b);
    printf("%d,%d",a,b);
    
}

这份代码你运行发现就能实现值的交换!!!这是因为你传入的是a、b的地址,函数是针对这个地址的内容进行修改的!!!

        因此经过上面的讲解,我们在写函数的时候应该遵循一个原则,如果我们希望变量的值修体现在外部,那么就要使用指针作为参数,针对地址内部的内容进行修改,如果不希望值被改变那么就直接传值就行了。

2.4、指针的基本应用----和数组的爱恨纠葛

其实从前面的讲解我们可以知道在程序中,不论你申请个什么类型的变量,反正在内存中都会开辟一个相应空间给你存储变量的值!那么对于一个数组也是一样的道理,只是数组中的变量的空间是连续的,这也是我们为什么能使用数组名字[下标]来访问数组中内容的原因!实际上[]这就是个运算符,真正的原理是:

1、数组的名字是数组在内存空间开辟的起始地址(起始房间的门牌号)。

2、数组的下标是该房间偏离首部的距离

理解上了上面的东西,你看我也可以这样来操作数组:

#include<stdio.h>

int main()
{
    int number[10] = {1,2,3,4,5,6,7,8,9,10};
    
    int *p = number;
    
    for(int i = 0;i < 10;i++){
        
        printf("地址%d,中的数据是:%d\n",(p+i),*(p+i));
        
    }
    
}

上面代码的 p+i  就代表直接求得从起始空间向后面偏移 i 个偏移量单位 ,然后*(p+i)的意思就是获取空间里面的内容,这段代码的执行结果如下:

地址436318432,中的数据是:1
地址436318436,中的数据是:2
地址436318440,中的数据是:3
地址436318444,中的数据是:4
地址436318448,中的数据是:5
地址436318452,中的数据是:6
地址436318456,中的数据是:7
地址436318460,中的数据是:8
地址436318464,中的数据是:9
地址436318468,中的数据是:10

注意看地址中地址的变化情况(每次运行肯定地址不一样,但是两个地址之间的差值是一样的),然后你再用char类型的和float类型和long类型的查看效果!!有了指针后我们对数组的操作会更加的灵活!!!

2.5 指针的深入---简单链表

在将指针的时候不得不提到数组,我们平时如下的语句:

int number[10];

我们的计算机会在内存空间中自动开辟10个连续的房间存放数组里面的数据,那么这就会诞生一个问题,如果我这会数据量比较大,比如申请如下的数组:

    int array[1024*1024][1024*1024];

如果可以的话你再申请大一点,反正稍微大一点就会失败的,因为我们计算机内的内存空间不可能有很多连续的空房间(因为比如我们初识按顺序打开了如下的app,然后有的app从中途不使用了,被我们退出了(带颜色的),那么就会造成内存空间的零零散散的),可能你会说把占用的空间全部挪动到一边去,对这是一种可行的方法,但是在我们8G内存或者更大的情况下你挪动一次代价太大!!!!

因此一般数据量很大的情况下我们可以利用上指针,我们可以实现如下的一种结构:

        这种结构专业的名词叫做《链表》,可以将离散的东西串起来,这算是指针的应用之一吧!这个在哪可以使用呢?没错你的计算机内存空间和外存空间的管理就是使用的这种方式,通常来说你计算机中随便一个比较大的文件都是采用的这种方式。我们下面来看看我们编程在哪里可以用到链表!

我们都知道在数组中查询一个数据比较快,但是我们如果想要删除一个数据呢?是不是删除之后后面的数据得往前挪动?如果数据量比较大是不是很麻烦,但是你看我用链表怎么实现:

#include<stdio.h>

// 这个就是我们自定义的数据类型,说白了就是将几个简单的凑一块变成复杂的数据类型
struct node{
    int val;
    // 指向下一个该类型数据的指针
    struct node * next;
};

int main()
{
    // 创建链表
    struct node *head = new struct node();
    head->val = 1;
    struct node *first = new struct node();
    first->val = 2;
    head->next = first;
    struct node *second = new struct node();
    second->val = 3;
    first->next = second;
    struct node *third = new struct node();
    third->val = 4;
    second->next = third;
    third->next = NULL;
    
    // 输出:
    struct node * p = head;
    while(p!=NULL){
        printf("%d-->",p->val);
        p = p->next;
    }


    // 现在来删除数据2,一句话就删除了,而且不用挪动!!
    head->next = second;
    
}

操作示意图:

        删除的时候就相当于直接把数据1的那个指针指向了3!因此链表删除和增加数据效率是比较高的,但是查找数据效率并不高,每次都得从头挨个比较才知道是否是我们需要的,数组是查找快但是增加和删除慢

2.6 指针深度历险-----高级链表

2.7 指针深度历险-----树结构

2.8 指针的深度历险-----图结构

3、挥之不去的梦魇---NULL

3.1、NULL介绍

        我们在写带有指针的程序的时候一定会遇见的一个bug是NULL,通常来说我们的NULL是指针的结束标志符,被我们称为空指针,其中并不包含地址,所以不能出现访问该空间中的内容这种语句,否则会出现NULL异常。

       在我们刚学习指针的时候不太会遇见,在学习到了链表的时候我们再做讲解!

 

 

 

 

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页