目录
? ? ? ? 在函数里面。我们大部分的形式是使用传值调用我们的参数,比如下面这个简单的函数
int Add(int a , int b )
{
return a+b ;
}
????????在函数的传入的参数里面,a和b就是我们需要传入的两个值,我们在调用这个函数的时候,就需要我们传入两个类型为 int 的值,人后进行计算。类似于这样的函数调用参数的方法,我们称之为传值调用
? ? ? ? 在函数传入的参数部分,除了可以传入值,我们还可以传入一个地址进行函数的实现,比如我们之前写的strlen函数的模拟实现就是这样的原理:
#include <stdio.h>
#include <assert.h>
int mystrlen(char* ptr)//这里传入一个指针解引用之后的值
{
int count = 0;
assert(ptr);//根据我们前面学过的arrest断言,我们这里使用这个断言防止我们传入的是一个野指针
while (*ptr)//当我们的ptr指针指向了这个字符串数组的隐藏的最后一位‘\0’循环就停止
{
count++;//计数器加1
ptr++;//地址加1,指向下一个地址
}
return count;//返回参数计数值
}
int main()
{
char str[] = "abcdefg";//这里创建一个字符串数组,最后一位有一个隐藏的‘\0’
char* ptr = &str[0];//创建一个指针ptr指向str的第一个元素
int ret = mystrlen(ptr);//传入指针参数,就是传入了数组的第一个元素的地址,并用ret变量接收这个mystrlen函数的返回值
printf("%d", ret);//打印返回值
return 0;
}
? ? ? ? 这里就是一个很典型的传址调用,我们有点时候想要实现一个函数的功能,必须要传入一个地址,而不能是一个值。比如一个交换两个数字的值的函数,就必须要使用传址调用。如果我们坚持使用传值调用,可能会出现和我们预期不同的结果,比如这里就使用一个传值调用看一下能不能交换两个数的值:
#include <stdio.h>
void swap(int x, int y)
{
int c = x;//定义一个中间变量的值c暂时“保管”我们的x的值
x = y;//x的值已经被“保管”了,现在我们就可以大胆的把y的值放在x里面了
y = c;//把原先x里面的x的值放在y里面,完成交换
}
int main()
{
int a = 10 ;
int b = 30 ;
printf("交换前:a = %d b = %d\n", a, b);
swap(a, b);//调用交换函数swap,并传入a和b的值
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
我们试一下结果,看一下a和b的值有没有交换:
不难看出,我们传入的值并没有发生交换。
? ? ? ? 所以我们的交换函数是没有实现它的作用的,所以这个是为什么呢?我们使用调试可以看一下出现这个结果的原因:
我们可以看到,在我们参数部分的x和y的值确实是经过了交换的,但是这个交换的值并没有通过函数传入a和b,所以其实这个交换的值并没有和a和b产生任何关联,也就是和a与b没有任何关系。
? ? ? ? 那么问题来了,既然我们想要使用函数交换我们主函数里面的值,我们就要让这个函数和我们的主函数产生一点关联。要怎么实现呢?我们这个时候就可以使用传址调用就可以了。
看一下我们改良之后的代码:
#include <stdio.h>
void swap(int* px, int* py)//这里的参数其实是两个指针,就是要我们在调用函数的时候传入两个地址
{
int tmp = 0;//这里定义一个中间变量,用来暂时储存我们值
tmp = *px;//把px对应的值传入中间变量
*px = *py;//把py对应的值传入这个px中
*py = tmp;//再把中间变量里面储存的px的值传入py,完成交换
}
int main()
{
int a = 10;
int b = 30;
printf("交换前:a=%d b=%d\n", a, b);
swap(&a, &b);//传入两个参数的地址
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
运行一下,我们发现,非常的不错,成功完成了交换两个数的值的任务
从上面的例子我们可以知道,使用函数时,传值调用可能是比较常用的,但是在有点时候,我们使用传址调用才可以完成我们的任务。传址调用就是这样一个可以起到将主函数和被调用函数的一种方法。
????????我们在指针A部分的时候就讲过这样一个知识:在一般情况下,一个单独的数组名代表的就是一个数组第一个元素的地址,也就是 arr == &arr [ 0 ] 。在这里,我们可以做一个小测试,使用printf函数打印一下这个数组名和数组的第一个元素的地址:
看结果我们就可以知道我们的数组名和数组第一个元素的地址其实是一个东西,要不然为什么很多时候我们传入一个数组作为参数时只传一个数组名呢。
? ? ? ? 但是,数组名这种特性有两种特殊情况:
1)sizeof(数组名):sizeof中单独放入一个数组名,这里的数组名就代表的时整个数组,计算的是整个数组的大小了,单位是字节。
这里可以使用一下sizeof函数试验一下:
可以看到,我们这个时候打印出来的就是一个数组的总长度,这个arr数组一共有10个元素,每个元素类型是一个int ,占4个字节,加起来就刚好是40
2)&+数组名:这里的数组名前面加上一个取地址符号,代表的就是整个数组的地址了,随然打印出来还是和单个数组名以及数组首元素地址相同,但是它代表的就不再是一个元素的空间,而是这一大片空间的首元素地址
那么我们怎么去证明呢?很简单,既然它是一个地址的话,我们加上1它就跳过当前元素跳到下一个元素,我们再看这两个元素之间的距离就可以知道所求对象的大小:
从上面的结果我们可以看到:这里地址加1只有&arr变化了很多,另外两个只变化了4,也就是一个字节,而&arr变成&arr+1却从56FBF0变成了56FC18,刚好就是40个字节,也就是一整个数组的地址空间大小。所以,我们就可以知道,其实arr和&arr[0]得到的都是一个数组里面单个元素的地址,而&arr代表的则是一整个数组的大小。
这就是两种数组名的特殊情况,其他情况下,一个单独的数组名代表的就是一个数组首元素的地址。
? ? ? ? 我们知道,其实数组名就是数组首元素的地址,那么其实指针作为一个专门储存地址的变量,我们就可以使用指针来访问数组,通过数组加+-整数,我们就可以访问到一个数组里面的全部元素。
下面就是一个使用指针访问数组的例子:
这样就是使用指针访问数组的一个典例
????????其实,从某种意义上来说,我们的数组名也可以看成一个指针,arr和p在这里可以看成是等价的,就比如我们现在不使用这个指针,而是使用数组名,也可以达成相同的效果:
如果是使用这样的地址+-整数的方式打印指定元素的话,会怎么样呢?
我们发现,使用? *arr + 整数 )和 arr [ 整数 ]我们得到的结果是一样的。
? ? ? ? 但其实,这两种写法其实是一样的。在计算机内部,arr[ k ] 就会被计算机变成*(arr + k )然后计算出相应的值。我们又知道假发其实是有交换律的,也就是说*(arr + k )和*(k+arr )是等价的。那么,我们使用k[ arr ] 能不能得到想要的结果呢 ?
? ? ? ? 那么我们就可以来看一下下面的代码:
????????我们可以看到,其实两种实现方式其实是一样的,我们可以通过 k [ arr ] 这种方式得到我们想要的数组元素,这也就证明了我们前面的说法,所以,我们就可以通过这样的方式实现数组的确定元素的找寻。虽然这个写法我们平时很少会使用,但是我们至少可以知道这样的写法可行的。所以我们以后写 代码的时候就可以有更多的思路。
? ? ? ? 也就是说,如果把arr看作一个指针p的话,那么就有这样的关系
这其实就是使用指针访问一个数组的原理,我们的指针在得到了数组第一个元素的地址的时候,我们就可以使用像 *(p+ i)这样的方式得到一个数组对应一个元素的值,并且可以打印出来。尊却的说,就是利用指针+-整数,然后得到对应的元素的地址,然后再进行解引用操作。就可以访问一个数组的任何元素了。
? ? ? ? 我们知道了数组是可以作为一个参数传给一个函数的,比如一个sizeof函数,得到的参数就是一个数组的数组名,也就是这个数组的地址,但是我们可以看一下下面的代码和输出结果:
这就和我们预期的有点不太一样了,我们这段代码其实是想要通过一个函数,我们向这个函数传入一个数组名,我们就可以通过这个数组名求出这个数组的长度。同样的代码,在两个地方却出现了不一样的结果。
? ? ? ? 函数内部并没有获得正确的数组元素个数,我们其实还可以再测试一下:
????????在这里我们可以清楚的看到,问题其实就出在sizeof(arr)的大小。正常在主函数里面,它的值就是40,但是我们把arr传入这个函数里里面的时候,我就发现,其实这个函数获得的arr的大小不是一整个数组的大小,而是变成了一个元素的大小。
? ? ? ? 我们就需要学习一下数组传参的本质了,数组在传参的时候,数组名作为的是首元素的地址,所以,其实在这里,我们也没有把整个数组传入函数,其实就是因为我们数组传入函数的时候,传入的其实是数组首元素的地址。于是,我们的数组传入之后,sizeof计算的就是一个元素地址的大小而不是整个数组地址的大小。所以,我们在函数里面是无法求得数组元素个数的,这就要求我们在需要使用传入函数一个数组时,如果还要知道整个数组的大小是多少的话,就要另外定义一个形参作为接受整个数组的大小的角色。
? ? ? ? 那么,其实我们无法传入整个数组,我们只可以传入整个数组首元素地址的话,我们就可以用一个指针代替这个数组名。比如:
void test(int * p )
{
printf("%d",sizeof(p));
}
我们同样可以传入一个数组名作为参数,毕竟这个时候,数组名的本质就是一个普通的地址了。
总结:?维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
? ? ? ? 冒泡排序的核心思想就是:将两两相邻的元素进行比较。
? ? ? ? 每一次循环,我们都可以把整个数组里面剩下的里面最大的那个元素挑出来。
? ? ? ? 比如:我们现在定义一个 arr [ 10 ] ,这个数组里面一共有10个元素,我们要把这个数组排序成升序的形式,先从第一个arr [ 0 ]开始,arr [ 0 ] 和 arr [ 1 ] 进行比较,然后再把更大的放在这两者相对右边(就是arr[ 1 ]上),然后arr[ 1 ] 和 arr [ 2 ] 比较,更大的放在arr [ 2 ] 上,以此类推,直到arr[ 8?] 和 arr [ 9?]比较完成并且交换完成之后,我们就得到了整个数组最大的数arr[9 ] ,接下来我们缩小范围,只需要在剩下的arr [ 0 ] 到 arr [ 8 ] 进行比较...经过不断的缩小范围,最后我们范围会缩小到只有arr [ 0 ]和arr [ 1 ] 比较,再交换完成之后,我们就得到了一个升序的数组。这就是冒泡排序。
?与此同时,我们既然要知道一个数组的长度,在前面我们就知道,我们需要使用另外一个参数对这个数组的长度读入。那么我们就可以写出下面的冒泡排序了。
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for(i=0; i<sz-1; i++)
{
int j = 0;
for(j=0; j<sz-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
———————————————————————————————————————————
感觉差不多了,这几天考试,今天写到这里了,过几天再来写C部分,嘿嘿