1

C++编译器到达main()末尾时没有遇到返回语句,则默认return 0;是结尾
返回值类型可以是任何类型,除了数组,但可以将数组作为结构或对象组成部分返回

显示字符串时,在字符串中包含换行符n,可减少输入量,但是endl确保程序继续运行前刷新输出(显示在屏幕上),而n不保证。

C++中回车、空格、制表符的作用相同。也就是说通常可以在能够使用回车的地方使用空格,反之亦然。

Short至少16位
Int至少与short一样长
Long至少32位,且至少与int一样长
Long long至少64位,且至少与long一样长
Float至少32位
Double至少48位,且不少于float
Long double至少和double一样多

<climits>中符号常量
CHAR_BIT char的位数
CHAR_MAX char的最大值
CHAR_MIN char的最小值
SCHAR_MAX signed char 的最大值
UCHAR_MAX unsigned char 的最大值
SHRT_MAX short的最大值
INT_MAX int的最大值
LONG_MAX long的最大值
ULLONG_MAX unsigned long long的最大值

如果知道变量的初值是什么,则应当对它进行初始化。可避免以后忘记给它赋值的情况发生,{ }内不包含任何东西,则变量将被初始化为零
应当在声明中对const进行初始化
若将非const地址赋给const指针,则可以使用原地址改变const指针的数据,因此不允许。
如果条件允许,则应将指针形参指明为指向const的指针

Strlen()返回字符串长度,而不是数组本身长度,并且只计算可见字符,不包括空字符
Getline()读取一行输入,直到换行符,(或者是读取指定的字数)随后getline()丢弃换行符,
Get()读取一行输入,直到换行符,将换行符保留在输入序列中
Cin.get()不含参数时,可读取下一个字符(即使是换行符)
cin读取char值时忽略空格和换行符
cin.get(ch)读取输入中的下一个字符,(即使是空格)
测试条件可以写while( cin.get(ch) )表示读取一个字符成功

对于枚举enum只定义了赋值运算符,没有算术运算
首个枚举量默认为0,后面没有被初始化的枚举量比前面的大1,可以创建多个值相同的量
Enum bits {zero, null=0, one, numer0_uno=1};

Int p1,p2;创建一个int指针和一个int变量

Short (*ps)[20] = &tell;
Ps是一个指向包含20个元素的short数组的指针
Short *ps[20] = &ti;
Ps是一个short指针数组,包含20个元素

在C++中用引号括起的字符串像数组名一样,也是第一个元素的地址
一般来说,给cout提供一个指针,它将打印地址。但如果指针类型为char,则会打印其指向的字符串。如果要显示字符串的地址,则必须强制转换为另一种指针类型,如int

Vector效率比数组稍低,如果需要长度固定的数组,应使用array,效率相同更方便更安全
Vector<typename> vt(n_elem), array<typename, n_elem> arr;后者n_elem不能是变量
Vector是动态数组的替代品,array是定长数组的替代品
使用at()和[ ]的区别在于,at()将在运行期间捕获非法索引,程序将默认中断,但运行时间更长,

arr[i] == *(arr +i)
&arr[i] == arr + i

++n和n++对于内置类型,采用哪种格式不会有差别,但是用户定义的类型,如果有用户定义的递增和背叛运算符,则前缀的效率更高

{ }复合语句(代码块)中定义的新变量,仅当程序执行语句块中的语句时才存在

C++规定,||和&&运算符是一个顺序点(sequence point),即先修改左侧的值,再判定右侧,如果左侧为true则不会去判定右侧的表达式
冒号和逗号也是顺序点

<cctype>中的字符函数
Isalpha()
Isalnum(),字母或数字
isdigit(),数字
islower()
isupper()
ispunct()标点符号

a>b? c : d;

for循环中continue使程序跳到更新表达式处,然后跳到测试表达式处,
while循环中continue使程序直接跳到测试表达式处。

当用户输入错误时1重置cin以接受新输入,2删除错误输入3提示再输入
While(! (cin>>g[i]) ){

Cin.clear();
While(cin.get() != ‘\n’)
    Continue;
Cout<<”please enter a number: “;

}

检查文件是否被成功打开的首先方法是使用方法is_open()
inFile.open(“bow.txt”);
if ( !inFile.is_open() ){

exit(EXIT_FAILURE);

}
exit()原型在头文件<cstdlib>中定义

在C++中括号为空与括号中使用void是等效的,意味着函数没有参数
参数(argument)表示实参,参量(parameter)表示形参

递归方法有时被称为分而治之策略divide-and-conquer-strategy

