嵌入式学习一阶段——C语言:指针一

指针

是时候详细讨论指针的生活。因为在第一阶段的剩余部分,我们会非常频繁的使用指针

变量的属性以及左右值

int a = 5;
属性:
    变量名
    变量的存储单元:系统会为每一个存储单元分配一个唯一的编号,变量的地址
    变量的值:变量存储单元的内容
访问变量:
    a = 1024;  把数值1024存储到变量a的地址中去
    b = a;  把变量 a 的值赋值给 b
​
在C语言中,任何的变量,都有两个意思
(1)、左值 : 地址
(2)、右值 : 值
​
对对象的访问:
    read 读取变量的值
        从变量的地址中去取值
    write 赋值
        把一个数值写到变量的地址中去
通过上面的描述,是不是只要知道变量的地址,我们就可以读取/写入变量呢??  ------ yes
通过变量的地址去访问变量  ------ 指针
​
变量的访问:
    (1)直接访问
        通过变量名去访问,“受到作用域的影响”
    (2)间接访问
        通过变量的地址去访问,“不受到作用域的限制的”  只要知道你的地址,并且这个地址是可以访问的,那么通过变量的地址去访问

指针的相关的概念

内存和地址

上面我们是不是老是讲到存储单元(内存)和地址,在这里我们介绍一下内存和地址!!
我们可以把计算机的内存看作一条长街上的一排房屋,每座房子可以容纳数据,并且可以通过房号来标识!
但是也有一定局限性。计算机的内存是数以万计的bit组成,每个bit 可以容纳0 或者 1。由于一个位所能标识的值的范围太有限了,所以单独的bit的用处不大,同学需要许多的bit来组成一个单位,这样子就可存储范围更加大的值![1690854175882](image/1690854175882.png)
这些位置的每一个都称为字节(byte),每一个字节都包含了一个存储一个字符所需要的位数。在许多现代的机器上,每一个字节都包含8个bit,可以存储无符号数0-255,或者有符号数 -128-127 ,上面这个图并没有显示这个位置的内容,但是内存的每一个位置总是会包含一些值,每一个字节通过地址来标识的,如上图所示
​
为了存储更加大的值,我们把两个或者更加多的字节合在一起作为一个更加大的存储单位。例如:int类型是由四个字节存储的,下面这个图表示的内存与上图是一样,但是这次是以四个字节来表示![1690854128637](image/1690854128637.png)
​
由于他们包含了更多的位,每一个字可以容纳我的无符号整数的方位0-2^32-1 , 有符号的数-2^31 -2^31-1
​
注意,尽管一个int 类型包含了4个字节,他仍然有地址。(这个时候,有同学就好奇,为什么这个地址不能说是101,102,103,而是100),至于他的地址是最左边这个位置,还是最右边这个位置,不同的机器就有不同的规定!!
​
我们只需要对两件事情感兴趣:**1、 内存中每一个位置有独一无二的地址标识
2、 内存的每一个位置一定会包含一个值**

地址和内容

这里有一个例子,这次他显示了内存中5个int类型的内容![1690854701017](image/1690854701017.png)
​
这里显示了5个整数,每一个都位于自己的那四个字节,如果你记住一个值的存储位置,你一行可以根据这个地址来取得这个值
​
但是,你要记住所有这些地址是不是太笨了,所以高级语言所提供的特效之以就是通过名字而不是通过地址来访问内存的位置。下面这个图与上面这些图相同,但是这次使用名字来代替地址
当然,这些名字就是我们所谓的变量。有一点是非常重要,你必须记住的。
名字与内存位置之间的关联并不是硬件所提供的,它是编译器为我们实现。
​
内存,地址,内容我们都讲完了,接下来是不是该讲讲指针了

什么是指针?

在程序运行的期间,系统会为每一个对象在存储器(Memory)中,分配一段存储单元
存储单元,按照字节大小给每一个存储单元分配了一个唯一的编号,这个编号称之为存储单元的地址
指针的概念和地址差不多,一个对象的地址,也可以称之为一个对象的指1

& 取地址符

单目运算符
对象x的地址 &x
    这个表达式的值就是x的地址(“以整数形式的值 非负数”)
​
eg:
    int main()
    {
        int a;
        scanf("%d",&a);
        printf("%d\n",a);
        printf("%p\n",&a);
//按照地址的形式(16进制输出),如果我们的机器是32bits,输出32bit的16进制 如果是64位,输出就是64bit
        printf("%u\n",&a);  //按照十进制输出,unsigned int
        printf("%x\n",&a);
    }

