找回密码
 注册
关于网站域名变更的通知
查看: 479|回复: 1
打印 上一主题 下一主题

C语言“难啃的硬骨头”之指针(一)

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2019-11-18 16:24 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
C语言在嵌入式学习中是必备的知识,审核大部分操作都要围绕C语言进行,而其中有三块“难啃的硬骨头”几乎是公认级别的。

  [1 Z0 X+ \2 I! `, F" ^' p' u& y' y: L
01
指针
指针公认最难理解的概念,也是让很多初学者选择放弃的直接原因
指针之所以难理解,因为指针本身就是一个变量,是一个非常特殊的变量,专门存放地址的变量,这个地址需要给申请空间才能装东西,而且因为是个变量可以中间赋值,这么一倒腾很多人就开始犯晕了,绕不开弯了。C语言之所以被很多高手所喜欢,就是指针的魅力,中间可以灵活的切换,执行效率超高,这点也是让小白晕菜的地方。
  s2 f, @. h' j. o  u% `
指针是学习绕不过去的知识点,而且学完C语言,下一步紧接着切换到数据结构和算法,指针是切换的重点,指针搞不定下一步进行起来就很难,会让很多人放弃继续学习的勇气。

( y0 X) h0 a4 i5 ]) a# Q0 G
指针直接对接内存结构,常见的C语言里面的指针乱指,数组越界根本原因就是内存问题。在指针这个点有无穷无尽的发挥空间。很多编程的技巧都在此集结。

" I* P9 C9 B: J: s+ _! s2 W7 w
指针还涉及如何申请释放内存,如果释放不及时就会出现内存泄露的情况,指针是高效好用,但不彻底搞明白对于有些人来说简直就是噩梦。
在概念方面问题可以参见此前推文《对于C语言指针最详尽的讲解》,那么在指针方面可以参见一下大神的经验:
/ c: ~8 \1 L" O; x' X- p# i
. m& ~/ A: _! f+ _  A: T1 j
▎复杂类型说明
7 s7 v7 q: M4 P4 S
要了解指针,多多少少会出现一些比较复杂的类型。所以先介绍一下如何完全理解一个复杂类型。/ c3 ]1 p$ C0 ?$ c5 f4 d) ]

3 @, a0 E( `; y3 |1 j! _8 [
要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样。

* W* ?2 q+ }* m& M2 Q4 v: T2 O* ]
所以笔者总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析。

0 u7 c: T. _* ^
下面让我们先从简单的类型开始慢慢分析吧。
/ g. X, I; ~1 u8 {
  • int p;
    4 B- h. }5 [& Z4 n! ~% \2 M  X% o

6 U7 ]2 \5 K  N3 F" q7 d
这是一个普通的整型变量
0 j9 r/ D; |* X  e  t) O5 C$ b4 ~! H
  • int p;

    3 C# y. G/ V  n! u- S  |
% f$ O$ H* ^* X( |1 D- e% Z8 ~
首先从P处开始,先与结合,所以说明P是一个指针。然后再与int结合,说明指针所指向的内容的类型为int型,所以P是一个返回整型数据的指针

6 D# V* D! X# @* B; q2 i
  • int p[3];
    1 j5 ]+ s4 N; D
; C  i' J4 T: A6 S2 [
首先从P处开始,先与[]结合,说明P是一个数组。然后与int结合,说明数组里的元素是整型的,所以P是一个由整型数据组成的数组。

9 d6 y5 s) `5 V4 Z7 u6 F. E
  • int *p[3];

    ! |; A  Q  i# J! h/ }1 J2 T3 C
; }5 |0 Q  g- E
首先从P处开始,先与[]结合,因为其优先级比高,所以P是一个数组。然后再与结合,说明数组里的元素是指针类型。之后再与int结合,说明指针所指向的内容的类型是整型的,所以P是一个由返回整型数据的指针所组成的数组。
4 L% G% o# `+ r6 B1 |, D- W: h
  • int (*p)[3];

    ' X4 a/ l4 j% c( S  Z) ?3 S
