类型提升规则

Screenshot from 2019-05-24 09-55-22-eccfa287-4a37-49d6-9ddc-cc617a1d02df

值得注意的是 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 &可左可右.

https://stackoverflow.com/questions/15681691/difference-between-returning-a-const-reference-and-rvalue-reference

https://stackoverflow.com/questions/39552272/is-there-a-difference-between-universal-references-and-forwarding-references

总结下,搞清楚他们的区别:

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++函数调用的解析

Screenshot from 2019-05-29 21-31-35-8f87a2eb-0468-453c-9c2e-c53eaa833e60

复合关系

  1. has-a 这种关系一般位于具体应用的对象间,这些对象为应用域的.
Person has a Name.
Animal has a Tail
  1. 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:合成的拷贝控制(默认构造,拷贝构造,赋值构造,析构)一定能用?

  1. 取决于它的成员类,如果成员类不完善(拷贝控制函数定义了 delete,or private 属性),那么它爹的对应的拷贝控制函数也是不可用的(=delete).

  2. 含有 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)的类.

不能创建对象