// 动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法 .
// 它是相对重要的一节课 , 如果能够将它学好 , 那么对于今后学习 OC 的内存管理会非常有帮助 .
//1. 内存区划分
// 内存区域划分 ( 地址由高到低 )
//(1) 栈区
//(2) 堆区
//(3) 静态区 ( 全局区 )
//(4) 常量区
//(5) 代码区
//(1) 栈区
// 函数中定义的局部变量由系统在栈区分配内存
// 开发人员不用关心系统如何分配内存 , 完全由系统分配和释放内存
// 栈区内的数据以栈的形式存储
// 栈的特点 : 先进后出
//(2) 静态全局区
// 全局变量和用 static 修饰的变量都由系统在静态全局存放
// 静态全局区所分配的内存 , 直到程序结束才能被释放
// 用 static 修饰的变量
//a. 存放在静态全局区
//b. 只能被初始化一次
//c. 如果没有赋初值 , 默认为 0
//(3) 常量区
// 常量 ('a', 123, 34.5, "iPhone")( 字符型 , 整型 , 实型 ) 都被存到常量区
// 常量区由系统管理
// 常量区的数据 , 是只读状态 , 不能修改
//(4) 代码区
// 程序中的函数和语句会被编译成 CPU 指令存在代码区
// 代码区由系统控制
//(5) 堆区
// 由开发人员手动申请 , 手动控制
// 申请内存函数
//void *malloc(size_t)
// 返回值类型 :void * ( 泛类型 ), 泛型指针可以转换成任意一种指针类型
// 函数名 :malloc
// 参数类型 :size_t, unsigned int( 无符号整型 )
// 参数含义 : 多少个字节
//
堆区地址存放在栈区
int
main(
int
argc,
const
char
* argv[]) {
//static
静态标示符
,
修饰的变量只会初始化一次
int
a = 10;
for ( int i = 0; i < 10; i++) {
static int b = 20;
b += a;
printf ( "b = %d " ,b);
}
// 常量和变量
// 常量 : 程序运行期间 , 常量里面的内容不可以被改变 , 只能被访问
// 变量 : 程序运行期间只可以被重新赋值被改变的量
//const 如果在变量类型之前加上关键字 const, 那么此时变量被当做常量使用
const int c = 30;
c = 60; // 被 const 修饰之后的 c 此时不能被再重新赋值了
printf ( "c = %d\n" ,c);
/* 复习
不同的数据类型所存储的不同的内容
int , short , long 存储整数 , 整形类型
float , double 存储小数 , 浮点型类型
char 存储字符或字符串 , 字符型类型
BOOL/bool 存储 YES(1) 和 NO(0), 布尔类型
假设定义一个结构体类型为 Stu 的结构体 :
Stu 存储结构体成员变量 , 结构体类型
int * , char * , float * , double * ,short * ,long * 存储相应类型的地址 指针类型
Stu * 存储结构体变量指针地址
*/
//const 关键字 公司笔试题
//const 关键字修饰的是 *p, 如何看修饰的变量 , 先忽略数据类型 , 再看修饰的变量是谁 , 修饰谁谁就不能被改变 , 但是可以改变 p ( 地址 )
int x = 10,y = 30;
//1.
const int *p = &y;
p = &x; //p 可以被改变
*p = 33; //*p 被修饰 , 所以不能被改变
//2. 和第一种情况一样
int const *p = &y; // 报错
//3.const 修饰 p( 地址 ), 那么指针就不能重新指向 ( 地址不能重新赋值 ) p 不可改变 ,*p 可变
int * const p = &y;
*p = 60;
p = &x; // 已被修饰不可改变
//4. 里面的 const 修饰了 p, 外面的 const 修饰 *p
const int * const p = &y;
*p = 40; // 报错
p = 50; // 报错
/*
内存的五大分区 :
1. 栈区 : 主要存放局部变量或者数组 , 定义在函数内部 , 当函数调用时开辟空间 , 函数结束的时候释放内存 ; 内存由系统分配和管理
2. 堆区 : 由开发人员手动开辟的内存区间 , 手动释放
3. 静态区 / 全局区 : 用于存储全局变量或者静态变量 ( 程序运行时开辟空间 , 程序结束时则释放空间 , 也是由系统管理内存 )
4. 常量区 : 存储各种类型的常量 只读
5. 代码区 : 存放程序编译之后形成的 CPU 指令 , 告诉计算机存放在代码区内
*/
// 如何在空间上存放数据 :
// 在堆区开辟空间
//malloc 手动开辟 n 个字符的空间 , 返回的是一个泛类型的地址 (void * 泛类型 : 所有的指针类型 )
// 如果申请的空间为 8 个字节的话
/*
char * 可以存储 8 个字符
int * 可以存储两个整型数
float * 可以存储两个浮点数
short * 可以存储 4 个短整型
long * 可以存储 1 个长整型
*/
char *p = malloc(8); // 在堆区开辟 8 个字节的空间
strcpy (p, "iphone6" );
printf ( "%s\n" ,p);
printf ( "%s\n" ,p + 2);
printf ( "%c\n" ,*(p + 3));
free (p); // 手动释放 , 释放的是我们手动开辟的空间 , 也就是把空间还给系统 ( 注 : 不可过度释放 , 即开辟一次释放一次 ,malloc 的次数 = free 的次数 )
p = NULL ; // 把空间还给系统之后 , 需要将地址置空
/*
堆区里比较常见的一些错误 :
1. 野指针 访问一块我们不能管理的内训空间 , 也就是没有获得空间所有权 ( 即此时这块空间不在堆区 )
解决方案 : 再将我们手动开辟的空间还给系统之后 , 记得将地址置空 NULL
2. 过度释放 : 对同一片内存 free 了多次
3. 内存泄露 : 没有对我们手动开辟的内存进行及时释放 , 就会造成内存堆积 , 造成隐患 ; 记得手动开辟空间之后一定要 free 一次
*/
// 在堆区开辟一块空间 , 存储 5 个整数 , 随机出 [10 30] 的值并输出
int *a = malloc( sizeof ( int ) * 5); // 开辟存储 5 个整数的空间
for ( int i = 0; i < 5 ; i++) {
*(a + i) = arc4random() % (30 - 10 + 1) + 10;
printf( "%d " ,*(a + i));
}
free (a); // 释放内存 防止内存泄露
a = NULL ; // 置空 防止野指针
// 在堆区开辟一块空间 , 存储 10 个整数 , 随机出 [10 30] 的值并升序输出
int *b = malloc ( sizeof ( int ) * 10);
for ( int i = 0; i < 10; i++) {
*(b + i) = arc4random () % (30 - 10 + 1) + 10;
}
for ( int i = 0; i < 10 - 1; i++) {
for ( int j = 0 ; j < 10 - 1 - i; j++) {
if (*(b + j) > *(b + j + 1)) {
int temp = *(b + j);
*(b + j) = *(b + j + 1);
*(b + j + 1) = temp;
}
}
}
printf ( "\n 排序好的数组为 :" );
for ( int i = 0; i < 10; i++) {
printf ( "%d " ,*(b + i));
}
char symbol[] = "Y8ou C6an3 Y1ou U2p,No3 Can8 N6o B B9!" ;
int count = 0; // 用来记录数字的个数
int i = 0;
while (symbol[i] != 0) {
if (symbol[i] >= '0' && symbol[i] <= '9') {
count++; // 如果是数字的话 , 则 count+1
}
i++; // 循环增量
}
// 根据数字的个数来动态在堆区开辟相应字节大小字节数的空间 , 因为字符串结束标志是 '\0', 所以我们开辟 count + 1 个字节数
char *c = malloc(count + 1);
int j = 0,k = 0;
while (symbol[j] != '\0') {
if (symbol[j] >= '0' && symbol[j] <= '9') {
*(c + k) = symbol[j];
k++; // 设置 k 的原因是每当 if 语句成立一次数组地址就可以往高位平移 +1
}
j++;
}
*(c + k) = '\0'; // 将堆区最后一个空间设置为 '\0'
printf ( "%s\n" ,c);
// 释放内存
free (c);
c = NULL ;
//calloc 堆区开辟空间 ,calloc(m, n); 分配 m 个 size = n 字节大小的空间
int *p1 = calloc (8, 4); // 有 8 个 4 个字节大小的整型数 , 共占 32 个字节
//realloc 工作原理 : 先以原先开辟的空间为基准 , 如果发现之前开辟的空间剩余不足 , 重新分配新的空间 , 同时 realloc 集成了对之前开辟空间的 free 操作
char *p2 = malloc (8);
// 假设此时发现开辟的空间不够使用 , 则需要重新分配一个更大的内存
char *p3 = realloc (p2, 20); // 把 p2 free 掉了 , 重新分配了 20 个字节大小
for ( int i = 0; i < 10; i++) {
static int b = 20;
b += a;
printf ( "b = %d " ,b);
}
// 常量和变量
// 常量 : 程序运行期间 , 常量里面的内容不可以被改变 , 只能被访问
// 变量 : 程序运行期间只可以被重新赋值被改变的量
//const 如果在变量类型之前加上关键字 const, 那么此时变量被当做常量使用
const int c = 30;
c = 60; // 被 const 修饰之后的 c 此时不能被再重新赋值了
printf ( "c = %d\n" ,c);
/* 复习
不同的数据类型所存储的不同的内容
int , short , long 存储整数 , 整形类型
float , double 存储小数 , 浮点型类型
char 存储字符或字符串 , 字符型类型
BOOL/bool 存储 YES(1) 和 NO(0), 布尔类型
假设定义一个结构体类型为 Stu 的结构体 :
Stu 存储结构体成员变量 , 结构体类型
int * , char * , float * , double * ,short * ,long * 存储相应类型的地址 指针类型
Stu * 存储结构体变量指针地址
*/
//const 关键字 公司笔试题
//const 关键字修饰的是 *p, 如何看修饰的变量 , 先忽略数据类型 , 再看修饰的变量是谁 , 修饰谁谁就不能被改变 , 但是可以改变 p ( 地址 )
int x = 10,y = 30;
//1.
const int *p = &y;
p = &x; //p 可以被改变
*p = 33; //*p 被修饰 , 所以不能被改变
//2. 和第一种情况一样
int const *p = &y; // 报错
//3.const 修饰 p( 地址 ), 那么指针就不能重新指向 ( 地址不能重新赋值 ) p 不可改变 ,*p 可变
int * const p = &y;
*p = 60;
p = &x; // 已被修饰不可改变
//4. 里面的 const 修饰了 p, 外面的 const 修饰 *p
const int * const p = &y;
*p = 40; // 报错
p = 50; // 报错
/*
内存的五大分区 :
1. 栈区 : 主要存放局部变量或者数组 , 定义在函数内部 , 当函数调用时开辟空间 , 函数结束的时候释放内存 ; 内存由系统分配和管理
2. 堆区 : 由开发人员手动开辟的内存区间 , 手动释放
3. 静态区 / 全局区 : 用于存储全局变量或者静态变量 ( 程序运行时开辟空间 , 程序结束时则释放空间 , 也是由系统管理内存 )
4. 常量区 : 存储各种类型的常量 只读
5. 代码区 : 存放程序编译之后形成的 CPU 指令 , 告诉计算机存放在代码区内
*/
// 如何在空间上存放数据 :
// 在堆区开辟空间
//malloc 手动开辟 n 个字符的空间 , 返回的是一个泛类型的地址 (void * 泛类型 : 所有的指针类型 )
// 如果申请的空间为 8 个字节的话
/*
char * 可以存储 8 个字符
int * 可以存储两个整型数
float * 可以存储两个浮点数
short * 可以存储 4 个短整型
long * 可以存储 1 个长整型
*/
char *p = malloc(8); // 在堆区开辟 8 个字节的空间
strcpy (p, "iphone6" );
printf ( "%s\n" ,p);
printf ( "%s\n" ,p + 2);
printf ( "%c\n" ,*(p + 3));
free (p); // 手动释放 , 释放的是我们手动开辟的空间 , 也就是把空间还给系统 ( 注 : 不可过度释放 , 即开辟一次释放一次 ,malloc 的次数 = free 的次数 )
p = NULL ; // 把空间还给系统之后 , 需要将地址置空
/*
堆区里比较常见的一些错误 :
1. 野指针 访问一块我们不能管理的内训空间 , 也就是没有获得空间所有权 ( 即此时这块空间不在堆区 )
解决方案 : 再将我们手动开辟的空间还给系统之后 , 记得将地址置空 NULL
2. 过度释放 : 对同一片内存 free 了多次
3. 内存泄露 : 没有对我们手动开辟的内存进行及时释放 , 就会造成内存堆积 , 造成隐患 ; 记得手动开辟空间之后一定要 free 一次
*/
// 在堆区开辟一块空间 , 存储 5 个整数 , 随机出 [10 30] 的值并输出
int *a = malloc( sizeof ( int ) * 5); // 开辟存储 5 个整数的空间
for ( int i = 0; i < 5 ; i++) {
*(a + i) = arc4random() % (30 - 10 + 1) + 10;
printf( "%d " ,*(a + i));
}
free (a); // 释放内存 防止内存泄露
a = NULL ; // 置空 防止野指针
// 在堆区开辟一块空间 , 存储 10 个整数 , 随机出 [10 30] 的值并升序输出
int *b = malloc ( sizeof ( int ) * 10);
for ( int i = 0; i < 10; i++) {
*(b + i) = arc4random () % (30 - 10 + 1) + 10;
}
for ( int i = 0; i < 10 - 1; i++) {
for ( int j = 0 ; j < 10 - 1 - i; j++) {
if (*(b + j) > *(b + j + 1)) {
int temp = *(b + j);
*(b + j) = *(b + j + 1);
*(b + j + 1) = temp;
}
}
}
printf ( "\n 排序好的数组为 :" );
for ( int i = 0; i < 10; i++) {
printf ( "%d " ,*(b + i));
}
char symbol[] = "Y8ou C6an3 Y1ou U2p,No3 Can8 N6o B B9!" ;
int count = 0; // 用来记录数字的个数
int i = 0;
while (symbol[i] != 0) {
if (symbol[i] >= '0' && symbol[i] <= '9') {
count++; // 如果是数字的话 , 则 count+1
}
i++; // 循环增量
}
// 根据数字的个数来动态在堆区开辟相应字节大小字节数的空间 , 因为字符串结束标志是 '\0', 所以我们开辟 count + 1 个字节数
char *c = malloc(count + 1);
int j = 0,k = 0;
while (symbol[j] != '\0') {
if (symbol[j] >= '0' && symbol[j] <= '9') {
*(c + k) = symbol[j];
k++; // 设置 k 的原因是每当 if 语句成立一次数组地址就可以往高位平移 +1
}
j++;
}
*(c + k) = '\0'; // 将堆区最后一个空间设置为 '\0'
printf ( "%s\n" ,c);
// 释放内存
free (c);
c = NULL ;
//calloc 堆区开辟空间 ,calloc(m, n); 分配 m 个 size = n 字节大小的空间
int *p1 = calloc (8, 4); // 有 8 个 4 个字节大小的整型数 , 共占 32 个字节
//realloc 工作原理 : 先以原先开辟的空间为基准 , 如果发现之前开辟的空间剩余不足 , 重新分配新的空间 , 同时 realloc 集成了对之前开辟空间的 free 操作
char *p2 = malloc (8);
// 假设此时发现开辟的空间不够使用 , 则需要重新分配一个更大的内存
char *p3 = realloc (p2, 20); // 把 p2 free 掉了 , 重新分配了 20 个字节大小
//
动态内存的其他函数
//(1)void *calloc(n, size)
// 和 malloc 一样 , 都是申请内存 , 但是 calloc 申请内存后 , 会对内存中的内容做清空的操作 ; 由于多了一步清空操作 , 效率要比 malloc 低
//n: 个数
//size: 字节数
//calloc 申请的内存字节数 = n * size
//(2)void realloc(p, size)
// 从给定的位置 p, 开始重新申请 size 个字节
// 从地址 p 向后申请 size 个字节 , 如果后面可用的字节够的话 , 就申请内存 , 并返回当前的指针 p; 如果不够的话 , 会再去内存中找一块连续的空间 , 如果找到 , 就返回这一块连续空间的首地址 , 并且把之前所占用的内存释放
//(3)void memset(p, c, n)
// 从指针 p 的位置开始初始化 n 个字节的内容 , 把内容改成 c
//(4)void *memcpy(void *dest, const void *source, n)
// 从指针 source 的位置开始 , 向指针 dest 位置 , 拷贝 n 个字节的内容
char str1[] = "ABC" ;
char str2[] = "123" ;
memcpy(str1, str2, 2);
printf( "%s\n" , str1);
//(5)memcmp(p1, p2, n)
// 比较 p1 和 p2 指向的内存所存放的内容是否相同 , 比较 n 个字节 , 相同返回 0, 不同返回差值
int *a1 = malloc (4);
*a1 = 1;
int *a2 = malloc (4);
*a2 = 3;
int result = memcmp(a1, a2, 1);
printf("%d\n", result);
return
0;
}
作业:
int
main(
int
argc,
const
char
* argv[]) {
//1. 输入 3 个单词 , 动态分配内存保存单词 , 并在最后输出
// 提示 : 定义一个指针数组保存数据 char * words[3] = {0};
// 存储 3 个单词 , 就意味着需要在堆区开辟 3 次空间 , 每次开辟空间都会返回对应的首地址 . 所以为了存储 3 个地址 , 定义指针数组存储 .
char * words[3] = { "Wo" , "Ai" , "Ni" };
for ( int i = 0; i < 3; i++) {
char *p1 = malloc ( sizeof (words[3]));
char *p1i = strcpy (p1, words[i]);
printf ( "%s\n" ,p1i);
free (p1i);
p1 = NULL ;
}
//2. (***) 已知一个数组 20 个元素 ( 随机 1 到 100 之间包含 1 和 100), 求大于平均数的元素个数 , 并动态生成一个新数组保存 ( 提示 :malloc 出 20 个元素保存 )
int a[20] = {0};
int sum = 0;
printf ( " 随机出的数字为 :\n" );
for ( int i = 0; i < 20; i++) {
a[i] = arc4random () % (100 - 1 + 1) + 1;
sum += a[i];
printf ( "%d " ,a[i]);
}
int count = 0; // 定义大于平均数的元素的个数 count
int avg = 0;
avg = sum / 20;
for ( int i = 0; i < 20; i++) {
if (a[i] > avg) {
count++; // 遍历 20 个元素 , 只要 if 语句成立一次 , 元素个数 count 就增加一个
}
}
printf ( " 大于平均数的个数为 :%d 个 , 分别为 :\n" ,count);
int *p = malloc (count * 4); // 定义一个指针类型 *p 来存储大于平均数的元素的地址 , 因为是 int 类型所以要 count * 4
int k = 0;
for ( int i = 0; i < 20; i++) {
if (a[i] > avg) {
k++; // 设置 k 的原因是每当 if 语句成立一次数组地址就可以往高位平移 +1
p[k - 1] = a[i];
printf ( "%d " ,p[k - 1]);
}
}
free (p);
p = NULL ;
return 0;
//1. 输入 3 个单词 , 动态分配内存保存单词 , 并在最后输出
// 提示 : 定义一个指针数组保存数据 char * words[3] = {0};
// 存储 3 个单词 , 就意味着需要在堆区开辟 3 次空间 , 每次开辟空间都会返回对应的首地址 . 所以为了存储 3 个地址 , 定义指针数组存储 .
char * words[3] = { "Wo" , "Ai" , "Ni" };
for ( int i = 0; i < 3; i++) {
char *p1 = malloc ( sizeof (words[3]));
char *p1i = strcpy (p1, words[i]);
printf ( "%s\n" ,p1i);
free (p1i);
p1 = NULL ;
}
//2. (***) 已知一个数组 20 个元素 ( 随机 1 到 100 之间包含 1 和 100), 求大于平均数的元素个数 , 并动态生成一个新数组保存 ( 提示 :malloc 出 20 个元素保存 )
int a[20] = {0};
int sum = 0;
printf ( " 随机出的数字为 :\n" );
for ( int i = 0; i < 20; i++) {
a[i] = arc4random () % (100 - 1 + 1) + 1;
sum += a[i];
printf ( "%d " ,a[i]);
}
int count = 0; // 定义大于平均数的元素的个数 count
int avg = 0;
avg = sum / 20;
for ( int i = 0; i < 20; i++) {
if (a[i] > avg) {
count++; // 遍历 20 个元素 , 只要 if 语句成立一次 , 元素个数 count 就增加一个
}
}
printf ( " 大于平均数的个数为 :%d 个 , 分别为 :\n" ,count);
int *p = malloc (count * 4); // 定义一个指针类型 *p 来存储大于平均数的元素的地址 , 因为是 int 类型所以要 count * 4
int k = 0;
for ( int i = 0; i < 20; i++) {
if (a[i] > avg) {
k++; // 设置 k 的原因是每当 if 语句成立一次数组地址就可以往高位平移 +1
p[k - 1] = a[i];
printf ( "%d " ,p[k - 1]);
}
}
free (p);
p = NULL ;
return 0;
}