第九章 内存模型和名称空间

单独编译

对于一个多文件程序,编译器首先合入引用的文件,然后编译每个源文件,生成目标文件,最后将目标文件链接到一起生成可执行文件。

头文件中常包含的内容:

        1 函数原型

        2 使用#define或者const定义的符号常量

        3 结构声明

        4 类声明

        5 模板声明

        6 内联函数

此处可以这样理解,头文件问包含的代码,只是用来告诉编译器,编译后续代码需要使用到的东西,例如函数原型,类、结构声明,模板声明。这些代码并不是在编译后真正生成的代码。内联函数和#define或者const定义的符号常量例外。

程序编译的步骤:

1 预处理器将包含的文件与源代码文件合并。

2 编译器创建每个源代码文件的目标代码文件。

3 连接程序将目标代码文件、库代码和启动代码合并,并生成可执行文件。

存储持续性、链接性、作用域

存储类别: 自动存储持续性、静态存储持续性、线程存储持续性、动态存储持续性。

作用域:描述变量在文件的多大范围内是可见的。

链接性:描述变量在不同文件间共享。

自动存储持续性:

在函数内(大括号内)申请的参数和变量为自动,其作用域为局部,函数内。没有链接性。

{
  int a = 1;
  {
    int a = 2;
  }
}
此种情况下,运用内部代码块时,同名a会屏蔽外部a,程序使用的是内部a = 2。
退出内部代码块时,外部a = 1变为可见

auto 关键字在c语言中,只是标记此变量是一个局部自动变量,在C++11 后,才是建立一个auto 类型的变量。

自动变量和栈:

编译器为函数创建一个LIFO栈,有一个栈顶指针和栈底指针,栈底指针指向栈开始位置;栈顶指针指向下一个可用内存单元,当执行函数时,创建的自动变量被加入到栈顶指针位置,栈顶指针移动到变量的后一个可用单元。函数调用结束后,栈顶指针被重置到调用前位置。

下图摘自原书:

寄存器变量:

c中寄存器变量 register int conut; 表示建议编译器使用CPU寄存器存储该变量。

C++中取消此用法,只是显式指出是自动变量。

静态持续变量:

静态存储变量包括全局变量、static 标记的外部变量和static标记的内部变量

提供了三种链接型:1 外部链接性(可在其他文件访问)2 内部链接性(只能文件内访问)3 无连接性(局部变量)

int a = 10;  // 全局变量,具有静态存储性,生存周期为整个程序,在静态存储区保存。外部文件可 
             // 以访问
static int b = 10;// static 全局变量,外部文件不能访问。
void text_fun()
{
   static int c = 10; //无链接性,具有静态存储性,生存周期为整个程序,在静态存储区保存
}

存储描述 持续性作用域链接性如何声明
自动(局部变量)自动代码块在代码块中
寄存器自动代码块在代码块中,使用关键字register
静态,无链接性静态代码块在代码块中,使用关键字static
静态,外部链接性静态文件内部不在任何函数中
静态,内部链接性静态文件内部不在任何函数内,使用关键字static

静态持续性与外部链接性:

单定义规则:C++ 具有单定义规则,变量只能定义一次。

两种声明规则:

        定义声明(定义):就是定义一个变量,会分配存储空间。

        引用声明(  引用):不分配空间,只是引用已有的。

引用声明使用关键字extern: extern int a; 声明int型a是一个外部变量,再其他文件。

且此声明不能被初始化,extern int a = 1;就不是引用声明了,会分配个新的存储空间。

用法: 在一个文件定义变量,其他文件使用关键字extern 声明。

局部变量会隐藏同名全局变量,此时局部变量用extern修饰时,表示的是全局变量

int a = 10;
void text_fun()
{
    int a = 2;
    cout << a;  //结果为2 ,因为a此时是一个局部自动变量,会屏蔽全局a,
}

void text_fun2()
{
    extern int a = 2;
    cout << a;  //结果为10 ,因为a代表全局变量a。
}

静态持续性与内部链接性:

如果两个文件都有一个同名变量int a = 1;是不合法的,违背了单定义规则;

如果变量被static修饰,static int a = 1;说明变量是一个链接性为内部的全局变量,将屏蔽其他文件定义的变量a。

总结:

        1 如果需要一个变量被其他文件引用,定义为普通全局变量int a = 10;其他文件中引用此变量 extern int a;(注意不能初始化);

       2 如果定义变量不需要其他文件引用,定义为static int a = 10;这样其他文件无法引用,不用考虑是否冲突。

       3 函数中的临时自动变量用extern 修饰,表示显式使用外部全部变量,不会把全局变量覆盖。

说明符和限定符:

