【C++】static修饰的“静态成员函数“--静态成员在哪定义?静态成员函数的作用?

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用

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

 

一、静态成员变量

1)特性

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

2)使用场景

现在我们有一个要求,需要统计现存对象以及累计创建对象的个数,所以我们依赖一个在没有实例存在的时候仍能存在的变量,而且该变量要和该类强相关,此时我们便可以利用到静态变量的特性,创建两个静态变量在public中(公共区中),并在类的外面定义他们,初始值为零,在每次调用构造函数或者拷贝构造函数时都使n加一,m也加一,每次调用析构函数时,m就减一,如此,n的数值就是累计创建的个数,m的值就是现存对象的个数,它们和类直接关联,而且在所有实例被清除后它们仍能存在着去记录数据。

总结:静态成员变量的作用就是突破类域

3)缺点:可能会在意外调用后被修改

但是这样使用静态变量也有一定的弊端,当有使用者(非用户,而是使用该类的其他人)在改变m和n的值时,将会影响我们的判断。

解决办法就是将这种变量全都放在private中,这样只有我们自己能使用它,它的适用范围变为了类的内部,我们称他为“类全局”。当然,如果有友元函数声明或者友元类声明也能调用他们。

4)注意

定义在类中的全局变量不走初始化列表

空指针、匿名对象都能访问他,因为他放在整个类中(限制于公有的前提)

 

二、静态成员函数

1)特性

1.静态成员一大特征就是无this指针,因此!!它没办法调用非静态的成员函数,毕竟非静态成员函数里面可都是有this指针的,所以一般和静态成员变量配套使用;

2.它可直接被调用而无需创建对应的"实例",类似这样:

YourClass:: YourStaticFunction();

3.它只能访问静态变量,这里的静态变量指同一类中的静态变量。如果是子类或者友元类的静态成员函数也可以访问该类中的静态成员变量。

 

三、实例观察现象

一下是作者编写的用于观察现象的小实例:

#include<iostream>
using namespace std;
//我们创建一个用于演示的"A"类
class A
{
public:A();//此处采用定义和声明分离的方法A(const A& other);~A();static void Print();//声明给静态,定义不用加staticA& operator= (const A aa);
private:static int _Creat;//静态成员变量_Creat声明static int _NumNow;//静态成员变量_NumNowint _num;
};//现在是在类外
//直接使用类名加冒号加变量名的方式直接给该类变量定义
//而且可以在这里个静态成员变量赋初识值
int A::_Creat = 0;
int A::_NumNow = 0;//声明一个非静态成员函数f2
A f2(void);
void func1(void);//构造函数
A::A():_num(0)
{cout << "A( )" << endl;//在构造函数中我们可以调用该类中的静态成员变量//这样我们就能统计存在和累计创建实例的个数了_Creat++;_NumNow++;
}//拷贝构造函数
A::A(const A& other)
{_num = other._num;cout << "A(const A& other)" << endl;
}//析构函数
A::~A()
{cout << "~A( )" << endl;_NumNow--;
}//输出两个变量的函数
void  A::Print()
{cout << "现存数量:" << _NumNow << " , " << "累计创建:" << _Creat << endl;
}//赋值运算符重载
A& A::operator= (const A aa)
{_num = aa._num;return *this;
}using namespace std;//命名空间展开
//定义一个非静态成员函数f2
A f2(void)
{A a;        //生成一个实例后传值返回return a;
}
//定义一个非静态成员函数f1用于演示
void func1(void)
{cout << "/********生成一个实例********/" << endl;A aa0;         //生成一个实例A::Print();    //输出变量cout << "/******创建一个匿名对象******/" << endl;A();           //创建一个匿名对象A::Print();    //输出变量cout << "/**************************/" << endl;cout << " 将f2返回值传值返回给实例temp1" << endl;A temp1 = f2();  //将f2返回值传值返回给实例temp1A::Print();    //输出变量cout << "/****先创建后使用运算符赋值**/" << endl;A temp2;temp2 = f2();A::Print();    //输出变量cout << "/****f2返回值赋给匿名对象****/" << endl;A() = f2();    //f2返回值赋给匿名对象cout << "/**************************/" << endl;
}//主函数
int main(void)
{func1();//函数结束后再观察一次变量std::cout << "func1 done" << endl;A::Print();return 0;
}

