C++ Primer学习二


拷贝控制

rule of three:定义了其中一个,剩下的几个都要定义

  1. copy constructor
  2. copy assign operator
  3. destructor

rule of five:定义了其中一个,剩下的几个都要定义

  1. copy constructor
  2. copy assign operator
  3. destructor
  4. move constructor
  5. move assign operator

深拷贝与浅拷贝
a. 浅拷贝:简单的赋值拷贝操作。浅拷贝带来的问题就是堆区的内存重复释放。要利用深拷贝解决。
b. 深拷贝:在堆区重新申请空间,进行拷贝操作。
在这里插入图片描述
编译器会默认生成拷贝构造函数,但是如果类里面有动态申请的内存空间,那么一定要自定义拷贝构造函数,用深拷贝去解决浅拷贝带来的问题,并且拷贝构造函数/类中重载赋值运算符一定要使用索引,因为不用索引的话,去调用对象的时候会首先调用一次拷贝构造,造成死循环

类似实现一种资源管理的方式,应该从哪里考虑:
构造函数初始化,拷贝构造++,析构函数--,如果减到0,直接delete掉。
class Foo{
public:
    int* resource_;
    size_t* count_;

    Foo(int resource):resource_(new int (resource)){
        cout<<"in constructor"<<endl;
        this->count_ = new size_t(1);
    }
    Foo(const Foo& foo){
        this->resource_ = foo.resource_;
        this->count_ = foo.count_;
        *(this->count_)+=1;
        cout<<"update this->count " <<*(this->count_)<<endl;
    }
    ~Foo(){
        *(this->count_) -=1;
        if(*(this->count_)  ==0){
            cout<<"do destructor"<<endl;
            delete this->count_;
            delete this->resource_;
        }
    }
};

int main(){
    Foo foo1(1);
    Foo foo2(foo1);
    Foo foo3(foo2);
    Foo foo4(foo3);
    cout<<"how many count " <<*(foo2.count_)<<endl;
    return 0;
}

输出结果
in constructor
update this->count 2
update this->count 3
update this->count 4
how many count 4
do destructor

移动构造

  1. 右值:右值可以偷过来
    a. 容易消失,没有名字,不可修改。
    b. 没有其他的人在使用。
  2. 如果有移动构造,移动来源的资源一定要释放掉。

运算符重载
a. + - / * 运算符重载:定义新的运算规则
b. 左移运算符重载:定义输出方式,一定要定义成友元函数,没法用隐式转换
c. 递增运算符重载:实现自己的数据类型。
d. 赋值运算符重载:进行赋值,注意深浅拷贝。重载赋值符号,也是放置浅拷贝的重要一项。
e. 关系运算符重载:让两个自定义对象进行对比操作。
f. 函数调用重载:仿函数,定义类似函数的行为,比较自由。

拷贝构造函数和拷贝赋值运算符的调用时机:

#include<iostream>
namespace _nmp2_2{
    class A{
    public:
        A():m_caa(0),m_cab(0){}; //构造函数
        A(const A& tmp){ // 拷贝构造
            this->m_caa = tmp.m_caa;
            this->m_cab = tmp.m_cab;
        }
        A& operator= (const A& tmp){ //拷贝赋值运算符
            m_caa = tmp.m_caa;
            m_cab = tmp.m_cab;
            return *this;
        }
    public:
        int m_caa;
        int m_cab;
    };
};

int main(){
    _nmp2_2::A oba;
    oba.m_caa = 10;
    oba.m_cab = 20;
    _nmp2_2::A obb = oba;// 拷贝构造
    obb = oba; // 拷贝赋值运算符
    return 0;
}
  • 类和类之间的关系一般就是继承关系,或者是组合关系。
  • 委托关系:一个类中包含指向另一个类的指针。

面向对象

  1. 运行时多态:虚函数+动态绑定:
class Book{
public:
    string ISBN;
    virtual void func(){
        cout<<"Book"<<endl;
    }
};

class ComicBook : public Book{
public:
    void func(){
        cout<<"Comic Book"<<endl;
    }
};

class ActionBook : public Book{
public:
    void func(){
        cout<<"Action Book"<<endl;
    }
};

int main(){
    ComicBook cb;
    ActionBook ac;
    Book* b1 = (Book*) &cb;
    Book& b2 = ac;
    b1->func(); // cout Comic Book
    b2.func(); // cout Action Book
    return 0;
}
  1. 派生类构造的时候,要先构造基类的东西。
  2. 派生类析构的时候,先析构自己的东西,再析构基类的东西。
  3. vtable:找到子类函数的关键。

