【C++】类和对象(4)

目录

1. 类型转换

非explicit的单参数构造函数

示例 

explicit的单参数构造函数

示例

不同版本的行为 

示例 (单参数)

示例(多参数且其余参数有默认值 )

 示例(多参数且无默认值)

2. static成员变量

例题1

例题2 

3. 友元

4. 内部类

例题

5. 匿名对象

示例 


1. 类型转换

C++支持将内置类型隐式转换为类类型对象,需要非explicit的单参数构造函数或者多参数但其余参数有默认值的构造函数

非explicit的单参数构造函数

示例 

假设定义一个 Length  类来表示长度,单位是米,它有一个接受 int  类型参数的构造函数用于设置长度数值:

#include <iostream>
using namespace std;class Length {
public:// 接受int类型参数的构造函数Length(int meter) : length(meter){}void show() const {cout << "长度为: " << length << " 米" << endl;}
private:int length;
};int main() { // 复制初始化,将int类型的5转换为Length类型的对象lenLength len = 5; //想要创建一个表示长度为5米的length对象len len.show();return 0;
}

这里 int  类型的 5  可以通过 Length  类的构造函数隐式转换为 Length  类型的对象,使用 Length len = 5;  这种复制初始化的方式,代码看起来就像是把一个整数值赋给了 Length  类型的对象,比较符合常规的赋值思维习惯,简单易懂。相比之下,直接初始化 Length len(5);  虽然功能一样,但复制初始化在这种类型转换很自然的场景里,从写法上会给人一种更直观的感觉。

explicit的单参数构造函数

示例

如果构造函数被声明为explicit ,就不再支持隐式类型转换,仅允许直接初始化

#include <iostream>
using namespace std;class MyClass 
{
public:    explicit MyClass(int value) // explicit 修饰构造函数,禁止隐式转换: data(value) {}   
private:int data;
};int main() 
{MyClass obj(10);// 正确,显式调用构造函数   // MyClass obj = 20; 错误! 因为 MyClass 的构造函数是 explicit,禁止隐式转换return 0;
}

不同版本的行为 

  • C++98/03Length len = 5; 复制初始化的处理方式是:先使用5调用构造函数 Length(int meter) 生成一个临时的Length类型的对象,然后再通过拷贝构造函数将临时对象复制到 len。允许编译器优化省略。
  • C++11优化为直接构造。编译器必须省略临时对象和拷贝构造函数的调用,Length len = 5; 编译器会直接调用构造函数初始化 len,提高了程序效率。

示例 (单参数)

class MyClass
{
public:MyClass(int value) // 非 explicit,允许隐式转换: data(value){} // 无拷贝构造函数(编译器会默认生成)
private:int data;
};void func(MyClass obj)
{/* ... */
}int main()
{MyClass obj = 10;  // 隐式转换:int → MyClass//C++98/03行为:先调用构造函数生成临时的MyClass类型对象,再通过拷贝构造函数(若用户未定义,编译器默认生成一个公有的)将临时对象赋值给obj(这里临时对象生命周期仅用于初始化obj,拷贝完成后销毁)//C++11及以后行为:允许直接构造obj而不生成临时对象,不调用拷贝构造函数func(20);   // 隐式转换:int → MyClass//C++98/03行为:(两次开销)先生成临时的MyClass类型对象,再通过拷贝构造(若用户未定义,编译器默认生成一个公有的)函数将临时对象传递给func函数的形参obj//C++11及以后行为:(一次开销)允许直接在函数func的形参obj的存储位置(函数栈帧)构造MyClass对象,而不生成临时对象return 0;
}
class MyClass
{
public:MyClass(int value) // 非 explicit,允许隐式转换: data(value){}// 无拷贝构造函数(编译器会默认生成)
private:int data;
};void func(const MyClass& a)//参数为引用类型
{/* ... */
}int main()
{MyClass obj = 10;  func(20);  //不管是C++98/03还是C++11及以后,当参数是引用类型时,必须创建临时对象,然后绑定到引用上,临时对象生命周期延长至函数结束return 0;
}

示例(多参数且其余参数有默认值 )

#include <iostream>
using namespace std;
class A 
{
public:// 多参数但其余参数有默认值的构造函数A(int a, int b = 4) : value(a + b) {cout << value << endl;}private:int value;
};int main() 
{        A obj2 = 10;  //这里 10 是int类型,隐式转换为A类型,调用多参数(有默认值)构造函数return 0;
}

 示例(多参数且无默认值)

