【五】【C++】类与对象(三)

const只读

在 C++ 中,const 关键字用于声明一个变量为常量,意味着一旦被初始化之后,它的值就不能被改变。

声明常量

使用 const 关键字可以声明变量为常量。这意味着这个变量的值不能被修改。

const int MAX_SIZE = 100;

指针与 const

const 可以与指针结合使用,用来声明指向常量的指针或者指针本身为常量。

指向常量的指针:不能通过这个指针来修改它所指向的值。

 
const int* ptr = &MAX_SIZE;

常量指针:指针本身的值(即存储的地址)不能被修改。

 
int value = 5;
int* const ptr = &value;

指向常量的常量指针:既不能修改指针存储的地址,也不能通过指针修改所指向的值。

 
const int* const ptr = &MAX_SIZE;

指针与const结合使用,主要看const后面紧跟着的是什么,如果紧跟着的是int* 数据类型,说明指针上的值不能改变,如果紧跟着的是指针名,说明指针本身不能改变,如果都存在,都无法改变。

const 成员函数:

在类中,如果一个成员函数不修改任何成员变量,那么这个函数可以被声明为 const

 
class MyClass {
public:int getValue() const {return value;}
private:int value;
};

const 与对象:

当一个对象被声明为 const,那么只能调用它的 const 成员函数。这是因为 const 成员函数保证不会修改对象的状态。

我们可以思考以下几个问题,

1. const对象可以调用非const成员函数吗?---const对象只能调用const成员函数

2. 非const对象可以调用const成员函数吗?---非const意味着可读可写,对于只读成员函数当然可以调用

3. const成员函数内可以调用其它的非const成员函数吗?---const成员函数不能修改成员变量,自然不能调用非const成员函数。

4. 非const成员函数内可以调用其它的const成员函数吗?---非const意味着可读可写,对于只读成员函数当然可以调用

初始化列表

在 C++ 中,初始化列表(Initializer List)是一个非常重要的特性,用于在构造函数中初始化类的成员变量。使用初始化列表可以提高代码的效率和可读性,并且在某些情况下是必要的。

基本语法

初始化列表紧随在构造函数的参数列表后面,由一个冒号 (:) 引入,后跟一个或多个用逗号分隔的初始化器。每个初始化器包括成员变量名称和用于初始化该成员的值或表达式。

 
/*初始化列表*/
#include <iostream>
using namespace std;
class Date {public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}void Show() {cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};
int main() {Date d1(2024, 1, 31);d1.Show();}

初始化列表相对于构造函数的优点

更高的效率

初始化列表直接初始化成员变量,而不是先默认构造它们然后再赋值。这在成员变量是对象时尤其重要,因为它避免了额外的构造和析构调用。

必要性

对于 const 成员和引用类型的成员变量,它们必须在初始化列表中初始化,因为这些类型的变量一旦被默认构造,就不能再被赋予新值。

初始化列表对于非基本数据类型的优势

对于类类型的成员变量,如果它们没有默认构造函数,或者有参数的构造函数更高效,使用初始化列表可以明确指定如何初始化这些成员。

对于const成员和引用类型的成员变量

 
/*构造函数的缺陷---难以初始化 const 和引用类型成员变量*/
#include <iostream>
using namespace std;
class A{
public:A(int& a){_ref= a;_n=10;}
private:int& _ref;const int _n;};

构造函数并不是严格意义上的初始化,上面代码尝试用构造函数初始化const和引用成员会报错,“[错误]在‘ int &’[-fpermissive ]中未初始化的引用成员”,原因是构造函数实际上是对已经初始化的成员变量进行赋值操作,也就是说成员变量已经被创建,但构造函数是对创建好的成员变量进行赋值操作。我们希望的初始化是在创建的过程中进行赋值操作。

对于const和引用成员,必须在创建的时候对其初始化,而构造函数是对创建好的对象进行赋值,因此只能使用初始化列表进行严格的初始化。

 
/*构造函数的缺陷---难以初始化 const 和引用类型成员变量*/
#include <iostream>
using namespace std;
class A {public:A(int& a):_ref(a),_n(10){}void Show() {cout << _ref << "---" << _n << endl;cout << &_ref << endl;}private:int& _ref;const int _n;};
int main() {int b = 100;A a(b);cout << &b << endl;a.Show();return 0;}

对于自定义类型成员变量

 
/*初始化列表对于非基本数据类型的优势*/
#include <iostream>
using namespace std;
class A {public:A(int a): _a(a){}void Show() {cout << "_a:" << _a << endl;}private:int _a;};
class B {public:B(int a, int ref): _aobj(a),_ref(ref),_n(10){}void Show() {_aobj.Show();cout << "_ref:" << _ref << endl << "_n:" << _n << endl;}private:A _aobj;int& _ref;const int _n;};
int main() {int c = 20;B b(100, c);b.Show();}

