C++学习——静态成员变量、静态成员函数

以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。

一、静态成员变量详解

1、被static修饰

1不同的对象占用不同的内存,这使得不同对象的成员变量相互独立,因此它们的值不受其他对象的影响。例如有两个相同类型的对象 a、b,它们都有一个成员变量 m_name,那么修改 a.m_name 的值不会影响 b.m_name 的值。

但有时候我们希望在多个对象之间共享数据,对象 a 改变了某份数据后对象 b 可以检测到。比如计数,以 Student 类为例,如果我们想知道班级中共有多少名学生,就可以设置一份共享的变量,每次创建对象时让该变量加 1。

在C++中,我们可以使用静态成员变量来实现“多个对象共享某个数据”。

静态成员变量是一种特殊的成员变量,它被关键字static修饰,例如:

class Student{
public:Student(char *name, int age, float score);void show();
public:static int m_total;  //静态成员变量
private:char *m_name;int m_age;float m_score;
};

这段代码声明了一个静态成员变量 m_total,用来统计学生的人数。

2、要在类外初始化 

注意,static 成员变量属于类,不属于某个具体的对象。

因此上面例子即使创建多个对象,也只为 m_total 分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。

static 成员变量必须在类声明的外部初始化,初始化格式如下:

//变量的数据类型                                  
data_type class_name::variable_name = value;//类名      //变量名         //变量的值

比如将上面的 m_total 初始化: int Student::m_total = 0;(在初始化时不能再加 static,但要加数据类型)。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。

static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在类外部初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。

static 成员变量既可以通过对象来访问,也可以通过类来访问。请看下面的例子:

//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;

3、不占用对象的内存 

static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。

换言之,static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存。

#include <iostream>
using namespace std;class Student {
public:Student(char *name, int age, float score);void show();
private:static int m_total;  //静态成员变量
private:char *m_name;int m_age;float m_score;
};//初始化静态成员变量
int Student::m_total = 0;Student::Student(char *name, int age, float score) : m_name(name), m_age(age), m_score(score) {m_total++;  //操作静态成员变量
}
void Student::show() {cout << m_name << "的年龄是" << m_age << ",成绩是" << m_score << "(当前共有" << m_total << "名学生)" << endl;
}int main() {cout << sizeof(Student) << endl;//创建匿名对象(new Student("小明", 15, 90))->show();(new Student("李磊", 16, 80))->show();(new Student("张华", 16, 99))->show();(new Student("王康", 14, 60))->show();return 0;
}

运行结果是:

12
小明的年龄是15,成绩是90(当前共有1名学生)
李磊的年龄是16,成绩是80(当前共有2名学生)
张华的年龄是16,成绩是99(当前共有3名学生)
王康的年龄是14,成绩是60(当前共有4名学生)

从上面代码运行结果可知,静态成员变量m_total不占据对象的内存(否则输出结果应为16)。每次创建对象时,会调用构造函数使 m_total 的值加 1。

4、总结

(1)一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量。

(2)static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。

(3)静态成员变量必须初始化,而且只能在类体外进行。初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。因为全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。

(4)静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。

二、静态成员函数详解

在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。

1、与普通成员函数的区别

普通成员函数有 this 指针,可以访问类中的任意成员;静态成员函数没有 this 指针,只能访问静态成员。

编译器在编译一个普通成员函数时,会隐式地增加一个形参 this 指针,并把当前对象的地址赋值给 this 指针,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。

编译器在编译一个静态成员函数时,不会为它增加形参 this 指针,但是静态成员函数可以通过类来直接调用,所以不管有没有创建对象,我们都可以调用静态成员函数。

静态成员函数没有 this 指针,不知道指向哪个对象,因此无法访问对象的普通成员变量和普通成员函数,只能访问静态成员变量和调用静态成员函数。

在C++中,静态成员函数的主要目的是访问静态成员。

普通成员函数也可以访问静态成员,但由于静态成员函数只对静态成员进行操作,它似乎更专一?是因为这个缘故才有静态成员函数?有没有必须用到静态成员函数的情形呢?)

2、例子说明

下面是一个完整的例子,该例通过静态成员函数来获得学生的总人数和总成绩:

