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

news2024/11/22 16:00:55

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类似的方式。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Neepu2023-部分Reserve复现

目录 Base IKUN检查器 dnSpy junk code Cheat Engine工具使用&#xff1a; 奇怪的ELF mov混淆问题&#xff1a; Xor Base 打开附件&#xff0c;可以看到主函数 先是给出一个物理题&#xff0c;要求输入答案&#xff0c;这个无关紧要&#xff0c;接着要求输入一串字符&…

MyBatis-Plus 可视化代码生成器来啦,生产力直接拉满

在基于Mybatis的开发模式中&#xff0c;很多开发者还会选择Mybatis-Plus来辅助功能开发&#xff0c;以此提高开发的效率。虽然Mybatis也有代码生成的工具&#xff0c;但Mybatis-Plus由于在Mybatis基础上做了一些调整&#xff0c;因此&#xff0c;常规的生成工具生成的代码还有一…

被Chatgpt碾压的打工人与大学生,准备反击!

最近一段时间&#xff0c;chatgpt可谓如火如荼&#xff0c;它的出现引发各行各业的震动&#xff0c;有人利用它实现了一夜暴富&#xff0c;有企业将它纳进人才招聘的技能要求中&#xff0c;国内各大厂商也纷纷下场推出自家的AI大模型&#xff0c;从第一代到GPT-4&#xff0c;所…

基于html+css的图片展示92

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

用好 kafka,你不得不知的那些工具

前言 工欲善其事&#xff0c;必先利其器。本文主要分享一下消息中间件 kafka 安装部署的过程&#xff0c;以及我平时在工作中针对 kafka 用的一些客户端工具和监控工具。 kafka 部署架构 一个 kafka 集群由多个kafka broker组成&#xff0c;每个broker将自己的元数据信息注册…

【ROS2】install micro_ros

本文参考b站up&#xff1a;“照祥同学”的教程来的&#xff0c;中间一些细节的操作谨以此文作为补充&#xff0c;或者说是我在按照教程走的时候遇到的问题记录。视频链接&#xff1a;第二节&#xff1a;安装micro_ros 的 Arduino 开发环境_哔哩哔哩_bilibili 1. 安装和配置ros…

集权安全 | 域渗透中的 DCSync技术分析

DCSync是AD域渗透中常用的凭据窃取手段&#xff0c;默认情况下&#xff0c;域内不同DC每隔15分钟会进行一次数据同步&#xff0c;当一个DC从另外一个DC同步数据时&#xff0c;发起请求的一方会通过目录复制协议&#xff08;MS- DRSR&#xff09;来对另外一台域控中的域用户密码…

一分钟图情论文:《面向学科建设的我国文献资源保障评价研究综述》

一分钟图情论文&#xff1a;《面向学科建设的我国文献资源保障评价研究综述》 高质量的文献资源保障工作不仅能够提供完备的环境、满足用户的信息需求&#xff0c;在高校中&#xff0c;还可以发挥促进教学资源优化和科研成果产出、增强科技基础能力等作用。华中师范大学的夏立…

『树莓派云台机器人』01. 使用手机控制树莓派云台机器人

目录 1. 检查是否已经开机&#xff0c;连接机器人wifi2. 安装树莓派控制app应用&#xff0c;直连模式连接机器人3. 机器人功能实现总结 欢迎关注 『树莓派云台机器人』 博客&#xff0c;持续更新中 欢迎关注 『树莓派云台机器人』 博客&#xff0c;持续更新中 动手组装等步骤请…

halcon 安装21.05版本 小坑记录

&#xff08;注意&#xff1a;都可以设置语言&#xff09; 1.选择扩展安装 影响安装进度显示 可以后续单独安装 Visual Studio变量检查扩展 2.破解相关 主程序dll路径 路径: C:\Users******\AppData\Local\Programs\MVTec\HALCON-21.05-Progress\bin\x64-win64 Visual St…

chatgpt赋能python:Python修改配置文件