使用typedef简化函数指针类型
Typedef const double (p_fun)(const double*, int); //p_fun是别名

使用内联函数通常做法是省略原型,将整个定义(即函数头和所有函数代码)放在本该提供原型的地方。内联函数不能递归,只有在函数很短时才能采用内联方式

返回引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元引用。最简单的方法是返回一个作为参数传递给函数的引用,另一种方法是用new分配新的存储空间并返回指向该内存空间的指针。不可以返回指向局部变量或临时对象的引用,因为函数执行完毕后,局部变量或临时对象将消失,引用将指向不存在的数据。

四舍五入int(f +0.5)

引用变量是一种伪装指针,它允许为变量创建别名,主要被用作处理结构和类对象的函数的参数。
使用引用参数的主要原因
1需要修改调用函数中的数据对象 2提高效率
使用引用与指针的指导原则:
对于使用传递的值而不作修改的函数
1数据对象很小,如内置数据类型或小型结构,按值传递
2数据对象是数组,用指针,这是唯一选择
3数据对象是较大的结构,用const指针或const引用
4数据对象是类对象,使用const引用(传递类对象参数的标准方式是按引用传递)
对于修改调用函数中数据的函数
1数据对象是内置数据类型,使用指针
2数据对象是数组,用指针,这是唯一选择
3数据对象是结构,使用引用或指针
4数据对象是类对象,使用引用

对于带参数列表的函数,必须从右向左添加默认值。即,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。

函数重载的关键是函数的参数列表---也称为函数特征标(function signature).如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。
编译器在检查函数特征标时,将把类型引用和类型本身视为同一特征标

后置返回类型(trailing return type)将返回类型移到了参数声明后面,可用于函数定义
Auto f (int x, float y) ->double {;}
Auto在这里是一个占位符,表示后置返回类型提供的类型
Template<class T1, class T2>
Auto f(T1 x, T2 y) ->decltype(x+y)
{

Return x+y ;

}
现在,decltype在参数声明后面,因此x和y位于作用域内,可以使用它们。

如果没有显示的初始化静态变量,编译器将把它设置为0,静态数组和结构将每个元素或成员的所有位都设置为0
代码块中使用static时,将导致局部变量的存储持续性为静态的。这意味着该变量只在该代码块中可用,该代码块不处于活动状态时仍然存在,两次函数调用之间,静态局部变量的值将保持不变,(静态变量适用于再生—可以用它将瑞士银行的秘密账号传递到下一个要去的地方)。另外如果初始化了静态局部变量,则程序只在启动时进行一次初始化,以后再调用函数时,将不会像自动变量那样再次被初始化。
C++不允许在一个函数中定义另外一个函数,因此所有的函数的存储持续性都自动为静态的。可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用,这要求必须在原型和函数定义中使用该关键字
Static int private(double x);

Static int private(double x){;}

通常,new负责在堆(heap)中找到一个足以满足要求的内存块,new运算符还有另一种变体,被称为定位(placement)new运算符,它让您能够指定要使用的位置。程序员可以使用这种特性来设置其内存管理规程,处理需要通过特定地址进行访问的硬件或在特定位置创建对象。
要使用定位new特性,首先需要包含头文件<new>,它提供的这种版本的new运算符的原型;然后将new运算符用于提供了所需地址的参数。除需要指定参数外,句法与常规new运算符相同。下面的代码段演示了new运算符的4种用法:

include <new>

Struct chaff
{

Char dross[20];
Int slag;

};
Char buffer1[50];
Char buffer2[500];
Int main()
{

Chaff *p1, *p2;
Int *p3, *p4;

//first, the regular forms of new

P1 = new chaff;    //place structure in heap
P3 = new int[20];    //place tructure in heap

//now, the two forms of placement new

P2 = new(buffer1) chaff;    //place structure in buffer1
P4 = new(buffer2) int[20];    //place int array in buffer2


}

Delete只能用于指向常规new运算符分配的堆内存的指针,如果使用new[]来分配内存,则应使用delete[]来释放内存。而使用定位new运算符分配内存的对象,必须显式的调用析构函数,这是需要显式调用析构函数的少数几种情形之一。如果有指向对象的指针,可以这样做:ptr-> ~Test();

Using debts::Debt; //makes the Debt structure definition available
Using debts::showDebt; //makes the showDebt function available
注意using声明只使用了名称,例如第二个声明没有描述showDebt的返回类型或特征标,而只给出了名称,因此,如果函数被重载,则一个using声明将导入所有版本。

C++程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象
定义位于类声明中的函数都将自动成为内联函数

