C++:类与对象补充 - 初始化列表、static成员、友元、匿名对象

news2024/12/23 14:05:57

目录

引言

一、 初始化列表

1.1 构造函数内部赋值

1.2 使用初始化列表

1.3 注意事项

1.4 explicit关键字

二、 static成员

2.1 概念

2.2 情景

2.3 特性

三、 友元

3.1 概念

3.2 语法

3.2.1 友元函数

3.2.2 友元类

3.3 特性

四、匿名对象

4.1 概念

4.2 语法

4.3 示例

4.4 用途


 

引言

在C++编程中,类与对象是重要的概念,但是有一些高级特性需要更深入的了解。本篇博客将介绍四个主题:初始化列表static成员友元匿名对象。这些特性可以让我们更加灵活地设计和使用类与对象,提高代码的效率和可维护性。

一、 初始化列表

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 使用初始化列表

初始化列表是C++中用于在构造函数中初始化类成员变量的一种机制。它允许在构造函数的参数列表之后,通过冒号(:)来显式地对成员变量进行初始化,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。这种方式在对象创建时仅执行一次,相比在构造函数体内使用赋值语句初始化成员变量,更加高效和推荐

初始化列表的语法如下:

ClassName::ClassName(parameters) : member1(value1), member2(value2), ..., memberN(valueN) {
    // 构造函数体
}

其中,ClassName 是类的名称,parameters 是构造函数的参数列表,而 member1, member2, ..., memberN 是类的成员变量,value1, value2, ..., valueN 是成员变量对应的初始值。

1.3 注意事项

 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。

② 对于const或引用类型的成员变量,初始化列表是唯一的初始化方式

因为在构造函数内部是进行赋值,而此处的两种对象只能进行初始化,之后不能再进行修改,因此只能使用初始化列表。

class MyClass {
private:
    int x;
    const int y;
    double& z;
public:
    // 初始化列表在这里进行成员变量的初始化
    MyClass(int a, int b, double& c) : x(a), y(b), z(c) {
        // 构造函数体
    }
};

int main() {
    int num = 42;
    double val = 3.14;
    MyClass obj(10, num, val);
    // obj.x 被初始化为 10
    // obj.y 被初始化为 num (42)
    // obj.z 被初始化为 val (3.14)
    return 0;
}

③ 自定义类型成员(且该类没有默认构造函数时)也必须使用初始化列表。

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

private:
    int _a;
};

class B {
public:
    B(int a, int ref)
            : _aobj(a) {}

private:
    A _aObj;  // 没有默认构造函数
}
    

若此时不使用初始化列表,实例化B类对象时将无法创建,因为在实例化时调用B构造函数,A类成员变量没有合适的默认构造函数,只有在初始化列表中显示使用有参构造函数才行。

④成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

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();
}

A. 输出1  1   B.程序崩溃   C.编译不通过   D.输出1  随机值

答案是D。

解析:

我们看到初始化列表是先对成员变量_a1先进行初始化,再初始化_a2,但是实际上初始化成员变量的顺序只与在类中的声明顺序有关!!!我们先声明的_a2,因此在初始化列表中,先对_a2进行初始化,但是此时_a1的值是随机值,因为类对其中内置类型是默认不做处理的,所以其值为随机值并初始化_a2,其次在使用形参a初始化_a1。由此我们能得到答案D。

⑤ 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。

上面这段话的意思是,当我们实例化一个类对象时,不管我们是否使用初始化列表对成员变量进行赋值,编译器都会先使用初始化列表进行初始化。那么问题来了,我不调用它都会自动使用,那么会造成什么结果呢?自动使用的结果是:内置成员变量的值为随机值不做处理,类类型的成员变量使用默认构造函数初始化。

当我们在函数体内操作时,只能是对其进行赋值操作,而不是初始化,如果要使用类类型的有参构造函数等,一定得使用初始化列表!!!还有上面提到的必须使用初始化列表的情况。

还有就是初始化列表初始化与函数体内赋值可以混合进行,不过要注意的是,不管成员变量有没有显示使用,都会经过初始化列表。

总之建议最好使用初始化列表对变量进行初始化!!!

1.4 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。

来看一段代码:

class MyClass {
public:
    MyClass(int x) : _x(x) {}

private:
    int _x;
};

int main() {
    MyClass obj1 = 42; // 隐式类型转换
    MyClass obj2(42);  // 使用显式构造函数进行对象的创建

    return 0;
}

是不是对obj1的实例化有有点疑惑?

