Chapter 1. Getting Started
1.1.4.读入未知数目的输入
int value;
while (std::cin >> value){…}
std::cin >> value先从标准输入中读取一个数保存在value中,输入操作符返回其左操作数。while中条件测试std::cin,即测试流的状态。如果流是有效的,那么测试成功。遇到文件结束符或遇到无效输入时,如读取了一个不是整数的值,则istream对象是无效的。windows下Ctrl-Z来输入文件结束符,unix下Ctrl-D
Charpter 2. 变量和基本类型
2.1.1. 整型
3.整型的赋值
8位的unisinged char,其取值范围从0到255。如果赋值超过这个范围的值,那么编译器将会取该值对256求其模后的值。(336存储到unsigned char中,实际赋值位80,336%256 = 80)
对于unsinged char类型来说,负数总是超出其取值范围。同样取模,所以-1存储到unsinged char中,实际值是255)
当将超过取值范围的值覆给singed类型时,由编译器决定实际的值。在实际操作中,很多编译器处理signed类型的方式和unsigned类型类似。
2.1.2.浮点型
一般flaot类型用32位表示,double用64位,long double用三个或四个字(96或128位)来表示。类型的取值范围决定了浮点数所含的有效数字位数。float型只能保证6位有效数字,而double型至少可以保证10位有效数字。
2.2 字符面值
std::cou/
t << “Hi” << st/
d::endl;
等价于
std::cout << “Hi” << std::endl;
可以使用这个特性来编写长字符串字面值:
std::cout << “a multi-line /
string literal /
using a backslash”
<< std::endl;
反斜线符号必须是该行的尾字符(不允许其后面由注释或空格),同样,后继行行首的任何空格和制表符都是字符串字面值的一部分。
2.2.3 定义对象
1.初始化
int val(1024); //直接初始化
int val = 1024; //复制初始化
C++中初始化和赋值是两种不同的操作,初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。
程序中变量可以声明多次,但只能定义一次。
extern double pi = 3.1415; // definition
只有当extern声明位于函数外部时,才可以含有初始式。
extern int j = 5; // ok
int main(){
extern int j = 5; // error
return 0;
}
附:
文件中定义的全局变量的可见性扩展到整个程序是在链接完成之后,而在编译阶段,他们的可见性仍局限于各自的文件。
----------------------------------------------
//A.cpp
int i;
void main() { }
//B.cpp
int i;
对A和B分别编译,都可以正常通过编译,但是进行链接的时候,却出现了错误
-----------------------------------------------
//A.cpp
void main() { i = 100; //试图使用B中定义的全局变量 }
//B.cpp
int i;
编译错误
----------------------------------------------
//A.cpp
extern int i;
void main() { i = 100; //试图使用B中定义的全局变量 }
//B.cpp
int i;
extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
----------------------------------------------
2.4 Const常量
因为常量在定义后就不能被修改,所以定义时必须初始化。
const对象默认为文件的局部变量,但通过指定const变量为extern,就可以在整个程序中访问const对象。非const变量默认为extern。要使const变量能够在其他的文件中访问,必须显式地指定它为extern:
// file_1.cc
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cc
extern const int bufSize; // uses bufSize from file_1
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...
2.5 引用
引用一定要初始化为同类型对象:
int ival = 1024;
int &refVal = ival; // ok: refVal refers to ival
int &refVal2; // error: a reference must be initialized
int &refVal3 = 10; // error: initializer must be an object
非const引用只能绑定到与该引用同类型的对象。Const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
const引用是指向const对象的引用:
const int ival = 1024;
const int &refval = ival; // ok
int &ref2 = ival; // error: non const reference to a const object,因为ref2是非const的引用,可以通过ref2对所引用的对象作修改,因此不合法。
const引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量:
int i = 42;
// legal for const references only
const int &r = 42;
const int &r2 = r + i;
将引用邦定到不同类型时:
double dval = 3.14;
const int &ri = dval;
编译器会转换代码如下:
int temp = dval;
const int &ri = temp;
因此,非const引用只能绑定到与该引用同类型的对象。Const引用则可以邦定到不同但相关的类型的对象或邦定到右值。
2.7 枚举
每个enum都定义一种唯一的类型
enum Points {point2d = 2, point2w, point3d = 3, pont3w};
Points pt3d = point3d; //ok
Points pt2w =3; // error
pt2w = polygon; // error
pt2w = pt3d; // ok
2.9 编写自己的头文件
头文件用于声明而不是用于定义。因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。有三个例外:头文件可以定义类、值在编译时就已知道的const对象和inline函数。
因为const对象默认为定义它的文件的局部变量,所以把它们的定义放在头文件是合法的。当我们在头文件定义了const变量后,每个包含该头文件的源文件都有了自己的const变量,其名称和值都一样。如果const变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该const变量应该在一个源文件中定义并初始化。应在头文件中为它添加extern声明,以使其能被多个文件共享。
Chapter 3. Library Type
3.2.2 .String对象的读写
string s;
cin >> s;
l 读取并忽略开头所有的空白字符(如空格,换行符,制表符)。
l 读取字符直至再次遇到空白字符,读取终止。
用getline读取整行文本。只要getline遇到换行符,即便它是输入的第一个字符,getline也将停止读入并返回。由于getline函数返回时丢弃换行符,换行符将不会存储在string对象中。
用+号将string对象和字符串字面值进行混合连接操作时,+操作符的左右操作数必须至少有一个是string类型。
3.2.4 .String对象中字符的处理
对string对象中的单个字符进行处理,其各种字符操作函数在cctype头文件中定义。
3.4.迭代器简介
不要把const_iterator对象与const的iteratror对象混淆起来。声明一个const迭代器时,必须初始化迭代器。一旦被初始化后,就不能改变它的值。
Chapter 4. Arrays and Pointers
4.1.1 数组的定义和初始化
数组的维数必须用值大于等于1的常量表达式定义。非const变量以及要道运行阶段才知道其值的const变量都不能用于定义数组的维数。
const unsigned buf_size = 512, max_files = 20;
int staff_size = 27; // nonconst
const unsigned sz = get_size(); // const value not known until run time
char input_buffer[buf_size]; // ok: const variable
string fileTable[max_files + 1]; // ok: constant expression
double salaries[staff_size]; // error: non const variable
int test_scores[get_size()]; // error: non const expression
int vals[sz]; // error: size not known until run time
虽然staff_size是用字面值常量进行初始化,但staff_size本身是一个非const对象,只有在运行时才能获得它的值。
定义数组时,如果没有显示提供元素初值,则数组元素会像普通变量一样初始化:
l 在函数体外定义的内置数组,其元素均初始化为0;
l 在函数体内定义的内置数组,其元素无初始化;
l 不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认人构造函数,则必须为该数组的元素提供显式初始化。
4.2.2 .指针的定义和初始化
对指针进行初始化或赋值只能使用以下四种类型的值:
l 0值常量表达式(编译时可获得0值的整型const对象或字面值常量0)
l 类型匹配的对象地址
l 另一对象之后的下一地址
l 同类型的另一个有效指针
4.2.4 .使用指针访问数组元素
int *p = &ia[2]; // ok: p points to the element indexed by
int j = p[1]; // ok: p[1] equivalent to *(p+1), p[1] is the same element as ia[3]
int k = p[-2]; // ok: p[-2] is the same element as ia[0]
4.2.5 指针和const限定符
4.指针和typedef
typedef string *pstring;
const pstring cstr; != const string *cstr;
== string *const cstr; == pstring const cstr;
4.3. C风格字符串
3.永远不要忘记字符串结束符null
char ca[] = {‘C’, ‘+’, ‘+’}; // not null-terminated
cout << strlen(ca) << endl; // disaster: ca isn’t null-terminated
4.3.1 .创建动态数组
2. 初始化动态分配的数组
动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化:
string *psa = new string[10]; // array of 10 empty strings
int *pia = new int[10]; // array of 10 uninitialized ints
int *pia2 = new int[10]; // array of 10 initialized ints
3. Const对象的动态数组(实际用处不大)
const int *pci_bad = new const int[100]; // error: uninitialized const array
const int *pci_ok = new const int[100](); // ok: value-initialized const array
const string *pcs = new const string[100]; //ok: array of 100 empty strings
4.允许动态分配空数组
char arr[0]; // error: cannot define zero-length array
char *cp = new char[0]; // ok: but cp can’t be dereferenced
混合使用标准库类string和C风格字符串
string st2(“Hello World”);
char *str = st2; // compile-time type error
char *str = st2.c_str(); // almost ok, but not quite
const char *str = st2.c_str() // ok
Chapter 5. Expressions
==Short类型是16位,最大能表示的数是32767(0111,1111,1111,1111),再大就会溢出:
short short_value = 32767;
short ival = 1;
// this calculation overflows
short_value += ival;
cout << "short_value: " << short_value << endl;
其结果是-32768(1000,0000,0000,0000),对除符号位的000,0000,0000,0000数取反加1得32768(1000,0000,0000,0000)
附:
在机器世界里:
正数的最高位是符号位0,负数的最高位是符号位1。
对于正数:反码==补码==原码。
对于负数:反码==除符号位以外的各位取反。
补码==反码+1.
原码==补码-1后的反码==补码的反码+1。
可以轻易发现如下规律:
自然计算 :a-b==c.
计算机计算:a-b==a+b的补码==d.
c的补码是d.
通过此法,可以把减法运算转换为加法运算。
所以补码的设计目的是:
1.使符号位能与有效值部分一起参加运算,从而简化运算规则.
2.减运算转换为加运算,进一步简化计算机中运算器的线路设计
补码的表示范围
将原数用二进制表示,最高位是符号位(0表示正数,1表示负数),就是原码表示法。在计算机中的数据都是以补码存放的,只有这样才能减轻CPU。提到补码,就得先说反码,计算机中是这样规定反码的,如果是正数,则按原码形式不变,如127仍为0111,1111;而如果为负数则,第一位为1,其他各位取反(即0变为1,1变为0),如原码-127(1111,1111),表示为1000,0000。补码同上,如果是正数,则按原码形式不变,如127仍为0111,1111;如果为负数,则除第一位为1外,其他各位取反加1,如-127,先取反为1000,0000,然后加1,为1000,0001。但1000,0000比较特殊,用它来表示-128,由此我们可知用补码表示的、带符号的八位二进制数,可表示的整数范围是-128~+127。因此可以得出结论:“正数的反码、补码均与原码相同;负数的反码是原码各位取反;补码则是反码+1”。
==求模操作符%的操作数只能为整型。
int ival = 42;
double dval = 3.14;
ival % 12; // ok: returns 6
ival % dval; // error: floating point operand
==如果两个操作数中只有一个是负数,操作结果取决于机器。
21 % 6; // ok: result is 3
21 % 7; // ok: result is 0
-21 % -8; // ok: result is -5
21 % -5; // machine-dependent: result is 1 or -4
21 / 6; // ok: result is 3
21 / 7; // ok: result is 3
-21 / -8; // ok: result is 2
21 / -5; // machine-dependent: result -4 or -5
==逻辑操作符中的短路求值:先计算左操作数,再计算右操作数。在仅靠左操作数无法确定逻辑表达式值结果的时候,才计算右操作数。
==不要串接使用关系操作符:
// oops! this condition does not determine if the 3 values are unequal
if (i < j < k) { /* ... */ }
只要k>1,上述表达式就为真。应当写成:
if (i < j && j < k) { /* ... */ }
==位操作符操作的整数的类型可以是有符号的也可以是无符号的。如果操作数是负数,则位操作如何处理其操作数的符号位依赖于机器。为了移植性,强烈建议用unsigned整型操作数。
==移位操作的右操作数不能是负数并且严格小于左操作数位数的值。否则,操作的效果未定义。
==Bitset优于整型数据的低级直接位操作,bitset对象的大小不受unsigned数的位数限制:
//初始化
bitset<30> bitset_quiz1; // bitset solution
unsigned long int_quiz1 = 0; // simulated collection of bits
//置位
bitset_quiz1.set(27); // indicate student number 27 passed
int_quiz1 |= 1UL<<27; // indicate student number 27 passed
//复位
bitset_quiz1.reset(27); // student number 27 failed
int_quiz1 &= ~(1UL<<27); // student number 27 failed
//察看结果
bool status;
status = bitset_quiz1[27]; // how did student number 27 do?
status = int_quiz1 & (1UL<<27); // how did student number 27 do?
==数组名是不可修改的左值,因此数组不可用作赋值操作的目标。
==赋值操作时,当左右操作数类型不同时,该操作实现的类型转换可能会修改被赋的值:
ival = 0; // result: type int value 0
ival = 3.14159; // result: type int value 3
==与其它二元操作符不同,赋值操作具有右结合特性。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型:
int ival; int *pval;
ival = pval = 0; // error: cannot assign the value of a pointer to an int
string s1, s2;
s1 = s2 = "OK"; // ok: "OK" converted to string
==对于赋值运算符需注意:
int i;
if (i = 2) cout << "haha"; //输出“haha“,将2赋给i,然后检验赋值的结果,此时,2为非零值。
if(I = 0) cout << “haha”; //不会输出“haha”
==对于自增、自减操作符,前置操作返回加1后的值,所以返回对象本身,是左值。而后置操作返回的则是右值,并且必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。建议:只有在必要时才使用后置操作符。
==Sizeof表达式的结果是编译时的常量,有三种语法形式:
sizeof (type name);
sizeof (expr);
sizeof expr;
Sales_item item, *p;
// three ways to obtain size required to hold an object of type Sales_item
sizeof(Sales_item); // size required to hold an object of type Sales_item
sizeof item; // size of item's type, e.g., sizeof(Sales_item)
sizeof *p; // size of type to which p points, e.g., sizeof(Sales_item)
==逗号表达式,从左到右计算,表达式的结果是最右边表达式的值。如果最右边表达式的值是左值,那么逗号表达式的值也是左值。
==如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:
// oops! language does not define order of evaluation
if (ia[index++] < ia[index])
这样可能产生以下两种情况:
if (ia[0] < ia[0]) // execution if rhs is evaluated first
if (ia[0] < ia[1]) // execution if lhs is evaluated first
为了防止这样的情况出现,应该这样写以避免两个操作数的值不会互相影响:
if (ia[index] < ia[index + 1]) {
// do whatever
}
++index;
==对于内置类型或没有定义默认构造函数的类型,不同的初始化方式则有显著的差别:
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
==对于delete空指针是合法和安全的:
int *ip = 0;
delete ip; // ok: always ok to delete a pointer that is equal to 0
==执行语句delete p;p变成dangling pointer,悬垂指针指向曾经存放对象的内存,但该对象已经不存在了。因此,一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。
==动态创建const对象:
// allocate and initialize a const object
const int *pci = new const int(1024);
要在创建时就初始化,并且其值以后不能被修改。返回的Const对象的指针(new返回的地址上存放的是const对象),也只能赋给指向const对象的指针。
==使用数组时,大多数情况下数组都会自动转化为指向第一个元素的指针:
int ia[10]; // array of 10 ints
int* ip = ia; // convert ia to pointer to first element
==当使用非const对象初始化const对象的引用时,系统将非const对象转换为const对象。还可以将非const对象的地址转换为指向相关const类型的指针:
int i;
const int ci = 0;
const int &j = i; // ok: convert non-const to reference to const int
const int *p = &ci; // ok: convert address of non-const to address of a const
Chapter 6 Statements
== 对于switch中的case标号必须是整型常量表达式:
// illegal case label values
case 3.14: // noninteger
case ival: // nonconstant
==对于switch结构,只能在它的最后一个case标号或default标号后面定义变量:
case true:
// error: declaration precedes a case label
string file_name = get_file_name();
break;
case false:
// ...
以为了避免出现代码跳过变量的定义和初始化的情况。
由于变量的作用域是从定义点开始有效,直到它所在的块结束为止。所以,可以这样解决以上问题:
case true:
{
// ok: declaration statement within a statement block
string file_name = get_file_name();
// ...
}
break;
case false:
// ...
==for语句头中,可以省略init-statement、conditon[则为true,一直循环]、expression[下面表达式中若没有对i的改变,将一直循环]中的任何一个(或全部)。
==可以在for语句的init-statement中定义多个对象,但该处只能出现一个语句,因此所有的对象必须具有相同的一般类型:
const int size = 42;
int val = 0, ia[size];
// declare 3 variables local to the for loop:
// ival is an int, pi a pointer to int, and ri a reference to int
for (int ival = 0, *pi = ia, &ri = val;
ival != size;
++ival, ++pi, ++ri)
// ...
Chapter7. Functions
==C++是一种静态强类型语言,对于每一次的函数调用,编译时都会检查其实参。调用函数时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该形参类型的类型。
gcd(3.14, 6.29); // ok: arguments are converted to int
==函数的形参可以是指针,此时将复制实参指针。如果函数将新指针值赋给形参,主调函数使用的实参指针的值没有改变。
void reset(int *ip)
{
*ip = 0; // changes the value of the object to which ip points
ip = 0; // changes only the local value of ip; the argument is unchanged
}
If we want to prevent changes to the value to which the pointer points, then the parameter should be defined as a pointer to const:
void use_ptr(const int *p)
{
// use_ptr may read but not write to *p
}
此时,既可以将int*也可以用const int*类型的实参调用use_ptr函数。仅能用int*的实参传递给reset函数。(可以将指向const对象的指针初始化为指向非const对象,但不可以让指向非const对象的指针指向const对象)
==在调用函数时,如果该函数使用:
l 非引用的非const形参,由于实参是以副本的形式传递,因此传递给函数的可以是const对象也可以是非const对象;
l 非引用的const形参,在函数中,不可以改变实参的局部副本。由于实参是以副本的形式传递,因此传递给函数的可以是const对象也可以是非const对象;为了和c语言兼容,具有const形参或非const形参的函数并无区别。
void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* ... */ } // error: redefines fcn(int)
l 非const的引用形参,则不能通过const对象进行调用(这时函数可以修改传递进来的对象),传递一个右值或具有需要转换的类型的对象同样是不允许的。
// function takes a non-const reference parameter
int incr(int &val)
{
return ++val;
}
int main()
{
short v1 = 0;
const int v2 = 42;
int v3 = incr(v1); // error: v1 is not an int
v3 = incr(v2); // error: v2 is const
v3 = incr(0); // error: literals are not lvalues
v3 = incr(v1 + v2); // error: addition doesn't yield an lvalue
int v4 = incr(v3); // ok: v3 is a non const object type int
}
== Reference parameters that are not changed should be references to const. Plain, nonconst reference parameters are less flexible. Such parameters may not be initialized by const objects, or by arguments that are literals or expressions that yield rvalues.
// returns index of first occurrence of c in s or s.size() if c isn't in s
// Note: s doesn't change, so it should be a reference to const
string::size_type find_char(string &s, char c)
{
string::size_type i = 0;
while (i != s.size() && s[i] != c)
++i; // not found, look at next character
return i;
}
if (find_char("Hello World", 'o')) // ...编译失败
bool is_sentence (const string &s)
{
// if there's a period and it's the last character in s
// then s is a sentence
return (find_char(s, '.') == s.size() - 1);// 编译错误
}
==数组:不能复制数组;使用数组的名字时,数组名会自动转化为指向其第一个元素的指针。
==当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型是否匹配,而不会检查数组的长度。
==对于数组实参来说,非引用类型的形参会初始化为其相应实参的副本。而在传递数组时,实参是指向数组的第一个元素的指针,形参复制的是这个指针的值,而不是数组元素的本身。函数操纵的是指针的副本,因此不会修改实参指针的值。函数可通过该指针改变它所指向的数组元素的值。通过指针形参作的任何改变都在修改数组元素的本身。
==数组形参可声明为数组的引用,如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组实参的大小与形参的大小是否匹配:
// ok: parameter is a reference to an array; size of array is fixed
void printValues(int (&arr)[10]) { /* ... */ } //两边的圆括号是必须的。
int main()
{
int i = 0, j[2] = {0, 1};
int k[10] = {0,1,2,3,4,5,6,7,8,9};
printValues(&i); // error: argument is not an array of 10 ints
printValues(j); // error: argument is not an array of 10 ints
printValues(k); // ok: argument is an array of 10 ints
return 0;
}
==多维数组中,形参是一个指针,指向数组的数组中的元素。数组中的每个元素本身就是含有10个int型对象的数组。
// first parameter is an array whose elements are arrays of 10 ints
void printValues(int matrix[][10], int rowSize);
// first parameter is an array whose elements are arrays of 10 ints
void printValues(int (matrix*)[10], int rowSize); //注意圆括号
==对于函数返回值,返回非引用类型时,在调用函数的地方会将函数返回值复制给临时对象。当函数返回引用类型时,没有复制返回值。而返回的是对象本身。但注意的是,返回引用的时候,千万不能返回局部变量的引用。
==内联函数应该在头文件中定义(需要标示inline),因为它的定义对编译器而言必须是可见的,以便编译器能在调用点内联展开该函数的代码。编译器隐式的将在类内定义的成员函数当作内联函数。
==const成员函数改变了隐含的this形参(this本身是const类型)的类型,隐含的this形参将是一个指向对象的const Type* 类型的指针。//哈哈,原来const成员函数中的const是为了调用成员函数的对象本身加const
// pseudo-code illustration of how the implicit this pointer is used
// This code is illegal: We may not explicitly define the this pointer ourselves
// Note that this is a pointer to const because same_isbn is a const member
bool Sales_item::same_isbn(const Sales_item *const this,
const Sales_item &rhs) const
{ return (this->isbn == rhs.isbn); }
==const对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用他们来调用非const成员函数,则是错误的。//在一般函数调用时,const实参是可以对非const形参(非引用)进行函数调用,因为用的是实参的副本,与此不同。
==函数不能仅仅基于不同的返回类型而实现重载。
// each pair declares the same function
Record lookup(const Account &acct);
Record lookup(const Account&); // parameter names are ignored
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&); // Telno and Phone are the same type
Record lookup(const Phone&, const Name&);
// default argument doesn't change the number of parameters
Record lookup(const Phone&, const Name& = "");
// const is irrelevent for nonreference parameters
Record lookup(Phone);
Record lookup(const Phone); // redeclaration
//由于实参的传递方式,复制形参时并不考虑形参是否为const,函数操纵的只是副本,函数无法修改实参,因此这两种形参并无本质区别:
const int a = 5;
int b = 3;
void f(const int i){ cout << i << endl; }
void m(int i){ cout << i << endl; }
int main(){
f(a); //ok
f(b); //ok
m(a); //ok
m(b); //ok
return 0;
}
//值得注意的是,形参与const形参的等价性仅适用于非引用或非指针形参。即仅当形参是引用或指针时,形参是否为const才有影响。
==在c++中,函数名字查找发生在类型检查之前,一旦在此作用域中,找到函数名字,编译器将不再继续检查这个名字是否在外层作用域中的存在。
==关于类型提升:
void ff(int);
void ff(short);
ff('a'); // char promotes to int, so matches f(int)
//对于任意整型实参值,int型版本都是优于short版本。
extern void manip(long);
extern void manip(float);
manip(3.14); // error: ambiguous call
//3.14为double,既可转化为long,也可转化为float。
==可基于函数的引用形参是指向const对象还是指向非const对象,实现函数重载,指针也同样:
Record lookup(Account&);
Record lookup(const Account&); // new function
const Account a(0);
Account b;
lookup(a); // calls lookup(const Account&)
lookup(b); // calls lookup(Account&)
而不能基于指针本身是否为const来实现函数的重载,因为const用于修饰指针本身,而不是修饰指针所指的类型,从而都复制了指针,指针本身是否为const并没有带来区别:
f(int *);
f(int *const); // redeclaration
==局部作用域中声明的函数不会重载全局作用域中定义的函数:
void print(const string &);
void print(double); // overloads the print function
void fooBar(int ival)
{
void print(int); // new scope: hides previous instances of print
print("Value: "); // error: print(const string &) is hidden
print(ival); // ok: print(int) is visible
print(3.14); // ok: calls print(int); print(double) is hidden
}
==函数的形参可以是指向函数的指针:
void useBigger(const string &, const string &,
bool(const string &, const string &));
// equivalent declaration: explicitly define the parameter as a pointer to function
void useBigger(const string &, const string &,
bool (*)(const string &, const string &));
==函数可以返回指向函数的指针:
int (*ff(int))(int*, int);
利用typedef简化声明:
// PF is a pointer to a function returning an int, taking an int* and an int
typedef int (*PF)(int*, int);
PF ff(int); // ff returns a pointer to function
具有函数类型的形参所对应的实参将自动转化为指向相应函数类型的指针,但是当返回的是函数时,无法实现同样的转化操作:
// func is a function type, not a pointer to function!
typedef int func(int*, int);
void f1(func); // ok: f1 has a parameter of function type
func f2(int); // error: f2 has a return type of function type
func *f3(int); // ok: f3 returns a pointer to function type
Chapter8.The IO Library
==IO类型的头文件:
iostream |
istream reads from a stream ostream writes to a stream iostream reads and writes a stream; derived from istream and ostream, |
fstream |
ifstream, reads from a file; derived from istream ofstream writes to a file; derived from ostream fstream, reads and writes a file; derived from iostream |
sstream |
istringstream reads from a string; derived from istream ostringstream writes to a string; derived from ostream stringstream reads and writes a string; derived from iostream |
==IO对象不可复制或赋值:
ofstream out1, out2;
out1 = out2; // error: cannot assign stream objects
// print function: parameter is copied
ofstream print(ofstream);
out2 = print(out2); // error: cannot copy stream objects
由于此原因,则流不能:
l 存储在vector或其他容器类型里(只有支持复制的元素类型才可以存储)
l 形参或返回类型也不能为流类型,如果要传递或返回IO对象,则必须传递或返回指向该对象的引用或指针:
ofstream &print(ofstream&); // ok: takes a reference, no copy
while (print(out2)) { /* ... */ } // ok: pass reference to out2
==流必须处于无错误的状态,才能用于输入或输出,检测流是否可用的最简单的方法是检查其真值:
if (cin)
// ok to use cin, it is in a valid state
while (cin >> word)
// ok: read operation successful ...
==输出缓冲区的刷新:
cout << "hi!" << flush; // flushes the buffer; adds no data
cout << "hi!" << ends; // inserts a null, then flushes the buffer
cout << "hi!" << endl; // inserts a newline, then flushes the buffer
操纵符unitbuf刷新所有输出:
cout << unitbuf << "first" << " second" << nounitbuf;
等价于:
cout << "first" << flush << " second" << flush;
==当输入流与输出流绑在一起时,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区。cin >> ival;将导致cout关联的缓冲区被刷新。
==文件流对象的使用:
1.创建文件流对象(两种方法)
l // construct an ifstream and bind it to the file named ifile
ifstream infile(ifile.c_str());
// ofstream output file object to write file named ofile
ofstream outfile(ofile.c_str());
l ifstream infile; // unbound input file stream
ofstream outfile; // unbound output file stream
//邦定到具体文件
infile.open("in"); // open file named "in" in the current directory
outfile.open("out"); // open file named "out" in the current directory
//由于历史原因,如果调用open或使用文件名作初始化式,需要传递的实参应为C风格字符串,而不是标准库string对象。
2.检查文件打开是否成功:
// check that the open succeeded
if (!infile) {
cerr << "error: unable to open input file: "
<< ifile << endl;
return -1;
}
3.文件流与新文件重新捆绑:
ifstream infile("in"); // opens file named "in" for reading
infile.close(); // closes "in"
infile.open("next"); // opens file named "next" for reading
//open会检查流是否已经打开,如果已经打开,则设置内部状态,以指出发生错误,其后使用文件流的任何尝试都会失败。
4.清除文件流的状态:要重用已存在的文件流对象读写多个文件,必须在读另一个文件之前调用clear清除该流状态。(关闭流不能改变流对象的内部状态,如果最后的读写操作失败了,对象的状态将保持错误模式,直到执行clear操作重新恢复流的有效状态为止。)
ifstream input;
vector::const_iterator it = files.begin();
// for each file in the vector
while (it != files.end()) {
input.open(it->c_str()); // open the file
// if the file is ok, read and "process" the input
if (!input)
break; // error: bail out!
while(input >> s) // do the work on this file
process(s);
input.close(); // close file when we're done with it
input.clear(); // reset state to ok
++it; // increment iterator to get next file
}
==以binary模式打开的流将文件以字节序列的形式处理,而不解释流中的字符。以out模式打开的文件会被清空。fstream对象以in和out模式同时打开,当文件同时以in和out打开时文件内容不清空。
== stringstream的特定操作:
stringstream strm; |
Creates an unbound stringstream. |
stringstream strm(s); |
Creates a stringstream that holds a copy of the string s. |
strm.str() |
Returns a copy of the string that strm holds. |
strm.str(s) |
Copies the string s into strm. Returns void. |
==stringstream的使用:
string line, word; // will hold a line and word from input, respectively
while (getline(cin, line)) { // read a line from the input into line
// do per-line processing
istringstream stream(line); // bind to stream to the line we read
while (stream >> word){ // read a word from line
// do per-word processing
}
}
//读每一行用getline函数,而对于每个单词则可用流对象(文件流或字符串流。。。)的>>操作符读就行了。
==stringstream提供的转化和格式化,在多种数据类型之间实现自动格式化:
int val1 = 512, val2 = 1024;
ostringstream format_message;
// ok: converts values to a string representation
format_message << "val1: " << val1 << "/n"
<< "val2: " << val2 << "/n";
其内容为:val1: 512/nval2: 1024
用istringstream读string对象,可重新将数值型数据找回来:
// str member obtains the string associated with a stringstream
istringstream input_istring(format_message.str());
string dump; // place to dump the labels from the formatted message
// extracts the stored ascii values, converting back to arithmetic types
input_istring >> dump >> val1 >> dump >> val2;
cout << val1 << " " << val2 << endl; // prints 512 1024
//dump是为了读取(和忽略)处于所需数据的周围的标号。
Chapter 9.Sequential Containers
==容器的构造函数:
C |
Create an empty container named c. C is a container name, such as vector, and T is the element type, such as int or string. Valid for all containers. |
C c(c2); |
Create c as a copy of container c2; c and c2 must be the same container type and hold values of the same type. Valid for all containers. |
C c(b, e); |
Create c with a copy of the elements from the range denoted by iterators b and e. Valid for all containers. |
C c(n, t); |
Create c with n elements, each with value t, which must be a value of the element type of C or a type convertible to that type. Sequential containers only. |
C c(n); |
Create c with n value-initialized elements. Sequential containers only. |
==将一个容器复制给另一个容器时,类型必须匹配。容器类型和元素类型都必须相同。但是使用跌代器或指针时,不要求容器类型相同,容器内的元素类型也可以不相同,只要它们相互兼容,即可实现复制。
// initialize slist with copy of each element of svec
list slist(svec.begin(), svec.end());
// find midpoint in the vector
vector::iterator mid = svec.begin() + svec.size()/2;
// initialize front with first half of svec: The elements up to but not including *mid
deque front(svec.begin(), mid);
// initialize back with second half of svec: The elements *mid through end of svec
deque back(mid, svec.end());
char *words[] = {"stately", "plump", "buck", "mulligan"};
// calculate how many elements in words
size_t words_size = sizeof(words)/sizeof(char *);
// use entire array to initialize words2
list words2(words, words + words_size);
==接受容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。
==容器元素类型必须满足两个约束:
l 元素类型必须支持赋值运算
l 元素类型的对象必须可以复制
==只有vector和deque容器的跌代器,支持算术运算(加和减)、关系运算(<=, <…)。
vector
// copy elements from vec into ilist
list
ilist.begin() + ilist.size()/2; // error: no addition on list iterators
==在容器中添加元素时,系统是将元素值复制到容器里。类似地,使用一段元素初始化新容器时,新容器存放的是原始元素的副本。被复制的原始值与新容器中的元素各不相关。
==不要存储end操作返回的跌代器。添加或删除deque或vector容器内的元素都会导致存储的跌代器失效:
vector::iterator first = v.begin(), last = v.end(); // cache end iterator
// diaster: behavior of this loop is undefined
while (first != last) {
// do some processing
// insert new value and reassign first, which otherwise would be invalid
first = v.insert(first, 42);
++first; // advance first just past the element we added
}
//重新计算end跌代器值以避免end跌代器失效:
// safer: recalculate end on each trip whenever the loop adds/erases elements
while (first != v.end()) {
// do some processing
first = v.insert(first, 42); // insert new value
++first; // advance first just past the element we added
}
==关于swap:该操作不会删除或插入任何元素,而且保证在常量时间内实现交换。由于容器内没有移动任何元素,因此跌代器不会失效。
而assign()和=会先删除其左操作数容器中的所有元素,然后将右操作数容器的所有元素插入到左边容器中,因此跌代器也失效。
c1 = c2; // replace contents of c1 with a copy of elements in c2
// equivalent operation using erase and insert
c1.erase(c1.begin(), c1.end()); // delete all elements in c1
c1.insert(c1.begin(), c2.begin(), c2.end()); // insert c2
==容器的适配器是在基础容器之上实现的。默认的stack和queue都基于deque容器实现的,而priority_queue则在vector容器上实现。可以通过将一个顺序容器指定为适配器的第二个类型参数,可覆盖其关联的基础容器类型,但是对于给定的适配器,其关联的容器必须满足一定的约束条件:
// empty stack implemented on top of vector
stack< string, vector > str_stk;
// str_stk2 is implemented on top of vector and holds a copy of svec
stack > str_stk2(svec);
Chapter 10.Associative Containers
==pair类型的使用繁琐,因此,如果需要定义多个相同的pair类型对象,可利用typedef简化其声明:
typedef pair Author;
Author proust("Marcel", "Proust");
Author joyce("James", "Joyce");
==map是键-值对的集合,map类型通常可理解为关联数组:可使用键作为下标来获取一个值,正如内置数组类型一样。对于键类型,唯一的约束是必须支持<操作符。
// count number of times each word occurs in the input
map word_count; // empty map from string to int
==map跌代器进行解引用将产生pair类型的对象,获得的pair对象,它的first成员存放键,为const,因此不能修改,而second成员存放值:
// get an iterator to an element in word_count
map::iterator map_it = word_count.begin();
// *map_it is a reference to a pair object
cout << map_it->first; // prints the key for this element
cout << " " << map_it->second; // prints the value of the element
map_it->first = "new key"; // error: key is const
++map_it->second; // ok: we can change value through an iterator
==下标访问map对象,用下标访问不存在元素将导致在map容器中添加一个新的元素,它的键即为该下标值:
map word_count; // empty map
// insert default initialzed element with key Anna; then assign 1 to its value
word_count["Anna"] = 1;
下标操作符返回左值,返回的左值是特定键所关联的值:
cout << word_count["Anna"]; // fetch element indexed by Anna; prints 1
++word_count["Anna"]; // fetch the element and add one to it
cout << word_count["Anna"]; // fetch the element and print it; prints 2
==set不支持下标操作符,set存储的元素仅仅是键,必须唯一,不能修改。
// set_it refers to the element with key == 1
set::iterator set_it = iset.find(1);
*set_it = 11; // error: keys in a set are read-only
cout << *set_it << endl; // ok: can read the key
==multimap不支持下标运算。
==程序设计的良好习惯:
1. 列出程序所涉及的操作
2. 建立所需要的数据结构
3. 实现这些行为
Chapter 11.Generic Algorithms
==string标准库为string对象与char*对象定义了相等(==)操作符。
==对于写容器的算法,为了确保算法有足够的元素存储数据的一种方法是使用插入迭代器,它是可以给基础容器添加元素的迭代器。
vector vec; // empty vector
// disaster: attempts to write to 10 (nonexistent) elements in vec
fill_n(vec.begin(), 10, 0);
vector vec; // empty vector
// ok: back_inserter creates an insert iterator that adds elements to vec
fill_n (back_inserter(vec), 10, 0); // appends 10 elements to vec
vector
// copy elements from ilst into ivec
copy (ilst.begin(), ilst.end(), back_inserter(ivec));
==replace()
// replace any element with value of 0 by 42
replace(ilst.begin(), ilst.end(), 0, 42);
==replace_copy()
// create empty vector to hold the replacement
vector ivec;
// use back_inserter to grow destination as needed
replace_copy (ilst.begin(), ilst.end(),
back_inserter(ivec), 0, 42);
ivec成为ilist的副本,但其中的0被42替换
==算法不直接修改容器的大小,如果需要添加或删除元素,则必须使用容器操作。
// sort words alphabetically so we can find the duplicates
sort(words.begin(), words.end());
/* eliminate duplicate words:
* unique reorders words so that each word appears once in the
* front portion of words and returns an iterator one past the unique range;
* erase uses a vector operation to remove the nonunique elements
*/
vector::iterator end_unique =
unique(words.begin(), words.end());
words.erase(end_unique, words.end());
==只有当容器提供push_front操作时,才能使用front_inserter。在vector或其他没有push_front运算的容器上使用front_inserter,将产生错误。
==流迭代器都是类模板:任何已定义输入操作符(>>操作符)的类型都可以定义istream_iterator。同理于定义了输出操作符。
==istream_iterator对象上的操作:
istream_iterator in_iter(cin); // read ints from cin
istream_iterator eof; // istream "end" iterator
// read until end of file, storing what was read in vec
while (in_iter != eof)
// increment advances the stream to the next value
// dereference reads next value from the istream
vec.push_back(*in_iter++);
等价于:
istream_iterator
istream_iterator
vector
==ostream_iterator对象上的操作:
// write one string per line to the standard output
ostream_iterator out_iter(cout, "/n");
// read strings from standard input and the end iterator
istream_iterator in_iter(cin), eof;
// read until eof and write what was read to the standard output
while (in_iter != eof)
// write value of in_iter to standard output
// and then increment the iterator to get the next value from cin
*out_iter++ = *in_iter++;
==一旦给ostream_iterator对象赋了一个值,写入就提交了,赋值后没有办法再改变这个值。它也没有->操作符。而istream_iterator有->操作符。
==反向迭代器与普通迭代器之间的关系:
==迭代器种类:
Input iterator (istream_iterator) |
Read, but not write; increment only |
Output iterator (ostream_iterator) |
Write, but not read; increment only |
Forward iterator |
Read and write; increment only |
Bidirectional iterator (map,set,list) |
Read and write; increment and decrement |
Random access iterator(string, vector,deque) |
Read and write; full iterator arithmetic |
==算法的形参模式:
alg (beg, end, other parms);
alg (beg, end, dest, other parms);//算法假定无论需要写入多少元素都是安全,如使用插入跌代器或者ostream_iterator
alg (beg, end, beg2, other parms); //假定以beg2开始的范围至少与beg和end指定的范围一样大
alg (beg, end, beg2, end2, other parms);
==容器特有的算法,如list特有的算法,对于list对象,应优先使用list容器特有的成员版本,而不是泛型算法。与对应的泛型算法不同,list容器特有的操作能添加和删除元素(remove,unique),破坏实参(merge,splice)。
Chapter 12.Classes
==不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用。
Screen myScreen;
// this code fails if display is a const member function
// display return a const reference; we cannot call set on a const
myScreen.display().set('*');
//基于const的重载来解决此问题
==mutable成员是在const成员函数中都能被修改的。
==如果返回类型使用由类定义的类型,则必须使用完全限定名:
class Screen {
public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline Screen::index Screen::get_cursor() const
{
return cursor;
}
==构造函数分两个阶段执行:1.初始化阶段;2.普通的计算阶段。计算阶段由构造函数函数体内的所有语句组成。
不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化(作隐式的初始化)。
因为内置类型的成员不进行隐式初始化,所以对这些成员是进行初始化还是赋值是无关紧要的。
除了两个例外(const或引用类型成员),对非类类型的数据成员进行赋值或使用初始化式在结果和性能上都是等价的。因为可以初始化const对象或引用类型的对象,但不能对他们赋值。在开始执行构造函数的函数体之前,要完成初始化。
必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。(因为类类型的数据成员总是在初始化阶段初始化。)
==内置和复合类型的成员,如指针和数组,只对定义在全局作用域中的对象才初始化。当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。因此,每个构造函数应当为每个内置或复合类型的成员提供初始式。
==默认构造函数将会隐式的使用string默认构造函数来初始化isbn:
Sales_item(const std::string &book):
isbn(book), units_sold(0), revenue(0.0) { }
Sales_item(): units_sold(0), revenue(0.0) { }
==static成员可以是函数或数据,独立于类类型的对象而存在。
==类可以定义static成员函数,它没有this形参,可以直接访问所属类的static成员,但不能直接使用非static成员。由于不是任何对象的组成部分,所以static成员函数不能声明为const。也不能声明为虚函数。
==static数据成员必须在类定义体的外部定义(正好一次)。不能通过类构造函数进行初始化,而在定义时进行初始化。
==像使用任意的类成员一样,在类定义体外部引用类的static成员时,必须指定成员是在哪个类中定义的。然而,static关键字只能用于类定义体内部的声明中,定义不能标示为static:
class Account {
public:
// interface functions here
void applyint() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
// define and initialize static class member
double Account::interestRate = initRate(); //等同于:而不用管是否是private的
double Account::interestRate = Account::initRate();
==只要初始化是一个常量表达式,整型const static数据成员就可以在类的定义体中进行初始化:
class Account {
public:
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
static const int period = 30; // interest posted every 30 days
double daily_tbl[period]; // ok: period is constant expression
};
注意:When a const static data member is initialized in the class body, the data member must still be defined outside the class definition
// definition of static member with no initializer;
// the initial value is specified inside the class definition
const int Account::period; //此时已不必再指定初始值
==static数据成员的类型可以是该成员所属的类类型。非static成员被限定声明为其自身类对象的指针或引用:
class Bar {
public:
// ...
private:
static Bar mem1; // ok
Bar *mem2; // ok
Bar mem3; // error
};
static数据成员可用作默认实参,非static数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用:
class Screen {
public:
// bkground refers to the static member
// declared later in the class definition
Screen& clear(char = bkground);
private:
static const char bkground = '#';
};
Chapter 13.Copy Control
==复制构造函数可用于初始化顺序容器中的元素。编译器首先使用string默认构造函数创建一个临时值来初始化svec,然后使用复制构造函数将临时值复制到svec的每个元素:
// default string constructor and five string copy constructors invoked
vector svec(5);
除非想使用容器元素的默认初始值,更有效的办法是,分配一个空容器并将已知元素的值加入容器。
==在合成复制构造函数中,如果一个类具有数组成员,则它将复制数组,复制数组时合成复制构造函数将复制数组的每一个元素。
==为了防止复制,类必须显示声明其复制构造函数为private,但类的友元和成员仍可以进行复制,如果也想禁止,则只声明但不对其定义。
==如果类需要析构函数,则它也需要赋值操作符合复制构造函数。
==与复制构造函数和赋值操作符不同,无论类是否定义了自己的析构函数,都会创建和运行合成析构函数。如果类定义了析构函数,则在类定义的析构函数结束之后运行合成析构函数。
==在复制控制中,复制指针时只复制指针中的地址,而不会复制指针指向的对象。
Chapter 14.Overloaded Operations and Convertions
==重载操作符必须具有至少一个类类型或枚举类型的操作数。
==对于输出操作符:第一个形参对ostream对象的引用,在该对象上将产生输出。为非const,因为写入到流会改变流的状态。第二个形参为const的引用,可以使用同一个定义来输出const和非const对象。
// general skeleton of the overloaded output operator
ostream& operator <<(ostream& os, const ClassType &object)
{
// any special logic to prepare object
// actual output of members
os << // ...
// return ostream object
return os;
}
==输入和输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性;第二个形参不能是const的。
istream& operator>>(istream& in, Sales_item& s)
{
double price;
in >> s.isbn >> s.units_sold >> price;
// check that the inputs succeeded
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // input failed: reset object to default state
return in;
}
==类定义下表操作符时,一般需要定义两个版本:一个为非const成员并返回引用,另一个为const成员并返回const引用。
==重载解引用操作符、箭头操作符的const和非const版本。
==重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
==定义自增自减操作符:
// prefix: return reference to incremented/decremented object
CheckedPtr& CheckedPtr::operator++()
{
if (curr == end)
throw out_of_range
("increment past the end of CheckedPtr");
++curr; // advance current state
return *this;
}
// postfix: increment/decrement object but return unchanged value
CheckedPtr CheckedPtr::operator++(int)
{
// no check needed here, the call to prefix increment will do the check
CheckedPtr ret(*this); // save current value
++*this; // advance one element, checking the increment
return ret; // return saved state
}
==函数对象的定义:
struct absInt {
int operator() (int val) { //函数调用操作符
return val < 0 ? -val : val;
}
};
==函数对象的函数适配器:
1. 绑定器:通过将一个操作数绑定到给定值而将二元函数对象转化为一元函数对象。(bind1st,bind2nd)
count_if(vec.begin(), vec.end(), bind2nd(less_equal(), 10));
2. 求反器:它将谓词函数对象的真值求反。(not1,not2)
count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal(), 10)));
==转换函数采用如下通用形式:
operator type() const;
type可作为函数返回类型的类型(除了void之外)都可以定义转化函数。不允许转化为数组或函数类型,转化为指针类型(数据和函数指针)以及引用类型是可以的。一般定义为const成员。
==对于类类型转化的使用,只要存在转换,编译器将在可以使用内置转换的地方自动调用它。类类型转换之后不能再跟另一个类类型转换,只能转换一次。
==SmallInt si; //存在对si进行int型和double型的转换操作
void extended_compute(long double);
extended_compute(si); // error:ambiguous. //因为对si的转换操作可以使用任一转换函数,但其后跟上一个标准转换来获得long double。(int 转换为long double,double转换为long double都可以)因此,没有一个转化比其他的更好,调用具有二义性。问题同样可能存在于有两个构造函数可以用来将一个值转换为目标类型之中。
Chapter 15.Object-Oriented Programming
==因为派生类对象都有基类部分,派生类中可以访问其基类的public和protected成员,就好像那些成员是派生类自己的成员一样。private成员始终是无法被派生类所访问的。
==派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。
void Bulk_item::memfcn(const Bulk_item &d, const Item_base &b)
{
// attempt to use protected member
double ret = price; // ok: uses this->price
ret = d.price; // ok: uses price from a Bulk_item object
ret = b.price; // error: no access to price from an Item_base
}
==虚函数中的默认实参是在编译时确定的,所以在一个调用省略了具有默认值的实参,则所有值由调用该函数的静态类型决定,与对象的动态类型无关。
==接口继承与实现继承:
public派生类继承基类的接口,它具有与基类相同的接口。使用private或protected派生的类不继承基类的接口,相反,这些派生通常被称为实现继承。派生类在实现中使用被继承类但继承基类的部分并未成为其接口的一部分。
==派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松。利用using声明:
class Base {
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base { . . . };
class Derived : private Base {
public:
// maintain access levels for members related to the size of the object
using Base::size;
protected:
using Base::n;
// ...
};
==如果基类定义了static成员,则整个继承层次中只有一个这样的成员。如果成员在基类中为private,则派生类不能访问它。惹可以访问,则既可以通过基类访问static成员,也可通过派生类访问static成员。
struct Base {
static void statmem(); // public by default
};
struct Derived : Base {
void f(const Derived&);
};
void Derived::f(const Derived &derived_obj)
{
Base::statmem(); // ok: Base defines statmem
Derived::statmem(); // ok: Derived in herits statmem
// ok: derived objects can be used to access static from base
derived_obj.statmem(); // accessed through Derived object
statmem(); // accessed through this class
==如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:
class Base { /* ... */ };
class Derived: public Base {
public:
// Base::Base(const Base&) not invoked automatically
Derived(const Derived& d):
Base(d) /* other member initialization */ { /*... */ }
};
否则,运行Base的默认构造函数初始化对象的基类部分。结果是它的Base部分将保存默认值,而它的Derived成员是另一对象的副本:
// probably incorrect definition of the Derived copy constructor
Derived(const Derived& d) /* derived member initizations */
{/* ... */ }
==派生类赋值操作符,也必须对基类部分进行显式赋值:
// Base::operator=(const Base&) not invoked automatically
Derived &Derived::operator=(const Derived &rhs)
{
if (this != &rhs) {
Base::operator=(rhs); // assigns the base part
// do whatever needed to clean up the old value in the derived part
// assign the members from the derived
}
return *this;
}
==如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自己类型定义的版本。
==对象、引用或指针的静态类型决定了对象能够完成的行为:
class Disc_item : public Item_base {
public:
std::pair
{ return std::make_pair(quantity, discount); }
// other members as before
};
Bulk_item bulk;
Bulk_item *bulkP = &bulk; // ok: static and dynamic types are the same
Item_base *itemP = &bulk; // ok: static and dynamic types differ
bulkP->discount_policy(); // ok: bulkP has type Bulk_item*
itemP->discount_policy(); // error: itemP has type Item_base*//discount_policy在基类中没有定义
==局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。
==如果虚函数的基类实例返回类类型的引用或指针,则该虚函数的派生类实例可以返回基类实例返回的类型的派生类。
==关于句柄类,需再研究。句柄将完全屏蔽基础继承层次。
==如果派生类仍未定义抽象基类中的纯虚函数,则派生类仍是抽象基类。
==结合10.6和15.9的内容,认真研究其中的内容。