类的默认访问属性是private
给类添加成员函数不会影响类对象的大小
class CBox
{
.....
CBox()=default; //default关键字指定,无参CBox构造函数应包含在类中,就是默认构造函数
}
在构造函数头初始化成员比使用赋值语句的效率高
对于const或引用类型的类成员,其初始化方式是无法选择的,唯一的方式是在构造函数中使用成员初始化列表。
构造函数声明为explicit,则只能显示地调用它,且不用于隐式转换
友元函数不是类的成员,它只是拥有特殊权限的普通全局函数
任何成员函数执行时,都自动包含一个名为this的隐藏指针,它指向调用该函数时使用的对象
仅当某个函数时类成员时,将其声明为const才有意义,其作用是使该函数中的this指针成为const,这意味着不能在该函数的定义内在赋值语句左边写上类的数据成员。const成员函数不能调用同类非const成员函数
每个静态数据成员只有一个实例存在,与定义了多少类对象无关,只有希望它初始值非0才需要初始化它
静态成员函数只能使用静态数据成员,即使本类的任何对象都不存在,它们也能存在并被使用
用指针动态创建对象后,调用delete操作符将在释放该对象占用的内存前首先调用该对象的析构函数。
为了避免对复制构造函数的无穷调用,必须将形参指定为const引用
如果动态地为类的成员分配空间,则必须实现复制构造函数
重载操作符
class CBox;
CBox box1;
CBox box2;
if(box1<box2)//等价于
if(box1.operator<(box2))
当定义以现在的同类对象进行初始化的类对象,或者通过以传值方式给函数传递对象时,调用默认复制构造函数。
当赋值语句的左边和右边是同类类型的对象时,调用默认赋值运算符。如果需要给类的数据成员动态分配空间,则必须实现赋值运算符。
区分重载运算符前缀和后缀形式的首要方法是利用形参列表。前缀形式没有形参,后缀形式又一个int类型的形参,后缀运算符函数的形参只是为了将其同前缀形式区别开来,除此之外它在函数实现中没有任何用处。
在后缀形式中,操作数是在表达式中使用其当前值之后递增的,要实现这一点,需要再递增当前对象之后创建当前对象的副本,并在修改过当前对象之后返回新创建的副本对象
函数对象提供了一种方式,可以将函数作为实参传递给另一个函数
正常的低效率 moto3=moto1+moto2;就存在2个复制临时对象的操作
CMessage operator+(const CMessage& aMess) const
{
cout<<"add operator function called"<<end;
size_t len{strlen(m_pMessage)+strlen(aMess.m_pMessage)+1};
CMessage message;
message.m_pMessage=new char[len];
strcpy_s(message.m_pMessage,len,m_pMessage);
strcat_s(message.m_pMessage,len,aMess.m_pMessage);
return message;
}
高效版本
CMessage& operator=(CMessage&& aMess)
{
cout<<"Move assignment opeartor function called".<<endl;
delete[] m_pMessage;
m_pMessage=aMessage.m_pMessage;
aMessage.m_pMessage=nullptr;
return *this;
}
具有rvalue引用形参的复制构造函数称为移动构造函数。
如果在类中定义operator=()成员函数和复制构造函数时,将形参定义为非常量rvalue引用,则需要确保也定义了具有const lvaue引用形参的标准版本。否则,编译器会根据它们的默认版本,逐一成员进行复制。
utility头文件提供了std::move()函数,可以强制使一个lvalue变成rvalue。避免当一个类包含另一个类而导致重新效率地下问题
模板类的定义方式
template <typename T>
class CSamples
{
T max() const;
}
template<typename T>
T CSamples<T>::max() const
{
T theMax{m_Values[0]};
...
}
utility头文件声明的std::forward()函数模板,如果给它传送一个rvalue引用实参,它就把该实参返回为rvalue,如果给它传送一个lvalue引用,它就把该实参返回为一个lvalue引用。称为完美转发,就是传进来是rvalue就让它继续是rvalue,是lvalue就继续
函数模板的默认实参
template<typename T,typename R=double>
R sigma(T values[],size_t count)
{
...
}
使用时
sigma<int,float>(...)
尖括号必不可少,即使没有形参值,也不能省略它们
模板特列。模板特例是为了一组特殊的形参值制定的一个额外的模板定义
template<>
CBox average(CBox boxer[],size_t count)
{
...
}
带空尖括号的第一行代码高速编译器,这是已有模板的一种特殊情况,模板实参由编译器通过第一个实参的类型来判断
当实参满足模板特例定义的形参时就会调用模板特例,还有部分特例的情况
template<typename T>
class CSamples<T*>
template<size_t Size>
class CSamples<CBox,Size>
#pragma once //防止该文件内容多次嵌入到源代码中。
#ifndef BOX_H //他们是任何C++编译器都支持的指令,只要没有定义符号BOX_H,编译过程中就将嵌入#ifndef指令后面一
#define BOX_H // 值到#endif指令之前的所有代码行。#Ifndef后面那一行定义了符号BOX_H,从而确保该头文件不被二次
#endif //嵌入
string内容
length()成员函数来查看字符串对象所封装的字符串长度。
string bees(7,'b') //string is "bbbbbbb"
string sentence{"this sentence is false."} string part{sentence,5,11}//从第五位开始,11个字符。
string每次用+运算符连接两个字符串时,都是在创建一个新的string对象,会带来开销,但是会尽可能使用移动语义,+运算符创建一个包含合并后的字符串的新字符串对象。+=运算符将作为右操作数的字符串或字符附加到作为左操作数的string对象后面,因此直接修改string对象,而不创建新的对象。
用at()成员函数与用[]操作符得到的结果相同。但是利用下标值得速度比使用at()函数块,但缺点是没有检查索引的有效性,如果索引超出了范围,那么使用下标操作符的结果将不确定,另一方面,使用at(),如果索引无效会抛出一个out_of_range异常。
substr()函数的第一个实参是要提取的子字符串中的第一个字符,第二个实参是子字符串中的字符个数
append()函数,可以在字符串末尾添加一个或多个字符
swap()成员函数可以交换封装在两个string对象中的字符串
c_str()如果需要将一个字符串对象转换为以空字符结尾的字符串,但内容不变。
find_first_of(),find_last_of(),find_first_not_of(),find_last_not_of()等等。。。
派生类不继承的基类成员仅有析构函数,构造函数以及任何重载赋值运算符的成员函数。
字符串的长度不影响对象的大小,因为容纳字符串的内存是在空闲存储器上分配的
创建派生类对象的基类部分实际上属于基类构造函数而非派生类构造函数的任务,基类构造函数总是先于派生类构造函数被调用,因为基类是派生类构建的基础,所以必须先创建基类。
基类的保护成员可以由派生类的函数访问。
派生类对象析构函数的调用顺序与构造函数相反,销毁对象时首先调用派生类的析构函数再调用基类的析构函数。
如果将基类的成员声明为private,则它们在派生类中永远都不可访问
如果将基类声明为Public,其成员在派生类中的访问级别保持不变。
如果将基类声明为protected,其public成员在派生类中将成为protected.
有时需要确保不能把类用作基类,为此可以把类指定为final,如:class CBox final
类友元关系是不可继承的
要使某个函数表现出虚函数的行为,该函数再任何派生类中都必须有与基类函数相同的名称,形参列表和返回类型。使用override修饰符可以告诉编译器派生类中的某虚函数重写了基类中的虚函数。如virtual double volume() const override,这时编译器会检查基类中是否有相同的签名volumn()函数,如果没有,会收到一个错误消息
有时希望禁止重写类的某个函数成员,可以把成员函数指定为final.
抽象类可以拥有数据成员和函数成员,纯虚函数是否存在是判断给定的类是否是抽象类的唯一条件,同样的道理,抽象类可以拥有多个纯虚函数,这种情况下,派生类必须给出基类中每个纯虚函数的定义,否则将仍然是抽象类。
dynamic_cast专门适合用于基类和派生类之间的转换,不仅可以应用于指针,也可以应用于引用,dynamic_cast与static_cast之间的区别在于,dynamic_cast操作符在运行时检查转换的有效性,如果操作无效,则结果为nullptr
嵌套类可以自由访问封装类的所有静态成员,非静态不能访问,封装类只能访问嵌套类的公有成员。