C语言笔记(适用于大一新生查漏补缺)

大家好,我是一名刚学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的字串,对应匹配的首字符的地址,找不到返回一个空指针

代码如下:

 

  • 8
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值