C++Primer_学习笔记(八)存储持续性、作用域和链接性

2019年7月22日

C++Primer Plus (第六版)中文版

8.5.1  重载函数模板

 

需要多个对不同类型使用同一种算法的函数时候,可使用模板。然而并非所有的类型都使用相同的算法。为了满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,被重载的模板的函数特征标必须不同。代码如下,新的模板的特征标为(T[] ,T[],int)。注意,在后一个模板中,最后一个参数的类型具体类型(int),而不是泛型。并非所有的模板参数都必须是模板参数类型。

 

8.5.2

    编写的模板函数很可能无法处理某些类型。另一方面,有时候通用化是有意义的,但是c++语法不允许这样做。例如,这两个包含位置坐标的结构相加是有意义的,虽然没有为结构定义运算符+。一种这样使用运算符+的模板便可处理重载了运算符+的结构。另一解决方案是,为特定类型提供具体化的模板定义。

8.5.3

假设定义了如下结构:

struct job

{

    char name[40];

    double salary;

    int floor;

};

另外,假设希望能够交换两个这种结构的内容。原来的模板使用下面的代码完成交换:

temp = a;

a = b;

b = temp;

    由于C++允许将一个结构赋给另一个结构,因此即使T是一个job结构,上述代码也适用。然而,假设只想交换salary和floor成员,则需要使用不同的代码,但是Swap()的参数将保持不变(两个job)结构的引用,因此无法使用模板重载来提供其他代码。

    然而,可以提供一个具体化函数定义—称为显示具体化(explicit specializaton),其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。

 

2019年7月29日

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

c++11中采用了四种不同的方案来存储数据。

  1. 自动存储持续性:在函数中定义中声明的变量,当函数开始执行时候被创建,当函数代码块结束时候它们使用的内存被释放。
  2. 静态存储持续性:在函数定义外定义的变量和使用的关键字static定义的变量的存储性都为静态变量。
  3. 线程存储持续性:能够将计算放在可行并行处理的不同线程中。如果用thread_local声明的,则声明周期与所属的线程一样长。
  4. 动态持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时候被称为自由存储(free store)或堆(heap)。

9.2.1作用域和链接

    作用域(scope)描述了名称在文件(翻译单元)的多大范围内可见。

链接性(linkage)描述了名称如何在不同单元间共享。

    在函数原型作用域(function prototype scope)中使用的名称只在包含参数列表的括号内可用(这就是为什么这些名称是什么以及是否出现都不重要的原因)。不同c++函数的作用域方式是通过存储持续性、作用域和链接来描述的。

9.2.2 自动存储持续性

    在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。即为两个函数中如果有命名相同的变量,则不会互相干扰,则是创建了两个独立的变量—只有在定义它们的函数中才能使用它们。

    使用c++11中的auto:

    在c++11中关键字auto用于自动类型推断,但是在c++98时候用于显式地指出变量为自动存储。主要的用途是指出当前变量为局部自动变量。这种用法不再合法,因此赋予了其新的含义比引入新关键字是更好的选择。

  1. 自动变量的初始化:可以使用任何在声明时其值为已知的表达式来初始化自动变量。
  2. 自动变量和栈:程序必须在运行时候对自动变量进行管理,常用的方法就是留出一段内存,并将视为栈,以管理变量的增减。之所以被称为栈,是由于象征性地被放在原有数据的上面(也就是说在相邻的内存单元中,而不是在同一个内存单元中),当程序使用完后,将其从栈中删除。
  3. 寄存器变量

关键字register最初是由C语言引入的,它建议编译器使用CPU寄存器来存储自动变量,这旨在提高访问变量的速度。但是在c++11中,此关键字只是显式地指出变量是自动的。而且只能用于原本就是自动的变量,使用它的唯一原因是指出程序员想使用一个自动变量,这个变量的名称可能与外部变量相同。这与auto以前的用法完全相同。然而保留了关键字register的重要原因是,避免使用了该关键字的现有代码的非法。

9.2.3静态持续变量

    c++提供了三种链接性:外部链接、内部链接和无链接性。

    static的两种用法:用于局部声明,以指出变量是无链接性的静态变量时候,static表示的是存储持续性;而用于代码块外的声明时,static表示内部链接性,而变量已经是静态持续性了。

    静态变量的初始化:除默认的零初始化外,还可对静态变量进行常量表达式和动态初始化。

    c++11新增关键字constexpr,这增加了创建常量表达式的方式。constexpr指定的函数返回值和参数必须要保证是字面值,而且必须有且只有一行return代码,这给函数的设计者带来了更多的限制,比如通常只能通过return 三目运算符+递归来计算返回的字面值。

给编译器足够的信心在编译期去做被constexpr修饰的表达式的优化。

 

9.2.4静态持续性、外部链接性

      链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量是在函数外部定义的,因此对所有函数而言都是外部的。例如,可以在main()前面或头文件中定义后面的任何函数中使用它,因此外部变量也称全局变量(相对于局部变量的自动变量)。

