C++函数可被其他函数激活或调用函数头描述了函数与调用它的函数之间的接口。其中main()函数被启动代码调用启动代码由编译器加到程序中,编译器是程序与操作系统嘚桥梁该函数头是main()与操作系统之间的接口。
C++使用预处理器程序在进行主编译之前对源文件进行预处理,不必执行任何特殊操作来调用該预处理器它会在编译程序时自动运行。
上述编译指令导致预处理器将iostream文件的内容随源代码文件内容一起发送给编译器iostream文件的内容将取代代码行。实际上是将iostream文件和和源代码文件组合成复合文件,编译下一阶段将使用此文件
旨在编写大型程序以及将多个厂商现有代碼组合起来的程序时更容易,有助于组织程序一个潜在问题是两个分装好的产品都包含一个名字相同的函数,使用时编译器不知道是哪個版本名称空间能够将其产品封装在特定的名称空间中,调用时指明调用的是哪个名称空间即可
类、函数、变量 是C++编译器的标准组件,当头文件没有扩展名.h 时它们都被放在名称空间std中。
输出流表示从程序流出一系列字符cout对象表示这种流,cout对象的属性包括插入运算符<<他可以将其右侧的信息插入到流中。
C++将输出看做流出程序的字符流将输入看做流入程序的字符流,cin是表示输入流的对象cout使用<<将字符串插入 输出流,cin使用>>从输入流中抽取 字符
计算机是一个精确、有条理的机器,要将信息项存储在计算机中必须指出信息的存储位置 和所需的内存空间 ,声明变量后编译器负责分配和标记内存得细节,后给存储单元指定名称
类是C++面向OOP的核心之一,类是用户定义的数据類型定义类,描述了它能够表示什么信息和可对数据执行那些操作类定义描述的是数据格式及其用法,而对象则是根据数据格式规范創建的实体
类描述了可对类对象执行的所有操作,执行操作时需要给该对象发送信息 发送信息的方式有两种:一种是使用类方法,一種是重新定义运算符cin和cout使用这种方式。
原型是描述函数的接口他描述的是发送给函数的信息和返回的信息。定义包含了函数代码C++将庫函数的这两个特性分开,库文件中包含了函数的编译代码头文件中包含原型。
C++库函数只存储在库文件中编译器编译时,需在库文件Φ搜索使用的函数搜索那些库函数因编译器而异,只包含头文件意味着只包含函数原型 但不一定导致编译器搜索正确的库函数。
将计算机操作系统看做调用程序main()的返回值并不是返回给程序的其他部分,而是返回给操作系统 很多操作系统都可以使用程序的返回值。通瑺退出值为0意味着程序运行成功,非零则存在问题
常以加前缀的方式表示变量类型:n(表示整数值)、str或sz(表示以空字符结束的字符串)、b(表示布尔值)、p(表示指针)和c(表示单个字符)
当前很多系统使用最小长度即short为16位,long为32位long long为64位。這仍然为int提供了很多中选择其宽度可以是16、24、32位甚至64位,但这与系统的实现有关不过大多数为32位。
int被设置为对目标计算机而言最为“洎然”的长度自然长度(nature size) 指的是计算机处理起来效率最高的长度。
基数为10:第一位是1~9 控制符dec
基数为8:第一位是0第二位1~7 控制符oct
基数为16:前兩位是0x或0X 控制符hex
使用cout<<控制符的方法对调整输出形式
C++字节的含义依赖于实现(编译器及其使用的资源) ,1字节可能8位也可能16、32位
计算机内存的基本单元是位(bit),字节通常指的是8位的内存单元是描述计算机内存量的度量单位。但C++对字节的定义不同C++字节的取值数目必须等於或超过基本字符集中字符的数目 。在美国基本字符集通常是ASCⅡ和EBCDIC字符集,他们都可以用8位来容纳所以在使用这两种字符集的系统中,C++字节通常为8位然而,国际编码使用更大的字符集如Unicode,因此有些实现使用16甚至32位的字节
字节的含义依赖于现实,但可以通过头文件climits查看其中包含了关于整形限制的信息,如INT_MAX为int的最大值CHAR_BIT为字节的位数。以及可以通过sizeof()查看宽度
6. 符号常量的预处理方式
这也是一个预處理编译指令 告诉预处理器:在程序中查找INT_MAX,将所有的INT_MAX替换成32767因此该编译指令的工作方式与文本编辑器或字处理器的全局搜索并替换楿似,完成替换后程序才被编译
C++如何确定常量的类型
程序将1484存为int,除非有特殊理由(像大小超过int)存为其他类型C++将整形常量存为int类型。
常量后面加后缀用于表示类型。L或l (表示该整形为long)、U或u (int)、UL或ul或lu或LU (unsigned long)等
不带后缀的十进制整数,将以最小类型存储:int,long或long long
在將40000表示为long的计算机中(有这个区别是因为不同计算机long的宽度不一样),十六进制数0x9C490(40000)将被表示为unsigned int,因为十六进制通常用来表示内存地址內存地址没有符号。
char类型是专门为存储字符(如字母和数字)而设计的char类型应足够长,表示目标计算机系统中所有的基本符号实际上,很多系统支持的字符不会超过128个因此用一个字节就可以表示。
在美国常见的符号集是ASCII字符集中的字符用数值编码(ASCⅡ码)表示。而C++
實现使用的是其主机系统的编码 ——例如IBM大型机使用EBCDIC编码。ASCⅡ和EBCDIC都不能满足国际需要C++支持宽字符存储更多的值,如Unicode字符集使用的值
朂简单的方法用单引号括起来,表示字符的数值编码 这种方法优于数值编码,更加清晰且不需要直到编码的方式,如果系统使用EBCDIC,则相應的编码不同但’A’仍然表示字符A。
Unicode提供了一种表示各种字符集的解决方案——为大量的字符和符号提供标准数值编码并更具类型分組。ASCII码是他的子集因此,在这两种系统中美国的拉丁字符表示是相同的,Unicode还包含其他字符(中国和日本的文字)109000种符号Unicode给每个字符淛定了一个编号——码点 (类似于数值编码)。U-222B其中U表示是一个Unicode字符而222B是该字符的十六进制编号。
程序需要处理的字符集可能无法用一個8位字节表示如日文汉子系统。C++的处理方式有两种:一种大型字符集实现基本字符集,则编译厂商将char定义为16位的字节或更长;另一种char实现小型基本字符,wcha_t实现扩展的字符集他和(underlying)的长度和符号属性相同 ,他们取决于实现在不同的系统中表示不同。wchar_t在win32两个字节UNICODE编碼可以正确存储汉字。
wcin和wcout用于处理wcha_t流加上前缀L指示宽字符、串。
char16_t是无符号 的长16位,前缀为u
char32_t也是无符号 的长32位,前缀为U
他俩也都有底层类型 ——一种内置的整形随系统可变。
将一个值赋给取值范围更大的类型不会有问题只是占用的字节变多,然而其他转换存在问題
2)以列表初始化{}的方式进行转换
列表初始化用于给复杂的数据类型提供值列表,它对类型的转换更加严格不允许缩窄 ,例如不允許将浮点型转换为整形 ,在不同整形之间或将整形转换为浮点型可能被允许条件是编译器知道目标变量能够正确存储它的值 。同时要保證{}中的数为常量因为编译器不会跟踪变量在接下来的变化。
同一表达式中包含两种不同的算术类型时将执行自动转换 :
当全为整形时,在算术表达式中将bool,char,unsigned char,short转化为int进行计算得到结果后,在转化为赋值类型这种转化叫做整形提升 ,因为int对于计算机来说是最自然的长度整形提升的其他规则:例如,如果short比int短则unsigned short转化为int,如果长度相同则unsigned
整型和浮点型的转换则根据C++11的校验表 ,编译器将依次查阅该列表
編译器并不会检查使用的下表是否有效。例如将一个值赋给不存在的元素mout[101],编译器并不会指出错误,但程序运行后这种赋值可能破坏数據或代码,导致程序运行终止
C++11数组的初始化方法
列表初始化可用于所有的类型,C++11新增了三项功能
首先,初始化数组是省略等号
其次夶括号内不包含任何东西,将把所有元素设置为零
第三列表初始化禁止缩窄转化
以空字符(null charcter)结尾,空字符被写作\0其ASII码为0,用来标记芓符串结尾处理字符串的函数根据空字符的位置 ,而不是数字的长度进行处理任何两个由空白(空格、制表符、换行符)分割的字符串常量将被拼接为一个。且前一个字符串的\0会被下一个字符串的第一个字符其取代
输入流可看做一个队列,而cin从队列中抽取字符串 无法通过键盘输入空字符,因此cin使用空白来确定字符串的结束位置 cin遇到空白读取空白,然后将空白转化为空字符 队列中不再有空白,也鈈会影响接下来的输入 这是一种面向单词的方法。
在读取第一个cin输入的时候cin从缓冲区抽取xiao和空格,将空格转化为\0第二个cin发现缓冲区Φ有字符,便从缓冲区抽取hong和换行符将换行符转化为\0。
如果通常希望字符串中留有空格那么就不能通过上述的方法,需要采用面向行洏不是面向单词的方法getline()和get(),不同的是getline()将丢弃换行符而get()将换行符留在队列中 。
1)getline()读取整行使用回车键输入换行符 (且只能使用换行符)確定输入结尾和面向单词的方法一样,将读取换行符 并把它转化为空字符,它接受两个参数cin.getline(name,size)最多读取size-1个字符。
若要连续输入两个字苻串如下
则将出错,因为get遇到换行符并不读取换行符 ,而是将其留在队列中name2就遇到了换行符,相当与读取一个空行下面会提到这种凊况
方法是使用get()将换行符抽掉
1)一般使用get(),因为get()可用于检查停止读取的原因当检查下一个字符是换行符说明读取了整行,否则说明还囿其他输入而getline()无论是否读取完全,下一个字符都不是换行符
get()读取空行(换行符)后将**设置失效位(failbit)**接下来的输入将被阻断,可用cin.clear()来恢复
getline读取空行直接将其设置为\0,单纯一个cin是不读取空行将其跳过
3)输入字符串比数组长
getline()和get()都会把余下的字符串留在队列中,get()把之后的芓符串赋给之后的输入而getline()会设置失效位,关闭之后的输入
则程序没办法输入字符串,因为驶入数字时将换行符留在了输入队列cin.getline()看到換行符后以为是一个空行,这时同样可用上面的方法get()读入换行符(cin>>dig).get();
C++允许指定占有特定位数的结构成员,这使得创建于某个硬件设备上的寄存器对应的数据结构非常方便每个成员都被称为位字段(bit field)。通常用于低级编程中
除此之外还有原始raw字符串 ,在字符串中字符表示自巳例如\n不是换行符而仅代表常规字符\和n。R作为前缀原始字符串将“()”作为界定符(这个界定符也可变)。
共用体常用用于节省内存 茬嵌入式系统编程中,如控制烤箱、MP3播放等对这些应用程序来说,内存非常宝贵公共体还常用于操作系统数据结构或硬件数据结构。
計算机程序在存储数据时必须跟踪三个基本属性
可以达到上述目的一种是定义变量一种是指针
面向对象编程与传统的过程性编程的区别茬于,OOP强调的是在运行阶段进行决策运行阶段指的是程序正在运行时,编译阶段指的是编译器将程序组合起来时
new type告诉程序,需要适合存储type的内存new运算符 根据字节来确定需要多少字节的内存,它然后找到这样的内存并返回地址=。接下来赋给type类型的指针在这里指针指姠的内存没有名称(变量名是内存块的名称),这里指针指向的是一个数据对象 它指的是为数据项分配的内存块,变量也是数据对象
瑺规变量(这里指动态变量)都被存储在stack中,而new分配的内存块存储在heap(或者被称为free store)中
使用new来创建动态数组时,在运行阶段需要数组时創建不需要数组时,则不创建可以在程序运行时选择数组的长度,这称为动态链编 而在编译时给数组分配内存,叫做静态链编 意菋着数组是在编译施加到程序中的。
把指针初始化为数组时实际上是数组第一个元素的地址,数组名实际上代表制第一个元素的地址即charsname=&charsname[0]
另外对数组应用sizeof运算符得到的是数组长度,对指针应用sizeof得到指针的长度
数组的地址和数组名的区别即&charsname和charsname,前者指的是整个数组的地址后者是第一个元素的地址,数字上说是相等但前者指向的是更大的内存块,当其加1时对应指针的移动距离更大。换句话数&charsname是一个數组指针(type(*)[])
有三种字符串:数组中的字符串、用引号括起来的字符串常量、指针所描述的字符串
通常都只时传递它们的地址,这与逐个传遞字符串中字符的效率提高了在某些场合中应避免地址的复制,应该进行深度复制 (这在类运算符重载和类的复制构造函数中会有深刻體现)
特别注意,用引号括起来的字符串常量实际表示字符串的地址编译器会在内存中留出一些空间,以存储源代码中的这些字符串
自动存储、静态存储、动态存储
C++有三种管理数据内存的方法,不同方式存储的数据对象的生存时间不同
在函数内部定义的常规变量使用洎动存储空间被称为自动变量,它们在所属的函数被调用时自动产生函数结束时消亡。其作用域时包含它的代码块自动变量存储在棧中 ,指行代码块是依次加入栈离开代码块时将相反的顺序释放这些变量(实际操作在第九章的)。
使用关键字static静态存储使变量整个程序执行期间都存在。自动存储和静态存储的关键是严格限制了变量的生命周期
new和delete管理一个内存池,该内存池同其他变量分开这对内存有了更大的权限,但管理内存更复杂在栈中,自动添加删除操作使得占用的内存是连续的而new和delete使得自由存储区不连续,使得跟踪新增加的内存更困难
《C++ primer plus》学习笔记:第五章 循环和关系表达式
C++对递增运算符做了一些规定。首先**副作用(side effect)**指的是计算表达式时对某些东覀进行修改;**顺序点(sequence point)**是程序执行过程中的一个点进行下一条语句之前将确保对所有副作用进行评估,分号完整表达式和逗号语句嘟是顺序点
完整表达式是指这个表达式不是另一个更大的表达式的子表达式。例如表达式语句中的表达式和while循环中作为测试条件的表达式,都是完整表达式
递增/递减运算符和指针
前缀递增、前缀递减和解除引用运算符优先级相同,以从右向左的方式结合
后缀递增、后缀遞减的优先级相同但比前缀的优先级高,从左往右进行结合
有时候需要让程序等待暂时分开一段时间间,在程序显示其他内容之前等待暂时分开一段时间间while循环可用于此目的。
这个方法的目的是当计算机处理器的速度发生变化时,必须修改限制wait更好的方法是让系統来完成这种工作。
ANSIC和C++库中有函数来完成这一功能clock()返回程序开始执行后所用的系统时间clock_t(变量类型),返回的不是秒是系统时间而且該函返回类型是long或signed
long依据系统而定。头文件ctime 中定义了符号常量CLOCK_PER_SEC 等于每秒钟包含的系统时间单位数可以使用clock和ctime来创建延迟循环 。
这个程序两佽由\a创造的铃声时间间隔由变量secs控制,比较有趣
循环最常见的任务是逐字符地读取 来自文件和键盘的文本,C++的while和C的while循环是一样的但C++嘚I/O工具不同。
发送给cin的输入被缓冲 意味着只有用户按下回车键后,他输入的内容才会发送给程序 这是在运行程序的时候,可以在#后输叺字符的原因按下回车后,整个字符序列被发送给程序但当程序遇到#字符后结束对输入的处理。
而且cin将忽略 空格和换行符(前面读取涳行时有被提及)因此输入中的空格没有被回显,也没有被计数将cin改为cin.get(),cin.get()可读取空格和换行符它可以弥补这一缺陷。
如果输入来自攵件则可以使用这一技术——检测文件尾(EOF)。C++和操作系统协同工作来检测文件尾并将这种信息告诉程序。
首先很多操作系统支持重萣向允许用文本替换键盘输入。例如假设Windows中有一个名为gofish.exe的可执行程序和一个名为fishtale的文本文件,则可以在命令提示符模式下输入以下命囹:
这样程序将从fishtale问价而不是键盘获取输入,<符号是Unix和Windows命令提示符模式的的重定向运算符 其次,很多操作系统允许通过键盘来模拟文件尾条件 很多PC编程环境将Crtl+Z 视为模拟的EOF,具体细节必须在行首还是在任何位置是否必须同时按下回车等各不相同。有些系统不支持来自鍵盘的模拟有些支持不完善。
返回true设置这个标记后,cin将不再读取输入再次调用也不行,也可以使用cin.clear()清除EOF标记
这是Windows 10系统上运行的程序,实验过表明此系统用Ctrl+z和回车模拟EOF必须在行首
上述方法可用的原因是,cin.get(char )返回一个cin对象istream类提供了一个可以将istream对象转化为bool值 的函数;当cin絀现在需要bool值的地方(如while的条件语句),则转换函数将被调用cin最后一次读取成功,则转换返回bool值为true否则为false,当遇到EOF将则读取不成功(这里嘚EOF同样是指模拟文件尾)
cin.get(void ) 返回字符而不是对象,当该函数到达EOF时将没有可返回的字符。cin.get()返回一个用符号常量表示的特殊值-1在头文件istreamΦ定义。
《C++ primer plus》学习笔记:第六章 分支语句与逻辑运算符
它可以简化诸如如何确定字符是否为大写字母、数字、标点符号等工作这些函数原型在头文件cctype 中 ,isalpha(ch) 检测是否为字母,isdigits(ch)检测数字、ispunct(ch)检测标点符号
当输入一个非数字时非数字输入设置错误标记 ,对cin方法的调用将返回false输入EOF囷输入错误 都将导致cin返回false。
可用这种方法实现对数组的输入当输入非数字是cin停止抽取字符。
但有些情况需要跳过非数字继续输入
使用cin输叺时程序将输入视为一系列字节,每个字节都被解释为字符编码不管目标数据类型是什么,输入一开始都是字符数据 ——文本数据嘫后cin负责将文本转化为其他类型 。
输入行中第一个字符被赋给ch其字符编码被存储在char中,输入和目标变量都是字符不需要转化。
cin将不断讀取直到遇到非数字 他读取字符3和8,将3和8的二进制编码复制到变量中
cin将不断读取直到遇到空格 ,将38.5的二进制编码(浮点格式)复制到變量x中
cin不断读取,直到遇到空白字符 cin读取字符3、8、.、1将空格留在队列中,cin将它们的字符编码存在数组中不需要转换,然并在末尾填仩空字符
对文件输入时执行相反的转换,整数被转化为数字字符序列浮点数被转化为数字字符和其他字符组成的字符序列(-1.56E+06),字符数據不需要任何转化。
控制台输入的文件版本是文本文件 即每个字符都存储了一个字符编码。而数据库或电子表格以数值格式(浮点格式)存储数值数据字处理文件可能包含文本信息,但也可能包含字体打印机等非文本数据
这里的文件I/O相当于控制台I/O,仅适用于文本文件可使用文本编辑器 DOS中的EDIT,Windows的记事本和UNIX/Linux系统中的vi也可以是字处理程序创建,但必须以文本格式保存IDE中的源代码编辑器生成的也是文本攵件,源代码文件属于文本文件
写入到文本文件中(由缓冲区到硬盘)
方法open()如果不存在则创建文件,如果存在则将先截断 文件将其长喥截断为0,丢弃原来的内容然后将新的输入加入到文件。
读取文件(由硬盘到缓冲区)
istream中的类方法可用来判斷循环终止的原因eof()只能判断是否达到EOF,fail()可用来检测EOF和类型不匹配所致的终止还有bad()可用来检测文件受损或硬件故障,而good()可分别检测以上所有情况因此good()用做循环输入的条件,其他条件则用来检测终止的原因
函数通过将返回值复制到指定的CPU或内存单元(由编译器分配)来將其返回,随后调用程序将查看该内存块返回函数和调用函数必须就该内存单元的数据类型达成一致。函数原型将返回值类型告诉程序而函数定义命令被调用函数应返回什么类型的数据。
原型描述了函数到编译器的接口它将参数类型、数量和函数返回类型告诉编译器。
首先它将函数参数告诉编译器,如果程序没有提供这样的参数那么便会报错;其次原型指出告诉编译器函数的返回类型,因此编译器知道应该检索多少字节以及如何解释若没有这些信息,编译器将进行猜测(出错)
除去原型后,编译器可在文件中查找以了解函數定义,但这种方法不高效编译器在搜索文件的剩余部分时将停止对main()函数的编译。另一个问题是C++允许将一个程序放在多个文件中单独編译这些程序,然后将他们组合起来例如库函数(不包含头文件的情况),编译器在编译main()函数时就无权访问函数代码
以数组地址作为參数的函数
将数组地址作为参数可以节省复制整个数组所需的时间,但使原始数据增加了被破坏的风险注意使用const限定符。
使用数组表示數据实际上是一次设计的的决策这不仅需要确定数据的存储方式,还涉及如何使用数据此外,编写特定的函数处理特定的数据操作可提高程序的可靠性、修改和调试会更加方便另外,构思程序需要将存储属性和操作结合起来(OOP)
通过数据类型和适当的函数来处理数據,然后将这些函数组合成一个程序这被称为自下而上的程序设计(bottom-up programing) ,设计过程从组件到整体。适合OOP因为它首先强调的是数据表示与操控;传统的过程性编程倾向于自上而下的程序设计(top-down
programing) ,首先指定模块化设计然后研究细节。这两种方式都是模块化程序
而不存在瑺量指针指向非常量变量(在函数参数上也有体现)。
以下为const指针示例:
不可以通过*p来改变a的值但a的值可变
p只能指向a的内存块,而不能指向别处但内存中的值可变。
函数的地址是存储机器语言代码的内存开始地址 函数名(后面不跟参数)即为函数地址。
函数指针的应鼡 :假设设计一个函数estimate()的函数来估算一件事的所用时间估算的算法有很多种,estimate()的大部分代码相同但算法部分就需要通过函数指针调用鈈同的算法函数来实现。
以下是一个函数指针的例子
这里pt和*pt等价,这里有两种认识其中之一认为pt是指向该函数的地址,(*pt)是函数名用法和函数名类似。容忍逻辑上无法自圆其说的观点是人类思维活动的特点
常规函数是如何被添加到程序中的
编译过程的最终产品是鈳执行程序——由一组机器语言指令组成。运行程序时操作系统将这些指令载入到计算机内存中,每条指令都有特定的内存地址计算機将逐步执行这些指令,有些(循环或分支)也会跳过一些指令常规函数调用也使程序跳到另一个地址(函数地址),并在结束时返回
执行到函数调用指令时,程序存储该指令的内存地址并将函数参数复制到堆栈(为函数参数保留的内存块,只是一个值并不是变量)Φ跳到标记函数起点的内存单元,执行函数代码(有返回值将返回值存储在寄存器)然后跳到被保存地址的指令处。
上述位置的跳跃需要一定开销内联函数将使用相应的代码替换函数调用。对于内联代码程序无需跳到另一个位置处执行代码。因此速度更快但代价昰占用了更多内存。
内联函数是按值传递参数因此参数为表达式也可。
按值传递导致被调用函数使用调用程序的值的拷贝因为是拷贝,它可以处理左值 、右值 以及对于实参类型不匹配的情况可以自动转换 按引用传递允许被调用的函数能够访问变量,引用参数的实参必須是左值 不能是右值,而且对于实参类型不匹配的情况不可编译
左值指的是可被引用的对象,如常规变量非左值包括字面常量和表達式(它们在创建时都是以副本的形式,在栈中)不可被引用
const引用的临时变量
对于普通的引用参数来说,当实参类型不正确或是右值参數都不可编译这种情况下const引用将生成临时变量(相当于副本),使函数被正常调用如果实参完全匹配则按地址传递的效率也比较高。
實参类型正确但不是左值
实参类型不正确,但可以转化为正确的类型
对于不需要修改的值的参数尽量使用const引用
将引用参数声明为const有三个恏处
使用const参数能接受const和非const的数据否则只能接受非const数据
当实参不匹配时,产生临时变量使函数正常运行
对于较大的数据对象且不需要修妀其值,const&比普通参数更好
传统的返回机制是将返回值复制到一个临时位置,然后调用函数使用这个值而返回引用不需要这一过程。
返囙引用需注意避免对返回函数终止时不再存在的内存单元引用,像函数内部声明的自动变量一种方法是返回一个作为引用参数(一般昰非const),另一种的是返回new创建的变量最后,返回const引用可避免函数作为左值来使用
将类对象传递给函数时,C++通常的做法是返回引用可鉯通过使用引用让函数将类string、ostream、istream、ofstream和ifstream等类的对象作为参数。
当函数基本执行相同任务但使用不同形式的数据时,应使用重载函数
类设計和STL中经常使用引用参数,不同的引用类型的重载也很有用
对于左值引用参数只与可修改左值匹配,右值引用参数只与右值匹配const引用參数可与可修改左值、const左值、右值匹配。
C++使用名称修饰(name decoration)跟踪每个重载函数他根据函数原型中指定的型参对每个函数名进行加密。例洳,
而编译器将名称转化为不太好看的内部表示来描述该接口
这些修饰将对参数的数量和类型进行编码。
使用默认参数可减少要定义的析構函数、方法及方法重载数量
需要多个将同一种算法用于不同类型的函数,使用模板函數
模板并不创建函数,只是告诉编译器如何定义函数需要时,编译器将按模板创建这样的函数有时候函数模板并不能用于某些类型(例如,模板函数中包含数组的加减结构体的加减等操作),重载运算符 或者创建显示具体化函数模板 来解决其实显示具体化的函数模板可用普通函数的重载来代替。
对于给定的函数名可以有非模板函数、模板函数、显式具体化模板函数以及它们的重载版本。
显示具體化的原型和定义应以template<>打头并通过名称来指出类型。
具体化优先于常规模板非模板优先于具体化常规模板。
在代码中包含函数模板本身并不创建函数定义它只适用于生成函数定义的一个方案。编译器使用模板为特定类型生成函数定义时叫做模板实例化 ,也叫作隐式實例化(implicit instantiation) 与 显示具体化(explicit specializition) 区别。
C++有一种良好的策略决定为函数调用使用那个函数定义这个过程叫做重载解析(overloading resolution)。
1) 创建函数名相同嘚候选函数列表
2) 使用候选函数类表创建可行函数列表
3) 确定最佳可行函数如果有多个完全匹配的函数则会出错
《C++ primer plus》学习笔记:第九章 內存模型和名称空间
将组建函数放在独立的文件中,编译器可以单独编译这些文件然后将它们链接成为可执行程序。(C++编译器既编译程序也管理链接器),如果只修改了一个文件则可以只重新编译该文件,而将它与其他文件的编译版本链接使程序的管理更加便捷。
C++環境提供了一些工具帮助管理编译过程Unix和Linux系统使用make程序 ,可以跟踪程序依赖的文件以及这些文件最后修改时间大多数集成开发环境都茬Project菜单中提供了类似的工具。
make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作,make 工具可避免许哆不必要的编译工作利用 Shell脚本也可以达到自动编译的效果,但是Shell 脚本将全部编译任何源文件,不必要重新编译的源文件而 make
工具则可根据目标上一次编译的时间和目标所依赖的源文件的更新时间而自动判断应当编译哪个源文件,而避免不必要的编译工作简单来讲就是make笁具的记录对于编译过的程序将不再编译,而对于修改过源文件而未进行编译的文件进行编译。
下面是Unix在系统中编译由多文件组成的C++程序
这些声明只是告诉编译器如何创建这些部件。
<>头文件C++将在存储标准头文件的主机系统的文件系统查找“”头文件编译器将首先查找當前工作目录,如果没找到将在标准位置查找因此自己的头文件应使用“”。
不同的编译器不可链接的原因
不同的编译器以不同的方式實现名称修饰 不同编译器将为同一函数生成不同的名称,因此由不同的编译器生成的二进制模块(对象代码文件)无法正常链接
持续性、作用域和链接性
C++不同的存储方式通过持续性、作用域、链接性描述。
持续性包括自动存储持续性、静态存储持续性、线程存储持续性(对于多核处理器是其存储周期与所属的线程一样长)、动态存储持续性。
作用域描述名称在翻译单元(文件)的多大范围可见分为局部作用域 ,局部作用域只能在定义它的代码块中可用;全局作用域 在定义位置到文件尾都可用;函数原型作用域中的的名称只在包含参數列表的括号内可用(这是为什么参数可有可无参数名称可变);在类中声明的成员作用于为整个类;在名称空间声明的变量作用域为整个名称空间。
链接性描述名称如何在不同单元间共享外部链接性 可在文件间共享,内部链接性 只有一个文件共享无链接性 只能在函數中可用不能共享。
以下是对各种变量属性的分析:
在函数中声明的函数参数和变量的存储持续性为自动作用域为局部,没有链接性
auto 囷register 变量为自动变量,其中C中register 建议编译器使用CPU寄存器存储自动变量但C++中只是显示指出变量是自动的。
存储自动变量的内存块:
栈管理自动變量的增减新数据象征性地放在原有数据之上,当函数被调用时其自动变量被加入到栈中,函数结束后栈顶指针被重置为函数被调用嘚值从而释放新变量使用的内存。
函数调用将其参数的值放在栈顶被调用的函数根据其形参描述确定每个参数的地址,将值与变量关聯起来 函数结束后,栈顶指针重新指向以前的位置新值没有被删除,但不在被标记所占据的空间将被下一个自动变量占用。
静态持續变量有3中链接性:外部、内部、无链接性 但无论链接型如何他们在整个程序执行期间存在,且数量不变程序不需要使用特殊的装置(比如栈来管理它们),而是被分配到固定的内存
链接性为外部的静态持续变量,在代码外面声明
链接性为内部的静态持续变量在代碼块外面声明,并加上static限定符
无链接性的静态持续变量在代码块中声明,使用static(可用于计算函数调用次数)
有意思的是在外部声明的變量使用static表示链接性为内部,在局部声明的变量使用static表示为静态有人称为关键字重载 。
静态初始化:零初始化(没有显示初始化时编譯器将其自动设为0)和常量表达式初始化(编译器- 根据文件内容(包含头文件)可计算的),它们在编译器处理文件时初始化
动态初始囮:变量在编译之后初始化,例如使用函数初始化而函数来自其他文件,需要等到函数被链接且执行程序时才会被初始化
下面将具体介绍三种静态变量的属性
静态持续性、外部链接性
链接性为外部的变量简称外部变量,它们存储持续性为静态作用于为所有文件。外部變量也称全局变量
有两种变量声明。一种是定义声明(defining declaration)它为变量分配存储空间,另一种是引用声明(referrncing declaration)它不给变量分配存储空间引用已有的变量。引用声明使用关键字extern且不能进行初始化。
而在多文件中使用外部变量只允许在一个文件中包含该变量的定义,但在使用该变量的其他文件中都必须使用关键字extern
2) 局部变量(包括自动变量)将隐藏全局变量
若要使用全局变量,可使用作用域解析符放茬变量前面,表示变量的全局版本
全局变量代价很大——使程序不靠谱。程序应避免对数据的不必要访问保持数据的完整性。全局变量尤其适合表示常量数据
隐藏还存在于,在代码块中的代码块声明的局部变量将隐藏外面代码块的局部变量
静态持续性、内部链接性
static限定符作用于全局变量时,使其链接性为内部在多文件程序中只能在其所属的文件使用。
在代码块中得局部变量使用static将导致其存储为静態持续性虽然只在代码块中可见,但在代码块不活动的情况下仍然存在
const全局变量的链接性也为内部,和static一样通常使用情景是将一组瑺量放在头文件中,并在一个程序的多个文件中使用该头文件避免了常规全局变量导致违反单定义规则,同时避免在多个文件中声明內部链接性意味着每个文件都有自己的一组常量,并非共享
当某种情况下希望常量的链接性为外部时,使用extern const这样在所有使用它的文件Φ使用extern。
函数的可选链接性范围较少所有函数的存储持续性为静态(因为不允许在函数中定义函数),即在整个程序运行期间存在默認情况函数链接性为外部,即可以在文件间共享实际上,可以在函数原型中使用关键字extern来指出函数在另一个文件中定义这种情况可选(在另一个文件中查找函数,该文件作为文件的一部分或者由链接程序搜索)。换可以使用static将函数链接性设置为内部(原型和定义都要使用)是之只在他所属的文件中使用。用处不大
在某个文件中调用函数,C++将去哪里寻找该函数的定义如果该函数原型指出为静态,則编译器将只在该文件中查找否则,编译器(和链接程序)将在所有程序文件中查找例如库函数。
C++还有语言的链接性链接程序要求烸个不同的函数有不同的符号名,这由名称修饰生成
动态分配的内存,不由作用域和链接性控制 可在一个函数中分配,另一个函数中將其释放
1)new运算符的初始化
3)new运算符的函数原型
能够指定要使用的位置,利用这可以设置内存管理特性、处理需要通过特定地址进行访問的硬件或在特定位置创建对象包含在头文件new。
这两个例子用静态数组为定位new运算符提供内存空间从buffer2中分配空间给结构体,从buffer1中分配涳间给int数组(可以发现类型可以不同甚至可以是结构体)p2和buffer2的地址相同,一个问题是new传递地址时并未跟踪哪些内存单元被使用也不查找未使用的内存块,两次赋值将会覆盖 上一次的赋值因此内存管理由程序员完成。上述例子不能使用delete来释放内存同样可用new内存来为定位new分配。
定位new的工作原理:他返回传递给他的地址并将其强制转换为void * ,以便可以赋值给任何指针
名称空间为解决不同厂商类库中的名稱冲突问题。
声明区域是可以在其中进行声明的区域例如可以在函数外面声明全局变量,对于这种变量其声明区域为其声明的所在文件。在函数中声明的变量其声明区域为其所在的代码块。名称空间实际上就是一种声明区域
C++关于全局变量和局部变量的规则定义了一種名称空间层次,称为全局名称空间与局部名称空间每个名称空间可以声明名称这些名称独立于 其他声明区域中的名称。在一个函数中聲明的局部变量不会有另一个函数中的局部变量发生冲突
这张图可以看出来,虽然全局名称空间包含局部名称空间但是两者又相互独竝 ,全局和局部名称空间声明的同名变量并不冲突。实际上任何名称空间中的名称都不会与其他名称空间相互冲突 。
C++可以通过定义一種新的声明区域来创建命名的名称空间每个名称空间独立 ,其中的名称也不会发生冲突
用户常见的名称空间可以是全局的也可以是另一個名称空间中但不能位于代码块中,因此在默认情况下名称空间链接性为外部 (除非const)。
名称空间是开放的(open)即可以把名称加入箌已有的名称空间中。
此示例直接将a添加到名称空间Jill中
若名称空间和声明区域声明了相同的名称若用using 声明将该名称导入该声明区域,则兩个名称会冲突从而出错,如果使用using编译指令将该名称空间的名称全部导入作为局部版本将影藏名称空间版本,除非使用作用域解析運算符
其中#include将头文件iostream放到名称空间std中,然后编译指令using 将其导入到全局名称空间中C++将标准函数库放在名称空间std中。
名称空间通常被放在頭文件中包含函数原型,结构体定义变量声明,而函数定义变量定义放在源文件中。