IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【c语言】复习无止境,day4--堆内存&宏函数篇 -> 正文阅读

[C++知识库]【c语言】复习无止境,day4--堆内存&宏函数篇

 进程的内存分布:
 		   text 代码段:存储的是二进制指令、常量(字符串字面值),该内存段的权限为只读,一旦强行修改就会产生段错误。
           data 全局数据段:里面存储着初始化过的全局变量、静态变量。
           bss 静态数据段:里面存储着末初 始化过的全局变量、静态变量,该内存段在程序执行前会被初始化为0。
           heap 堆:由于程序员手动管理,该内存无法与标识符建立映射关系(无法取名字),必须与指针配合使用。
               优点:够大,分配和释放可控。
               缺点:使用麻烦,容易产生内存泄漏、内存碎片。
           stack 栈:由系统自动管理,随着函数被调用,会自动分配内存,函数执行结束后自动释放内存。
               优点:使用方便,采用栈结构方式管理安全,不会产生内存泄漏、内存碎片。
               缺点:大小有限,一次使用过多可能会产生段错误,分配和释放不可控不适合长期保存数据。  **变量的分类:**
           变量的属性:存储位置 生命周期 作用域
           全局变量:
               data 初始化过的,bss 未初始化的 
               整个程序的运行时期
               任何位置都可以使用,其它源文件中需要声明
           静态全局变量
               data 初始化过的,bss 未初始化的 
               整个程序的运行时期
               在目标文件内可以使用
           局部变量
               stack
               函数开始执行到结束
               函数内
           静态局部变量
               data 初始化过的,bss 未初始化的
               整个程序的运行时期
               函数内  

 什么是堆内存:
              是进程中的一个内存段(text\data\bss\heap\stack),是由程序员手动管理
              特点:足够大,缺点:使用麻烦
               为什么要使用堆内存:
           1、随着程序的复杂而数据变多
           2、其他的内存段申请和释放不受控制,堆内存的申请释放受控制
       
       如何使用堆内存: 注意:C语言中没有控制堆内存的语句,只能使用C标准库中的函数
       #include <stdlib.h>
    	   void *malloc(size_t size);     
          功能:从堆内存中申请size个字节的内存
          返回值:成功时返回申请到的内存的首地址,失败返回NULL
          注意:通过malloc申请的堆内存中存储的是什么内容不确定
      
         void free(void *ptr);
          功能:释放一块堆内存
          ptr:要释放的堆内存的首地址
          注意:可以释放NULL,但是不能重复释放和非法地址
          注意:释放仅仅是使用权,而且数据并不会全部清理为0
      
         void *calloc(size_t nmemb, size_t size);   
          功能:从堆内存中申请nmemb块size个字节的内存,申请到的依然是连续的内存
          注意:通过calloc申请到的内存会被初始化为0
      
         void *realloc(void *ptr, size_t size);
          功能:改变已有内存块的大小,size表示调整后的大小,在原有基础上调大或者调小
          返回值:是调整后的内存块的首地址,必须要重新接收返回值,因为可能不是在原来的位置的调整的
              如果不能在原来的位置上进行:
                  1、申请一块新的符合大小要求的内存块
                  2、把原内存块中的数据拷贝过去
   				   3、把原内存块释放掉,返回新内存块的首地址  *
   malloc的内存管理机制:
       当首次向malloc申请内存时,malloc会向操作系统申请内存,操作系统会直接分配33页(1页=4096字节)内存交给malloc来管理
       但是并不意味着可以越界访问,因为malloc还会把使用权分配给其他人,这样就会产生脏数据
   
     
   每个内存块之间会有一些空隙(4~12字节),这些空隙一些是为了内存对齐,其中一定有4个字节是用来记录malloc的维护信息,这些维护信息决定了下次分配内存的位置,如果这些维护信息被破坏时,会影响接下来的malloc和free函数的调用
   
      堆内存越界的后果?
   
   使用堆内存要注意的问题:
   内存泄漏:
       内存无法再使用,也无法释放,而再次使用时只能重新申请,然后又重复以上过程,日积月累会导致系统中可用的内存越来越少
       注意:程序一旦结束,属于它的资源全部都会被操作系统回收
       如何尽量避免内存泄漏:
           谁申请的谁释放,谁知道该释放谁释放
       如何判断定位内存泄漏:(上网搜一下)
           1、查看内存的使用情况 
           2、分析代码,借助代码分析工具来检查malloc free的使用情况
           3、封装啊malloc、free函数,记录调用的信息到日志中
   内存碎片:
       已经释放了但是无法再继续使用的内存叫做内存碎片,是由于申请和释放的时间不协调导致的,无法避免只能尽量减少
           如何减少内存碎片:
               1、尽量使用栈内存
               2、不要频繁地申请和释放内存
               3、尽量申请大块内存自己管理
   
   内存清理函数:
   #include <strings.h>
   
      void bzero(void *s, size_t n); 功能:把一块内存,清理为0 s:内存块的首地址
   n:内存块的字节数
   
      #include <string.h>
   
      void *memset(void *s, int c, size_t n); 功能:把内存块按字节设置为c
   s:内存块的首地址 c:想要设置的ASCII码  0~255 n:内存块的字节数 返回值:成功后返回内存的首地址
   
   堆内存定义二维数组:
   指针数组:定义n*m二维数组
       类型* 数组名[n];
       for(int i=0; i<n; i++)
       {
           arr[i] = malloc(m*sizeof(类型));
       }
       注意:每行的m的值可以不同,形成不规则的二维数组 数组指针:
       类型 (*指针名)[n] = malloc(sizeof(类型)*n*m);
           申请了m行n列的二维数组
       注意:所有的多维数组都是用一维数组模拟的
   
   
   字符:
   在计算机中字符是以整数形式存储的,当需要显示时会根据ASCII码中对应关系显示吹相应的符号或图案 '\0'    0 '0'     48 'A'     65 'a'     97 printf("%c",ch);
   串:
   是一种数据结构,由一组连续的若干个相同类型的数据组成,末尾有一个结束标志 对于这种数据结构的处理都是批量性的,从开头位置到结束标志为止。str
   
   字符串:
    由字符组成的串型结构,结束标志是'\0'
   
   字符串的输入:
    scanf %s 地址 注意:不能输入空格 不负责输入字符串的长度,直到遇到enter键,会段错误
   
      char *gets(char *s); 功能:输入字符串,可以接受空格
   返回值:实现链式调用(把一个函数的返回值,作为另一个函数的参数) 不负责输入字符串的长度,直到遇到enter键,会段错误
   
      char *fgets(char *s, int size, FILE *stream);
   功能:可以设置输入的字符串长度最多为size-1个字符,超出部分不接收,会在末尾给'\0'预留位置。
   剩余数据会残留在缓冲区影响后续数据输入 注意:如果没超出范围,则会接收输入的最后一个'\n'
   
   
   **字符串的输出:** 
   printf %s 地址
   
      int puts(const char *s); 功能:输出一个字符串,在末尾自动添加一个\n 返回值:成功输出的字符个数
   
   **字符串的存在形式:** 
   字符数组:char str[10]
       由char类型组成的数组,要为'\0'预留位置
       使用的是栈内存,数据是可以修改的
       缺点:给\0预留位置,初始化麻烦
   
      **字符串字面值:**
       "由双引号包含的若干个字符",末尾会自动添加\0
       字符串字面值是以地址形式存在的,该数据存储在代码段,如果修改就会产生段错误
       const char* p = "strstr";
       sizeof("strstr"); 结果是字符个数+1
       注意:末尾隐藏了一个\0
       注意:多个一模一样的字符串字面值在代码段中只会存在一份
   
   
      平时的使用方法:   
      字符串数组 = 字符串字面值
       char str[20] = "hello world";
       赋值完成后字符串会存在两份,一份存在代码段,另一份存储在栈内存(可修改的)
   
      练习1:实现一个函数,判断字符串是否是回文串 
   
   
   **字符串的常用操作:**
        #include <string.h>
      
         size_t strlen(const char *s);
          功能:计算字符串的长度,结果不包括'\0' { if(NULL==s) return 0; size_t i=0; while(s[i++]!='\0'); return i-1; { assert(NULL != str); const   
   char* tmp=str; }
      
         char *strcpy(char *dest, const char *src);
          功能:把src拷贝给dest,相当于给dest赋值 =
          返回值:dest(链式调用) { char* tmp=dest; int i=0; while(src[i++]!='\0') { dest[i]=src[i]; } dest[i]='\0'; return p;
   }
      
         char *strcat(char *dest, const char *src);
          功能:把src追加到dest的末尾,相当于+=
          返回值:dest(链式调用) { assert(NULL!=dest&&NULL!=src) int =0; while(src[i++]!='\0') while(dest[i++]=src[i++]) return dest; }
      
         int strcmp(const char *s1, const char *s2);
          功能:比较两个字符串,根据字典序,谁在前面谁小,一旦计算出结果立即返回,后面不再比较
          返回值:
              s1 > s2     正数
              s1 == s2      0
              s1 < s2     负数 {int i=0;	 while(s1[i++]==s2[i++]&& *s1); 	if(s1[i]>s2[i]) 	return 1; 	else if(s1[i]<s2[i]) 	return  -1;
      	return 0; }    
      
         char *strncpy(char *dest, const char *src, size_t n);
          功能:只把src前n个字符拷贝给dest,相当于给dest赋值
          
          char *strncat(char *dest, const char *src, size_t n);
          功能:只追加n个字符
          
          int strncmp(const char *s1, const char *s2, size_t n);
          功能:只比较前n个字符
      
         常见的笔试面试题:
              自己实现strlen、strcpy、strcat、strcmp四个函数
      
         char *strstr(const char *haystack, const char *needle);
          功能:在haystck中查找是否存在needle子串
          返回值:在haystck中第一次出现needle的位置,如果找不到返回NULL
      
         int sprintf(char *str, const char *format, ...);
          功能:把各大类型的数据输出到str中
          返回值:字符串str的长度
      
         int sscanf(const char *str, const char *format, ...);
          功能:从str中读取数据到变量中
          返回值:成功读取到的变量的个数
   
   **输出缓冲区:** 程序要输出的数据并不能立即显示到屏幕上,而是先存储在输出缓冲区中,当满足一定条件时才能够显示显示出来。
       1、遇到\n后
       2、遇到输入语句后
       3、当缓冲区满4k时
       4、当程序结束时
       5、fflush(stdout) 手动刷新 注意:缓冲区机制可以提高数据的速写速度
   
   **输入缓冲区:** 程序并不会立即获取屏幕上输入的数据,而是按下回车键后程序才从输入缓冲区中读取数据
   
     
   1、当读取整型、浮点型数据,而缓冲区中是字符型数据时,此时会读取失败,并且数据依然会残留在缓冲区中,会影响接下来的所有的数据读取。
   
      2、fgets可以指定读取size个字节,如果有多余的数据会残留在输入缓冲区中
   
      3、当先输入整型、浮点型数据,紧接着输入字符数据时
       解决方法:scanf(" %c",&ch)  
   
      把输入缓冲区的当前位置指针移动到了缓冲区的末尾,相当于清空输入缓冲区,只能在GNU编译环境下使用
       stdin->_IO_read_ptr = stdin->_IO_read_end;
   
   
   	     		预处理指令:
               程序员所编写的代码并不能被真正的编译器编译,需要一段程序翻译一下
               翻译的过程叫做预处理,被翻译的代码叫做预处理指令,以#开头的都是预处理指令
           
               查看预处理的结果:
                   gcc -E code.c   把预处理的结果显示到屏幕上
                   gcc -E code.c -o code.i 把预处理的结果存储到code.i文件中
                预处理指令的分类:
               #include 文件包含
                   #include <> 从系统指定的路径查找并导入头文件
                   #include "" 先从当前路径查找,如果找不到再从系统指定路径查找并导入头文件
                       操作系统是通过设置环境变量来指定头文件的系统查找路径,还可以通过编译参数 -I /path
                       环境变量的设置通过 系统配置文件设置 : ~/.bashrc
               
               #define 定义宏
                   宏常量:
                       #define MAX 100
                           优点:提高代码的可扩展性(方便批量修改)、提高安全性(常量)、提高代码可读性、可以用在case后面
                           注意:一般宏名全部大写,末尾不要加分号。
                           【函数名:小写+下划线、全局变量:首字母大写、局部变量:全部小写、指针变量:+p、数组:arr、字符串str】
                       预定义好的宏:
                           __func__   获取函数名
                           __FILE__   获取文件名
                           __LINE__   获取当前行号
                           __DATE__   获取当前日期
                           __TIME__   获取当前时间
           
                   宏函数:其实就是带参数的宏
                       不是真正的函数,不检查参数类型、没有传参、没有返回值,只有计算的结果
                       #define sum(a,b) a+b
                       1、把代码使用的宏函数替换为 宏函数后面的代码
                       2、把宏函数代码中使用的参数替换为调用者提供的参数
                       注意:宏函数、宏常量不能换行,但是可以使用续行符 \ 也可以使用大括号保护代码
                   宏的二义性:
                       由于宏代码所处的位置、参数不同可能导致宏有不同的功能,这种叫做宏的二义性
                       如何避免:宏函数整体加小括号、每个参数都加小括号
                       注意:使用宏函数时不要提供带自变运算符的变量作为参数
                       注意:容易出选择题,例如:问哪个宏可能有二义性,问宏函数的结果
                   
                   常见的笔试面试题:
                       如果是普通类型,他们从功能上没有任意区别
                   #define INTP int*
                       INTP p1,p2,p3;  //只有p1是指针,p2p3都是int类型变量
           
                   typedef int* INTP;
                       INTP p1,p2,p3;  //p1p2p3都是指针变量
           
                   宏函数与普通函数的区别?
                       它们是什么
                           宏函数:不是真正的函数,只是代码替换,用起来像函数而已
                           函数: 是一段具有某项功能代码的集合,编译成二进制指令存储在
                           代码段,有独立的命名空间、栈内存
                       
                       有什么不一样:
                           函数:  返回值    类型检查  安全 入栈、出栈 速度慢  跳转
                           宏函数:运算结果  通用      危险 替换       速度快 冗余
                       优缺点:
                   
               条件编译:
                   根据条件决定了哪些代码是否参与最终的编译
           
                   版本控制: [#if 0 注释代码]   
                       #if
                       #elif
                       #else
                       #endif
                   
                   头文件卫士:    防止头文件被重复包含
                       #ifndef 宏名
                       #define 宏名
                       #endif//宏名
                   
                   判断、调试:    -D宏名  在编译时添加宏
                       #ifdef 宏名
                       #else
                       #endif
                   
                   代码调试:
                   #ifdef DEBUG
                       #define debug(...) printf(__VA_ARGS__)
                   #else
                       #define debug(...)
                   #endif
           
                   #define error(...) printf("%s:%s:%d %s %m %s %s\n",
                   __FILE__,__func__,__LINE__,__VA_ARGS__,__DATE__,__TIME__)
           
                   作业:
                       实现一个交换两个变量的宏函数,能写多少种?哪种最通用
                       swap(a,b)
           
                   常考的笔试面试题:
                       定义一个宏 表示100年有多少秒,忽略闰年问题
                       #define YEAR_SEC (3600*24*365*100u) 
           
                       在类型重定义时 #define 与 typedef的区别?
           
                       宏函数与普通函数的区别?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-11 16:27:42  更:2021-07-11 16:30:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/27 22:41:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码