#include <iostream>
using namespace std;
class B
{
public:// 多参数且无默认值的构造函数B(int a, int b) : num(a + b) {cout << num << endl;}private:int num;
};int main() 
{// 不能隐式转换,显式调用构造函数进行类型转换B obj = B(3, 5);//理论上B(3, 5)复制初始化 先调用构造函数创建临时对象,再调用拷贝构造函数将临时对象赋值给obj,实际上进行了优化//不产生临时对象,也不调用拷贝构造函数,等价于直接初始化 B obj(3,5),二者在效果和性能上完全一致return 0;
}
#include <iostream>
using namespace std;
class Point 
{
public:Point(int x, int y) : m_x(x), m_y(y) {}void print() const {std::cout << "(" << m_x << ", " << m_y << ")";}private:int m_x;int m_y;
};void displayPoint(const Point& p)
{p.print();std::cout << std::endl;
}int main() 
{// 1.显式构造Point对象后传递displayPoint(Point(10, 20));//Point(10, 20)调用构造函数创建临时对象,这个临时对象直接绑定到函数的参数p上// 2.使用列表初始化语法,编译器隐式调用构造函数displayPoint({ 30, 40 });//编译器看到这种形式,知道接受一个Point类型引用参数,Point类有构造函数,此时编译器会隐式地使用{10,20}作为参数调用构造函数,创建临时对象//3.直接初始化然后再传Point p(5, 5);displayPoint(p);return 0;
}

2. static成员变量

  • 用static修饰的成员变量,称之为静态成员变量,静态成员变量必须在在类外进行初始化。
  • 所有对象共享同一份静态成员变量,它存储在全局数据区,而不是在每个对象的内存空间中。
  • 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态成员,因为没有this指针。
  • 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受public、protected、private访问限定符的限制。
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

示例: 

//实现一个类,计算程序中创建出了多少个类对象?
#include<iostream>
using namespace std;
class A
{
public:A(){++_scount;}A(const A& t){++_scount;}~A(){--_scount;}  //静态成员函数static int GetACount(){//_a++; error! 静态成员函数不能访问非静态成员,因为没有this指针return _scount;}void func(){cout << _scount << endl;cout << GetACount() << endl;}
private:int _a = 4;//类里面声明static int _scount;
};//类外⾯初始化
int A::_scount = 0;int main()
{cout << A::GetACount() << endl;A a1, a2;A a3(a1);cout << A::GetACount() << endl;cout << a1.GetACount() << endl;//cout << A::_scount << endl;  编译报错:error C2248 : “A::_scount” : ⽆法访问private成员(在“A”类中声明) 受访问限定符限制!return 0;
}

例题1

 链接[ 求1+2+3+……n]

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 a[n]; 变长数组Sum*p=new Sum[n];return Sum::Getret();}
};

例题2 