单定义规则:一方面,在每个使用外部变量的文件中,都必须声明它;另一方面,c++有“单定义规则”(One Definition Rule,ODR,该规则指出,变量只能有一次定义。为了满足这种需求,c++提供了两种声明。一种是定义声明(defining declaration)或简称为定义(definition),它给变量分配存储空间;另一种是引用声明(referencing declaration)或简称为声明(declaration),它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,且不进行初始化;否则,声明为定义,导致分配存储空间。

如果要在多个文件中使用外部变量,只需要在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它。

注意:单定义规则并非意味着不能有多个变量的名称相同。例如,在不同函数中声明的同名自动变量是彼此独立的,它们都有自己的地址。另外,局部变量可能隐藏同名的全局变量。然而,虽然程序中可包含多个同名的变量,但每个变量都只有一个定义。

9.2.5 静态持续性、内部链接性

       static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。在多文件程序中内部链接性和外部链接性之间的差别很有意义。链接性为内部的变量只能在其所属的文件中使用;但常规外部变量都具有外部链接性,即可以在其他文件中使用。

注意:在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件必须使用关键字extren声明它。

 

9.2.6 静态链接性、无链接性

       无链接性的局部变量:将static限定符用于代码块中定义的变量。在代码块中使用static时候,将导致局部变量的存储持续性为静态的。这意味着虽然该变量只在该代码块可用,但它在该代码不处于活动状态时仍然存在。因此两次函数调用之间,静态局部变量的值将保持不变。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时候,将不会自动变量那样再次被初始化。

9.2.7说明符和限定符

       在同一个声明中不能使用多个说明符,但thread_local除外,它可与staticextern结合使用。c++11auto用作自动类型推断。关键字register用于声明中指示寄存器,而在c++11中,它只是显式地指出变量是自动的。关键字static被用在作用域为整个文件的声明中时,表示内部链接性;被用在局部声明中,表示局部变量的存储持续性为静态的。关键字extern表明引用声明,即表明引用在其他地方定义的变量。关键字thread_local指出变量的持续性与所属线程的持续性相同。thread_local变量之于线程,犹如常规静态变量之于整个程序。

       1.cv-限定符

       const表明,内存被初始化后,程序便不能再对它进行修改,c++中,const限定符对默认存储类型稍有影响。在默认情况下全局变量的链接性为外部的,但const全局的链接性为内部的。也就是说在c++看来全局const定义就像使用了static说明符一样。

       volatile,表明即使代码没有对内存单元进行修改,其值也可能发生变化。听起来似乎很神奇,实际上并非如此。例如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息。在这种情况下,硬件可能修改其中的内容。或者两个程序可能互相影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会变化。如果不降变量声明为volatile,则编译器将进行这种优化;将变量声明为volatile,相当于告诉编译器,不要进行这种优化。

  1. mutable

该关键字可以是结构(或类)变量为const,其中某个成员也可以被修改。例如:

struct data

{

        char name[30];

        mutable int accesses;

};



const data veep = {“Claybourne Clodde”,0};

strcpy(veep.name,”Joye Joux”);       //not allowed

veep.accesses++;                             //allowed

 

9.2.8函数和链接性

        在默认情况下,函数的链接性为外部的,既可以在文件间共享。实际上,可以在函数原型中使用关键字extern来指出函数是在另一个文件中被定义的,不过这是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是由链接程序搜索的库文件)。还可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用。必须同时在还原型和函数定义中使用该关键字:

static int private(double x);



static int private(double x)
{



}

        这样就意味着该函数只在这个文件中可见,还意味着可以在其他文件中定义同名的函数。和变量一样,在定义静态函数的

文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍将使用静态函数。

 

9.2.9 语言链接性

        另一种形式的链接性-称为语言链接性(language linking)也对函数有影响。链接程序要求每个不同的函数都有不同的符号名。在C语言中,一个名称只对应一个函数,因此这很容易实现。为了满足内部需求,C语言编译器可能将spiff这样的函数名翻译为_spiff。这种方法被称为C语言的链接性(C language linkage)。但是在C++中,同一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。因此,c++编译器执行名称矫正或名称修饰,为重载函数生成了不同的符号名称。这种方法被称为C++语言的链接性。

        链接程序寻找c++函数调用匹配的函数时候,使用的方法与C语言不同。但是如果要在C++程序中使用C库中预编译的函数,可以用函数原型来指出要使用的约定:

extern  “C” void spiff(int);     //use C protocol for name look-up

extern  void spiff(int);         //use C++ protocol for name look-up

extern  ”C++” void spaff(int);   //use C++ protocol for name look-up

第一个原型使用C语言的链接性;而后面的两个使用C++语言链接性。第二个原型是通过默认方式指出这一点,而第三个显式地指出了这一点。

 

9.2.10存储方案和动态分配

       动态内存由运算符newdelete控制,而不是由作用域和链接性规则控制。因此,可以在一个函数中分配动态内存,而在另一个函数中将其释放。与自动内存不同,动态内存不是LIFO,其分配和释放顺序要取决于newdelete在何时以何种方式被使用。通常,编译器使用三块独立的内存:一块用于静态变量,一块用于自动变量,一块用于动态存储。

  1. new运算符初始化
int *pi = new int (6);

double *pd = new double(99.99);
  1. new失败时

new可能请求不到内存量。在最初的10年里,c++在这种情况下返回空指针,但现在将引发异常std::bad_alloc

  1. new:运算符、函数和替换函数
void *operator new(std::size_t);

void *operator new[](std::size_t);

这些函数被称为分配函数(alloction function,它们位于全局名称空间中。同样有由deletedelete[]调用的释放函数(deallocation function:

void operator new(void *);

void operator new[](void *);

        c++将这些函数称为可替换的(replaceable)。这意味着如果您有足够的知识和意愿,可为newdelete提供替换函数,并根据需要对其进行定制。例如,可定义作用域为类的替换函数,并对其进行定制,以满足该类的内存分配需求。在代码中,仍将使用new运算符,但它将调用您定义的new()函数。

        4,定为new运算符

        通常,new负责在堆中(heap)找到一个足以满足要求的内存块,new运算符还有另一种变体,被称为定为(placementnew运算符,他让您能够指定要使用的位置。程序员可能使用这种特性来设置其他内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。

 

 

 

 

                                         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值