iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >【C++深入浅出】类和对象下篇
  • 469
分享到

【C++深入浅出】类和对象下篇

摘要

一. 前言         老样子,先来回顾一下上期的内容:上期我们着重学了c++类中的六大默认成员函数,并自己动手实现了一个日期类,相信各位对C++中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦,终于要结束咯,吧唧吧唧  


一. 前言

        老样子,先来回顾一下上期的内容:上期我们着重学了c++类中的六大默认成员函数,并自己动手实现了一个日期类,相信各位对C++中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦,终于要结束咯,吧唧吧唧

        话不多说,开吃咯!!!

二. 初始化列表

2.1 引入

        我们先来看看下面的代码:

class Date{public:Date(int year = 2023, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:const int _year;const int _month;const int _day;};int main(){Date d;return 0;}

当我们编译代码时,发现编译器报了一大堆错误。报错的主要原因主要有两个

1、const变量定义时需要进行初始化

2、const变量不能作为左值

        欸,可能有些小伙伴就纳闷了:我们不是在构造函数中对const成员变量进行初始化了吗? 实际上,在构造函数函数体内进行的并不是初始化,而是赋值操作。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。

        出于这个原因,于是编译器就会报出以上两种错误。那怎么办呢?众所周知,初始化是在定义变量时进行的,那变量又是在哪定义的呢?答案是:初始化列表

2.2 概念

        在C++中,初始化列表可以认为是成员变量定义的地方。

        初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。举例如下:

class Date{public:Date(int year = 2023, int month = 1, int day = 1):_year(year) //初始化列表,是每个成员变量定义的地方,可以进行初始化,_month(month) //用month的值初始化成员变量_month,_day(day){}private://成员变量的声明const int _year = 0; const int _month = 0;const int _day = 0;};int main(){Date d;return 0;}

2.3 注意事项

  1. 变量的初始化只能初始化一次,故每个成员变量在初始化列表中只能出现一次
  2. 当类中包含以下成员时,必须放在初始化列表位置进行初始化
    class A{public:A(int a) //显式定义构造函数,不自动生成默认构造函数:_a(a){}private:int _a;};class B{public:B(int a, int ref):_a(a) //调用有参构造函数初始化, _ref(ref) //初始化引用变量, _n(10) //初始化const变量{}private:A _a; // 没有默认构造函数的类int& _ref; // 引用变量const int _n; // const变量};
  3. 建议尽量使用初始化列表初始化,因为初始化列表是成员变量定义的地方,无论你是否显式地写,每个成员都要走初始化列表
    class Time{public:Time(int hour = 0):_hour(hour){cout << "Time()" << endl;}private:int _hour;};class Date1{public:Date1(int day):_day(day)  //使用初始化列表进行初始化,_t(day){}private:int _day;Time _t;};class Date2{public:Date2(int day){_day = day; //在构造函数内部进行赋值_t = day;}private:int _day;Time _t;};int main(){Date1 d1(3);cout << "-----------------------" << endl;Date2 d2(3);return 0;}

  4. C++11支持在声明处给缺省值,这个缺省值就是给初始化列表的。如果初始化列表没有显式给值,则使用这个缺省值;如果显式给了,就用给的值进行初始化。

  5. 初始化列表对成员变量的初始化顺序与其声明的次序相同,与初始化列表的先后次序无关。举个小例子

    class A{public:A(int a):_a1(a)  //初始化列表的顺序和声明一样,即也是先初始化_a2再初始化_a1, _a2(_a1)  //那么,这里用_a1初始化_a2会发生什么?_a1的值是多少{}void Print() {cout << _a1 << " " << _a2 << endl;}private://成员变量的声明,先_a2再_a1int _a2;int _a1;};int main() {A aa(1);aa.Print();}

    上面代码的输出结果是1 随机值

    解析:由于_a2的声明在_a1前,_a2会先于_a1进行初始化,因此_a2初始化时_a1还是个随机值,故_a2会被初始化为随机值,然后_a1再初始化为1。


    我们也可以使用调试来观察初始化顺序,如下所示:


三. explicit关键字

