C++笔记---类和对象(下)

news2024/9/21 19:08:17

1. 初始化列表

1.1 初始化列表的使用

在构造函数中,对成员变量进行初始化可以说是公式化的步骤,而初始化列表就将这一步骤进行了标准化。

初始化列表紧跟在构造函数的参数列表后面,使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式(每个成员变量只能出现一次)。

初始化列表在对成员变量进行初始化时,依照的是声明的顺序,而与初始化列表中给出的顺序无关,一般情况下,建议将二者的顺序统一。

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}

private:
    //成员变量的声明
	int _hour;
	int _minute;
	int _second;
};

括号中只要是有值的表达式皆可,这意味着我们也可以这样写:

:_hour((int)malloc(sizeof(int)))

1.2 必须使用初始化列表进行初始化的变量

之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,例如:

Time(int hour = 0, int minute = 0, int second = 0)
{
    _hour = hour;
    _minute = minute;
    _second = second;
}

这样会存在一个问题,即在函数体中无法对以下两种成员变量进行赋值

1. const成员变量

2. 引用类型的成员变量

而这两种类型的变量却恰恰必须要进行初始化,否则会报错。

初始化列表的出现,使得这一问题得以解决,在初始化列表中,可以对二者进行初始化。

或者说,必须依赖初始化列表对二者进行初始化。

所以,一般来说,我们认为初始化列表是成员变量被定义的地方,这也就解释了为什么初始化列表中每个成员变量至多只能出现一次。

class test
{
public:
    test(const int x, int& y)
        :_x(x)
        ,_y(y)
    {}

    // 初始化列表完成成员变量的定义
    // 效果等同于
    // const int _x = x;
    // int& _y = y;

private:
    // 此处只是声明
    const int _x;
    int& _y;
}

但是从底层逻辑来讲,这个说法并不完全合理。

因为对象在被定义的时候,成员变量空间的开辟就已经全部完成,而不是在调用构造函数时才开辟的,下面举一个例子来说明这一点:

 #include<iostream>
 using namespace std;

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

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

 int main()
 {
     A aa(1);
     aa.Print();
 }

在这个程序中,因为_a2先被声明,所以在初始化列表中_a2会先进行初始化。

而_a2是通过_a1进行初始化的,假如此时_a1并没有被定义,那么程序一定会报错,但事实上在输出的结果中我们会看到,_a2的值是一个随机值。

这就说明,初始化列表本质上依然只有初始化的功能,但其确实有权限对只能在定义时初始化的变量进行初始化,因此可以认为初始化列表是成员变量定义的地方。

除了上面提到的两种类型的变量之外,没有默认构造函数的自定义类型的变量在作为成员变量时也必须要放到初始化列表,并给出参数,否则会发生报错“没有合适的默认构造函数可用”。

1.3 其他细节

所有的成员变量都会走初始化列表,例如:

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
    Type x;
};

其中,“Type”为内置类型或自定义类型。

x虽然没有在初始化列表进行初始化,但其也会走初始化列表。

假如Type为内置类型,则其初始化方式未知;假如Type为自定义类型,则会调用该类型的构造函数来对其进行初始化。

C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显式在初始化列表初始化的 成员使用的。例如:

class Time
{
public:
	Time(int hour = 0)
        :_hour(hour)
	{}

private:
	int _hour = 0;
	int _minute = 0;
	int _second = 0;
};

上面这个类在实例化时,_minute和_second并没有显式在初始化列表初始化,但是在经过初始化列表时,由于在声明处存在缺省值,所以二者也会用缺省值以初始化列表的方式进行初始化。

_hour显式在初始化列表进行了初始化,则其缺省值弃用,按照显示方式初始化。

也就是说,成员变量都会经过初始化列表(static成员除外,接下来会讲到),发生的行为如下图所示:

 1.4 总结

1. 每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方,但并不完全正确。

2. 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。

3. 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显式在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显式在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。

4. 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。

2. 类型转换

C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。

2.1 实例+机理

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

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	Date d;
    d = 2024;
	d.Print();
	return 0;
}

 而上面这段代码中,我们将int类型的2024直接赋值给了Date类类型的d。

这里就发生了隐式的类型转换,具体的机理为:

1. 创建一个Date类类型的临时对象;

2. 将2024作为参数传入对应的构造函数中,对该临时变量进行初始化;

3. 将该临时对象作为参数,对d进行拷贝构造;

4. 销毁临时变量。

 这样的过程类似于将int类型的变量赋值给double类型的变量:

