C++基础——类与对象

1 概述

C++是面向对象的语言,面向对象语言三大特性:封装、继承、多态。
C++将万事万物抽象为对象,对象上有其属性和行为。

2 封装

2.1 封装的意义

封装是面向对象的三大特性之一,封装将属性和行为作为一个整体,对属性和行为加以权限控制。
创建类语法:

class 类名 {
访问权限:属性;行为;
};

示例:

class Student {
private:string m_name;int m_age;
public:Student(string name, int age) {m_name = name;m_age = age;}string getName() {return m_name;}
};

上面除了定义属性和行为,也定义了权限访问控制符
C++包含三种访问控制符:

  • public 可被类中函数、子类函数、友元函数和类对象访问
  • protected 可被类中函数、子类函数、友元函数访问
  • private 可被类中函数和友元函数访问

2.2 struct和class区别

二者的区别在于默认的访问权限不同,struct默认访问权限为public,class默认访问权限为private

#include <iostream>
using namespace std;class C1 {int m_A;
};struct S1 {int m_A;
};int main() {C1 c1;//c1.m_A; 报错,无法访问S1 s1;s1.m_A = 10;return 0;
}

2.3 成员属性设置为私有

通常,将成员属性设置为私有,并提供设置和获取的方法。
这样的好处是可以控制成员属性的访问权限,对于写权限,能够检测数据的有效性。

class Test {
public://num1提供读写接口void setNum1(int num1) {m_num1 = num1;}int getNum1() {return m_num1;}//num2只提供读接口int getNum2() {return m_num2;}//num3只提供写接口,并判定数据范围void setNum3(int num3) {if (num3 > 0) {m_num3 = num3;} else {m_num3 = 0;}}private:int m_num1;int m_num2;int m_num3;
};

封装的思想通常会将属性设置为私有,暴露必要的修改行为给外部。

3 对象创建和清理

3.1 构造函数和析构函数

一个类具有最基础的两个函数是构造函数和析构函数,即使程序员未添加这两个函数,编译器也会生成一个默认的版本。
其中构造函数用于为类创建一个对象,并进行一些初始化的工作。
析构函数与构造函数相反,是为了清理一个对象,并释放资源。
构造函数和析构函数都是由编译器自动调用。

class Test {
private:int m_num;
public:Test();Test(int num) {m_num = num;}~Test();
};

构造函数可以重载,而析构函数只能有一个。构造函数可以通过传入不同的参数来重载,而析构函数没有参数。
当未定义构造函数和析构函数时,编译器会自动生成以下两个实现:

Test(){}
~Test(){}

默认会生成两个空实现。
而如果提供了有参构造函数,则编译器不会提供默认的空参构造函数,如果有此使用场景,则需要自己再提供一个空参构造函数。

3.2 构造函数分类及调用方式

构造函数有两种分类方式:
按参数分为:有参构造和无参构造
按类型分类:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐藏转换法

#include <iostream>using namespace std;class Person {
public://无参构造函数Person() {cout << "无参构造函数" << endl;}//有参构造函数Person(int age) {cout << "有参构造函数" << endl;m_age = age;}//拷贝构造函数Person(const Person &person) {cout << "拷贝构造函数" << endl;m_age = person.m_age;}~Person() {cout << "析构函数" << endl;}public:int m_age;
};void test1() {//调用无参构造函数Person p1;//显示调用无参构造函数Person p2 = Person();//调用拷贝构造函数Person P3(p1);
}void test2() {//括号法Person p1(10);//括号不能用于调用无参构造函数,这样定义会被认为是一个函数声明//Person p();//显示法Person p2 = Person(10);Person p3 = Person(p2);//创建匿名对象,无法使用,直接析构Person();Person(10);//隐式转换法,隐式调用有参构造函数和拷贝构造函数Person p4 = 10;Person p5 = p4;//不能利用拷贝构造初始化匿名对象,会被认为是对象声明//Person p6(p4);
}int main() {test1();test2();
}

输出:

无参构造函数
无参构造函数
拷贝构造函数
析构函数
析构函数
析构函数
有参构造函数
有参构造函数
拷贝构造函数
无参构造函数
析构函数
有参构造函数
析构函数
有参构造函数
拷贝构造函数
析构函数
析构函数
析构函数
析构函数
析构函数

比较需要注意的是隐藏转换法

3.3 拷贝构造函数的调用时机

调用拷贝构造函数有三种情况:

  • 使用一个已有的对象去初始化另一个对象
  • 值传递的方式给对象类型参数传值
  • 以值的方式返回局部对象