一个int类型对象怎么能够和类类型对象对等呢??这和我们的认知不太一样啊!

我来介绍一下此处进行的隐式类型转换:

一般隐式类型转换原理上都会产生临时变量,此处先使用构造函数初始化一个匿名临时变量MyClass(42)(现在读者仅需了解此处有匿名对象即可),再使用默认生成的拷贝构造函数初始化obj1,便是我们看到的代码,不过这种方式效率比较低,因此现代大多是编译器会对过程进行优化,只调用一次构造函数直接实例化obj1对象,将42传给形参x实例化。

我们来对其进行验证:

 这是上述代码的运行结果,和上述解释的一致。

还有一种有参构造函数也能算此种情况,那就是除第一个参数无默认值其余均有默认值的构造函数。

 此段代码的原理和上述情况是相同的。

由此我们引入explicit关键字

explicit 是C++中的一个关键字,用于修饰单参数构造函数,它的作用是防止编译器进行隐式类型转换。通常情况下,当我们在构造函数中只有一个参数时,编译器会自动进行类型转换,将该参数类型转换为类的对象,从而创建临时对象。使用 explicit 关键字可以阻止这种隐式的自动类型转换。

使用 explicit 关键字可以避免一些意外的类型转换,增强了代码的可读性和安全性。它通常用于那些希望明确声明只接受显式构造函数调用的类。

二、 static成员

2.1 概念

static 是C++中的一个关键字,用于定义静态成员变量和静态成员函数。静态成员与类本身相关联,而不是与类的对象相关联。这意味着静态成员在类的所有对象之间共享,而不是每个对象都有自己的副本。静态成员在整个类的生命周期中存在,直到程序结束

2.2 情景

问题:实现一个类,并计算程序中创建出了多少个类对象

思路:我们可以定义全局变量并在构造函数内对其进行递增!

弊端:程序其他地方可能会对此全局变量进行修改!

那么让我们看一看正确思路(读者先见一见使用方法即可):

#include <iostream>

class Counter {
public:
    Counter() {
        count++; // 每次创建对象时递增计数
    }

    static int getCount() {
        return count;
    }

private:
    static int count; // 静态成员变量用于计数
};

int Counter::count = 0; // 静态成员变量初始化

int main() {
    Counter obj1;
    Counter obj2;
    Counter obj3;

    std::cout << "Number of objects created: " << Counter::getCount() << std::endl; // 输出:Number of objects created: 3

    return 0;
}

在上面的示例中,我们创建了一个名为 Counter 的类,它具有一个静态成员变量 count 和一个构造函数。每当对象被创建时,构造函数会自动调用,并在其中递增 count。这样,每次创建对象时,就会自动计算已创建的对象数量。

main 函数中,我们创建了三个 Counter 对象,然后通过 Counter::getCount() 静态成员函数获取已创建的对象数量,并输出结果。输出结果为 3,表示程序中一共创建了三个 Counter 对象。

我们在类中声明了一个静态成员变量count并在类外进行初始化,实例中仅有默认构造函数,在实例化对象时构造函数中的count变量++,静态变量是属于类的,而不是类对象,静态成员在整个类的生命周期中存在,直到程序结束。其中getCount函数为静态成员函数,通过调用来获取类声明对象的个数。

解释一下,静态成员变量和函数均属于类,不属于对象,静态成员函数中无this指针,他们均可通过类名::变量/函数调用,也可以通过类对象调用。

2.3 特性

  1. 所有对象共享:静态成员变量在类的所有对象之间共享,对于所有对象来说,它们都指向同一个内存位置,存放在静态区。

  2. 类内声明,类外初始化:静态成员变量必须在类的内部进行声明,但是在类外部进行初始化,通常在类外初始化静态成员变量。

  3. 可以通过类名访问:由于静态成员与类相关联,而不是与对象相关联,因此可以通过类名来访问静态成员,而不需要创建对象

  4. 不占用对象空间:由于静态成员是与类相关联的,而不是与对象相关联的,因此它不占用对象的内存空间

  5. 静态成员函数:静态成员函数是一个与类关联而不是与对象关联的函数。它只能访问类的静态成员变量和其他静态成员函数,而不能访问普通成员变量和非静态成员函数,因为它没有this指针。静态成员函数可以通过类名直接调用,无需创建对象。

  6. 静态成员的访问权限:静态成员变量和静态成员函数具有与类的访问权限相同的访问级别。如果一个成员函数是私有的,那么只有类的成员函数可以访问它,即使是静态成员函数,不能通过类名调用。

  7. 静态成员和动态内存分配:静态成员变量不占用对象的内存空间,因此它不会影响对象的大小。但是,如果静态成员变量是指向动态分配内存的指针,那么这个指针指向的内存块是位于堆上的,因此需要手动释放,否则可能导致内存泄漏。

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

  9. 静态成员函数不可以调用非静态成员函数,因为其中并没this指针,非静态成员函数可以调用类的静态成员函数。