通过观察显现我们还可以看到编译器存在将多次构造再拷贝构造直接优化为一次构造的情况,例如第三组的输出情况。


四、遇到的问题:

遇到了现存数出现负数的问题:

赋值运算符重载返回值不是传引用返回导致多次调用析构的问题,在刚开始的现象中出现了现存数量为-1的情况,原因是析构函数多调用了一次,刚开始认为是赋值给匿名对象的原因,于是注释掉后重试发现想象仍然存在,有两组涉及到赋值运算符的组,编译器优化掉赋值的一组没有问题,最后发现问题在没有优化的有这组里,那么就可以确定是赋值运算符重载的问题了

然后发现是赋值运算符重载采用了传值返回,将其改为传引用返回后可以正常运行,我们平成使用赋值运算符重载也要使用传引用返回,这样可以减少不必要的拷贝,并且传引用返回可以链式赋值,而传值返回则不行,

然后尝试性得给赋值运算符重载的返回值从传值返回改为传引用返回,然后就得到了正确的结果

void func1(void)
{cout << "/******************/" << endl;A() = f2(); A::Print();    //输出变量cout << "/******************/" << endl;
}

 那么为什么赋值运算符重载传值返回会出现多次析构的问题呢?

经过在代码中添加输出,得到了以下结果:

赋值运算符重载调用了拷贝构造,如果是传引用返回则不会调用这一次拷贝构造,

所以我猜测是传值返回本质是将一个临时的自定义类型对象的值拷贝给另外一个对象的过程,但是不知道为什么这个过程中创建临时变量的构造没有触发,或者理解为没有显示调用但是析构却显示调用了,导致计数出现问题,应该是编译器部分优化的问题。

如果说怎样规避这样的问题的话那就是尽可能减少这类隐式的转化。


以下是问题代码:

#include<iostream>
using namespace std;
//我们创建一个用于演示的"A"类
class A
{
public:A();//此处采用定义和声明分离的方法A(const A& other);~A();static void Print();//声明给静态,定义不用加staticA operator= (const A aa);
private:static int _Creat;//静态成员变量_Creat声明static int _NumNow;//静态成员变量_NumNowint _num;
};//现在是在类外
//直接使用类名加冒号加变量名的方式直接给该类变量定义
//而且可以在这里个静态成员变量赋初识值
int A::_Creat = 0;
int A::_NumNow = 0;//声明一个非静态成员函数f2
A f2(void);
void func1(void);//构造函数
A::A():_num(0)
{cout << "A( )" << endl;//在构造函数中我们可以调用该类中的静态成员变量//这样我们就能统计存在和累计创建实例的个数了_Creat++;_NumNow++;
}//拷贝构造函数
A::A(const A& other)
{_num = other._num;cout << "A(const A& other)" << endl;
}//析构函数
A::~A()
{cout << "~A( )" << endl;_NumNow--;
}//输出两个变量的函数
void  A::Print()
{cout << "现存数量:" << _NumNow << " , " << "累计创建:" << _Creat << endl;
}//赋值运算符重载
A A::operator= (const A aa)
{_num = aa._num;return *this;
}using namespace std;//命名空间展开
//定义一个非静态成员函数f2
A f2(void)
{A a;        //生成一个实例后传值返回return a;
}
//定义一个非静态成员函数f1用于演示
void func1(void)
{cout << "/******************/" << endl;A aa0;         //生成一个实例A::Print();    //输出变量cout << "/******************/" << endl;A();           //创建一个匿名对象A::Print();    //输出变量cout << "/******************/" << endl;A temp1 = f2();  //将f2返回值给实例temp1A::Print();    //输出变量cout << "/******************/" << endl;//A() = f2();    //f2返回值赋给匿名对象//这个行为很危险,具体逻辑可能需要看到汇编层才能判断//如果这样赋值会出现多次析构的情况//我们尝试另一个例子和上一组形成对照A temp2;temp2 = f2();A::Print();    //输出变量cout << "/******************/" << endl;
}//主函数
int main(void)
{func1();//函数结束后再观察一次变量std::cout << "func1 done" << endl;A::Print();return 0;
}