#include <iostream>using namespace std;class Person {
public://无参构造函数Person() {cout << "无参构造函数" << endl;}//有参构造函数Person(int age) {cout << "有参构造函数" << endl;m_age = age;}//拷贝构造函数Person(const Person &person) {cout << "拷贝构造函数" << endl;m_age = person.m_age;}~Person() {cout << "析构函数" << endl;}public:int m_age;
};void test1() {//调用有参构造函数Person p1(10);//括号法调用拷贝构造函数Person p2(p1);//隐式转换法调用拷贝构造函数Person p3 = p1;//显示调用拷贝构造函数Person p4 = Person(p1);//赋值操作,不会调用拷贝构造函数Person p5;p5 = p1;
}void doSomething1(Person p){}
void test2() {Person p;//对象赋值给形参,调用拷贝构造函数doSomething1(p);
}Person doSomething2() {Person p;return p;
}
void test3() {Person p = doSomething2();
}int main() {test1();test2();test3();
}

输出:

有参构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
无参构造函数
析构函数
析构函数
析构函数
析构函数
析构函数
无参构造函数
拷贝构造函数
析构函数
析构函数
无参构造函数
析构函数

最后返回局部对象与预想中有差异是因为编译器优化,感兴趣可以搜索RVO了解。
由于返回值和值传递传对象都会导致对象复制,对象大小会随着属性的增加而增加,所以一般对象作为参数的时候,都是使用引用或者指针进行传参或返回。

3.4 构造函数调用规则

默认情况下,C++编译器会至少给一个类添加3个函数:

  • 默认构造函数(无参构造函数,实现为空)
  • 默认析构函数(空实现析构函数)
  • 默认拷贝构造函数(对定义的属性进行拷贝)

构造函数生成规则如下:

  • 如果用户定义有参构造函数,则编译器不会提供默认无参构造函数,但还是会提供默认拷贝构造函数
  • 如果用户定义拷贝构造函数,则编译器不会提供默认无参构造函数

1、使用编译器默认函数

class Person {
public:int m_age;
};int main() {Person p1;p1.m_age = 10;Person p2 = p1;cout << "p2.m_age = " << p2.m_age << endl;
}

这里调用了默认无参构造和拷贝构造函数。析构函数由于没有实现所以无法体现。

2、提供有参构造的情况

class Person {
public://无参构造函数//Person() {//    cout << "无参构造函数" << endl;//}//有参构造函数Person(int age) {cout << "有参构造函数" << endl;m_age = age;}//拷贝构造函数Person(const Person &person) {cout << "拷贝构造函数" << endl;m_age = person.m_age;}~Person() {cout << "析构函数" << endl;}public:int m_age;
};

在这里插入图片描述
没有匹配的构造函数调用,编译器不提供默认构造函数。
3、提供拷贝构造函数

class Person {
public://拷贝构造函数Person(const Person &person) {cout << "拷贝构造函数" << endl;m_age = person.m_age;}~Person() {cout << "析构函数" << endl;}public:int m_age;
};

在这里插入图片描述
提供了拷贝构造函数后,不会提供默认无参构造函数。

3.5 深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝,指向同一个内存区域
深拷贝:在堆中重新申请空间,进行拷贝

class Person {
public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int age ,int height) {cout << "有参构造函数!" << endl;m_age = age;m_height = new int(height);}//拷贝构造函数  Person(const Person& p) {cout << "拷贝构造函数!" << endl;//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题m_age = p.m_age;m_height = new int(*p.m_height);}//析构函数~Person() {cout << "析构函数!" << endl;if (m_height != NULL){delete m_height;}}
public:int m_age;int* m_height;
};void test01()
{Person p1(18, 180);Person p2(p1);cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}int main() {test01();return 0;
}

由于类中的属性是在堆上分配的,如果使用浅拷贝,会导致两个对象的m_height都指向了同一个堆内存区域,而当一个对象析构之后,会释放该内存区域,而另一个对象还在使用该内存区域,会导致不确定的结果。而第二个对象析构时,则会导致重复释放堆区,会导致程序终止。所以如果有堆上分配内存的属性,需要使用深拷贝,重新申请堆内存区域进行赋值。
示例如下:

void test1() {Person p1(18, 180);{Person p2(p1);cout << "p2.height = " << *p2.m_height << endl;}cout << "p1.height = " << *p1.m_height << endl;
}int main() {test1();
}

输出:

有参构造函数
p2.height = 180
析构函数
p1.height = 887973408
析构函数Process finished with exit code -1073740940 (0xC0000374)

上面是注释了深拷贝的结果,p2正常访问,然后p2被析构,此时p1的m_height指向的内存实际已经释放,所以访问该内存会导致不确定的结果,然后p1会重复释放m_height指向的内存,导致程序出错终止。

3.6 初始化列表

C++提供了一种初始化列表的语法来初始化属性

