首页 > 其他分享 >大集合!!C语言指针知识要点大合集!!小白不要错过喔!!收藏这一篇就足够!!(1)

大集合!!C语言指针知识要点大合集!!小白不要错过喔!!收藏这一篇就足够!!(1)

时间:2024-05-29 11:59:07浏览次数:11  
标签:变量 int C语言 数组名 num 数组 合集 指针

指针!!是C语言最本质的特征,学好了指针才能算正式入门C语言喔!!如果你是C语言小白,看这篇文章就对啦!!✍

什么是指针?

在学习指针之前,我们要先了解内存。我们的代码在运行的时候,会把数据存放在哪里?放在内存里!

内存地址

内存就像是一栋大旅馆,这栋旅馆里有许多房间,每个房间有着不同的房号,房间里面住着人。

内存与旅馆
大旅馆
房号101102103……505……
客人TomBenKiki……Ami……
内存
地址0x12ffff660x12ffff6a0x12ffff6e……0x13ffffff……
数据二进制数据1二进制数据3二进制数据4……二进制数据n……

如图所示,通过这样类比,内存也被划分为许多块,划分为最小的内存单元是bit。1 bit只能存放一个二进制数字 0 或 1 。

而1byte(字节)=4bit(比特),bit 和byte 都是表示内存大小的单位,我们在编程时创建的变量数据所占的内存空间大小都是以字节byte为单位的。因此,我们以byte为基本单位,每个字节都给一个编号,这个编号就是这个字节byte的地址。

(想要知道关于各种数据类型所占内存空间的大小信息可以跳转到下面这篇博客喔!)

C语言各数据类型大小和取值范围_c不同数据类型精度-CSDN博客

而指针,它的含义就是上面所说的地址。

指针作为内存编号,我们就可以通过它访问到该内存空间里的数据,也就可以说这个指针指向了这里内存空间的数据

到这里,我们已经建立起了对指针的基本认识。让我们继续往下看吧!

& 和 *

&为单目操作符,能取出某个变量的地址;*解引用符号,能取出某个指针指向的变量(这里做简单复习)。

指针变量

指针变量,顾名思义就是存放指针的变量。

指针变量的大小

这取决于系统环境32位环境上指针变量大小为4byte64位上为8byte

指针变量的类型

指针变量的类型,取决于这个指针指向什么类型的数据。

常见指针类型:

被指向的变量类型指针变量的类型名称
charchar*char指针
intint*int指针
longlong*long指针
floatfloat*float指针
doubledouble*doouble指针
struct stustruct stu*结构体stu指针
………………

如表,创建一个指针变量,只要根据它指向的变量类型,加上” * “号就是这个指针变量的类型了。

二级指针

而指针变量作为一个变量,也需要开辟内存来存储指向数据的地址,所以它自己也有一个地址,那么指针变量的地址存在哪里呢?存在二级指针里,也就是指针的指针,或者说指向指针变量的指针。

void*

其中还有个特殊的指针类型,void* ——泛型指针,这个类型的指针变量可以存放任意一种类型的指针,但是也因为其类型不确定,因此不可以被解引用,也不可以进行任何运算

上文我们说到,指针能够准确地指到某个byte的内存单元上,但是指针对指向的内存要如何访问,就取决于这个指针的类型了。

PS:实际上对于任何数据都一样,只有赋值“ = ”操作、“++”、“ - - ”自增自减操作才可以修改内存里的二进制数据,其他任何操作都不会对其二进制数据进行修改。而对于某块内存里的二进制数据,在没有被修改的情况下,如何解读和操作其中的二进制数据,取决于数据的类型

比如对于二进制数据0000 0000 0000 0000 0000 0000 0011 0000这个4byte大小的数据来说:

这一点在接下来的内容就会有所体现了。

(除此之外,还有指针数组,数组指针,函数指针……下面会一一解答。)

const与指针