在设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数,但只能有一个默认构造函数。默认构造函数可以没有任何参数,如果有,则必须给所有参数提供默认值。如果有多个构造函数,则必须以相同的方式使用new,要么都带[],要么都不带[],因为只有一个析构函数,所有的构造函数都必须与它兼容。

只接受一个参数的构造函数定义了从参数类型到类类型的转换,如果使用关键字explicit限定了这种构造函数,则它只能用于显示转换(bean = B(10) ),否则也可以用于隐式转换。构造函数只用于从某种类型到类类型的转换。要进行相反的转换,必须使用特殊的C++运算符函数—转换函数。并且最好使用显示转换而避免隐式转换(explicit operator int()/double() const;)相关情况下,将加法定义为友元可以让程序更容易适应自动类型转换,原因在于,两个操作数都成为函数参数,因此与函数原型匹配(total = p+j;转换为total = operator+(p+j);)如果经常需要将double值与类对象相加,则重载加法更合适;如果只是偶尔使用则依赖自动转换更简单,但为了更保险,可以使用显式转换

就像应尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象,就应将其声明为const(类方法void stack::show() const)
Const Stock& Stock::topval(const Stock& s) const;该函数隐式地访问一个对象,而显示地访问另一个对象,并返回其中一个对象的引用。括号中的const表明不会修改被显式访问的对象,而括号后面的const表明不会修改被隐式访问的对象,由于返回了两个const对象之一的引用因此返回类型也为const引用。

初始化对象数组的方案是,首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。因此要创建类对象数组,则这个类必须有默认构造函数。

Class Bakery
{
Private:

const int Months = 12;    //wrong
Static const int Months =12;    //right

Enum{Months = 12}; //right

Double costs[Months];
…

}
类声明只是描述了对象的形式,并没有创建对象。因此在创建对象前没有用于存储值的空间,cosnt方法是行不通的。
在类声明中声明的枚举的作用域为整个类,因此可以用枚举为整型常量提供作用域为整个类的符号名称。注意,用这种方式声明枚举不会创建类数据成员。也就是说,所有对象中都不包含枚举。另外Months只是一个符号名称,在作用域为整个类的代码中遇到它,编译器将用12来替换它。由于这里使用枚举只是为了创建符号常量,并不打算创建枚举类型的变量,因此不需要提供枚举名。
可以使用关键字static在类中定义常量,这将创建一个名为Months的常量,该常量将与其他静态变量存储在一起,而不是存储在对象中。因此只有一个Months常量,被所有Bakery对象共享。

Time Time::operator+(const Time& t) const { }

Total = coding + fixing;
这将调用operator+()方法。在运算符表示法中,运算符左侧的对象(这里为coding)是调用对象,运算符右边的对象(这里为fixing)是作为参数被传递的对象

=,(),[],->只能通过成员函数进行重载

对于非成员重载运算符函数来说运算符表达式左边的操作数对应于运算符函数的第一个函数,运算符表达式右边的操作数对应于运算符函数的第二个参数。
当运算符函数是成员函数时,第一个操作数将是调用该函数的对象,
如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数的顺序。比如:重载<<运算符,使之可以与cout一起使用,要让ostream对象成为第一个操作数,需要将运算符函数定义为友元,并返回ostream&
ostream& operator<<(ostream& os, const c_name& obj)
{

os<< …;
return os;

}

因为operator<<()直接访问Time对象的私有成员,所以它必须是Time类的友元;但由于它并不直接访问ostream对象的私有成员,所以并不一定必须是ostream类的友元。

只有在类声明中的原型中才能使用friend关键字,除非函数定义也是原型,否则不能在函数定义中使用friend关键字。

如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作,这样不仅可以避免麻烦,而且可以确保新的对象是按照正确的方式创建的。

产生随机数

include <cstdlib>

include <ctime>

srand(time(0)); //seed random-number generator
D = rand()%360; //
C++自带了一个<random>头文件,产生随机数的功能很强大

类.h中的static成员在类.cpp中初始化(int Class_n:: numb = 0;)注意在类声明中不能初始化静态成员变量,因为声明描述了如何分配内存,但并不分配内存。初始化语句指出了类型,并使用了作用域运算符但没有使用关键字static。有一种例外情况:静态数据成员为const整数类型或枚举型,则可以在类声明中初始化。

每当程序生成了对象副本时,编译器都将使用复制构造函数,具体地说,当函数按值传递对象或返回对象时,都将使用复制构造函数,由于按值传递对象将使用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。