class Person {
public:传统方式初始化//Person(int a, int b, int c) {//    m_A = a;//    m_B = b;//    m_C = c;//}//初始化列表方式初始化Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}void PrintPerson() {cout << "mA:" << m_A << endl;cout << "mB:" << m_B << endl;cout << "mC:" << m_C << endl;}
private:int m_A;int m_B;int m_C;
};int main() {Person p(1, 2, 3);p.PrintPerson();return 0;
}

初始化列表的语法可以省略原始构造函数中的一些模版代码。

3.7 类对象作为类成员

class Phone
{
public:Phone(string name){m_PhoneName = name;cout << "Phone构造" << endl;}~Phone(){cout << "Phone析构" << endl;}string m_PhoneName;
};class Person
{
public://初始化列表可以告诉编译器调用哪一个构造函数Person(string name, string pName) :m_Name(name), m_Phone(pName){cout << "Person构造" << endl;}~Person(){cout << "Person析构" << endl;}void playGame(){cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;}string m_Name;Phone m_Phone;};
void test01()
{//当类中成员是其他类对象时,我们称该成员为 对象成员//构造的顺序是 :先调用对象成员的构造,再调用本类构造//析构顺序与构造相反Person p("张三" , "苹果X");p.playGame();}int main() {test01();
}

先调用对象成员的构造函数,然后调用本类的构造函数。析构的时候正好与构造的时候顺序相反。

3.8 静态成员

静态成员就是在成员变量或者成员函数前加上static关键字

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
      1、静态成员变量
class Person
{public:static int m_A; //静态成员变量
private:static int m_B; //静态成员变量也是有访问权限的
};
//类外初始化
int Person::m_A = 10;
int Person::m_B = 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl;Person p2;p2.m_A = 200;cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据cout << "p2.m_A = " << p2.m_A << endl;//2、通过类名cout << "m_A = " << Person::m_A << endl;//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}int main() {test01();
}

静态成员变量也有访问权限,与非静态成员变量的访问权限一样。静态成员变量可以通过对象和类名访问。

class Person
{
public:static void func(){cout << "func调用" << endl;m_A = 100;//m_B = 100; //错误,不可以访问非静态成员变量}static int m_A; //静态成员变量int m_B; // 
private://静态成员函数也是有访问权限的static void func2(){cout << "func2调用" << endl;}
};
int Person::m_A = 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.func();//2、通过类名Person::func();//Person::func2(); //私有权限访问不到
}int main() {test01();
}

静态成员函数只能访问静态成员变量,也只能直接调用静态成员函数,可以在其中创建对象来调用非静态成员函数,因为非静态成员函数属于对象。静态成员函数也可以通过对象和类名两种方式调用。

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

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

相关文章

设计模式——建造者模式

目录 建造者模式盖房项目需求基本介绍四个角色实例代码注意事项和细节抽象工厂模式 VS 建造者模式 建造者模式 盖房项目需求 传统方式&#xff1a;打地基&#xff0c;砌墙&#xff0c;封顶 盖房子步骤 public abstract class AbstractHouse {// 地基public abstract void b…

运行java命令出现 Error: Invalid or corrupt jarfile XXX.jar

朋友 我当你一秒朋友 朋友 我当你一世朋友 奇怪 过去再不堪回首 怀缅 时时其实还有 运行java命令出现 Error: Invalid or corrupt jarfile XXX.jar 基本可以断定&#xff0c;是jar不完整导致的。不完整&#xff01;&#xff01;&#xff01;记住关键字 检查1&#xff1a; …

解决 SSLError: HTTPSConnectionPool(host=‘huggingface.co‘, port=443)

看我的回答&#xff1a; https://github.com/huggingface/transformers/issues/17611#issuecomment-1794486960 能问这个问题的都是网络不太好的&#xff0c;你懂的&#xff0c;所以答案全是解决网络的。 得益于这个回答&#xff1a;#17611 (comment) 看了一下代码&#xf…

时序预测 | MATLAB实现WOA-CNN-BiLSTM-Attention时间序列预测(SE注意力机制)

时序预测 | MATLAB实现WOA-CNN-BiLSTM-Attention时间序列预测&#xff08;SE注意力机制&#xff09; 目录 时序预测 | MATLAB实现WOA-CNN-BiLSTM-Attention时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本描述 1.MAT…

虚幻引擎 5.1 中全新的增强型输入操作系统

教程链接 https://www.youtube.com/watch?vCYiHNbAIp4s 前提 虚幻引擎5.1之后&#xff0c;项目设置里的input选项&#xff0c;默认会有一条警告&#xff0c;告知旧的input系统已经不能用了。 做法 在content文件夹下新建一个input按钮 input文件夹里面分成两部分内容 1.…

【JavaEE】实现简单博客系统-前端部分

文件目录&#xff1a; 展示&#xff1a; blog_list.html: <!DOCTYPE html> <html lang"cn"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><t…

轻量封装WebGPU渲染系统示例<21>- 3D呈现元胞自动机之生命游戏(源码)

