C++ Primer(第4版)-第1部分:基本语言
第1章 快速入门
每个C++程序都包含一个或多个函数,而且必须有一个命名为main。
main函数是唯一被操作系统显式调用的函数,main函数的返回值必须是int或者void(无返回值)
int main(int argc, char *argv[])
{
return 0;
}函数体是函数定义的最后部分,是以花括号开始并以花括号结束的语句块;
注释
C++ 中有单行注释和成对注释两种类型的注释。单行注释以双斜线(//)开头,行中处于双斜线右边的内容是注释,被编译器忽略。 另一种定界符,注释对(/ /),是从 C 语言继承过来的。这种注释以“/”开头,以“/”结尾。编译器把落入注释对“/*/”之间的内容作为注释。
注释的风格是在注释的每一行以星号开始,指明整个范围是多行注释的一部分;
/
comment pairs / / cannot nest.
“cannot nest” is considered source code,
as is the rest of the program
/
注释不会增加可执行程序的大小,编译器会忽略所有注释;推荐把确定函数边界的花括号自成一行,且缩进输入或输出表达式从而使操作符排列整齐;
头文件
标准库的头文件用尖括号< >括起来,非标准库的头文件用双引号“ ”括起来;输出:
cout:输入内存缓冲区再到输出设备
clog:输入内存缓冲区但不到输出设备
cerr:直接输出到输出设备控制结构:if、for、while
第2章 变量和基本类型
void是一种特殊的基本内置类型,它没有对应值,一般用作无返回值函数的返回类型。
基本内置类型
一个字节(byte)=8位(bit)
一个字(word)=4个字节=32位
类型 | 含义 | 最小存储空间(字节) | 说明 |
---|---|---|---|
bool | 布尔型 | 1 | |
char | 字符型 | 1 | |
wchar_t | 宽字符型 | 2 | 通常用来表示中文、日文等字符 |
short | 短整型 | 2 | |
int | 整型 | 2 | 64位win7上是4 |
long | 长整型 | 4 | |
float | 单精度 | 4 | 精确到小数点后6位有效数字 |
double | 双精度 | 8 | 精确到小数点后10位有效数字 |
long double | 扩展精度浮点型 | 12或16 | 保留小数点后10位有效数字,64位win7上是8 |
signed可以表示正数和负数(包括0),unsigned只能表示大于或等于0的数
字面值常量
像42这样的值,称为字面值常量
(1)整形字面值
以0(零)开头的字面值整数常量表示八进制,以0x或0X开头的表示十六进制
20 // decimal
024 // octal
0x14 // hexadecimal
在数值后面加小写l或大写L表示long类型,推荐使用大写L,而不是小写l,以免与1混淆。
在数值后面加u 或 U表示unsigned类型,同时加UL表示unsigned long型。
128u //unsigned
1024UL // unsigned long
1L // long
8Lu // unsigned long
(2)浮点字面值
默认浮点字面值常量是double型,在数值后面加上F或f表示float型
在数值后面加上L或l表示扩展精度long double
(3)char字面值
在字符字面值前加上L得到wchar_t型字面值,如L’a’
(3)字符串字面值
为了兼容 C 语言,C++ 中所有的字符串字面值都由编译器自动在末尾添加一个空字符。”A” 表示包含字母 A 和空字符两个字符的字符串。
宽字符串字面值是一串常量宽字符,同样以一个宽空字符结束。L”a wide string literal”一般情况下,给变量赋值,如果该值超过变量的取值范围,编译器会将该值对变量的可能取值数目求模,得到所得值。例如:
(1)336赋值给一个unsigned char,则实际所得值是80,因为unsigned char取值范围是0-256,336%256=80,80正好在0-256之间。
(2)336赋值给一个char,则实际所得值是80,因为char取值范围是-128-127,有256个取值,336%256=80,80正好在-128-127之间。
(3)384赋值给一个char,则实际所得值是-128,因为char取值范围是-128-127,有256个取值,384%256=128,128不在-128-127之间,128>127,所以取值应该是-128到0之间,正好是-128。
(4)128赋值给一个char,则实际所得值是-128,因为char取值范围是-128-127,有256个取值,128%256=128,128不在-128-127之间,128>127,所以取值应该是-128到0之间,正好是-128。在float和double之间选择:尽量使用double,精度更高,而计算的代价几乎可以忽略不计。
在行的末尾加反斜杠’\’,可将此行和下一行当作同一行处理。
std::string s1 = “Hello \
world “直接初始化和复制初始化
int iVal(1024);//直接初始化
int iVal2=1024;//复制初始化
直接初始化语法更灵活,且效率更高。但是对于内置类型来说,两者几乎没有差别。内置类型(例如:int, long, double等)的自动初始化
如果内置类型没有初始化,例如 int iV1;
如果在函数体外定义,iV1将自动初始化为0;
如果在函数体内定义,iV1不进行自动初始化。
所以,在函数体内定义的内置类型变量,一定要手动初始化。例如:int iV1 = 0 ;
例如:
std::string global_str;
int global_int;
int main()
{
int local_int;
std::string local_str;
…
return 0;
}
其中,global_str和local_str初始值都是空字符串,global_int初始值是0,local_int没有初始值。左值和右值
左值就是变量的地址,或一个代表“对象在内存中位置”的表达式
右值就是变量的值。
一般的,变量名出现在=的左边,就是左值。出现在=的右边的变量名或字面常量就是一个右值。声明(declaration):表明变量的类型和名字。程序中变量可以声明多次。
定义(definition):表明变量的类型和名字,同时分配存储空间,还可以为变量指定初始值。在一个程序中,同一个变量有且只有一个定义。
定义也是声明。可以通过extern关键字声明变量名而不定义它。
extern int i; // declares but does not define i
int i; // declares and defines i
如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern:
extern double pi = 3.1416; // definition,因为有初始化式,分配了存储空间,就是定义(这个语句只能出现在函数外,即使一个全局变量)const限定符修饰的常量。
常量在定义时必须初始化,而且一旦定义后就不能修改。
在全局作用域里定义非const变量,在整个程序中都可以访问(因为全局的非const变量默认就是extern)。
在全局作用域里定义的const常量,如果不在前面加extern,那么该常量就只在定义该对象的文件(指的是可编译的单元,如.cpp,.cxx,.cc等,而不是头文件.h)中可以访问。要使const变量能够在其他文件中访问,必须显示将它指定为extern。
//file_1.cpp
int counter;//definition
//file_2.cpp
extern int counter;//use counter from file_1.cpp
++counter;
//file_1.cpp
//defines and initializes a const that is accessible to other files
extern const int bufSize=24;
//file_2.cpp
extern int bufSize;//use bufSize from file_1
引用
引用初始化后,不能将该引用绑定到另一个对象。
const引用,通常用作函数参数,既能提高效率,同时又避免修改原值。
非const引用只能绑定到与该引用同类型的对象。const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
char c1 = 1;
int i1 = 0;
int &ref1 = i1;
int &ref2 = c1;//这句无法通过编译,无法从char转换为int&
const int &ref3 = i1;
const int &ref4 = c1;typedef 通常被用于以下三种目的:
(1)为了隐藏特定类型的实现,强调使用类型的目的。
(2)简化复杂的类型定义,使其更易理解。
(3)允许一种类型用于多个目的,同时使得每次使用该类型的目的明确。枚举类型enum
枚举类型中的成员,如果没有显示初始化,那么它将默认比前一个成员大1,依此类推。class和struct的区别:
(1)默认情况下(不显式注明类型时),struct的成员都是public,而class的成员都是private。
(2)默认情况下,从struct的继承的成员是public,而从class继承的的成员都是private。头文件
头文件用于声明而不是用于定义,所以在头文件中不应该有定义语句(因为定义只可以出现一次,而声明可以出现多次。而头文件可能会被多个源文件包含),如:
double fica_rate;//没有extern,是定义
extern int ival = 10;//虽然有extern,是定义
上述规则有3个例外,头文件可以定义类,const 对象(值在编译时就已经知道的),inline函数。
如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义。例如:const int ival = sqrt(ival2);预处理器变量,避免多重包含:
#ifndef XIONG_H
#define XIONG_H
……
#endif
XIONG_H就是预处理器变量,该名字在程序中必须是唯一的。
第3章 标准库类型
:: 操作符,该操作符是作用域操作符(第 1.2.2 节)。它的含义是右操作数的名字可以在左操作数的作用域中找到。
例如,std::cin 表示cin可以在命名空间std中找到。using namespace std;//使用命名空间std
string
头文件:
#include
using std::string;//using声明,string是可见的
//using namespace std;//using指示,无法控制哪些名字可见,该空间的所有名字都是可见的
//注意:using声明比using指示更好。一般的,需要使用哪个名字,就使用哪个名字的using声明,别偷懒使用using指示。string初始化:
string s1;
string s2(s1);
string s3(“value”);
string s4(n,’c’);//将s4初始化为字符’c’的n个副本getline读取整行文本,但会丢弃换行符,所以使用getline读取的string不含换行符。
string 对象的操作:
s.empty():判断s是否是空字符串,注意:不是清空字符串
s.size():返回s中的字符个数,返回值是string::size_type类型。不要把size的返回值赋给一个int型变量,因为size_type是unsigned型,它能存储的string的长度最大值是int所能存储的2倍。
s[n]:返回s中位置为n的字符,n从0开始计数,最大值为s.size()-1。如果超出范围,则会引起溢出错误。s[n]是左值,可以改变,如s[1] = ‘x’;
s3=s1+s2;//连接字符串。注意:+左右操作数必须至少有一个是string类型的(不能都是字符串字面值),如s3 = “123” + “456”;是错误的。s3 = s1 + “aaa”; 是对的。
s1=s2;//把s1的内容替换为s2的副本
s1==s2;//比较s1和s2c标准库头文件命名形式:name.h
c++标准库头文件命名形式:cname,少了.h,前面多加c。使用vector容器的头文件:
#include
using std::vector;vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector
和 vector 都是数据类型。 C++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件。因为一般的类都有!=操作符,而不一定有<操作符。
for(int i = 0; i != 100; ++i)所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。推荐:使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的 vector 类型也是这样。
每种容器类型都定义了自己的迭代器类型,如 vector:vector
::iterator iter;
该语句定义了一个名为 iter 的变量,它的数据类型是 vector定义的 iterator 类型。每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。 const_iterator
每种容器类型还定义了一种名为 const_iterator 的类型,该类型只能用于读取容器内元素,但不能改变其值。
for (vector::const_iterator iter = text.begin(); iter != text.end(); ++iter)
cout << *iter << endl; // print each element in text使用bitset的头文件
#include
using std::bitset;
第4章 数组和指针
与vector相比,数组的显著缺陷:
数组长度是固定的,也不提供获取容量大小的size操作。
数组一经定义,就不允许再添加新元素。如果必须在数组中添加新元素,程序员就必须自己管理内存:要求系统重新分配一个新的内存空间用于存放更大的数组,然后把原数组的所有元素复制到新分配的内存空间中。
与vector相比,使用数组的程序容易出错,难以调试。数组的初始化
• 在函数体外定义的内置数组,其元素均初始化为 0。
• 在函数体内定义的内置数组,其元素无初始化。
• 如果其元素为类类型,不管数组在哪里定义,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。char ca3[] = “1234”;实际上ca3的维数是5,最后一个字符是‘\0’。
例如:int nL = sizeof ca3; 也可以这么写: int nL = sizeof(ca3); nL =5;sizeof
(1) sizeof后面接某类型名时,必须加圆括号,例如:
int nL = sizeof(char); 获取char类型的大小,结果是1。
注意:sizof后面接非内置类型名时,例如某自定义的类C,返回值并不是该类C的各项成员变量独立大小之和,具体视情况而定。
(2) sizeof后面接表达式时,可以加圆括号,也可以不加。例如:
char c1[] = “123”;
int nL = sizeof(c1); 或 int nL = sizeof c1; 都是正确的,结果是 nL=4;指向const的指针。
const double cptr; cptr是一个指向const double对象的指针。指针指向的对象是const,不能修改,如cptr=10是错的。
指向 const 的指针常用作函数的形参。将形参定义为指向 const 的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。const指针
int errNumb = 0;
int *const curErr = &errNumb; //curErr 是指向 int 型对象的const 指针,const指针在定义是必须初始化。指向 const对象的 const指针
const double pi = 3.14159;
const double *const pi_ptr = π //pi_ptr 首先是一个 const 指针,指向 double 类型的 const 对象。区分“指向const的指针”和“const指针”的方法:看const关键字在‘’的前面还是后面。
const在前,是“指向const的指针”。如:const double cptr;
const在后,是“const指针”。如:int *const curErr = &errNumb;操纵C风格字符串的标准库函数
strlen(s) :返回 s 的长度,不包括字符串结束符 null,如 int nl = strlen(“1234”); //nl = 4
strcmp(s1, s2) : 比较两个字符串 s1 和 s2 是否相同。若 s1 与 s2 相等,返回 0;若 s1 大于 s2,返回正数;若 s1 小于 s2,则返回负数
strcat(s1, s2) :将字符串 s2 连接到 s1 后,并返回 s1
strcpy(s1, s2) :将 s2 复制给 s1,并返回 s1
strncat(s1, s2,n) :将 s2 的前 n 个字符连接到 s1 后面,并返回 s1
strncpy(s1, s2, n) :将 s2 的前 n 个字符复制给 s1,并返回 s1
调用者必须确保目标字符串s1有足够的大小。如果必须使用 C 风格字符串,则使用标准库函数 strncat 和 strncpy 比 strcat 和 strcpy 函数更安全。
strcpy 和 strncpy 的差别在哪里,各自的优缺点是什么?
差别:strcpy复制整个的字符串,strncpy只复制指定字符串中指定数目的字符。
各自优缺点:strcpy简单,但不安全;strncpy稍微复杂,需要指定复制的字符的数目,但更安全。动态数组
(1)创建:
int pia = new int[10];
(2)初始化
string psa = new string[10];//数组是 string 类型,分配了保存对象的内存空间后,将调用 string 类型的默认构造函数依次初始化数组中的每个元素。
int pia = new int[10];//数组成员是int型,属于内置类型,此处只分配了10个int对象的空间,但是没有初始化数组元素。
int pia2 = new int[10]();//圆括号要求编译器对数组做值初始化,在本例中即把数组元素都设置为 0。
(3)允许动态分配数组
size_t n = get_size(); // get_size returns number of elements needed
int* p = new int[n];
(4)动态空间的释放
delete[] pia;混合使用标准库类 string 和 C 风格字符串
• 可以使用 C 风格字符串对 string 对象进行初始化或赋值。
• string 类型的加法操作需要两个操作数,可以使用 C 风格字符串作为其中的一个操作数,也允许将 C 风格字符串用作复合赋值操作的右操作数。多维数组
因为多维数组其实就是数组的数组,所以由多维数组转换而成的指针类型应是指向第一个内层数组的指针。定义指向数组的指针与定义数组本身类似:首先声明元素类型,后接(数组)变量名字和维数。窍门在于(数组)变量的名字其实是指针,因此需在标识符前加上 。如果从内向外阅读 ip 的声明,则可理解为:ip 是 int[4] 类型——即 ip 是一个指向含有 4 个元素的数组的指针。
在下面的声明中,圆括号是必不可少的:
int ip[4]; // array of pointers to int,int型指针数组,含有4个int型指针
int (ip)[4]; // pointer to an array of 4 ints,指向数组的指针,该数组是含有4个int元素的数组
第5章 表达式
如果两个操作数为正,除法(/)和求模(%)操作的结果也是正数(或零);
如果两个操作数都是负数,除法操作的结果为正数(或零),而求模操作的结果则为负数(或零);
如果只有一个操作数为负数,这两种操作的结果取决于机器;求模结果的符号也取决于机器,而除法操作的值则是负数(或零)。溢出:表达式的求值结果超出了它的类型的表示范围,例如16位的int型变量的取值范围是-32768~32767,int i=32767+10; 就会导致溢出。
对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用 unsigned 整型操作数。
左移操作符(<<)在右边插入 0 以补充空位。对于右移操作符(>>),如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值,如何选择需依据具体的实现而定。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。sizeof 操作符
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为 size_t 。
因为 sizeof 返回整个数组在内存中的存储长度,所以用 sizeof 数组的结果除以 sizeof 其元素类型的结果,即可求出数组元素的个数:
假设A是一个int型数组,那么A的元素个数可用如下方法求得:int nCount = sizeof(A) / sizeof(int) ;C++ 定义了算术类型之间的内置转换以尽可能防止精度损失。通常,如果表达式的操作数分别为整型和浮点型,则整型的操作数被转换为浮点型。
显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。
第7章 函数
局部变量
在函数体内定义的变量只在该函数中才可以访问。这种变量称为局部变量,它们相对于定义它们的函数而言是“局部”的,其名字只能在该函数的作用域中可见。这种变量只在函数运行时存在。局部变量(内置类型)需要手动初始化。函数返回值
函数必须指定返回类型(包括void类型), 在定义或声明函数时,没有显式指定返回类型是不合法的。
函数的返回类型包括:内置类型(如 int 或者 double)、类类型或复合类型(如 int& 或 string*),void 类型。
函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针。函数形参表
函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表()或含有单个关键字 void 的形参表来表示。如:
void process() { / … / } // implicit void parameter list
void process(void){ / … / } // equivalent declaration
参数表中的参数不能同名。类似地,函数体中的变量也不能与函数的任意参数同名。形参和实参
形参是在函数定义的形参表中定义的,是一个变量,其作用域是整个函数。
实参出现在函数调用中,是一个表达式,进行函数调用时,用传递给函数的实参对形参进行初始化。非引用形参和引用形参
普通的非引用类型的参数通过复制对应的实参实现初始化,形参不会修改实参的值。
如果形参为引用类型,则它是实参的别名,不会复制实参,形参的改变就会修改实参。指针形参
如果函数形参是非 const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值。如:void reset(int ip)
如果要保护指针指向的值不被修改,则形参需定义为指向 const 对象的指针:
void use_ptr(const int p)
{
// use_ptr may read but not write to *p
}const 形参
在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参。复制实参的局限性
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
• 需要在函数中修改实参的值。
• 需要以大型对象作为实参传递。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过大。
• 没有办法实现对象的复制。
对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。引用形参
与所有引用一样,引用形参就是实参的别名,而并非副本。用途:
(1)利用引用形参让函数修改实参的值。
(2)引用形参的另一种用法是向主调函数返回额外的结果,相当于函数有多个返回值。(函数最多只能有一个返回值)
(3)函数的参数是大型对象时,为了避免复制实参以提高效率,或者使用无法复制的类类型作为形参时,也应该将形参定义为引用类型,最好使用const 引用。利用 const 引用避免复制,能提高效率,但又不会更改实参的值。
形参是const引用,实参既可以是const,也可以是非const。而如果形参是非const,则实参不能是const的。所以非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。vector 和其他容器类型的形参
为了避免复制 vector (容器类型不可复制),应将vector类型形参声明为引用类型。
推荐:通过传递指向容器中需要处理的元素的迭代器来传递容器。例如:
void print(vector::const_iterator beg, vector ::const_iterator end) {} 数组形参
将数组形参直接定义为指针要比使用数组语法定义更好。这样就明确地表示,函数操纵的是指向数组元素的指针,而不是数组本身。
void printValues(int) { / … */ }
和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。
void printValues(int (&arr)[10]) { / … / }
引用的数组和数组的引用
int &arr[10]:表示的是int引用类型的数组,该数组有10个int引用类型的元素。
int (&arr)[10]:表示的是数组的引用,该数组有10个int类型的元素。传递给函数的数组的处理
有三种常见的编程技巧确保函数的操作不超出数组实参的边界。
(1)在数组中放置结束标记
在数组本身放置一个标记来检测数组的结束。C 风格字符串就是采用这种方法的一个例子,它是一种字符数组,并且以空字符 null 作为结束的标记。
(2)使用标准库规范
传递指向数组第一个和最后一个元素的下一个位置的指针。
void printValues(const int beg, const int end){}
(3)显式传递表示数组大小的形参
将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。函数返回值
返回非引用类型:返回值既可以是局部对象,也可以是求解表达式的结果。
返回引用:千万不要返回局部对象的引用。当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。
不要返回指向局部对象的指针。main()函数的返回值
一般情况下,返回类型不是 void 的函数必须返回一个值,但此规则有一个例外情况:允许主函数 main 没有返回值就可结束。如果程序控制执行到主函数 main 的最后
一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。函数声明
正如变量必须先声明后使用一样,函数也必须在被调用之前先声明。与变量的定义类似,函数的声明也可以和函数的定义分离;一个函数只能定义一次,但是可声明多次。默认实参
默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。
调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。自动对象
默认情况下,局部变量的生命期局限于所在函数的每次执行期间。只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。静态局部对象
静态局部对象,位于函数的作用域内,生存期跟程序的生存期一样,但是只能在定义它的函数中使用。
static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。内联函数
内联机制适用于优化小的、只有几行的而且经常被调用的函数。
内联函数应该在头文件中定义。可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见类的成员函数
类的成员函数必须在类内声明,可以在类内也可在类外定义。编译器隐式地将在类内定义的成员函数当作内联函数构造函数和初始化列表
构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开始。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。
Sales_item(): units_sold(0), revenue(0.0) { }
一般的,只对内置类型的成员在初始化列表中初始化。除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。合成的默认构造函数
如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。它将依据如同变量初始化的规则初始化类中所有成员:
对于类类型的成员,会调用该成员所属类自身的默认构造函数实现初始化。
内置类型成员的初始化依赖于类对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。如果对象在局部作用域中定义,则这些成员没有初始化。除了给它们赋值之外,出于其他任何目的对未初始化成员的使用都没有定义。
仅包含类类型成员的类,可以使用默认构造函数。
含有内置类型或复合类型成员(如指针)的类,应该定义自己的默认构造函数来初始化这些成员。函数重载和重复声明的区别
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的。指向函数的指针
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。
bool (pf)(const string &, const string &);
这个语句将 pf 声明为指向函数的指针,它所指向的函数带有两个 const string& 类型的形参和 bool 类型的返回值。
pf 两侧的圆括号是必需的,否则:pf就是一个返回bool* 指针的函数。用 typedef 简化函数指针的定义
函数指针类型相当地冗长。使用 typedef 为指针类型定义同义词,可将函数指针的使用大大简化:
typedef bool (*cmpFcn)(const string &, const string &);
在要使用这种函数指针类型时,只需直接使用 cmpFcn 即可,不必每次都把整个类型声明全部写出来。函数指针的初始化和赋值
函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。
将函数指针初始化为 0,表示该指针不指向任何函数。
指向不同函数类型的指针之间不存在转换:函数指针形参
函数的形参可以是指向函数的指针。返回指向函数的指针
函数可以返回指向函数的指针,但是不能返回函数。
int (ff(int)) (int, int);
ff是一个函数,它带有一个int型参数,它返回一个函数指针int ()(int, int),该函数指针指向的函数带有两个分别是int和int型的形参,并返回一个int型返回值。
等价于:
typedef int (PF)(int*, int);//PF是一个函数指针
PF ff(int);//ff是一个函数,返回值是指向函数的指针
第8章 标准IO库
顺序容器和关联容器
顺序容器内的元素按其位置存储和访问。关联容器,其元素按键(key)排序。
IO对象不可复制或赋值
标准库类型不允许做复制或赋值操作。所以:
(1)由于流对象不能复制,因此不能存储在 vector(或其他)容器中(即不存在存储流对象的 vector 或其他容器)。
(2)形参或返回类型也不能为流类型。如果需要传递或返回 IO 对象,则必须传递或返回指向该对象的指针或引用: ofstream &print(ofstream&);iostream定义读写控制窗口的类型,fstream定义读写已命名文件的类型,而sstream所定义的类型则用于读写存储在内存中的string对象;
只有支持赋值的元素类型可以存储在vector或其他容器类型里,因此不存在存储流对象的vector或其他容器;
形参或返回类型也不能为流类型。如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用;
为了确保用户看到程序实际上处理的所有输出,最好的方法是保证所有的输出操作都显式地调用了flush或endl;
当输入流和输出流绑定在一起时,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区;
如果在调用tie函数时传递实参0,则打破该流上已存在的捆绑;
关闭流并不能改变流对象的内部状态;