初始化列表的初始化顺序

成员变量的初始化顺序与它们在类中的声明顺序一致,而不是初始化列表中的顺序。这可以确保即使成员变量之间有依赖关系,它们也总是以一致的顺序被初始化。

 
/*初始化列表---初始化顺序*/
#include <iostream>
using namespace std;
class Date {public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}void Show() {cout << _year << "-" << _month << "-" << _day << endl;}private:int _day;int _month;int _year;};
int main() {Date d1(2024, 1, 31);d1.Show();}

explicit 关键字

在 C++ 中,explicit 关键字用于阻止类构造函数的隐式自动类型转换。它主要用在类的单参数构造函数上,或者在构造函数有默认参数时,构造函数虽然有多个参数,但可以仅通过一个实际参数调用。

在没有 explicit 关键字的情况下,C++ 允许单参数构造函数隐式地将一个值转换为其类类型。这种隐式转换有时可能导致意料之外的行为。

 
/*explicit关键字*/
#include <iostream>
using namespace std;
class Date {public:Date(int year): _year(year),_month(0),_day(0){}Date& operator=(const Date& d) {if (this != &d) {_year = d._year;_month = d._month;_day = d._day;}return *this;}void Show() {cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};
int main() {Date d1(2022);d1.Show();d1 = 2023;//Date d(2023)d1.Show();return 0;}

当我们将整型的2023传入d1时,发生隐式转换将整型2023转化为日期类,调用单参数构造函数,以整型2023为参数构造日期类作返回值。

此时在Date构造函数前面添加explicit关键字,阻止类构造函数的隐式自动类型转换。

 
/*explicit关键字*/
#include <iostream>
using namespace std;
class Date {public:explicit Date(int year): _year(year),_month(0),_day(0){}Date& operator=(const Date& d) {if (this != &d) {_year = d._year;_month = d._month;_day = d._day;}return *this;}void Show() {cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};
int main() {Date d1(2022);d1.Show();d1 = 2023;//Date d(2023)d1.Show();return 0;}

static成员

在 C++ 中,static 关键字用于定义类的静态成员,包括静态成员变量和静态成员函数。这些成员与类的特定实例无关,而是属于类本身。

静态成员变量

类的静态成员变量在所有实例之间共享。也就是说,不管创建了多少对象,静态成员变量只有一份副本。

静态成员变量可以通过类名直接访问,而不需要类的实例。

静态成员变量必须在类定义外进行初始化(通常在源文件中)。

静态成员函数

静态成员函数可以访问类的静态成员变量和其他静态成员函数,但不能直接访问类的非静态成员。(静态函数没有this指针)

静态成员函数可以通过类名直接访问,而不需要类的实例。

 
/*静态static*/
#include<iostream>
using namespace std;
class A{
public:static void add(){n++;}static int n;};
int A::n=0;int main(){cout<<A::n<<endl;A a;a.n++;A b;cout<<b.n<<endl;A::add();cout<<A::n<<endl;}

尝试在类内重载<<运算符

 
/*在类内 重载<<与>>运算符*/
#include <iostream>
using namespace std;
class Date {public:Date(int year, int month, int day): _year(year),_month(month),_day(day){}ostream& operator<<(ostream& _cout) {_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}private:int _year;int _month;int _day;};
int main() {Date d1(2024, 1, 31);//_cout << d1 << endl;d1 << cout << endl;}

我们在类内重载<<运算符,以至于我们使用<<时,左边必须是Date类的对象,右边才是第一个参数_cout。因为<<左边的Date对象需要作为this指针传入operator<<函数中,正常来说我们希望<<左边作为_cout参数右边作为this指针传入函数内,因此在类内不便于我们重载<<与>>运算符。

尝试在类外重载<<运算符

 
/*在类内 重载<<与>>运算符*/
#include <iostream>
using namespace std;
class Date {public:Date(int year, int month, int day): _year(year),_month(month),_day(day){}private:int _year;int _month;int _day;};ostream& operator<<(ostream& _cout, const Date& d) {_cout << d._year << "-" << d._month << "-" << d._day << endl;return _cout;}
int main() {Date d1(2024, 1, 31);cout << d1 << endl;}

在类外外面重载<<与>>运算符有一个严重的问题,无法在类外访问类内被private修饰的成员变量。

友元

在C++中,友元(Friend)是一种机制,它允许一个类或函数访问另一个类的私有成员。这意味着友元能够绕过C++中的封装性,使得外部类或函数可以直接访问另一个类的私有数据和成员函数。

类友元:

友元可以是一个类,这意味着一个类可以允许另一个类访问它的私有成员。

在一个类中声明另一个类为友元,可以在类的定义中使用 friend 关键字。

 
class ClassA {
private:int privateVarA;
public:ClassA() : privateVarA(0) {}friend class ClassB; // 声明ClassB为ClassA的友元
};class ClassB {
public:void AccessPrivateVar(ClassA& obj) {obj.privateVarA = 42; // ClassB可以访问ClassA的私有成员}
};

函数友元

友元也可以是一个函数,这意味着一个函数可以访问一个类的私有成员。

在一个类中声明一个函数为友元,可以在类的定义中使用 friend 关键字。

 
class MyClass {
private:int privateVar;
public:MyClass() : privateVar(0) {}friend void FriendFunction(MyClass& obj); // 声明FriendFunction为MyClass的友元函数
};void FriendFunction(MyClass& obj) {obj.privateVar = 42; // FriendFunction可以访问MyClass的私有成员
}

在类外重载<<运算符

 
/*在类内 重载<<与>>运算符*/
#include <iostream>
using namespace std;
class Date {
public:Date(int year, int month, int day): _year(year),_month(month),_day(day){}friend ostream& operator<<(ostream& _cout, const Date& d);private:int _year;int _month;int _day;};ostream& operator<<(ostream& _cout, const Date& d) {_cout << d._year << "-" << d._month << "-" << d._day << endl;return _cout;}
int main() {Date d1(2024, 1, 31);cout<<d1<<endl;}

在类内声明ostream& operator<<(ostream& _cout, const Date& d);这个函数是Date的友元函数。表示这个函数可以访问Date类内的私有成员。

内部类

在C++中,内部类(也称为嵌套类)是定义在另一个类中的类。这种结构允许组织代码的方式更加紧密和结构化,使得外部类和内部类之间的关系更加明确。内部类可以访问外部类的私有成员,这是因为C++标准规定内部类是外部类的友元。

定义内部类

内部类(嵌套类)是在另一个类的内部定义的类。这使得嵌套类能够访问外部类的所有成员(包括私有成员)。

 
class OuterClass {
public:// 外部类的成员class InnerClass {public:// 内部类的成员};
};

访问外部类成员

嵌套类可以直接访问外部类的成员,包括私有成员。因为C++标准规定内部类是外部类的友元。

 
class OuterClass {int value = 0; // 私有成员public:class InnerClass {public:void display(OuterClass& outer) {cout << outer.value << endl; // 直接访问外部类的私有成员}};
};

实例化内部类

要实例化嵌套类,您首先需要外部类的实例,然后使用该实例来实例化内部类。

如果内部类是公有的,也可以直接从外部类的外部实例化它。否则,你可能需要在外部类内部或通过外部类的公有方法来实例化内部类。

 
/*内部类*/
#include <iostream>
using namespace std;
class OuterClass {int value = 0; // 私有成员public:class InnerClass {public:void display(OuterClass& outer) {cout << outer.value << endl; // 直接访问外部类的私有成员}};};
int main() {OuterClass a;OuterClass::InnerClass b;b.display(a);}

匿名对象

在C++中,匿名对象是指没有名称的对象。它们通常在创建对象时立即使用,而不需要将其存储在变量中。匿名对象可以用于各种场景,比如作为函数参数、作为表达式的一部分或在任何只需要临时对象的地方。

匿名对象在使用后立即被销毁,这意味着它们的生命周期非常短。这使得它们在处理不需要长期存储的数据时非常有用。

作为函数参数

假设我们有一个函数display,它接受一个对象参数,并显示一些信息。我们可以在调用display时创建一个匿名对象:

 
#include <iostream>class MyClass {
public:void show() const {std::cout << "MyClass show function called" << std::endl;}
};void display(const MyClass& obj) {obj.show();
}int main() {// 使用匿名对象作为函数参数display(MyClass());return 0;
}

类方法链式调用

在某些情况下,你可能想要立即调用一个对象的方法,而不是先将其存储在变量中。可以创建一个匿名对象来实现这一点:

 
#include<iostream>
class MyClass {
public:void show() const {std::cout << "MyClass show function called" << std::endl;}
};int main() {// 创建一个匿名对象并立即调用它的方法MyClass().show();return 0;
}

短暂的生命周期

 
/*短暂的生命周期*/
#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;};
int main(){A();return 0;}

匿名对象的生命周期只有一行。

拷贝构造优化

1.构造函数构造出来的对象A,用来拷贝构造一个对象B,优化成,构造函数直接构造出来的对象B(直接用对象A)

2.函数值返回一个对象A,优化成直接返回A,A用来拷贝构造一个对象B,优化成,直接用对象A,A生命周期延长。

/*拷贝构造的优化*/
#include <iostream>
using namespace std;
class A {public:A(int a = 0): _a(a) {cout << "A(int a=0)" << endl;}A(const A& aa): _a(aa._a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this !=  &aa) {_a = aa._a;}return *this;}~A() {cout << "~A()" << endl;}private:int _a;};
void f1(A aa) {}
A f2() {A aa;return aa;}
int main() {A aa1;f1(aa1);cout << endl;f2();cout << endl;f1(1);//构造函数构造出来的对象A,用来拷贝构造一个对象B,优化成,构造函数直接构造出来的对象B(直接用对象A)f1(A(2));//构造函数构造出来对象A,用来拷贝构造一个对象B,优化成,构造函数直接构造出来的对象B(直接用对象A)cout << endl;A aa2 = f2();//函数返回一个对象A,用来拷贝构造一个对象B,优化成,直接用对象A,A生命周期延长cout << endl;aa1 = f2();//函数返回一个对象A,赋值重载,无法优化cout << endl;return 0;}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

Quartus生成烧录到FPGA板载Flash的jic文件

简要说明&#xff1a; Altera的FPGA芯片有两种基本分类&#xff0c;一类是纯FPGA&#xff0c;另一类是FPGASoc&#xff08;System on chip)&#xff0c;也就是FPGAHPS&#xff08;Hard Processor System&#xff0c;硬核处理器&#xff09;&#xff0c;对应两种Flash烧录方式&a…

【HTML】自定义属性(data)

自定义属性 data: 的用法&#xff08;如何设置,如何获取) &#xff0c;有何优势&#xff1f; data-* 的值的获取和设置&#xff0c;2种方法: 传统方法 getAttribute() 获取 data- 属性值; setAttribute() 设置 data- 属性值getAttribute() 获取 data- 属性值; setAttribute()…

什么是自动化测试分层?3个层次从高到低又是哪些?

自动化测试分层&#xff0c;可以理解为将测试任务分解为多个层次&#xff0c;并在每个层次中定义特定的测试任务和测试范围&#xff0c;通过这种方式&#xff0c;测试人员可以更好地管理和组织测试工作&#xff0c;确保测试的全面性和准确性。 自动化测试可以分解为3个层次&am…

@ControllerAdvice(实现全局异常、全局参数、请求参数预处理)

近期看到了ControllerAdvice这个注解&#xff0c;本身只是为了看下全局异常处理的&#xff0c;简单了解后发现可以分别与ExceptionHandler、ModelAttribute、InitBinder实现 全局异常、全局参数、请求参数预处理 的功能。 目录 一、全局异常处理二、全局参数处理三、请求参数预…

LPC804开发(11.ADC使用)

1.前言 刚刚研究ADC的时候发现芯片里面还有应该mrt 16位的定时器没有搞&#xff0c;那回头补上吧。下午研究了一下ADC的使用&#xff0c;我也只是能用上&#xff0c;这里我只是抛砖引玉一下吧&#xff0c;有需要的还请各位自行深入探究&#xff0c;我这里讲解的是软件触发模式…

AI工具【OCR 01】Java可使用的OCR工具Tess4J使用举例(身份证信息识别核心代码及信息提取方法分享)

Java可使用的OCR工具Tess4J使用举例 1.简介1.1 简单介绍1.2 官方说明 2.使用举例2.1 依赖及语言数据包2.2 核心代码2.3 识别身份证信息2.3.1 核心代码2.3.2 截取指定字符2.3.3 去掉字符串里的非中文字符2.3.4 提取出生日期&#xff08;待优化&#xff09;2.3.5 实测 3.总结 1.简…

Unity SRP 管线【第九讲:URP 点光源与聚光灯】

文章目录 CPU数据搜集GPU数据使用光照计算 CPU数据搜集 我们只能支持有限数量的其他灯。并将这些灯光数据&#xff08;位置、颜色、阴影强度、方向光光源、灯光遮蔽Probe、灯光层级Mask&#xff09;发送到GPU以供场景中所有物体渲染使用。 //ForwardLights.cs 额外光源数量与…

qt内存自动释放的两种情况

qt内存管理机制 QObject的parent 我们时常能看到QWidget或者其他的控件的构造函数中有一项参数parent&#xff0c;默认值都为NULL&#xff0c;例如&#xff1a; QLineEdit(const QString &contents, QWidget *parent nullptr); QWidget(QWidget *parent nullptr, Qt::…

vue3学习——初始化项目及配置

初始化项目 环境 node 16pnpm 8.0.0 命令 pnpm create vite进行以下选择 &#x1f447; – 项目名 – VUe – Ts – cd/目录 – pnpm run dev 浏览器自动打开 package.json 配置eslint 安装依赖包 pnpm i eslint -D npx eslint --init // 生成配置文件进行以下选择 &a…

【2024】大三寒假再回首:缺乏自我意识是毒药,反思和回顾是解药

2024年初&#xff0c;学习状态回顾 开稿时间&#xff1a;2024-1-23 归家百里去&#xff0c;飘雪送客迟。 搁笔日又久&#xff0c;一顾迷惘时。 我们饱含着过去的习惯&#xff0c;缺乏自我意识是毒药&#xff0c;反思和回顾是解药。 文章目录 2024年初&#xff0c;学习状态回顾一…

vue——实现多行粘贴到table事件——技能提升

最近在写后台管理系统时&#xff0c;遇到一个需求&#xff0c;就是要从excel表格中复制多行内容&#xff0c;然后粘贴到后台系统中的table表格中。 如下图所示&#xff1a;一次性复制三行内容&#xff0c;光标放在红框中的第一个框中&#xff0c;然后按ctrlv粘贴事件&#xff0…

掌上医院预约挂号缴费系统源码,与医院信息系统共享数据,实现在线预约挂号、移动支付、医保支付、检验检查报告查看、门诊病历查询等功能。

随着信息技术的发展和互联网的普及&#xff0c;越来越多的患者开始习惯于通过互联网获取医疗服务。网上预约挂号是近年来开展的一项便民就医服务&#xff0c;旨在缓解看病难、挂号难的就医难题&#xff0c;许多患者为看一次病要跑很多次医院&#xff0c;最终还不一定能保证看得…

MySQL数据控制语言DCL

MySQL数据控制语言DCL 目录 MySQL数据控制语言DCLDCL关键字1.事务事务的四大特性START TRANSACTION&#xff1a;开始事务ROLLBACK&#xff1a;回滚COMMIT&#xff1a;提交事务 2.用户权限CREATE USER&#xff1a;创建新的用户并指定权限DROP USER&#xff1a;删除用户ALTER USE…

深度强化学习(王树森)笔记10

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

LeetCode —— 17. 电话号码的字母组合

&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️…

基于人体姿态的目标追踪实现

文章目录 概要人体姿态识别人体关键点转检测框实时跟踪器概要 这篇博文简单介绍了如何实现基于人体姿态的多目标跟踪算法。主要分为以下几个步骤: 基于FastDeploy实现人体姿态识别;将人体姿态转化为检测框;基于SORT算法实现目标跟踪。人体姿态识别 参考Fastdeploy实现。 模…

Ubuntu系统硬盘分区攻略(磁盘分区、RAID磁盘阵列、RAID阵列、固态硬盘分区、机械硬盘分区、swap分区、swap交换分区)

文章目录 分区需求分区方案分区顺序相关疑问swap分区不是应该放在最后吗&#xff1f;我安装系统分区的时候&#xff0c;上面有available devices&#xff0c;下面有create software raid(md)&#xff0c;我该用哪个&#xff1f;我available devices下面有个893G的固态&#xff…

Selenium自动化测试 —— 模拟鼠标键盘的操作事件

鼠标操作事件 在实际的web产品测试中&#xff0c;对于鼠标的操作&#xff0c;不单单只有click()&#xff0c;有时候还要用到右击、双击、拖动等操作&#xff0c;这些操作包含在ActionChains类中。 ActionChains类中鼠标操作常用方法&#xff1a; 首先导入ActionChains类&#…

Python OpenCV实现图片像素区域缩放

Python OpenCV实现图片像素区域缩放 前言项目安装OpenCV和Pillow思路代码编写 前言 遇到一个要将大量图片缩放成统一规格的难题&#xff0c;并且这些图片周围还有很多空白像素&#xff0c;所以用Python实现一下。 项目 安装OpenCV和Pillow pip install opencv-python pip …

C++:异常体系

异常体系 异常1.C语言传统的处理错误的方式2.C异常概念3.异常的使用3.1异常的抛出和捕获3.2 异常的重新抛出3.3异常安全3.4 异常规范 4.C标准库的异常体系5.异常的优缺点 异常 1.C语言传统的处理错误的方式 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以…