动态内存分配
存储区的划分:
在计算机的内存中,可以分成5个区,每个区都有着不一样的效果。按内存编号从小到大的顺序,分别是:
(1)、代码区:
计算机将我们写的代码通过二进制转换后,放进了这个代码区。
(2)、常量区:
在我们写代码时,所有的常量,都放在常量区,常量区的所有值都是可读不可写的。也就是说,常量区的所有值都是不能改变的。若强行对其赋值,则在运行的时候,直接导致程序崩溃。
常用的一个关键字:const。
const可以把一个变量声明成常量:constint 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会限制作用域,其他文件不能使用。 staticint n2 =10;
//静态区的变量,可以不做初始化,自动清0. //可以计数使用 int test(){ staticint count = 0; return ++ count ; }
int main(int argc,constchar * 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,constchar * 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,constchar * 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,constchar * argv[]) { char st[20] = {0}; backString(st,20); printf("%s",st ); return 0; } |
29、链表
#import <Foundation/Foundation.h> //节点结构体 typedefstruct Node{ int data;//数据 structNode *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,constchar * argv[]) {
Node *first =malloc(sizeof(Node)); first ->data = 0; first ->next =NULL;
addNode(first,1); addNode(first,2); addNode(first,5);
return 0; } |