动态内存分配
存储区的划分:
在计算机的内存中,可以分成5个区,每个区都有着不一样的效果。按内存编号从小到大的顺序,分别是:
(1)、代码区:
计算机将我们写的代码通过二进制转换后,放进了这个代码区。
(2)、常量区:
在我们写代码时,所有的常量,都放在常量区,常量区的所有值都是可读不可写的。也就是说,常量区的所有值都是不能改变的。若强行对其赋值,则在运行的时候,直接导致程序崩溃。
常用的一个关键字:const。
const可以把一个变量声明成常量:const int i = 10; 此时,就应该把num1当成常量来使用。此时再给num1赋值,会报错。如果用指针取到num1的地址,然后再用 * 取值后给其赋其他值,虽然可以成功赋值,但是此时的num1已经不是原来的num1,而是原来的num1用const修饰后,直接转移到了常量区,用指针取地址后,取到的是num1在栈区的地址,改动的也是原来栈区内的num1。也就说,有两个num1,一个在常量区,一个在栈区。因此,为了保证安全,不能这样写。
// 常量区
// 程序中出现的所有常量,保存到常量区
// 常量区的所有值:只读不可写
// const
// 可以将变量声明成常量
// int num1 = 10;
// num1 = 20;//可以改值
// //const的意思:只读,num1的值不能改变了。
// const int num1 = 10;//定义成常量,要当做常量使用。
// num1 = 20;//报错
//不要这样写。这样写会破坏安全性。
// int *pi = &num1;
// *pi = 20;
//num1的值在常量区有一份,在栈区有一份。
// printf("%d\n",num1);//10,这个num1已经放到了常量区。
// printf("%d\n",*pi);//20,这个值是num1的值,但是这个num1的位置是在栈区。
//const 放在不用的位置,效果不一样(作业)
// const int *p = NULL;
(3)、静态区:
用来存放静态值的地方。在代码中,用static来标记。用static来标记的语句,在整个程序运行过程,只进行一次初始化,不论程序是否循环到该语句,都不再执行。如果初始化的时候,没有给初始值,系统会自动给0。静态区的东西常驻内存,直到 程序运行结束才会释放。全局变量也放在静态区,也叫全局区。这个时候,其他文件可以用到这个全局变量。此时,如果我们在这个全局变量加上static修饰,这时候的static会限制作用域,限制其他文件不能用。static 依然受到作用域的约束。
#import <Foundation/Foundation.h>
//也在静态区,也叫做全局区
//int n2 = 10;
//如果全局变量加了static 会限制作用域,其他文件不能使用。
static int n2 =10;
//静态区的变量,可以不做初始化,自动清0.
//可以计数使用
int test(){
static int count = 0;
return ++ count ;
}
int main(int argc, const char * argv[]){
//静态区
// static int num = 10;
// printf("%d\n",num);
//
// num = 20;
//
// printf("%d\n",num);
// while (1) {
// int i = 0;
// printf("%d\n",i++);//全部打印0
// }
// while (1) {
// //静态变量只被初始化一次。
// static int i = 0;//这条语句执行过一次后,这条语句就失效了,不再执行
printf("%d\n",i++);//全部打印0
}
//依然受作用域的约束
i = 10;//报错
printf("%d\n",test());//1
printf("%d\n",test());//2
return 0;
}
(4)、堆区:
用malloc等函数分配的内存。
堆区是唯一一个由我们自己可以掌管内存大小,并对内存回收的区。即:⼿动分配,⼿动释放。
堆,是内存中,最大的一块。因此,大部分情况下,类型的内存申请是在堆中完成的。
malloc函数:动态分配内存函数:void *malloc();
void * 表示任意指针类型。也就是返回值可以是任意类型的指针。
括号内的参数,意思是分配多少字节的大小。
//参数的意思是:写多少,就分配几个字节的内存。
//void * 任意指针类型
// char *p1 = malloc(1);//在堆中分配了一个字节内存
// char *p2 = malloc(10);
// int *ip = malloc(sizeof(int));
// *ip = 20;
// printf("%d\n",*ip);//20
一些例子:
// //练习
// //数组:20个元素,都是int类型的。
// //在堆区分配空间
// //使用随机数填值。[0~30]后打印
//
// int *arr = malloc(sizeof(int) * 20);//在堆中分配了20个int类型的内存。用arr指针来找到在堆中的位置。
//
// for (int i =0; i < 20; i ++) {
// *(arr + i) = arc4random() % 30;
// }
//
// for (int i = 0; i < 20 ; i ++) {
// printf("第 %d 个元素:%d\n",(i+1),arr[i]);
// }
// //申请的内存,如果不手动释放,会一直存在。造成内存泄露。
// //释放内存
// free(arr);
free()函数:与malloc等动态分配内存的函数使用,对分配号的内存进行释放,回收内存。但是这里的释放,是标记删除。意思是对编号进行释放,但是内容仍然存在,只是告诉系统,这个内存已经可以回收,可以重新分配给其他人用。
注意:不能像下面那样写
//切记,不能这么写,是错误的
//原因是:先分配了6个字节,还没有释放,就去申请另外的空间,这样导致6个空间的内存不能释放。
// int *p4 = malloc(6);
// p4 = malloc(10);
例子:
//练习:
// //找出字符串中的所有数字,用malloc来存放这些数字
// char *str = "a1b2c3d4";
// //统计数字
// int count = 0;
// //循环增量
// int i = 0;
// //收集数字
// char tempArr[100] = {0};
//
// while (*(str+i) != '\0') {
// if (str[i] >= '0' && str[i] <='9') {
// tempArr[count] = str[i];
// count ++;
// }
// i ++;
// }
// //加上'\0'表示结束
// tempArr[count] = '\0';
// //分配空间
// char *arr = malloc(sizeof(char) * (count+1));
// //赋值:把栈区的数据,拷贝到堆区。
// strcpy(arr, tempArr);
// printf("%s\n",arr);//1234
// //释放
// free(arr);//释放的意思是标记删除
//
// printf("%s\n",arr);//仍然为1234
//
// arr = NULL;
//
// printf("%s\n",arr);//打印 (null)
由于free是标记删除,并不对内存里的内容进行清除,此时,还需要将我们的指针指向NULL:arr = NULL;
练习:
// char *words[3] = {0};
// for (int i = 0; i < 3; i ++) {
// char temp[20] = {0};
// printf("输入单词,回车结束:\n");
// scanf("%s",temp);
// words[i] = malloc(sizeof(char) * (strlen(temp)+1));
// strcpy(words[i], temp);
// }
//
// for (int i = 0; i < 3; i++) {
// printf("%s\n",words[i]);
// }
//// free(words); 三个地址要逐个释放。其实words是数组名,数组名就在栈区,不能用free来释放。words[i]的值才是在堆中开辟的空间的地址。能用free释放
// for (int i = 0 ; i < 3; i ++) {
// free(words[i]);
// words[i] = NULL;
// }
calloc函数:与malloc差不多,只是有两个参数,第一个参数是告诉有多少个后面的类型,第二个参数是告诉有多少字节。那么全部分配下来的内存为:第一个参数 * 第二个参数。calloc在分配好内存后
//calloc
// //第一个参数:有多少个
// //第二个参数:有多少字节
// //全部清0
// //使用规则和malloc分配的内存一样
// char *p2 = calloc(10, sizeof(int));//有10个int类型,4字节的空间,即分配了10 * 4 = 40个字节的空间,并全部清0
// char * p1 = calloc(10, 4);//有10个4字节的空间,即分配了10 * 4 = 40个字节的空间,并全部清0
//
// for (int i = 0; i < 40 ; i ++) {
// printf("%d",p1[i]);//0000000000000000000000000000000000000000
// }
// free(p1);
// p1 = NULL;
// free(p2);
// p2 = NULL;
realloc 函数:改变一块内存。
//realloc
// char *p2 = malloc(100);
//
// //第一个参数:你要改变哪一块的内存
// //第二个参数:要变成多大
// char *p3 = realloc(p2, 120);
//
// free(p3);
// p2=NULL;
// p3 = NULL;
内存操作函数:
(1)、memset:设置内存给定一些内容:
//memset内存设置
// char *p3 = malloc(10);
// //第一个参数:设置哪块内存
// //第二个参数:设置成什么
// //第三个参数:设置多少个字节
// memset(p3, 2, 10);//设置p3所指向的内存,把里边的东西设置成2,设置10个字节。
// for (int i = 0; i < 10; i ++) {
// printf("%d",p3[i]);//2222222222
// }
(2)、memcpy:内存拷贝函数
//memcpy内存拷贝
// char *p1 = malloc(10);
// char *p2 = malloc(10);
//
// memset(p1, 5, 10);
// //第一个参数:拷贝到哪
// //第二个参数:从哪拷贝
// //第三个参数:拷贝多少字节
// memcpy(p2, p1, 10);//拷贝到p2,从p1拷贝过来,拷贝10个字节。
// for (int i = 0; i < 10; i ++) {
// printf("%d",p2[i]);//5555555555
// }
// free(p1);
// p1 = NULL;
// free(p2);
// p2 = NULL;
(3)、memcmp: 内存比较函数
//memcmp内存比较
// char *p1 = malloc(10);
// memset(p1, 2, 10);
//
// char *p2 = malloc(10);
//
// memset(p2, 3, 10);
// //第一个参数:第一块内存
// //第二个参数:第二块内存
// //第三个参数:比较多少个字节
// //内存比较和字符串比较一样,找到第一个不相等的字节,求差值。
// //如果所有字节逗相等,返回0。
// //返回 >0 ,第一个比第二个大,
// //返回 <0 ,第一个比第二个小。
// int n = memcmp(p1, p2, 10);
// printf("%d",n);//-1
(5)、栈区
栈区的东西,有个特性,就是在栈内,数据是先进后出的。
在函数里定义的变量,基本上都是在栈区的,因此,一旦函数执行完毕,所有栈释放(也叫出栈),只是删除编号(地址),并没有清除数据,这时候,这些栈的使用权回归系统所有,系统可以再次分配,对里边的值重写。若是没有分配,那么在下一次遇到一个结构,类型一样的函数,里边的变量如果不给初值,那么这个变量就会使用上次出栈后遗留在栈中的数据。
#import <Foundation/Foundation.h>
int a(){
int num1 = 100;
return num1;
}
int b(){
int num2 ;
return num2;
}
int c(){
int num3 ;
return num3;
}
int main(int argc, const char * argv[])
{ // 2015-04-08 09:36:38 北京
// 栈区
// int n1 = 10;
// int n2 = 20;//
// 内存不具有清空的功能
//
// //每一个函数都有一个叫做 栈帧 的东西
// //由于a,b两个函数结构相同,使用内存情况相同,b其实使用的是a的内存,所以原来的值被使用。
// a();
// printf("%d\n",b());//打印100
// //在c函数使用之前,调用了printf,导致b残留的数据被覆盖,原来的值无法使用。
// printf("%d\n",c());//打印0
// int n3 = 30;
return 0;
}
一个错误的例子:
#import <Foundation/Foundation.h>
////错误
//char *backStr(){//
// //str在backStr的栈内,一旦函数执行完毕,所有的栈内释放(也叫出栈),这时候,你的str字符数组的内存使用权回归系统,系统可以再分配。这个时候,原来的数据会被覆盖,导致无法使用。
// char str[] = "iPhone";
// return str ;
//}
int main(int argc, const char * argv[])
{ // char *ps = backStr();
// printf("%s\n",ps);
return 0;
}
此时,应该想到,跨作用域改值的只有指针作为函数参数的时候,才能实现。所以重新定义函数:
#import <Foundation/Foundation.h>
void backString(char str[],int length){
char s[] = "iPhone";
if (strlen(s ) > length) {
strcpy(str , s );
}else{
return;//经常用来终止函数
}
}
int main(int argc, const char * argv[])
{
char st[20] = {0};
backString(st,20);
printf("%s",st );
return 0;
}
29、链表
#import <Foundation/Foundation.h>
//节点结构体
typedef struct Node{
int data;//数据
struct Node *next;//节点
}Node ;
//添加节点
void addNode(Node *n,int num){
//临时指针
Node *temp = n;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = malloc(sizeof(Node));
temp->next->data = num;
temp->next->next = NULL;
}
int main(int argc, const char * argv[])
{
// 2015-04-08 17:19:41 北京
Node *first = malloc(sizeof(Node));
first ->data = 0;
first ->next = NULL;
addNode(first,1);
addNode(first,2);
addNode(first,5);
return 0;
}