默认的复制构造函数逐个复制非静态成员,复制的是成员的值(也称浅复制),如果成员本身也是类对象,将使用这个类的复制构造函数来复制成员对象。静态函数不受影响,因为它们属于整个类,而不是各个对象。
如果构造函数包含静态数据成员,并且其值在新对象创建时发生变化,则应该提供一个显式复制构造函数来处理计数问题,有时必须提供一个复制构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。这需要深度复制。类似于深度赋值1,检查自我赋值情况,2,释放成员指针以前指向的内存,3,复制数据而不仅仅是数据的地址,4,返回一个指向调用对象的引用,以连续赋值。

使用new的类通常需要包含显式复制构造函数和执行深度复制的赋值运算符,是否需要显式提供取决于默认的成员复制是否合适
通常构造函数使用new时,需要注意:析构函数、复制构造函数、重载赋值运算符
派生类使用new时,必须为派生类定义,显式析构函数、复制构造函数、赋值运算符

函数应当避免将对象赋给自身;否则给对象重新赋值前,释放内存操作可能删除对象的内容赋值运算符返回一个指向调用对象的引用,这位做可以像常规赋值操作那样,连续进行赋值。

在重载时,C++区分常量和非常量函数的特征标

可以将成员函数声明为静态的(函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static)。后果有两个,1,不能通过对象调用静态成员函数,实际上,静态成员函数也不能使用this指针。如果静态成员函数是在公有部声明的,则可以使用类名和作用域解析运算符来使用它。2,由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员

有关返回对象的说明
首先,返回对象将调用复制构造函数,而返回引用不会。
其次,引用指向的对象应在调用函数执行时存在,局部变量是不行的,因为已经被析构
最后,参数声明为const时,若返回参数引用,返回类型必须为const
常见的返回非const对象情形是,重载赋值运算符或重载与cout一起使用的<<运算符,前者旨在提高效率,而后者是只能这样做。

类中包含const成员或声明为引用的成员时,构造函数必须使用成员初始化列表语法(member initializer list),从概念上说,调用构造函数时,对象将在括号中的代码执行之前被创建,调用构造函数将导致程序先给类成员变量分配内存,然后程序流程进入到括号中,再使用常规赋值方式将值存储到内存中,因此,对于const数据成员,必须在执行到构造函数体之前,即创建对象时进行初始化。C++提供了一种特殊的语法完成上述工作,即成员初始化列表,由逗号分隔的初始化列表组成(前面带冒号)它位于参数列表的右括号之后,函数体左括号之前,通过初值可以是常量或构造函数的参数列表中的参数,这种方法并不限于初始化常量,其他类成员亦可,但只有构造函数可以使用这种初始化列表语法。
Queue::Queue(int qs) : qsize(qs) //qsize是const类型
{

front = rear = nullptr;
items = 0;

}

Queue:Queue(int qs) : qsize(qs), front(nullptr), rear(nullptr), items(0)
{
}
const与引用一样,只能在被创建时进行初始化,对于简单的数据成员使用成员初始化列表语法与函数体中赋值没什么区别,但,对于本身就是类对象的成员来说,成员初始化列表的效率更高,
数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化列表中的排列顺序无关。
在类定义中初始化,等价于成员初始化列表,然而如果构造函数调用成员初始化列表,则类内初始化将被覆盖

派生类构造函数必须使用基类构造函数,创建时派生类对象时,C++使用成员初始化列表语法首先创建基类对象,如果不调用基类构造函数,程序将使用默认的基类构造函数,派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。

派生类对象可以使用基类的方法,条件是方法不是私有的,基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用可以在不进行显式类型转换的情况下引用派生类对象,但,基类指针或引用只能调用基类方法。
如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。这样,程序将根据对象类型而不是引用或指针的类型来选择方法版本。为基类声明一个虚析构函数也是一种惯例,(除非该类不用做基类),这样当通过指向对象的基类指针或引用来删除派生类对象时,程序将首先调用派生类的析构函数,然后调用基类的析构函数,而不仅仅是调用基类的析构函数。

公有继承是最常用的方式,它建立一种is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。
私有继承是has-a关系的一部分,获得实现,不获得接口。包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未命名的继承对象添加到类中。
保护继承是私有继承的变体,也是has-a关系。

包含建立的也是has-a关系,与私有继承和保护继承相比,包含更容易实现和使用,通常优先采用包含的方式。然而私有继承和保护继承比包含有一些不同的功能,例如,继承允许派生类访问基类的保护成员,还允许派生类重新定义从基类继承的虚函数,另一方面如果需要使用某个类的几个对象,则用包含理适合。

