Cpp

C++ programming tips

From C++ Primer Plus

运算符()重载与仿函数

运算符 ()

在stl queue等容器中,需要重载比较运算符()而不是 <

struct cmp {
    bool operator () (triple e1,triple e2) {
        return e1.dist > e2.dist;
    }
};
priority_queue<triple,vector<triple>,cmp> pq;

cmp中的()运算符重载是仿函数

既能想普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息

class ShorterThan {
    public:
        explicit ShorterThan(int maxLength) : length(maxLength) {}
        bool operator() (const string& str) const {
            return str.length() < length;
        }
    private:
        const int length;
};

count_if(myVector.begin(), myVector.end(), ShorterThan(length))
//count_if规定了第三个参数只能接受一个参数,但要使得方便修改,利用仿函数即可
}

hash_function

static size_t hash(const pair<int,int> & a) {
    return a.first * 3005 + a.second;
}
unordered_map<edge,int,size_t(*)(const pair<int,int> &)>es(n,hash);

unordered_map(size_type __n,
        const hasher& __hf = hasher(),
        const key_equal& __eql = key_equal(),
        const allocator_type& __a = allocator_type())
: _M_h(__n, __hf, __eql, __a)
{ }

在上述中使用哈希表需要传递hash function,从声明中知有size和hasher,key_equal()使用==操作符,pair已经有重载

除了上述的直接构建实例(注意static,成员函数指针和静态函数指针不同),还可以使用前面提到的仿函数

C++函数

左值与右值

左值,有名称的量,可以通过变量名直接访问

右值,没有名称,存在于内存中的量

a = 1;//左值
int && a = fun();//fun()的返回值是右值类似的还有常量

引用

可以多层函数嵌套对对象进行修改,但是需要注意避免返回函数终止时不再存在的内存单元引用

同时可以进行赋值

type & function(type & arg)
function(arg) = newarg

当返回是引用时则可以这样,否则不行。或者返回使用const

const type & function(type & arg)
function(arg) = newarg (此时则不允许这样)

什么时候在函数调用时创建临时变量:

当实参与引用参数不匹配,C++将生成临时变量。目前当参数为const时:(1)实参的类型正确,但不是左值(可被引用的数据对象)(2)类型不正确,但可以转换为正确的类型

默认参数

int function(int tk = 1,int m = 2,int n = 3)

有默认参数的右侧的参数也必须都有默认参数

函数重载

函数重载的关键是特征标——函数的参数列表,而不是函数的返回类型。因此函数重载返回类型可以不同。

模板

模板同样可以重载

将模板用作参数

template <template <typename T>class Thing>
class A{};

template <typename T>class是参数,声明Thing必须是一个模板类

模板别名

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

显式具体化

函数有多个原型,则编译器在选择原型时,优先选择非模板版本,显式具体化优先于模板

void swap(job &, job &);

template <> void swap<job>(job &,job &);//显式具体化

template <typename T>
void swap(T &,T &);

显式具体化与显式实例化不同

template void swap<int>(int,int);      //显式实例化
//使用模板生成一个swap的实例

template <> void swap<int> (int ,int ) //显式具体化
template <> void swap (int ,int )      //显式具体化
template <>表示不要使用swap模板来生成一个使用int类型的实例

模板中的符号

使用decltype(arg),用于模板中推断临时变量的类型

后置返回类型

template<typename T1,typename T2>
auto func(T1 x,T2 y) -> decltype(x + y) {
    return x + y;
}

构造函数

explicit 声明的构造函数不允许默认隐式转换和复制转换

class A {
    A(int) {} 
    A(int,int) {}
};
A a = 1;\\在没有explicit下允许
A a = {1,2};\\在没有explicit下允许
A b = A(a);\\拷贝构造函数,未重载时默认提供

只有接受一个参数的构造函数才能作为转换构造函数

转换函数

operator typename();

友元函数

非成员函数无法访问类的私有成员

类的友元函数是非成员函数,其访问权限与成员函数相同

friend func(args);\\声明在类中

友元函数不能继承

友元函数用于类继承时,因为友元不是成员函数,所以不能使用作用域解析运算符来指出要使用哪个函数,所以使用强制类型转换,使得匹配原型时能够选择正确的函数