1. 创建一个double类型的临时变量;

2. 将2024强制类型转换为duoble类型并存到临时变量中;

3. 将该临时变量赋值给double类型的变量;

4. 销毁临时变量。

但是,在套机理中,临时对象的存在似乎并不是很必要。

编译器一般会将这个过程优化为直接构造,即,将等号右边作为参数直接对等号左边的对象进行构造。

C++11以后可支持多参数转化,例如:

Date d1 = { 2004, 12, 18 };

2.2 意义

例如:

Date arr[3] = {{2004, 12, 18}, {2024, 8, 20}, {2024, 9, 1}};

如果不存在隐式类型转换,我们需要将上面的代码写成:

Date d1(2004, 12, 18);
Date d2(2024, 8, 20);
Date d3(2024, 9, 1);
Date arr[3] = {d1, d2, d3}; 

总结来说,类的类型转换就是方便我们利用临时对象写代码。

3. static成员

被static修饰的成员就叫static成员,也叫静态成员,分为静态成员变量和静态成员函数

静态成员也是类的成员,受public、protected、private访问限定符的限制。

突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。

3.1 static成员变量

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

class A
{
private:
	// 类里面声明
	static int _scount;
};

// 类外面定义初始化
int A::_scount = 0;

与一般的成员变量不同,静态成员变量为所有类对象所共享(有且仅有一个),不属于某个具体的对象,不存在对象中,存放在静态区。

静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员 变量不属于某个对象,不走构造函数初始化列表。

准确来说,static成员变量就是受到类域限制的全局变量

3.2 static成员函数

用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。

class A
{
public:
	// 无this指针
	static int GetACount()
	{
		return _scount;
	}
private:
	// 类里面声明
	static int _scount;
};

// 类外面初始化
int A::_scount = 0;

int main()
{
	cout << A::GetACount() << endl;
	cout << a1.GetACount() << endl;

	return 0;
}

由于没有this指针,所以静态成员函数只能访问静态成员变量,而不能访问非静态的。

当然,非静态的成员函数因为有this指针,所以可以访问任意的静态成员变量和静态成员函数。

3.3 运用练习

求1+2+3+...+n_牛客题霸_牛客网

//求1+2+3+...+n,
//要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

// 利用按位与
class Solution {
public:
    int Sum_Solution(int n) {
        n && (n += Sum_Solution(n - 1));
        return n;
    }
};

// 利用static成员
class Sum
{
public:
    Sum()
    {
        _ret += _i;
        _i++;
    }

    static int GetRet()
    {
        return _ret;
    }

private:
    static int _i;
    static int _ret;
};

int Sum::_i = 1;
int Sum::_ret = 0;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum arry[n];
        return Sum::GetRet();
    }
};

 4. 友元

大多数情况下,为了类的封装性,我们会将成员变量设置为私有。

但是,在某些情况下,我们的某个非类的成员的函数又不得不对类的成员变量进行访问。

例如日期类的实现中,对“<<”和“>>”的重载函数C++笔记---日期类实现-CSDN博客。

友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数友元类函数声明或者类 声明的前面加friend,并且把友元声明放到一个类的里面

4.1 友元函数

有了友元声明,外部友元函数就可访问类的私有和保护成员,但友元函数仅仅是一种声明,他不是类的成员函数。

一个函数可以是多个类的友元函数。

// 前置声明,否则A的友元函数声明编译器不认识B 
class B;

class A
{
    // 友元声明
    friend void func(const A& aa, const B& bb);
private:
    int _a1 = 1;
    int _a2 = 2;
};

class B
{
    // 友元声明
    friend void func(const A& aa, const B& bb);
private:
    int _b1 = 3;
    int _b2 = 4;
};

void func(const A& aa, const B& bb)
{
    cout << aa._a1 << endl;
    cout << bb._b1 << endl;
}

int main()
{
    A aa;
    B bb;
    func(aa, bb);
    return 0;
}

4.2 友元类

友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。

class A
{
    // 友元声明
    friend class B;
private:
    int _a1 = 1;
    int _a2 = 2;
};

class B
{
public:
    void func1(const A& aa)
    {
        cout << aa._a1 << endl;
        cout << _b1 << endl;
    }
    void func2(const A& aa)
    {
        cout << aa._a2 << endl;
        cout << _b2 << endl;
    }
private:
    int _b1 = 3;
    int _b2 = 4;
};

int main()
{
    A aa;
    B bb;
    bb.func1(aa);
    bb.func1(aa);
    return 0;
}