        构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有隐式类型转换的作用,如下:

class Date{public:    // 1. 单参构造函数,具有隐式类型转换作用Date(int year):_year(year){}//2. 虽然有多个参数,但是后两个参数可以不传递,具有类型转换作用//用explicit修饰构造函数,可以禁止类型转换    //explicit Date(int year, int month = 1, int day = 1)//: _year(year)//, _month(month)//, _day(day)//{}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}private:int _year;int _month;int _day;};int main(){Date d1(2022); //使用单参构造函数初始化d1// 用一个整形变量给日期类型对象赋值// 实际编译器背后会用2023构造一个匿名的临时对象,最后用这个临时对象给d1对象赋值d1 = 2023; return 0;}

像上面这种运算符左右两边类型不匹配,运算时编译器背后进行处理的过程,称之为隐式类型转换


但是,这样的代码往往可读性不好,我们更希望书写代码时左右两边的类型是一致的,那有没有什么办法可以禁止编译器进行隐式类型转换呢?有,就是explicit关键字

使用 explicit(显式的) 修饰构造函数,将会禁止构造函数的隐式类型转换。很简单,直接在构造函数前面加上explicit即可,这里就不再进行演示了。

四. static成员

4.1 概念

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

class A{static int GetCount() //静态成员函数{return count;}private:static int count; //静态成员变量};int A::count = 10; //静态成员变量要在类外进行初始化

4.2 特性

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

小问题:静态成员函数可以调用非静态成员函数吗?反过来呢?


问题解答:答案是不行,静态成员函数不能调用非静态成员函数,因为静态成员函数没有隐藏的this指针,而非静态成员函数需要通过this指针来调用。但是非静态成员函数可以调用静态成员函数,因为静态成员函数的特点是没有this指针,故可以直接进行调用。


五. 友元

5.1 概念

        在C++中,为了封装性我们一般将成员变量声明为【private】私有的,只允许在类内访问成员变量。但是有时候我们需要在类外访问这些成员变量,此时有两种方法:1.将成员变量声明为【public】共有;2.利用友元

        友元提供了一种突破封装的方式,为代码的编写提供了便利。友元分为友元类友元函数,当一个函数/类声明为某个类的友元函数/类时,这个函数/类访问类中成员时不受访问限定符限制。下面是函数/类声明为友元的方式,用到了friend关键字

class A{friend void GetCount(const A& a); //将全局函数GetCount声明为A类的友元函数friend class B; //将B类声明为A类的友元类private:int count = 10;int num = 20;};class B{public:void GetNum(const A& a){cout << a.num << endl; //b类中可以访问a类的私有成员}};void GetCount(const A& a){cout << a.count << endl; //可以访问A类的私有成员}int main(){A a;B b;GetCount(a);b.GetNum(a);return 0;}

小贴士:虽然友元提供了便利,但是友元会增加耦合度,破坏程序的封装性,故不建议使用友元。

5.2 友元函数   

        友元函数一般用作于流提取运算符>>以及流插入运算符<<的重载,这两个运算符的重载比较特殊,不能当做成员函数进行重载。

class Date{public:Date(int year = 2023, int month = 1, int day = 1):_year(year), _month(month), _day(day){}//如果重载为Date的成员函数,第一个参数为隐藏的this指针,但cout是ostream类的对象,第一个参数应该是ostream类型,互相矛盾// ostream& operator<<(const Date& d);   private:int _year;int _month;int _day;};//为了让第一个参数类型为ostream,故当做全局函数重载const ostream& operator<<(const ostream& out, const Date& d){out << d._year << "年" << d.month << "月" << d.day << "日";return out;}int main(){Date d;cout << d;  //重载流插入运算符使其可以输出日期类 return 0;}

        那么问题就来了,既然不能声明为成员函数,那我们在全局函数中要怎么访问Date的私有成员呢?

         这时候就不得不使用我们上面说的友元了,将operator<<声明为Date类的友元函数后,代码成功运行:

class Date{public:Date(int year = 2023, int month = 1, int day = 1):_year(year), _month(month), _day(day){}friend ostream& operator<<(ostream& out, const Date& d); //将operator<<全局函数声明为Date类的友元函数private:int _year;int _month;int _day;};

注意事项

  • 友元函数可以访问类中的私有成员,它是定义在类外部的普通函数,但需要在类的内部进行声明,声明时需要加friend关键字
  • 友元函数不能用const修饰,const只能修饰成员函数
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数

5.3 友元类

        友元类中的所有成员函数都可以访问另一个类的非公有成员。友元关系是单向的,不具有交换性。例如B是A的友元类,B中的所有成员函数可以访问A中的私有成员,但A中的成员函数不能访问B中的私有成员。举例如下:

class A{friend class B; //定义B是A的友元类void GetSum(B& b){cout << b.sum << endl;  //这里会报错,A类的成员函数无法访问B类的私有成员,不具有交换性}private:int count = 20;};class B{void GetCount(A& a){cout << a.count << endl; //通过编译,B是A的友元类,B中成员函数可以访问A的私有成员}private:int sum = 10;};

        以上程序编译时会报错如下


        友元关系也不具有传递性。例如:C是B的友元类,B是A的友元类,无法说明C是A的友元。举例如下

class A{friend class B; //定义B是A的友元类private:int a_sum = 10;};class B{friend class C; //定义C是B的友元类private:int b_sum = 20;};class C{void GetBSum(B& b){cout << b.b_sum << endl;  //编译通过,C是B的友元类}void GetASum(A& a){cout << a.a_sum << endl;  //这里编译器会报错,C不是A的友元类,无法访问私有成员,友元关系不具有传递性}private:int c_sum = 30;};