Private,friend,public,protected

friend的作用见上

protected用于类继承时,派生类对声明为protected的基类的成员可以直接访问

虚函数

基类中对应的函数需要声明为virtual,派生类中对应的函数也许要声明

如何工作:维护一个虚函数表,查看派生类和基类的虚函数,如果在派生类中有对应的虚函数,则将派生类的虚函数填入表中,如果没有则将基类的虚函数填入表中

用虚函数实现多态,因为可以用基类的指针和引用指向派生类,这时候调用对应的函数,如果是虚函数中,则会根据对象实际的类型(编译器跟踪)选择实际调用的函数

编译:动态联编

析构函数通常是被成名为虚函数

类继承

需要注意类复制构造函数,赋值运算符,特别是成员中含有指针,而指针需要使用new来分配空间,需要深拷贝

使用复制构造函数的情况:

  1. 将新对象初始化为一个同类对象
  2. 按值将对象传递给函数
  3. 函数按值返回对象
  4. 编译器生成临时对象

使用赋值运算符的情况:

  1. 如果语句创建新的对象,则使用初始化;如果语句修改已有对象的值,则是赋值。

私有继承

私有继承是另一种实现has-a关系的途径,所以成员都将成为派生类的私有成员

在私有继承中如何访问基类的对象?

答:类型强制转换

同样的对于基类的友元函数也可以通过类型强制转换的方式进行。

实现has-a的方式有两种:私有继承和包含

通常使用包含,但是私有继承可以访问protected成员

保护成员

基类的公有成员和保护成员都将成员派生类的保护成员,基类的接口在三代继承中仍然可用,而私有继承不可用

特征公有继承保护继承私有继承
公有成员变成派生类的公有成员派生类的保护成员派生类的私有成员
保护成员变成派生类的保护成员派生类的保护成员派生类的私有成员
私有成员变成只能通过基类接口访问只能通过基类接口访问只能通过基类接口访问
能否隐式向上转换是(只能在派生类中)

多重继承(MI)

class Singer:public Worker{};
class Waiter:public Worker{};
class SingerWaiter: public Worker,public Worker{};

这样SingerWaiter中有两个worker对象,不能进行隐式类型转换(派生类向基类转换)

要使得只继承一个,使用虚基类

class Singer:virtual public Worker{};
class Waiter:public virtual Worker{};
class SingerWaiter: public Worker,public Worker{};

虚基类的构造函数不会传递,需要显式指明work的构造函数

当祖先相同时,必须要引入虚基类

当继承了多个同名的方法,需要使用类限定符

STL容器

class A{
    typed
};
X::value_type

vector

vector的操作中只有push_back()是O(1)时间复杂度,从头插入,删除都是O(n)复杂度

模板initializer_list

可以用于可变参数

初始化

vector<int> a = {1,2,3}//使用了initializer_list

RTTI

运行阶段类型识别

dynamic_cast<pointer>:将使用一个基类的指针来生成一个指向派生类的指针
typeid(运算符):返回一个指出对象的类型的指
type_info(类):存储了有关特定类型的信息
typeid(Class A) == typeid(pointer)

typeid接受类名或结果为对象的表达式,返回一个对type_info对象的引用,type_info中可以比较两个类型

智能指针

auto_ptr<>  //不能两个之间赋值,会导致delete两次
shared_ptr<>//引用计数
unique_ptr<>//有所有权概念

C++11标准

移动语义和右值引用

新的类功能

默认的方法和禁用的方法

default和delete

委托构造函数,继承构造函数

构造函数可以调用基类的构造函数和其他的构造函数

管理虚方法

override:重写覆盖对应的虚方法

final:不允许重写该虚方法

Lambda函数,函数体,包装器

[&,=a]() {} \\lambda表达式,和函数指针、函数体、函数统称为called type
std::function<ret type(args type)> name;\\可将以上的called type使用function包装,可以直接调用function的构造函数
std::function<int(int)> var([=count](int a) {return a + count;})

可变参数模板

template<typename T> void show_list(const T& value) { std::cout << value << '\n'; }
template<typename T, typename...Args> void show_list(const T&value, const Args& ... args) {
    std::cout << value << ",. ";
    show_list(args...);
}
//使用函数重载和解包递归的操作