learn C++ NO.6——类和对象(4)

1.再谈构造函数

1.1.构造函数体赋值

在创建类的对象时,编译器回去调用类的构造函数,来各个成员变量一个合适的值。

class Date
{
public:
    Date(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
在这里插入图片描述

1.2.初始化列表

类的构造函数可以使用初始化列表来初始化成员变量。初始化列表位于构造函数的参数列表之后,使用冒号分隔。初始化列表的语法格式为:成员变量名1(初始值), 成员变量名2(初始值), … ,其中初始值可以是一个表达式或者是一个常量值。

class Date
{
public:
    Date(int year,int month,int day)
    //类对象的成员变量定义的位置
    :_year(year)
    ,_month(month)
    ,_day(day)
    {
    }
    
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
	//类对象的定义
	Date d1(2023,5,20);
    return 0;
}

初始化列表本质上就是类对象的成员变量定义的地方。为什么类对象的成员变量一定就需要定义的地方呢?且听下面分析。

1.2.1初始化列表的特点

1、类中如果包含以下类型成员变量,必须放在初始化列表位置进行初始化。

1、引用成员变量
2、const成员变量
3、自定义类型成员变量(且该类没有默认构造函数)

class A
{
public:
    A(int a)
    :_a(a)
    {}
private:
    int _a;

};
class B
{
public:
    B(int a, int& ref)
        :_aobj(a)
        ,_ref(ref)
        ,_n(10)
        {}
private:
    A _aobj; // 没有默认构造函数
    int& _ref; // 引用
    const int _n; // const
    int x = 1;//这是声明缺省值
};

2、每个成员函数有且只有一次初始化。
在这里插入图片描述
3、尽可能的使用初始化列表去做初始化工作,因为不管我们是否使用初始化列表,对于自定义类型成员变量来说,一定会先使用初始化列表来进行初始化。

class Time
{
public:
    Time(int hour = 0)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }

private:
    int _hour;
};

class Date
{
public:
    Date(int day)
    {}
private:
    int _day;
    Time _t;
};

4、 初始化列表并不能完全替代构造函数体内赋值。

class Stack
{
public:
    Stack(int capacity = 10)
        : _a((int*)malloc(capacity * sizeof(int)))
        ,_top(0)
        ,_capacity(capacity)
        //初始化列表并不能完成所有初始化场景
    {
    	//判断有效性
        if (nullptr == _a)
        {
            perror("malloc申请空间失败");
            exit(-1);
        }

        // 要求数组初始化一下
        memset(_a, 0, sizeof(int) * capacity);
    }
private:
    int* _a;
    int _top;
    int _capacity;
};

5(重点)、成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

class A
{
public:
    A(int a)
        :_a1(a)
        , _a2(_a1)
    {}

    void Print()
    {
        cout << _a1 << " " << _a2 << endl;
    }
private:
    int _a2;
    int _a1;
};

int main()
{
    A aa(1);
    aa.Print();
    
    return 0;
}
//A.输出1  1
//B.程序崩溃
//C.编译不通过
//D.输出1  随机值

这里我们来分析一下代码,首先,会调用初始化列表来初始化对象的成员变量。由于类的成员变量的声明顺序为a2、a1。所以,这里初始化列表先用a1的值来初始化a2。但是,a1此时还是随机值,故a2的值为随机值。而a1会被初始化成1。答案为D。

1.3.explicit关键字

这里在正式介绍explicit关键字前,先介绍一下类类型的隐式类型转换。请看下面样例。

class A
{
public:


    A(int a)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }

    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }

private:
    int _a;
};

int main()
{
    A aa1(1);
    A aa2 = 2; // 隐式类型转换,整形转换成自定义类型
    

    A& aa3 = 2;
    // error C2440: “初始化”: 无法从“int”转换为“A &”
    const A& aa3 = 2;
    
    return 0;
}

在这里插入图片描述
在这里插入图片描述
pxplicit关键字可以禁止这类的类型转化。在A的构造函数前加上explicit关键字后,aa2和aa3便不能定义,编译器报错,没有显式的类型转化。
在这里插入图片描述

2.静态成员

2.1.静态成员的概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

