每周100个C++知识点(备忘录)(三)

001-019 函数模板

1.函数模板是通用的函数描述,是一种使用泛型来定义的函数。定义的方式为:①(C++98以后)template ;②(C++98以前)template

2.在参数类型不同但所需函数的运算功能相同时,使用函数模板会很好。它使生成多个函数定义更简单、更可靠。同时,大多将模板放在头文件中(头文件是后续内容)

3.模板重载:对不同类型的实现方式不同,可以定义重载的模板。模板定义中的参数并非必须是泛型,也可以是具体的类型

4.使用模板的局限性:有些类型可能无法处理,需要使用:①重载运算符(第11章);②为特定类型提供具体化的模板定义,也就是具体化

5.显式具体化:用途:将一个结构赋给另一个结构,可以使用泛型模板,但是假设仅对结构中的某些内容进行操作,则此时使用重载是不可行的,解决办法是显式具体化,即提供一个具体化函数定义

6.规则:①对于给定函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本;②显式具体化的原型和定义应该以template<>开头,并通过名称来指出类型,定义方法为:1>template <> return_Type func_Name(type &,type &),或2>template <> return_Type func_Name(type &,type &) ;③具体化优先于常规模板,非模板函数优先于具体化和常规模板

7.最初,编译器进行隐式实例化,来使用模板生成函数定义(和普通函数一样),现在还允许显式实例化,也就是为编译器指定所需类型。区分☆★☆显式实例化与显式具体化★☆★的区别,语法是:显式实例化:template returen_Type Name(type,type)。显式具体化:template <> return_Type Name(type,type)或return_Type Name(type,type)

8.显式实例化的意思是:使用函数模板生成某类型的函数定义;显式具体化的意思是:不适用函数模板来生成函数定义,而使用专门为某类型显式地定义的函数定义。

9.在同一文件或转换单元中使用同一种类型的显式实例和显式具体化将出错

10.隐式实例化、显式实例化和显式具体化统称为具体化,它们都是使用具体类型的函数定义,而不是通用描述。注意使用template和template <>区分实例化和具体化。还可以在程序中使用函数来创建显式具体化,语法是Name(parameters)

11.编译器选择哪个函数版本?选择过程称为重载解析。步骤:①创建候选函数列表,包含于被调用函数的名称相同的函数和模板函数;②使用候选函数列表创建可行函数列表,即参数数目正确的函数,这里有一个隐式转换序列,以进行适合的类型转换;③确定是否有最佳的可行函数,如果有则调用,否则调用出错

12.编译器选择函数版本时的匹配顺序(最优至最差):①完全匹配,但常规函数优先于模板;②提升转换,如char转换为Int,float转换为double;③标准转换,如int转换为char,long转换为double;④用户定义的转换,如类声明中定义的转换

13.如果有多个完全匹配则是错误(报错内容可能包括ambiguous二义性),但仍有特殊情况,假设一个参数为char,另一个为const char &,则这两个均是完全匹配。因此有一些“无关紧要的转换”,包括const、volatile、*<–>[]、&的转换

14.对于上述二义性,当且仅当参数是引用和指针时,非const优先于const,非模板函数优先于模板函数,这样的时候不会出现二义性错误;如果两个完全匹配都是模板函数,则较具体的模板函数优先,也就是显式具体化优先于使用模板隐式生成的具体化

15.要找出“最具体”的模板,意味着需要进行转换的参数较少时,函数“更具体”。找出最具体的模板的规则被称为函数模板的部分排序规则

16.自定义选择执行函数,可以在使用函数时用:name<>(params)这种形式,编译器会选择模板函数来进行调用;name(params)也可以,这是显式实例化的函数

17.对于不能确定类型的过程中变量,C++提供了如下方法:decltype(expression) var;;①也就是expression的类型赋给var,②如果expression是函数,则var是函数返回值的类型,③如果expression是左值,且用括号括起,则var为指向其类型的引用

18.如果需要多次声明,则可以typedef decltype(expression) varname;

19.对于不知道返回类型的函数,可以使用语法:auto name(params) -> type;,结合着decltype标识符,可以写为:template<class T1,class T2> auto gt(T1 x,T2 y)->decltype(x+y) {… return x+y;}

