C++编译器到达main()末尾时没有遇到返回语句,则默认return 0;是结尾
返回值类型能够是任何类型,除了数组,但能够将数组做为结构或对象组成部分返回git
显示字符串时,在字符串中包含换行符n,可减小输入量,可是endl确保程序继续运行前刷新输出(显示在屏幕上),而n不保证。程序员
C++中回车、空格、制表符的做用相同。也就是说一般能够在可以使用回车的地方使用空格,反之亦然。算法
Short至少16位
Int至少与short同样长
Long至少32位,且至少与int同样长
Long long至少64位,且至少与long同样长
Float至少32位
Double至少48位,且很多于float
Long double至少和double同样多express
<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变量dom
Short (*ps)[20] = &tell;
Ps是一个指向包含20个元素的short数组的指针
Short *ps[20] = &ti;
Ps是一个short指针数组,包含20个元素ide
在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种用法:
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关键字。
若是方法经过计算获得一个新的类对象,则应考虑是否可使用类构造函数来完成这种工做,这样不只能够避免麻烦,并且能够确保新的对象是按照正确的方式建立的。
产生随机数
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有些相像,能够接受两种参数:类名,结果为对象的表达式
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())来处理细节
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简洁易懂,且可自动推断返回类型。