         以上程序编译时会报错如下 


六. 内部类

        一个类不仅可以定义在全局范围内,还可以定义在另一个类的内部。我们将定义在某个类内部的类称之为内部类。下面的B类就是一个内部类:

class A //A称为外部类{public:class B //B类在A类的内部定义,称之为内部类{private:int sum; //b类的成员变量};private:int count; //a类的成员变量};

        内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何特殊的访问权限。有以下两个具体体现

  • 内部类可以定义在外部类的的任何位置,不受外部类访问限定符的限制。
  • sizeof(外部类)=外部类,和内部类没有任何关系

        内部类是外部类的友元类,内部类可以通过外部类的对象访问外部类的所有成员。但外部类不是内部类的友元类,无权访问内部类的私有成员。

class A //A是外部类{public:class B //B是内部类{int GetACount(A& a)  {return a.count; //可以访问外部类的私有成员}private:int sum; };int GetBSum(B& b){return b.sum; //这里会报错,外部类不能访问内部类的私有成员}private:int count; //a类的成员变量};

        内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名,如下所示

class A //A是外部类{public:class B //B是内部类{int GetACount()  {return _count; //可以直接访问外部类的静态成员变量,无需类名/类对象}private:int sum; };private:static int _count; // A中的静态成员变量};int A::_count = 10; //类外进行初始化

七. 匿名对象