三、 友元

3.1 概念

友元(friend)是C++中的一个特性,它允许一个类将其他类或非成员函数声明为自己的友元,从而允许这些友元访问该类的私有成员和保护成员,友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

3.2 语法

友元声明语法是在类的内部进行的,通过在类中声明其他类或函数为友元来建立友元关系。友元声明使用 friend 关键字。

class ClassName {
public:
    // 成员函数和成员变量的声明

    friend ReturnType FriendFunctionName(Parameters); // 友元函数的声明

    friend class FriendClassName; // 友元类的声明
};

通过两个示例来详细解释友元的使用

3.2.1 友元函数

#include <iostream>

class A {
public:
    A(int value) : _a(value) {}

    // 友元函数声明
    friend void showAValue(const A &objA);

private:
    int _a;
};

// 友元函数定义,访问类A的私有成员
void showAValue(const A &objA) {
    std::cout << "A's value: " << objA._a << std::endl;
}

int main() {
    A objA(10);

    // 调用友元函数,访问类A的私有成员
    showAValue(objA); // 输出:A's value: 10
    return 0;
}

在上述示例中,我们定义了类A。在类A中声明了一个友元函数 showAValue,允许该函数访问类A的私有成员 _a

main 函数中,我们创建了类A的对象 objA ,然后调用 showAValue 函数访问类A的私有成员。

3.2.2 友元类

#include <iostream>

class A; // 前向声明类A,用于在类B中声明友元类

class B {
public:
    B(int value) : _b(value) {}

    void showAValue(const A& objA); // 类A的引用作为参数

private:
    int _b;
};

class A {
public:
    A(int value) : _a(value) {}
    friend class B; // 声明B为A的友元类
    
private:
    int _a;
};

void B::showAValue(const A& objA) {
    std::cout << "A's value: " << objA._a << std::endl; // 在B的成员函数中访问A的私有成员
}

int main() {
    A objA(10);
    B objB(20);

    objB.showAValue(objA); // 输出:A's value: 10

    return 0;
}

在上述示例中,我们定义了两个类:类A和类B。在类B中声明了一个友元函数 showAValue,它的参数是类A的引用。在类A中将类B声明为友元类,这样 showAValue 函数可以访问类A的私有成员 _a

main 函数中,我们创建了类A的对象 objA 和类B的对象 objB,然后调用 showAValue 函数,输出了类A的私有成员 _a 的值。由于 showAValue 函数是类B的成员函数,并且在类A中将类B声明为友元类,因此它可以访问类A的私有成员。

3.3 特性

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

解释一下第二点

友元函数不能使用 const 修饰符。在C++中,友元函数是独立于类的非成员函数,它虽然可以访问声明为友元的类的私有成员和保护成员,但不能被声明为 const 成员函数。

const 成员函数是指在类中被声明为 const 的成员函数,它表示该成员函数不会修改类的成员变量(mutable 成员变量)。这样的成员函数可以在 const 类对象上调用,以保证对象的状态不会被修改。

因为友元函数不属于类的成员函数,所以它没有 constvolatile 修饰符。友元函数不受限于类对象的 const 语义,因此它可以修改类的私有成员和保护成员,不受 const 限制。

四、匿名对象

4.1 概念

匿名对象是在C++中创建的一个没有命名的临时对象,它不被赋予任何变量名,只能在创建的表达式中使用一次,之后就会立即销毁。

匿名对象通常用于简化代码和临时的计算,它们不需要被命名,因为它们只在创建它们的表达式中存在,并且在表达式结束后就会被销毁,从而节省了内存和资源。

4.2 语法

匿名对象的语法非常简单,它是在创建对象时省略对象的命名,直接使用临时对象。匿名对象只能在创建的表达式中使用一次,之后就会立即被销毁。

ClassType(); // 创建匿名对象

在上面的语法中,ClassType 是要创建的类的名称,后面的括号表示调用类的构造函数来创建对象。

4.3 示例

#include <iostream>

class Point {
public:
    Point(int x, int y) : _x(x), _y(y) {}

    int getX() const { return _x; }
    int getY() const { return _y; }

private:
    int _x;
    int _y;
};