* 指针运算符

单目运算符
    * 地址  《--------》 地址对应的那个对象
eg:
    int a = 5;
    * & a  : * 接一个a地址,整个表达式,等价于 这个地址对应的那个对象
    ====》
    * & a ==== a
​
* & a = 1024;
    b = * & a;
​
& * & * a 是什么意思?? * & 可以约掉*
  • & * & a ==== a & * & a ==== &a

    note:* 地址等价于 地址对应的那个对象 不是变量的值或者变量的地址,因为那个对象,在不同的上下文,可能是左值,也可能是右值。 int a =4; int* p = & a;

    • p = * ( & a) = a

指针变量

A 指针
B 变量
这个问题好像就在问赵英哲是不是人??
换句话说就在问一个男人是不是人??
白马是不是马?

指针变量就是一个变量,只不过这个变量保存的是另外一个对象的指针(地址)
指针变量和普通变量有点儿不一样的,这玩意保存的另外一个对象的地址

指针变量的内容

讲了什么指针变量,那么指针变量里面存的是什么东西呢??我们把话题回到之前那张图
我们现在对这些变量进行一个声明!!

int a =112; int b = -1; float c = 3.14; int *d = & a; float *e = & c;

我们现在看看d和e的声明,他们可以被声明为指针,并用其他变量的地址赋予初始化。d和e的内容是地址不是整型或者浮点型。事实上,从图中也可以很容易的得出,d的内容与a的存储地址是一致的,而e的内容与c的存储地址是一样。
这也正是我们对两个指针进行初始化所期待的结果。区分变量d的地址(112)和它的内容(100)是非常重要的。
同时也必须意识到100这个数值是用于标识其他位置(是。。。。的地址)。在这一点上,房屋,街道这个比喻就不再有效,要用刚刚那个海盗寻宝的故事来举例,因为房子里面的内容绝对不可能是其他房子的地址。

在我们转到下一步之前,先看有些设计到这些变量的表达式

int a =112; int b = -1; float c = 3.14; int *d = & a; float *e = & c; 指针变量其实也很简单,d的值是100,e的值是108.如果你认为d和e的值分别是112和3.14。 这是一个非常极其容易常见的错误。 d 和 e被声明为指针并不会改变这些表达式的求值方式:一个变量的值就是分配给这个变量的内存位置所存储的值 如果你简单认为由于d和e都是指针,所以他们可以自动获取存储与位置100和108的值。那么你是错误的。

间接访问操作符

通过一个指针访问它所指向的地址的过程叫间接访问。这个用于执行间接访问的操作符是单目运算符 * ,我们还是用上面这个例子来讲:![1690860246101](image/1690860246101.png)

d的值是100,当我们对d使用间接访问符时,它表示访问内存位置100,并察看那里的值。
因此,“ *d的右值是112 ---- 位置100 的内容 , 它的左值是位置100本身 ”

指针变量该如何定义

普通变量的定义:
    变量的类型 变量名;
指针变量的定义:
    指向的类型* 变量名;   //定义指针变量时,变量名前面*,是用来标记后面的那个变量是指针变量

eg:
    如果p保存了a的地址,则我们是:p指向a
p = &a;
p指向a
p的指向的类型是:a的类型typeof(a)

&a 这个表达式,保存的a的地址
    &a 指向a

int a = 5 ;
    int b;
        //定义这个p
        //根据上下文,p 保存了&a p 指向a
        // p是一个指针,并且p的指向的类型是typeof(a)
    int * p ;
            /*
                p是一个变量,在C语言中,任何变量都有左右值:
                    p的左值是:p本身的地址
                    p的右值是:p存储空间里面的内容 &a
            */
    p = &a;
        //*p = *&a = a
    *p = 1024;
    printf("a=%d\n",a);

b = *p;
    printf("b = %d\n",b);

指针变量的使用

p = &a;
*p => *&a =>a

野指针和空指针

野指针

是一个指向未知(undefine),不确定的地方的指针
未知的,不确定,指向的地方可能存在,也可能不存在,并且指向的地方可能可以访问,也可能不能访问。
对于野指针的内存访问,会产生什么结果??
    可能可以访问,也有可能是访问不了(导致非法访问)
