一. 动态内存分配
1.1 为什么要使用动态内存分配
声明数组时,必须用一个编译时常量指定数组长度。但是有时数组的长度在运行时才知道,因为它所需要的内存空间取决于输入数据。
我们常采取的方法是声明一个较大的数组,它可以容纳可能出现的最多元素
这种方法的优点是简单,但它也有缺点
-
如果程序要使用的元素数量超过了声明的长度,它就无法处理这种情况,要避免这种情况,可以把数组声明的更大一些,但这种做法会使得第 2 个缺点进一步恶化
-
如果程序实际需要的元素数量比较少时,巨型数组的绝大部分内存空间都被浪费了
-
要时刻注意输入的数据不应超过数组的容纳范围,避免数组溢出
1.2 malloc 和 free
当程序在运行时另外需要一些内存时,可以用 malloc 函数,从内存池中提取一块合适的内存,并向程序返回一个指向这块内存的指针
当一块运行时分配的内存不再使用时,应调用 free 函数把它归还给内存池供以后使用
这两个函数原型如下
void *malloc(size_t size);
void free(void *pointer);
malloc 所分配的是一块连续的内存,如果内存池的可用内存无法满足 malloc 的请求,那么 malloc 返回一个 NULL 指针,因此对每个 malloc 返回的指针都应进行检查,确保它并非 NULL,这非常重要
free 的参数,要么是一个先前从 malloc,realloc 返回的指针,要么是 NULL,向 free 传递一个 NULL 参数不会有任何效果
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(void){
int *arr = (int*)malloc(4*sizeof(int));
assert(arr != NULL);
for(int i = 0; i < 4; ++i){
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
1.2.1 calloc
calloc 也用于内存分配,malloc 和 calloc 之间的主要区别是后者返回指向内存的指针之前把它初始化为 0。这个初始化能带来方便。
函数原型如下
void *calloc(size_t nmemb, size_t size);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int main(void){
int *arr = calloc(3, sizeof(int));
assert(arr != NULL);
for(int i = 0; i < 3; ++i){
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
1.3 realloc
realloc 函数用于修改一个原先已经分配的内存块的大小,可以使一块内存扩大或缩小
函数原型如下
void *realloc(void *ptr, size_t new_size);
如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内容添加到原先内存块的后面。新内存并未以任何方法进行初始化
如果它用于缩小一个内存块,该内存块尾部的部分内存便被拿掉,剩余内存的原先内容依然保留
如果原先的内存块无法改变大小,realloc 将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上,并释放原来的那块内存
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(void){
int *arr = (int*)malloc(4*sizeof(int));
assert(arr != NULL);
for(int i = 0; i < 4; ++i){
printf("%d ", arr[i]);
}
printf("\n");
arr = (int*)realloc(arr, 10 * sizeof(int));
assert(arr != NULL);
for(int i = 0; i < 10; ++i){
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
1.4. 常见的动态内存错误
在使用动态内存分配的程序中,常常会出现许多错误
- 对 NULL 指针进行解引用操作
- 对分配的内存进行操作时越界
- 释放非动态分配的内存
- 试图释放动态分配的内存的一部分
- 一块动态内存被释放后又继续使用
一个 malloc 或 calloc 一定要对应一个 free,以防止内存泄漏
二. 字符串函数
2.1 字符串拷贝
char *strcpy(char *dest, const char *src);
strcpy 函数将 src 所指向的字符串,包括结尾的空字节 ‘\0’,复制到dest所指向的缓冲区。 两个字符串不能重叠。并且目标字符串 dest 必须足够大以接收拷贝。 小心缓冲区超限!
#include <stdio.h>
#include <limits.h>
#include <string.h>
int main(void){
char str_1[8] = "Alice";
char str_2[8] = {0};
printf("str_2: %s\n", str_2);
strcpy(str_2, str_1);
printf("str_2: %s\n", str_2);
return 0;
}
char *strncpy(char *dest, const char *src, size_t n);
strncpy()函数与此类似,只是最多复制n个字节的src。
#include <stdio.h>
#include <limits.h>
#include <string.h>
int main(void){
char str_1[8] = "Alice_1";
char str_2[2] = "en";
printf("str_1: %s\n", str_1);
strncpy(str_1+3, str_2, 2);
printf("str_1: %s\n", str_1);
return 0;
}
2.2 字符串是否相等
int strcmp(const char *s1, const char *s2);
strcmp 判断两个字符串是否相等,相等返回 0。否则返回非 0 值
#include <stdio.h>
#include <limits.h>
#include <string.h>
int main(void){
char str_1[8] = "Alice";
char str_2[8] = "Alice";
if(!strcmp(str_1, str_2))
printf("Equal.\n");
else
printf("Unequal.\n");
return 0;
}
作业
1. 给你两个字符串 str_1 和 str_2 ,请你在 str_1 字符串中找出 str_2 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
示例 1
输入:haystack = “hello”, needle = “ll”
输出:2
示例 2
输入:haystack = “aaaaa”, needle = “bba”
输出:-1