实现原理: 基本PBR光照与gpu compute计算 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifeSpherePBR.ts当前示例运行效果: 其他效果截图: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如…

ZZ038 物联网应用与服务赛题第J套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 &#xff08;J卷&#xff09; 赛位号&#xff1a;______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等&#xff1b; 2.竞赛任务中所使用…

掌握未来技术趋势:深度学习与量子计算的融合

掌握未来技术趋势&#xff1a;深度学习与量子计算的融合 摘要&#xff1a;本博客将探讨深度学习与量子计算融合的未来趋势&#xff0c;分析这两大技术领域结合带来的潜力和挑战。通过具体案例和技术细节&#xff0c;我们将一睹这两大技术在人工智能、药物研发和金融科技等领域…

Linux安装nodejs问题

安装nodejs后&#xff0c;使用node -v报下图 参考下面两个可解决&#xff1a;【Linux-编译器gcc/glibc升级】CentOS7.9使用NodeJS18时报错/lib64/libm.so.6: version GLIBC_2.27‘ not found-CSDN博客 报错信息ImportError: /lib64/libstdc.so.6: version CXXABI_1.3.9‘ not f…

挑战100天 AI In LeetCode Day04(热题+面试经典150题)

挑战100天 AI In LeetCode Day04&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-62.1 题目2.2 题解 三、面试经典 150 题-63.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&am…

DDD技术方案落地实践 | 京东云技术团队

1. 引言 从接触领域驱动设计的初学阶段&#xff0c;到实现一个旧系统改造到DDD模型&#xff0c;再到按DDD规范落地的3个的项目。对于领域驱动模型设计研发&#xff0c;从开始的各种疑惑到吸收各种先进的理念&#xff0c;目前在技术实施这一块已经基本比较成熟。在既往经验中总…

输电线路AR可视化巡检降低作业风险

随着现代工业的快速发展&#xff0c;各行业的一线技术工人要处理的问题越来越复杂&#xff0c;一些工作中棘手的问题迫切需要远端专家的协同处理。但远端专家赶来现场往往面临着专家差旅成本高、设备停机损失大、专业支持滞后、突发故障无法立即解决等痛点。传统的远程协助似乎…

MySQL的基本建表及操作

MySQL的基本建表及操作 文章目录 MySQL的基本建表及操作一、表的结构二、建库和建表1.建库2.建表 三、复制表四、查表和删表 一、表的结构 数据库表的结构由表名、列名、列数据类型、列约束和索引等元素组成。具体来说&#xff0c;下面是数据库表的结构元素的详细解释&#xf…

宏观角度认识递归之 Pow(x,n) 问题

50. Pow(x, n) - 力扣&#xff08;LeetCode&#xff09; 计算 x 的 n 次幂&#xff0c;如果是直接暴力求解的话&#xff0c;会造成计算时间周期过长&#xff0c;所以要从别的角度出发&#xff0c;将幂等数分为两个幂等数相乘&#xff0c;例如&#xff1a;三的八次方&#xff0c…

合肥工业大学数字逻辑实验三

** 数字逻辑 实验报告** ✅作者简介:CSDN内容合伙人、信息安全专业在校大学生🏆 🔥系列专栏 :hfut实验课设 📃新人博主 :欢迎点赞收藏关注,会回访! 💬舞台再大,你不上台,永远是个观众。平台再好,你不参与,永远是局外人。能力再大,你不行动,只能看别人成功!…

生成m3u8视频:批量剪辑与分割的完美结合

在视频处理领域&#xff0c;m3u8视频格式的出现为高效处理和优化视频内容提供了新的可能。尤其在批量剪辑和分割视频的过程中&#xff0c;掌握m3u8视频的生成技巧&#xff0c;意味着更高效的工作流程和更出色的创作效果。现在一起来看看云炫AI智剪如何生成m3u8视频的操作吧。 步…

验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &#xff1b;否则&#…

闪客网盘系统源码,已测试对接腾讯COS及本地和支付(支持限速+按时收费+文件分享+可对接易支付)- 修复版

正文概述 资源入口 支持对文件下载限速 对接易支付 推广赚钱啥的功能 源码非常的好 支持腾讯cos 阿里云cos 本地储存 远程存储 源码仅支持服务器搭建 php7.2 伪静态thinkphp 运行目录public 导入数据库 修改config目录下的database.php数据库信息 后台地址&#xff1a; 域名/ad…

Maven中的继承与聚合

一&#xff0c;继承 前面我们将项目拆分成各个小模块&#xff0c;但是每个小模块中有很多相同的依赖于是我们创建一个父工程将模块中相同的依赖定义在父工程中&#xff0c;然后子工程继承父工程Maven作用&#xff1a;简化依赖配置&#xff0c;统一依赖管理,可以实现多重继承像J…