多次析构情况如下:

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

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

相关文章

Springboot捕获全局异常:MethodArgumentNotValidException

1.控制器 方法上添加Valid注解 PostMapping("/update")RequiresPermissions("user:update")public R update(RequestBody Valid UserEntity user) {userService.update(user);return R.ok();}2.实体类 public class UserEntity implements Serializable …

C#面向对象,封装、继承、多态、委托与事件实例

一&#xff0e;面向对象封装性编程 创建一个控制台应用程序&#xff0c;要求&#xff1a; 1&#xff0e;定义一个服装类&#xff08;Cloth&#xff09;&#xff0c;具体要求如下 &#xff08;1&#xff09;包含3个字段&#xff1a;服装品牌&#xff08;mark&#xff09;,服装…

【springboot】读取外部的配置文件

【springboot】读取外部的配置文件 一、使用场景二、代码实现&#xff08;一&#xff09;application.yml 的配置&#xff08;二&#xff09;编辑 customer.yml&#xff08;三&#xff09;自定义方法读取外部配置文件&#xff08;四&#xff09;使用外部配置文件的配置 一、使用…

解锁 Vue 项目中 TSX 配置与应用简单攻略

在 Vue 项目中配置 TSX 写法 在 Vue 项目中使用 TSX 可以为我们带来更灵活、高效的开发体验&#xff0c;特别是在处理复杂组件逻辑和动态渲染时。以下是详细的配置步骤&#xff1a; 一、安装相关依赖 首先&#xff0c;我们需要在命令行中输入以下命令来安装 vitejs/plugin-v…

游戏引擎学习第22天

移除 DllMain() 并成功重新编译 以下是对内容的详细复述与总结&#xff1a; 问题和解决方案&#xff1a; 在编译过程中遇到了一些问题&#xff0c;特别是如何告知编译器不要退出程序&#xff0c;而是继续处理。问题的根源在于编译过程中传递给链接器的参数设置不正确。原本尝试…

【C#设计模式(15)——命令模式(Command Pattern)】

前言 命令模式的关键通过将请求封装成一个对象&#xff0c;使命令的发送者和接收者解耦。这种方式能更方便地添加新的命令&#xff0c;如执行命令的排队、延迟、撤销和重做等操作。 代码 #region 基础的命令模式 //命令&#xff08;抽象类&#xff09; public abstract class …

QT6学习第四天 感受QT的文件编译

QT6学习第四天 感受QT的文件编译 使用纯代码编写程序新建工程 使用其他编辑器纯代码编写程序并在命令行运行使用 .ui 表单文件生成界面使用自定义 C 窗口类使用现成的QT Designer界面类 使用纯代码编写程序 我们知道QT Creator中可以用拖拽的方式在 .ui 文件上布局&#xff0c…

【SpringBoot】28 API接口防刷(Redis + 拦截器)

Gitee仓库 https://gitee.com/Lin_DH/system 介绍 常用的 API 安全措施包括&#xff1a;防火墙、验证码、鉴权、IP限制、数据加密、限流、监控、网关等&#xff0c;以确保接口的安全性。 常见措施 1&#xff09;防火墙 防火墙是网络安全中最基本的安全设备之一&#xff0c…

4——单页面应用程序,vue-cli脚手架

单页面应用程序(英文名:Single Page Application)简称 SPA,顾名 思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。 1、脚手架 ① 什么是脚手架 vue-cli 是 Vue.js 开发的标准工具&#xff61;它简化了程序员基于 webpack …

小程序 - 个人简历

为了让招聘人员快速地认识自己&#xff0c;可以做一个“个人简历”微信小程序&#xff0c; 展示自己的个人信息。 下面将对“个人简历”微信小程序进行详细讲解。 目录 个人简历 创建图片目录 页面开发 index.wxml index.wxss 功能实现截图 总结 个人简历 创建图片目录…

