一. 动态内存分配

1.1 为什么要使用动态内存分配

声明数组时,必须用一个编译时常量指定数组长度。但是有时数组的长度在运行时才知道,因为它所需要的内存空间取决于输入数据。

我们常采取的方法是声明一个较大的数组,它可以容纳可能出现的最多元素

这种方法的优点是简单,但它也有缺点

  1. 如果程序要使用的元素数量超过了声明的长度,它就无法处理这种情况,要避免这种情况,可以把数组声明的更大一些,但这种做法会使得第 2 个缺点进一步恶化

  2. 如果程序实际需要的元素数量比较少时,巨型数组的绝大部分内存空间都被浪费了

  3. 要时刻注意输入的数据不应超过数组的容纳范围,避免数组溢出

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. 常见的动态内存错误

在使用动态内存分配的程序中,常常会出现许多错误

  1. 对 NULL 指针进行解引用操作
  2. 对分配的内存进行操作时越界
  3. 释放非动态分配的内存
  4. 试图释放动态分配的内存的一部分
  5. 一块动态内存被释放后又继续使用

一个 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