6.1上节OJ课作业讲解
text1
description:
实现形式:
执行结果:
text2:(字符串的反向输出)
最重要的是记住将字符串反转的算法,建立两个数组,将c数组中最后一个元素赋值给d数组的第一个元素,逐渐到c数组的第一个元素(索引为0)。
字符串比较函数strcmp,左操作数大于右操作数,输出1,左操作数小于左操作数,输出-1。
6.2指针的本质(间接访问的原理)
1.指针的定义
内存区域中的每个字节都对应一个编号,这个编号就是“地址”,
如果在程序定义了一个变量,那么在程序进行编译时,系统就会给这变量分配内存单元,
按变量地址存取变量值的方式称为“直接访问”,如:printf(“%d“,i),scanf(“%d”,&i)等。
另一种存取变量值的方式称为:“间接访问”,即将变量i的地址存放到另一个变量中。在C语言中,指针变量是一种特殊的变量,它用来存放变量地址。
指针变量与指针是两个不同的概念,一个变量的地址称为该变量的”指针“,例如,地址2000是变量i的指针,如果有一个变量专门用来存放另一边变量i的地址(即指针),那么称他为”指针变量“。、
如下图的i_pointer就是一个指针变量。
那么指针变量本身占用多大的空间呢。
本章中编写的程序都是64位应用程序,寻址范围是64位即8字节,对于本章来说sizeof(i_pointer)=8。
如果编写的是32位的。那么寻址范围就是4字节。(考研往往会强调程序是32位的程序)
2.取地址操作符(&)与取值操作符(*)
取地址操作符为&,也称为引用。通过该操作符我们可以获取一个变量的地址值。
取值操作符为*,也称为解引用,通过该操作符我们可以得到一个地址对应的数据。
我们通过&i获取整型变量i的地址值,然后对整形指针变量p进行初始化,p中存储的是整型变量i的地址值。
所以通过*p(printf函数中的*p)就可以获取整型变量i的值。(所以*只是一个取值的作用)。
p中存储的是一个绝对地址值。那为什么取值的时候会获取4字节大小的空间呢?
这是因为p为整型变量指针,每个int型数据占用4字节大小的空间。
注意以下几点
(1)指针变量前面的”*“表示该变量为指针型变量
(2)在定义指针变量时必须指定其类型,需要注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中:
(3)如果执行了语句。
说某个变量的地址的时候,讲的都是他的起始地址。
直接访问(直接给你一个金元宝)
间接访问(给你一个藏宝图,找到地址,再找到10)
指针变量的初始化是某个变量取地址来赋值,不能随机写个数。
一定要是相同类型,相同的藏宝图(神器)找相同类型的宝藏(神器)。
*是解值运算符。(单目运算符,从右至左)
分布间接访问:
(4)c语言本质上是一种自由形式的语言,很容易诱使我们把*写在靠近类型的一侧。
类似于:int*a,b,c的语句会使我们很自然地为这条语句把所有的三个变量声明为指向整型的指针。
但实际上,取值操作符只对a标识符起作用。
要声明三个指针变量,正确的语句如下:
6.3指针的传递使用场景
指针的使用场景通常有两个,即传递与偏移。
时刻记住只有再这两种场景下使用指针,才能正确使用指针。
1.指针的传递
在本例的主函数中,定义了整型变量i,其值初始化为10,然后通过子函数修改整型变量i的值。
但是我们发现语句printf(“after change i=%d\n”,i).
打印出来的i的值为10,子函数change并未改变变量i的值。
这是为什么呢?
为什么执行了change函数之后我们依然是10。
//C语言函数的调用时值传递,实参赋值给形参
打个比方:
但是i的值不发生改变,依然是10.
指针传递使用场景:
i的地址 61fe1c
j的地址 61fdf0:的确是5,但没有存储到i的地址。
接下来利用指针来升级改造:
官名:: 进程地址空间
函数里的*j其实是等价于变量i,只是间接访问。
6.4指针的偏移使用场景
1.指针的偏移
前面介绍了指针的传递,指针即地址,就像我们找到了一栋楼,这栋楼号是B,那么往前就是A,往后就是C,所以应用指针的另一个场景就是对其进行加减,但对指针进行乘除是没有意义的,就像家庭地址乘5并没有什么意义。
在工作中,我们对指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移。
以前我们输出数组:
自加自减
数组名存储着数组的起始地址ox61fdfo,其类型为整型指针
可以将其赋值给整型指针变量p,从监控窗口看到:
p+1的值为ox61fdf4,因为加1之后偏移的长度实际上是其基类型的长度,也就是sizeof(int),
这样通过*(p+1)就可以得到元素a[1]。
变化的是int(基类型)的长度,所以p如果指向1,那么(p+1) 就可以指向2。
增加一些难度
p=&a[4]; //让p指向最后一个元素
这里的a[4]是一个整型数据,要加取地址操作符。
指针与字符数组
一维数组存储的是数组的首地址,数组c中存储的是一个起始地址。
因为函数只能传递形参变量,所以传递过去的只有字符数组的首地址,
简而言之,数组名作为实参传递给子函数时,是弱化为指针的。
计算机设计的原理不但要建立在数学思想上,更要建立在硬件上。
C语言中ACSII码字符单引号’和双引号”在程序中经常出现,很简单,但却是十分重要的语法标点符号,初学者容易混淆使用。
双引号在字符串常量时使用
6.5指针与malloc动态内存申请,栈与堆的差异
1.指针与动态内存申请
C语言的数组长度固定是其定义的整型,浮点型,字符型常量,数组变量都在栈空间中,而栈空间大小在编译时是确定的,如果大小不确定,那么就要使用堆空间。
那么为什么要使用堆空间,堆空间的作用是什么?
堆空间比栈空间大很多。
malloc返回的void*代表无类型指针
所以前面要强制转换加上(char*)转换成字符类型指针。
无类型的指针是不能进行偏移的,所以不会定义void型。
成功在堆空间中申请了20个字节,并且把malloc success复制到p中。
由于malloc是你向操作系统借的堆空间(向校长借的),你就要还,下借不难。
你借的是p,那么你还的也要是p。
释放申请的空间的地址必须是最初malloc返回给我们的地址。
申请的堆空间大小必须大于你输入字符串空间的大小。
同时注意指针本身的大小和指针空间的大小是两码事
既然有栈空间,为什么还要用堆空间呢?
对于大数据,你不知道size的大小,你是要使用堆空间,他是动态的。
下面通过node理解他们的差异:
堆空间的进程不会消亡,而栈空间会消亡。
只有free之后才会消亡。