美文网首页
c++语言基础(一)

c++语言基础(一)

作者: 仙人掌__ | 来源:发表于2024-02-22 15:25 被阅读0次

    一、构造函数

    不同的语法使用场景会自动调用功能不同的构造函数。c++的构造函数主要有如下几种类型:无参(多参)构造函数、拷贝构造函数、移动构造函数、赋值构造函数(赋值语句)、移动赋值构造函数(移动赋值语句)、类型转换构造函数;

    • 无参构造函数:无参构造函数称为默认构造函数;如果未定义任何构造函数,编译器默认会生成一个

    如下情况会调用无参构造函数(多个参数的构造函数也一样)

    class MyClass
    {
    public:
      MyClass():myInt_val(0),myString(""){}
      MyClass(int val,string str):myInt_val(val),myString(str){}
    private:
      int myInt_val;
      string myString;
    };
    
    MyClass myObj1;
    MyClass myObj11(3,"str");
    MyClass *myObj2 = new MyClass;
    MyClass *myObj3 = new MyClass();
    MyClass *myObj33 = new MyClass(3,"str");
    MyClass *myObj4 = new MyClass[2];
    
    • 拷贝构造函数/移动构造函数:=进行初始化、作为函数参数、函数返回值等等时调用

    拷贝构造函数,参数为自身类型的引用类型或者常引用类型称为拷贝构造函数。如果未定义,编译器会默认生成一个拷贝构造函数,且为浅拷贝;

    移动构造函数,参数为类的右值引用类型

    class MyClass
    {
    public:
      MyClass(const MyClass& obj):myInt_val(obj.myInt_val),
            myString(string(obj.myString)){
            cout<< "这里是拷贝构造函数" <<endl;
      }
      MyClass(MyClass&& obj):myInt_val(obj.myInt_val){
            myString = std::move(obj.myString);
            cout<< "这里是移动构造函数" <<endl;
      }
    private:
      int myInt_val;
      string myString;
    
      static MyClass funcArg(MyClass obj,MyClass& obj2) {
        MyClass obj3 = obj;
        return obj3;
      }
      static MyClass& funcArg2(MyClass* obj) {
        MyClass *retObj = new MyClass;
        *retObj = *obj;
        return *retObj;
      }
    };
    
    MyClass myObj1;
    // 拷贝构造函数
    MyClass myObj5 = myObj1;
    // 参数为类类型时:会调用拷贝构造函数。
    // 参数或者返回值为类引用类型时:不调用构造函数
    // 返回值为类类型时:添加-fno-elide-constructors,调用拷贝或者移动构造函数;不添加,编译器会优化,不调用任何构造函数
    funcArg(myObj1, myObj1);
    // 参数为指针、返回值引用或者指针:不调用任何构造函数
    funcArg2(&myObj1);
    
    • 赋值构造函数/移动赋值构造函数: 主要当使用=进行赋值时调用

    赋值构造函数:即进行对象赋值时调用的构造函数。如果未定义,编译器会默认生成一个,简单进行遍历的赋值

    1、参数必须为引用类型或者常引用类型

    2、返回值必须是引用类型

    3、必须return *this; 保证前后为一个对象

    移动赋值构造函数,与赋值构造函数区别是参数为右值类型,其它一样

    如果都未定义,编译器都会默认各生成一个

    class MyClass
    {
    public:
      MyClass& operator =(const MyClass & obj){
            cout<< "这里是赋值构造函数" <<endl;
            myInt_val = obj.myInt_val;
            myString = obj.myString;
    
            return *this;
      }
      MyClass& operator =(const MyClass && obj){
            cout<< "这里是移动赋值构造函数" <<endl;
            myInt_val = obj.myInt_val;
            myString = std::move(obj.myString);
            return *this;
        }
    private:
      int myInt_val;
      string myString;
    };
    
    MyClass myObj1;
    // 当未定义移动构造函数或者移动赋值语句,调用赋值语句
    // 否则调用移动赋值语句
    myObj1 = global_func();
    
    • 类型转换构造函数:类型转换构造函数,参数只有一个并且非自身类型,不常用
    class MyClass
    {
    public:
        MyClass(int val) {
            myInt_val = val;
            cout<< "这里类型转换构造函数" <<endl;
        }
    private:
      int myInt_val;
      string myString;
    };
    
    // 显示调用转换构造函数MyClass(int val)
    MyClass myObj1(3);
    
    • 个人思考:这么多构造函数,其实大部分情况下使用编译器默认的即可。对于频率调用较高的拷贝构造函数,当类中成员使用智能指针后,拷贝构造函数也只是指针的赋值,不存在效率问题,所以也无需定义移动构造函数了。就像oc的拷贝一样,大部分情况下也是这样。

    二、智能指针

    c++中有四个智能指针,auto_ptr,shared_ptr,weak_ptr,unique_ptr,其中后三个是c++11支持的,第一个已经被弃用,属于c++98的标准

    为什么要用智能指针?用来自动的实现对象的内存管理,不需要手写delete语句

    实现原理:智能指针实际上是一个模板类,魔板类通过引用计数管理对应的指针内存,当类在离开作用域时会自动执行析构函数,这个模板类在析构函数中判断如果引用计数为0,则释放该指针对应的内存。

    1、shared_ptr:多个智能指针可以指向同一个指针对象,每次引用计数都会+1

    2、weak_ptr:仅仅指向指针对象,引用计数不变,用来解决shared_ptr导致的循环引用问题

    3、unique_ptr: 每一个智能指针只能同时指向一个指针对象,也就是它的引用计数最多为1

    使用例子:

    class SmartTestClass {
    public:
        SmartTestClass(){
            cout<<"这里是构造函数 SmartTestClass \n";
        }
    
        ~SmartTestClass(){
            cout<<"这里是析构函数 SmartTestClass \n";
        }
    };
    class Smart_ptr_Test
    {
    public:
        Smart_ptr_Test(string s)
        {
            str = s;
            sharedClass = std::make_shared<SmartTestClass>();
            cout << str<<" Test creat\n";
        }
        ~Smart_ptr_Test()
        {
            cout<<"Test delete:"<<str<<" buffer "<<buffer<<endl;
        }
        string& getStr()
        {
            return str;
        }
        void setStr(string s)
        {
            str = s;
        }
        void print()
        {
            cout<<str<<" buffer "<<endl;
        }
    
        std::shared_ptr<SmartTestClass> getPtr(){
            return sharedClass;
        }
    
        string buffer;
    private:
        string str;
    
        std::shared_ptr<SmartTestClass> sharedClass;
    };
    
    unique_ptr<Smart_ptr_Test> fun()
    {
        return unique_ptr<Smart_ptr_Test>(new Smart_ptr_Test("unique_ptr_789"));
    }
    
    unique_ptr<Smart_ptr_Test> utest(new Smart_ptr_Test("unique_ptr 123"));
    unique_ptr<Smart_ptr_Test> utest2 = make_unique<Smart_ptr_Test>("unique_ptr 456");
    
    ptest->print();
    if(ptest == NULL)cout<<"ptest = NULL\n";
    Smart_ptr_Test* p = ptest2.release();
    p->print();
    ptest.reset(p);
    ptest->print();
    
    ptest2 = std::move(ptest);
    //这里可以用 =,因为unique_ptr实现了移动构造函数
    ptest2 = fun();
    // 编译报错,因为unique_ptr同时智能指向一个指针对象
    ptest2 = ptest;
    
    • 个人思考:智能指针确实可以解放对于动态内存的释放操作,但是也会造成循环引用得问题。不过c++循环引用只有相互引用才会产生,这种情况应该比较少见,不管怎样,极力推荐

    三、lamda表达式

    即匿名函数,闭包表达式

    • 语法参数:

    [捕获列表 ] ( 参数 列表) [可选值] -> 返回值 { 函数体 }

    其中捕获列表的含义如下:

    [] 不捕获任何变量。

    [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。

    [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。

    [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。

    [bar] 按值捕获 bar 变量,同时不捕获其他变量。

    [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。

    可选值:默认没有,可以为mutable 代表可以在函数体内修改外部补货变量的值,和OC的__block类似

    • 使用例子:
    //全局变量
    int all_num = 0;
    
    /** lambda表达式的实现原理:在编译期间,编译器生成一个局部类,其中封装了捕获的变量以及要执行的代码。
     */
    void lambda_main()
    {    
        //局部变量
        int a = 1;
        int b = 2;
        cout << "lambda1:\n";
        auto lambda1 = [=] () mutable {
            //全局变量可以访问甚至修改
            all_num = 10;
            a = 20;
            //加入mutable后则可以对外部变量进行修改;否则编译器会报错
            cout << a << " "
            << b << " "
            << endl;
        };
        lambda1();
        cout << all_num << " " << num_1 <<endl;
        cout << "lambda2:\n";
        auto lambda2 = [&]{
            all_num = 100;
            a = 10;
            b = 20;
            cout << a << " "
            << b << " "
            << endl;
        };
        lambda2();
        cout << all_num << endl;
    }
    
    • 原理:

    在编译期间,编译器生成一个局部类,其中封装了捕获的变量以及要执行的代码。

    假设lamda表达式捕捉了两个整数变量a和b、编译器的实现主要分为如下几个步骤:

    1、创建 lambda 类,实现构造函数,使用 lambda 表达式的函数体重载 operator()(所以 lambda 表达式 也叫匿名函数对象;

    2、创建 lambda 对象

    3、通过对象调用 operator()

    伪代码如下:

    class lambda_xxxx
    {
    private:
        int a;
        int b;
    public:
        lambda_xxxx(int _a, int _b) :a(_a), b(_b)
        {
        }
        bool operator()(int x, int y) throw()
        {
            函数体......
        }
    };
    
    void LambdaDemo()
    {
      int a = 1;
      int b = 2;
        auto lambda1 = [=] () {
          ......
            return true
        };
        lambda1();
        // 转化为如下:
        int a = 1;
        int b = 2;
        lambda_xxxx lambda = lambda_xxxx(a, b);
        bool ret = lambda.operator()(3, 4);
    }
    
    • 个人思考:lamada表达式会像oc/Swift那样造成循环引用吗?

    不会,首先c++中造成循环引用得满足两个条件1、使用shared_ptr指针;2、相互引用;对于第2点,lamda表达式需要在捕获this的同时还得作为this所在类的成员变量,但是c++的静态特性决定了对于捕获了this的lamda表达式无法作为this所在类的成员变量,所以无法相互引用,即不会构成循环引用

    相关文章

      网友评论

          本文标题:c++语言基础(一)

          本文链接:https://www.haomeiwen.com/subject/lbqgadtx.html