堆内存管理&字符串
2019.5.25
堆内存管理
什么是堆内存
程序在内存是分段:
代码段+只读段:二进制指令,常量全局段:初始化过和的全局变量,静态变量静态数据段:没有初始化过和的迁居变量,静态变量栈:局部变量,块变量(采用栈的方式进行管理)堆:数据无序的存储到这一块内存中,受物理内存的限制
为什么使用堆内存
1. 栈内存的大小有限
2. 栈内存中的数据释放不受程序员控制(函数结束后,属于它的内存就会被系统自动释放,它不适合长期存放数据)
如何使用堆内存
堆内存无法与标识符建立对应关系(必须使用指针来指向堆内存)C语言中没有管理堆内存的语句,但标准库提供一套函数来管理堆内存堆内存的管理和释放由程序员手动操作(显示调用函数来管理)
void *malloc(size_t size);
功能:从栈内存申请size个字节size:要申请的字节数返回值:所申请到的内存首字节地址需要使用指针来接收 注意:使用malloc所申请的堆内存的内容默认情况下不确定(但首次使用时是0),如果size的值为0,返回值是NULL
练习1:计算100-1000所有的素数,存储在堆内存中
void bzero(void *s,size_t n);
功能:所一块内存的所有字节,设置为0(清理内存)s:要清理的内存首地址n:清理的字节数
void* 是一种无类型的指针
不能直接使用,不能通过这种指针变量解引用(必须转换为其他类型)可以与任意类型的指针进行自动转换 void* = 任意类型 任意类型 = void*void* 也叫万能指针,解决函数之间传递指针参数时类型不确定问题
void *menset(void *s, int c, size_t n);
功能:把内存的每一个字节都设置为cs:内存首地址c:字节中要设置的数据,-128~127n:要设置的字节数返回值:初始化的内存首地址,也就是s的值(链式调用)
void free(void *ptr);
功能:释放内存,只是把内存的使用权收回,内容还在。ptr:malloc的返回值,之前所申请的内存的首地址。 注意: 内存释放后,还能继续访问(不产生段,可能会造成脏数据),但是在非法访问。 内存在释放后,指针要立即置NULL,否则指针就会变成野指针。 一块内存不能释放两次,否则会出现堆崩溃。
void *calloc(size_t nmemb, size_t size);
功能:从堆内存申请nmemb*size个字节(内存已初始化为0)size:每次申请的字节数nmemb:申请的次数返回值:所申请的内存的首地址(size或nmemb只要有一个0,返回值为NULL) 注意:calloc的速度会比malloc慢,因此绝大多数情况下只使用malloc
void *realloc(void *ptr, size_t size);
功能:调整已申请到的内存的大小。ptr:内存的首地址,malloc或calloc的返回值,如果ptr为NULL而size大于0,相当于申请内存。size:把内存调整为size个字节,可以调大,也可以调小,如果ptr的值合法,而size的值为0,则相当于释放内存。返回值:调整后的内存的首地址,一定要** 重新接收 ** 。
练习2:修改练习1,不浪费内存。
使用内存时要注意的问题
当程序结束后,所有属于它的所有资源都会被操作系统回收
内存泄漏:
由于失误而忘记或无法释放堆内存,导致堆内存无法循环利用,从而每次重新申请内存使可用内存越来越少。
保护指针不被修改
类型* const p = malloc(size);
malloc和free成对出现
谁申请谁释放
定位内存泄漏
当程序运行后观察内存的使用情况,内存暴涨检查循环中的申请内存 cat /proc/meminfo linux下查看内存使用情况检查每个malloc的free(在free前查是否已经变成空指针)检查条件,业务逻辑free会被对用到
内存碎片:
已经释放但不能被再次分配使用的内存
频繁的申请,释放内存,导致申请和释放的不协调,一部分内存无法再次被使用。内存碎片无法杜绝,只能尽量减少
如何减少内存碎片
尽量减少使用堆内存,栈内存可以解决的尽量使用栈内存尽量申请大块的内存不要频繁的申请和释放
字符:
字符就是符号,图案,在计算机中以整数形式存储,当需要显示的时候根据ASCII表中的对应关系,显示相应的符号和图案
CHARASCII
'0'48'a'97'A'65'\0'0
字符输入:
scanf("%c",&ch); ch = getchar();
字符输出:
printf("%c",ch); putchar(ch);
注意:先输入整数,浮点代数据,会影响后续字符数据的输入(把回车当成字符获取)
串:
是一种数据结构,由若干个相同类型的元素组成,有一个明确的结束标志。
字符串
由字符组成的串行结构,它的结束标志是’\0’ 注意: 所有对字符串的操作都以’\0’作为结束标志
字符串的存在形式
字符数组:char arr[] = {'a','b','c','d'}; 注意:要为'\0'预留位置 字符串字面值:"双引号包括的若干个字符,末尾隐藏着\0" 以地址形式存在 注意:存储在只读段,不能被修改否则会产生段错误
一般常用字符串字符值来初始化字符数组,编译器自动把字符串字面值拷贝到字符数组中,包括'\0'之后这个字符串就有了两份,一份在栈中,一份在只读段。
字符串的输出
printf %s 指针 puts(指针) 会自动在末尾增加一个'\n'
字符串的输入 '\0'会自动添加
scanf %s 指针//不接收空格 gets(指针);//可以接收空格 fgets(指针,size,stdin);//只能接收到size-1个字符,为'\0'预留 当输入的字符不足size-1个是,会连\n一起接收 stdin->_IO_read_end = stdin->_IO_read_ptr//清缓冲区
练习1:实现一个计算字符串长度的函数,不包括’\0’ 练习2:实现一个字符串输入函数,可以指定字符的数量,但不接收到’\n’
字符串的操作
strlen 计算字符串长度 strcpy(str,"haha") 拷贝字符串,相当于赋值(只有初始化才能用=赋值) strcat(str,"haha") 连接两个字符串 strcmp() 按字典序比较字符串大小
conditionreturn
str1 == str20str1 > str21str1 < str2-1
atoi/atol/atoll("123a") 字符串转整数 strstr 在str1中查找str2,返回str2首次出现的位置,没有NULL
作业1:实现itoa函数 作业2:实现一个函数,用于判断字符串是不是回文串 作业3:实现一个字符串逆序函数 作业4:把字符串中的单词首字母大写,空格替换为“% %” 作业5:把一段文字的单词逆序 作业6:计算字符串2在字符串1中出现的次数 作业7:实现两个大数相加