C++ Primer Plus (第六版) 中文版(部分章节)
C++ Primer Plus (第六版) 中文版(部分章节)
Bjarne Stroustrup's homepage!
Bjarne Stroustrup's homepage!
https://www.stroustrup.com/
编译和链接
UNIX编译和链接 cc cfront
GNU C++编译器 g++(Linux系统中最常用的编译器)
Windows命令行编译器 Cygwin和MinGW
Macintosh 开发框架Xcode-自带g++/clang
包含文件(include file)/头文件(header file)
C++新式风格没有扩展名
源代码中的标记和空白
一行代码中不可分割的元素叫作标记。
通常,必须用空格、制表符或回车将两标记分开,空格、制表符和回车统称为空白(white space)。
有些字符(如括号和逗号)是不需要用空白分开的标记
C++源代码风格
C++源代码风格,遵循了下述规则。
- 每条语句占一行。
- 每个函数都有一个开始花括号和一个结束花括号,这两个花括号各占一行。
- 函数中的语句都相对于花括号进行缩进。
- 与函数名称相关的圆括号周围没有空白。
前三条规则旨在确保代码清晰易读;
第四条规则帮助区分函数和一些也使用圆括号的C++内置结构
(如循环)。
C空行分隔声明与主逻辑,C++中不常见
C的常用方法:空行将声明语句与程序的其他部分分开。(C++中不常见)
声明语句与变量
声明通常指出了要存储的数据类型和程序对存储在这里的数据使用的名称。
程序中的声明语句叫作定义声明(defining declaration)语句,简称为定义(definition)。
这意味着它将致编译器为变量分配内存空间。
在较为复杂的情况下,还可能有引用声明(reference declaration)。
这些明命令计算机使用在其他地方定义的变量。
通常,声明不一定是定义。
对于声明变量,C++的做法是尽可能在首次使用变量前声明它。
类简介
类是用户定义的一种数据类型。
要定义类,需要描述它能够表示什么信息和可对数据执行哪些操作。
类之于对象就像类型之于变量。
也就是说,类定义描述的是数据格式及其用法,而对象则是根据数据格式规范创建的实体。
换句话说,如果说类就好比所有著名演员,则对象就好比某个著名的演员,如蛙人Kermit。
我们来扩展这种类比,表示演员的类中包括该类可执行的操作的定义,如念某一角色的台词,表达悲伤、威胁恫吓,接受奖励等。
如果了解其他OOP术语,就知道C++类对应于某些语言中的对象类型,而C++对象对应于对象实例或实例变量。
注意:类描述了一种数据类型的全部属性(包括可使用它执行的操作),对象是根据这些描述创建的实体。
类描述指定了可对类对象执行的所有操作。要对特定对象执行这些允许的操作,需要给该对象发送一条消息。
C++提供了两种发送消息的方式:
- 一种方式是使用类方法(本质上就是函数调用);
- 另一种方式是重新定义运算符;
函数原型
注意:C++程序应当为程序中使用的每个函数提供原型。
函数原型之于函数就像变量声明之于变量——指出涉及的类型。
原型结尾的分号表明它是一条语句,这使得它是一个原型,而不是函数头。如果省略分号,编译器将把这行代码解释为一个函数头,并要求接着提供定义该函数的函数体。
不要混淆函数原型和函数定义。可以看出,原型只描述函数接口。
也就是说,它描述的是发送给函数的信息和返回的信息。而定义中包含了函数的代码。
C和C++将库函数的这两项特性(原型和定义)分开了。库文件中包含了函数的编译代码,而头文件中则包含了原型。
应在首次使用函数之前提供其原型。通常的做法是把原型放到main()函数定义的前面。
- 函数原型描述了函数接口,即函数如何与程序的其他部分交互。
- 参数列表指出了何种信息将被传递给函数。
- 函数类型指出了返回值的类型。
程序员有时将函数比作一个由出入它们的信息所指定的黑盒子(black boxes)。函数原型将这种观点诠释得淋漓尽致。
函数调用
在C++中,函数调用中必须包括括号,即使没有参数。
函数定义
C++不允许将函数定义嵌套在另一个函数定义中。
每个函数定义都是独立的,所有函数的创建都是平等的。
变量名 /命名方案
必须遵循几种简单的C++命名规则。
- 在名称中只能使用字母字符、数字和下划线(_)。
- 名称的第一个字符不能是数字。
- 区分大写字符与小写字符。
- 不能将C++关键字用作名称。
- 以两个下划线打头或以下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。
- 以一个下划线开头的名称被保留给实现,用作全局标识符。
- C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。
如果想用两个或更多的单词组成一个名称,通常的做法是用下划线字符将单词分开,如my_onions;或者从第二个单词开始将每个单词的第一个字母大写,如myEyeTooth。(C程序员倾向于按C语言的方式使用下划线。)
与函数命名一样,大写在变量命名中也是一个关键问题,很多程序员可能会在变量名中加入其他的信息,即描述变量类型或内容的前缀。
常以这种方式使用的其他前缀有:str或s(表示以空字符结束的字符串)b(表示布尔值)p(表示指针)和c(表示单个字符)。
通用字符名(universal character name)
用法类似于转义序列,以\u或\U打头。\u后面是4个十六进制位,\U后面则是8个十六进制位。这些位表示的是字符的ISO 10646码点。
wchar_t
程序需要处理的字符集可能无法用一个8位的字节表示,如日文汉字系统。对于这种情况,C++的处理方式有两种。
首先,如果大型字符集是实现的基本字符集,则编译器厂商可以将char定义为一个16位的字节或更长的字节。
其次,一种实现可以同时支持一个小型基本字符集和一个较大的扩展字符集。8位char可以表示基本字符集,另一种类型wchar_t(宽字符类型)可以表示扩展字符集。wchar_t类型是一种整数类型,它有足够的空间,可以表示系统使用的最大扩展字符集。这种类型与另一种整型(底层[underlying]类型)的长度和符号属性相同。对底层类型的选择取决于实现,因此在一个系统中,它可能是unsigned short,而在另一个系统中,则可能是int。
cin 和cout将输入和输出看作是char 流,因此不适于用来处理wchar_t类型。iostream头文件的最新版本提供了作用相似的工具——wcin 和wcout,可用于处理wchar_t流。
另外,可以通过加上前级L来指示宽字符常量和宽字符串。
下面的代码将字母P的wchar_t版本存储到变量bob中,并显示单词tall的wchar_t版本:
wchar_t bob = L'P'; // a wide-character constant
wcout<< L"tall" << endl; // outputting a wide-character string
在支持两字节wchar_t的系统中,上述代码将把每个字符存储在一个两个字节的内存单元中。
C++11新增的类型:char16_t和char32_t
其中前者是无符号的,长16位,
而后者也是无符号的,但长32位。
C++11使用前缀u表示char16_t字符常量和字符串常量,如u'C'和u"be good”;
并使用前缀U表示char32_t常量,如U'R'和U"dirty rat"。
类型char16_t与\u00F6形式的通用字符名匹配,而类型char32_t与\U0000222B形式的通用字符名匹配。
前缀u和U分别指出字符字面值的类型为char16_t和char32_t:
char16_t ch1 = u'q'; // basic character in 16-bit form
char32_t ch2 = U'Wu0000222B'; // universal character name in 32-bit form
与wchar_t一样,char16_t和char32_t也都有底层类型——一种内置的整型,但底层类型可能随系统而异。
C++11支持UTF-8
C++11支持Unicode字符编码方案UTF-8。
根据编码的数字值,字符可能存储为1~4个八位组。
C++使用前缀u8来表示这种类型的字符串字面值。
C++11新增原始(Raw)字符串。
在原始字符串中,字符表示的就是自己。
可包含回车:输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中添加回车字符。
不能用"表示字符串的开头和结尾。
原始字符串用"("和")"作定界符,并使用前缀R来标识原始字符串。
cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n';
上述代码将显示如下内容:
Jim "King" Tutt uses "\n" instead of endl.
如果要在原始字符串中包含)",
该如何办呢?编译器见到第一个)"时,会不会认为字符串到此结束?会的。
但原始字符串语法允许您在表示字符串开头的“和(之间添加其他字符,这意味着表示字符串结尾的“和)之间也必须包含这些字符。因此,使用R"+*(标识原始字符串的开头时,必须使用标识原始字符串的结尾。因此,下面的语句:
cout << R"+*("(Who wouldn't?)", she whispered.)+*" 《< endl;
将显示如下内容:
"(Who wouldn't?)", she whispered.
总之,这使用"+*(
和)+*"
替代了默认定界符"(
和)"
。
自定义定界符时,在默认定界符之间添加任意数量的基本字符,但空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)除外。
可将前缀R与其他字符串前缀结合使用,以标识wchar_t等类型的原始字符串。可将R放在前面,也可将其放在后面,如Ru、UR等。
const 限定符
创建变量的通用格式:
const type name = value
注意,应在声明中对const进行初始化。
如果在声明常量时没有提供值,则该常量的值将是不确定的,且无法修改。
const比#define好:
- 首先,它能够明确指定类型。
- 其次,可以使用C++的作用域规则将定义限制在特定的函数或文件中。
- 最后,可以将const用于更复杂的类型。
结构标记
在C++中,结构标记的用法与基本类型名相同。这种变化强调的是,结构声明定义了一种新类型。
在C++中,省略struct不会出错。
struct 结构中的位字段
与C语言一样,C++也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。
字段的类型应为整型或枚举,接下来是冒号,冒号后面是一个数字,它指定了使用的位数。
可以使用没有名称的字段来提供间距。
每个成员都被称为位字段(bit field)。
下面是一个例子:
struct torgle_register
{
unsigned int SN : 4; // 4 bits for SN value
unsigned int : 4; // 4 bits unused
bool goodIn : 1; // valid input (1 bit)
bool goodTorgle : 1; // successful torgling
};
可以像通常那样初始化这些字段,还可以使用标准的结构表示法来访问位字段:
torgle _register tr = { 14, true, false );
...
if (tr.goodIn) // if statement covered in Chapter 6
...
位字段通常用在低级编程中。
一般来说,可以使用整型和附录E介绍的按位运算符来代替这种方式。
指针
对于每个指针变量,都需要一个*
。
在C++中,int*
是一种复合类型,是指向int的指针。
一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。
指向数组的指针执行过+-操作后,需要反向复原指针,以指向数组首地址,之后才能正确delete。
数组的地址
对数组取地址时,数组名也不会被解释为其地址。等等,数组名难道不被解释为数组的地址吗?不完全如此:数组名被解释为其第1个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:
short tell[10]; // tell an array of 20 bytes
cout << tell << endl; // displays &tell[0]
cout << &tell << endl; // displays address of whole array
从数字上说,这两个地址相同;
但从概念上说,&tel[0](即tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。
因此,表达式tell+1将地址值加2,而表达式&tel+1将地址加20。
换句话说,tell是一个short 指针(short),而&tel 是一个这样的指针,即指向包含10个元素的short数组(short()[10])。
您可能会问,前面有关&tel]的类型描述是如何来的呢?首先,您可以这样声明和初始化这种指针:
short (pas)[10] = &tell; // pas points to array of 10 shorts
如果省略括号,优先级规则将使得pas先与[10]结合,导致pas是一个short指针数组,它包含10个元素,因此括号是必不可少的。其次,如果要描述变量的类型,可将声明中的变量名删除。因此,pas的类型为shor()[10]。另外,由于pas 被设置为&tell,因此pas与tell等价,所以(pas)[0]为tell数组的第一个元素。
字符串第一个字符地址
在cout和多数C++表达式中,char数组名、char指针以及用括号括起的字符串常量都被解释为字符串第一个字符地址。
strcpy() || strncpy()
应使用strcpy()或strncpy(),而不是赋值运算符来将字符串赋值给数组。(strncpy中长度比数组长度少1,显式给数组最后一个元素赋值'\0')
自动存储、静态存储和动态存储
自动存储、静态存储和动态存储(根据用于分配内存的方法,管理数据内存的方式)
C++11新增 线程存储
1.自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。
自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在程序执行过程中,栈将不断地增大和缩小。
2.静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:
一种是在函数外面定义它;
另一种是在声明变量时使用关键字 static:
static double fee = 56.50;
在K&RC中,只能初始化静态数组和静态结构,而C++Release2.0(及后续版本)和ANSIC中,也可以初始化自动数组和自动结构。然而,一些您可能已经发现,有些C++实现还不支持对自动数组和自动结构的初始化。
自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。
3.动态存储
new 和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间(free store)或堆(heap)。该内存池同用于静态变量和自动变量的内存是分开的。
new和delete 让您能够在一个函数中分配内存,而在另一个函数中释放它。
因此,数据的生命周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的,但new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。
vector & array
vector vector
array array<typeName, n_elem> arr;定长数组替代品 栈
前缀格式和后缀格式
++ --
C++允许您针对类定义这些运算符,在这种情况下,用户这样定义前级函数:将值加1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。
因此,对于类而言,前缀版本的效率比后缀版本高。
总之,对于内置类型,采用哪种格式不会有差别;但对于用户定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高。
*和++同时用于指针
doubie arrI5] = (21.1, 32.8,23.4, 45.2, 37.4):
double pt = arr; // pt points to arrl0], i.e. to 21.1
++pt; // pt points to arr[1], i.e. to 32.8
将和++同时用于指针时提出了这样的同题:将什么解除引用,将什么递增。
这取决于运算符的位置和优先级。
前缀递增、前缀递减和解除引用运算符的优先级相同,以从右到左的方式进行结合。
后缀递增和后缀递减的优先级相同,但比前缀运算符的优先级高,这两个运算符以从左到右的方式进行结合。
前缀运算符的从右到左结合规则意味着++pt 的含义如下:先将++应用于pt(因为++位于的右边)。然后将应用于被递增后的pt:
double x = ++pt; // increment pointer, take the value; i.e., arr[2], or 23.4
另一方面,++pt意味着先取得pt 指向的值,然后将这个值加1:
++pt; // increment the pointed to value; i.e., change 23.4 to 24.4
在这种情况下,pt仍然指向 arr[2]。
接下来,请看下面的组合:
(pt)++;// increment pointed-to value
圆括号指出,首先对指针解除引用,得到24.4。然后,运算符++将这个值递增到25.4,pt仍然指向 amr[2]。
最后,来看看下面的组合:
x= pt++; // dereference original location, then increment pointer
后级运算符++的优先级更高,这意味着将运算符用于pt,而不是pt,因此对指针递增。然而后缀运算符意味着将对原来的地址(&arr[2])而不是递增后的新地址解除引用,因此pt++的值为arr[2],即25.4,但该语句执行完毕后,pt的值将为arr[3]的地址。
逗号运算符
C++规定,逗号表达式的值是第二部分的值。
在所有运算符中,逗号运算符的优先级是最低的。
clock()
ANSI C和C++库中有一个函数名为clock(),返回程序开始执行后所用的系统时间。
这有两个复杂的问题:
首先,clock()返回时间的单位不一定是秒;
其次,该函数的返回类型在某些系统上可能是long,在另一些系统上可能是unsigned long或其他类型。
但头文件ctime(较早的实现中为time.h)提供了这些问题的解决方案。首先,它定义了一个符号常量——CLOCKS_PER_SEC、该常量等于每秒钟包含的系统时间单位数。因此,将系统时间除以这个值,可以得到秒数。或者将秒数乘以CLOCKS PER_SEC,可以得到以系统时间单位为单位的时间。
其次,ctime将clock_t作为clock()返回类型的别名,这意味着可以将变量声明为clock_t类型、编译器将把它转换为long、unsigned int或适合系统的其他类型。
类型别名
C++为类型建立别名的方式有两种。
第一种是使用预处理器:
#define BYTE char // preprocessor replaces BYTE with char
这样,预处理器将在编译程序时用char 替换所有的BYTE,从而使BYTE成为char的别名。
第二种方法是使用C++(和C)的关键字typedef
来创建别名。例如,要将byte作为char的别名,可以这样做:
typedef char byte; // makes byte an alias for char
下面是通用格式:
typedef typeName aliasName;
换句话说,如果要将 aliasName 作为某种类型的别名,可以声明aliasName,如同将aliasName声明为这种类型的变量那样,然后在声明的前面加上关键字typedef。
注意,typedef不会创建新类型,而只是为已有的类型建立一个新名称。
字节输入
许多程序都逐字节地读取文本输入或文本文件,istream类提供了多种可完成这种工作的方法。char ch;
cin >> ch;
然而,它将忽略空格、换行符和制表符。
下面的成员函数调用读取输入中的下一个字符(而不管该字符是什么)并将其存储到ch中:
cin.get(ch); // cin =cin.get(ch);
成员函数调用cin.get()
返回下一个输入字符——包括空格、换行符和制表符,因此,可以这样使用它:
ch = cin.get();
cin.get(char)
成员函数调用通过返回转换为 false的bool值来指出已到达EOF
,
而cin.get()
成员函数调用则通过返回EOF
值来指出已到达EOF
,EOF
是在iostream中定义的。
if else if else
实际上,只是一个if else 被包含在另一个if else中。
==
许多程序员将更直观的表达式 variablevalue反转为valuevariable,以此来捕获将相等运算符误写为赋值运算符的错误。
逻辑运算符 两种表示方式
- 标识符 && || !
- C++保留字 标识符and、or、not(C语言 头文件iso646.h/C++不要求头文件)
静态数据成员
静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。
但如果静态成员是const整数类型或枚举型,则可以在类声明中初始化。
虚函数
注意:如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。这样,程序将根据对象类型而不是引用或指针的类型来选择方法版本。为基类声明一个虚析构函数也是一种惯例。
提示:如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置为非虚方法。
vbtl
调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。如果使用类声明中的第三个虚函数,程序将使用地址为数组中第三个元素的函数。
总之,使用虚函数时,在内存和执行速度方面有一定的成本,包括:
- 每个对象都将增大,增大量为存储地址的空间;
- 对于每个类,编译器都创建一个虚函数地址表(数组);
- 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。
虽然非虚函数的效率比虚函数稍高,但不具备动态联编功能。
13.4.3有关虚函数注意事项
我们已经讨论了虚函数的一些要点。
- 在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的。
- 如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。
- 如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。
虚析构
提示:通常应给基类提供一个虚析构函数,即使它并不需要析构函数。
虚构造 显式调用
警告:如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。
空指针和new
很多代码都是在new在失败时返回空指针时编写的。
为处理new 的变化,有些编译器提供了一个标记(开关),让用户选择所需的行为。
当前,C++标准提供了一种在失败时返回空指针的new,其用法如下:
int *pi=new (std::nothrow) int;
智能指针与new
模板auto_ptr 使用delete 而不是delete[],因此只能与new一起使用,而不能与new[]一起使用。但unique_ptr 有使用new[]和delete[]的版本。
警告:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr 或shared_ptr;不使用new或new[]分配存时,不能使用unique_ptr。
重载++运算符
iterator& operator++() // for ++it
{
pt = pt->p_next;
return *this;
}
iterator operator++(int) // for it++
{
iterator tmp= *this;
pt = pt->p_next;
return tmp;
}
为区分++运算符的前缀版本和后缀版本,C++将operator++作为前级版本,将operator++(int)作为后缀版本;其中的参数永远也不会被用到,所以不必指定其名称。
valarray slice 类。
slice 类对象可用作数组索引,在这种情况下,它表示的不是一个值而是一组值。
slice对象被初始化为三个整数值:起始索引、索引数和跨距。
起始索引是第一个被选中的元素的索引,
索引数指出要选择多少个元素,
跨距表示元素之间的间隔。
例如,slice(1,4,3)创建的对象表示选择4个元素,它们的索引分别是1、4、7和10。也就是说,从起始索引开始,加上跨距得到下一个元素的索引,依此类推,直到选择了4个元素。如果varint 是一个valarray
varint[slice(1,4,3)] = 10; // set selected elements to 10
这种特殊的下标指定功能让您能够使用一个一维valarray 对象来表示二维数据。
例如,假设要表示一个4行3列的数组,可以将信息存储在一个包含12个元素的valarray 对象中,然后使用一个slice(0,3,1)对象作为下标,来表示元素0、1和2,即第1行。同样,下标slice(0,4,3)表示元素0、3、6和9,即第一列。
complex & varlarray
模板类complex和valarray 支持复数和数组的数值运算。
使用临时文件
开发应用程序时,经常需要使用临时文件,这种文件的存在是短暂的,必须受程序控制。
创建临时文件、复制另一个文件的内容并删除文件其实都很简单。
首先,需要为临时文件制定一个命名方案,但如何确保每个文件都被指定了独一无二的文件名呢?cstdio中声明的tmpnam()
标准函数可以帮助您。
char* tmpnam( char* pszName );
tmpnam()
函数创建一个临时文件名,将它放在pszName
指向的C风格字符串中。常量L_tmpnam
和TMP_MAX
(二者都是在cstdio中定义的)限制了文件名包含的字符数以及在确保当前目录中不生成重复文件名的情况下tmpnam()
可被调用的最多次数。下面是生成10个临时文件名的代码。
#include <cstdio>
#include <iostream>
int main()
{
using namespace std;
cout << "This system can generate up to " << TMP_MAX
<<"temporary names of up to " << L_tmpnam
<<" characters.\n";
char pszName [ L_tmpnam ]= {'\0'};
cout << "Here are ten names:\n";
for( int i=0; 10 >i; i++ )
tmpnam( pszName );
cout << pszName << endl;
return 0;
}
更具体地说,使用tmpnam()可以生成TMPNAM个不同的文件名,其中每个文件名包含的字符不超过L_tmpnam个。生成什么样的文件名取决于实现,您可以运行该程序,来看看编译器给您生成的文件名。
推荐书籍
- 重构:改善既有代码的设计(第2版)
- C和指针
- C专家编程
- C陷阱与缺陷
- C++沉思录
- “笨办法”学Python3
- Python 核心编程(第3版)
- 代码整洁之道
- 代码整洁之道:程序员的职业素养
- UNIX环境高级编程(第3版)
- UNIX网络编程 卷1:套接字联网API(第3版)
- UNIX网络编程卷2:进程间通信(第2版)
- Linux/UNIX系统编程手册(上、下册)
- UNIX操作系统设计
- 编程珠玑(第2版·修订版)
- 编程珠玑(续)(修订版)
- 计算机科学概论(第12版)
- 领域驱动设计:软件核心复杂性应对之道(修订版)
- Google 软件测试之道
- SQL入门经典(第5版)
本文来自博客园,作者:Theseus‘Ship,转载请注明原文链接:https://www.cnblogs.com/yongchao/p/17370028.html