第一章没什么代码
第二章代码
第三章代码
第四章代码
addpntrs.cpp
arraynew.cpp
arrsruct.cpp
结构体定义
定义完结构体,再定义结构体数组
结构体有点像int类型,是一种类型
结构体数组的定义及初始化
guests[0].volume来得到该…
代码
inflatable guests[2] = // initializing an array of structs
{
{"Bambi", 0.5, 21.99}, // first structure in array
{"Godzilla", 2000, 565.99} // next structure in array
};
choices.cpp
delete.cpp
instr1/2/3.cpp
mixtypes.cpp
newstrcut.cpp
numstr.cpp
ptrstr.cpp
cout<<字符串地址,输出字符串;可以接受char animal[20]的animal,const char * bird的bird
delete [] ps;
ps = animal;指向同地址
ps = new char[strlen(animal) + 1]; strcpy(ps,aniaml);
可以cout<< (int *) animal来看animal字符串的地址
如果直接char * ps;接着cin >> ps; 不行,因为ps的指针是未知的
内存必须先申请再读写
strtype1/2/3/4.cpp
use_new.cpp
第五章代码
bigstep.cpp
comstr1.cpp
dowhile.cpp
express.cpp
forstr2.cpp
nested.cpp
textin1/2/3/4.cpp
cin<<ch 不接受空字符,是个问题
可以在#后面输入字符,是个问题
使用cin.get(char)进行补救,可以解决不接受空字符的问题
cin.get(char)是通过引用来修改值的,不同于使用变量的地址
linux系统的ctrl + d才是EOF,ctrl + z是暂时挂起,按fg可恢复
Windows系统的ctrl + z是EOF
由于EOF表示的不是有效字符编码,因此可能不与char类型兼容。例如,在有些系统中,char类型是没有符号的,因此char变量不可能为EOF值(−1)。由于这种原因,如果使用cin.get( )(没有参数)并测试EOF,则必须将返回值赋给int变量,而不是char变量。另外,如果将ch的类型声明为int,而不是char,则必须在显示ch时将其强制转换为char类型。
int ch; // should be int, not char
while ((ch = cin.get()) != EOF) // test for end-of-file
{
cout.put(char(ch));
}
while.cpp
头文件ctime定义了一个符号常量—CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数,将系统时间除以这个值,可以得到秒数
第六章代码
cctypes.cpp
#include
如果cin位于测试条件中,则将被转换为bool类型。如果输入成功,则转换后的值为true,否则为false
condit.cpp
enum.cpp
outfile.cpp
ofstream outFile;
outFile.open(“carinfo.txt”);
outFile << fixed;
outFile.precision(2);
outFile.setf(ios_base::showpoint);
outFile << "Make and model: " << automobile << endl;
outFile << "Year: " << year << endl;
outFile << "Was asking $" << a_price << endl;
outFile << "Now asking $" << d_price << endl;
outFile.close(); // done with file
这里的outFile有点类似于cout
sumafile.cpp
ifstream inFile;
if (!inFile.is_open()) // failed to open file
{
cout << "Could not open the file " << filename << endl;
cout << "Program terminating.\n";
// cin.get(); // keep window open
exit(EXIT_FAILURE);
}
while (inFile.good()) // while input good and not at EOF
{
++count; // one more item read
sum += value; // calculate running total
inFile >> value; // get next value
}
if (inFile.eof())
cout << "End of file reached.\n";
else if (inFile.fail())
cout << "Input terminated by data mismatch.\n";
else
cout << "Input terminated for unknown reason.\n";
if (count == 0)
cout << "No data processed.\n";
else
{
cout << "Items read: " << count << endl;
cout << "Sum: " << sum << endl;
cout << "Average: " << sum / count << endl;
}
inFile.close();
输入格式不对,如将字符输入给double型,看inFile.fail()
到文件尾:inFile.eof()
第七章
arfupt.cpp
数组名
和&数组名
的区别
数组名
和&数组名
虽然在数值上相同,但是在类型上不同——数组名
是指向首个元素的指针,&数组名
是指向整个数组的指针。在指针加减整数的时候,前者以一个元素为颗粒度,后者以整个数组为颗粒度。array
的值就等于&array[0]
的值arrfun2.cpp
arrfun3.cpp
for (i = 0; i < limit; i++)
{
cout << "Enter value #" << (i + 1) << ": ";
cin >> temp;
if (!cin) // bad input
{
cin.clear();
while (cin.get() != '\n')
continue;
cout << "Bad input; input process terminated.\n";
break;
}
else if (temp < 0) // signal to terminate
break;
ar[i] = temp;
}
输入后,通过!cin判断是否是好的输入,若是不好,则cin.clear(),接着循环进行,直到get到回车,break;
void show_array(const double ar[], int n);
show的时候,要对这个数组加个const
arrfun4.cpp
int sum_arr(const int * begin, const int * end)
{
const int * pt;
int total = 0;
for (pt = begin; pt != end; pt++)
total = total + *pt;
return total;
}
sum = sum_arr(cookies + 4, cookies + 8);
arrobj.cpp
funptr.cpp
lotto.cpp
ruler.cpp
包含多个递归调用的递归
subdivide( )函数使用变量level来控制递归层。函数调用自身时,将把level减1,当level为0时,该函数将不再调用自己。注意,subdivide( )调用自己两次,一次针对左半部分,另一次针对右半部分。最初的中点被用作一次调用的右端点和另一次调用的左端点。请注意,调用次数将呈几何级数增长。也就是说,调用一次导致两个调用,然后导致4个调用,再导致8个调用,依此类推。这就是6层调用能够填充64个元素的原因(2 6 =64)。这将不断导致函数调用数(以及存储的变量数)翻倍,因此如果要求的递归层次很多,这种递归方式将是一种糟糕的选择;然而,如果递归层次较少,这将是一种精致而简单的选择。
画尺子
subdivide(ruler,min,max, i);
i=1分一次,i=2分两次
for (int j = 1; j < Len - 2; j++)
ruler[j] = ' '; // reset to blank ruler
void subdivide(char ar[], int low, int high, int level)
{
if (level == 0)
return;
int mid = (high + low) / 2;
ar[mid] = '|';
subdivide(ar, low, mid, level - 1);
subdivide(ar, mid, high, level - 1);
}
strctptr.cpp
void rect_to_polar(const rect * pxy, polar * pda);
void show_polar (const polar * pda);
while (cin >> rplace.x >> rplace.y)
{
rect_to_polar(&rplace, &pplace); // pass addresses
show_polar(&pplace); // pass address
cout << "Next two numbers (q to quit): ";
}
//挺优雅的
strgback.cpp
// builds string made of n c characters
char * buildstr(char c, int n)
{
char * pstr = new char[n + 1];
pstr[n] = '\0'; // terminate string
while (n-- > 0)
pstr[n] = c; // fill rest of string
return pstr;
}
strgfun.cpp
const char * str, str是指针,可以++来遍历,*str是
unsigned int c_in_str(const char * str, char ch)
{
unsigned int count = 0;
while (*str) // quit when *str is '\0'
{
if (*str == ch)
count++;
str++; // move pointer to next char
}
return count;
}
structfun.cpp
#include
answer.distance =
sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.y, xypos.x);
第八章代码
arrtemp.cpp
template <class T, size_t n>
void display(const std::array<T, n> & ar);
模板类声明
这里用模板类
std::size_t是一个typedef,对应于合适的整型,在我的电脑上是long unsigned int
choices.cpp
template<class T>
T lesser(T a, T b)
函数调用与模板函数和非模板函数都匹配,因此选择非模板函数,返回20。
lesser<>(m, n)有“<>”这个符号,一定是用模板
lesser(x, y),虽然输入x,y是double,但强行用int模板
filefunct.cpp
方法setf( )返回调用它之前有效的所有格式化设置。
同理,os.precision(0)也是
void file_it(ostream & os, double fo, const double fe[],int n)
ios_base::fmtflags initial;
initial = os.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize sz = os.precision(0);
ofstream是ostream的子类
funtemp.cpp
template <typename T> // or class T
void Swap(T &a, T &b)
inline.cpp
left.cpp
leftover.cpp
计算位数如下:
unsigned digits = 1;
while (n /= 10)
digits++;
strquote.cpp
用const 修饰函数的返回值,如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
例如函数:
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str = GetString();
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
例如不要把函数int GetInt(void) 写成const int GetInt(void)。
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
class A
{
A & operate = (const A &other); // 赋值函数
};
A a, b, c; // a, b, c 为A 的对象
a = b = c; // 正常的链式赋值
(a = b) = c; // 不正常的链式赋值,但合法
如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。
const 成员函数(const的作用:说明其不会修改数据成员)
如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误
a. const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
b. const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
c. const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查.
e. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的。
const引用可以读取但是不可以被修改引用对象,任何对const引用进行赋值都是不合法的,它适用指向const对象的引用,而非const的引用不适用于指向const对象的引用。
swaps.cpp
通过引用来交换值
void swapr(int & a, int & b) // use references
{
int temp;
temp = a; // use a, b for values of variables
a = b;
b = temp;
}
twoswap.cpp
可以提供一个具体化函数定义——称为显式具体化(explicit specialization),其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
template <typename T>
void Swap(T &a, T &b);
template <> void Swap<job>(job &j1, job &j2);
//当参数为Job对象时,将使用显示具体化
tempover.cpp
第九章代码
external.cpp support.cpp
::warming作用域解析运算符(::),放在变量名前面时,该运算符表示使用变量的全局版本。
extern double warming; // use warming from another file使用别的文件的全局变量,要用extern来声明它
CMakeLists.txt配置
add_library(support_library STATIC support.cpp )
add_executable(my_cmake_exe external.cpp)
target_link_libraries( my_cmake_exe support_library )
file1.cpp coordin.h file2.cpp
namesp.h namesp.cpp usenmsp.cpp
“namesp.h“中:
namespace pers
{
定义结构体;
函数原型;
}
namespace debts
{
using namespace pers;//使用刚定义的名称空间
}
“namesp.cpp“中:
namespace pers
{
写函数
}
“usenmsp.cpp“中:
using debts::Debt;
using debts::showDebt;
using namespace debts;
twofile1.cpp twofile2.cpp
twofile1.cpp使用twofile2.cpp的内容
twofile1.cpp中的int tom=3 twofile2.cpp使用extern int tom;
twofile1.cpp中的int dick = 30; twofile2.cpp使用static int dick = 10;
twofile1.cpp中的static int harry = 300; twofile2.cpp使用int harry = 200;
默认是extern
不能两个文件都int errors=3 这种做法将失败,因为它违反了单定义规则。file2中的定义试图创建一个外部变量,因此程序将包含errors的两个定义,这是错误。但如果文件定义了一个静态外部变量,其名称与另一个文件中声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量.
newplace.cpp
定位new运算符
new运算符还有另一种变体,被称为定位(placement)new运算符,它让您能够指定要使用的位置。
char buffer[BUF]; // chunk of memory静态区
(void *) buffer来看内存地址
pd2 = new (buffer) double[N];
pd2 = new (buffer + N * sizeof(double)) double[N];
不需要delete,因为new的不在动态存储区
static.cpp
静态变量total只在程序运行时被设置为0,以后在两次函数调用之间,其值将保持不变,因此能够记录读取的字符总数。
using namespace std;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next);
while (next != '\n') // string didn't fit!
cin.get(next); // dispose of remainder
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize);
}
cout << "Bye\n";
tip: C++也为静态存储持续性变量提供了3种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)
补充:cv限定符,cv表示const和volatile
第十章代码
stock.h stock.cpp
通过内联函数来使得不需要重复输入计算代码
Stock(); // default constructor
Stock(const std::string & co, long n = 0, double pr = 0.0);
~Stock(); // noisy destructor
默认构造函数
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
this是个对象指针,返回对象,要加*
使用对象指针的函数:&top->topval(stocks[st])
作用域为类的常量
一种方式是在类中声明一个枚举
用这种方式声明枚举并不会创建类数据成员。也就是说,所有对象中都不包含枚举。另外,Months只是一个符号名称,在作用域为整个类的代码中遇到它时,编译器将用30来替换它
另一种在类中定义常量的方式——使用关键字static
static const int Months = 12;
第十一章代码
运算符重载函数
Time operator+(const Time & t) const;
morefixing.operator+(total);
total = weeding + waxing;
Time operator*(double n) const;
friend Time operator*(double m, const Time & t)
{ return t * m; } // inline definition
//这样数*对象和对象*数都实现了
友元函数
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
对于非成员重载运算符函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数。
虽然该友元函数在类声明中声明,但是它不是成员函数,因此不能使用成员运算符来调用;
虽然该友元函数不是成员函数,但它与成员函数的访问权限相同
编写函数定义。因为它不是成员函数,所以不要使用Time::限定符。另外,不要在定义中使用关键字friend
加了const好像不能当左值:os << t.hours;所以std::ostream & os不能加const
std::ostream & operator<<(std::ostream & os, const Time & t)
{
os << t.hours << " hours, " << t.minutes << " minutes";
return os;
}//return os 因为才能在同一行多次<<
如果函数功能在其他函数实现了,可以调用其他函数:
friend Time operator*(double m, const Time & t)
{ return t * m; }
转换函数1
转换函数2
第十二章代码
stringbad.h stringbad.cpp vegnews.cpp
sting1.h string1.cpp sayings1.cpp saying2.cpp
String::String(const char * s) // 构造函数
{
len = std::strlen(s); // set size
str = new char[len + 1]; // allot storage
std::strcpy(str, s); // initialize pointer
num_strings++; // set object count
}
String & String::operator=(const String & st) //赋值运算符
{
if (this == &st)
return *this;
delete [] str;//先删除本身
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
return *this;
}
new char[1]与new char分配内存量一致,但需要兼容delete []所以使用前者
delete[]与空指针兼容。因此也可用str = nullptr来指向空指针
返回对象将调用复制构造函数,而返回引用不会。
定位new运算符
将delete用于pc2和pc4时,将自动调用为pc2和pc4指向的对象调用析构函数;然而,将delete[]用于buffer时,不会为使用定位new运算符创建的对象调用析构函数。
pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1
delete [] buffer; // free buffer
对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。
原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。
C++11提供了另一种禁用方法的方式——使用关键字delete,与其将来面对无法预料的运行故障,不如得到一个易于跟踪的编译错误,指出这些方法是不可访问的。
C++为类构造函数提供了一种可用来初始化数据成员的特殊语法。这种语法包括冒号和由逗号分隔的初始化列表,被放在构造函数参数的右括号后,函数体的左括号之前。每一个初始化器都由被初始化的成员的名称和包含初始值的括号组成。从概念上来说,这些初始化操作是在对象创建时进行的,此时函数体中的语句还没有执行。语法如下:
第十三章代码
public继承
多态共有继承
希望同一个方法在派生类和基类中的行为是不同的。
Brass类和BrassPlus类都声明了ViewAcct( )和Withdraw( )方法,但BrassPlus对象和Brass对象的这些方法的行为是不同的;程序将使用对象类型来确定使用哪个版本:
Brass类在声明ViewAcct( )和Withdraw( )时使用了新关键字virtual。这些方法被称为虚方法(virtual method);对于在两个类中行为相同的方法,则只在基类中声明。
Brass类还声明了一个虚析构函数,虽然该析构函数不执行任何操作。
使用virtual,如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。因此,经常在基类中将派生类会重新定义的方法声明为虚方法。
虚析构函数:确保释放派生对象时,按正确的顺序调用析构函数,如果析构函数是虚的,将调用相应对象类型的析构函数。
如果析构函数是虚的,将调用相应对象类型的析构函数。
关键字virtual只用于类声明的方法原型中,而没有用于程序清单的方法定义中
非构造函数不能使用成员初始化列表语法,但派生类方法可以调用公有的基类方法。
如果代码没有使用作用域解析运算符,编译器将认为ViewAcct( )是BrassPlus::ViewAcct( ),这将创建一个不会终止的递归函数——这可不
好。
可以在类实现中写一些辅助函数,来防止代码重复
typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;
format setFormat();
void restore(format f, precis p);
format setFormat()
{
// set up ###.## format
return cout.setf(std::ios_base::fixed,
std::ios_base::floatfield);
}
void restore(format f, precis p)
{
cout.setf(f, std::ios_base::floatfield);
cout.precision(p);
}
静态联编和动态联编
程序调用函数时,将使用哪个可执行代码块呢,在C++中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数在编译过程中进行联编被称为静态联编(static binding),又称为早期联编(early binding)。虚函数使这项工作变得更困难,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编(dynamic binding),又称为晚期联编(late binding)。
C++不允许将一种类型的地址赋给另一种类型的指针,也不允许一种类型的引用指向另一种类型;但是,指向基类的引用或指针可以引用派生类对象,而不必进行显式类型转换。
按值传递导致只将BrassPlus对象的Brass部分传递给函数fv( )。但随引用和指针发生的隐式向上转换导致函数fr( )和fp( )分别为Brass对象和BrassPlus对象使用Brass::ViewAcct( )和BrassPlus::ViewAcct( )。
通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地
址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中(参见图13.5)。注意,无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员,只是表的大小不同而已。
使用虚函数时,在内存和执行速度方面有一定的成本,包括:每个对象都将增大,增大量为存储地址的空间;对于每个类,编译器都创建一个虚函数地址表(数组);对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。虽然非虚函数的效率比虚函数稍高,但不具备动态联编功能。
构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类的构造函数,所以将类构造函数声明为虚的没什么意义。
析构函数应当是虚函数,除非类不用做基类。如果使用默认的静态联编,delete语句将调用~Employee( )析构函数。这将释放由Singer对象中的Employee部分指向的内存,但不会释放新的类成员指向的内存。但如果析构函数是虚的,则上述代码将先调用~Singer析构函数释放由Singer组件指向的内存,然后,调用~Employee()析构函数来释放由Employee组件指向的内存。
通常应给基类提供一个虚析构函数,即使它并不需要析构函数。
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数。如果由于这个原因引起了设计问题,可以通过让友元函数使用虚成员函数来解决。
新定义将showperks( )定义为一个不接受任何参数的函数。重新定义不会生成函数的两个重载版本,而是隐藏了接受一个int参数的基类版本。总之,重新定义继承的方法并不是重载。如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管参数特征标如何。
也就是说:子类的virtual void showperks() const会覆盖掉virtual void showperks(int a)const
但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变(covariance of return type),因为允许返回类型随类类型的变化而变化,这种例外只适用于返回值,而不适用于参数。
如果基类声明被重载了,则应在派生类中重新定义所有的基类版本如果不需要修改,则新定义可只调用基类版本:
所以要么如果子类和父类的方法一样,则要么不用虚函数,在父类用就行,要么就用虚函数,但是要重新定义基类版本
用vitual不同输入参数,但同函数名也会覆盖
protected:BrassPlus类可以直接访问balance,而不需要使用Brass方法。
抽象基类
派生类不使用new
派生类使用new
派生类析构函数自动调用基类的析构函数,故其自身的职责是对派生类构造函数执行工作的进行清理。
hasDMA复制构造函数只能访问hasDMA的数据,因此它必须调用baseDMA复制构造函数来处理共享的baseDMA数据
hasDMA::hasDMA(const hasDMA & hs)
: baseDMA(hs) // invoke base class copy constructor
{
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
hasDMA::~hasDMA()
{
delete [] style;
}
hasDMA & hasDMA::operator=(const hasDMA & hs)
{
if (this == &hs)
return *this;
baseDMA::operator=(hs); // copy base portion
delete [] style; // prepare for new style
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}
对于友元:
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
os << (const baseDMA &) hs;
os << "Style: " << hs.style << std::endl;
return os;
}
第十四章代码
包含对象成员的类
使用私有或保护继承
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。
私有继承提供的特性与包含相同:获得实现,但不获得接口。
新的Student类不需要私有数据,因为两个基类已经提供了所需的所有数据成员。
对于继承类,新版本的构造函数将使用成员初始化列表语法,它使用类名而不是成员名来标识构造函数:
访问基类的方法:
Student类的代码如何访问内部的string对象呢?
使用强制类型转换
return (const string &) *this;
访问基类的友元函数
通过显式地转换为基类来调用正确的函数
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << (const string &) stu << ":\n";
stu.arr_out(os); // use private method for scores
return os;
}
使用包含还是私有继承
多重继承使得能够使用两个或更多的基类派生出新的类,将基类的功能组合在一起
如果添加一个从Singer和Waiter类派生出的SingingWaiter类后,将带来一些问题。具体地说,将出现以下问题。有多少Worker ,哪个方法?
继承两个基类对象,包含两个Worker对象拷贝
C++引入多重继承的同时,引入了一种新技术——虚基类(virtual base class),使MI成为可能
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。
通过在类声明中使用关键字virtual,可以使Worker被用作Singer和Waiter的虚基类
现在,SingingWaiter对象将只包含Worker对象的一个副本。从本质上说,继承的Singer和Waiter对象共享一个Worker对象,而不是各自引入自己的Worker对象副本。因为SingingWaiter现在只包含了一个Worker子对象,所以可以使用多态。
使用虚基类时,需要对类构造函数采用一种新的方法。对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。
SingingWaiter(const Worker & wk, int p = 0, int v = other)
: Worker(wk), Waiter(wk,p), Singer(wk,v) {}
对于哪个方法的问题,可以使用对象.Singer::Show().
Singer::show();
使用虚基类将改变C++解析二义性的方式。使用非虚基类时,规则很简单。如果类从不同的类那里继承了两个或更多的同名成员(数据或方法),则使用该成员名时,如果没有用类名进行限定,将导致二义性。但如果使用的是虚基类,则这样做不一定会导致二义性。在这种情况下,如果某个名称优先于(dominates)其他所有名称,则使用它时,即便不使用限定符,也不会导致二义性。派生类中的名称优先于直接或间接祖先类中的相同名称。任何一个omg( )定义都不优先于其他omg( )定义,因为C和E都不是对方的基类。所以,在F中使用非限定的omg( )将导致二义性。
类模板
不能将模板成员函数放在独立的实现文件中,模板必须与特定的模板实例化请求一起使用。
template <class T, int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP() {};
explicit ArrayTP(const T & v);
virtual T & operator[](int i);
virtual T operator[](int i) const;
};
递归使用模板 ArrayTP<ArrayTP<int,5>,10> twodee
使用多个类型参数 Pair<string, int>
默认类型模板参数
模板的具体化
成员模板
模板可用作结构、类或模板类的成员
template <typename T>
class beta
{
private:
template <typename V> // nested template class member
class hold
{
private:
V val;
嵌套模板
将模板作为参数
模板类和友元
第十五章代码
友元类
class Tv
{
public:
friend class Remote;
友元类方法
class Tv; // forward declaration
class Remote
{
事实上,唯一直接访问Tv成员的Remote方法是Remote::set_chan( ),因此它是唯一
需要作为友元的方法。确实可以选择仅让特定的类成员成为另一个类的友元,而不必让整个类成为友元,但这样做稍微有点麻烦,必须小心排列各种声明和定义的顺序。
在编译器在Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应该先看到Remote类的声明和set_chan( )方法的声明。
嵌套类
引发异常、try块和catch块
异常类
运行阶段类型识别(RTTI)
dynamic_cast和typeid
static_cast、const_cast和reiterpret_cast