C++内存与命名空间

C++ Primer Plus读书笔记

内存与命名空间

1.头文件

  • 一种组织程序的策略,就是:一个文件(头文件)包含了用户定义类型的定义,另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成一个软件包,可以应用在各种程序中。

  • 不把函数定义放在头文件中,原因是:如果在头文件中包含了一个函数的定义,然后在其他两个文件中include这个文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则将会出错。

  • 下面是头文件中常包含的内容:

    函数原型
    使用#define或const定义的符号变量
    结构声明
    类声明
    模板声明
    内联函数
  • 头文件管理
//假设头文件名称为zmyyq.h
#ifndef ZMYYQ_H_
......
#endif

2.存储持续性

  • 存储持续性(用来确定数据在内存中留存的时间)

    • 1.自动存储持续性:在函数定义中声明的变量(包含函数参数)的存储持续性为自动的,他们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,他们使用的内存就被释放

    • 2.静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态,他们在整个程序运行过程中都存在。

      __static修饰的内容__
      1. 如果是局部变量,则在用static修饰局部变量后,该变量只在初次运行时进行初始化工作,且只进行一次。
      
          局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在__静态数据区__ ,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。
      2. 通常情况下对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。例如:
      file1.c
      int a=1;
      
      file2.c
      
      #include<stdio.h>
      
      extern int a;
      int main(void)
      {
          printf("%d\",a);
          return 0;
      }
      则执行结果为 1
      但是如果在file1.c中把int a=1改为static int a=1; 那么在file2.c是无法访问到变量a的。原因在于用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。
      
      3. 用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。
      extern
      在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
      
      注意extern声明的位置对其作用域也有关系,如果是在main函数中进行声明的,则只能在main函数中调用,在其它函数中不能调用。其实要调用其它文件中的函数和变量,只需把该文件用#include包含进来即可,为啥要用extern?因为用extern会加速程序的编译过程,这样能节省时间。
    • 3.线程存储持续性:使用关键字thread_local声明的变量,则其生命周期与所属的线程是一样的,这部分属于并行编程。

    • 4.动态存储持续性:用new运算符分配的内存将一直存在,知道使用delete运算符将其释放或程序结束为止,这种内存的存储持续性为动态的,有时候被称为自由存储或堆

  • 作用域和链接

    • 作用域描述了名称在文件的多大范围可见
      • C++函数的作用域可以是整个类或者整个名称空间,但不可能是局部的,也就是说代码块中不可以定义函数
    • 链接性描述了名称如何在不同单元间共享。
      • 1.外部链接性:可在其他文件中访问
      • 2.内部链接性:只能在当前文件中访问
      • 3.无链接性:只能在当前代码块中访问
  • 自动存储持续性:
    自动指的是在函数声明的函数参数和变量的存储性为自动,作用域为局部,没有链接性

    1. 自动变量的初始化:可以使用任何在声明时其值为已知的表达式来初始化自动变量。由于自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。

      • 解决自动变量管理的常用方法是留出一段内存,将其视为栈,管理变量的增减。之所以被称为栈是因为新数据被象征性地放在原有数据的上面,当程序使用完之后从栈中删除.
      • 跟踪栈的实现方法:程序中通常使用2个指针来跟踪栈,一个指向栈底(栈开始的位置),另一个指针指向堆顶——下一个可用的内存单元。当函数被调用时,该函数的自动变量将加入到栈中,栈顶指针指向变量后面的可用内存单元,当函数调用结束之后栈顶指针被重置为函数被调用之前的位置。
      • 栈空间的大小,在linux中是使用ulimit来指定的,编译时确定。在windows中,栈的大小是在链接是确定的。一般都是十几兆到几十兆之间不会太大。
  • 总结各个区

    动态存储区(堆):(动态分配)

    • malloc,new动态分配在heap堆区。
    • 动态存储区(堆),程序员自己分配自己释放。
    • 使用delete 指针释放完内存后,指针还是存在的,并且还是之前的位置,只是它指向的内容已经变了。

    动态存储区(栈):(动态分配)

    • 自动变量、const变量在stack栈区。
    • 动态存储区(栈),系统自动分配释放。

    静态存储区:(静态分配)

    • extern,全局变量,在static静态存储区。
    • 静态存储区,一旦分配,不会被回收,可读可写

    程序代码区:(静态分配)

    • main函数、其他函数在code程序代码区。
    • 程序代码区,一旦分配,可读不可写,不可改变
  • 说明符与限定符

    • 下面的属于存储说明符
      1. auto
      2. register 寄存器
      3. static 静态
      4. extern
      5. thread_local C++11新增的
      6. mutable
        • 这个说明符是const的一个延伸,在使用const对结构进行修饰时,该结构不可被改变,但是他的成员还是可以被修改的,用这个修饰结构中的成员,则该成员不可被修改

          struct data
          {
          char a[30];
          mutable int access;//该成员变量不可被修改
          }
    • cv限定符

      1. c表示const,它表明内存被初始化后,程序便不能再对它进行修改。它对于默认存储类型稍有影响。在默认情况下,全局变量的链接性为外部的,但const全局变量的链接性是内部的,也就是用了const就相当于加上了static。

      2. v表示volatile,该关键字表明:即使程序代码没有对内存单元进行修改,其值也可能会发生变化(大多是硬件改变某些取值,也有可能是两个程序共享一块内存)这个关键字的作用是为了改善编译器的优化能力,假设编译器发现程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个量两次,而是将这个值缓存到寄存器中,假设变量的值在这两次使用之间都不会变化,如果不设置这个volatile则告诉编译器不进行这种优化。

  • 函数的链接性:

    • 函数在存储的持续性上都自动为静态的,因为函数定义的时候不允许在一个函数中定义另一个函数。
    • 函数加上一个static表示只允许在当前代码文件中运行,一般来说,如果一个函数只在某个文件中执行,则要把他定义成static。
    • 使用extern来指出函数是在另一个文件中定义的。如果使用extern则该函数只能在一个地方定义。
  • 查找函数的方式

    • 假设在程序的某个文件中调用一个函数,则按照一下顺序进行查找
      1. 如果该文件中的函数原型指出该函数是static,则编译器只在该文件中查找函数定义,否则将在所有文件中查找。
      2. 如果找到两个定义,编译器将会发出错误消息,每个外部函数只能有一个定义。如果在文件中找不到,则会到库中查找。这就意味着如果定义了一个与库函数同名的函数,编译器将使用自己写的函数,如果没有才去找库函数。

2.名称空间

  • 名称空间可以是全局的,也可以位于另一个名称空间中,但是不能位于代码块中,因此他的链接性为外部的
  • using声明和using编译指令

    • using声明使得特定的标识符可用;(局部性)
    • using编译指令使得整个名称空间可用。(所有)
    • 对比:

      1. 使用using编译指令导入一个名称空间中的所有名称与使用多个using声明是不一样的。如果某个名称已经在函数中被声明,则不能使用using声明导入相同的名称,
      2. 使用using编译指令时,如果导入了一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样,不过仍可以用作用域解析运算符来调用。
      3. 一般来说,使用using声明比使用using编译指令要安全,它只导入指定的名称,如果与局部的名称发生冲突,则编译器会发出指示。
        
        #include<iostream>
        
        using namespace std;
        
        namespace ZhuMengYanYiQuan
        {
            double fetch = 3.14;
        }
        
        double fetch = 5.16;
        
        int main()
        {
            double fetch = 4.15;
            using namespace ZhuMengYanYiQuan;
            cout << fetch << endl;    //4.15
            cout << ::fetch << endl;  //5.16
            cout << ZhuMengYanYiQuan::fetch << endl; //3.14
            return 0;
        }
  • 名称空间的其他特性
    • 可以将名称空间声明进行嵌套,调用的时候就按着嵌套的顺序使用::进行解析即可。

      namespace ZhuMengYanYiQuan
      {
      namespace QuanQuanQuan
      {
      float ChunJie = 5.5;
      }
      double fetch = 3.14;
      }
    • 未命名的名称空间:表示在该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域末尾,与全局变量类似。因为它没有名字,所以不能让他在其他的地方使用。但是它提供了链接性为内部的静态变量的替代品。

      namespace
      {
      int ice;
      int hot;
      }
  • 名称空间及其用途:下面是一些指导性的准则

    • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。
    • 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量。
    • 如果开发了一个函数库或类库,将其放在一个名称空间中。
    • 仅将编译指令作为一种将旧代码转换为使用名称空间的权宜之计。
    • 在头文件中不要使用using编译指令,首先这样会掩盖要让那些名称可用哪些不可用,另外,包含头文件的顺序可能会影响程序的行为。如果非要加using编译指令,则应将其放在所有预处理编译指令#include之后。
    • 导入名称时,首选使用作用域解析运算或者using声明的方法。

    • 对于using声明,首选将其作用域设置为局部而不是全局。

3.总结

  • 一种代码的组织策略:

    • 使用头文件来定义用户类型、函数原型;

    • 并将函数定义放在一个独立的源代码中;

    • 头文件和源代码文件一起用来定义和实现用户定义的类型和方法;
    • 调用这些函数的函数放在第三个文件之中。
    • 也就是一个.h和.cpp用来定义和实现功能,对他们的调用放在另一个.cpp中
  • C++的存储方案

    • 静态变量存在于整个程序执行期间,对于在函数外面定义的变量,其所属的文件中位于该变量定义之后的所有函数都可以使用,并且可以在程序的其他文件中使用(外部链接性),另一个文件要使用它,必须使用extern关键字来声明。
    • 对于文件之间共享的变量,应在一个文件包含其定义声明,也可进行初始化。在其他文件中包含声明(使用extern且不初始化)
    • 使用static修饰的在函数之外声明的变量,只能在当前的文件中使用,不能在其他文件中使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章