6 |- d# H& c0 Z7 ~' m: G
首先从P处开始,先与结合,说明P是一个指针。然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组。之后再与int结合,说明数组里的元素是整型的。所以P是一个指向由整型数据组成3个整数的指针。
& z7 }, n. G3 `4 I
  • int **p;
    ; u7 Y6 Z& v/ Y% F! |/ Q
! A! p) N4 J" G
首先从P开始,先与*结合,说明P是一个指针。然后再与*结合,说明指针所指向的元素是指针。之后再与int结合,说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针。

8 M# K6 q8 ~( m2 l. R2 ]3 ?) ~
  • int p(int);
      v/ V! Z1 n/ ]2 |( ~; ~
( D9 n9 |, W4 n
从P处起,先与()结合,说明P是一个函数。然后进入()里分析,说明该函数有一个整型变量的参数,之后再与外面的int结合,说明函数的返回值是一个整型数据。
" B+ i% R! D+ ^  b/ B$ p( q5 J
  • Int (*p)(int);
    ( K; I# C% o% H
' u6 C$ w3 @+ V, @
从P处开始,先与指针结合,说明P是一个指针。然后与()结合,说明指针指向的是一个函数。之后再与()里的int结合,说明函数有一个int型的参数,再与最外层的int结合,说明函数的返回类型是整型,所以P是一个指向有一个整型参数且返回类型为整型的函数的指针。

9 e- z# h9 F  \0 m$ Z5 W8 h& u
  • int (p(int))[3];
    3 b8 H. d  Q7 _0 J1 C
  C' q+ L4 M& W: N
可以先跳过,不看这个类型,过于复杂。从P开始,先与()结合,说明P是一个函数。然后进入()里面,与int结合,说明函数有一个整型变量参数。然后再与外面的结合,说明函数返回的是一个指针。之后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组。接着再与结合,说明数组里的元素是指针,最后再与int结合,说明指针指向的内容是整型数据。所以P是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。

. K: ?8 o+ O" d
说到这里也就差不多了。理解了这几个类型,其它的类型对我们来说也是小菜了。不过一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用。这上面的几种类型已经足够我们用了。
4 D+ G9 l8 d( |4 e
细说指针
" t0 G& k4 S; D1 F  }6 {
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。

' i" f6 g3 r, z# J+ b4 B, \
要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。

; K1 G+ G% Y/ M
先声明几个指针放着做例子:! ~  h4 X: v3 Q; J4 J
6 e0 E: x3 i; R0 m/ j( }! D2 W/ v
(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
# f' r! e1 Q  i/ O1 L: B7 ]( T
指针的类型
( L9 S* R- A- S3 F% X
从语法的角度看,小伙伴们只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。

9 h6 o. q8 {! H2 M( {! q
让我们看看上述例子中各个指针的类型:
% z3 b" I% g3 l4 H" F
2 @- n' r3 C* G4 o; b
(1)intptr;//指针的类型是int
(2)charptr;//指针的类型是char
(3)intptr;//指针的类型是int
(4)int(ptr)[3];//指针的类型是int()[3]
(5)int*(ptr)[4];//指针的类型是int(*)[4]

7 N. M+ n" h* C! J6 H7 Y; I
怎么样?找出指针的类型的方法是不是很简单?
8 Z/ l( F- F( o  J& H1 n* |2 \2 Y
指针所指向的类型

" c. [- G7 L) u7 U# v: o+ A- B
当通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。

) u" h5 v3 R) k' F7 e
从语法上看,小伙伴们只需把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。

' ]+ @, u) F  x8 z+ E
上述例子中各个指针所指向的类型:

% x* r' r+ Z0 a
(1)intptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char*
(3)int*ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int(*)[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*(*)[4]
/ k* v# ~0 ?- N5 `0 _4 L" J* u
在指针的算术运算中,指针所指向的类型有很大的作用。
& T5 e3 D5 u+ I6 ?* Z+ q0 I
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当小伙伴们对C 越来越熟悉时,就会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。
. {" |6 |6 }4 }% {; p
笔者看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
' |2 ~& S6 |3 ?
▎指针的值
6 ^4 C1 Y5 f8 w6 R0 G4 x( Y
即指针所指向的内存区或地址。

8 V( O2 j: B% X8 ~/ y3 }
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。

4 `9 `' W* V+ u; m  w
在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。
# S8 [2 B& u0 Q( W" u. i# ?
以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。

3 Z( d, w8 [( H9 n6 Y; a! A; V$ a
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
0 G0 M  f* `8 R
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?

4 @' p7 ^: q! F. M+ u) g
指针本身所占据的内存区

5 Z" l+ Q  ?  r+ D
指针本身占了多大的内存?只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据4个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
7 V  G& z* T# k" H' A' M
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-10-11 13:57 , Processed in 0.156250 second(s), 26 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表