非法的内存访问:
    不存在的地方,你去访问
    存在但是不能读的地方,你去读了
    存在但是不能写,你去写

非法的内存访问:段错误“Segment fault”,系统就把你的这个进程杀掉

下面这个代码是常见的错误
    int *a;
    *a = 10;
你声明了一个名为a的指针变量,后面的那条语句是把10存储在a所指向的内存位置

note:
    但是究竟a指向哪里呢??我们声明了这个变量,但从未对他进行初始化,所以我们没有办法
    去预测10这个值会存储到声明地方。所以 如果一个程序给指针变量来了一个赋值的操作,会发生什么情况呢??
    如果你的运气好,a的初始值是一个非法的地址,这样子的赋值的语句就会因为程序出错而终止程序。

例子:
    (1)、int *p;   // 你不赋予初值,不代表p没有值,相反 p 是一定会有一个值
                    //只不过这个值,你不知道而已,并且还没有办法知道
        请问p是野指针吗?
    (2)、int *p = 5;  //p是指向一个地址为5 的地方,p指向了一个确定的地方
            不是野指针
    (3)、
int *p;
            int a;
            p = &a;

int a;
            int *p = &a;

int a;
            int *p;
            p = &a;

空指针

空指针是指向空(不存在的地方,NULL) 的指针
空指针不是野指针,但是空指针指向的地方,一定不能访问
对空指针的访问,是非法的内存访问 =====》 “Segment fault”
    int *p = NULL;
    *p;   //段错误

指针的加减

a+b
a-b
只要求a,b是一个数,整数,浮点数
a,b其实也可以是一个指针,指针的值也是一个整数
指针是可以做加减操作的(但是进行加法的两个数字不能都是指针),指针的加减一个整数,表达式的类型还是原来指针的类型

相同类型的指针可以做减法(即'-'):算出两地址之间的相同类型的元素个数;
相同类型的指针不可以做加法,这毫无意义;
相同类型的指针可以做比较(即'<'运算)
相同类型的指针可以做赋值运算(即'='运算)

eg: int a; char b; typeof(a+b) : int typeof(a+3) : int

typeof(a+0.3) : double

.....
int *p;
typeof(p + 3) : int *
double *p;
typeof(p - 3) : double *

int a;
int *p = &a;   //&a : 0x300
    p+1;
&a+1;   //0x301 ???

指针做加减

p + i ,不是简单做加减数值,而是加减 i 个 指向单元的长度
从数值上来说:
    p+i    p的值 + i * sizeof(*p)

int*p = &a;
p+1;
    p+1*sizeof(int)
double *p = &a;
p+1;
    p + 1 * sizeof(double)
.....

p+i : p是一个指针(地址),i整数
typeof(*p)   -----> p 指向的类型
typeof(p)    -----> p 的类型
p+i  : 是在p的值的基础上,往后面移动了 i 个指向单元的长度

例子:
    int a[10];
    //数组元素的地址是连续的!!![1690871889096](image/1690871889096.png)
    &a[0]: a[0]的地址
    &a[0] + 1 : &a[1]
    &a[0] + 2 : &a[2]
    ......
    &a[0] + i : &a[i]

&a[0] : 是一个指针
    typeof(&a[0])  =========> int *
    指针的类型是如何描述??
        指向的类型 *
    &a[0] + 1 : int* + 1
    &a[0] + i : &a[i]

数组名当作指针

数组名可以当作指针来用

结论:
    数组名可以当作指针(指针常量)来用,数组名当指针用的时候,数组名可以看作是指向第一个数组元素的指针常量。
已知a是一个数组名,a当作指针来看,a == &a[0]

数组名既可以代表整个数组,又可以当作指针来用,那么什么时候当作指针,什么时候代表整个数组
(1)、在如下情况,代表整个数组
    sizeof(a);
    typeof(a);
    &a;
(2)在其他的情况,就当作指针来用咯
    p = a;  a当作指针来用
    a+1;   a当作指针来用
why???
eg:
    int a[10];
    int *p;
        //想让p指向数组的第一个元素
    p = &a[0]; (1)
    p = a; (2)
    (1)and(2) 的含义是一样的,在(2)处,a当作指针来看,&a[0]

int a[10];
    a+1;
    a+i;
    *(a+i);
         //a+1 这个表达式中,a当作指针来用
         // &a[0] + 1 === &a[1];