设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调用顺序为(C->A->B->D

设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数调用顺序为(B->A->D->C

C c;
int main()
{A a;B b;static D d;return 0;
}

解析:

变量c是全局对象,其构造函数最先调用;在main函数中先声明A a; 然后声明B b; 所以A的构造函数先于B调用;D d;是静态局部对象,其构造函数在main函数首次执行到该声明处调用,在A和B之后。所以顺序为:C->A->B->D

非静态局部对象a、b 析构时机是离开main函数作用域时,顺序为构造的反序(B->A);静态对象(全局c和静态局部d)析构时机是程序结束时,顺序为构造的反序(全局C先构造,静态局部D后构造,故析构顺序为D->C)。所以顺序为B->A->D->C

3. 友元

一般来说,类的私有成员外部是不能访问的,而友元函数在一些特殊场景下很有用,比如需要在类外部的函数中访问类的私有数据进行特定操作,又不想把这些数据设为公有成员破坏封装性,就可以将该函数声明为友元函数。

  • 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加 friend,并且把友元声明放到一个类的里面。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。
  • 友元函数不属于类的成员函数,没有 this  指针。调用时和普通函数一样,直接使用函数名调用。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
  • 一个函数可以是多个类的友元函数。
  • 友元提供了便利,但是友元会增加耦合度,破坏了类的封装性,所以友元不宜多用,使用时要谨慎。
#include<iostream>
using namespace std;class B; //前置声明,否则编译器在A类中编译时会因为不知道B是什么类型而报错,告诉编译器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)//func函数可以是多个类的友元函数
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}int main()
{A aa;B bb;func(aa, bb);return 0;
}

当一个类被声明为另一个类的友元类时,那么这个类的所有成员函数都可以访问另一个类的私有和保护成员,无需为每个成员函数单独声明friend关键字。 

#include<iostream>
using namespace std;class A
{//友元声明
friend class B;// B整体是A的友元类,友元类的声明是对整个类的授权,B类的所有成员函数都可访问A的私有成员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.func2(aa);return 0;
}

4. 内部类

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在 全局相比,它只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类内部类默认是外部类的友元类。

#include<iostream>
using namespace std;class A
{
private:static int _k;int _h = 1;
public:class B  //B默认就是A的友元{public:void foo(const A& a){cout << _k << endl;          cout << a._h << endl;        }private:int _b = 1;};
};int A::_k = 1; //初始化静态成员变量int main()
{cout << sizeof(A) << endl; //4 非静态成员_hA::B b;//B类的作用域在A类内部,在外部使用B类时需要指定其所属的外部类域A aa;b.foo(aa);return 0;
}

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

例题

 链接[ 求1+2+3+……n] 

class Solution 
{
private:static int _i;static int _ret;class Sum //内部类{public:Sum(){_ret+=_i;++_i;}         };public:int Sum_Solution(int n) {Sum a[n];//变长数组// Sum*p=new Sum[n];// delete []p;return _ret;}};int Solution:: _i=1;int Solution:: _ret=0;

5. 匿名对象

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

匿名对象通常是临时对象,它们在表达式结束后会被自动销毁

示例 

#include<iostream>
using namespace std;class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};class Solution
{
public:int Sum_Solution(int n) {//...return n;}
};int main()
{//有名对象//A aa1(); 不能这么定义对象,因为编译器无法识别这是是⼀个函数声明,还是对象定义  A aa1;cout << endl;A aa2(2);cout << endl;  //匿名对象,匿名对象的特点不⽤取名字,但是它的⽣命周期只有这一行,我们可以看到下一行他就会自动调用析构函数A();cout << endl;A(1);cout << endl;//匿名对象在这样场景下就很好⽤:/*Solution sl;cout << sl.Sum_Solution(10) << endl;*///为了更方便cout << Solution().Sum_Solution(10) << endl;return 0;
}

运行结果:

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

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

相关文章

苍穹外卖10

WebSocket WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通信----浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并进行双向数据传输。 HTTP协议和WebSocket协议对比&#xff1a; HTTP是短链接 WebSocke…

STM32的Flash映射双重机制

在STM32微控制器中&#xff0c;存在一个重要的内存映射特性&#xff1a;Flash存储器可以同时出现在两个不同的地址区域&#xff0c;而且可以通过重映射功能改变CPU启动时从哪个地址获取初始指令。 STM32的Flash映射双重机制 当描述"通常起始于地址0x00000000&#xff0c…

在 Spring Boot 中实现异常处理的全面指南

在现代 Web 应用开发中&#xff0c;异常处理是确保系统健壮性和用户体验的关键环节。Spring Boot 作为一个功能强大的 Java 框架&#xff0c;提供了灵活的异常处理机制&#xff0c;能够统一管理应用程序中的错误&#xff0c;提升代码可维护性和响应一致性。2025 年&#xff0c;…

学习记录:DAY19

Docker 部署与项目需求分析 前言 人总是本能地恐惧未知&#xff0c;令生活陷入到经验主义的循环之中。但我们终将面对。今天的目标是把 Docker 部署学完&#xff0c;然后对项目进行需求分析。 日程 下午 4:30&#xff1a;Docker 部署项目部分学完了&#xff0c;做下笔记。晚…

Jackson 使用方法详解

Jackson 是 Java 生态中最流行的 JSON 处理库&#xff0c;也是 Spring Boot 的默认 JSON 解析器。它提供了高性能的 JSON 序列化&#xff08;对象 → JSON&#xff09;和反序列化&#xff08;JSON → 对象&#xff09;功能。以下是 Jackson 的全面使用指南。 1. 基础依赖 Mave…

【网络入侵检测】基于源码分析Suricata的统计模块

【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。 1. 概要 👋 在 Suricata 的配置文件中,stats 节点用于配置统计信息相关的参数,它的主要作用是控制 Suricata 如何收集和输出统计数据,帮助用户了解 Suricata 的运行状态和…

回归预测 | Matlab实现DBO-LightGBM蜣螂算法优化轻量级梯度提升机多输入单输出回归预测,作者:机器学习之心

回归预测 | Matlab实现DBO-LightGBM蜣螂算法优化轻量级梯度提升机多输入单输出回归预测&#xff0c;作者&#xff1a;机器学习之心 目录 回归预测 | Matlab实现DBO-LightGBM蜣螂算法优化轻量级梯度提升机多输入单输出回归预测&#xff0c;作者&#xff1a;机器学习之心预测效果…

风力发电领域canopen转Profinet网关的应用

在风力发电领域&#xff0c;开疆canopen转Profinet网关KJ-PNG-205的应用案例通常涉及将风力涡轮机内部的CANopen网络与外部的Profinet工业以太网连接起来。这种转换网关允许风力发电场的控制系统通过Profinet协议收集和监控涡轮机的状态信息&#xff0c;同时发送控制命令。 风力…

因特网和万维网

本文来源 &#xff1a;腾讯元宝 因特网&#xff08;Internet&#xff09;和万维网&#xff08;World Wide Web&#xff0c;简称WWW&#xff09;是紧密相关但完全不同的两个概念&#xff0c;它们的核心区别如下&#xff1a; 本质不同​​ ​​因特网&#xff08;Internet&#…

Visual Studio 技能:调整软件界面布局

专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏&#xff0c;故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 &#xff08;一&#xff09;WIn32 专栏导航 上一篇&#xff1a;Windows编程&#xff1a;在VS2019里面&#xff0c;调整代码字体大…

LeetCode 热题 100_最小路径和(92_64_中等_C++)(多维动态规划)

LeetCode 热题 100_最小路径和&#xff08;92_64&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;多维动态规划&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;多维动态规划&…

Sql刷题日志(day6)

一、笔试 1、insert ignore&#xff1a;在插入数据时忽略主键冲突或其他唯一性约束冲突。 如果插入的记录会导致主键冲突&#xff08;如 actor_id 已存在&#xff09;&#xff0c;该语句不会报错&#xff0c;而是直接忽略插入操作 语法&#xff1a; INSERT IGNORE INTO tab…

Java多线程入门案例详解:继承Thread类实现线程

本文通过一个简单案例&#xff0c;讲解如何通过继承 Thread 类来实现多线程程序&#xff0c;并详细分析了代码结构与运行机制。 一、前言 在 Java 中&#xff0c;实现多线程主要有两种方式&#xff1a; 继承 Thread 类 实现 Runnable 接口 本文以继承 Thread 类为例&#x…

Netty在线客服系统落地方案

本文不讲然后代码方面的东西&#xff0c;只聊方案&#xff01;&#xff01; 这方案基于 Spring Boot 2.6、Netty、MyBatis Plus、Redis 构建的一套支持 单体应用 的在线客服系统。 系统支持客户自由与后台客服实时聊天、客服未在线钉钉提醒通知客服、消息已读未读标记、消息已…

SDK游戏盾、高防IP、高防CDN三者的区别与选型指南

在网络安全防护领域&#xff0c;SDK游戏盾、高防IP和高防CDN是常见的解决方案&#xff0c;但各自的功能定位、技术实现和适用场景差异显著。本文将通过对比核心差异&#xff0c;帮助您快速理解三者特点并选择适合的防护方案。 一、核心功能定位 SDK游戏盾 功能核心&#xff1a…

GRPO有什么缺点,如何改进?

一、GRPO的核心原理与设计目标 Group Relative Policy Optimization(GRPO)是DeepSeek团队提出的一种强化学习算法,旨在解决传统PPO(Proximal Policy Optimization)在大语言模型(LLM)训练中的资源消耗问题。其核心创新在于 通过组内相对奖励替代价值函数(Critic Model)…

登高架设作业指的是什么?有什么安全操作规程?

登高架设作业是指在高处从事脚手架、跨越架架设或拆除的作业。具体包括以下方面&#xff1a; 脚手架作业 搭建各类脚手架&#xff0c;如落地式脚手架、悬挑式脚手架、附着式升降脚手架等&#xff0c;为建筑施工、设备安装、高处维修等作业提供安全稳定的工作平台。对脚手架进行…

前端实现商品放大镜效果(Vue3完整实现)

前端实现商品放大镜效果&#xff08;Vue3完整实现&#xff09; 前言 在电商类项目中&#xff0c;商品图片的细节展示至关重要。放大镜效果能显著提升用户体验&#xff0c;允许用户在不跳转页面的情况下查看高清细节。本文将基于Vue3实现一个高性能的放大镜组件&#xff0c;完整…

【C++11特性】Lambda表达式(匿名函数)

一、函数对象 在C中&#xff0c;我们把所有能当作函数使用的对象当作函数对象。 一般来说&#xff0c;如果我们列出一个对象&#xff0c;而它的后面又跟有由花括号包裹的参数列表&#xff0c;就像fun(arg1, arg2, …)&#xff0c;这个对象就被称为函数对象。函数对象大致可分为…

大模型在肝硬化腹水风险预测及临床方案制定中的应用研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 1.3 研究方法与数据来源 二、肝硬化及大模型相关理论基础 2.1 肝硬化概述 2.2 大模型技术原理 2.3 大模型在医疗领域的应用现状 三、大模型预测肝硬化腹水术前风险 3.1 术前风险因素分析 3.2 大模型预测术前…