多重继承MI使得能够在类设计中重用多个类的代码。MI会带来一些问题,即多次定义同一个名称,继承多个基类对象等。可以使用类限定符来解决名称二义性问题,使用虚基类避免继承多个基类对象的问题,但,使用虚基类后,就需要为编写构造函数初始化列表以及解决二义性问题引入新规则。

重新定义继承的方法并不是重载,如果重新定义派生类中的函数,不只是使用相同的函数参数列表覆盖基类声明,而是隐藏所有同名基类方法。这引出了两条经验规则:1,如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类的引用或指针,则可以修改为指向派生类的引用或指针,这称为返回类型协变(covariance of return type)2,如果基类声明被重载了,应在派生类中重新定义所有的基类版本。如果不需修改,则新定义可以只调用基类版本。(void h::show() const {L::show();},没有重新定义的版本将被隐藏,派生类对象将无法使用它们。

对于外部世界来说,保护成员的行为与私有成员相似,但对于派生类来说,保护成员的行为与公有成员相似。对于成员函数来说,保护访问控制很有用,它让派生类能够访问公众不能使用的内部函数。

C++通过使用纯虚函数(pure virtual function)提供未实现的函数。纯虚函数声明的结尾处为=0,(virtual double Area() const = 0;)当类声明中包含纯虚函数时,不能创建该类的对象。这里的理由是:包含纯虚函数的类只用作基类(abstract base class, ABC),抽象基类。C++允许纯虚函数有定义,可以在基类实现文件中进行定义,而将原型声明为虚的(void Move(int nx, int ny) = 0,总之,在原型中使用=0指出类是一个抽象基类,在类中可以不定义该函数。ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征使用常规虚函数来实现这种接口。
不一定非得定义纯虚方法,对于包含纯虚成员的类,不能使用它来创建对象,纯虚方法用于定义派生类的通用接口。

当基类和派生类都采用动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符都必须使用相应的基类方法秋处理基类元素,这种要求是通过三种不同的方式完成的,1,析构函数自动完成,2,构造函数,通过在初始化成员列表中调用基类的复制构造函数来完成,如果不这样做将自动调用基类的默认构造函数,3,赋值运算符,通过使用作用域解析运算符显式调用基类赋值运算符完成。
构造函数、析构函数、赋值运算符都是不能被继承的,因为基类的构造函数、析构函数都在派生类构造和析构时使用,而赋值运算符是因为包含一个类型为其所属类的形参,

ostream等友元不是成员函数,所以派生类实现文件中不能使用作用域解析运算符来指出使用基类与派生类中哪个operator<<函数,只能使用强制类型转换,以匹配原型时能选择正确的函数
std::ostream& operator<<(std::ostream& os, const hasDMA& hs)
{

os<<(const baseDMA&)hs;
os<<”style:”<<hs,stytle;
return os;

}

私有继承提供无名称的子对象成员,而包含提供显式命名的对象成员。
在构造函数中包含使用成员名标识构造函数
Student(const char* str):name(str) {}
私有继承使用类名:
Student(const char* str):std::string(str) {}
包含使用对象名调用方法,私有继承使用类名和作用域解析运算符调用方法
在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针。
包含能够包括多个同类的子对象,而私有继承只能使用一个某类型的对象。
通常应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员或需要重新定义虚函数,则应使用私有继承。

使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员,假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法;另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员,即使采用的是私有派生。例如希望Student可以使用val的方法max()
class Student :private std::string, private std::val<double>
{

public:

using std::val<double>::max


};
上述using声明使得基类方法就像是派生类方法一样,但只适用于继承,不适用于包含。
cout<<”high score: “<<ada[i].max()<<endl;

using C1::fn;
double fn(double){};
派生类C2中的using声明让C2对象可使用基类C1的三个fn()方法,但将选择C2而不是C1定义的fn(double)

在C++11中,可使用虚说明符override指出您要覆盖的一个虚函数:将其放在参数列表后面,如果声明的与基类方法不匹配,编译器将视为错误
virtual void f(char* ch) const override {std::cout<<val();}
说明符final解决了另一个问题。您可能想禁止派生类覆盖特定的虚方法,为此可在参数列表后面加上final
virtual void f(char* ch) const final {std::cout<<val();}

在多重继承MI中必须使用关键字public限定每一个基类,因为编译器默认私有派生。C++引入多重继承的同时,引入了一种新技术—虚基类(virtual base class),使得MI成为可能,虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。例如可在类声明中使用关键字virtual使得Worker被用作Singer和Waiter的虚基类(virtual和public的次序无关紧要)
class Singer: virtual public Worker {…};
class Waiter: public virtual Worker {…};
class SingingWaiter: public Singer, public Waiter {…};
现在SingingWaiter对象只包含Worker对象的一个副本。

虚基类和虚函数之间并不存在明显的联系。

如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。C++在基类是虚的时,禁止信息通过中间类自动传递给基类,因为多重继承MI有多条途径传递信息给虚基类,会发生冲突。
以下是禁止的:
SingingWaiter(const Worker& wk, int p=0, int v =Singer::other)

:Waiter(wk,p), Singer(wk,v) {}

以下是正确的:
SingingWaiter(const Worker& wk, int p=0, int v =Singer::other)

:Worker(wk), Waiter(wk,p), Singer(wk,v) {}

对于单继承,如果没有定义Show(),将使用最近祖先中的定义,而在MI中每个直接祖先都有一个Show()函数,这会产生二义性。
解决方法一:使用模块化方式,而非递增方式,即提供一个只显示Worker组件的方法和一个只显示Waiter组件或Singer组件的方法。然后在SingingWorker::Show()方法中组合起来。
void Worker::Data() const
{

cout<<”name: “<<fullname<<”\n”;
cout<<”Employee ID: “<<id<<”\n”;

}
void Waiter::Data() const
{

cout<<”panache rating:”<<panache<<”\n”;

}
void Singer::Data() const
{

cout<<”Vocal range:”<<pv[voice] <<”\n”;

}
void SingingWaiter::Data() const
{

Singer::Data();
Waiter::Data();

}
void SingingWaiter::Show() const
{

cout<<”Category: singing waiter\n”;
Worker::Data();
Data();

}
与此相似,其他Show()方法可以组合适当的Data()组件。且Data()应当设置为保护的方法,这样便只能在继承层次结构中的类中使用它,在其他地方不能使用
总之在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则,另外,如果在编写这些类时没有考虑到MI,则还可能需要重新编写它们。
通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。

派生类中的名称优先于直接或间接祖先类中的相同名称。

template <class T, int n>
模板头中的表达式参数可以是整型、枚举、引用或指针。(double m 是非法的,double*m是合法的),另外模板代码不能修改参数的值,也不能使用参数的地址,所以在模板中不能使用诸如n++和&n等表达式。另外实例化模板时,用途表达式参数的值必须是常量表达式。

template <class T1, class T2 =int>
class Topo{…};
可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数提供默认值。

TP<double, 30> *pt;创建指针
pt = new TP<double, 30>创建对象
编译器在需要对象之前,不会生成类的隐式实例化,类定义(实例化)在声明类对象并指定特定类型时生成

当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化(explicit instantiation)例如下面的声明将TP<string, 100>声明为一个类:
template class TP<string, 100>;

显式具体化是特定类型的定义,要提供一个专供const char*使用的Sorted模板,示例如下:
template <> class Sorted<const char*>{…};
C++允许部分具体化,即给类型参数之一指定具体的类型。
template <class T1, class T2> class Pair{};
template<class T1> class Pair<T1, int>{};
template后面的<>声明的是没有被具体化的类型参数,因此第二个声明将T2具体化为int但T1不变,注意,如果指定所有的类型,则<>内将为空,这将导致显式具体化。

模板类的约束模板友元函数
首先,在类定义前面声明每个模板函数
template <class T> void counts();
template <class T> void report(T&);
然后,在函数中再次将模板声明为友元,这些语句根据类型参数的类型声明具体化。
template <class TT>
class HasT
{

friend void counts<TT>();
friend void report<>(HasT<TT>&);

};
声明中<>指出这是模板具体化,对于report(),<>可以为空,因为可以从函数参数推断出如下模板类型参数:HasT<TT>
但counts()函数没有参数,因此必须使用模板参数语法<TT>指明其具体化。还需要注意的是TT是HasT类的参数类型。
最后,为友元提供模板定义。

模板类的非约束模板友元函数
通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元,对于非约束友元,友元模板类型参数与模板类类型参数是不同的。
template <class T>
class Many
{

template <class C, class d> friend void show(C&, D&);

};

可以使用using为模板具体化指定别名
template<class T>

using arrtype = std::array<T, 12>;

这将arrtype定义为一个模板别名,可使用它来指定类型
arrtype<double> gallons;
arrtype<int> days;
arrtype<std::string> months;
C++允许将语法using =用于非模板。与常规typedef等价,介可读性更强
typedef const char* pc1;
using pc2 = const char*
typedef const int(pa1)[10];
using pa2 = const int()[10];

友元类的友元声明可以位于类的公有、私有或保护部分,其所有的位置无关紧要。
friend class Remote;
友元成员函数则必须小心排列各种声明和定义的顺序。
class Tv; //forward declaration
class Remote{}; //Tv-using methods as prototypes only
class Tv{};
//put Remote method definitions here

typeid运算符使得能够确定两个对象是否为同种类型。它与sizeof有些相像,可以接受两种参数:类名,结果为对象的表达式

include<typeinfo>

typeid(Magnificent) == typeid(*pg)
cout<<”now processing type”<<typeid(*pg).name();
如果在扩展的if else语句系列中使用了typeid则应考虑是否应该使用虚函数和dynamic_cast

dynamic_cast使得能够在类层次结构中进行向上转换,而不允许其他转换。

High bar;
const High* pbar = &bar;

High pb = const_cast<High>(pbar);
*pb成为一个可用于修改bar对象值的指针,它删除了const标签。提供该运算符的原因是,有时候可能需要这样一个值,它在大多数时候是常量,而有时又是可以修改的。在这种情况下,可以将该值声明为const并在需要的时候使得const_cast

static_cast<type-name>(expression)
仅当type-name可被隐式转换为expression所属的类型或expression可被隐式转换为type-name所属的类型时,上述转换才合法

嵌套类是在其他类中声明的类,它有助于设计这样的助手类,即实现其他类,但不必是公有接口的组成部分。
有两种访问权限适合于嵌套类,1,嵌套类的声明位置决定了嵌套类的作用域,即它决定了程序的哪些部分可以创建这种类的对象,2,和其他类一样,嵌套类的公有部分、保护部分、私有部分控制了对类成员的访问,在哪些地方可以使用嵌套类以及如何使用嵌套类,取决于作用域和访问控制。

程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做
如果程序要使用多个指向同一个对象的指针,应选择shared_ptr这样的情况包括:1,有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大最小元素。2,两个对象都包含第三个对象的指针;3,STL容器包含指针。
如果程序不需要多个指向同一个对象的指针,可使用unique_ptr.如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权将转让给接受返回值的unique_ptr,可将unique_ptr存储到STL容器中,只要不调用将一个unique_ptr复制或赋给另一个的方法或算法(如sort())

所有STL容器都提供了一些基本方法,
size() 返回容器中元素数目
swap() 交换两个容器内容
begin() 返回一个指向容器中第一个元素的迭代器
end() 返回一个表示超过容器尾的迭代器
迭代器的行为就像指针。模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型,还有一个C++自动类型推断很有用的地方
vector<double>::iterator pd = scores.begin();
改为 auto pd = scores.begin();

erase(it1, it2) 删除给定区间元素
insert(it1, it2, it3) 插入位置、插入起止区间
vector的成员函数swap()效率比非成员的高,但非成员的能够交换两个类型不同的容器的内容。
for_each(it1, it2, f );将被指向的函数应用于区间各元素,但不能修改元素值,适合所有容器
基于范围的for循环是为用于STL设计的
for(auto x: books) show(x);
for(auto& x:books) chage(x);
Random_shuffle(it1, it2) 随机排列区间元素,要求容器类允许随机访问
copy(it1, it2, it3)复制区间,第一个元素复制到的位置

为区分++运算符的前缀后缀版本,C++将operator++作为前缀版本,将operator++(int)作为后缀版本,其中的参数永远也不会被用到,所以不必指定其名称。

作为一种编程风格,最好避免直接使用迭代器,而应尽可能使用STL函数(如for_each())来处理细节

include<iterator>

ostream_iterator<int,char> out_iter(cout,” ”);
copy(v.begin(), v,end(), out_iter);
out_iter迭代器是一个接口,让您能够使用cout来显示信息,4个参数分别表示,数据类型,字符类型,要使用的输出流,每个数据项后显示的分隔符。也可以直接使用匿名迭代器
copy(v.begin(), v,end(), ostream_iterator<int,char> (cout,” ”));
使用反向迭代器反向显示内容
copy(v.rbegin(), v,rend(), out_iter);
如果可以在显式声明迭代器和使用STL函数来处理内部问题,之间选择,请采用后者,后一种方法做的工作少,人为出错机会少。
out_iter可以换成其他迭代器如下,复制算法就变为插入算法
back_insert_iterator
front_insert_iterator
insert_iterator//将元素插入到构造函数参数指定的位置前面。
声明方法为:insert_iterator<vector<int>> insert_iter(v, v,begin());

容器种类:
deque如果多数操作发生在序列的起始和结尾处,应考虑使用
list
queue
priority_queue
stack
vector是最简单的序列类型,除非其他类型特殊优点更好满足要求,否则应使用这种
map
multimap
set
multiset
bitset
forward_list
unordered_map
unordered_multimap
unordered_set
unordered_multiset
其中bitset不视为容器,视为一种独立的类别
关联容器的优点在于,提供了对元素的快速访问,与序列相似,关联容器也允许插入新元素,但不能指定元素的插入位置,原因是关联容器通常有用于确定数据放置位置的算法,以便能够快速检索信息,(通常是使用某种树实现的)
无序关联容器是基于数据结构哈希表的,旨在提高添加和删除元素的速度以及提高查找算法的效率

很多STL算法都使用函数对象--也叫函数符(functor)函数符是可以以函数方式与()结合使用的任意对象,包括函数名、指向函数的指针和重载了()运算符的类对象

STL是一个容器类模板、迭代器类模板、函数对象模板、和算法函数模板的集合,它们的设计是一致的,都是基于泛型编程原则的。算法通过使用模板,从而独立于所存储的对象的类型,通过使用迭代器接口,从而独立于容器的类型,迭代器是广义指针。
STL算法可用于非STL容器,如常规数组、string对象、array对象以及您设计的秉承STL迭代器和容器规则的任何类

模板类complex和valarray支持复数和数组的数值运算。

通常使用缓冲区可以更高效地处理输入和输出,通常缓冲区为512字节或其整数倍,当标准输出连接的是硬盘上的文件时,缓冲可以节省大量的时间。键盘输入每次提供一个字符,因此在这种情况下,程序无需缓冲区来帮助匹配不同的数据传输速率,然而,对键盘输入进行缓冲可以让用户在将输入传输给程序之前返回并更正。

多数C++实现都会在输入即将发生时刷新缓冲区
控制符flush刷新缓冲区,而控制符endl刷新缓冲区并插入一个换行符
关闭文件将刷新缓冲区,从而确保文件被更新。

如果需要同时打开两个文件,则必须为每个文件创建一个流。然而,如果要依次处理一组文件,例如可能要计算某个名称在10个文件中的出现次数,则可以打开一个流,并将它依次关联到各个文件,这在节省计算机资源方面比每个文件打开一个流的效率高。

程序读取并显示整个文件后,将设置eofbit元素,这使程序相信,它已经处理完文件,并禁止对文件做进一步的读写,使用clear()方法重置流状态,并打开eofbit后,程序便可以再次访问该文件。

对于其他类型的指针,C++将其对应于void,并打印地址的数值表示,如果要获得字符串的地址,则必须将其强制转换为其他类型如(cout<<(void) amout;

C++11新增了右值引用,这是使用&&表示的,右值引用可关联到右值,即可出现在赋值表达式右边,但不能对其应用地址运算符的值,右值包括字面常量(C字符串除外,它表示地址)、诸如x+y等表达式、返回值的函数(条件是该函数返回的不是引用)
int x = 10;
int y = 23;
int && r1 =13;
int && r2 = x+y;
double &&r3 = std::sqrt(2.0);
注意,r2关联到的是当时计算x+y得到的结果。也就是说,r2关联到的是33,即使以后修改了x或y,也不会影响到r2
将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址。
引入右值引用的主要目的之一是实现移动语义。

在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,这意味着右值引用参数不应是const,,移动赋值运算符也一样

lambda函数
[](int x) {return x&3 ==0;}
返回类型相当于使用decltyp根据返回值推断得到的,这里为bool,如果不包含返回语句,推断出的返回类型将为void
仅当lambda表达式完全由一条返回语句组成时,自动类型推断才管用,否则,需要使用新增的返回类型后置语法
[](double x)->double{int y=x; return x-y;}

可给lambda指定一个名称,像使用函数那样使用有名称的lambda
auto mod = [](int x){return x%3 == 0;}
bool result = mod(z) //result is true if z%3==0
最后lambda有一些额外的功能。具体地说,lambda可访问作用域内的任何动态变量;要捕获使用的变量,可将其名称放在[]内。
如果只指定了变量名,如[z]将按值访问变量;
如果在名称前加上&如[&count]将按引用访问变量。
[&]能够按引用访问所有动态变量。
[=]能够按值访问所有动态变量。
[ted, &ed]能够按值访问ted以及按引用访问ed,
[&, ted]能够按值访问ted以及按引用访问其他所有动态变量,
[=, &ed]能够按引用访问ed以及按值访问其他所有动态变量

C++引入lambda的主要目的是将类似于函数的表达式用作接受函数指针或函数符的函数的参数。因此,典型的lambda是测试表达式或比较表达式,可编写为一条返回语句,这使得lambda简洁易懂,且可自动推断返回类型。


123654_
81 声望5 粉丝

君子曰:学不可以已。