const(缩写constant恒定不变的)是变量的修饰符,被它修饰过的变量被称为常变量。常变量虽然本质上还是一个变量但是它的值不能被直接修改

const与指针变量之间的用法有两种:

①const*pf,const限制通过指针pf修改它指向的变量,硬改会报错。

②*const pf,const限制对指针变量pf本身进行修改,硬改会报错。

注意“ * ”位置的不同带来的不同效果。

常变量不能直接被修改,但是可以间接地去修改它也就是通过它的地址来修改

对于情况①要修改它指向的变量,只要通过另外一个和它指向同一变量的指针变量来修改即可。

对于情况②要修改指针pf,就通过指向pf的指针(这是个二级指针)来修改即可。

但是,写了const就是为了提醒程序员编程时避免对该变量进行了修改的,所以既然用了const就别修改变量的值;要修改变量的值,就别用const。

指针的运算

!!记住!!对指针的访问和操作,具体方式取决于他的指针类型。

指针+-

①指针加减的结果还是指针

②指针只能和整数进行加减。因为内存地址的编号都是整数,所以加减之后的结果只能是整数的地址编号。

举个例子,现在有一个地址0x12FFFF60,这个指针+1或 -1 ,结果是什么呢?可不是0x12FFFF61或者0x12FFFF5F喔!

​//X86环境下(64位)
int a = 10;//假设地址是0x12FFFF60
int* p = &a;
char* q = &a;
p = p + 1;
q = q + 1;

​

看上方代码1,

p的类型是int*,而int类型占4个字节的内存空间,所以p + 1,p会往后走4个字节,因此最后p保存的地址是0x12FFFF64,数值上加了4;

q的类型是char*,而char类型占1个字节的内存空间,所以q + 1,q会往后走1个字节,因此最后q保存的地址是0x12FFFF61。

所以,假设某个!指针类型所指向的变量!占x个字节,指针加减整数n时,就对应的就在数值上加减n*x。

总之,指针+ - 整数,就是让指针进行偏移,+就是向高值地址偏移,-就是向低值地址偏移,偏移量的单位取决于指针变量的类型,指向int就是4个字节,指向char就是1个字节,如此类推。也就是说int*类型的指针-2就向低值地址偏移 2 * 4 个字节;+3就向高值地址偏移 3 * 4 个字节。

PS:地址都是用十六进制数字表示的,1个十六进制数字的大小在0~15之间,需要4个二进制位来存储,也就是说,1个十六进制数据的大小在0000(0)~1111(15)之间,1个二进制数据占1个bit位,所以1个十六进制数据占4个bit位,也就是1byte。而地址都是以十六进制数的形式来表示的,指针指向的最小单位又刚好为1byte所以对指针的加减就刚好可以体现在指针数值的加减上了

指针 - 指针

指针+ - 一个整数得到的是指针,所以反过来指针 - 指针得到的就是一个整数

int num[5]={ 1,2,3,4,5 };
int* p = #
int* q = &num[1];
int ret = q - p;

看上方代码2,

p存放的是数组num的首元素地址,q存放的是数组num的第2个元素的地址。而数组在内存中是连续储存的,假设p存放的首元素地址是0x12345600,那么q存放的就是0x12345604(int数组,一个元素占4个字节)。q-p在数值上的结果是4,但是q和p都是int*类型,int类型在内存里占4个byte,所以数值结果4 ÷ 占用字节数4 = 1,所以,最后ret的结果为1。

因此,我们可以利用这个特点去计算两个指针之间的元素个数,当然前提是,两个指针都指向在同一段连续内存里的变量,比如上方代码都,p和q都指向同一个数组num,数组在内存里是连续的

那么有没有指针+指针呢?答案是没有,指针-指针能得出两个指针之间的元素个数,但指针+指针的结果是没有任何具体意义的,所以我们不会让一个指针+另一个指针的,编译器对此情况也会报错。

数组与指针

数组名的意义

数组名表示的就是首元素地址,但数组名有两种不同的意义:

