• 【C++初阶-类和对象下】嗯...这样对劲多了


    前言

    博主水平有限,不足之处如能斧正,感激不尽!

    通过前两篇类和对象的讲解,我们对类和对象有了大概的认识,此篇主要是补充类和对象的其他语法,主要有:

    • 构造函数相关
      • 初始化列表
      • explicit关键字
    • static成员
    • const成员函数
    • 友元
      • 友元函数
      • 友元类
    • 内部类
    • 匿名对象

    一、构造函数相关

    我们提过,构造函数体内对属性的“初始化”实际上是赋值,到了函数体内,属性已经定义好了。那我们怎么能给属性真正初始化呢?

    这就需要用到初始化列表。

    初始化列表

    是什么

    初始化列表是对象的属性定义的地方。

    为什么

    某些属性必须初始化,而不是定义后赋初值。

    • 没有默认构造的自定义类型(需要传参构造)
    • const常量
    • 引用
    特性
    • 无论有没有显式写出初始化列表,调用构造函数的时候都会走一遍初始化列表
    • 初始化的顺序是按照成员声明的顺序,不是按照写在初始化列表里的顺序
    怎么用
    className(datatype mem1, datatype mem2):_mem1(mem1),_mem2(mem2)
    {}
    
    • 1
    • 2

    函数体前,用冒号开头,通过括号给成员初始化,成员的初始化用逗号隔开。

    class B
    {
    public:
    	B(int b)
    	{
    		_b = b;
    	}
    private:
    	int _b;
    };
    
    class A
    {
    public:
    	A(const int a1, int& a2, int b):_a1(a1), _a2(a2), _bb(b)
    	{}
    
    private:
    	const int _a1;
    	int& _a2;
    	B _bb;
    };
    
    int main()
    {
    	int a1 = 10;
    	int a2 = 20;
    	int b = 30;
    
    	A aa(a1, a2, b);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    如果不适用初始化列表初始化:

    在这里插入图片描述

    建议

    尽量都用初始化列表来初始化:用了一定没问题,不用可能有问题。


    explicit关键字

    单参构造在类型合适的时候,会被赋值运算符重载触发 类型转换 的功能。

    听着很抽象,结合explicit来理解。

    是什么

    explicit 是用来 禁用函数自动类型转换 的关键字

    为什么

    有时我们不想要这样触发的类型转换

    怎么用
    explicit A()
    {
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4

    接下来就看看,有无explicit修饰的构造有什么区别。

    class A1
    {
    public:
        explicit A1(int a):_a(a)
        {}
    private:
        int _a;
    };
    
    class A2
    {
    public:
        explicit A2(int a):_a(a)
        {}
    private:
        int _a;
    };
    
    int main()
    {
        //单参构造函数,用函数调用触发,不会发生类型转换
        A1 aa1(10);
        
        //单参数构造函数,用赋值运算符重载触发,会发生类型转换
        A2 aa2 = 20;//error:No viable conversion from 'int' to 'A2'
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    二、static成员

    是什么

    和整个类深度绑定的成员。

    为什么

    有些成员,需要能够被同类的所有对象访问。

    特性

    static成员:

    • 和整个类深度绑定,不属于任何一个对象
    • 受到访问限定符的限制

    static成员变量

    • 类内声明,类外定义初始化
    • 在对象生成之前生成

    static成员函数

    • 指定类域即可调用
    • 没有this指针(只能访问静态的成员)
    怎么用

    1. static成员变量:类内声明,类外定义初始化

    class A
    {
    public:
        A()
        {
            //...
            cnt++;
        }
        
        A(const A& aa2)
        {
            //...
            cnt++;
        }
        
        void Printcnt()
        {
            cout << cnt << endl;
        }
    private:
        int _mem = 0;
        
        //计算调用了几次构造和拷贝构造
        static int cnt;
    };
    
    int A::cnt = 0;
    
    A test(A aa)
    {
        return aa;
    }
    
    int main()
    {
        A aa1;
        A aa2(aa1);
        test(aa1);
        
        aa1.Printcnt();
        return 0;
    }
    
    :4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    2. static成员函数

    class A
    {
    public:
        static void Print()
        {
            cout << "className: A" << endl;
        }
    private:
        int _mem = 0;
    };
    
    int main()
    {
        A aa1;
        A aa2;
        A aa3;
        
        aa1.Print();
        aa2.Print();
        aa3.Print();
        
        return 0;
    }
    
    :className: A
    className: A
    className: A
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    【静态成员函数可以调用非静态成员函数吗?】

    :不可以,没有this指针,非静态成员函数的第一个this形参接收不到。

    【非静态成员函数可以调用静态成员函数吗?】

    可以,静态成员函数属于整个类,类外指定类域即可调用,类内的成员函数更是可以直接调用。


    三、const成员函数

    是什么

    this指针具有常性的成员函数。

    为什么

    带上const对象一起玩。

    默认情况下,this的类型是 classname* const,尽管它是隐式传递的,也还是要遵循初始化规则,这也代表,当对象为常量对象,this指针传参会不匹配:

    const classname* const 传给 classname* const

    所以引入了const成员函数,
    来声明此函数的this是const className* const。

    怎么用
    void Print() const
    {
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4

    放在参数列表后面,表示此成员函数的this指针为 const className* const this。

    有什么用
    • 提高传参灵活性
    • 保护不用改变的对象
    建议

    不用改变对象的函数全声明成const成员函数。


    四、友元

    友元函数

    有些时候,我们实现函数时,需要在类外访问类内的私有成员,但是又不想通过成员函数的形式,就有了友元。

    是什么

    是对非成员函数对类成员的访问权限的声明(声明后可以访问类内成员)。

    虽然是满足了需要,但是这也破坏了类的封装,尽量少用。

    为什么

    有时需要允许特定的函数访问私有成员。

    特性
    • 仅仅声明了函数对类的访问权限,不声明具体函数
    • 只有类的访问权限,不是类的成员函数(没有this)
    • 定义在类内哪里都行
    怎么用
    class A
    {
    public:
        friend void PrintMem(A& aa);
    private:
        int _mem = 0;
    };
    
    void PrintMem(A& aa)
    {
        cout << aa._mem << endl;
    }
    
    int main(int argc, const char * argv[])
    {
        A aa;
        PrintMem(aa);
        
        return 0;
    }
    
    :0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    下图就印证了:友元函数的声明仅仅是声明了“函数的访问权限”,而非“对函数本身的声明”。

    在这里插入图片描述


    友元类

    基本和友元函数一样。

    特性
    • 能直接访问外部类的static成员
    • 友元关系是单向的
    • 友元关系不能传递
    • 友元关系不能继承(后面讲)
    • 定义类内在哪里都行
    class A
    {
    public:
        friend class B;
    private:
        int _mem = 10;
    };
    
    class B
    {
    public:
        void PrintA()
        {
            cout << _aa._mem << endl;
        }
    private:
        int _mem = 0;
        A _aa;
    };
    
    int main(int argc, const char * argv[])
    {
        B bb;
        bb.PrintA();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    五、内部类

    是什么

    类中类,但它是独立的类。

    特性
    • 内部类是外部类的友元类
    • 外部类不是内部类的友元类
    • sizeof(外部类)就是外部类的大小,不包括内部类的大小
    • 内部类定义在哪里都可以,但要指定类域
    怎么用
    class A
    {
    public:
        class B
        {
        public:
            void Print(A& aa)
            {
                cout << "内部类B访问外部类A的static成员:" << _a1 << endl;
                cout << "内部类B访问外部类A的普通成员:" << aa._a2 << endl;
            }
        private:
            int _b = 30;
        };
        
    private:
        static int _a1;
        int _a2 = 20;
    };
    
    int A::_a1 = 10;
    
    int main()
    {
        A aa;
        A::B bb;
        bb.Print(aa);
        
        return 0;
    }
    
    :内部类B访问外部类A的static成员:10
    内部类B访问外部类A的普通成员:20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    建议

    破坏封装,尽量少用。


    六、匿名对象

    是什么

    没有名字,只在当前行生效(下一行前自动调用析构)的对象。

    为什么

    有时候,我们实例化一个对象仅仅只是为了调用它的函数或其他简单的操作,这时候要实例化出一个普通对象,用完还要等到出生命周期才调用析构——不如弄个临时的用用。

    怎么用
    class A
    {
    public:
        void Print()
        {
            cout << _aa << endl;
        }
        
        ~A()
        {
            cout << "~A()" << endl;
        }
    private:
        int _aa;
    };
    
    int main()
    {
        A().Print();
        cout << "------------" << endl;
        
        return 0;
    }
    
    :10
    ~A()
    ------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    今天的分享就到这里啦

    这里是培根的blog,期待与你共同进步!

    下期见~

  • 相关阅读:
    软考网络工程师路由器配置考点总结
    TAO 训练时遇到 docker报错
    为什么残差连接的网络结构更容易学习?
    Oracle EBS AR收款核销异常会计事件ID丢失修复
    【SpringBoot】统一功能处理
    Tortoise 没有显示绿色图标
    Spring Cloud【服务网关Gateway(过滤器详解、网关过滤器GatewayFilter、自定义网关过滤器、过滤器之全局过滤器、网关的cors跨域配置)】(七)
    苹果、安卓中的ipsec功能
    MiniWord .NET Word模板引擎,藉由Word模板和数据简单、快速生成文件。
    IEEE754浮点型+JS精度问题
  • 原文地址:https://blog.csdn.net/BaconZzz/article/details/127573338