说明符:

        auto: 自动类型推断。

        register:C中说明建议使用寄存器存储,C++中,显式说明为自动变量

        static: 说明变量为静态的,存储再静态存储区,如果是全局变量,链接性为文件内部,外部不可见。

        extern:声明变量是外部的。(注意extern 声明的变量C++中不能被初始化)

        thread_local: 指出变量的持续性与其所属的线程的持续性相同。

        mutable:

限定符:

        const:说明变量为常量。在C++中,const会改变变量的链接性,链接性将是内部的,外部变量将引用不到(和static一样效果)。C++这样做的目的是可以将const声明的常量放到头文件中,不同的文件都可以引用,因为链接性为内部,所以不违背单定义规则。注意,每个文件中定义的const常量都占用不同的内存空间。

另外可以使用extern关键字覆盖默认的内部链接性 extern const int a = 0;其他文件可已通过extern const int a;引用。

        volatile:用于告诉编译器,一个变量的值可能会在意料之外的情况下发生改变,因此在编译器优化过程中,不应该对这个变量进行某些特定的优化。   表明即使代码没有改变内存单元的值,其值也可能改变(外部系统,如串口)。针对此种变量,编译器不会主动进行优化。例如编译器不会将值放在临时寄存器中,因为此值每次使用可能不同。

       mutable:用于修饰类的非静态成员变量。当一个成员变量被声明为mutable时,意味着即使在一个const成员函数中,该成员变量的值仍然可以被修改。通常情况下,const成员函数表示该函数不会修改对象的状态,即它不会修改任何非静态成员变量。但是,有时候我们可能需要在const成员函数中修改某些特定的成员变量,这时就可以使用mutable来标记这些成员变量。

函数和链接性:

普通函数的链接性

         void text_fun();  链接性是外部的:

外部文件可以通过引用声明引用此函数 extern void  text_fun(); 注意 extern 可以省略。

外部文件想引用此函数,每个文件都需要声明,之后才可以使用。

函数内部链接性:

        如果适用static 修饰函数:  static void text_fun();  则此函数将只具有内部链接性,外部文件不能引用,将会屏蔽外部同名函数。具有单定义规则,其他文件可以定义相同名字函数。

        内联函数不受单定义规则影响,所以可以放在头文件中,引用到不同的源文件。

编译器查找函数的规则:如果是静态函数,将在本文件中查找,如果是非静态函数,将在整个函数中查找,如果找到两个同名的非静态函数,则报错,如果都没有将在库文件中查找。

语言链接性:

如果再C++中使用C语言,需要显式声明。

extern "C" void spiff(int);

存储方案和动态分配:

基础类型可以用小括号或者大括号初始化。   
 cout << "start_new_fun_text" << endl;
    int* ia = new int(6); // 用小括号初始化
    delete ia;
    cout << "start_new_fun_text  ia:" << *ia <<  endl;
    int* ib = new int{7}; 用大括号初始化
    delete ib;
    cout << "start_new_fun_text  ib:" << *ib <<  endl;

数组或者结构必须使用大括号初始化
    int* ip = new int[3] {1, 2, 4};
    cout << "start_new_fun_text  *ip:" << *ip <<  endl;
    cout << "start_new_fun_text  *(ip+1):" << *(ip+1) <<  endl;
    cout << "start_new_fun_text  *(ip+2):" << *(ip+2) <<  endl;
    delete ip;

定位new运算符:

如果希望new运算符申请指定内存块中的内存,可以使用定位new运算符。

    int arr[100] = {0};
    int* p = new(arr)int(8); //从arr中分配内存
    cout << "start_new_fun_text  arr:" << arr <<  endl;
    cout << "start_new_fun_text  p:" << p <<  endl;
    cout << "start_new_fun_text  *p:" << *p <<  endl;
    cout << "start_new_fun_text  arr[0]:" << arr[0] <<  endl;

运行结果:
start_new_fun_text  arr:0x62fc30
start_new_fun_text  p:0x62fc30
start_new_fun_text  *p:8
start_new_fun_text  arr[0]:8

名称空间

C++ 使用namespace声明名称空间,可以理解成将代码块起了一个名字。

名称空间不能位于代码块中,所以链接性是外部的。(代码块中外部就引用不到了)

namespace apple {
    int color;
    int weight;
    void apple_eat() {
      cout << "fun apple eat "; 
    }
}

namespace orange {
    int color;
    int weight;
}

void namespace_text_1() {
  apple::color = 1;     //使用作用域解析符::来引用名称空间的名称,
  orange::weight = 2;
}
void namespace_text_2() {
  using namespace apple;  using 编译引用名称空间中的全部定义
  color = 1;
  using apple::weight;  //unsing 声明引用单个定义
  weight =2 ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值