大家好,我是一名刚学C语言两个月不到的菜狗子,以下是我两周以来学习指针的详细笔记,笔记内容可以说是结合了鹏哥C语言,翁恺C语言,以及CSDN上的一些大佬的分享,也加入了我很多的见解(每一个我自己的想法都有对应的代码验证)。
这是一篇给新手用来查漏补缺或者学习指针中较高级(对我来说比较高级)知识的笔记,主旨并不是讲明白最基础的概念,不建议完全没学过的新手用来入门C语言,但是较为复杂,难懂的内容我都会尽力做到讲明白,至于其中我也不会的两个题就丢出来给大家思考和讨论了(我记得好像就两个题,文章中会进行说明,下次博客更新的时候会提供答案,提前说明我会挖几个坑下次填哈哈)
以下为文章的目录,每一块标题都用黄色标注出来了,读者可以按需翻找
1.指针的定义 2.野指针 3.指针的基础运算
4.字符指针 5.指针数组 6.数组指针
7.二级指针 8.指针与数组指针的步长 9.一级指针传参和二级指针的传参
10.void*空指针 11.函数指针 12.函数指针数组
13.回调函数 14.指针相关作业含sizeof()、strlen()与指针的结合
废话一下,标题不让写萌新或者新手,我只能被迫写了一个大一新生,哭死,这水平根本不配代表大一新生
指针:
定义及基础知识
1.在内存中取一个字节为一个内存单元,每个字节里面存放一个指针
比如int a = 10;这个代码,其中a占四个字节,我们取a的地址的时候其实拿到的是四个字节中的第一个字节的地址
2.指针在32位系统中占4个字节,在64位系统中占8个字节
3.不同类型的指针都占同样的字节,那么指针类型的意义是什么?
(1)指针类型决定了指针解引用的权限有多大,即能操作几个字节
(2)指针类型决定了,指针走一步能走多远(详见例一)
例一
整型指针加一与char类型指针加一的结果不同
Int类型加一相当于跳过一个整型,char类型加一相当于跳过一个char类型
所以两个结果一个是+4一个是+1
具体的作用就是比如一个数组p指针,每次给p加一,那么每次跳过多少个字节取决于这个指针的类型,那么设置指针的类型就有意义了
野指针:
指向的位置是不可知的就是野指针
原因:
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放了
例一:指针未初始化
没有初始化指针的时候里面存的是一个随机值,指向的是内存中某一个位置,这个值指向的是不是这个程序都不一定,所以再将20赋给这个值,然后再访问的时候就会出现非法访问内存了
例二:指针越界
例三:
相当于订的房间住完退订后,再想去住就会有问题了
综上,解决方法就是
1.使用指针的时候要记得初始化(但不知道要初始化为什么地址的时候,给一个NULL即可)
2.C语言本身是不会检查数据的越界行为的,自己要注意
3.为了防止野指针的出现,可以在开始的时候设置为NULL,使用前判断一下是不是NULL,使用完设置为NULL
指针的运算:
例一:指针相减
输出的结果是9,为什么?
指针-指针得到的是两个指针之间的元素个数
原因是什么?王八屁股——龟腚
指针和指针相减的前提是:两个指针指向的是同一块空间
为什么不用学习指针加指针?
有些运算是有意义的,有些是没有意义的,比如日期减日期得到的是相差的天数,但是日期加上日期没有什么实际的意义,指针的加减同理
例二:用指针求字符串的长度
这个代码可以指针-指针的方式进行运算
代码如下:
注意:
王八的屁股——龟腚
允许使用数组的最后一个的下一位进行比较,但是不允许第一位的前一位,总之就是访问数组的时候要++不要—,就是可以往后越界但是不能往前越界
字符指针:
常量字符串的概念:
Char arr[] = “abcdef”; 就是把这串字符放进arr[]数组中去,输入和输出函数都可以用
Char* arr = “abcdef”; 就是定义了一个常量字符串,里面的值是不能修改的只能用于输出函数,不能用于输入函数,被当作常量并且存储在内存静态区
Char*不仅能存储单个字符,也能存储字符串
存储字符串的时候是把首字符的地址存储起来了
例一:字符串不同方式定义时产生的区别
输出的结果是:
因为前两个是分别开辟了两块不同的空间
但是三和四是将同一块空间存储的内容的地址给了不同的指针,实际上三和四还是指向的是同一个地址,相当于把一个常量赋值给了a和b两个变量
指针数组:
定义:存放指针的数组,本质上是一个数组,存放的指针
格式如:int* arr[]
这样就是存放整型指针的数组了
例一:指针数组的初始化
菜狗写法
以下是较高级写法
这样就能遍历这个数组了
输出的时候也可以写成arr[i][j]
为什么输出的时候是用*(arr[i]+j)在后面讲解步长的时候有具体说明
数组指针:
本质上是一种指针,指向数组的指针
格式如:int (*parr)[10] = &arr;
先对parr解引用表示这是一个指针,指向的是有10个元素的数组,如果没有括号就成了一个数组了([]的优先级比*要高),所以可以把arr的地址放在*parr里面
Parr就是一个数组指针,存放的是数组的地址,*parr就相当于一个数组名
例一:数组指针与指针的加一跨过的字节大小不同
看似两个输出的结果是一致的,但是实际上类型是完全不一样的
第一个输出arr的是数组类型
第二个输出&arr的是指针类型,这个指针指向arr[10]这个数组
因为类型不一样,所以加一跨过的字节也是不一样的
这样输出的结果:
第一个跨过一个整型,就是4个字节,第二个跨过一个数组,就是10个字节
结论:
综上其实&arr和arr虽然值是一样的,但是意义是不一样的
&arr取得是数组的地址,而不是数组首元素的地址,当其地址+1的时候跳过的是整个数组的大小
补充:数组名是数组首元素的地址,但是有例外
1.sizeof数组名表示整个数组,计算的是整个数组大小
2.&数组名表示整个数组,取出的是整个数组的地址
例二:用指针的方式来访问数组元素
(*pa)+i就是让首元素的地址加i,其中*pa就相当于数组名,输出的结果是一个地址
所以要在进行一次解引用,*((*pa)+i)这样才是这个地址对应的值
输出的结果就是遍历这个数组
一般情况下一维数组不用数组指针,写起来很麻烦关键
例三:数组指针在二维数组的应用
二维数组的数组名表示首元素的地址,二维数组的首元素是第一行的地址
所以参数就应该是第一行的地址,这时候就要用到数组指针了,这个指针指向一行
遍历的时候用:
分析:*(p)就是第一行的地址,*(p+1)就是第二行的地址,所以*(p+i)就是每一行的地址
每一行的地址就是每行首元素的地址,给这个地址再加1就是一行中的每一个元素的地址
所以就是*(p+i)+j,就能拿到所有元素的地址,再解引用结果就是每一个元素的值
*(*(p+i)+j)
二级指针:
首先要明白:
&是取当前变量的地址,而*是获取当前地址中保存的值,两者是刚好相反的,如果同时存在会互相抵消
例一:二级指针的初始化
结果为:
分析易得实际上二级指针保存的就是一级指针的地址,三级保存的是二级指针的地址
二级指针一般有什么用途?
指针与数组的关系:
对于一维数组来说,数组名就是首元素的地址,但是对于二维数组来说是不一样的
二维数组本质上就是一个以一维数组为元素的一维数组,所以二维数组名其实是第一个一维数组的地址
Int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
可以看成是三个一维数组为元素组成的一维数组,三个元素名分别为arr[0]、arr[1]、arr[2]
一维数组的数组名就是其首元素的地址,所以可以得出
Arr[0] == &arr[0][0]
Arr[1] == &arr[1][0]
Arr[2] == &arr[2][0]
结合之后学习的数组指针的知识可以知道,arr[i](i<3)就是这个数组的数组名,可以定义一个数组指针如下
Int (*p)[3] = &arr;
*p指向的是一个存有{arr[0],arr[1],arr[2]}三个元素的数组
为什么这里的*p++遍历的是名字为(*p)的数组的三个元素(三个一维数组),而不是所有元素(共12个)?
详见下面的指针与数组指针的步长
为什么在这里提及?
为了让读者对二维数组的理解更进一步,每一个arr[i]都是一个数组名,便于后面数组指针的讲解
-------------------------------------------------------------------------------
指针与数组指针的步长:
问题:(*p)+1的指向的是什么?arr[0]+1指向的又是什么?
用以下代码来验证:
输出结果是:
可以看出,arr[0]+1跨过了4个字节,而(*p)+1跨过了4*4=16个字节
原因是对指针进行运算的时候,跨过的字节长度与指针指向的数据类型有关系
先说结论一:
指针的步长在定义指针的时候就已经确定了,指针的步长等于其指向的内存的大小
比如较为简单的是
Int *a指向Int类型的内存,步长为4
Char *a指向的是char类型的内存,步长为1
较为复杂一点的是一维数组
一维数组名就是数组首元素的地址,实质上也是一个指针,而&一维数组名虽然也是数组首元素地址,两者的数值相等,但是两个地址的值并不等价
1.一维数组名实际上是一个指针,指向数组的首元素地址,如果数组首元素的地址为char类型的内存,那么步长就是1
2.&一维数组名,步长为数组的内存大小,所以可以理解为指向整个数组,如对char a[10]进行操作的时候步长就是1*10=10个字节
代码验证如下:
输出结果是:
第一个步长为一,第二个步长为十
目前最复杂的就是二维数组的步长:
根据结论一可知
首先二级指针的定义是指向指针的指针,而指针的内存块大小为4个字节,所以char** p的步长就是4,int** p的步长也是4,因为都是指针
为什么要提及二级指针?
因为二维数组首元素地址就是一个二级指针
比如int (*p)[4] 与int arr[3][4]在形式上是一样的
二维数组名即为二维数组的首行元素地址(可以理解为一维数组的首地址,即&一维数组名)
这个首行元素地址虽然与二维数组首元素地址数值相等,但是并不等价,类比一维数组
如下:
输出结果是:
说明这两个的地址是相同的,但是步长呢?
1.arr为二维数组的首行元素的地址,就相当于{1,3,5,7}整个一维数组的首地址(char a[5]中的&a),根据一维数组中的结论可知,这个arr的步长就是指向的整个数组的长度(第一行)4*4=16
2.&arr就是整个二维数组的首地址(本质上类似于一维数组中的指向整个数组的情况)4*4*3=48
代码验证:
输出结果是:
一个步长为16,一个步长为48
总结:
1. 指针的步长在定义指针的时候就已经确定了,指针的步长等于其指向的内存的大小
2.数组名与&数组名虽然地址相同但不等价.
1)数组名是指向数组首元素的地址的指针,其步长取决于数组首元素的内存大小(以int为例)当首元素是单个数据类型的时候步长为4,当首元素为一维数组的时候,步长为4*n,n为这个一维数组中的元素个数
2)&数组名是指向数组的指针,指向的是整个数组,所以其步长就是整个数组的内存大小
当整个数组是int arr[10]的时候步长为4*10=40,当整个数组为int arr[2][10]的时候步长为4*10*2=80
-------------------------------------------------------------------------------
根据以上的讲解我们可以知道在如下的数组中
Int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
Int (*p)[3] = &arr;
(*p)是一个数组名,那么直接对数组名进行++,步长就是一个元素的内存大小4*4=16,所以遍历的才是这三个一维数组(*p)[0]、(*p)[1]、(*p)[2],如果是&数组名那么结果是截然不同的
紧接上面我们说到的(*p)+1,这是访问了以一维数组为元素的二维数组中的每一个元素,那么如果我们想要访问每一个一维数组中的元素怎么办?
在外面继续套娃即可*((*p+i)+j)
这是什么意思呢?
首先(*p)是第一个数组名,即第一个数组的首元素地址,我们给(*p)+j,就是访问第一个数组中的第j+1个元素,比如j=0的时候,就是访问第一个数组的第一个元素,结果是1
j=1的时候就是访问第一个数组中的第二个元素,结果是2
当((*p)+i)的时候,就是访问第i+1个数组的操作了,i=0的时候访问的是第一个数组{1,2,3,4},i=1的时候访问的是第二个数组{5,6,7,8}
学会了这个我们就能用指针来遍历二维数组了,代码如下:
当然我们学习这么多知识肯定不是为了这么简单的操作
基于以上所有的知识我们进行一个小总结:
结论1.二位数数组中a[i]就是a[i][0]的地址
在二维数组中一维数组的数组名就是数组第一个元素的地址,所以
a[0]就是a[0][0]的地址,即
a[0] == &a[0][0]
同样的a[1] == &a[1][0],a[2] == a[2][0]
结论2.a[i]+j == &a[i][j]
a[0]+1就是a[0][1]的地址,这在上文中也证明过,所以
结论3.需要二级指针的原因是一次解引用不能取出对应的值
二维数组a[i][j]的数组名a表示的是谁的地址?
二维数组就是以一维数组为元素的一维数组,所以二维数组的首元素地址应该是a[0]
,而不是a[0][0],所以数组名a表示的是a[0]的地址
---------------------------------------------------------------------------
代码验证:
输出结果:
为什么三个地址是一样的?
因为arr的首元素地址是arr[0],但是对于arr[0]这个数组来说,他的首元素地址是arr[0][0],arr[0]作为数组名,它的地址不就是arr[0][0](这个数组的首元素的地址)吗?所以三个地址是一样的
问题:既然三个地址都是一样的,那为什么我们还要进行三者的辨析?有什么意义?
这个问题交给以后的我再来解决吧,暂时就先不解决(没能力了)
-----------------------------------------------------------------------------
接着说上面的内容
用式子描述出来就是:
a == &a[0]
a[0] == &a[0][0]
那么a == &(&a[0][0])
意思就是二维数组名a的地址的地址,必须进行两次取值才能取出数组中存储的数据,所以对于二维数组a[m][n]而言,如果只定义了一个指针变量p(int* p)指向二维数组a,那么不能把a赋值给b,因为两者的类型不兼容,a是一个int(*)[n]类型的变量,即数组指针类型,而p是一个int*类型的,int*类型只需要解引用一次就能得到值,但是int(*p)[n]需要解引用两次才能得到值,因为a == &(&(a[0][0]))取了两次地址
我们有三种选择:要么把&a[0][0]赋值给p,要么把a[0]赋值给p,要么把*a赋值给p。前面两种好解决,主要是第三种*a赋值给p该怎么解决?
将二维数组名a赋值给指针变量p,那么如何将p指向元素a[i][j]?
根据数组指针的步长那一块知识我们可以知道:
如果我们想以 ”行” 为单位来进行a[i][j]的访问,那么就要对a[i]取地址(这样指针对应的内存大小刚好a[i]整个数组,就是以 ”行” 为单位进行访问了)
a就代表第一个元素(即第一行),a+1就代表第二个元素(即第二行),a+i依次类推
用式子来表示就是:p+i == &a[i]
现在是两个地址,并且是取了两次地址,我们进行一次解引用:
*(p+i) == a[i],这样就能表示行了,再加上j就能表示行和列了
*(p+i)+j == &a[i][j]
例一:指针步长的陷阱
判断以下代码输出的结果是什么?
猜测:输出的是Y和o,或者是Y和a
输出的结果是一个Y,但是到第二次打印的时候就会引发异常,为什么呢?
因为ptr是一个二级指针,当执行ptr++的时候不是加上一个sizeof(char),该指针指向的是一个char*类型的指针,所以再进行++的时候就会加上一个sizeof(char*),即一个指针的长度,就是四个字节
猜测错误的原因:
当猜测为输出Y和o的时候就是认为加上了一个sizeof(char)即加1所得到的结果
但猜测为输出Y和a的时候就是认为加上了一个sizeof(char*)即加4所得到的结果
正解:
在执行ptr++的时候就是使指针加上了一个sizeof(char*),即&ptr+4
再对其进行解引用*(&ptr+4)就会出现问题,该指针指向的地方不知道是什么地方
那么为什么我们在用指针遍历二维数组的时候加n就没问题?
实例如下:
这里因为**p是一个二级指针,所以它在保存*b的地址的时候,会为**p单独开辟一块独立的空间,所以它就有了一个新的地址,这个新的地址再进行加一的时候就会出现问题
回到例三的问题中去我们可以发现
因为这个指针**ptr已经不再是以前的那个地址了,所以进行++运算的时候难免会出现问题,就像实例中的一样,++之后的地址其实是一个野指针,跟*p已经没有关系了
一级指针传参和二级指针传参:
例一:
如果把函数参数设置为二级指针,那么可以传什么过去呢?
可以传二级指针,一级指针变量的地址和存放一级指针的数组的地址
Void*指针:
Void*指针是一个无具体类型的指针,其中可以存放任意类型的地址
但是不能进行解引用操作,因为不知道到底应该访问几个字节导致的
包括+n的时候跳过几个字节也是不知道的
例一:
别的类型都能放进去
函数指针:
定义:
指向函数地址的指针
&函数名 == 函数名
两者都表示函数的地址
但是&数组名 != 数组名
因为前者是整个数组的地址,后者是数组首元素的地址
(若有一个add函数)
*pf() = &add();
这样容易产生歧义,因为者而言写更像是像pf()即调用这个函数,再对函数调用完返回的值进行解引用,所以我们加上()如下:
格式:(*pf)() = &add();
这样子pf就是一个函数指针变量了
例一:
初始化函数指针
应该是void (*pt)(char*) = &test;
例二:函数指针的调用格式
Pf放的就是add的地址,对pf解引用就是找到pf的地址
所以pf就等价于add,两者都是函数的地址(函数名就是地址)
所以也可以写成int ret = pf(3,5);
这三种方式是完全等价的
*在这里没有实际上的意义,只是帮助理解,就算加上很多*结果都是一样的
(*****pf)(3,5)也是一样的结果,但是只有函数这里*是没意义的,别类推
例三:指针的嵌套辨认()
代码1:
(void (*)())这是一种函数指针类型
将0强制类型转换成一种函数指针类型(void (*)())0
就是在0这个地址有一个函数,我们对其解引用然后调用
*p(),p就是函数名,这里的p其实就是(void (*)())
因为0这个地址放置的这个函数是无参的,所以在调用的时候也不用传递参数
得到结果:
(*(void (*)()))()
综上:
实际上就是调用0地址处的函数,该函数无参,返回类型是void
1.void(*)()-就是函数指针类型
2.(void(*)())0-就是对0进行强制类型转换,被解释为一个函数的地址
3.* (void(*)())0-对0地址处进行了解引用操作
4.( * (void(*)())0)()-调用0地址处的函数
代码2:
1.signal和()结合说明signal是函数名
2.signal函数第一个参数的类型为int,第二个参数类型是函数指针
,该函数指针指向一个参数为Int,返回类型为void 的函数
3.将其中的signal(int,coid(*)(int))取出后就变成了void (*p)(int),这里的p表示取出的内容
Signal函数的返回类型也是一个函数指针,该函数指针,指向一个参数为int,返回类型为void的函数
那么为什么不写成void(*)(int) signal(int,void(*)(int))?
因为语法不允许,必要把*与函数名结合在一起当返回类型为函数指针的时候
代码三:
先看变量p,*p表示这是一个指针,这个指针指向的是函数,即(*p)(),其中(*p)就是函数名,这个函数的参数是Int,返回值类型也是Int
代码四:
首先看p,()的优先级高于*的优先级,所以这是一个函数,p(int),参数为int,
外面有一个*,即*(p(int)),说明这个函数的返回值类型是一个指针
返回的指针指向一个数组,即(*ptr)[3],ptr == p(int)
这个数组是一个int*类型的数组,所以里面存放的是int*类型的指针
函数指针数组:
定义:
整型指针 int*
整型指针数组 int* arr[2]
函数指针数组就是存放函数指针的数组
初始化得到结果是:
例一:
实现整型变量的加减乘除的计算器
以下代码的可扩展性太低,并且代码重复的地方太多,我们需要对其进行优化
就用函数指针数组
优化后的代码为:
这样的代码十分的方便,但是参数要相同的时候才能这样
这里的函数指针数组起到了一个转移的作用,所以也被叫做转移表(若是读者感兴趣请自行查阅,我忘了查了哈哈哈)
指向函数指针数组的指针:
类比指向整数指针数组的指针可以得到
进一步得到:
把p2的地址给*p3这个指针,*p3这个指针的类型是int(*)(int,int),但是语法规定不能写成Int(*)(int,int) (*p3)[4] = &p2这种形式(类似int a = b)
所以只能写成int(* (*p3)[4])(int,int) = &p2
小总结:
回调函数:
通过函数指针调用的函数
当你把函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
例一:
补充:
函数名、取地址名和解引用函数名的关系
三者其实是相等的
例二:
Qsort函数的使用
C语言库中的qsort()函数可以实现对整型数据,字符串数据,结构体数据的排序
但是我们自己写的冒泡排序只能排序一种类型(int类型),如果要比较字符串那么还需要重写一下函数
1是数组的首元素地址,2是数组的元素个数,3是每个元素所占字节的大小,
Qsort函数在设计的时候就是为了让不同类型的数据都能进行排序,所以4才设置的是函数指针,这个指针是用来比较待排序数据中两个元素的函数
这个函数返回的值大于0说明大于,等于0说明等于,小于0说明小于
e1和e2是两个元素的地址,用来进行比较的时候因为void* 是无类型的指针,对其解引用是没有意义的,所以不好比较,我们对其进行强制类型转换再进行比较即可
代码一:用qsort实现冒泡排序
形参其中的4最好换成sizeof(arr[0])
代码二:使用qsort排序结构体
问题:对结构体排序的时候应该是以什么标准来进行排序?
有很多标准,如年龄
代码一:以年龄为标准进行排序
代码二:按照名字排序
使用<string.h>头文件中的strcmp()函数来进行比较
这个函数的返回值是正负数和0,与所需要的返回值是相同的
比较方式:如abc和adcq
从左往右比较对应位置的ASCII码,d比b大,所以adcq大于abc
这是升序排序的方法,如果想要降序排序只需要将a和b的位置互相换一下
例三:自己写一个冒泡排序,可以用于排序多种数据类型
问题1:交换环节中如何交换两个地址的内容?
都解引用然后把值赋给对方可以吗?
不能,因为void* base你并不知道这是一个什么类型的值
所以解引用的时候不能确定用什么类型
应该采用交换字节的方式,一个指针占4个字节,那么前四个字节和后四个字节互相交换一下对应的位置即可
问题二:
为什么在比较arr[j]与arr[j+1]的时候要将base类型强制转换成char*类型?
因为只有char*类型的指针才是+n就是跳过n个字节,其他的指针+n都是跳过n的k倍个字节,别的类型不好算
以上是int类型的排序,那么如果要用结构体进行排序呢?
作为待完成作业,请读者下去自己写一下,我会在下次博客进行更新
指针作业一:
模拟实现strcpy函数,即字符串复制函数
输出结果:
可以优化为:
最简化的是:
因为 ‘\0’ 的ASCII码就是0,所以当0赋值上去的时候就会判定为假然后直接跳出循环
补充:在char* arr2的前面加上const修饰:const char* arr2即可
这样就可以保护src的值不会被修改,防止在赋值的时候dest和src写反了
作业二:
判断以下输出的结果
复习一下sizeof的知识
数组名代表整个元素的情况
Sizeof(数组名)中数组名表示的是整个数组的地址,即计算的是整个数组的大小
&数组名中的数组名表示的是整个数组,取出的是整个数组的地址
数组名代表第一个元素的情况
除此以外的数组名都是数组首元素地址
输出结果:
第一个是16,计算的是整个数组的长度
第二个是4/8(取决于int的长度),因为是a+0所以不代表整个数组的长度,而是首元素的地址,即一个int的长度,就是4 + 0 = 4
第三个是4,*a是数组的第一个元素,sizeof(*a)就是计算int类型的字节长度
第四个是4/8,同理第二个
第五个是4,因为是第二个元素
--------------------------------
第六个是4/8(取决于指针的大小),计算的是一个地址的地址
第七个是16,(*&a) == a是整个数组的地址,直接就是整个数组的长度
第八个是4/8,一个指针的大小
第九个是4/8,同理第八个
第十个是4/8,同理第八个
--------------------------------
第一个是6,一共六个元素
第二个是4/8,指针的大小
第三个是1,第一个元素的大小
第四个是1,第二个元素的大小
第五个是4/8,还是一个指针的大小
第六个是4/8,一个指针的大小
第七个是4/8,一个指针的大小
--------------------------------
第一个是随机值
第二个也是随机值,因为两个都是数组的地址
第三个结果未知,把首元素进行解引用得到的结果是a,对应的ASCII码是97,对97进行strlen()操作结果肯定未知
第四个结果是未知,和第三个原因一样,三和四都会引发异常
第五个结果是随机值,虽然传进去的是一个&arr(数组的地址),即数组指针char(*)[6],但是strlen接收的形参是一个char*,所以还是首元素地址,最后输出的同一二一样是随机值
第六个和第七个的结果都是随机值
----------------------------------------------
这种初始化方式里面放的是{a,b,c,d,e,f,\0}
第一个结果是7
第二个是4/8,指针
第三个是1,第一个元素
第四个是1,第二个元素
第五个是4/8,是一个指针,类型是char(*)[7]
第六个是4/8,加一是跳过了一个char[7]的数组
第七个是4/8,第二个元素的地址
第一个是6,数到\0就停止了
第二个是6,也是数组首元素地址
第三个和第四个都是报错,因为97这个地址不知道放的什么
第五个是6,数组的地址,但是长度都是一样的
第六个是随机值,因为是整个数组的地址加一,所以会跳过一个char[6]的数组,下一段放的是什么还不知道
第七个是5,从第二个元素开始数的
第一个是4/8
第二个是4/8,第一个和第二个都是指针
第三个是1,a的大小
第四个是1,a的大小
第五个和第六个都是指针,结果是4/8
第七个是指针,结果是4/8
---------------------------------------
第一个是6,字符串的长度
第二个是5,从第二个开始数的字符串的长度
第三个和第四个都是报错,97这个地址存放的是什么我们还不知道
第五个是随机值,因为对指针进行取地址,这个地方存放的是什么东西都不知道
第六个是随机值
第七个是从第二个元素往后数,结果是5
----------------------------------------
第一个是48,12个int类型的元素
第二个是4,第一行第一个元素所占空间的大小
第三个是16,a[0]是第一行数组的地址,
第四个是4/8,第一行第二个元素的地址,即一个指针的大小
第五个是4,int类型的元素(第一行第二个元素)
第六个是4/8,二维数组的第二行的地址,一个指针的大小
第七个是16,对第二行进行解引用,计算的就是第二行的大小
第八个是4/8,对第一行进行取地址再加一,得到就是第二行的地址,一个指针的大小
第九个就是16,第二行的大小
第十个是16,第一行的地址a进行解引用操作,得到的是第一行的大小
第十一个是16,sizeof内部是不进行运算的,只看其类型,a[3]是一行的地址(虽然越界了),计算出来的结果和a[1],a[2]是一样的,都是一行的大小,甚至a[-1]的结果也是16,只看类型不看计算及其结果
作业三:
输出的结果是:(十六进制表示的)
0x100014 0x100001 0x100004
第一个是加上20
第二个直接加上1,整型加一是不用看类型的,该加多少就加上多少
第三个是跳过一个int*类型的大小,就是加上4
作业四:
判断输出的结果:
输出的结果是1,因为这个是三个逗号表达式,实际上是{1,3,5}
作业五:
判断输出下列输出的结果:
输出的结果是:
分析:
对于p来说,指向的是每四个int为一组的数组,那么每次p加一就是跳过四个字节,但是a[5][5]指向的是每五个int为一组的数组,每次a加一就是跳过五个字节,
所以对于a[4][2]就是红色的方块,对于p[4][2]就是绿色的方块
两个指针相减输出的值就是两个指针之间的元素个数(王八屁股—龟腚)
对于%d打印,输出的应该是-4,还要考虑p[4][2]-a[4][2]是一个小地址减大地址
对于%p打印,输出的直接是该数字用十六进制表示的补码,应该是FFFFFFFC
最后一行就是二进制补码,在转换为十六进制补码就行
作业六:
下面是过程的示意图
实际上cp[]中存储的是c[]里面所有内容倒过来的值
{“FIRST”,”POINT”,”NEW”,”ENTER”};
第一个打印出的值就是POINT
第二个打印:
* ++cpp: Cpp先++,经过两次++后cpp现在指向的是c+1的值,即NEW
这一步操作完了后进行外面的* --( )操作
* --( ):先进行--,原本指向c+1,经--让c+1变成了c,再解引用得到的结果就是指向ENTER
因为char* c[]本身就是指针数组,里面存放的是指针
在*-- * ++cpp的操作结束后,实际上指向的是ENTER这个数组的首元素地址,即E
再进行+3的操作就能向后移动三位,输出的是ENTER中的第四位
因为是以%s的形式进行打印的,所以打印结果是ER
综上,第二个打印输出的结果是ER
第三个打印:
把*cpp[-2]看成是**(cpp-2)原本cpp指向的是第三格的c,经过—指向c+3
所以*cpp[-2]指向的就是FIRST,对其加三,指向S,再以%s的形式输出结果即为ST
第四个打印:
先看成*(*(cpp-1)-1)+1,原本cpp指向第三格,再进行cpp-1就指向第二格了(c+2),再进行一次解引用操作就指向的是c中的第三格了,再进行--的操作就成c中的第二格了,c中的第二格指向的是NEW,最后进行一次解引用加一,指向的是NEW这个数组的第二项,就是从E开始,以%s的形式进行打印输出的就是EW
作业七:
字符串左旋
什么是左旋?
比如ABCD左旋一个字符就是先把A取出去,然后BCD左移一位,最后在BCD的后面加上A
左旋两个字符就是取出AB,然后CD向左移动两位,再把AB放在CD的后面
代码如下:
这是左旋,右旋也是同样的道理
方法二:
三步翻转法:(读者可先思考,下次博客更新我会给出答案)
1.左边逆序2.右边逆序3.整体逆序
比如ABCDEF,要左旋两位
作业八:
思路一:
不断的左旋,每左旋一次进行一次比较即可,代码略
思路二:
当比较AABCD和BCDAA的时候,我们只需要将
设置一个字符串(AABCD的两倍)AABCDAABCD,每次比较五个字符,每比较完一组就加一,全部比较完实际上就相当于左旋了一遍
这个设置的字符串包含了其中所有左旋的可能性
补充:
strcat(char arr[],”xxxxxxxx”)函数的第一个参数是字符串数组,第二个函数是要追加的字符串数组,但是这个函数不能自己追加自己
strncat(char arr1[],char arr2[],n)函数前两个参数是需要追加的字符串,第三个参数n表示需要追加的字符串长度
strstr()函数判断str2是不是str1的字串,对应匹配的首字符的地址,找不到返回一个空指针
代码如下:
更多推荐
C语言笔记(适用于大一新生查漏补缺)
发布评论