友元声明就好比是两个人A和B。

A提前向自家的保安(编译器)说明“B是我的好朋友,如果他要访问我家,请放行”(友元声明)。

所以,当B来访问A时可以畅通无阻(B的函数可以访问A的成员变量)。

但在上面的程序中,B并没有对自家的保安进行过说明(没有为A进行友元声明),所以当A来访问B时,保安不会放行(A的函数无法访问到B的成员变量)。

也就是说,友元类的声明并不是让两个类成为了好友可以互相访问,而是对对方放行的一种许可。

1. 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。

2. 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。

 友元有时能提供便利,但是会增加类的耦合度,破坏了封装,所以友元不宜多用。

5. 内部类

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。

内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。

内部类默认是外部类的友元类。

内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考 虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其 他地方都用不了。

例如刚才利用Sum类解决问题时,可以将Sum类设计为Solution类的内部类:

// 设计成内部类
class Solution {
private:
    static int _i;
    static int _ret;

    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            _i++;
        }

        static int GetRet()
        {
            return _ret;
        }

    };
public:
    int Sum_Solution(int n) {
        Sum arry[n];
        return Sum::GetRet();
    }
};

int Solution::_i = 1;
int Solution::_ret = 0;

6. 匿名对象

用类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名(实参)定义出来的叫有名对象。

匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。

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

int main()
{
    // 定义匿名对象
    A();
    A(10);

    return 0;
}

在调用Solution类中的函数来解决问题时,匿名对象能带来极大的便利:

class Solution 
{
public:
    int Sum_Solution(int n) 
    {
        //...
        return n;
    }
};

int main()
{
    cout <<  Solution().Sum_Solution(10) << endl;
    return 0;
}

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

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

相关文章

今日总结:解决了三个小问题

问题01 1.在Bootstrap5中&#xff0c;offcanvas失去焦点后会自动回到顶端&#xff0c;用了一个非常简单的方法&#xff0c;将触发侧边栏的按钮代码由 <button type"button">换成 <a type"button">更多解决方法详见&#xff1a;更多方法 注…

杰发科技AC7801——GPIO通过寄存器地址控制高低电平

通过这个寄存器来查看控制的是哪个ODR值&#xff0c;使用sample&#xff0c;发现是0x20080068的第7和第9位 使用51控制寄存器的代码来置高置低代码&#xff0c;注意变量需要用unsigned int来声明 unsigned int ledBit 0;mdelay(100);ledBit | (1 << 9); ledBit & ~…

【第五节】Win32汇编程序设计

目录 一、汇编的第一个“helloworld” 二、汇编中的标号 三、的使用 四、数据定义 五、全局变量 六、局部变量 七、结构体 八、结构体的访问 九、获取变量地址 十、函数 十一、分支与循环 十二、内联汇编 十三、裸函数的使用 一、汇编的第一个“helloworld” .38…

从桌面到云端,2024年智能录屏解决方案全攻略

从教学演示到游戏直播&#xff0c;从软件教程到会议记录&#xff0c;录屏软件已经逐渐成为不可或缺的工具。那么面对这众多录屏软件我们要怎么选择呢&#xff1f;有没有和win10录屏快捷键一样可以快捷操控的工具呢&#xff1f;这次我们一起来探讨吧。 1.福昕录屏大师 链接&am…

Windows下线程的创建与使用(win32-API)

一、前言 线程是比进程更轻量级的执行单元&#xff0c;允许在一个进程中并发执行多个控制流。每一个线程都有自己的程序计数器、寄存器集和栈空间&#xff0c;但它们共享所属进程的全局数据和资源。这种共享内存模型使线程间的通信比进程间通信更为高效&#xff0c;同时也带来…

2-71 基于matlab的小波分析在心电信号去噪中的应用

基于matlab的小波分析在心电信号去噪中的应用&#xff0c;主要针对心电信号中的肌电干扰/基线漂移/工频干扰进行的算法研究&#xff0c;输出了三类去噪结果。程序已调通&#xff0c;可直接运行。 2-71 基线漂移去噪 工频干扰去噪 - 小红书 (xiaohongshu.com)

android apk 加固后的地图加载异常及重新签名

1.首先根据需求将打包生成后的APK进行加固&#xff0c;可以使用360、阿里、腾讯加固等。 2.加固后的APK无法直接安装&#xff0c;需要重新进行签名。 3.首先找到sdk的位置&#xff0c;进入build-tools目录。 4.根据gradle文件选择版本目录。 5.将加固后的APK放至该目录下。在…

