C语言指针专题
C语言指针专题
c语言的指针,是c语言的灵魂,是c语言的重点也是难点。很多新人在接触c语言指针后很难理解,尤其是指针数组,数组指针,指针函数,函数指针,二维数组,多重指针等概念。
可能老师在教指针的时候会告诉你,指针就是地址,这是由于老师认为你在这个阶段并不能理解什么是指针,指针是地址说法会让你在基本的使用不会出错,但指针远远不仅如此。
下面换一个角度,从一个设计角度来看待指针和数据。
什么是数据?
说指针之前,先来说一下什么是数据,这是必要的,因为指针就是一种数据。要了解指针之前,你必须要先正确的认识什么是数据。
数据:能被计算机接收并且处理的客体信息(符号),都称为数据。(可能并不恰当,可以参考百度百科,这里只讨论计算机范畴的数据,在本篇文章中数据是这种定义)。
逻辑上的数据可以是一组学生,一串声音,一组图形。这些数据有的是离散的,有的是连续的。数据存储在计算机中称为数据的存储结构,就是数据在计算机中的存在形式。下面以一个班级为例来说明。
例1:假如一个班级叫计算机科学与技术2020届1班。尝试表达出这个班级。
|
计算机科学与技术2020届1班 |
|
|
数据的构成 |
含义 |
|
name |
班级名 |
|
Number |
学生数量 |
|
Mem |
学生成员 |
|
… |
… |
其实一个班级可有有很多很多的信息,在解决问题的时候,一般只会抽象自己需要的数据。下面逐个分析
(1) name: 是一组字符串,由一系列字符组成。可以保存ASCII码,顺序的保存一系列的数据,以\0标示着字符串终止。
(2) Number: 一个整形数
(3) Mem:一组顺序存储在线性表中的学生信息,每个学生又包括姓名,年龄,性别等信息。
世界上的数据太多了,在设计一门语言的时候,无法也不会去把每一种数据都设计好唯一的数据类型。所以怎么样对待数据呢?
从例1可以看出任何数据都可以拆分成原子类型。
原子类型:变量值不可再分解。是最基本的粒子。如Number无法继续分割。C语言中原子类型有 char; short; int ;long; long long;float等。
固定聚合类型:构成该变量的成分确定。比如复数可以由两个实数依确定的次序关系构成。一个二维空间的点只包含(x,y)。各自的位置和标示时确定的。
可变聚合类型:构成该变量的成分不确定。比如字符串,一组数据。
对于以上三种类型要注意区分,下面通过原子类型来说明c语言的数据。
数据在内存中如果存储和表示呢?比如一个数A:1234H,你如何将他存入内存。现在以1234H为例,将数据存入内存。
表
|
地址 |
数据 |
|
0000 |
9AH |
|
0001 |
CCH |
|
0002 |
34H |
|
0003 |
12H |
|
0004 |
7CH |
|
0005 |
8BH |
从上图中可以看出,数据被存放在了0002和0003处.至于为什么是34H在前12H在后这就牵扯到数据的存储大小端的话题,这里不再叙述。
所以总结下来,只需要知道,数据在计算机中表示,首先需要知道的是数据存储在什么位置。这叫数据的地址(注意,这严格说不叫指针,只叫地址,不要混淆这两个概念)
现在先释放A,再来存储一个数据B:34H,存入0002地址处。
表
|
地址 |
数据 |
|
0000 |
9AH |
|
0001 |
CCH |
|
0002 |
34H |
|
0003 |
12H |
|
0004 |
7CH |
|
0005 |
8BH |
现在内存中数据是不是和之前一模一样?现在读取B,从0002处获取数据。现在回忆一下A获取数据在哪,也是0002。为什么同样是0002,我们获取的数据分别是1234H和34H呢?
换句话说我们怎么区分读取一个字节还是两个字节?这就要靠数据的第二个属性,数据类型来标注数据的长度
现在记住数据在计算机中的两个最基本的属性:数据存储地址和数据类型,前者是数据的位置,后者是数据的长度。(还有其他的属性,比如大端和小端,对齐等,在这里先不去关心)。
什么是指针?
指针,是一个特殊的数据,它也能被计算机存储和处理。指针是计算机底层的东西,但是我不准备从存储地址这里直接切进去。我从更高的一层进行切入。
举个例子,什么是指针呢。比如你晚上睡觉的时候,忘记把第二天换的衣服拿好放在床头,第二天发现空调坏了,很冷你不想起来拿衣服,这个时候你发现你室友起来了,
你请求他帮你拿一下衣服,你用手指指向了某一件衣服说xxx,麻烦你帮我拿一下那件衣服。这个例子中,你没有自己起来去拿衣服,而是借助你室友帮你拿的,
如果你直接自己下去拿,衣服作为一个数据被你直接取到,这叫普通的变量,不叫指针,你让你室友帮你拿,你就是指针,你指向了一件衣服。
这就很方便了,因为你不用这样起来自己拿衣服了。
再举个例子,你是某个公司的老总,公司有很多的部门,你需要完成一个项目,对于整个公司事务很多,你并不想一直关注在一件事情上,你任命了一个部门经理,
告诉部门经理这个项目交给你们部门的A去做。后面的事情你不用直接去访问A组,而是通过部门经理询问这个项目完成的如何,部门经理询问A这个项目的进度。
这里面部门经理是指针,A是数据。就算后续再来项目,你也可以告诉部门经理某个项目交给谁。
我并不喜欢指针是钥匙这种说法,并不能很直观的表明指针的设计初衷。
同样的用比较计算机的方式来说,对于一组数据,你并不想去关注具体处理哪个数据,这时候你可以借助一个指针,指针的指向不用去管,
你只需要去处理数据,数据处理完让有关人员更改指针指向你继续处理,对于你来说,只是处理数据,和具体的哪个数据无关,从而简化操作。
就像例子二,你不需要去时刻记住这个项目目前由哪一组负责然后去询问哪一组,而只需要做告诉经理这个项目给谁,下面所有的跟进都是问项目经理项目进展如何了。
指针在计算机中又是如何被理解的呢?下面看看指针的主要属性。
还是上述的例子,我们现在用一个指针p指向A,A在内存中位置如下
|
别名 |
地址 |
数据 |
|
|
0000 |
9AH |
|
|
0001 |
CCH |
|
A |
0002 |
34H |
|
|
0003 |
12H |
|
p |
0004 |
02 |
|
|
0005 |
00 |
还记得变量的基本属性么
A的地址 0002 A的长度 2字节
p的地址 0004 p的长度 2字节(这里地址是2个字节,所以需要两个字节才可以)
首先从p的值获取A的地址0002,从0002处获取A的数据1234H。这就完成了指针的取值操作,如果改变p的值到0000获取的便是CC9AH
现在把A释放掉,B:0002存储34H,p指向B。
B的地址 0002 A的长度 1字节
p的地址 0004 p的长度 2字节(这里地址是2个字节,所以需要两个字节才可以)
通过指针p可以获取B的地址0002 在通过0002获取B的数据34H。
有没有发现p作为变量的基本属性没有改变,同样的操作为什么一个读取的1234一个读取的是34,换句话说我怎么知道从p存储的地址处取多少?
是不是说基本属性不管用了?答案是否定的,可以看到p的数据长度始终没有改变,和地址宽度保持一致。
p的数据类型的含义有点小变换,p的数据类型记录的信息是p的指针指向的数据的类型。(int *p称为int型指针)表示着指针指向的数据的长度。
以后在分析指针的时候,可以站在这一角度分析指针,这使得指针变得容易很多,下面来看一下指针的使用。
下面就把指针放在c语言中来看了
(1) 指针的基本操作
elemType *p;//定义一个数据类型
elemType A;
elemType *p = &A;//定义一个数据类型,并赋初值
p++;//将p加上一个elemType类型的长度。所有的加减不像普通变量一样,每次加减k,p的值都是 加上sizeof(elemType) * k;
普通的指针不再做详细的叙述,这些在任何一本教材上都有。
(2)数组和指针
数组:数组是一组连续的变量,比如 int A[5]。记住,数组我们虽然可以用下标进行索引,但是他已经不是int类型的了,他是由5个int型数据聚合的整体,说白了你就把他看成一个数据,是一个整体。
指针数组:语言上能区分,辣椒炒肉重点在炒肉上一样,指针数组重点在数组上,是一个数组,数组里元素的类型是指针。比如 int *A[5] 这*是和int结合的。
数组指针:重点在指针上,是一个指针,指针指向了数组。比如int (*A)[5],*和A结合,个人感觉c语言这里设计的很不好。很不好理解。
二维数组:可以理解为数组里面存储了一个数组。对于数组int A[m][n]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A[i]也是一个数组,这个数组里元素的类型是什么呢,数组内元素类型为int。
A是什么呢,也是数组,数组内元素的类型是一个长度为n的数组。
那么 &A[0][0] &A[0] &A都是什么意思呢?
其实如果使用printf打印出来,这三个得到的结果一样。
#define M 10
#define N 9
int A[M][N];
printf("&A[0][0] = %xH\n"
" &A[0] = %xH\n"
" &A = %xH\n",&A[0][0],&A[0],&A);
输出如下:
&A[0][0] = 62fcb0H
&A[0] = 62fcb0H
&A = 62fcb0H
&A[0][0] 是一个指针,指针的类型是什么?那看A[0][0]是什么类型,是int型吧,按照上面说的方法来统计一下,现在知道指针的值是62fcb0H 指针变量类型是int型的指针
&A[0] 也是一个指针,指针类型是什么?还记得上面说的么,它存储的是一个数组,那么指针的值是62fcb0H,指针的类型是一个含有N个int型数据的数组的指针,即 int [M]类型。
&A也是一个指针,指针类型是什么呢?按照上面的说法,它是一个包含有M和具有N个int型的数组的数组。指针的值是62fcb0H,指针的数据类型是int [M][N]
以上三个虽然值都是一样的,也就是说指向的起始地址都是一样的,但是指针类型的不同导致了他们三个有区别,最直观的区别是进行加减的时候,他们三个的值便产生了变化。
对三个地址都进行了+1操作,再次输出数据就产生了变化
&A[0][0] = 62fcb0H
&A[0] = 62fcb0H
&A = 62fcb0H
&A[0][0] + 1 = 62fcb4H
&A[0] + 1 = 62fcd4H
&A + 1 = 62fe18H
其中 &A[0][0] + 1 =62fcb0H + 4H = 62fcb4H
&A[0] + 1 = 62fcb0H + sizeof( int ) * 9 = 62fcd4H
&A + 1 = 62fcb0H + sizeof( int ) * 9 * 10 = 62fe18H
再次强调,在变量类型上看待二维数组
(2) 函数指针和指针函数
想一想辣椒炒肉吧,指针函数是一个函数,函数的返回值是指针。
函数指针是一个指针,是一个指向函数的指针。指针函数就不说了,下面说一下函数指针,这个在程序里应用很多,一般用在回调上。
指针函数:int *func(int a,int b);
函数指针: int (*func)(int a,int b);
函数指针怎么用
int compare(int a,int b)
{
return a - b;
}
int sum(int a,int b)
{
return a+b;
}
int main()
{
int (*funcpointer)(int,int);
funcpointer = sum;
printf("%d\n",funcpointer(1,3));
funcpointer = compare;
printf("%d\n",funcpointer(1,3));
return 0;
}
int (*funcpointer)(int,int);
int 是指向函数的返回值
funcpointer 是函数指针
括号里的变量表示参数。
然后他就能指向同类型的函数了,指向的时候直接把函数名给他即可
(3) 其他项杂谈
1 void型指针:上面说到了指针有指针类型的概念,如果在你设计一个函数的时候,并不知道函数的类型的时候怎么办,比如scanf函数,你输入浮点和整形使用的都是一个,
怎么办呢,他使用的就是void 的指针来接收的,然后根据你前面格式化的输入来确定指针的类型。也就是说,void类型的指针可以指向任何类型的指针,但是使用的时候必须转换回去。
2 多重指针:多重指针把这个指针当成普通的指针来看,他的指针类型是个指针罢了。

浙公网安备 33010602011771号