class A
{
    
private:
    static int _count;
};

//必须在类外定义
//只有定义时,可以突破一次类域限制
int A::_count = 0;

访问 _count变量必须是在类域内才能访问。当我们需要在类域外访问该静态成员变量时,可以借助静态成员函数来实现访问。

class A
{
public:
    static int Getcount()
    {
        return  _count;
    }
    
private:
    static int _count;
};

//必须在类外定义
//只有定义时,可以突破一次类域限制
int A::_count = 0;

int main()
{
    //需要指明类域进行访问静态成员函数
    cout << A::Getcount() << endl;
    return 0;
}

2.2.经典试题:计算程序中运行到cout处时,创建出了多少个类对象。

class A
{
public:
    A() { ++_scount; }
    A(const A& t) { ++_scount; }
    ~A() { --_scount; }
    static int GetACount() { return _scount; }
private:
    static int _scount;
};

int A::_scount = 0;


A a1;

void Func()
{
    A a;
    static A aa;
}

void TestA()
{
    cout << A::GetACount() << endl;
    A a1, a2;
    Func();
    cout << A::GetACount() << endl;
}

int main()
{
    TestA();
    return 0;
}

在程序一开始,便在全局定义了第一个类对象a1,所以第一个cout执行结果为1。紧接着,定义了两个局部类对象分别是a1,a2。需要注意的是a1和全局的a1不冲突,因为处于不同的作用域范围。执行Func函数,定义了一个局部类对象a和静态类对象aa。Func函数调用结束,局部对象销毁。而静态对象的是定义在静态区的,不会随着函数调用的结束而销毁。所以,第二个cout执行结果为4。

2.3.静态成员的特性

1、静态成员是被所有类的对象共享的,它不属于某个具体的类对象,它是属于整个类,存放在静态区中。
2、静态成员变量必须定义在类外面,定义时不添加static关键字,类中存放的是声明。
3、类静态成员可以用类名::静态成员或者对象.静态成员来进行访问。
4、因为静态成员函数是没有隐含的this指针,所以不能访问非静态的类成员。
5、静态成员也是类成员,会受到public、private、protect访问限定符的限制。

2.4.两个关于静态成员函数和非静态成员函数调用问题

1、静态成员函数可以调用非静态成员函数吗?
答案是不可以,因为静态成员函数没有this指针。调用非静态成员函数的参数部分需要隐含this指针,如果在非静态成员函数内部有对类的成员变量进行访问就会报错。
2. 非静态成员函数可以调用类的静态成员函数吗?
答案是可以。因为非静态成员函数也是类一部分,在类的作用域内调用静态成员函数是OK的。

2.5.简单提及单例类的思想

假设我们需要设计一个只能在栈上或者堆上开辟类对象的方法。我们需要怎么做呢?首先,我们要讲构造函数私有,通过静态成员函数的调用来实现这一功能。

class A
{
public:
    static A GetStackObj()
    {
        A aa;
        return aa;
    }

    static A* GetHeapObj()
    {
        return new A;
    }
private:
    //无法直接构造
    A()
    {}

private:
    int _a1 = 1;
    int _a2 = 2;
};

int main()
{

    A aa1 = A::GetStackObj();//栈上对象
    A aa2 = *(A::GetHeapObj());//堆上对象
    return 0;
}

3.友元

3.1.友元的概念

友元(friend)是C++中的一个关键字,用于实现类之间的访问控制。在C++中,类可以将其他类或函数声明为友元,从而让它们访问自己的私有成员和保护成员。被声明为友元的类或函数可以直接访问另一个类的私有成员和保护成员,而不需要通过该类的公有接口来访问。友元的使用可以增加程序的灵活性和可扩展性,但也会增加程序的耦合度和不安全性。因此,在使用友元时需要谨慎考虑,并遵循最小暴露原则,尽可能地减少对其他类的访问控制。

3.2.友元函数

在前文日期类的实现中,就已经提到了<<运算符和>>运算符重载需要声明友元。这是因为,重载类对象成员函数,this指针会占用默认的第一个参数。没法实现类似cout << obj这样使用库函数。这时候就需要友元函数声明,并将运算符重载到全局。

