C++ 知识点整理
- 类型提升规则
- 引用与 const 引用
- c++11 constexpr
- const 修饰的位置
- struct 与 class 区别
- auto
- decltype
- c++11 委托构造函数
- c++初始化列表
- RAII 概念
- 区分类型转换函数和转换构造函数,explicit
- mutable 关键字
- c++11 新特性 memory model
- c++11 新特性 左值右值(lvalue xvalue prvalue)
- c++11 转移拷贝和转移构造
- static_cast 等
- 智能指针
- 虚拟继承
- local static 对象与 non local static 对象
- 为何需要虚析构函数
- c++11 构造和析构期间不用虚函数
- 接口设计易用
- 类型转换函数
- throw
- 传值和返回值
- c++模板 全特化 偏特化
- swap 对象
- stl algorithm
- copy_and_swap 策略
- inline
- 再谈 pimpl 模式
- 前向声明
- 函数对象
- 名称空间的掩盖
- pure-virtual impure-vitual 和 non-vitrual 的选择
- c++的模板方法设计模式
- 考虑单纯的虚函数以外的选择
- 静态绑定与动态绑定
- c++函数调用的解析
- 复合关系
- C++中 static 变量与继承
- noexcept
- 合成的移动赋值和移动拷贝
- c++11 =default =delete 修饰 assginment/copy construtor
- override and final 修饰符
- 避免使用虚函数
- 抽象基类
类型提升规则
值得注意的是 unsigned int + int , int 提升为 unsigned int;
单精度,双精度的提升问题:
float f = 1.1;
double d = 1.1;
if (f==d) {return 0;} else {return 1;}
引用与 const 引用
引用即别名. 绑定的对象不能再换其他的对象,但是对象的值可以被改变.
引用又可以叫左值引用, 只能绑定左值.
int big = 1;
int small = 2;
int & ret = big;
//const引用 有点像const char * const 指针
const unsigned char & uc_ref = (small + big);
const char & c_ref = (small + big);
const int & i_ref = (small + big);
//fail
//c_ref = small;
ret = small;
std::cout << "const ref convert to tmp char:" << c_ref << std::endl;
std::cout << "const ref convet to tmp unsigned char:" << uc_ref << std::endl;
std::cout << "const ref no convert " << i_ref << std::endl;
std::cout << ret << std::endl;
const 引用
普通引用只能绑定左值,意思是=右边的是必须在内存里一直存放的,不能是临时的.
但是常量引用比较厉害,左值右值都行.
const &
引用也能 hold 住=右边的临时(右)值,延迟生命和左边的变量一样长.
https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
和右值引用&&
有啥区别?
右值引用只能绑右值,const &
可左可右.
总结下,搞清楚他们的区别:
A b;
A & a = b; //ok
A & a = A(b); //no 引用只能bind左值
auto & a = b; //same as A &
auto & a = A(b); //no
const A & ca = A(b); //右值ok
const A & ca = b; //左值ok const了,ca的值不能修改
const auto & a = b; //ok same as const A &
auto && a = b; //ok 万能引用!
auto && a = A(b);//ok
a = A(b);//ok! 可以修改值
const A & a = A(b);//ok
a = b; //no! const引用不能改值
const auto && a = b; //no !右值引用,且不能改值
const auto && a = A(b);//ok ,但不能改值了
c++11 constexpr
常量表达式 https://blog.csdn.net/qq_37568748/article/details/82287153
- 修饰变量 将变量声明为 constexpr 类型以便由编译器来验证变量的值是否是一个常量表达式。声明为 constexpr 的变量一定是一个常量,而且必须用常量表达式初始化,否则编译器将报错
- 修饰函数 参数和返回值都必须是常量,一般就是 const getXXX {return xxxx}
- 修饰成员函数
const 修饰的位置
const 修饰返回值; const 修饰函数参数; const 修饰成员函数;
struct 与 class 区别
auto
编译器推断后的类型不一定等于原来的类型;
const int i = 10;
int & j = i;
auto a = j; // auto不是引用 而是int类型
auto b = i ; //是int const被拿掉.
const auto c = i; //保留const的做法
auto & d = j; //这是保留引用的做法
decltype
decltype(f()) sum = x;// sum的类型就是f的返回类型
int a = 10;
int & c = a;
decltype(a) aa ; //指定类型推断
decltype(c) cc = 100; //与auto不同,原来c是什么类型,cc就是什么类型,这里是引用,因此需要初始化
decltype((a)) aaa = 100; //()起来的变量,被decltype得到的是引用类型.
用于函数指针比较清晰:
先看 c 语言的, 来个函数指针,参数也是函数指针,返回也是函数指针?
void (*signal)( void (*handler)(int,char), int)(int, char);
signal 函数接收两个参数: 1 void ()(int,char)的函数指针 hander. 2 in2 返回的也是函数参数指针: void ()(int ,char);
如果某个函数需要返回 signal 函数呢?..很绕了. 如果用 decltype:
//很简洁的
decltype(signal) * someFunction(int i){...};
c++11 委托构造函数
class A {
public:
A(const std::string & name, int size, int type ):
_name(name),_size(size),_type(type)
{}
//把构造函数委托给另外1个构造函数
A(const std::string & name): A(name,100,1) {}
A(int size): A("good",size,1) {}
private:
const std::string & _name;
int _size;
int _type;
}
c++初始化列表
类成员初始化最好用它,避免一次默认构造.
先是类成员初始化,
再是构造函数,
再是派生类成员初始化,
再是派生来构造函数
引用成员和 const 成员的初始化必须放在初始化列表
RAII 概念
“Resource Acquisition is Initialization” in effectivec++ item15: 1.如果自己构造函数中申请分配资源,在析构函数中释放资源. 2.智能指针包裹资源,资源交给它来管理,就是引用计数. 注意:
- copy RAII 对象最好禁止 G 不管复制多少个,希望最好 1 个释放的时候,资源即释放.
- 不好禁止的话,将资源用智能指针包裹,即引用计数的方式
区分类型转换函数和转换构造函数,explicit
转换构造是构造函数,参数只有 1 个构造函数,将其他类转成本类,一般是隐式转换.
类型转换函数是将类型转换为数据
class A {
pubic:
int _a;
//转换构造
A(int a) {
_a = a;
}
//类型转换
operator double() {
return double(_a);
}
}
A obj = 10; //ok发生了上面的隐式构造转换.
obj._a ; //10
//fail if explicit
A obj = 10;
//ok if explicit and static_cast
A obj = static_cast<A>(10);
- explict 关键字
声明转换构造函数为 explict,明确告诉编译器禁止这种隐式转换行为.
explicit关键字只对有一个参数的类构造函数有效
. 规范中提到 explicit 的优点是可以避免不合时宜的类型变换,缺点无。 除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为 explicit.
mutable 关键字
class A{
sth(){
b++;
} const;
mutable private int b;
}
const后缀修饰的成员函数
不可改变成员的状态,但是mutable
修饰的成员例外.
c++11 新特性 memory model
https://www.zhihu.com/question/24301047 //tbd
c++11 新特性 左值右值(lvalue xvalue prvalue)
左值:会分配内存,右边可用=号. 右值:临时对象(或字面值), 表达式结束后,对象销毁.
i++; //先使用i再+,不能再=了,右值
++i; //先加再用i,++i=1; ok,左值
func(xx); //函数返回值若不是引用,右值
c++11 引入了所谓 xvalue,下面 3 种情况会产生:
返回T&& 的函数
static_cast<char&&>(x).
std::move(x).
它的作用是 Hold 住临时值,别急着释放,重复利用,提高效率! 但是它仍具有右值的性质,如右边不能放=号.. 如果是具名的右值引用,它只能绑定右值,包括 prvalue 和 xvalue, 但其本身的表达式 value cate 为左值,(需要地方存放它!)
c++11 转移拷贝和转移构造
https://blog.csdn.net/jisuanji198509/article/details/80652551
A a = A(1); //触发转移=拷贝 if any.
A b;
b= A(1); //触发转义构造 if any.
A c;
//将左值理解为右值,都是编译器行为
b= std::move(c)//触发转移=拷贝 if any
static_cast 等
https://blog.csdn.net/q1n2hen/article/details/77994009
dynamic_cast 效率并不好.
const_cast 破除 const 性质:
这涉及到一个问题, const 变量到底存在哪?
如果按arm-gcc理解,const变量编译器本应放在rom区.
这里肯定不是的,const变量该放哪就放哪.
如果是字面值常量,那可能扔到test段了.
const int a = 10;
a = 11;// no!
b = const_cast<int>(a);
b = 12;
reinterpret_cast 主要用于为解释为不同类型的指针:
int i = 0x12345678
int * a = &i;
char *b = reinterpret_cast<char*>a;
智能指针
RAII auto_ptr, shared_ptr, unique_ptr, weak_ptr;
auto_ptr 的问题?
weak_ptr 的作用? https://blog.csdn.net/leeds1993/article/details/54563918
虚拟继承
解决菱形多继承问题.子类有 2 个 ptr,1 个指向子类新创建的 virtual fun table,另 1 个指向虚基类 table,实现共享基础类.
虚基类 table 存储的是,虚基类相对直接继承类的偏移.
https://blog.csdn.net/bxw1992/article/details/77726390
virtual pubic
always 比 public 好?它能解决多重继承的菱形问题,但付出更多的空间代价和复杂度代价.
local static 对象与 non local static 对象
见 effective c+= item4.
Singleton模式
的来源啊!回答: 为何需要 1 个静态变量,1 个静态函数?
Q:用 static 函数代替直接对 static 对象的使用,若直接用后者,后者可能还未初始化好!
比如:用1个static变量初始化另外1个static对象,
why?
A:不同编译单元里的 static 对象的初始化次序并不是确定的
放在 static 里,去 new 出来,保证该对象一定创建,而且可共享.
为何需要虚析构函数
effectivec++ item7 为了防止派生类内存泄露:
Base * ptr = &Child;
ptr->virtual_func();//
/*
1 .派生类申请了内存,在派生类的析构中释放.
2 .基类指针使用该派生类.
3 .delete ptr;
若无虚析构,此时调用的是Base:~Base();
若有,则调用的是Child:~Child(),在~Child()里,又会调用Base():~Base(),才是正确的
*/
编译器默认不产生 virtual ~Base()!,需要手动指定.
c++11 构造和析构期间不用虚函数
effectivec++ item9: 不会有虚函数的效果, vptr 都没初始化好呢.
接口设计易用
提供接口时,考虑使用者 stupid. 工厂模式创建实例,good:
Elem* createElement();
但是调用者用完后可能忘了 delete,于是改成 shared_ptr:
std::shared_ptr<Elem> createElement();
类型转换函数
这些都很熟悉了:
default construtor
constructor
copy constructor
assignment copy constructor
destructor
类型转换是将 1 个类型转换成另外 1 个类型:
class A {
operator type( )
{
//实现转换的语句
}
}
type t;
A() + t; //A()将执行类型转换变成type.
throw
void fun() noexcept(); //表示fun函数不允许抛出任何异常,即fun函数是异常安全的。
void fun() throw(...); //表示fun函数可以抛出任何形式的异常。
void fun() throw(exceptionType); // 表示fun函数只能抛出exceptionType类型的异常。
传值和返回值
传参时,pass-by-value 效率肯定没有 pass-by-reference-const 高. 但内置类型、迭代器、和函数对象除外,老实的 pass-by-value 也挺好.
返回时,除非特别有必要,采用 return-by-value. return-by-reference 并不是好想法.
c++模板 全特化 偏特化
template<class T,class N>
class Template{};
//全特化:
template<> class Template<int,char>{};
//偏特化:
template<class T> class Template<T,int>{};
注意:函数模版不存在偏特化,只有类模版才能偏特化
swap 对象
见<高效 swap="" 实现="">高效>
stl algorithm
https://blog.csdn.net/jerryjbiao/article/category/870957/2?
copy_and_swap 策略
赋值 copy 里,释放原来的,再拷贝传进来的
异常函数 3 个等级: 基本安全,强烈安全,不抛异常
copy_swap 可基本实现强烈安全
,但性能复出 copy 的代价.
inline
对函数的调用用函数内容整体替换之,类似宏.
对象的构造析构等一般都不能 inline,背后有复杂的异常处理.
nline 函数不适合动态库,不利用升级.
再谈 pimpl 模式
通过定义 private 的对象指针(智能指针),对引用和管理对象. 前面强调的是别的对象,实际将自身再抽离一层,也是有好处的. 见 effectivec++ item31, 将编译依存关系将到最低.
对外是 Person. 实现时,Person 其实依赖 PersonImple, PersonImple 把包含其他对象的依赖(Date)
生动的例子是 android 里的智能指针(wp,sp,RefBase)
前向声明
见pimple 模式与前向声明
函数对象
见<函数对象与 bind="">函数对象与>
名称空间的掩盖
public 派生类的函数会掩盖基类的同名函数,包括重载版本.
要想不掩盖,需要在类定义显示声明using Base::f()
或通过转交的形式:
Derived {
void f(){
//显示调用Base::f();
Base::f();
}
}
pure-virtual impure-vitual 和 non-vitrual 的选择
继承的 2 个功能: 接口继承和实现继承. 3 类函数: pure-vitual 只是为让派生类继承接口; non-vitrual 是为了让派生类继承接口的功能(实现); impure-virtual 基于两者之间.(改写了是要接口,不改写时,是要默认的实现)
impure-virtual 的最佳实践:
class Base {
virtual void fly() = 0;
};
class Child1: public Base {
} ;
class Child2: public Base {
} ;
void Base:fly(){
//默认fly行为,纯虚函数的实现
};
void Child::fly(){
//保持默认实现行为
Base::fly();
}
void Child2::fly(){
//自己的重写代码
}
c++的模板方法设计模式
non-vitrual-interface(NVI): 类实现提供 non-virtual 作为 vitrual 函数的 wrapper
class A {
public:
int someThing() {
//sth before
int ret = my_virtual_funcion();
//sth later
return ret;
}
virtual void my_virtual_function();
}
这样对外继承接口的实现(someThing),单实际可通过(my_virtual_function)改写自己所需的函数功能. 好处:针对 my_virtual_function 实现一些固定的调用前(sth before)和调用后(sth after)的处理.
考虑单纯的虚函数以外的选择
模板方法(NVI)
桥接模式(引入某对象指针作为成员)
策略模式(引入某函数对象对位成员)
静态绑定与动态绑定
对象的静态类型:对象在声明时采用的类型。是在编译期确定的。
对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。
静态绑定:绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。
动态绑定:绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期。
只有虚函数才使用的是动态绑定,其他的全部是静态绑定
动态绑定
的本质:
Base * base;
//child指向的对象内存里有个vptr,它才决定了虚函数的实际调用.
base = child;
base->fun();
c++函数调用的解析
复合关系
- has-a
这种关系一般位于具体应用的对象间,这些对象为
应用域
的.
Person has a Name.
Animal has a Tail
- is-implemented-in-terms-of
纯粹的技术细节的人工制品, 这些对象为实现域
的.
Mutex
Buffer,
Search Trees
C++中 static 变量与继承
static 内存独有1份,肯定父子是共享的,父亲可通过 private 声明是否共享.
noexcept
class A {
A(A && a) noexcept;//显式告诉编译器该函数不抛异常,不要插入额外的异常处理代码.
}
Q:为何移动构造函数偏向不抛异常?
以 vector 为例子,需要重新分配内存,如果中间使用了移动拷贝,也就是把旧的剪切
到新的内存里,若抛异常,无法再恢复. 而普通的拷贝构造函数不会改变旧元素,如果异常了大不了抛弃 new 的新内存,再重新来.
合成的移动赋值和移动拷贝
编译器会在合适的时机,合成的赋值构造和赋值拷贝.
但不一定会合成移动赋值和移动拷贝,除非:
一个类没有定义任何自己的拷贝控制成员,并且数据成员确实可移动时
,才会合成.
如果没有移动拷贝控制,那就直接用普通拷贝控制,都拷贝好了.
c++11 =default =delete 修饰 assginment/copy construtor
=default: 希望编译器合成响应的拷贝控制函数 =delete: 虽然声明了,但是不希望用它,也就是禁止某种行为.(比如隐式转换)
class A {
A() = default;// 合成默认的
A(const & a) = delete; //阻止拷贝
A & A(const & a) operator=() =delete; //阻止赋值
~A() = default; //合成默认的析构
}
析构函数不能 delete,否则无法删除对象了.
Q:合成的拷贝控制(默认构造,拷贝构造,赋值构造,析构)一定能用?
-
取决于它的成员类,如果成员类不完善(拷贝控制函数定义了 delete,or private 属性),那么它爹的对应的拷贝控制函数也是不可用的(=delete).
-
含有 const or 引用的成员时,合成的无法使用。
Q:再考虑上合成的转移拷贝构造,转移赋值构造一定能用?
类似的,如果类成员的转移构造控制不完善(未合成,=delete, or private),那么它爹的也不行.
一句话:尽量自己定义清楚五大金刚
–拷贝控制函数!
## 引用限定符
class A {
A & operator+(const A & a, const A & b) const &;
A & operator-(const A & a, const A & b) const &&;
}
&
表明,强制左侧运算对象 this, 只能作为左值
;
&&
表明,强制左侧运算对象 this, 只能作为右值
;
A a,b,c;
a+b = c; // ok
a-b = c; //no!
c= a+b; //no!
c= a-b; //ok
override and final 修饰符
class A {
virtual void func(int );
//派生类不准覆盖该函数
void fun() finnal;
}
class B: public class A {
//明确告诉编译器,派生要覆写虚函数func,且调用一致
void func(int) override;
}
避免使用虚函数
加作用域限定符即可
base= child;
base->func();//调用之类的虚函数实现
base->Base::func();//调用基类
抽象基类
定义了纯虚函数(virtual fun()=0)的类.
不能创建对象