本文最后更新于 1216 天前,其中的信息可能已经有所发展或是发生改变。
以《C++ Primer Plus》第六版为顺序整理,不排除中间插入某天突然学到的知识。对于每个知识点不会详解,需要详解的内容将会另开新章。
记录内容:
1、我不会或者不熟悉的
2、我认为重要的
3、C++11及以后新增的会在本文中提及,但详细会开新篇
1 cin.get(字符数组名,接收长度)
此函数是cin的成员函数,作用为接收除结束符(默认为回车)外所有的字符,有无参、一参、二参形式,结束符不会在缓冲区中丢弃,需要注意的是对于cin>>与cin.get()混用的情况,在cin>>之后需要用cin.ignore()丢弃缓冲区中的'\n'(作用为跳过一个字符,或者可以用cin.get()来抵消换行符),否则cin.get()会失效。
2 using namespace std;
using编译指令。旨在编写大型程序以及将多个厂商现有的代码组合起来的程序时更容易。面对不同版本的同名函数时作区分用。可以用在特定的函数定义中,让该函数能够使用命名称空间std中的所有元素。
3 运算符sizeof
可对类型名或变泉名使用sizeof运算符。对类型名(如 int)使 用sizeof运算符时,应将名称放在括号中;但对变量名(如 n_short)使用该运算符时,括号是可选的: cout << "int is " << sizeof (int) << "bytes.\n" ; cout << "short is " << sizeof n_short << "bytes.\n";
4 C++赋值法(相比于C而言)
int wrens(432); // alternative C++ syntax, set wrens to 432 int hamburgers = {24}; // set hamburgers to 24 int emus{7}; // set emus to 7 当大括号为空时,默认为0 unsigned本身是unsigned int的缩写
5 整型字面值
C++使用前一(两)位来标识数字常量的基数。如果第一位为1〜9 ,则基数为10(十进制):因此93是以10为基数的。如果第一位是0,第二位为1〜7,则基数为8(八进制);因此042的基数是8,它相当于十进制数34。如果前两位为0x或0X,则基数为16(十六进制):因此0x42为十六进制数,相当于十进制数66。对于十六进制数,字符a〜f和A〜F表示了十六进制位,对应于10〜15。OxF为15,0xA5为165。
6 wcha_t
扩展字符集。可以表示系统使用的最大扩展字符集。wcin和 wcout可用于处理wchar_t流。可以通过加上前缀L來指示 宽字符常量和宽字符串(在MFC里用过这玩意应该)。
7 const限定符
修改变量声明和初始化。尽可能使用C++的const来代替C中的#define。
8 以{}形式初始化时进行的转换
C++11将使大括号的初始化称为列表初始化,因为这种初始化常用于给复杂数据类型提供值列表。它对类型转换的要求更严格。列表初始化不允许缩窄,即变量的类型可能无法表示赋给它的值。例如,不允许将浮点型转换为整型。在不同的整型之间转换或将整型转换为浮点型可能被允许,条件是编译器知道目标变量能够正确地存储赋给它的值。例如,可将long变量初始化为int值,因为long总是至少与int—样长;相反方向的转换也可能被允许,只要int变量能够存储賦给它的long常量: const int code = 66; int x = 66; char cl {31325}; // narrowing, not allowed char c2 = {66} ; // allowed because char can hold 66 char c3 {code}; // ditto(同上) char c4 = {x} ; // not allowed, x is not constant x = 31325; char c5 = x; // allowed by this form of initialization
9 强制转换
强制转换的通用格式如下: (typeName) value // converts value to typeName type typeName (value) // converts value to typeName type 第一种格式来自C语言,第二种格式是纯粹的C++。目的是为了让其看上去像函数调用。 static_cast<>可用于将值从一种数值类型转换为另一种数值类型,比普通的强制转换更为严格: static_cast <typeName> (value) // converts value to typeName type
10 auto
C++11。让编译器能够根据初始值的类型推断变量的类型。在初始化声明中,如果使用关键字auto,而不指定变量的类型,编译器将把变量的类型设置成与初始值相同。 个人感觉还是迭代器时用的比较多,毕竟能少打不少字。
11 数组(C++11改动)
初始化数组时,可省略等号(=): double earnings[4]{l.2e4,1.6e4,1.1e4,1.7e4}; // okay with C++11 可不在大括号内包含任何东西,这将把所有元素都设置为零: unsigned int counts[10] = {}; // all elements set to 0 float balances[100]; // all elements set to 0
12 字符串拼接
C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。实际上,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。因此,下面所有的输出语句都是等效的: cout << " I'd give my right arm to be" " a great violinist.\n" ; cout << " I'd give my right arm to be a great violinist.\n"; cout << " I'd give my right ar" "m to be a great violinist.\n"; 拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\0)后面。第一个字符串中的\0字符将被第二个字符串的第一个字符取代。
13 C++11新类型字符串
C++11新增了类型charl6_t和char32_t。可创建这些类型的数组和这些类型的字符串字面值。对于这些类型的字符串字面值,C++分别使用前缀L、u和U表示,下面是一个如何使用这些前缀的例子: wchar_t title[] = L"Chief Astrogator"; //w_char string charl6_t name[] = u"Felonia Ripova"; //char_16 string char32_t car[] = U"Humber Super Snipe"; //char_32 string C++11还支持Unicode字符编码方案UTF-8。在这种方案中,根据编码的数字值,字符可能存储为1〜4个八位组。C++使用前缀u8来表示这种类型的字符串字面值。 C++11新增的另一种类型是原始(raw)字符串。在原始字符串中,字符表示的就是自己,例如,序列\n不表示换行符,而表示两个常规字符——斜杠和n,因此在屏幕上显示时,将显示这两个字符。可在字符串中使用",而无需使用繁琐的\"。当然,既然可在字符串字面量包含",就不能再使用它来表示字符串的开头和末尾。因此,原始字符串将"(和)"用作定界符,并使用前缀R來标识原始字符串: cout << R"(Jim "King" Tuttuses "\n" instead of endl.)" << '\n';
14 共用体(union)
共用体能够存储不同的数据类型,但只能同时存储其中的一种类型。例如: union one4all { int int_val; long long_val; double double_val; }; one4all可以在同一时间表示三者中的其中一种数据类型,当一个数据可能是多种数据类型中一种时使用,从而节约空间(多数用在嵌入式系统编程中); 此外可以使用匿名的方式,例如: struct widget { char brand[20]; int type; union { long id_num; char id_char[20]; }; }; 两者共用同一个地址;
15 array
C++11新增模板类;array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。 array<int,5> ai; array<double,4> ad = {1.2 ,2.1 ,3.43 ,4.3}; 与数组不同的是,它可以将一个array对象赋给另一个array对象:而对于数组,必须逐元素复制数据; 可以使用at()成员函数获取变量来保证安全;
16 关于自增
针对y = (4 + x++) + (6 + x++);的情形,由于C++没有规定自增是在子表达式执行后自增还是在整个赋值语句执行后自增,所以该语句在不同情况下x的自增时间不同,C++只能保证x在赋值语句执行后被执行两次自增; 因此严禁在赋值语句中使用自增; 对于内置类型,采用哪种格式不会有差别;但对于用户定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高。用户这样定义前缀函数:将值加1,然后返回结果;但后缀版本首先复制一个副木,将其加1,然后将复制的副本返回。简而言之,基本就用前缀自增/自减就行; 由于C++是由右往左进行计算的,所以*++pt指的是*(++pt),同理++*pt指的是++(*pt);
17 逗号运算符
逗号运算符由左往右运算; C++规定逗号表达式的值是第二部分的值; 在所有运算符中逗号运算符的优先级是最低的; 举例:cats=(17,240); 此时cats为240;
18 延时循环
以前通常使用线程sleep之类的方法,但在书中提供了该问题的新的解决方案,位于头文件ctiem中; 酋先,它定义了一个符号常量:CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数。因此,将系统时间除以这个值,可以得到秒数。或各将秒数乘以CLOCKS_PER_SEC,可以得到以系统时间单位为单位的时间; 其次,ctime将clock_t作为clocl()返回类型的别名,意味着可以将变量声明为clock_t类型,编译器将把它转换为long、unsigned int或适合系统的其它类型。 float secs; cin >> secs; clock_t delay = secs * CLOCKS_PER_SEC; clock_t start = clock(); while (clock() - start < delay ) ; 该程序以系统时间单位为单位(而不是以秒为单位)计算延迟时间,避免了在每轮循环中将系统时间转换为秒。
include <iostream>
include <ctime>
using namespace std;
int main() {
float secs;
cin >> secs;
clock_t delay = secs * CLOCKS_PER_SEC;
clock_t start = clock();
while (true)
{
cout << "done" << endl;
while (clock() - start < delay); //循环延迟secs秒
start = clock();
}
return 0;
}
19 类型别名
一共有两种方法,其一是预处理器#define,另一种是typede; 其中#define在声明一系列变量时不适用,例如: define FL0AT_P0INTER float * FL0AT_P0INTER pa , pb; 预处理器置换将该声明转换为这样: float * pa , pb; 而typede不会;
20 基于范围的for循环
即for(a:b)的形式; 当需要修改数组的值时:for(&a:b)的形式; 还可以这样用:for(int x : {1,2,3,4,5})
21 运算符优先级
括号、对象、指针 大于 负号、自增、强制转换、取值、取地址、非、位反、sizeof 大于 */% 大于 +- 大于 左移、右移 大于 ><等关系运算符 大于 ==和!= 大于 按位与& 大于 按位异或^ 大于 逻辑与&& 大于 逻辑或|| 大于 ?...:...(由右往左运算) 大于 赋值运算符 =、+=、>>=、^=等 大于 逗号运算符
22 cctype(五分之一本书处)
用于判断变量类型(指其本质类型,非C++类型,如数字、字母等),位于头文件cctype中; isalpha判断是否是字母 ispunct判断是否是符号 isdigits判断是否数字 isspace判断是否是空格(换行符、空格和字符表) isalnum判断是否是字母和数字 iscntrl判断是否是控制字符 isgraph判断是否是空格以外的打印字符 islower判断是否是小写字母 isupper判断是否是大写字母 isprint判断是否是打印字符(含空格) isxdigit判断是否是十六进制数字 tolower将大写字母转化为小写字母 toupper将小写字母转化为大写字母
23 switch和if…else…
switch不能处理浮点数(只能整数,含char)、不能处理范围、不能进行两个变量的比较; 当所有分支都是整数常量且分支数大于2时switch的效率更高;
24 读取数字的循环
while(true) { int n; cin>>n; } 针对上述的循环数字输入,当输入非数字字符时会使循环结束(返回false),可以利用if和cin.clear()消除这种错误状态;
25 文件写入
头文件fstream,其中定义了一个用于处理输出的ofstream类; 需要将ofstream对象与文件关联起来。为此,方法之一是使用open()方法; 使用完文件后,应使用方法closeO将其关闭; 可以使用和cout一样的输出控制方法如fout<<precision(4);
26 文件读取
头文件fstream,其中定义了一个用于处理输出的ifstream类; 需要将Ifstream对象与文件关联起来。为此,方法之一是使用open()方法; 使用完文件后,应使用方法closeO将其关闭; 可以使用ifstream对象和get()方法来读取一个字符,使用ifstream对象和getline()来读取一行字符; 可以结合使用ifstream和eof()、fail()等方法来判断输入是否成功; ifstream对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否 则被转换为false; 检查文件是否被成功打开的首先方法是使用方法is_open();
27 const和指针
如果数据类型本身并不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针; 将const变量的地址赋给指向const的指针可行; 将const的地址赋给常规指针不可行; 当有两层及两层以上间接关系时上述两种方法皆不可行;
28 内联函数
普通的函数调用:执行函数调用指令→存储该指令的内存地址→将函数参数复制到堆栈→跳到标记函数起点的内存单元→执行函数代码→调回到地址被保存的指令处;(来回跳跃并记录跳跃未知意味着使用函数时需要一定的开销) 内联函数:编译器使用相应的函数代码替换函数调用,程序无需跳到另一个位置处执行代码,运行速度比常规函数稍快,但需要占用更多内存。如果程序在N个不同地方调用同一个内联函数,则该程序将包含该函数代码的N个副本; 用法:在函数声明或定义前加上关键字inline,通常省略原型,将整个定义放在本应提供原型的地方;内联函数不能递归;
29 引用变量
形如: int rats; int& rodents = rats; 即将rodents作为rats的别名,二者具有相同的值和内存单元;引用必须在声明引用时将其初始化,而不能和指针一样先声明再赋值: int rats = 101; int* pt = &rats; int& rodents = *pt; int bunnies = 50; pt = &bunnies; rodents在初始化时已经固定引用rats,之后的再赋值不会改变; 将引用用作函数参数即按引用传递,允许被调用的函数能够访问调用函数中的变量;当不需要修改被引用值时应当加上const修饰; 按引用传递时函数参数应当是个变量,而不能是常量或者表达式(远古版本的C++允许这么写); 按引用传递当函数形参为const引用或为左值且类型不匹配时会产生一个临时匿名变量,在远古版本C++以及当下部分编译器中会产生引用失效的问题,即由于产生了临时变量,对临时变量进行修改不会改变原始数据的值,从而使之与按值传递相似; 引入引用的主要目的是为了结构和类;使用结构引用参数的方式与使用基本变量引用相同; 返回引用:按值返回时需要将返回值复制到一个临时位置,当返回值过大(如结构体和类对象)时,效率过低,所以这时通常使用返回引用,即将返回值直接复制给接收的左值; 返回引用需要注意的是应当避免返回函数终止时不再存在的内存单元引用,即在函数体内定义了一个临时变量,该临时变量会在函数运行结束后被回收,此时返回的地址将不复存在,从而产生错误; 通常返回一个作为参数传递给函数的引用;或者用new分配新的存储空间(由于无法使用delete,会产生问题,后面会讲述关于auto_ptr和unique_ptr的方式自动完成释放的方法); accumulate(dup ,five) = four; //返回的是& 形如上述可以通过编译的原因在于其返回的是一个引用,是一个可修改的内存块,是左值;常规函数的返回值位于临时变量内存单元中,执行完便不复存在,故而无法赋值;当要禁止上述操作时应当使用const引用作为返回类型;
30 左值和右值
左值:可被引用的数据对象,例如:变量、数组元素、结构成员、引用和解除引用的指针等; 右值:字面常量(字符串如string除外,其实质为地址)和包含多项的表达式;
31 默认参数
形如char *left(const char *str, int n =1);的形式,即设定第二个参数的缺省值为1; 对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值;
32 函数重载
多态(重载)可以使用多个具有不同参数列表的同名函数; 此时C++不会自动进行标准类型转换,如果没有匹配的函数将会报错; 类型引用和类型本身不能作为函数重载的标识,而const可以;
33 函数模板
以泛型的方式编写程序,又称通用编程; 以交换为例,如下: template <typename T> void swap(T &a, T &b) { T temp; temp = a; a = b; b = temp; } 在C++98时typename关键字可用class取代; 通常将模板放在头文件中;
34 具体化和实例化
由于模板在部分具体情况下有局限性,故而就有了显式具体化方法来处理这部分情况; 1对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数以及它们的重载版本; 2 显式具体化的原型和定义应template<>开头,并通过名称来指出类型; 3 具体化优先于常规模板,而非模板函数优先于具体化和常规模板; 以交换为例,以下分别为非模板函数、模板函数和具体化的原型: // void swap(job &, job &); // template <typename T> void swap(T &, T &); // template <> void swap( job &, job & ); 隐式实例化:在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案,编译器使用模板为特定类型生成函数定义时,得到的是模板实例;模板并非函数定义,但使用int的模板实例是函数定义。这种实例化的方式成为隐式实例化; 显式实例化:过去的C++只能通过隐式实例化,现在还允许使用显式实例化,格式如下: template void swap<int> (int, int); 意为使用swap()模板生成int类型的函数定义; 具体化和实例化的区别:显式具体化有<>而显式实例化没有;前者用于某些模板所无法顾及的特殊情况,对其进行具体实现(如对于没有重载加法的结构体进行加法运算的模板函数);后者则是将隐式实例化给写出来; 注意:试图在同一个文件或转换单元中使用同一种类型的显示实例和显式具体化将出错;
35 重载解析
对于函数重载、函数模板和函数模板重载,C++决定为函数调用哪一个函数定义的过程为重载解析; 1 创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数; 2 使用候选函数列表创建可行的函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况; 3 确定是否有最佳的可行函数,若有,则使用,否则函数调用出错; 最佳匹配的顺序: 1 完全匹配,且常规函数优于模板; 2 提升转换(如short→int); 3 标准转换(如int→char); 4 用户定义的转换; 部分"无关紧要"的转换(如数组→指针)也属于完全匹配的范畴; 当有多个匹配的原型时编译器将会产生诸如ambiguou(二义性)的错误;部分有多个完全匹配的情况下不会报错如非const优先匹配非const;显式具体化由于隐式具体化;
36 decltype(C++11)
template<class T1, class T2> void ft(T1 x, T2 y) { type z = x + y; } 针对如上情况,由于不清楚x和y的类型,自然也无法得知z的类型,于是C++11中新增了关键字decltype来处理这种情况: decltype(x+y) z = x + y; //即令z的类型和x+y相同; 需要注意的是如下情况: int j = 3; int &k = j; int &n = j; decltype(k+n); 虽然k和n都是引用,但表达式k+n不是引用,而是int; 针对需要多次声明的情况可以结合typedef来使用;
37 C++11后置返回类型
有一个问题是上述decltype所无法解决的,那就是声明或者定义函数时当返回类型不确定的情况,于是C++11中新增了后置返回类型的方式; 形如: double h(int x, float y); 使用新增的语法可编写为: auto h(int x, float y) -> doube; 其中auto是一个占位符,以此结合decltyoe来指定返回类型,如下: auto h(int x, float y) -> decltype(x + y){} 此时decltype在参数声明的后面且在x和y的作用域内,因此可以使用;
第九章 内存模型和名称空间
38 单独编译
对于UNIX、LINUX以及多数编译器都具备单独编译的功能,即根据文件修改的最后时间的选择是否重新编译; 在C和C++中通常将程序分为三部分: 1 头文件:包含结构声明和使用这些结构的函数的原型 2 源代码文件:包含与结构有关的函数的代码 3 源代码文件:包含调用与结构相关的函数的代码 其中在头文件中常包含的内容有: 1 函数原型 2 使用#define或const定义的符号常量 3 结构声明 4 类声明 5 模板声明 6 内联函数 在同一个文件中只能将同一个头文件包含一次,为了避免多重声明,可以使用预处理器编译指令#ifndef和#endif,如下: #ifndef COORDIN_H_ #define COORDIN_H_ //头文件 #endif 对于每个被编译的文件C++使用的术语为 翻译单元(translation unit); 对于不同编译器编译的文件队友不同的名称修饰,链接时可能会产生链接错误,需要重新编译源代码来消除这种错误;
39 存储类别(存储持续性)
在C++中共有四种不同的方案(C++11新增一种)来存储数据,这些方案的区别在于数据保留在内存中的时间: 1 自动存储持续性:在函数定义中声明的变量(包含函数参数)的存储持续性为自动的。其在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时其使用的内存被释放。C++有两种存储持续性为自动的变量; 2 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性为静态。其在程序整个运行过程中都存在。C++有三种存储持续性为静态的变量; 3 线程存储持续性(C++11):在多核处理器中,程序将计算放在可并行处理的不同线程中,如果是使用关键字thread_local声明的,则其生命周期与所属的线程一样长; 4 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。其内存的持续性为动态,也被成为自由存储或堆;
40 自动变量
在C以及C++11以前,auto关键字被用于显式指出变量为自动存储持续性(基本没用),C++11后被废止; 在C以及C++11以前,register关键字被用于建议编译器使用CPU寄存器来存储自动变量,旨在提高访问变量的速度,C++11后失去了这个作用,仅是显式指出变量是自动的,当下只有想注明某自动变量的名称可能与外部变量相同的时候使用(基本没用); 自动变量和栈: 由于自动变量的数目会随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理,常用的方法是留出一段内存,并将视其为栈; 之所以被称为栈,是由于新数据被象征性地放在原有数据上面(在相邻的内存单元中,而不是在用一个内存单元中),当程序使用完后,将其从栈中删除; 栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项; 程序使用两个指针来跟踪栈,一个指向栈底,一个指向栈顶,当函数被调用时,其自动变量被加入到栈中,栈顶指针指向变量后面的下一个可用的内存单元。函数结束后,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存;
41 静态持续变量与外部链接性
C和C++为静态存储持续性变量提供了3种链接性:外部链接性(可在其它文件中访问,即全局变量)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。则3种连接性都在整个程序执行期间存在。由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的方式来管理。编译器将分配固定的内存块来存储所有的静态变量,其在整个程序执行期间一直存在。默认情况下,它们被初始化为0(零初始化); 创建外部链接性变量必须在代码块的外面声明它; 创建内部链接性变量必须在代码块的外面声明它,并使用static; 创建无链接性变量必须在代码块的内部声明它,并使用static; C++11新增了关键字constempr增加了创建常量表达式的方式; 单定义规则:由于变量只能有一次定义,为此C++提供了两种变量声明: 1 定义声明(简称定义),为变量分配存储空间; 2 引用声明(简称声明),不分配空间,引用已有的变量; 引用声明:使用关键字extern且不进行初始化(只在一个文件中初始化),主要用于多个文件中使用外部变量; C++新增(比起C)了作用于解析运算符(::),放在变量名前,表示使用变量的全局版本(当有同名的局部变量时);
42 const的第二种用法(对于数组)
const char * const months[2] = { "January","February" } 在上述实例中第一个const防止字符串被修改,第二个const确保数组中每个指针始终指向它最初指向的字符串;
43 静态持续性和内部链接性
将static用于作用域为整个文件的变量时,变量的链接性会变为内部; 在两个不同文件中分别定义同名的内部变量和外部变量不违反单定义规则;
44 静态持续性和无链接性
将static用于作用域为代码块的变量时,变量的链接性会变为无链接性; 主要用于同一个函数在多次调用时保持变量的不变性;
45 说明符
C++中有以下说明符: 1 auto(在C++11以后被更替含义) 2 register 3 static 4 extern 5 thread_local(C++11新增) 6 mutable 除thread_local外,在同一个声明中不能使用多个说明符; thread_local可与static或extern结合使用,其之于线程如同常规静态变量之于整个程序; mutable的含义将根据const来解释;
46 限定符
有如下: 1 const 2 volatile 故而又被成为cv-限定符; volatile表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化(可能被另一个程序或者硬件修改);如果编译器发现程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中;当把变量声明为volatile后则编译器将不会再进行这种优化;
47 mutable
mutable用于指出,即使结构或类变量为const,其某个成员也可以被修改。如下: struct data { char name[10]; mutable int accesses; } const data veep = {……}; veep.accesses = 2;
48 四论const
第四次提到这个限定符了; 在C++中,在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的,即全局const定义和static相似;之所以如此,例如有全局变量定义在头文件中,该头文件被多个源文件包含,若其链接性为外部,则会违反单定义规则; 如果需要某个常量的链接性为外部,则可以使用extern来覆盖默认的内部链接性,如下: extern const int states = 50; 此时必须在所有使用该常量的文件中使用extern来声明它;
49 函数链接性
因为C++不允许在一个函数中定义另一个函数,所以所有的函数的存储持续性都默认为静态的,链接性为外部的; 可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的,但非必须; 可以使用关键字static设置其链接性为内部的,使之只能在文件内部使用,必须同时在原型和定义中使用该关键字,作用为可以在其它文件中定义同名函数; 其中内联函数不受单定义规则的约束(毕竟在原型/头文件处定义);
50 语言链接性
由于C语言一个名称只对应一个函数,而在C++中一个名称可能对应多个函数,因此C++编译器将会执行名称矫正或名称修饰,为重载函数生成不同的符号名称,例如spiff(double,int): C:_spiff C++:_spiff_d_i 这面临一个问题,即C++想要调用C库中预编译的函数会产生差异,故而我们用函数原型来指出要使用的约定: extern "C" void spiff(double,int); extern void spiff(double,int); extern "C++" void spiff(double,int); 第一个原型使用C链接性,后两个使用C++链接性,其中第二个以默认的方式指出,而第三个则是显式指出;
51 动态分配
通常编译器使用三块独立的内存:一块由于静态变量(可能再细分),一块用于自动变量,另一块用于动态存储; 通常而言new分配的内存会在程序结束时被释放,但对于部分操作系统中不会,因此必须使用delete; 在C++98中,new并初始化通常如下: int *pi = new int (6); 当要初始化常规结构或数组时,需要使用大括号的列表初始化(C++11新增): struct where {double x;double y;double z;}; where *one = new where {2.5,5.3,7.2}; int *ar = new int[4] {2,4,6,7}; 也可以将列表初始化用于单值变量: int *pin = new int {7}; 当new找不到请求的内存量时,会引发异常std::bad_alloc(第15章) new将会调用如下函数(new[]、delete同理): void * operator new(std::size_t); 其中std::size_t是一个typedef,对应于合适的整型,对于如下: int *pi = new int; 将会转换为: int *pi = new(sizeof(int)); 对于如下: int *pa = new int[40]; 将会转换为: int *pa = new(40*sizeof(int)); 定位new运算符: 通常new在堆中找到一个足以能够满足要求的内存块,但new还有一种变体,被称为定位new运算符,它让你能够制定要使用的位置,可以用来设置内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象; 头文件:new;
#include<new>
struct chaff
{
char dross[20];
int slag;
}
char buffer1[50];
char buffer2[500];
int main()
{
chaff *p1, *p2;
int *p3, *p4;
p1 = new chaff;
p3 = new int[20];
p2 = new {buffer1} chaff;
p4 = new {buffer2} int[20];
}
上述代码从buffer1中分配空间给结构chaff,从buffer2中分配空间给一个包含20个元素的int数组;
52 namespace
名称空间可以是全局的,也可以在另一个名称空间中,但不能位于代码块中,默认情况在名称空间中声明的名称的链接性为外部的(除非引用了常量); 除了用户定义的名称空间外,还存在另一个名称空间:全局名称空间,它对应于文件级声明区域,全局变量位于全局名称空间中; 名称空间是开放的,可以把名称加入到已有的名称空间中; 通过作用域解析运算符::来访问给定名称空间中的名称; 为了避免每次使用名称时对其进行限定,故而C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用,其中using声明使特定的标识符可用,using编译指令使整个名称空间可用; 通常而言using声明比using编译指令更为安全,如果using声明的名称与局部名称发生冲突,编译器会警告;而using编译指令会导入所有的名称,如果和局部名称发生冲突,则局部名称将覆盖名称空间版本,编译器不会发生警告; 名称空间可以嵌套; 可以使用省略名称空间的名称来创建未命名的名称空间,因此不能显式的使用using来使它在其它位置可用,则提供了链接性为内部的静态变量的替代品;
参考资料:
- 《C++ Primer Plus》第六版
- 百度/谷歌
评论