//声明
class Date
{
    friend ostream& operator <<(ostream& out, const Date& d);

private:
	int _year;
	int _month;
	int _day;
}

//定义
ostream& operator <<(ostream& out, const Date& d)
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}

友元函数的说明:
1、友元函数不能被const修饰。
2、友元函数可以在类定义的任何地方声明,不受访问限定符的限制。
3、一个函数可以使多个类的友元函数。
4、友元函数的调用和普通函数的调用原理相同

3.3.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的所有成员函数。友元关系是单向的,不具有交换性。友元也不具备传递性,即若B是A的友元,C是B的友元,不能说明C是A的友元。友元关系不能继承,这里我们暂时不谈,需要了解有这一概念即可。

class A
{
    friend class B;//声明B是我的友元,B内可以访问A的成员
    //而A内不能访问B的成员
    
private:
    int _a;
    double _d;
protected:
    char _c;
};

class B
{
public:
    int GetAi()
    {
        return _ca._a;
    }
    double GetAd()
    {
        return _ca._d;
    }
    char GetAc()
    {
        return _ca._c;
    }
private:
    int _i;
    A _ca;
};

4.内部类

内部类顾名思义就是一个类定义在另一个类的内部。内部类是一个独立的类,它并不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。内部类是外部类的友元,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

4.1.内部类的特性

1、内部类可以定义在外部类的任意处,无论public、private、protected都是可以的。

class A
{
private:
    static int k;
    int h;
public:
    class B// B天生就是A的友元
    {
    public:
        void foo(const A& a)
        {
            cout << k << endl;//可以直接访问static成员
            cout << a.h << endl;//OK
        }
    };
    
};

int A::k = 1;

int main()
{
    A::B _b;
    cout<<sizeof(A)<<endl;//4字节
    return 0;
}

这里k是存储在静态区中,所以不计入sizeof的大小。内部类可以直接访问外部类的static成员,不需要外部类的对象/类名。

5.匿名对象