Python 修改配置文件 Python 作为一种优秀的编程语言&#xff0c;在实际使用中起到了很大的作用。对于开发者来说&#xff0c;修改配置文件是一个常见的操作&#xff0c;Python 也支持在代码中修改配置文件。本篇文章将介绍如何使用 Python 修改配置文件&#xff0c;并且分享一…

【CSAPP】虚拟内存 | 地址空间 | 页表内存保护 | 页错误引发异常逐出 (evicted)

&#x1f4ad; 写在前面&#xff1a;本文将学习《深入理解计算机系统》虚拟内存部分&#xff0c;CSAPP 是计算机科学经典教材《Computer Systems: A Programmers Perspective》的缩写&#xff0c;该教材由Randal E. Bryant和David R. OHallaron 合著。 &#x1f4dc; 本章目录…

Bytebase:更好地管理你的 OceanBase 数据库

我们很高兴宣布&#xff1a;OceanBase 用户现在可以使用 Bytebase 进行数据库变更管理啦&#xff01;&#x1f680; Bytebase 是一款为 DevOps 团队准备的数据库 CI/CD 工具&#xff0c;专为开发者和 DBA 打造&#xff0c;也是唯一被 CNCF Landscape 收录的 Database CI/CD 产…

k8s 弹性伸缩的使用

1.手动扩缩容 编辑一个yaml文件 vi deployment-nginx.yaml apiVersion: apps/v1 kind: Deployment metadata:lables:app: nginxname: nginxnamespace: default spec:replicas: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name:…

C语言交换数组内容

代码&#xff1a; int main() {int arr1[] { 1,2,3,4,5 };int arr2[] { 6,7,8,9,0 };int sz sizeof(arr1) / sizeof(arr1[0]);int i 0;for (i 0;i<sz; i ) {int tmp arr1[i];arr1[i] arr2[i];arr2[i] tmp;}for (i 0; i < sz; i) {printf("%d ", ar…

用Vue简单开发一个学习界面

文章目录 一.首先创建我们的Vue文件夹二.源代码BodyDemoHearderDemoHomeDemoMarkdownDemoFileManager.jsMain.js&#xff08;注意绑定&#xff09;APP源代码 效果图&#xff08;按钮功能&#xff09;新增二级菜单&#xff08;v-for&#xff09;需要的可以私信 一.首先创建我们的…

办公技巧:学会这 7 种 PPT 制作技巧,让 PPT 制作效率飙升

F4 键&#xff1a;重复上一步操作 例如需要你在一分钟内完成 8 个形状的排版&#xff0c;你会怎么做&#xff1f; 如果是最基础的方式&#xff0c;可能得画出一个之后&#xff0c;慢慢的按住 Ctrl 复制新的出来&#xff0c;但这样实在是太慢了&#xff01;&#xff08;你是这样…

游戏安全运营前置化,10项安全测试预见外挂风险

自今年起&#xff0c;游戏版号已恢复常态化发放。据统计&#xff0c;截至目前年内累计发放460款游戏版号&#xff0c;每月的发放数量均超80款。多款热门新游已上线&#xff0c;大量游戏也已进入测试阶段&#xff0c;蓄势待发&#xff0c;游戏行业持续回暖。 在游戏行业动态回暖…

【历史上的今天】5 月 26 日:美国首个计算机软件程序专利;苹果市值首次超越微软;Wiki 的发明者出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 5 月 26 日&#xff0c;在 1995 年的今天&#xff0c;微软公司首席执行官比尔盖茨发表了一番讲话&#xff0c;他认为自己的公司在估计互联网的影响和普及方面错…

opencv实践-图像去畸变

目录 1.背景2.镜头成像畸变原因3.去畸变方法4. opencv去畸变函数5.代码实现 1.背景 由于相机的镜头并不完全理想&#xff0c;成像时会产生线条扭曲、失真等。对双目图像、鸟瞰图等进行处理时&#xff0c;首先要矫正去畸变。 2.镜头成像畸变原因 相机的镜头前有一块透镜&…