静态成员:静态变量和静态成员函数,加static关键字:
a. 静态成员变量:
i. 所有对象共享同一份数据
ii. 在编译阶段分配内存,不和类对象在同一个存储空间上,只有非静态成员变量才属于类的对象上
iii. 类内声明,类外初始化。
b. 静态函数:
i. 所有对象共享同一个函数。
ii. 静态成员函数只能访问静态成员变量。

  1. 纯虚函数不能实例化对象。
  2. 如何让抽象类不能生成对象?
  • 构造函数和拷贝构造函数都用protected修饰。
  1. protect和private的区别:子类中不可访问父类中的private的属性和函数,但可以访问protect的。
  2. C++中struct和类的唯一区别就是权限不同,struct默认public,public继承,class 默认private,private继承。
  3. 基类的析构函数,一般加上virtual,让派生类释放自己的,基类释放自己的,每个类做好自己的事情,派生类不要管基类的释放。
  4. 容器中存放类对象:如果vector< Base > vc 中push_back一个派生类,那么派生类相对于基类多出来的部分,会被砍掉。但是如果说把类型换换,vector< Base* > vc2, vc2中插入一个派生类指针,根据多态性,可以访问到多出来的那部分。
  • 如果类中包含静态成员变量,无论这个静态成员变量是否使用,都会给这个静态成员变量分配内存。
  • 全局对象的初始化顺序是不固定的
  • 做父类应该有个虚析构函数。
  • 对于不允许进行拷贝构造或者拷贝赋值运算符的函数:用=delete或者定义为private函数。

模板与泛型编程

  1. C++除了面向对象编程思想之外,还有泛型编程思想,主要利用的技术就是模板。
  2. 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况,不同之处在于,面向对象编程能够处理在程序运行之前都都未知的情况,而泛型编程,在编译时就能获知类型
  3. 模板的声明和定义要都放在.h中:编译器看到模板不会生成代码,一定要实例化一个模板的一个特定版本的时候,编译器才会生成代码,为了生成一个实例化版本,模板的头文件中要包含模板的定义和声明。
  4. 函数模板:建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表。
    在这里插入图片描述
  5. 类模板:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。类模板在初始化的时候一定要显式的表明是什么类型的。
    在这里插入图片描述
    在类内,声明T之后,就不用再用T再次声明,但是在类外,但凡要用到A类,就要表明T的类型。
template<typename T>
class A{
    T a;
    
    A& func(){
        cout<<"hello world"<<endl;
    }

    A& func2();
};

template<typename T>
A<T>& A<T>::func2(){
    cout<<"hello world 2"<<endl;
    return *this;
}

对于类模板中的静态数据,每个类型共享一个,而不是所有的共享一个。

template<typename T>
class A{
public:
    static int count;
};

template<typename T>
int A<T>::count = 0;

int main(){
    A<int> a1;
    a1.count+=1;
    A<long> a2;
    a2.count+=10;

    cout<<A<int>::count<<endl; // 1
    cout<<A<long>::count<<endl; // 10

    return 0;
}

普通类中包含模板函数 …

class DebugDelete{
public:
    template <typename T>
    void operator() (T*p){
        cout<<"delete it"<<endl;
        delete p;
    }
};

int main(){
    double* p = new double(10);
    DebugDelete d;
    d(p);

    return 0;
}
  1. 一个类型推断的小例子
template<typename It>
auto func(It begin, It end) -> decltype(*begin){
    return *begin;
}
  1. 虽然不能直接将一个右值引用绑定到到一个左值上,但可以用move获得一个绑定到左值上的右值引用。std::move不会创建新的对象,仅仅是类型转换,但是std::move会影响到编译器函数重载的匹配。 std::move(a) == static_cast< A&&>(a);
  2. 左值常引用相当于万能型:可以用左值或者右值进行初始化。
  3. 改成const &,可以省去参数的拷贝,一定要用const &,多用,好用。
  4. 完美转发 = 引用折叠 + 万能引用 + std::forward。
  5. 想保留左值右值属性的时候,用std::forward。
template <typename FUNC, typename T1, typename T2>
void flip(FUNC func, T1&& t1, T2&& t2){
    func(t2,t1); // t2 t1 都是左值,并且不能将左值调用到右值的函数里面,所以要用完美转发
    func(std::forward<T2>(t2),std::forward<T1>(t1));
}
void f(int v1,int& v2){
    cout<<v1 <<" " <<++v2<<endl;
}

int main(int argc, char * argv[])
{
    int i = 10;

    // FUNC->f
    // t1->int
    // t2->int
    // 左值传到右值里面相当于一个引用
    flip(f,i,42);
    cout<<i<<endl; //11
}
  1. 模板重载的时候,会按照匹配度去调用。 非模板匹配的好的,就会调用非函数模板。
  2. 可变参数模板:当参数个数未知,类型未知,一定要用模板,有点类似递归,要有一个最终的出口。
//出口
template <typename T>
ostream& print(ostream& os, const T& t){
    os<<t<<endl;
    return os;
}

template <typename T, typename... Args>
ostream& print(ostream& os, const T& t, Args... args){
    os<<t<<" ";
    print(os,args...);
    return os;
}


int main(int argc, char * argv[])
{
   print(cout,1,"string", 0.0, 3L);
}

  1. 命名空间:减少命名冲突。
  2. 头文件当中禁止使用using,cpp文件中放到匿名命名空间中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值