BUUCTF—Reverse—helloword(6)

一道安卓逆向的签到题 下载附件 使用JADX-gui反编译工具打开&#xff08;注意配环境&#xff09;&#xff0c;找到主函数 jadx 本身就是一个开源项目&#xff0c;源代码已经在 Github 上开源了 官方地址&#xff1a;GitHub - skylot/jadx: Dex to Java decompiler 发现flag …

单点登录深入详解之设计方案总结

基于cookie的单点登录解决方案 概述 用户登录之后 , 将认证信息存储至 Cookie &#xff0c;当再次访问本服务或者访问其他应用服务时&#xff0c;直接从 Cookie 中传递认证信息&#xff0c;进行鉴权处理。 问题 1. 如何保障Cookie内用户认证信息的安全性? 第一, Cookie…

JSONArray 与Object 之间的转换

PageResult<JSONArray> pageResult new PageResult<>();// 查出来的数据 JSONArray resultArray new JSONArray(); ject data new JSONObject();data.put("code", code); resultArray.add(data);// 将resultArray数据放入JSONArray,不是再包装成一个 …

cangjie (仓颉) vscode环境搭建

sdk下载 下载中心-仓颉编程语言官网 可选择半年更新版&#xff0c;不用申请。目前版本&#xff1a;0.53.13 &#xff0c;选择不同平台压缩包下载解压到任意位置即可 补充下载&#xff0c;vscode插件解压后&#xff0c;在vscode扩展中选择从vsix安装&#xff0c;安装后新增名为…

SmartSQL:一款方便、快捷的数据库文档查询、导出工具

&#x1f6a9; 项目介绍 SmartSQL 是一款方便、快捷的数据库文档查询、导出工具&#xff01;从最初仅支持SqlServer数据库、CHM文档格式开始&#xff0c;通过不断地探索开发、集思广益和不断改进&#xff0c;又陆续支持Word、Excel、PDF、Html、Xml、Json、MarkDown等文档格式…

IT监控 | Oracle云监控全解析

Oracle云(Oracle Cloud)是Oracle公司提供的云服务平台&#xff0c;涵盖了IaaS、PaaS、SaaS和DaaS&#xff0c;支持企业在云中构建、部署、集成和扩展应用&#xff0c;为企业提供了管理服务器、应用程序、存储、网络和数据中心的全面控制能力。 跟踪Oracle云基础设施的关键组件将…

攻防世界-web ics-06 [解法思路]

进入环境 点击左边的列表只有报表中心有反应 注意看url直接就是index.php?id1 我先试了sqlmap不行&#xff0c;然后就沉淀了一下 想到了id后面的参数问题&#xff0c;我谁便改了几个数都没反应 就想着用bp抓包爆一下这个参数&#xff0c;用了一个数字10000的字典 发现2333…

zotero安卓测试版下载和使用

2023年年底&#xff0c;Zotero官方就已经推出了安卓版的测试版Zotero for Android (beta),&#xff0c;但名额有限且只能通过Google商店下载。此外&#xff0c;还有一些第三方开发的安卓应用&#xff0c;如Zoo for Zotero、ZotDroid等。 在首次使用Zotero安卓版时&#xff0c;用…

洛谷 P1722 矩阵 II C语言 记忆化搜索

题目&#xff1a; https://www.luogu.com.cn/problem/P1722 我们按照案例画一下 我们会发现&#xff0c;会出现重复的子结构。 代码如下&#xff1a; #include<iostream> using namespace std; int mem[300][300]; int n; int f[305][305]; int dfs(int x,int red,…

MCU(一) 时钟详解 —— 以 GD32E103 时钟树结构为例

微控制器 (MCU) 的时钟系统是系统运行的核心&#xff0c;它提供了各模块所需的时钟信号。本文以 GD32E103 系列 MCU 为例&#xff0c;详细讲解其 时钟树结构&#xff08;Clock Tree&#xff09;。通过理解时钟源、分配与预分频器设置&#xff0c;可以灵活配置系统时钟以实现高性…