再次学习malloc()

发布时间 2023-04-19 19:15:35作者: 针眼画师

在力扣做题,发现给的函数是char ** fizzBuzz(int n, int* returnSize)这种类型的,也就是返回的是一个二级指针

题目
给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:

answer[i] == "FizzBuzz" 如果 i 同时是 3 和 5 的倍数。
answer[i] == "Fizz" 如果 i 是 3 的倍数。
answer[i] == "Buzz" 如果 i 是 5 的倍数。
answer[i] == i (以字符串形式)如果上述条件全不满足。
示例 1:
输入:n = 3
输出:["1","2","Fizz"]
示例 2:
输入:n = 5
输出:["1","2","Fizz","4","Buzz"]
代码
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
char ** fizzBuzz(int n, int* returnSize)
{
    char **ans;
		ans=(char **) malloc (sizeof(char*)*n);

	for(int i=1;i<=n;i++)
	{
		if(i%3==0&&i%5==0)
		{
			ans[i-1]="FizzBuzz";  // ans才是二级指针,ans[i-1]中储存的是指针,字符串可以直接赋值给指针;
		}
		else if(i%3==0)
		{
			ans[i-1]="Fizz";      // 字符串常量不需要分配内存,指针储存了它的地址;
		}
		else if(i%5==0)
		{
			ans[i-1]="Buzz";
		}
		else
		{
			ans[i-1]=(char**)malloc(sizeof(char*));  // 不是字符串常量,需要分配地址和内存,需要malloc();
			sprintf(ans[i-1],"%d",i);
		}
	}
    *returnSize=n;
    return ans;
}

以前用过的是int* twoSum(int* nums, int numsSize, int* returnSize)这样的,这里int* twoSum()代表函数返回一个指针(其实就是一个数组),nums是在函数外声明的数组,numsSize是数组nums的元素个数,returnSize是返回地址处储存元素的个数的地址。

为什么这么说?什么叫“地址处储存元素的个数”?

因为数组名和数组的首元素地址等价,如果return ans;就等价于return &ans[0],所以函数如果返回一个指针,那这个指针有可能是指向一个单个值,也有可能指向一个数组。这里的returnSize是一个指针,我们通过*returnSize=size把这些信息传递出去。
所以可以看到,这个函数做到了“传入数组”和“返回数组”。

既然数组也可以视为指针,为什么要用malloc()创建数组?

这涉及到变量的储存方式,简单来说:
  • 如果直接用数组,那么这个数组的生命只有这个函数的大括号这么长,因为它是属于这个函数的局部变量。
  • 如果使用malloc(),那么这个数组的生命将持续到内存被free()释放或者main()结束。
局部变量储存在“栈”中,这是由系统自行分配的,程序员无法插手,所以使用受到限制,但优点是速度快。用malloc()分配的变量储存在“堆”中,这个内存是由程序员控制的,在没有使用free()释放的情况下,将持续到程序结束。

所以我在初次学习malloc()中遇到的那句Note: The returned array must be malloced, assume caller calls free().,其意思就是,不要使用局部变量,要使用malloc(),这样力扣才能得到你储存的东西(这句话出现次数多是因为力扣总是给一个函数,而不是让自己写程序,所以只能考虑到函数的一些局限性)。

可是有一个问题,我在上个博客中说过,如果malloc()free()不成对使用,那么就有内存泄漏的风险。那我在函数中是否也要成对写呢?如果成对写,内存还是在函数中就被释放,和局部变量无异。如果不成对写,又有内存泄漏的风险,怎么办?

不用担心,他告诉了assume caller calls free(),也就是让你假设函数的调用者调用了free(),所以你才必须使用malloc(),因此不用在函数中使用free(),不用担心内存泄漏的问题。
另:如果在函数内使用了free(),会怎么样?有时还是会正常输出,但是这里储存的东西变成了未定义的。

回过头来,我们看一开始的返回值为二级指针的函数。

首先我们肯定还是要使用malloc(),但是返回值为二级指针,写法如下:
char **ans = (char**) malloc(sizeof(char*)*size);
指针是储存某个值地址的变量,那么二级指针就是储存这些指针的地址的变量。
前面说过,指针可以看作是数组,只不过元素个数没有声明而已。那么二级指针,就可以看作是二维数组。
举个例子就能明白:
// 3个字符数组
char a[3]={'1','2','3'};
char b[2]={'4','5'};
char c[1]={'6'};
// 3个指向储存字符类型值的指针
char *a1,*b1,*c1;
// 储存数组的地址
a1=&a;
b1=&b;
c1=&c;
// 1个指向储存字符类型值的指针的指针
char **a2[3]={&a1,&b1,&c1};
// 这个二级指针a2的效果如下:
a2 -> a1 -> a = {'1','2','3'}
   -> b2 -> b = {'4','5'}
   -> c2 -> c = {'6'}

在这个题目中,返回的是元素为字符串的数组,而我们知道字符串就是特殊的字符数组,因此返回值本身就应该是二维数组。而这里没有用二维数组,而是二维指针的原因,前面已经说了,局部变量只能在函数内使用,所以必须要使用malloc()