现在有一个需求:先输入一个整数n
,再输入以空格分隔的n
个整数,然后求出这n
个整数中最大的数。
#include<stdio.h>
int main() {
int n;
int arr[20];
// 输入n的值
scanf("%d", &n);
// 循环n次,输入n个数据
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
// 暂时认为第一个元素为最大的
int max = arr[0];
// max与各个元素比较,把较大的放入max
for (int i = 0; i < n; i++)
{
if (arr[i] > max)
max = arr[i];
}
printf("%d\n", max);
}
输入:
10
8 6 4 1 2 5 7 9 3 0
输出:
9
这里的特殊性在于,数据的数量n
不确定,由用户输入决定。对于不同的输入,会出现以下3种情况。
- 如果
n
小于20,那么仅使用数组arr
中的n
个元素,后续的20 - n
个元素闲置。 - 如果
n
等于20,数组arr
中所有元素均被使用到。 - 如果
n
大于20,数组arr
无法容纳多于20个元素的数据。
第一种情况会造成有空的元素闲置,而第三种情况数组无法容纳所有需要输入的数据。那么,能否待用户输入n
后,再确定数组的元素个数呢?
变长数组
int n;
scanf("%d", &n);
int arr[n];
printf("sizeof of arr %d\n", sizeof(arr));
变长数组已经从C语言标准中移除了。编译器不一定会支持变长数组的特性。也就是说,这段代码可能在编译器中无法通过编译。
申请内存空间
更通用的方法是,使用头文件stdlib.h
中的malloc
函数,从内存中申请一段连续的内存空间。
函数 malloc 的声明如下:
void* malloc(size_t size);
参数size
为需要申请的内存空间大小。
返回值为void *
类型的指针。若申请成功,返回值为成功申请的内存的首地址。若申请失败,返回值为NULL
。
通过malloc
函数成功申请内存空间后,我们可以按照需要,将返回的指针转为任意类型的指针使用。只要通过指针访问内存时,不要超过这段内存空间的大小即可。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* pInt = NULL;
pInt = malloc(sizeof(int));
double* pDouble = NULL;
pDouble = malloc(sizeof(double));
*pInt = 123;
*pDouble = 3.1415926;
printf("%d %f", *pInt, *pDouble);
return 0;
}
malloc(sizeof(int))
申请了4字节的内存空间,若申请成功,它将返回一个void *
类型的指针,其数值为成功申请的内存空间的首地址。我们可以把这4字节的内存空间用于装int
类型的数据。只要将void *
通过赋值转换为int *
,接着对int *
类型的指针取值再赋值即可。
同样的,malloc(sizeof(double))
申请了8字节的内存空间,若申请成功,它将返回一个void *
类型的指针,其数值为成功申请的内存空间的首地址。我们可以把这8字节的内存空间用于装double
类型的数据。只要将void *
通过赋值转换为double *
,接着对double *
类型的指针取值再赋值即可。
C
与C++
的语法差异
在C语言中,**void ***
可以通过赋值转换为其他类型的指针。
int* pInt = NULL;
pInt = malloc(sizeof(int));
double* pDouble = NULL;
pDouble = malloc(sizeof(double));
但是,在C++
中,必须先把void *
指针强制转换后,才能赋值给其他类型的指针。建议无论是写C语言代码还是**C++**
代码,都做强制类型转换,这样有利于代码的可移植性。
通过强制转换将
void *
转换为其他类型的指针后,再赋值:
int *pInt = NULL;
pInt = (int *)malloc(sizeof(int));
double *pDouble = NULL;
pDouble = (double *)malloc(sizeof(double));
要严格保证使用指针访问成功申请的内存空间时,不要超过申请时预定的空间大小。
double* pDouble = NULL;
pDouble = (double*)malloc(sizeof(int));
*pDouble = 3.1415926;
上面的代码中,申请了sizeof(int)
,即4字节大小的空间。若申请成功,它将返回一个void*
类型的指针,其数值为成功申请的内存空间的首地址。接着,把它转换为double*
类型的指针,并赋值给pDouble
。但是,若对pDouble
指针使用取值运算符*
。将访问从首地址开始的8个内存空间,超出了申请时预定的4个字节空间,这种做法可能导致程序崩溃。
动态创建数组
若需要动态创建一个有10
个int
元素的数组,那么需要申请sizeof(int) * 10
字节的内存空间,或者写成sizeof(int[10])
也行。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* pArr = NULL;
// 申请sizeof(int) * n,转换为int *使用
pArr = (int*)malloc(sizeof(int) * 10);
// 给数组元素赋值
for (int i = 0; i < 10; i++)
pArr[i] = i;
// 打印数组元素
for (int i = 0; i < 10; i++)
printf("%d ", pArr[i]);
}
pArr = (int*)malloc(sizeof(int) * 10);
中的sizeof(int) * 10
写成sizeof(int[10])
也是可以的。
之前讨论的都是malloc
函数成功申请到内存的情况,作为一个稳健的程序应当也考虑到失败的情况。
返回值判断是否申请成功
若malloc
函数申请内存空间失败,它将返回NULL
。对NULL
指针取值将导致程序崩溃。建议每次通过malloc
函数申请内存空间都对返回值进行判断。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* pInt = NULL;
pInt = (int*)malloc(sizeof(int));
// 判断malloc函数是否成功申请内存空间
if (pInt != NULL)
{
// 若不为NULL,再使用这个指针
*pInt = 123;
printf("%d", *pInt);
}
}
释放内存空间
通过malloc
函数申请内存空间,并使用完成后,要记得使用free
函数把这段内存空间释放。
函数free
的声明如下:
void free (void* ptr);
通过 malloc 申请内存空间后,系统内记录了这段内存空间的首地址和空间大小,保存到已分配的内存空间列表中,并保证这段空间不会再分配给别的地方。需要释放这段内存空间时,将首地址传入free
函数。free
函数将查找这个首地址是否在已分配的内存空间列表中,若存在,则根据列表中记录的首地址和空间大小,释放这段内存空间。释放后,这段内存空间可以再次分配给别的地方。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* pInt = NULL;
pInt = malloc(sizeof(int));
if (pInt != NULL)
{
*pInt = 123;
printf("%d", *pInt);
// 释放内存空间
free(pInt);
}
}
上面的代码中,申请了sizeof(int)
个内存空间当做int
类型来使用。使用完毕后,使用free
函数将其释放。free
函数的参数是void *
类型的指针,而void *
类型的指针可以接收任何类型的指针。所以,可以直接将pInt
传递给free
函数而无需转换。
不能释放偏移后的指针
若将pInt
偏移后,再传递给free
函数。已分配的内存空间列表中并没有记录这个首地址,这样做并不能释放之前malloc(sizeof(int))
分配的内存空间,并且有可能导致程序崩溃。
// pInt偏移后,再传递给free函数
free(pInt + 1);
若只调用malloc
申请内存空间,而不调用free
函数释放内存空间,成功申请内存空间将一直保留直到程序结束。这期间程序所占用的内存空间将会越来越大,直到没有可分配的空间,无法再成功申请内存空间为止。
#include <stdio.h>
#include <stdlib.h>
int main()
{
while (1)
{
void* p = malloc(1024 * 1024);
printf("%d\n", p);
}
return 0;
}
这种情况往往是申请了内存空间,但是忘记释放导致的。对于不再使用的内存空间,应当及时释放。
如上面示例代码的情况,申请的内存空间首地址存放到指针p
中,而下一次新申请的内存空间首地址会覆盖掉上一次的首地址。由于没有保存内存空间的首地址,程序中将无法再通过任何方式使用或释放这些内存空间。这种现象被称作内存泄露,具有内存泄露问题的代码若长时间运行,会导致程序所占用的内存空间将会越来越大,直到没有可分配的空间,无法再成功申请内存空间为止。
从函数中返回指针
由于通过malloc
函数申请的内存空间直到调用free
函数释放或程序结束前都是有效的。因此,将指向malloc
函数申请的内存空间的指针从函数中返回是合法的。
#include <stdio.h>
#include <stdlib.h>
int* func()
{
int* pInt = NULL;
pInt = malloc(sizeof(int));
if (pInt != NULL)
{
*pInt = 123;
}
return pInt;
}
int main()
{
int* p = func();
if (p != NULL)
{
printf("%d", *p);
// 使用完记得释放
free(p);
}
return 0;
}
正确输出
123
函数func
中,申请了sizeof(int)
字节内存空间,若申请成功,将这段内存空间存放整型数据123
。并将指向这段内存空间的指针pInt
作为返回值返回。
函数main
中,调用函数func
获得返回的int *
类型的指针p
。由于不能保证func
函数返回的指针一定有效,这里也要做指针判空。若指针不为空,才可以使用它。使用完毕后,记得使用free
函数释放内存空间。
若在函数中申请一段内存空间作为数组使用,将数组首元素指针从函数中返回。在被调函数结束后,主调函数依然可以通过数组首元素指针偏移访问数组的所有元素。但是,必须要注意偏移时,不要访问超过内存空间预定大小的位置。并且使用完毕后,记得释放内存空间。
#include <stdio.h>
#include <stdlib.h>
int* func(int n)
{
int* pArr = NULL;
pArr = malloc(sizeof(int) * n);
if (pArr == NULL)
{
// 申请失败,直接返回NULL
return pArr;
}
// 申请成功,给每个元素赋值
for (int i = 0; i < n; i++)
pArr[i] = i;
return pArr;
}
int main()
{
// 数组长度为n,n初始化为10
int n = 10;
// 获取数组首元素指针
int* p = func(n);
if (p != NULL)
{
// 通过首元素指针偏移访问所有数组元素
for (int i = 0; i < n; i++)
printf("%d ", p[i]);
// 使用完记得释放
free(p);
}
return 0;
}
函数func
中,申请了sizeof(int) * n
字节内存空间。若申请失败,此时pArr
为NULL
,则直接返回pArr
若申请成功,给每个元素赋值后,将数组首元素指针pArr
返回。
函数main
中,调用函数func
获得返回的int *
类型的指针p
,它指向一个int
类型数组的首元素。由于不能保证func
函数返回的指针一定有效,这里也要做指针判空。若指针不为空,才可以使用它。通过首元素指针偏移可以访问所有数组元素。但是,首元素指针最多向后偏移9个元素。若继续向后偏移,将导致越界访问。最后,别忘了将首元素指针传入free
函数释放内存空间。