C++中的匿名对象指的是没有命名的临时对象,该对象是在表达式中创建的,用于执行某些操作并返回结果

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }

    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }

    A& operator=(const A& aa)
    {
        cout << "A& operator=(const A& aa)" << endl;

        if (this != &aa)
        {
            _a = aa._a;
        }

        return *this;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

int main()
{
    A a(10);
    A(10);//匿名对象,声明周期仅在表达式行内
    return 0;
}

5.1.匿名对象生命周期的延长

int main()
{
    A(10);//匿名对象,声明周期仅在表达式行内
    const A& ra = A(10);
    A(10)
    return 0;
}

在这里插入图片描述
当我们将一个匿名对象赋值给一个 const 引用时,编译器会将这个匿名对象的生命周期延长到引用的作用域范围内。这是因为当我们定义一个 const 引用时,编译器会在内存中分配一个临时变量来存储这个引用所指向的值,而这个临时变量的生命周期和 const 引用的作用域范围相同。因此,当我们将一个匿名对象赋值给 const 引用时,编译器会将这个匿名对象的生命周期延长到 const 引用的作用域范围内,以保证 const 引用能够正确地引用这个对象。这样做可以避免出现悬垂指针的问题,保证程序的安全性和正确性。

6.关于构造函数的补充

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }

    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }

    A& operator=(const A& aa)
    {
        cout << "A& operator=(const A& aa)" << endl;

        if (this != &aa)
        {
            _a = aa._a;
        }

        return *this;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

void Func1(A aa)
{

}

void Func2(const A& aa)
{

}

int main()
{
	A aa1;
	Func1(aa1);
	Func2(aa1);
	return 0;
}

首先,创建aa1对象会调用一次构造函数。而调用Func1函数,在传参的过程中会产生临时变量。将形参拷贝给实参,会调用拷贝构造。而Func2传参并不会调用拷贝构造,因为传的是引用,不会开辟临时空间。
在这里插入图片描述
请看下面的场景。

//class A...
//这里我就不再写了

void Func1(A aa)
{}

A Func5()
{
    A aa;
    return aa;
}

int main()
{
    A ra1 = Func5(); // 拷贝构造+拷贝构造 ->优化为拷贝构造
    cout << "==============" << endl;
    A ra2;
    ra2 = Func5();
	return 0;
}

在这里插入图片描述
对于同一行内的连续的构造+拷贝构造编译器会进行优化,优化为直接构造。而ra2这样的定义的方式,对于内置类型来说会连续调用三次构造函数,对程序性能有所影响。建议对于自定义类型的定义采用ra1类似的方式。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/28086.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件测试必备7大技能

一、测试用例的编写 1.在测试中最重要的文档&#xff0c;他是测试工作的核心&#xff0c;是一组在测试时输入输出的标准&#xff0c;是软件需求的具体对照。编写测试用例&#xff0c;是测试人员的基本功&#xff0c;真正能写好的人并不多。 2.测试用例包含的内容&#xff1a;…

【小白向】树莓派连接手机热点后 设置静态IP

树莓派连接手机热点后 设置静态IP 1.连接至手机热点2.查看当前 IP 地址3.修改 dhcpcd.conf 文件4.重启网络服务5.检查网络设置 1.连接至手机热点 在树莓派上打开 Wi-Fi 设置&#xff0c;并选择你要连接的手机热点&#xff0c;输入密码连接热点&#xff0c;确保你已经成功连接至…

Telerik Report Server R2 2023

Telerik Report Server R2 2023 仪表报告项-使用仪表或类似表盘的显示提供数据的可视化表示。 报告项上的AccessibleRole属性-ARIA(可访问的富Internet应用程序)支持已显著改进。在Web上&#xff0c;当启用了辅助功能时&#xff0c;呈现的报表项包含预定义的辅助功能角色。这样…

哈希表--想彻底了解哈希表,看这一篇文章就可以了

为了一次存储便能得到所查记录&#xff0c;在记录的存储位置和它的关键字之间建立一个确定的对应关系H&#xff0c;已H&#xff08;key)作为关键字为key的记录在表中的位置&#xff0c;这个对应关系H为哈希&#xff08;Hash&#xff09;函数&#xff0c; 按这个思路建立的表为哈…

创建可引导的 macOS 安装器(可启动U盘)

Apple官网下载的macOS镜像&#xff0c;只是一个安装包&#xff0c;不带引导不能直接安装到空白mac机器的。 1、首先&#xff0c;你必须要有台能正常运行macOS的mac pc。 2、下载macOS Sierra 10.12 El Capitan 10.11 Yosemite 10.10 Mountain Lion 10.8 Lion 10.7 点按以…

Ada Tutorial(2)SPARK Examiner + SPARK Prover

文章目录 代码 Task1.adb代码 task3.adbtask4.adb 在Ada和SPARK中&#xff0c;SPARK_Mode是一个编译指示&#xff0c;它表示随后的代码将使用SPARK语言规则进行编译和分析。 在with SPARK_Mode > On的影响下&#xff0c;编译器会在编译过程中应用SPARK语言规则&#xff0c;它…

C语言之数组详解(1)(更新前面数组博客的不足)

目录 一、一维数组 1.一维数组的创建和初始化 (1).数组的创建 (2).数组的初始化 2.一维数组的使用 3.一维数组在内存中的存储 二、二维数组 1.二维数组的创建和初始化 (1).二维数组的创建 (2).二维数组的初始化 2.二维数组的使用 3.二维数组在内存中的存储 三、数组作为函数参…

微软发布自己的 Linux 发行版:Azure Linux

导读在内部使用两年并自 2022 年 10 月起以公共预览版运行后&#xff0c;微软终于在日前正式公开发布了其 Azure Linux 的发行版。 在内部使用两年并自 2022 年 10 月起以公共预览版运行后&#xff0c;微软终于在日前正式公开发布了其 Azure Linux 的发行版。 微软 Azure Lin…

DSDP140B 57160001-ACX

​ DSDP140B 57160001-ACX DSDP140B 57160001-ACX 单相漏电保护器可以接在三相四线制电路中使用 单相漏电维护器不可以接在三相四线制电路中使用。术有专攻&#xff0c;单相漏电开关在漏电维护器内部装置的零序电流互感器检测的是一根相线&#xff08;前方&#xff09;和一…

完型填空技巧

完形中分值最高的是逻辑关系题&#xff0c;逻辑关系分为两种&#xff0c;一种是选项就是逻辑关系的&#xff0c;例: Given the advantages of electronic money, you might thinkthat we would move quickly to the cashless society in which allpayments are made electronic…

【C++】红黑树的模拟实现

文章目录 一、红黑树的概念二、红黑树的性质三、红黑树节点的定义四、红黑树结构五、红黑树的插入操作六、红黑树的调整1.叔叔存在且为红2.叔叔不存在或者存在且为黑3.插入完整代码4.总结 七、红黑树的验证八、红黑树的删除九、红黑树与AVL树的比较十、红黑树的应用十一、红黑树…

LVS+Keepalived 高可用群集实战部署

LVSKeepalived 高可用群集实战部署 一、LVSKeepalived 高可用群集1.LVS2、Keepalived工作原理和作用3、Keepalived体系主要模块及其作用4、Keepalived实现原理剖析5、VRRP &#xff08;虚拟路由冗余协议&#xff09; LVSKeepalived 高可用群集部署&#xff08;抢占模式&#xf…

[nlp] OPT与GPT-2的差异

Meta AI 开源1750亿参数大模型- OPT,FlagAI一键调用! - 知乎简介Meta AI 开源 OPT系列模型,其中最大模型1750 亿参数(需申请访问权限)媲美 GPT-3。OPT系列模型包括了多组不同参数规模的模型权重,如图: OPT开源了一系列大模型,但是实际调用这些模型有很高的技术门槛。为…

PortSwigger web缓存中毒(Cache Poisoning)

一、什么web缓存中毒&#xff1f; Web缓存中毒&#xff08;Web Cache Poisoning&#xff09;是一种攻击技术&#xff0c;攻击者通过操纵Web应用程序的缓存系统&#xff0c;将恶意或欺骗性内容注入到合法的缓存中&#xff0c;以欺骗用户或绕过安全控制。 Web缓存中毒的原理是利用…

scala

面向对象 Scala 的面向对象思想和Java 的面向对象思想和概念是一致的。 Scala 中语法和 Java 不同&#xff0c;补充了更多的功能。 6.1类和对象详解 6.1.1组成结构 构造函数: 在创建对象的时候给属性赋值 成员变量: 成员方法(函数) 局部变量 代码块 6.1.2构造器 每个…

【宝塔建站】Ubuntu下使用宝塔面板一键搭建Z-Blog个人博客

文章目录 1.前言2.网站搭建2.1. 网页下载和安装2.2.网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测试5.结语 1.前言 Ubuntu系统作…

【深度学习】pytorch pth模型转为onnx模型后出现冗余节点“identity”,onnx模型的冗余节点“identity”

情況描述 onnx模型的冗余节点“identity”如下图。 解决方式 首先&#xff0c;确保您已经安装了onnx-simplifier库&#xff1a; pip install onnx-simplifier然后&#xff0c;您可以按照以下方式使用onnx-simplifier库&#xff1a; import onnx from onnxsim import simp…

STM32F407软件模拟I2C实现MPU6050通讯(CUBEIDE)

STM32F407软件模拟I2C实现MPU6050通讯&#xff08;CUBEIDE&#xff09; 文章目录 STM32F407软件模拟I2C实现MPU6050通讯&#xff08;CUBEIDE&#xff09;模拟I2C读写的实现mpu6050_iic.cmpu6050_iic.h代码分析 复位&#xff0c;读取温度&#xff0c;角度等函数封装mpu6050.cmpu…

QT学习07:五种按钮控件

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 抽象类&#xff1a;QAbstractButtonQPushButtonQToolButtonQCommandLinkButtonQRadioButtonQCheckBoxQButtonGroup 抽象类&#xff1a;QAbstractButton 是所有按钮类的祖先。 QAbstractButton的信号&#xff1a…

深入理解CSS字符转义行为

深入理解CSS字符转义行为 深入理解CSS字符转义行为 前言为什么要转义&#xff1f;CSS 转义什么是合法css的表达式 左半部分右半部分 练习参考链接 前言 在日常的开发中&#xff0c;我们经常写css。比如常见的按钮: <button class"btn"></button>&am…