• c++ 各种奇门鬼爪的构造函数 和 类的初始化


    统一初始化:

    c++11以前:

    结构体: Mystruct mystruct={1,2,3};

    类: MyClass myclass(1,2,3);

    C++11以后统一为:

    结构体: Mystruct mystruct={1,2,3};

    类: MyClass myclass={1,2,3};

    等号也可省略:

    结构体: Mystruct mystruct{1,2,3};

    类: MyClass myclass{1,2,3};

    且不局限在类:

    int a=3;

    int b(3);

    int c={3};

    int d{3};

    int e{} 赋初始值,可以是0,nullptr,0.0等,看类型而定。

    阻止narrowing:

    int x =3.14; ok

    int x={3.14} 报错

    初始化数组

    int* parray=new int[4]{0,1,2,3};

    构造函数中:

    myConstructor: mArray{0,1,2,3}

    c++ 17: initializer_list<int>:

    auto a={11};

    auto b={11,2};

    得到int: auto c{11};

    auto b{11,2};//error


    不能像java那样,对于无参构造

    AClass a();

    C++应该这样:  AClass a;// 因为上面那样在c++看来是个函数申明。


    参考:C++转换构造函数:将其它类型转换为当前类的类型 (biancheng.net)

    C++类型转换函数:将当前类的类型转换为其它类型 (biancheng.net)

    类型转换函数的语法格式为:

    operator type(){
        //TODO:
        return data;
    }

    operator 是 C++ 关键字,type 是要转换的目标类型,data 是要返回的 type 类型的数据。肯定返回类型和要转换的一致,所以返回值省略了。

    #include <iostream>
    #include <cstdlib>
    using namespace std;
    
    class Complex{
    public:
        Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
    public:
        operator double() const { return m_real; }  //类型转换函数,或者 类型转换运算符
    private:
        double m_real;
        double m_imag;
    };
    
    int main(){
        //下面是正确的用法
        int m = 100;
        Complex c(12.5, 23.8);
        long n = static_cast<long>(m);  //宽转换,没有信息丢失
        char ch = static_cast<char>(m);  //窄转换,可能会丢失信息
        int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) );  //将void指针转换为具体类型指针
        void *p2 = static_cast<void*>(p1);  //将具体类型指针,转换为void指针
        double real= static_cast<double>(c);  //调用类型转换函数
       
        //下面的用法是错误的
        float *p3 = static_cast<float*>(p1);  //不能在两个具体类型的指针之间进行转换
        p3 = static_cast<float*>(0X2DF9);  //不能将整数转换为指针类型
    
        return 0;
    }

    转换

    int main(){
        Complex a(10.0, 20.0);
        a = (Complex)25.5;  //错误,转换失败
        return 0;
    }
    
    添加构造函数
    Complex(double real): m_real(real), m_imag(0.0){ }  //转换构造函数
    
    a = (Complex)25.5;
    相当于:a.Complex(25.5);

    各种构造,体现了构造函数的本意——在创建对象时初始化对象。

    Complex c1(26.4);  //创建具名对象
    Complex c2 = 240.3;  //以拷贝的方式初始化对象: 将 240.3 转换为 Complex 类型(创建一个 Complex 类的匿名对象),然后再拷贝给 c2。
    Complex(15.9);  //创建匿名对象
    c1 = Complex(46.9);  //创建一个匿名对象并将它赋值给 c1
    Complex c1();  //调用Complex()
    Complex c2(10, 20);  //调用Complex(double real, double imag)
    Complex c3(c2);  //调用Complex(const Complex &c)
    Complex c4(25.7);  //调用Complex(double real)

    在 C/C++ 中,不同的数据类型之间可以相互转换。无需用户指明如何转换的称为自动类型转换(隐式类型转换),需要用户显式地指明如何转换的称为强制类型转换。

    自动类型转换示例:

    1. int a = 6;
    2. a = 7.5 + a;

    编译器对 7.5 是作为 double 类型处理的,在求解表达式时,先将 a 转换为 double 类型,然后与 7.5 相加,得到和为 13.5。在向整型变量 a 赋值时,将 13.5 转换为整数 13,然后赋给 a。整个过程中,我们并没有告诉编译器如何去做,编译器使用内置的规则完成数据类型的转换。

    强制类型转换示例:

    1. int n = 100;
    2. int *p1 = &n;
    3. float *p2 = (float*)p1;

    p1 是int *类型,它指向的内存里面保存的是整数,p2 是float *类型,将 p1 赋值给 p2 后,p2 也指向了这块内存,并把这块内存中的数据作为小数处理。我们知道,整数和小数的存储格式大相径庭,将整数作为小数处理非常荒诞,可能会引发莫名其妙的错误,所以编译器默认不允许将 p1 赋值给 p2。但是,使用强制类型转换后,编译器就认为我们知道这种风险的存在,并进行了适当的权衡,所以最终还是允许了这种行为。

    关于整数和小数在内存中的存储格式,请猛击《整数在内存中是如何存储的》《小数在内存中是如何存储的》。

    不管是自动类型转换还是强制类型转换,前提必须是编译器知道如何转换,例如,将小数转换为整数会抹掉小数点后面的数字,将int *转换为float *只是简单地复制指针的值,这些规则都是编译器内置的,我们并没有告诉编译器。

    换句话说,如果编译器不知道转换规则就不能转换,使用强制类型也无用,请看下面的例子:

    1. #include <iostream>
    2. using namespace std;
    3. //复数类
    4. class Complex{
    5. public:
    6. Complex(): m_real(0.0), m_imag(0.0){ }
    7. Complex(double real, double imag): m_real(real), m_imag(imag){ }
    8. public:
    9. friend ostream & operator<<(ostream &out, Complex &c); //友元函数
    10. private:
    11. double m_real; //实部
    12. double m_imag; //虚部
    13. };
    14. //重载>>运算符
    15. ostream & operator<<(ostream &out, Complex &c){
    16. out << c.m_real <<" + "<< c.m_imag <<"i";;
    17. return out;
    18. }
    19. int main(){
    20. Complex a(10.0, 20.0);
    21. a = (Complex)25.5; //错误,转换失败
    22. return 0;
    23. }

    25.5 是实数,a 是复数,将 25.5 赋值给 a 后,我们期望 a 的实部变为 25.5,而虚部为 0。但是,编译器并不知道这个转换规则,这超出了编译器的处理能力,所以转换失败,即使加上强制类型转换也无用。

    幸运的是,C++ 允许我们自定义类型转换规则,用户可以将其它类型转换为当前类类型,也可以将当前类类型转换为其它类型。这种自定义的类型转换规则只能以类的成员函数的形式出现,换句话说,这种转换规则只适用于类。

    本节我们先讲解如何将其它类型转换为当前类类型,下节再讲解如何将当前类类型转换为其它类型。

    转换构造函数

    将其它类型转换为当前类类型需要借助转换构造函数(Conversion constructor)。转换构造函数也是一种构造函数,它遵循构造函数的一般规则。转换构造函数只有一个参数。

    仍然以 Complex 类为例,我们为它添加转换构造函数:

    1. #include <iostream>
    2. using namespace std;
    3. //复数类
    4. class Complex{
    5. public:
    6. Complex(): m_real(0.0), m_imag(0.0){ }
    7. Complex(double real, double imag): m_real(real), m_imag(imag){ }
    8. Complex(double real): m_real(real), m_imag(0.0){ } //转换构造函数
    9. public:
    10. friend ostream & operator<<(ostream &out, Complex &c); //友元函数
    11. private:
    12. double m_real; //实部
    13. double m_imag; //虚部
    14. };
    15. //重载>>运算符
    16. ostream & operator<<(ostream &out, Complex &c){
    17. out << c.m_real <<" + "<< c.m_imag <<"i";;
    18. return out;
    19. }
    20. int main(){
    21. Complex a(10.0, 20.0);
    22. cout<<a<<endl;
    23. a = 25.5; //调用转换构造函数
    24. cout<<a<<endl;
    25. return 0;
    26. }

    运行结果:
    10 + 20i
    25.5 + 0i

    Complex(double real);就是转换构造函数,它的作用是将 double 类型的参数 real 转换成 Complex 类的对象,并将 real 作为复数的实部,将 0 作为复数的虚部。这样一来,a = 25.5;整体上的效果相当于:

    a.Complex(25.5);

    将赋值的过程转换成了函数调用的过程。

    在进行数学运算、赋值、拷贝等操作时,如果遇到类型不兼容、需要将 double 类型转换为 Complex 类型时,编译器会检索当前的类是否定义了转换构造函数,如果没有定义的话就转换失败,如果定义了的话就调用转换构造函数。

    转换构造函数也是构造函数的一种,它除了可以用来将其它类型转换为当前类类型,还可以用来初始化对象,这是构造函数本来的意义。下面创建对象的方式是正确的:

    1. Complex c1(26.4); //创建具名对象
    2. Complex c2 = 240.3; //以拷贝的方式初始化对象
    3. Complex(15.9); //创建匿名对象
    4. c1 = Complex(46.9); //创建一个匿名对象并将它赋值给 c1

    在以拷贝的方式初始化对象时,编译器先调用转换构造函数,将 240.3 转换为 Complex 类型(创建一个 Complex 类的匿名对象),然后再拷贝给 c2。

    如果已经对+运算符进行了重载,使之能进行两个 Complex 类对象的相加,那么下面的语句也是正确的:

    1. Complex c1(15.6, 89.9);
    2. Complex c2;
    3. c2 = c1 + 29.6;
    4. cout<<c2<<endl;

    在进行加法运算符时,编译器先将 29.6 转换为 Complex 类型(创建一个 Complex 类的匿名对象)再相加。

    需要注意的是,为了获得目标类型,编译器会“不择手段”,会综合使用内置的转换规则和用户自定义的转换规则,并且会进行多级类型转换,例如:

    • 编译器会根据内置规则先将 int 转换为 double,再根据用户自定义规则将 double 转换为 Complex(int --> double --> Complex);
    • 编译器会根据内置规则先将 char 转换为 int,再将 int 转换为 double,最后根据用户自定义规则将 double 转换为 Complex(char --> int --> double --> Complex)。


    从本例看,只要一个类型能转换为 double 类型,就能转换为 Complex 类型。请看下面的例子:

    1. int main(){
    2. Complex c1 = 100; //int --> double --> Complex
    3. cout<<c1<<endl;
    4. c1 = 'A'; //char --> int --> double --> Complex
    5. cout<<c1<<endl;
    6. c1 = true; //bool --> int --> double --> Complex
    7. cout<<c1<<endl;
    8. Complex c2(25.8, 0.7);
    9. //假设已经重载了+运算符
    10. c1 = c2 + 'H' + true + 15; //将char、bool、int都转换为Complex类型再运算
    11. cout<<c1<<endl;
    12. return 0;
    13. }

    运行结果:
    100 + 0i
    65 + 0i
    1 + 0i
    113.8 + 0.7i

    再谈构造函数

    构造函数的本意是在创建对象的时候初始化对象,编译器会根据传递的实参来匹配不同的(重载的)构造函数。回顾一下以前的章节,到目前为止我们已经学习了以下几种构造函数。

    1) 默认构造函数。就是编译器自动生成的构造函数。以 Complex 类为例,它的原型为:

    Complex();  //没有参数


    2) 普通构造函数。就是用户自定义的构造函数。以 Complex 类为例,它的原型为:

    Complex(double real, double imag);  //两个参数


    3) 拷贝构造函数。在以拷贝的方式初始化对象时调用。以 Complex 类为例,它的原型为:

    Complex(const Complex &c);


    4) 转换构造函数。将其它类型转换为当前类类型时调用。以 Complex 为例,它的原型为:

    Complex(double real);


    不管哪一种构造函数,都能够用来初始化对象,这是构造函数的本意。假设 Complex 类定义了以上所有的构造函数,那么下面创建对象的方式都是正确的:

    1. Complex c1(); //调用Complex()
    2. Complex c2(10, 20); //调用Complex(double real, double imag)
    3. Complex c3(c2); //调用Complex(const Complex &c)
    4. Complex c4(25.7); //调用Complex(double real)

    这些代码都体现了构造函数的本意——在创建对象时初始化对象。

    除了在创建对象时初始化对象,其他情况下也会调用构造函数,例如,以拷贝的的方式初始化对象时会调用拷贝构造函数,将其它类型转换为当前类类型时会调用转换构造函数。这些在其他情况下调用的构造函数,就成了特殊的构造函数了。特殊的构造函数并不一定能体现出构造函数的本意。

    对 Complex 类的进一步精简

    上面的 Complex 类中我们定义了三个构造函数,其中包括两个普通的构造函数和一个转换构造函数。其实,借助函数的默认参数,我们可以将这三个构造函数简化为一个,请看下面的代码:

    1. #include <iostream>
    2. using namespace std;
    3. //复数类
    4. class Complex{
    5. public:
    6. Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
    7. public:
    8. friend ostream & operator<<(ostream &out, Complex &c); //友元函数
    9. private:
    10. double m_real; //实部
    11. double m_imag; //虚部
    12. };
    13. //重载>>运算符
    14. ostream & operator<<(ostream &out, Complex &c){
    15. out << c.m_real <<" + "<< c.m_imag <<"i";;
    16. return out;
    17. }
    18. int main(){
    19. Complex a(10.0, 20.0); //向构造函数传递 2 个实参,不使用默认参数
    20. Complex b(89.5); //向构造函数传递 1 个实参,使用 1 个默认参数
    21. Complex c; //不向构造函数传递实参,使用全部默认参数
    22. a = 25.5; //调用转换构造函数(向构造函数传递 1 个实参,使用 1 个默认参数)
    23. return 0;
    24. }

    精简后的构造函数包含了两个默认参数,在调用它时可以省略部分或者全部实参,也就是可以向它传递 0 个、1 个、2 个实参。转换构造函数就是包含了一个参数的构造函数,恰好能够和其他两个普通的构造函数“融合”在一起。


    在已分配的内存上,new一个对象放上。一般用于内存池类似地方。

    void* p=很大一块内存  malloc( type* sizeof(type))

    type obj = new (p) type();

    在vector的源码中用到:

  • 相关阅读:
    python算法:嵌套数组转变成一维数组
    python算法:约瑟夫问题
    python的加密算法(1):反转加密
    算法:桶排序
    正则表达式学习(3):相关符号解释
    代码这样写更优雅(1)
    正则表达式学习(2)
    神奇的Bank系统之旅哦
    C#之第五单元的项目打印购物单
    C#语言的基础
  • 原文地址:https://www.cnblogs.com/bigben0123/p/14746097.html
Copyright © 2020-2023  润新知