// printf("a[0] = %p\n",&a[0]);
        // printf("a + 1 = %p\n",a+1);
        //a + i =====> &a[i]

//*(a+i) ===> *(&a[0] + i) ====> *(&a[i]) ====> a[i]

结论:
    *(p+i) <======>p[i] , when i > 0 ;

多维数组

数组名可以看作是一个指向第一个元素的指针常量!!!

eg: *((a+1)+2)

* ( * (a+1)+2)![1690874191556](image/1690874191556.png)

练习: int a3;

a[0]+1 :此处数组名a[0],当作一个指针在用
        a[0] + 1 ====> &a[0][0] +1 ===>&a[0][1]
a+1:   此处数组名a,当作指针在用
        a+1 ===> &a[0] + 1 ===> &a[1]
a:
(1)代表整个数组
    (2)当指针在用,代表首元素的地址 &a[0][0]
        typeof(&a[0]) : typeof(a[0]) * ====> int[4] *

a[0]:
    (1)代表二维数组中某一个一维数组
    (2)当作指针来用,代表首元素的地址 &a[0][0]
    typeof(&a[0][0]) : typeof(a[0][0]) * ====> int *

&a[0][0]:
     typeof(&a[0][0]) : typeof(a[0][0]) * ====> int *

&a[0]:
    typeof(&a[0]) : typeof(a[0]) * ====> int[4] *

&a:
    typeof(&a) : typeof(a) * =====> int[4][3] *

a[3][4];
a[0][2] <====>*(*(a)+2)
*(*(a)+2) = *(*(&a[0])+2) = *(*&a[0] + 2) = *(a[0] + 2) = *(&a[0][0] + 2) = a[0][2];
*(*(a+1)+4) = *(*(&a[1])+4) = *(a[1]+4) = *(&a[1][0] + 4) = *(&a[2][0])  = a[2][0];

数组指针和指针数组

数组指针是什么??

A 数组 B 指针

数组指针就是一个指针,指向一个数组的指针
函数指针就是一个指针,指向一个函数的指针

语言习惯: 周旋丰是一个非常优秀,非常帅气,非常.......的人 zhouxuanfeng is man that ..........

例子: int a[4]; // int[4]

int[4] *p;  <=====> int (*p) [4];

int (*p) [4]  ====> int[4] *p;

//p指向a
p = &a;

// p = a;  // p = &a[0]  p指向a[0];

指针数组

指针数组是一个数组,里面的元素全部是指针
eg:
    int * p[4];  ====> int* p[4]
    p 是一个含有4个int * 类型的元素的数组
    p也是我们的数组名
eg:
    int * p[4];     //定义了一个数组,名字叫p,里面放了4个指针类型的元素
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    p[0] = &a[6];

printf("%d\n",*p[0]);  //7

练习:

(1)、分析如下代码: int a[4] = {1,2,3,4}; int *p[4]; //指针的数组 for(int i = 0 ; i< 4 ; i++) { p[i] = a+i; // } for(int i = 0 ; i< 4 ; i++) { printf(p[i]); }

(2)、分析如下代码: int a[4] = {1,2,3,4}; int (*p)[4]; //数组的指针 指向一个 int[4]类型的数组, int[4] * p = &a; //我们是不是讲了 用一个指针去指向某个地址, 指针的类型和指向的类型应该是一样 //为什么这里是&a //如果通过p去访问a[3]这个元素,并且打印出来 //a[3] = *(&a[3]) = *(&a[0]+3) = *(a+3) = *( *(&a) +3) = *( * (p ) +3); printf("%d\n" , __);

(3)、分析如下代码: int a3= {1,2,3,4,5,6,7,8,9,10,11,12}; int (*p)[4]; //数组指针 到底是干嘛的?? p = a; //为什么这里是a赋值给了p printf(p1); printf(p0);

p = a+1;
    printf(p[1][2]);

(4)、int a[12] = {1,2,3,4,5,7,8,9,10,11,12}; printf("%p %p %p \n", a , a-1 ,&a-1);

(5)、int b3 = {1,2,3,4,5,6,7,8,9,10,11,12}; printf("%p %p %p %p\n", b,b+1,&b+1,b[1]+1);

(6)、 int a[5] = {1,2,3,4,5}; int *ptr; ptr = (int )(&a+1); printf("%d\n",(ptr-1));

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值