void displayPoint(const Point& p) {
    std::cout << "Point: (" << p.getX() << ", " << p.getY() << ")" << std::endl;
}

int main() {
    // 创建匿名对象并直接传递给函数
    displayPoint(Point(2, 3));

    // 作为函数返回值
    Point p1(1, 1);
    Point p2(2, 2);
    Point sum = Point(p1.getX() + p2.getX(), p1.getY() + p2.getY());
    displayPoint(sum);

    // 临时计算
    int result = Point(4, 5).getX() + Point(6, 7).getY();
    std::cout << "Result: " << result << std::endl;

    return 0;
}

在上述示例中,我们定义了一个 Point 类,表示二维坐标点。然后在 main 函数中,我们使用匿名对象在不同的情况下进行了演示:

  1. displayPoint 函数中,直接创建并传递匿名对象,用于显示点的坐标。
  2. 在计算两个点的和时,使用匿名对象作为函数返回值,而无需定义额外的变量。
  3. 在临时计算中,直接使用匿名对象的成员函数进行运算。

通过匿名对象,我们可以简化代码并避免定义不必要的临时变量,从而提高代码的简洁性和可读性。但要注意,由于匿名对象的生命周期仅限于其所在的表达式,不应在其生命周期之外使用它。

4.4 用途

  1. 作为函数返回值:某些情况下,函数可以返回一个临时的匿名对象,而无需定义一个变量来接收返回值。
  2. 作为函数参数:可以直接将匿名对象作为函数的参数传递,而不需要在调用函数前定义一个变量来存储对象。
  3. 临时计算:在某些表达式中,可以直接使用匿名对象进行计算,而不必显示地命名和定义变量。

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

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

相关文章

Soundpad解决自动键失效的问题

这里给出解决方法&#xff0c;具体原因我也不太懂&#xff0c;因为我也是做实验得出某些操作可能会导致自动键不起作用。 首先打开首选项&#xff0c;配置如下图所示&#xff0c;这里只改了特殊热键的五个键位和自动键 我之前犯的错误&#xff0c;我相信大部分跟我一样&#…

74、75、76——tomcat项目实战

tomcat项目实战 tomcat 依赖 java运行环境,必须要有jre , 选择 jdk1.8 JvmPertest 千万不能用 kyj易捷支付 项目机器 选择 一台机器 ,安装jdk1.8的机器下载tomcat的包 上传到机器,解压tomcattomcat文件 bin文件夹: 启动文件 堆栈配置文件 catalina.sh JAVA_OPTS="-Xm…

vue3+ts使用antv/x6

使用 2.x 版本 x6.antv 新官网: 安装 npm install antv/x6 //"antv/x6": "^2.1.6",项目结构 1、初始化画布 index.vue <template><div id"container"></div> </template><script setup langts> import { onM…

DTW(Dynamic Time Warping)动态时间规整

转载于知乎DTW(Dynamic Time Warping)动态时间规整 - 知乎 DTW可以计算两个时间序列的相似度&#xff0c;尤其适用于不同长度、不同节奏的时间序列&#xff08;比如不同的人读同一个词的音频序列&#xff09;。DTW将自动warping扭曲 时间序列&#xff08;即在时间轴上进行局部的…

关于大功率H桥电机驱动模块

关于大功率H桥电机驱动模块 简介接线说明模块接线说明PWM调速控制说明 材料准备实际接线图测试视频总结 简介 大功率H桥电机驱动模块是由两个半桥驱动IC外加4个外部NMOS管组成&#xff0c;发热量小&#xff0c;刹车效果好。 两路PWM输入&#xff0c;占空比可在0-99%内调节。工…

2023/08/10

文章目录 一、计算属性传参二、小程序、h5跳转其他平台授权三、封装popup弹窗四、实现保存海报五、下载图片和复制分享链接 一、计算属性传参 计算属性的值往往通过一个回调函数返回&#xff0c;但是这个回调函数是无法传递参数的&#xff0c;要想实现计算属性传参可以通过闭包…

Python爬虫(十)_正则表达式

什么是正则表达式 正则表达式&#xff0c;又称规则表达式&#xff0c;通常被用来检索、替换那些符合某个模式&#xff08;规则&#xff09;的文本。 正则表达式是对字符串操作的一种逻辑公式&#xff0c;就是用事先定义好的一些特定字符、及这些特定字符的组合&#xff0c;组成…

Prometheus技术文档-基本使用-配置文件全解!!!!!

