万千只cnm 发表于 2021-8-6 14:03:59

c指针及其使用详解

本帖最后由 万千只cnm 于 2021-8-6 14:03 编辑

                                                                                             c指针及其使用详解

众所周知 c语言学习过程当中 指针一直是个重点和难点 ,此帖谈一下我看完《c与指针》等书之后的见解;
希望能帮到大家,内容有不对的欢迎大家指正{:5_91:}
目录:
       一: 内存中数据是如何存储的
                  二:指针是如何寻址的
                  三:数组                                       

                         :特性
                         :数组(名)与指针区别
                  四:类型
                           :数组名的类型
                           :指针类型
                           :类型相融
                  五:指针运算
                  六:复杂指针类型/声明
                  七:函数指针/指针作为参时
                  八:附:左右值区别(操作符 不懂的童鞋先看这
      
                  
                                                               一
要想彻底理解指针及其概念 ,就得弄清楚数据如何存储的 ,这里涉及到底层的一些知识 推荐一下小甲鱼讲的王爽版本的汇编 : https://www.bilibili.com/video/BV1Rs411c7HG?from=search&seid=5397185164506602840
(满满年代感{:5_104:}
1cpu只认识二进制不管是数据,还是要执行的代码(也是数据),在内存中就是一连串的二进制数字 CPU和内存之间需要读取或存储数组,之间就需要一个沟通的桥梁,这个桥梁就叫做地址总线(总线有三种:地址/数据/控制)。

那为什么叫地址总线而不叫数据总线?这是因为CPU在读取内存中的数据时的第一步是先找到数据的地址,有了地址才能把数据的具体位置找到,然后通过数据总线传输。



就像在商店里买东西,先得找到物品,才能付钱买{:9_223:}



2
    内存中最小的储存单元是一个字节,可以把这一个个字节看成一个个房间,在这个房间中能储存数据,这些房子会整整齐齐地排成一排。
这个时候就有问题了,CPU应该怎么确定数据是储存在哪个房间呢?这个时候就要靠地址了,可以把这个地址看成房间的号码,显而易见的是每一个房间的号码是必须要是不同的

如8086能寻 1MB的 范围地址(段地址加偏移量,类似下标法寻址法)如debug程序中:



   
如上图最左边 073F:01bd就是地址(073f是段地址 冒号右边就是偏移地址) ,右边就是地址中所储存的数据(以16进制显示,两个为一字节)


而我们的数据就是存储在这些地址中,对这些地址中的数据进行操作以达到程序的目的
但跟c语言和指针有什么关系呢?
                                                                  



1
我们都知道源代码是经过编译器和连接器 才能成为可执行文件方能运行
前面说了cpu是通过地址来寻找所需内容的,当然不认识变量名 ,这里就要提一下符号表:https://blog.csdn.net/qq_30120725/article/details/47064035

一个变量名其实就相当于一个符号,代表一个具体的地址位置 注意:代表一个位置(地址),地址里存放的数据(内容)与地址本身不同
编译时自动转换为内存中相应的具体地址。


举个列子 我们先写一个源程序




生成汇编程序:




可以看到并没有出现a这个变量名。
而是在栈段中操作,有同学就要问了,这也看不到地址呀?
众所周知 自动变量就是储存在栈中,栈当然也是内存中的地址啦,栈中数据通过ss sp两寄存器来对数据进行操作 ,相当于数组通过下标来寻址
这个例子中a就在栈顶以下8个内存单元 ,把666这个值“赋值”给对应位置(地址
movl      $666, -8(%rbp)                //地址为栈顶以下八个内存单元2                   当我们定义一个变量
int b = 666;我们只知道 b的值为666 怎么知道b变量的地址呢 ?
------- 在c语言中 我们可以用 &操作服来获取变量本身的地址


      重点来了,当我们定义一个指针变量并初始化
int *p = &b;我们把取了整型变量b的本身地址(不是里面的值
就可以说    指向整型类型的指针p指向了整型变量b

3
综上
任何变量都有其本身的地址,还有其里面的内容(除放在register中的变量


假设 变量b的地址为:0x123指针变量p的地址为0x678
p指向b时:
(纯手画{:10_281:}
   可以看到 0x678这个地址当中的内容 是0x123,也就是a本身的地址,而不是a的值(内容)666


那我们如何通过指针 获取指向变量里的值呢? 当然是通过解引用

正如 :
*p ;
此时 *p表达式的值就是 a里的内容整型值666了

我们无需知道指针本身的地址,只需要知道指针里存放的内容,也就是指针的指向 (如 0x678 对我们并不重要,只需要知道指向0x123
看到这大家有没有理解指针呢?{:7_138:}

         
三(数组


c语言中 指针与数组密不可分,也很容易混淆
数组就是数组 ,指针就是指针,不可以混为一谈


1
数组的特性:
               数组里的元素类型是统一的
               数组是可以随机访问的,里面的元素也是匿名的
                   --- 可以通过(且只能) 首地址+偏移地址 来访问数组里的元素    如array【2】== *(array+2)
2
提供一种理解 多维数组/复杂数组的角度 :
    c语言的数组都是一维的,只不过元素可以是任意类型
如:
int array;      可以理解为: array是一个拥有两个元素的一维数组   里面元素的类型 是一个拥有三个int型元素的的数组

3
数组与指针的区别:
                   不同:
      a: 数组在定义时就分配好所有空间 ,而指针只分配了本身的空间
int arrar ;   //<span style="background-color: rgb(255, 255, 255);">分配好了10 *sizeof(int) 字节的空间</span>         指针只为自身分配空间,而不会为指向的对象分配空间
          b: 数组 :保存数据       指针:保存数据的地址
               数组名就相当于一个符号,代表了首地址,里面元素线性排布


             c:取值的方式不同
                  符号(变量)本身的地址编译时就可以知道 ,而符号里的内容运行时才可以知道
                     指针:
                            1 .取本身地址里面的内容 (指针分配了自身的空间
                            2. 对值(内容)进行解引用

                      数组:
                               1. 直接对本身地址进行解引用
------------------
可能有同学要问了:啥意思呀有什么区别?
举个列子
    在一个源文件中
int array={1,2,3};   另一个源文件中
extern int *array;
*array = 0;
编译器会认为是个指针
                         取array地址,访问里面内容 ----整型1   -->会认为整型1是要访问的地址!
                           会将内存地址为 1的空间进行赋值 ,非常危险!!!!!

             d:数组名是不可修改的左值
                                       数组名能修改里面的元素不就找不到了{:10_281:}

               相同:

         表达式中数组名就是指针,因为最终代码都是具体地址
   如:int array;
   int *p = array;
p;
array;通过数组名和指针访问都是可行的 ,都是通过指针类型的运算获得元素的地址进行访问
-----
          这个结论对于牛角尖的同学来说确实有列外:就是把数组当作整体来看
            1 数组名作为sizeof 操作数时 ,此时是整个数组的大小
                     2 使用 &取数组的地址时
                     3 数组是一个字符串常量初始值
                                       
四(类型

所谓指针的类型-- 就是指向   ~~   的指针 ,可以构建非常复杂类型的指针
当然数组(名)也有类型

1
结论:
       数组名的类型就是    指向其元素类型的指针



int array;//一维数组 ,里面元素 是包含3个整型的数组
   int (*p1);// 数组指针


p1 = array;此时可以赋值,类型相同
array数组里的元素 是一个数组 -->   array的类型就是 指向数组的指针!


注: 变量作为左值和右值 所代表的意义不同(详看第十节


2
附: 两个指针类型是否相融(是否可以进行赋值
         要满足两点 :
                              1. 指针 指向的类型要相融(一个指向char 一个指向int 当然不行
                              2. 等式左边指针 所指向的类型 必须具有右边指针 所指向类型的 全部限定符(const等


如:
#include <stdio.h>
void fun(const char **p){
    ;
}
int main(int argc,char **argv) {
   
    fun(argv);
    return 0;
}
是否会发出警告呢,错在哪了 (不要上机测试)
欢迎评论{:9_228:}


3
指针常量
      用一个字符串常量给指针赋值char *p= "I love fish";    字符串常量的值是一个地址!且放在静态存储区      ----->p指向的内容是不可以修改的,本身可以修改   

五(指针的运算


大部分类型变量都可以进行运算,指针也不列外

1
   先说一下“步长” :
          所谓步长,就是 指向类型的宽度(长度)   
int array;            我们把array当作一个指针来看 --》 指向整型的指针
             这里一个步长就是sizeof int 字节

2
运算:
       加法运算(与整型) : 数组匿名访问 随机访问的基础,语法糖的原理


指针(数组名) +1-->    地址+一个步长
int array={0};
array; 这里 array 就是: *(array +2)--》
                                                             1.取首地址
                                                      2.加上2*sizeof(步长) ,这里就是地址+8(假设一个整型4字节)指向后俩个元素
                                                      3.解引用进行访问
注:array也可以用指针替代       这也是数组为什么只能省略第一维的原因 :要知道元素步长 以通过运算访问

       减法(指针之间):
               指针减法需满足
                                    同一个数组之间&&指针类型相同--> 返回的是其元素相隔的距离(会自动除以步长


-------------------
其他运算符(如加法) ,没有任何意义



六(复杂 指针/声明


指针与数组可以组合成非常恐怖的声明
但如果你看懂了上面几节 ,如指针数组 数组指针 等就会迎刃而解


复杂声明:
https://blog.csdn.net/wangweixaut061/article/details/6549768    指针数组等:
https://blog.csdn.net/warrior_harlan/article/details/78556346

   总结小秘诀 :
                     1:复杂定义中 只要看见了 (*符号)-->就是一个指针
                               2: 声明转化为表达式的结果类型 就是声明的类型

int (*(*a)(int *));   (*符号)-->是个指针       (*(*a)(int *))表达式的值就是int 类型,这也是c语言中非常有趣的现象
   a到底是什么指针呢 欢迎评论{:10_264:}
                            3 定义只需要 去掉符号,外面加一层括号--》 就是强制转换啦


int *a;(int *) 就是强制转换时 目标类型

七(函数/数组/指针




1
函数调用时其实 也是通过函数指针来完成的
int f(int );
    int (*pf)(int) = &f;
// int (*pf)(int) = f;也可以 函数名在被使用时,总是由编译器转换为函数指针,指向内存中的代码块
int ans = f(25);
int ans = (*pf)(25);//这三种都可以
int ans = pf(25);这里 f被转换为指针

2
当我们向函数传数组时 :


char str="fishc";
    printf("%p \n%s\n",str,str);

输出:


有同学就问了: 为什么一个标识符能打印两种不同结果呢 ?

----当数组作为函数参数时 都会被转换为指向元素的指针
   形参就是真的指针,而不是当作;
实参 char str;               匹配形参 char(*);
       char *str;                              char **;

所以这就是
gets函数会溢出的原因 : 传数组,实际是指针 -->函数无法通过指针检查到数组边界!!!

八(左右值)



所谓左右值 就是出现在 = 左右两边
左边叫左值,右边叫右值(废话{:9_237:}      
https://fishc.com.cn/forum.php?mod=viewthread&tid=69833&extra=page%3D1%26filter%3Dtypeid%26typeid%3D571



1
能否作为左值 关键是:
                               符号能否代表一个具体位置(地址)


如数组名,就可以当左值(不能修改的左值),代表数组的首地址
如整型 666,不能当左值 ,只是个值(常量)!!




2
变量当左值与右值时 , 意义不一样
int a = 666;
int b;


b =a ;整型变量a做右值 -->代表的是符号a地址当中的 值
------

a = 123;a做左值--> 此时符号a代表本身的位置(地址)要把值123存到a代表的地址中
我们只关心a的地址 来存放数据

3
操作符 来判断左右值
表达式是由许多操作符和符号组成的--》 一个表达式的值能否成为左右值 可以依据操作符


总结:
   
[]下标访问
.   成员访问
->指针成员访问
*   间接访问这四个操作符的表达式的值 能做左值(代表一个位置

--------------------------------
而其他操作符 如 : ++ * / 等
                                        结果(值)都是一个值!! 只能当右值












最后: 本人水平有限希望能帮助大家,有什么错误或不理解地方都可以提问,我都会回答{:9_236:}
   也祝各位鱼友越来越牛,网站越办越好!




万千只cnm 发表于 2021-8-6 14:04:00

坏了 怎么没自动保存 咋办{:5_104:}{:5_104:}{:5_104:}{:5_104:}

万千只cnm 发表于 2021-8-6 14:04:01

心态崩了呜呜呜 都快写完了{:10_245:}

万千只cnm 发表于 2021-8-6 14:04:02

没保存重新写了一次 ,希望大家喜欢{:9_221:}

Daniel_Zhang 发表于 2021-8-21 16:41:15

加油{:10_254:}

万千只cnm 发表于 2021-8-21 16:48:14

Daniel_Zhang 发表于 2021-8-21 16:41
加油

{:5_91:}谢谢

小伤口 发表于 2021-9-9 15:09:05

写的真好,找时间好好研究一下{:10_254:}

万千只cnm 发表于 2021-9-9 22:49:54

小伤口 发表于 2021-9-9 15:09
写的真好,找时间好好研究一下

{:5_100:}谢谢

yangyiheng 发表于 2021-9-18 22:42:17

写得非常好,一看就懂

万千只cnm 发表于 2021-9-18 22:55:33

yangyiheng 发表于 2021-9-18 22:42
写得非常好,一看就懂

能评个分嘛{:5_91:}

yangyiheng 发表于 2021-9-19 07:43:29

万千只cnm 发表于 2021-9-18 22:55
能评个分嘛

抱歉,用户组太低,没办法评分

嘉岳呀 发表于 2021-9-30 21:04:53

不错

飞花落尽 发表于 2021-10-31 15:57:15

666

飞花落尽 发表于 2021-10-31 15:57:46

会继续更吗?

210687245 发表于 2021-11-2 21:11:57

写的很好

香蕉那个不拿拿 发表于 2022-8-24 19:13:16

祝你申精成功{:10_256:}{:10_256:}

asky533 发表于 2022-8-24 22:38:43

看到申精贴,过来顶一把
不过确实讲的非常非常非常非常非常非常好!!!
页: [1]
查看完整版本: c指针及其使用详解