①只是表示首元素地址。

②代表整个数组。

意义②只存在于sizeof(数组名)和&数组名两种情况,其余情况都是意义①。

int num[10] = { 0 };
int* p = #
int* q = &num[0];
int* r = num;
printf("%x", p);
printf("%x", q);
printf("%x", r);

代码5 

因此,在数值上p,q,r是一样的,打印结果都是一样的首元素地址。因为&num、&num[0]、num在数值上,其表示的地址是一样的,但是实际意义又有所不同。

数组的索引

还是用上面的代码5,我们要访问num数组的第一个元素,我们会写num[0];访问第五个元素我们会写num[4]。而在编译时,num[4]会被当做*(num+4)进行编译,两者效果是相同的。

int num[10] = { 0 };
int p = num[0];
int q = *(num+0)
if(p == q)
    printf("good\n");

如代码6,最后会打印一个good。num能表示首元素地址,num+0就是指针不偏移仍然指向首元素,再 * 解引用之后,拿到的就是num数组的首元素,也就是num[0]。这样一来,也就能解释为什么数组的索引要从0开始了。

所以num[ i ] 和 *(num + i) 是完全等价的。

顺便一提,操作符[ ]是双目操作符,一个操作数是整数,一个是指针,而且满足交换律,所以就会有num[ 0 ]和0 [num]是完全等价的,但是还是别这么写了,不然会被老板或者老师骂hhhhh。

当然在数组定义的时候就不是这么回事了,以上是针对索引来说的。

一维数组传参

当我们明白了数组名能表示首元素地址之后,我们来看下面的代码7:

int numAdd(int arr[10],int num)
{
    int ret = 0;
    for(int i = 0; i < num; i++)
    {
        ret += arr[i];
    }
    return ret;
}    

这个函数的作用是:传入一个int类型的数组,求出所有元素的和并返回。而我们会如此来调用这个函数,看代码8:

int arr[10]={1,2,3,4,5,6,7,8,9,0};
int result = numAdd(arr,10);

传参我们传的是数组名arr而不是传arr[10](这么传明显是错的)。换句话说,我们实际上往函数里面传入的是一个指针(因为数组名能表示首元素地址嘛)。那我们是不是可以这么写函数的声明,代码9↓

​int numAdd(int* arr,int num);

事实上这样声明和原来是没有区别的。而且因为num[ i ] 和 *(num + i) 是完全等价的。代码7的声明还有可以写成:

​int numAdd(int* (arr+10),int num);

因为这里是在声明参数,而不是在定义数组,所以[ ]会被当成索引看。那么不管是arr+多少,最后传进去的就是一个int*类型的指针,所以这里声明形参的时候,[ ]里数字是多少是不是就没关系了。也就是说,代码7还可以像下面这样声明:

​int numAdd(int arr[], int num);

[ ]里没有数字,arr[ ]可以解读为*(arr),那是不是就和代码9一样了?

综上所述,数组传参传入的是一个指针,它指向数组的首元素,以上对数组传参的声明均有效,且效果完全一样但是为了代码的可阅读性,代码7和代码9的声明是推荐的。

二维数组的本质

我们在定义和索引二维数组时,我们用行和列来进行理解,认为它是n行m列的数组,同时二维数组在内存里也是完全连续的。如图:

int num[3][3]={1,2,3,4,5,6,7,8,9}如此存储。

索引[0][0][0][1][0][2][1][0][1][1][1][2][2][0][2][1][2][2]
地址0x100x140x180x1c0x210x250x290x2d0x32
元素123456789

int num[9]={1,2,3,4,5,6,7,8,9}的存储方式是一模一样的。存储方式相同却有不一样的索引方式,是为什么呢?

实际上,我们在定义int num[3][3]={1,2,3,4,5,6,7,8,9}时,其实是创建了三个数组,再把这三个数组放在一个新的数组里面。