简介&#xff1a; Prometheus是一个开源的系统监控和告警系统&#xff0c;由Google的BorgMon监控系统发展而来。它主要用于监控和度量各种时间序列数据&#xff0c;比如系统性能、网络延迟、应用程序错误等。Prometheus通过采集监控数据并存储在时间序列数据库中&#xff0c;…

eNSP:双向重定向和路由策略练习

实验要求&#xff1a; 拓扑图&#xff1a; IP、路由器 r1: <Huawei>sys [Huawei]sys r1 [r1]int g 0/0/0 [r1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [r1-GigabitEthernet0/0/0]int g 0/0/1 [r1-GigabitEthernet0/0/1]ip add 14.1.1.1 24 [r1-GigabitEthernet0/0/1]…

Linux下安装nginx (tar解压版安装)

Linux下安装nginx (tar安装) 1、下载nginx 官方下载地址https://nginx.org/en/download.html 在这里插入图片描述 2.解压 解压‘nginx-1.16.1.tar.gz’到指定目录&#xff08;/usr/local/myWorkSpace&#xff09;并且重命名 命令&#xff1a; tar -xvf nginx-1.16.1.tar.gz …

畜牧虚拟仿真 | 鱼授精过程VR模拟演练系统

随着科技的发展&#xff0c;虚拟现实(VR)技术逐渐渗透到各个领域&#xff0c;为人们提供了更加真实、直观的体验。在动物养殖教育领域&#xff0c;鱼授精过程VR模拟演练系统正成为一种新的教学手段&#xff0c;它能够帮助人们更好地理解和掌握鱼授精的操作技巧&#xff0c;从而…

C# OpenCvSharp读取rtsp流录制mp4

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using Syste…

【雕爷学编程】Arduino动手做(12)---霍尔磁场传感器模块2

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

Linux: network: tools: tcpdump,抓取vlan包需要注意的事情;不然会出现LLC协议

https://bugzilla.redhat.com/show_bug.cgi?id498981#c4 https://serverfault.com/questions/544651/vlan-tags-not-shown-in-packet-capture-linux-via-tcpdump 如果不加-e参数&#xff0c;抓取不到 vlan信息&#xff0c;会导致wireshark解析出现问题。因为&#xff0c;抓到…

调整项目符号/项目编号与文本的距离

百度知道多年前的答案是调整标尺&#xff0c;我的PPT里没有标尺 调节悬挂缩进即可

STM32HAL库:简化STM32微控制器开发

简介&#xff1a;在微控制器开发领域&#xff0c;效率、易用性和兼容性至关重要。STMicroelectronics通过其STM32HAL库为这些问题提供了解决方案&#xff0c;该库是专门为STM32微控制器系列设计的软件开发库。本文旨在探索STM32HAL库的特性、优势和应用程序&#xff0c;并提供使…

【看表情包学Linux】初识文件描述符 | 虚拟文件系统 (VFS) 初探 | 系统传递标记位 | O_TRUNC | O_APPEND

爆笑教程《看表情包学Linux》&#x1f448; 猛戳订阅&#xff01;​​​​​ &#x1f4ad; 写在前面&#xff1a;通过上一章节的讲解&#xff0c;想必大家已对文件系统基本的接口有一个简单的了解&#xff0c;本章我们将继续深入讲解&#xff0c;继续学习系统传递标志位&…

跨境商城服务平台搭建与开发(金融服务+税务管理)

随着全球电子商务的快速发展&#xff0c;跨境贸易已经成为一种新的商业趋势。在这个背景下&#xff0c;搭建一个跨境商城服务平台&#xff0c;提供金融服务、税务管理等一系列服务&#xff0c;可以极大地促进跨境贸易的发展。本文将详细阐述跨境商城服务平台搭建与开发的步骤。…

在单元测试中使用Jest模拟VS Code extension API

对VS Code extension进行单元测试时通常会遇到一个问题&#xff0c;代码中所使用的VS Code编辑器的功能都依赖于vscode库&#xff0c;但是我们在单元测试中并没有添加对vscode库的依赖&#xff0c;所以导致运行单元测试时出错。由于vscode库是作为第三方依赖被引入到我们的VS C…

SpringBoot集成Redis及Redis使用方法

目录 应用背景 Redis简介 更新问题 一&#xff1a;环境配置 1.1: 在pom.xml文件中添加依赖 1.2&#xff1a;配置SpringBoot核心配置文件application.properties 二&#xff1a;在Config文件夹中创建RedisConfig配置文件类 2.1&#xff1a;RedisTemplate中的几个角色&am…