020-028 单独编译

20.UNIX和Linux系统提供了make程序,用以跟踪程序以来的文件以及这些文件的最后修改时间;VS提供了Project中的类似工具:解决方案资源管理器

21.C++程序可以分为以下结构:①头文件,包含结构声明和使用这些结构的函数的原型;②源代码文件:包含与结构有关的函数的代码;③源代码文件:包含调用与结构相关的函数的代码

22.不要把函数定义或变量声明放在头文件中★★★★★因为如果在头文件中包含一个函数定义,则同一程序中将包含同一个函数的两个定义,除非函数是内联的,否则出错。头文件中一般包含:函数原型、使用#define或const定义的符号常量、结构声明、类声明、模板声明、内联函数。原理:①结构声明不创建变量,只是告诉编译器如何创建变量;模板也不是将被编译的代码,而是指示编译器如何生成与源代码中的函数调用相匹配的函数定义;const的数据和内联函数有特殊的链接属性

23.使用<>括起的是标准头文件,将在存储标准头文件的主机系统文件中查找;而使用“”括起的是用户定义的头文件,编译器首先查找当前工作目录或源代码目录(取决于编译器),如果没找到,再到标准位置查找

24.★★★★★★不要将头文件加入到项目列表中(#include管理头文件),也不要再源代码文件中使用#include来包含其他源代码文件(导致多重声明)

25.在一个文件(程序)中只能将同一个头文件包含一次,但一般都会包含,使用#ifndef xxx与#define xxx和#endif这组语句可以检查头文件。这种方法不是防止编译器将头文件包含两次,而是忽略除了第一次包含外的所有内容

26.stdafx.h在C++中起到的作用是头文件预编译,即把C++工程中使用的头文件预先编译,以后该工程编译时,直接使用预编译结果,增加速度。任何代码都应当放在#include "stdafx.h"之后

27.单独编译的文件的专业术语是翻译单元

28.多个库链接时,由于编译器的不同,可能产生文件的名称修饰不同,导致链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配,因此要在一个编译器下运行一次

029-044 存储持续性、作用域和链接性

29.C++的变量持续性(数据保留在内存中的时间):①自动存储持续性,函数定义中声明的变量(参数),在执行完函数或代码块儿时内存释放;②静态存储持续性,函数定义外定义的变量和使用static关键字定义的变量,在整个程序运行过程中都存在;③线程存储持续性,使用thread_local关键字声明的变量,声明周期与线程一样长;④动态存储持续性,使用new运算符分配的内存将一直存在,直到使用delete将其释放,有时被称为自由存储或堆

30.作用域:描述名称在文件的多大范围内可见;链接性:描述名称如何在不同单元间共享,为外部的名称可在文件间共享,为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性

31.作用域:①局部作用域,只在定义它的代码块中可用(由花括号括起的叫代码块);②全局作用域,定义位置到文件结尾都可用;③自动变量作用域是局部,静态变量作用域取决于它如何被定义,函数原型的作用域中使用的名称只在包含参数列表的括号内可用,类中成员的作用域是类内,名称空间中声明的变量的作用域是整个名称空间

32.默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性

33.对于自动变量,只有在定义它们的函数、代码块中才可以使用。如果在函数外定义了一个自动变量,在函数内又定义一个不同名的自动变量,则在函数内可以访问两个变量,但在函数外不可访问函数内定义的变量;如果函数内定义了一个同名变量,则函数内访问该变量,将只访问函数内定义的,而函数外的同名变量在函数内部失效(隐藏定义)

34.auto关键字原来用于显式地指出变量是自动存储局部变量,而在C++11标准后,auto关键字用来自动类型推断

35.自动变量的初始化:可以使用任何在声明时值为已知的表达式来初始化自动变量

36.自动变量存储在栈中,新数据放在原有数据的上面(内存单元相邻);程序使用两个指针跟踪栈,一个在栈底(初始位置),另一个在堆顶(可用内存单元),当函数被调用,自动变量加入栈中,栈顶指针指向变量后面的下一个可用的内存单元,函数结束,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存

37.栈是LIFO先进后出的,这样可用简化参数传递

38.寄存器变量(C++11已经不再用):使用关键字register声明,现在这个关键字只是显式地声明变量是自动变量,这个变量的名称可能与外部变量相同(和auto以前的用法完全一样)

39.静态持续变量:有三种链接性:①外部链接性(可在其他文件中访问);②内部链接性(只能在当前文件中访问);③无链接性(只能在当前函数或代码块中访问)。静态持续变量在整个程序运行期间都存在,生命周期比自动变量更长

40.编译器分配固定的内存块给静态变量;如果静态变量没有显式地初始化,则编译器把它们都设置为0,默认情况下,静态数组和结构的每个元素或成员都设置为0

41.创建:①外部链接性的静态持续变量,在代码块外声明它;②内部链接性的静态持续变量,在代码块的外面声明它,且使用static限定符;③没有链接性的静态连续变量,在代码块内声明它,且使用static限定符

42.没有链接性的静态连续变量表现和自动变量一样,区别是前者在函数没有执行时,也留在内存中

43.static修饰符的两种用法:①用于局部声明,指出变量是无链接性的静态变量时,static表示的是持续存储性(变量不放入栈中,而是在固定内存位置);②用于代码块外的声明时,static表示内部链接性,此时变量已经是静态持续了

44.静态变量的初始化:零初始化和常量初始化(默认为0,常量可以是单值、表达式和sizeof()运算符),被统称为静态初始化,着意味着在编译器处理文件时初始化变量;动态初始化意味着变量将在编译后进行初始化

045-050 静态持续性、外部链接性

45.链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件;外部变量时在函数外部定义的,对所有函数而言都是外部的。也称为全局变量

46.单定义规则(ODR):变量只能有一次定义。为此,C++提供两种变量声明,一种是定义声明(定义),另一种是引用声明(声明),后者不给变量分配存储空间,使用关键字extern进行声明,且不进行初始化,否则,声明为定义,导致分配存储空间(可能造成错误)

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

48.在程序中出现与外部变量同名的变量,将被认为是自动变量,只有在声明该变量的函数内才有意义(局部变量隐藏全局变量)

49.在全局变量被隐藏的函数中,访问全局变量,使用::运算符

50.对于全局变量,一般把程序运行过程中完全不变、经常访问的声明为全局变量,最好使用const修饰符。但是使用全局变量让程序变得不可靠,通常应当使用局部变量

051-053 静态持续性、内部链接性

51.在把static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。这样做的好处是,可以使用static限定符声明与外部变量同名的静态变量,该静态变量只作用于该文件

52.在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件使用extern声明它

53.可以使用外部变量在多文件程序中共享数据;使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(名称空间提供了另一种共享数据的方法);将作用域为整个文件的变量变为静态的,就不用担心其名称于其他文件中的作用域为整个文件的变量发生冲突

054-054 静态持续性、无链接性

54.静态局部变量的意义:在两次调用该函数之间,静态局部变量的值将保持不变(适用于银行密码之类的“可再生”词条)。且首次初始化时初始化静态局部变量,之后不再初始化变量

055-063 说明符和限定符

55.有关存储的信息,还通过以下被称为存储说明符或cv-限定符的关键字提供:const、volatile、auto、register、static、extern、thread_local、mutable

56.除thread_local外(可与static或extern结合),其他说明符不可以重叠使用

57.volatile关键字告诉程序不进行以下优化:在运行过程中,程序的某几条语句中多次使用了某个变量的值,则编译器可能不查找这个值两次,而是将这个值缓存到寄存器中

58.mutable关键字告诉程序,即使结构或类的变量为const,其某个成员也可以被修改

59.在默认情况下全局变量的链接性是外部的,但const全局变量的链接性为内部的,即在C++看来,全局const定义就像使用了static说明符一样

60.如果将商量声明在头文件中,并且初始化,则预处理器头文件的内容将包含到每个源文件中,每个源文件都有使用const说明声明的定义

61.使用const意味着内部链接,内部链接意味着,每个文件都有自己的一组常量,也就是每个定义都是其所属文件私有的,这样只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量

62.如果要定义某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性,语法是extern const type name = xxx;(由ODR,只有一个地方定义,其他地方声明)

63.代码块内部的常量只在代码块内部可用,不必担心变量名与全局常量一样

064-068 函数和链接性

64.C++不允许在一个函数内定义另一个函数,也就是说函数的存储持续性都自动为静态的,也就是在整个程序执行期间都一直存在。默认情况下,函数链接性为外部的,可用在文件间共享。可用在原型中使用extern说明函数是在另一个文件中定义的,但这是可选的;使用static将函数的链接性设置为内部,使它只能在一个文件中使用,且在原型和定义中都需要输入static关键字

65.使用static修饰符,就是定义静态函数,静态函数将覆盖外部定义,即使在外部定义了同名的函数,该文件也将使用静态函数

66.非内联函数都适用ODR,对于每个非内联函数,只能有一个定义。链接性为外部的函数来说,在多个文件程序中,只能有一个文件(可能是库文件)包含该函数的定义,但使用该函数的每个文件都应当包含其函数原型

67.内联函数不受ODR约束,因此内联函数可用被放在头文件中,C++要求同一个函数的所有内联定义都必须相同

68.编译器会首先检查static函数,否则编译器将在所有的程序文件中查找,如果查找到两个定义,则报错,如果没找到,则在库中搜索,着意味着如果定义了一个与库函数相同名称的函数,编译器将使用自定的版本,而不是库函数

069-069 语言链接性

69.如果要在C++中使用C语言库中预编译的函数,则需要:extern “C” returnType name(params);现实可提供其他语言的链接性,使用""标注。内部机制是编译器的”名称修饰或名称矫正“

070-080 存储方案和动态分配

70.通常,编译器使用三块独立内存,一块用于静态变量,一块用于自动变量,另一块用于动态存储。C++使用new和delete来控制动态存储,C使用malloc()控制自动存储

71.动态存储中的存储方案:可用跟踪动态内存的自动和静态指针变量。也就是说,由new分配的内存,将一直存在,但是声明的变量在语句块结束后就会消失,因此必须将其地址传递或返回给函数。但如果指针的链接性声明为外部的,则文件中位于该声明后面的所有函数都可以使用它。在其他文件中,通过extern修饰符也可以访问它

72.new运算符的初始化(C++11标准):使用大括号进行列表初始化,也可以将列表初始化用于单值变量

73.new找不到请求的内存时,引发异常std::bad_alloc(第15章讲)

74.运算符new和new []分别调用:void * operator new(std::size_t);和void * operator new [] (std::size_t);两个函数,这些函数称为分配函数,位于全局名称空间中,delete和delete []调用释放函数。它们使用了运算符重载,std::size_t是一个typedef,对应于合适的整型

75.根据74,int * pi = new int;将被转换为int * pi = new(sizeof(int));,int * pa = new int[40];将被转换为int * pa = new(40*sizeof(int));。同时,C++允许用户自己定义new()函数。delete相关函数类似

76.定位new运算符,一般new负责在堆中找到一块内存,使用定位new运算符,可以指定要使用的位置。必须包含头文件,使用方法:先声明开辟一块地址,再使用new运算符创建结构/变量,最后使用new (buffer) var/structure,来完成定位。结果是,将var/structure放在buffer中

77.定位new运算符占用的内存块,如果其原来不是使用常规new声明的,也不能使用delete删除

78.定位new运算符的另一种用法:与初始化结合使用,从而将信息放在特定的硬件地址

79.定位new运算符的机制:返回传递给它的地址,并将该地址强制转化为void *,让它可以赋给任何类型的指针。可以重载new运算符;且new运算符可以用于类对象

80.定位new运算符的函数:函数原型是一个包含两个参数的函数,new(sizeof(type),buffer);,其中第一个总是std::size_t,这样的重载函数都被称为定义new

081-100 名称空间

81.名称空间的意义:更好地管理有相同名称的函数、类、变量、枚举、结构、类的成员等信息,更好控制名称的作用域

82.声明区域:声明区域是可以在其中进行声明的区域,函数外声明全局变量,声明区域为声明所在的文件;函数中声明的变量,声明区域为其声明所在的代码块

83.潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾。潜在作用域比声明区域小,是因为变量必须定义后才能使用

84.变量并非在其潜在作用域内都是可见的(局部变量对全局变量的隐藏),变量对程序而言可见的范围称为作用域

85.C++自身关于全局变量和局部变量的规则定义了一种名称空间层次,每个声明区域内可以声明名称,这些名称独立于在其他声明区域中声明的名称

86.创建名称空间,使用关键字namespace,语法是namespace Name{definitions of other vars};名称空间可以是全局的,也可以位于另一个名称空间中,但是不能位于代码块中。意味着在名称空间中声明的名称的链接性是外部的(除非引用了常量)

87.全局名称空间:文件级声明区域,全局变量被描述为位于全局名称空间中

88.要增加或更改或定义已有名称空间中的变量,则可以再次使用namespace运算符,并且将新的内容使用大括号括起,添加在已有名称空间内

89.访问给定名称空间中的值:使用域解析运算符::,并且需要使用名称空间来限定该名称。未被修饰的名称称为未限定名称,包含名称空间的名称称为限定名称

90.使用名称空间中的值的两种方法:using声明和using编译。使用using声明,方法为using Name::var,可以在函数内部和外部使用该方法,前者将名称添加到局部生命区域,后者将名称添加到全局名称空间中;使用using编译使得空间中所有变量均可用,using namespace Name;,编译器不允许同时进行两个名称空间的using编译(导致二义性,Name1::apple和Name2::apple可以区分,但如果都using编译,则两个apple不可以区分)

91.在局部名称空间中,声明的同名变量将隐藏名称空间中的变量,但是可以使用域解析运算符来使用名称空间中定义的变量,也可以使用域解析运算符来使用定义的全局变量,二者区别是::前有无名称空间

92.如果名称空间名和声明区域定义变量同名,则会报错:如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本;在函数内声明的名称空间看作是在函数外声明的,但是作用域是函数

93.一般来说,使用using声明比使用using编译指令更安全。可以使用一个嵌套式名称空间来创建一个包含常用using声明的名称空间;在名称空间中也可以使用using指令使内部的名称可用,此时访问嵌套变量时,使用两个名称空间::变量名的方法均可用

94.如果存在名称空间嵌套,则导入存在传递性,也就是导入一个包含其他名称空间的名称空间,则相当于导入了两个名称空间

95.可以使用namespace xxx=一个长的名称空间名字的方法来简化(别名),这个方法也可以用于简化嵌套名称空间:namespace xxx=A::B::C

96.未命名的名称空间,潜在作用域是从声明点到该声明区域末尾,它们和全局变量相似,但是这种名称空间没有名称,不可以使用using显式声明或编译,因此这种名称空间只可以用于该文件中,是链接性为内部的静态变量的替代品

  1. 也就是说,通常情况下,使用命名空间可以这样:首先创建一个包含命名空间的头文件(以及其他结构、变量等),再在一个源文件中实现命名空间中的函数等内容,最后在其他源文件中调用命名空间

98.如果一个函数有多个重载,则在使用了using进行声明后(编译也是),将导入该函数的所有重载版本,声明时只用说明函数名称即可

99.一些指导性原则:①使用在已命名的名称空间中声明的变量,而不是外部全局变量、静态全局变量;②如果开发了一个函数库或类库,则将其放在名称空间中(例如C++将cmath中定义的数学类库放在std名称空间下);③不要在头文件中使用using编译指令,这样掩盖了让哪些名称可用,而且包含头文件的顺序会影响程序的行为,因此包含using编译,将其放在所有预处理编译命令#include之后;④导入名称时,首选使用作用域解析运算符或using声明方法;⑤对于using声明,首选将其作用域设置为局部而不是全局

100.老式头文件没有使用名称空间,如iostream.h,但新头文件iostream则使用了std名称空间

下期预告:对象和类入门,对象和类真的,真的,太太太太太太多了!!!QwQ,TAT。苦恼,不知道学Qt还是MFC

另外,看到这里就要多尝试做做项目咯!对象和类学完会有个大的飞跃!加油鸭~

发布了8 篇原创文章 · 获赞 2 · 访问量 637

猜你喜欢

转载自blog.csdn.net/qq_41450779/article/details/104056540