如图:二维数组num里面存着3个一维数组的数组名,一行就是1个一维数组,它们的数组名分别是num[0],num[1],num[2]。其中,num[0]存放1,2,3;num[1]存放4,5,6;num[2]存放7,8,9 。

这就是二维数组的本质——一个存放了若干个一维数组的一维数组就是二维数组,存放一维数组的方式就是存放它们的数组名

而我们前面就说到,数组名也是一个指针,它指向的是它的首元素。那么,存放了一维数组的数组名的二维数组,就可以认为,它里面存放的就是若干个指针。这里就可以引入一个概念——指针数组了。

指针数组

顾名思义,就是存放指针的数组。

定义:指针类型 数组名[ 数组大小 ]。例: int* pf [3],表示的是一个大小为3的一维数组,存放的元素类型是int*。

让我们看代码10:

int main()
{
	int num[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int* q[3] = { num[0],num[1],num[2] };
	printf("%d %d", num[1][1], q[1][1]);
	return 0;
}

这里定义了一个大小为3的指针数组q,它的里面依次存储了num[0],num[1],num[2],num二维数组里的三行一维数组的数组名。

这时我们就会发现,num和q是等价的了,你可以在本地实现这段代码10,然后改变printf语句里对num和q的索引,只要索引一样,输出的结果就一样。因此我们也可以认为,p此时已经和一个二维数组无异。

我们再用“ num[ i ] 和 *(num + i) 是完全等价的 ”结论来看二维数组。

比如我们要拿出整数5,我们就会这样索引——num[1][1],整数5存放在第二行第二列,也就是第2个一维数组num[1]的第2个元素,我们就认为对数组num[1]进行索引[1]拿到了整数5。num[1]为数组名,所以num[1][1] <==> *(num[1] + 1)。而对数组num进行索引[1]拿第2行的一维数组数组名,所以还有*(num[1] + 1)  <==>  *(*(num + 1) + 1)

问题又来了:如果说,二维数组可以认为是一个存放了指针的指针数组,那么存放在里面的指针又是什么指针类型呢?代码10里的num[0],num[1],num[2]的类型是int*吗。当然不是!这里我们就可以继续引入第二个概念——数组指针。

数组指针

顾名思义,就是指向数组的指针。

定义:数组类型(* 指针变量名)[数组大小]。

例:int(*pf)[3],表示的是pf这个指针指向一个大小为3的int类型数组。pf的类型为int(*)[3](对于某个变量,在定义时去掉标识符,剩下的就是它的类型,所有变量都适用喔!!后面就知道这个结论有什么用了)。

同样以代码10为例

int main()
{
	int num[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int* q[3] = { num[0],num[1],num[2] };
	printf("%d %d", num[1][1], q[1][1]);
	return 0;
}

拿上文的结论num[1][1] <==> *(num[1] + 1)  <==>  *(*(num + 1) + 1)来看,这里我们可以发现,num + 1它指的是哪的地址?它指的是num数组的第二行的一维数组首元素4,而不是第一行第二个元素2。这就说明,num+1,它并不是往后偏移了一个int类型,而是直接往后偏移了一整个一维数组num[0]指向了num[1]的首元素。

指针偏移的单位取决于指针的类型,因此我们可以反过来得知:因为num的+ - 的偏移量单位是一个大小为3的int数组,所以num就是一个数组指针

可是num不是指针数组的数组名吗?没错!两者并不冲突,这个num就是一个数组指针数组指针

我通俗地解读一下——是【(数组指针)数组】指针,num是3个数组指针num[0],num[1],num[2]组成的数组的指针。将其推广,实际上所有的二维数组都能叫做数组指针数组,二维数组的数组名都能叫做数组指针数组指针(很绕口是吧,不用记的,叫人家二维数组就行)。

你还记得&数组名的特殊意义吗?没错!它代表了整个数组,因为&数组名的类型就是数组指针。

结语

关于指针的内容还没有圆满完成!!因为篇幅很长,所以这里分开两篇发布。

传送门

标签:变量,int,C语言,数组名,num,数组,合集,指针
From: https://blog.csdn.net/Elnaij/article/details/139131225

相关文章

  • 【C语言】atoi函数的使用及模拟实现
    atoi(asciitointeger),是把参数 str 所指向的字符串转换为一个整数(int类型)的库函数。使用场景引子:有兴趣的朋友可以听我逐句翻译一下cpluscplus.com里的这段解释(要考六级了练一下):将字符串转换为整型解析C-字符串str,将它的所含物解释为一个整数,将这个整数作为int类型......
  • 智能指针
    在谈智能指针之前,先谈谈为什么需要智能指针?智能指针的价值1.自动内存管理:智能指针可以自动管理它们所指向的内存。当智能指针离开其作用域或被重置时,它们会自动删除所指向的对象,从而避免了程序员显式调用delete的需要。这有助于减少由于忘记释放内存而导致的内存泄漏。2......
  • Pycharm + Git 操作合集
    一、首先需要下载Git这里就不展示如何下载啦~~~二、在Pycharm中配置Git三、本地项目上传到远程仓库或从远程仓库Clone项目到Pycharm情况一:仓库有项目,放到Pycharm中1.拉取仓库使用Git来进行clone操作克隆远程仓库:打开VCS->Git->Clone,如......
  • (算法)双指针——快乐数 <数据分块>
    1.题⽬链接:快乐数2.题⽬描述:3.题⽬分析: 为了⽅便叙述,将「对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和」这⼀个操作记为x操作; 题⽬告诉我们,当我们不断重复x操作的时候,计算⼀定会「死循环」,死的⽅式有两种:         ▪情况⼀:⼀直在1中......
  • (算法)双指针——复写零 <原地复写>
    1.题⽬链接:1089.复写零2.题⽬描述:3.解法(原地复写-双指针): 算法思路: 如果「从前向后」进⾏原地复写操作的话,由于0的出现会复写两次,导致没有复写的数「被覆盖掉」。因此我们选择「从后往前」的复写策略。但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数......
  • C语言结构体知识
    一:什么是结构体        结构体英文名:Structures。是一种用户定义的数据类型,它允许你将不同类型的数据组合在一起,变成一个单独的单元,这个单元里面包含了用户所创建的不同类型的变量二:关于结构体类型    如上图在这个示例中,Stu就是一个结构体类型,它包含了......
  • C语言猜数字游戏完整版
    题目:电脑随机生成1~100的随机数;玩家猜数字过程中,根据猜测数据的大小给出或多或少的反馈,直到猜对,游戏结束。首先,随机数的生成:①rand函数可以生成随机数,rand函数会返回一个伪随机数,这个随机数的范围是0~RANDMAX之间;②srand函数:用来初始化随机数的生成器;③time函数:在程序......
  • 想要初步了解指针?看这篇就够啦!
    初级指针    一:指针的解释        什么是指针?本质上指针是内存地址,平时说的指针是指针变量,用来存放地址,指针变量里面存放的是地址而通过这个地址,就可以找到一个内存单元            上示例,定义了int类型的a变量,声明了一个整型......
  • 数据结构-单向链表的实现(c语言)
    链表的定义:链表是由一系列的结点组成的,每个结点包含两个域,分别是指针域(*next)与数据域(data)。单向链表的实现//.h文件#ifndeDXLB_H#defineDXLB_H//定义结点结构体typedefstructLINKNODE{structLINKNODE*next;//指向下一个结点的指针intdata;......
  • 031 指针学习—引用数组
    目录1数组元素的指针(1)定义(2)举例(3)注意事项2指针的算术运算(1)前提(2)运算规则(3)举例[1]p+i[2]p-i[3]++p与p++[4]--p与p--[5]p1-p2(4)注意事项3通过指针引用数组元素(1)引用数组元素方法(2)举例例1:输出a[10]数组中的全部元素例2:通过指针变量输出整型数组a的10个......