QT-小游戏翻金币

QT-小游戏翻金币 一、演示效果二、使用步骤三、下载链接 一、演示效果 二、使用步骤 #include "chooselevelscene.h" #include <QMenuBar> #include <QPainter> #include "mypushbutton.h" #include <QDebug> #include <QTimer> …

Ajax-3

一.图片上传 1.获取图片文件对象 2.使用FormData携带图片文件 const fd new FormData() fd.append(参数名, 值) 3.提交表单数据到服务器&#xff0c;使用图片url网址 二.AJAX原理—XMLHttpRequest 定义&#xff1a;XMLHttpReques&#xff08;XHR&#xff09;对象用于与服务器…

P38-数据存储1

百度2015年系统工程师笔试题 编程题 编程题 编程题 编程题

20240820飞凌的OK3588-C的核心板在Linux R4下使用poweroff关机

20240820飞凌的OK3588-C的核心板在Linux R4下使用poweroff关机 2024/8/20 14:03 经过测试&#xff0c;poweroff有效&#xff0c;关机之后&#xff0c;12V/0.024A12*0.0240.288W shutdown无效。 reboot -p无效。 rootok3588:/# rootok3588:/# shutdown -h now sh: shutdown: c…

Maven-06.依赖管理-依赖传递

一.依赖传递 什么是依赖传递&#xff1a;projectA依赖于JAR包和projectB&#xff0c;而JAR包又依赖于黄色的JAR包。而projectB依赖于projectC和其他JAR包。因此projectA依赖于projectB,projectC和图中的所有JAR包。这就是依赖的传递性。其中蓝绿色部分成为直接依赖。在当前项目…

ant design pro 的环境变量的使用

如上图所示&#xff0c;定义好环境变量后&#xff0c;整个应用的名字就发生的变化。 规则 环境变量的名称要以 UMI_APP 开头 如何使用这个环境变量 直接用 process.env.UMI_APP_APP_NAME 来调它。 ant design pro 如何去保存颜色ant design pro v6 如何做好角色管理ant desi…

【2025校招】4399 NLP算法工程师笔试题

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/19 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;2h 本试卷分为单选&#xff0c;自我评价题&#xff0c;编程题 单选和自我评价这里不再介绍&#xff0c;4399的编程题一如既往地抽象&#xff…

JavaScript(25)——BOM、延迟函数、JS执行机制

BOM BOM是浏览器对象模型 window对象是一个全局对象&#xff0c;也就是JavaScript中的顶级对象所有通过var定义的全局作用域中的变量&#xff0c;函数都会变成window对象的属性和方法window对象下的属性和方法调用的时候可以省略window 延时函数 let a setTimeout(回调函数…

Python(Falsk) + React Golang(Gin) + Vue 全栈开发的最佳实践

前面分别讲了 Python(Falsk) 、 React 、 Golang(Gin) 、 Vue(Element)&#xff0c;现在整体的给大家汇报一下&#xff0c;这个是简单搭建的demo&#xff0c;后面的添砖加瓦需要自己动手咯&#xff0c;有不明白的可以参考一下小编前面的文章&#xff0c;也许会给大家有答疑解惑…

QTday04

1.思维导图 2. . #include "widget.h" #include "ui_widget.h" #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(this)) //给语音播报者实例化空间{ui->setupUi(th…

ansible搭建+ansible常用模块

ansible搭建 管理机安装ansible,被管理节点必须打开ssh服务 1.管理机安装ansible yum -y install ansible 2.查看版本 ansible --version ansible 2.9.27 3.查找配置文件 find /etc/ -name "*ansible*" /etc/ansible /etc/ansible/ansible.cfg 4.三台被管理机…

轻松捕捉屏幕精彩,2024年录屏神器大盘点

随着技术的不断进步和用户需求的日益多样化&#xff0c;市场上的录屏软件更是层出不穷&#xff0c;各有千秋。今天&#xff0c;就让我们一同盘点那些和win10录屏快捷键一样可以快捷控制的专业录屏软件&#xff0c;探索它们如何助力我们更加高效地捕捉屏幕上的每一个精彩瞬间。 …

数据结构-链表-第二天

结合leetcode学习c 链表比数组更易增加和删除数据&#xff0c;但访问速度更慢 定义 链表&#xff08;linked list&#xff09;是一种线性数据结构&#xff0c;其中的每个元素都是一个节点对象&#xff0c;各个节点通过“引用”相连接。 引用记录了下一个节点的内存地址&#…