#include <iostream>
using namespace std;class Student{
public:Student(char *name, int age, float score);void show();
public:  //声明静态成员函数static int getTotal();static float getPoints();
private:static int m_total;  //总人数static float m_points;  //总成绩
private:char *m_name;int m_age;float m_score;
};int Student::m_total = 0;
float Student::m_points = 0.0;Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){m_total++;m_points += score;
}
void Student::show(){cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义静态成员函数
int Student::getTotal(){return m_total;
}
float Student::getPoints(){return m_points;
}int main(){(new Student("小明", 15, 90.6)) -> show();(new Student("李磊", 16, 80.5)) -> show();(new Student("张华", 16, 99.0)) -> show();(new Student("王康", 14, 60.8)) -> show();int total = Student::getTotal();float points = Student::getPoints();cout<<"当前共有"<<total<<"名学生,总成绩是"<<points<<",平均分是"<<points/total<<endl;return 0;
}
小明的年龄是15,成绩是90.6
李磊的年龄是16,成绩是80.5
张华的年龄是16,成绩是99
王康的年龄是14,成绩是60.8
当前共有4名学生,总成绩是330.9,平均分是82.725

(1)静态成员函数一般通过类来调用,也可以通过对象来调用,上例演示如何通过类来调用。 

(2)总人数 m_total 和总成绩 m_points 由各个对象累加得到,必须声明为 static 才能共享。

(3)函数getTotal()、getPoints() 分别用来获取总人数和总成绩,为了访问 static 成员变量,我们将这两个函数声明为 static。当然也可以将它们声明为普通成员函数,但由于它们都只对静态成员进行操作,加上 static 语义更加明确。

(4)和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。

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

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

相关文章

没用的知识增加了,尝试用文心实现褒义词贬义词快速分类

尝试用文心实现褒义词贬义词快速分类 一、我的需求二、项目环境搭建千帆SDK安装及使用流程 三、项目实现过程创建应用获取签名调用接口计算向量积总结 百度世界大会将于10月17日在北京首钢园举办&#xff0c;今天进入倒计时五天了。通过官方渠道的信息了解到&#xff0c;这次是…

Jmeter连接mysql数据库详细步骤

一、一般平常工作中使用jmeter 连接数据库的作用 主要包括&#xff1a; 1、本身对数据库进行测试&#xff08;功能、性能测试&#xff09;时会需要使用jmeter连接数据库 2、功能测试时&#xff0c;测试出来的结果需要和数据库中的数据进行对比是否正确一致。这时候可以通过j…

XML外部实体注入攻击XXE

xml是扩展性标记语言&#xff0c;来标记数据、定义数据类型&#xff0c;是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义&#xff08;可选&#xff09;、文档元素&#xff0c;一般无法直接打开&#xff0c;可以选择用excl或记事本打…

内存空间的分配与回收之连续分配管理方式

1.连续分配管理方式 连续分配:指为用户进程分配的必须是一个连续的内存空间。 1.单一连续分配 在单一连续分配方式中&#xff0c;内存被分为系统区和用户区。系统区通常位于内存的低地址部分&#xff0c;用于存放操作系统相关数据;用户区用于存放用户进程相关数据。内存中只…

BES耳机空间音频技术实现

BES耳机空间音频技术实现 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?加我微信hezkz17, 本群提供音频技术答疑服务 音响和耳机在空间音频技术上实现方式是不同的 虚拟现实可谓是空间音频技术最具代表性的应 用领域。虽然虚拟现实的起源可以追溯到1 9 6 8年, …

2023年淘宝天猫双11活动时间什么时候开始到几月几号结束?

2023年淘宝天猫双11超级红包领取时间 第一阶段&#xff1a;2023年10月24日20:00 至11月03日23:59 第二阶段&#xff1a;2023年11月04日00:00 至 11月11日23:59 2023年淘宝天猫双11超级红包使用时间 第一阶段&#xff1a;2023年10月31日20:00 至11月03日23:59 第二阶段&…

字符串左旋 与 字符串旋转结果

字符串左旋 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 方法1 三步翻转法 要求:abcdef 左旋两个 整体逆序:fedcba左边逆序:cdef ba右边逆序:cdef ab #include<stdio.h> #include<…

Xcode 14.3.1build 报错整理

1、Command PhaseScriptExecution failed with a nonzero exit code 2、In /Users/XX/XX/XX/fayuan-mediator-app-rn/ios/Pods/CocoaLibEvent/lib/libevent.a(buffer.o), building for iOS Simulator, but linking in object file built for iOS, file /Users/XX/XX/XX/fayuan…

微服务设计原则:构建弹性和可维护的应用

文章目录 1. 单一职责原则2. 独立性和自治性3. 弹性和容错性4. API 网关5. 日志和监控6. 版本管理7. 自动化部署和持续集成8. 安全性9. 数据一致性10. 文档和通信拓展思考结论 &#x1f389;欢迎来到架构设计专栏~微服务设计原则&#xff1a;构建弹性和可维护的应用 ☆* o(≧▽…

Qt打开ui文件经常报错

报错如下&#xff1a; 解决方法&#xff1a; 最后设置成默认值 即可

竞赛选题 深度学习 机器视觉 人脸识别系统 - opencv python

文章目录 0 前言1 机器学习-人脸识别过程人脸检测人脸对其人脸特征向量化人脸识别 2 深度学习-人脸识别过程人脸检测人脸识别Metric Larning 3 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 人脸识别系统 该项目…

高并发下的服务容错

在微服务架构中&#xff0c;我们将业务拆分成一个个的服务&#xff0c;服务与服务之间可以相互调用&#xff0c;但是由于网络 原因或者自身的原因&#xff0c;服务并不能保证服务的100%可用&#xff0c;如果单个服务出现问题&#xff0c;调用这个服务就会 出现网络延迟&#xf…

平面设计师之路 优漫动游

1、入门 平面设计师之路 首先将几个原则&#xff0c;不管你记得住记不住&#xff0c;这些方式和态度决定了你能够深入到平面设计的哪一步。 原则一&#xff1a;实践是掌握真理的唯一途径。这句话是衍生物&#xff0c;因为我觉得原来的那句话实验性质太浓厚了&#xff0c…

计算机网络传输层知识总结·

传输层提供的服务 传输层的功能 ●传输层提供进程之间的逻辑通信&#xff0c;即端到端的通信 ●复用和分用 ●差错检测&#xff08;首部和数据部分&#xff09; ●面向连接的TCP和无连接的UDP 端口的作用 ●端口标识的是主机中的进程 ●硬件端口是不同…

3D WEB轻量化引擎HOOPS:促进CAD软件的创新与协作

CAD软件一直以来都在现代工程、建筑、制造和设计领域发挥着至关重要的作用。在数字时代&#xff0c;CAD软件的开发者不断追求提高软件性能、增加功能和改善用户体验&#xff0c;在这一努力中&#xff0c;HOOPS技术&#xff08;高度优化的面向对象并行软件&#xff09;滑块露头角…

IDEA启动报错Failed to create JVM. JVM path的解决办法

今天启动IDEA时IDEA报错&#xff0c;提示如下。 if you already hava a JDK installed, define a JAVA_HOME variable in Computer > Systen Properties > System Settings > Environment Variables.Failed to create JVM. JVM path:D:\ideaIU2023.2.3\IntelliJ IDE…

使用c++视觉处理----canny 边缘检测、sobel边缘检测、scharr 滤波边缘检测

使用c视觉处理canny 边缘检测、sobel边缘检测、scharr 滤波边缘检测 #include <opencv2/opencv.hpp>int main() {// 读取图像cv::Mat image cv::imread("1.jpg", cv::IMREAD_GRAYSCALE); // 转为灰度图像if (image.empty()) {std::cerr << "无法加…

Spring Boot 中常用的注解@RequestParam

Spring Boot 中常用的注解RequestParam RequestParam 是 Spring Framework 和 Spring Boot 中常用的注解之一&#xff0c;用于从请求中获取参数值。它通常用于处理 HTTP 请求中的查询参数&#xff08;query parameters&#xff09;或表单数据。下面详细解释 RequestParam 的用…

混凝土搅拌站预拌厂数字孪生可视化管理系统,三维可视化数据监控平台

本项目基于三维建模、数据融合等技术&#xff0c;构建一套实时的混凝土搅拌站厂区数字孪生可视化系统&#xff0c;提升混凝土搅拌站厂区信息化建设水平。 通过三维可视化项目的建设&#xff0c;实现搅拌站厂区展示和漫游、生产流程中设备的实时映射孪生、关键设备参数及指标图…

GPU如何统计显存占用

GPU统计显存占用有两种方法&#xff1a; 方法1&#xff1a; 使用 watch -n 1 nvidia-smi 这个会2秒刷新一次&#xff0c;可以在一个终端运行程序另外一个终端执行这个然后看具体的显存变化【很费劲&#xff0c;而且可能一闪而过】 方法2&#xff1a; 要统计程序的最大显存…