        C++支持我们不给对象起名字,这样的对象我们称为匿名对象,其定义方式如下:

int main(){//对象类型+():创建一个匿名对象A();  //这里就是创建一个匿名对象Areturn 0;}

        匿名对象的声明周期只在当前行,当前行结束后会自动调用析构函数进行销毁:

       匿名对象具有常属性,即不能对匿名对象中的成员变量进行修改:

int main(){A().count = 10; //编译器报错:表达式必须是可修改的左值return 0;}

        可以给匿名对象取别名,这样可以延长匿名对象的声明周期:

int main(){//给匿名对象取别名const A& cla1 = A(); //注意:这里必须是const引用,因为匿名对象具有常性,权限不能放大cout << "程序即将结束" << endl;return 0;}

        匿名对象经常用在仅需调用某个类的成员函数的情况,可以简化我们代码的编写。举例如下 

class Solution //Solution类用来求两数之和{public:int Sum_Solution(int x,int y)  //返回两数之和 {return x + y;}};int main(){//不使用匿名对象Solution s1; //要先定义一个类对象,这个对象仅仅只是用来调用方法s1.Sum_Solution(2, 2); //然后再去调用成员函数//使用匿名对象Solution().Sum_Solution(2, 3); //代码更加简便}

        上面的Solution类是不是很熟悉?没错,在我们使用C++进行刷题时每次能够遇到它


 以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏

来源地址:https://blog.csdn.net/m0_69909682/article/details/132586220

--结束END--

本文标题: 【C++深入浅出】类和对象下篇

本文链接: https://www.lsjlt.com/news/426805.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 【C++深入浅出】类和对象下篇
    一. 前言         老样子,先来回顾一下上期的内容:上期我们着重学了C++类中的六大默认成员函数,并自己动手实现了一个日期类,相信各位对C++中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦,终于要结束咯,吧唧吧唧  ...
    99+
    2023-10-10
    c++ 开发语言 初始化列表 static成员 友元 匿名对象 explicit关键字
  • 深入浅出分析Java类和对象
    目录一、什么是类二、Java的类和C语言的结构体异同三、类和类的实例化类的声明实例化的对象,成员遵循默认值规则类的实例化静态属性(静态成员变量)四、构造方法创建构造方法this一、什...
    99+
    2022-11-13
  • Java 深入浅出解析面向对象之抽象类和接口
    目录抽象类声明抽象类声明抽象方法案例使用规则接口声明接口案例接口特性抽象类和接口的区别抽象类 java语言,声明类时 abstract class Db{} 说明Db类为抽象类。 j...
    99+
    2022-11-13
  • C++入门浅谈之类和对象
    目录一、面向过程vs面向对象二、类的限定符及封装三、类的实例化四、this指针五、默认成员函数1. 构造函数2. 析构函数3. 拷贝函数4. 赋值运算符重载总结一、面向过程vs面向对...
    99+
    2022-11-12
  • redis深入浅出分布式锁实现下篇
    目录优化之UUID防误删项目中正确使用总结优化之UUID防误删 问题:删除操作缺乏原子性。 场景: index1执行删除时,查询到的lock值确实和uuid相等 uuid=v1 ...
    99+
    2022-11-13
  • 【C++深入浅出】日期类的实现
    目录 一. 前言  二. 日期类的框架 三. 日期类的实现 3.1 构造函数 3.2 析构函数 3.3 赋值运算符重载 3.4 关系运算符重载 3.5 日期 +/- 天数 3.6 自增与自减运算符重载 3.7 日期 - 日期 四. 完整...
    99+
    2023-09-18
    c++ 类和对象 日期类 运算符重载
  • Java深入浅出数组的定义与使用下篇
    接着上一篇继续,老铁们 1.检查数组的有序性 给定一个整型数组, 判断是否该数组是有序的(升序) public static boolean isUp(int[] arr...
    99+
    2022-11-13
  • C++中对象&类的深入理解
    什么是对象 任何事物都是一个对象, 也就是传说中的万物皆为对象. 对象的组成: 数据: 描述对象的属性 函数: 描述对象的行为, 根据外界的信息进行相应操作的代码...
    99+
    2022-11-12
  • 深入浅析Java 抽象类和接口
    目录一、抽象类1.抽象类1.1抽象类的定义1.2抽象方法的定义方式1.3抽象类的定义方式2.抽象类和实例类的区别3.抽象类示例4.抽象类的特征二、接口1.接口1.1接口的定义1.1定...
    99+
    2022-11-12
  • C语言由浅入深讲解文件的操作下篇
    目录文件的顺序读写字符输入输出fgetc和fputcfgetcfputc:文本行输入输出函数fgets和fputsfgets:fputs:格式化输入输出函数fscanf和fprint...
    99+
    2022-11-13
  • Java由浅入深通关抽象类与接口下
    目录1.对象的比较1.1 Comparable<T>1.2 Comparator<T>2.克隆对象2.1 Cloneable2.2 深拷贝和浅拷贝3.Obje...
    99+
    2022-11-13
  • c++深入浅出讲解堆排序和堆
    目录堆是什么最大堆最小堆堆排序最终代码关于堆堆是什么 堆是一种特殊的完全二叉树 如果你是初学者,你的表情一定是这样的 别想复杂 首先,你一定见过这种图 咱们暂时不管数字 这就是一个...
    99+
    2022-11-13
  • C++深入刨析类与对象的使用
    目录this指针this指针存放在哪nullptr与类类的默认成员函数构造函数意义析构函数拷贝构造运算符重载this指针 现在给出一段代码,实现一个普通的日期 date 的打印: c...
    99+
    2022-11-13
  • 一篇文章带你深入了解Java对象与Java类
    目录1.面向对象是什么?2.Java类1.什么是类2.Java类类的结构Java类的格式3.java对象4.类和对象5.类中的变量,方法1.变量分类成员变量:局部变量:2.方...
    99+
    2022-11-12
  • 深入浅析C# 11 对 ref 和 struct 的改进
    目录前言背景ref 字段生命周期scopedunscopedref struct 约束反射实际用例栈上定长列表栈上链表未来计划高级生命周期总结前言 C# 11 中即将到来一个可以让重...
    99+
    2022-11-13
  • 【C++深入浅出】初识C++上篇(关键字,命名空间,输入输出,缺省参数,函数重载)
         目录 一. 前言 二. 什么是C++ 三. C++关键字初探 四. 命名空间 4.1 为什么要引入命名空间 4.2 命名空间的定义 4.3 命名空间使用 五. C++的输入输出 六. 缺省参数 6.1 缺省参数的概念 6.2 缺...
    99+
    2023-09-02
    c++ 开发语言 笔记 函数重载 缺省参数 命名空间
  • C++深入讲解类与对象之OOP面向对象编程与封装
    目录1.面向对象编程2.面向过程性编程和面向对象编程3.类的引入4.类的定义4.1类的两种定义方式4.1.1声明和定义全部放在类体中4.2.2.声明和定义不放在类体中5.类的访问限定...
    99+
    2022-11-13
  • 深入浅析java中面向对象的继承和多态
    深入浅析java中面向对象的继承和多态?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。继承和多态一.this super关键字this: 可以在构造器中的第一代码中...
    99+
    2023-05-31
    java 继承 多态
  • C++深入探索类和对象之封装及class与struct的区别
    目录封装的意义访问权限class和struct的区别成员属性私有案例练习封装的意义 封装是C++三大面向对象之一 意义: 1、设计类的时候,属性和行为写在一起,表现事物 2、类在设计...
    99+
    2022-11-13
  • C++深入探究类与对象之对象模型与this指针使用方法
    目录C++对象模型和this指针1 成员变量和成员函数分开存储2 this指针概念3 空指针访问成员函数4 const修饰成员函数C++面向对象的三大特性为:封装、继承、多态 C++...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作