类和对象(下)

目录

1.初始化列表

1.1 构造函数体内的赋值

1.2 初始化列表

1.对象整体定义和成员变量定义的区别

2.初始化列表的写法

1.3 和C++11的联系

1.4 针对初始化列表的建议

2.静态成员

2.1 静态成员变量

1.概念

2.特性

2.2 静态成员函数

1.概念

2.特性

3.友元

3.1 友元函数

3.2 友元类

4.内部类

4.1 概念

4.2 特性

5.隐式类型转换和explicit关键字

5.1 隐式类型转换

1.支持隐式类型转换的前提

2.隐式类型转换的底层原理

2.1 复习:C语言的隐式类型转换和const引用

2.2 自定义类型的隐式类型转换

3.隐式类型转换的场景

第一种:string类的构造场景

第二种:string对象的传参场景

5.2 explicit

6.匿名对象

6.1 匿名对象的创建

6.2 匿名对象的特点

6.3 匿名对象的场景

1.方便类中成员函数的调用

2.某些传参场景(同上隐式类型转换)

7.拷贝对象时的编译器优化

7.1 连续的构造+拷贝->一次构造

7.2 连续的拷贝+拷贝 ->一次拷贝

7.3 连续的拷贝+赋值 ->不能优化


 

1.初始化列表

1.1 构造函数体内的赋值

以前成员变量的赋值我们都是在构造函数中完成的,但是这种赋值单单是赋值,不是初始化,更不是定义(初始化只有一次,赋值可以多次),成员变量的定义和初始化都不是在函数体中完成的,那是在哪里呢?初始化列表!

函数体中赋值:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

1.2 初始化列表

1.对象整体定义和成员变量定义的区别

当我们写下Date d时,d一个对象被整体定义了,但是d里面的成员定义好了吗?并没有!只有在构造函数调用时的初始化列表中,成员变量才被定义。(比喻:我们把房子建好了,这个房子相当于d对象整体,但是房子里面的各个房间并没有装修好,这些房间也就是各个成员)

2.初始化列表的写法

成员变量的定义和初始化都在初始化列表中完成,那么我们怎么写呢?

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

初始化列表和函数体内的初始化是可以混着用的。

class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};

注意几点:

  1. 我们自己不显示写初始化列表时,编译器自己有个初始化列表,它的初始化列表针对成员变量只有定义没有初始化

  2. 针对某些特殊成员,他们必须在定义的时候初始化,比如:引用类型、const类型;如果类中有这些成员而我们没有显示写初始化列表时,这些成员就只有初始化而没有定义,编译就会出错,所以这两种成员我们只能在初始化列表中初始化。

  3. 如果我们不显示写初始化列表:其他的内置类型(除引用、const类型)也会定义,但是只有定义没有初始化,所以这些内置类型会给随机值;而自定义类型,调用它的默认构造。

  4. 如果自定义类型没有可用的默认构造,并且我们没有显示写初始化列表,也会导致编译出错,所以针对这种自定义类型,我们只能在初始化列表中显示调用他的其他构造函数。

  5. 总结:必须用初始化列表来初始化的三种类型:

    • 引用类型

    • const类型

    • 没有默认构造的自定义类型

1.3 和C++11的联系

C++11针对内置类型,在声明处是可以给缺省值的,但是这个为什么叫缺省值?和缺省参数有关系吗?

  • 这里的缺省值就是给初始化列表用来初始化内置类型的!

至于为什么叫缺省值,因为他和缺省参数一样是个备胎,我们显示写初始化列表给了值以后,就不用缺省值了。

  • 初始化列表的定义初始化顺序是按照类中的声明顺序来的

class A
{
public:A(int a):_a1(a), _a2(_a1){}
​void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2;int _a1;
};
​
int main() 
{A aa(1);aa.Print();
}

上面代码的声明次序中,a2在a1前面,所以我们先初始化a2,再初始化a1,所以a1为1,a2为随机值。

1.4 针对初始化列表的建议

其实在绝大多数情况下,我们都建议写初始化列表。因为不管你是否使用初始化列表,对于自定义类型的成员变量,一定会先使用初始化列表初始化。

  • 前面说明,这三种类型必须使用初始化列表初始化:引用类型、const类型、没有默认构造的自定义类型。

  • 但是有默认构造的自定义类型也建议使用初始化列表,因为我们在函数体中不太好显示初始化自定义类型。比如下面代码:

class A
{}
​
class B
{
public:B(int a)//:_aobj(a) 初始化列表这么写{A tmp(a);_aobj = tmp;}
​
private:A  _aobj;
}

这种写法显然有点孬,不如初始化列表高效。

  • 还有内置类型也是建议使用初始化列表的,但并不是全部,比如涉及资源申请的指针类型,有时候比较冗余的话还是在函数体中赋值比较正常。

2.静态成员

2.1 静态成员变量

1.概念

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

2.特性

  1. 静态成员变量不属于任何一个对象,而是属于整个类的,或者说为这个类的所有对象所共享。

  2. 静态成员变量的使用:

    类名::静态成员变量

    对象名.静态成员变量

  3. 静态成员变量的定义:

    • 静态成员变量能在声明位置给值吗?结合前面所学:不可以!

      声明位置给的缺省值是给初始化列表的,而初始化列表是在对象定义完后用来定义初始化成员变量的,但是静态成员变量属于一个对象吗?显然不是,所以静态成员变量不能在类里面定义。

    • 静态成员变量只能在类外定义,声明和定义是要分离的,具体怎么写,如下:

      class A
      {
      public://....
      ​
      private:int _a = 1;static int _b;//静态成员变量声明
      };
      int A::_b = 0;//静态成员变量定义

      注意几点:

      1. 不要忘记指定类域。

      2. 定义时要加类型,但不要加static。

      3. 这里在声明定义时访问静态成员变量并不受访问限定符的限制,只是定义而已。

  4. 静态成员变量也受访问限定符的影响。

2.2 静态成员函数

1.概念

用static修饰的成员函数,称之为静态成员函数。

2.特性

  1. 静态成员函数不属于任何一个对象,而是属于整个类的,或者说为这个类的所有对象所共享。

  2. 静态成员函数的使用:

    类名::静态成员函数

    对象名.静态成员函数

    class A
    {
    public://....static int getStatic(){return _b;}
    ​
    private:int _a = 1;static int _b;
    };
    int A::_b = 0;
    int main() 
    {A a;int a = A::getStatic(); int b = a.getStatic(); 
    }
    ```

  1. 静态成员函数不属于任何一个对象,也不需要一个对象来调用,那么它的函数参数有this指针吗?

    显然没有!因此静态成员函数不能访问非静态成员变量、调用非静态成员函数。

  2. 静态成员函数也受访问限定符的影响。

  • 总结:这么理解静态成员变量和静态成员函数更加清晰:

静态成员变量和静态成员函数其实就是受限制的全局变量和全局函数,他们并不在类中,包括静态成员变量(所以只有非静态成员变量是在类中的)。他们受什么限制呢?类域限制和访问限定符限制。

3.友元

针对某些场景我们在类外需要访问类中的私有成员,我们目前有两种方法解决:

  1. 变私有为公有。

  2. 添加这些私有成员的get函数。

方法一过于暴力对封装的破坏性大,方法二又不适合C++(Java经常这么用),这时候就用到了C++的一种突破封装的语法:友元。

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装(但相比直接修改访问限定符,破坏程度较小),所以友元不宜多用。 友元分为:友元函数和友元类

3.1 友元函数

友元函数的经典场景就是operator<<和operator>>的重载。我们尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

class Date
{
public://...
​//友元函数friend ostream&  operator<<(ostream& out, const Date& d);//cout << d1friend istream&  operator>>(istream& in, Date& d);//cin >> d1
private://...
};
​
ostream&  operator<<(ostream& out, const Date& d) 
{out << d._year << ":" << d._month << ":" << d._day << endl;return out;
}
​
istream&  operator>>(istream& in, Date& d) 
{in >> d._year >> d._month >> d._day; return in;
}

  1. 友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

  2. 友元函数可访问类的私有和保护成员,但不是类的成员函数。

  3. 友元函数不能用const修饰。

  4. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

  5. 一个函数可以是多个类的友元函数。

  6. 友元函数的调用与普通函数的调用原理相同。

3.2 友元类

class Time
{friend class Date;  //声明Date类为Time类的友元类,则在Date类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问Time类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  2. 友元关系是单向的,不具有交换性。(比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。)

  3. 友元关系不能传递。(如果C是B的友元, B是A的友元,不能说明C时A的友元。)

  4. 友元关系不能继承。

4.内部类

4.1 概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。

4.2 特性

  1. 内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。

  2. 外部类对内部类没有任何优越的访问权限。

  3. 内部类天生为外部类的友元类,但是外部类不是内部类的友元。

  4. 内部类的实例化要指定外部类的类域。

  5. 内部类可以定义在外部类的public、protected、private限定符中。

  6. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象名/类名。

  7. sizeof(外部类)=外部类,和内部类没有任何关系。

class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void foo(const A& a){cout << k << endl;cout << a.h << endl;}};
};
int A::k = 1;int main()
{A::B b; b.foo(A());return 0;
}

5.隐式类型转换和explicit关键字

5.1 隐式类型转换

1.支持隐式类型转换的前提

构造函数不仅可以初始化对象,而且对于单参数构造函数(单个参数或者因为缺省参数也可以传单个参数),还具有类型转换的作用。比如Date类:

class Date
{
public:Date(int year = 1970,int month = 1, int day = 1):_year(year),_month(_month),_day(_day){}private:int _year;int _month;int _day;
};int main()
{Date d = 2023;//针对单参数构造函数,支持隐式类型转换return 0;
}

2.隐式类型转换的底层原理

2.1 复习:C语言的隐式类型转换和const引用
  • C语言的隐式类型转换: double b = 3.14; int a = b;

  • 类型转换(强制类型转换和隐式类型转换)在底层上会产生一个临时变量,这个临时变量具有常量的属性。类型转换是并不会真的改变原来变量的类型的。

  • 所以使用引用时,是要加上const的:double b = 3.14;const int& a = b;

2.2 自定义类型的隐式类型转换

因为构造函数的特殊性而让自定义类型也能隐式类型转换,底层也是这个原理:

  • Date d = 2023; 底层上会先使用2023构造一个临时对象Date tmp(2023, 1, 1);然后临时对象tmp拷贝构造给d。(所以这个过程是构造+拷贝,但是编译器会优化成一次构造,具体后面会系统说)

  • 这个临时对象tmp具有常量的属性,所有涉及到引用时,要加const:

    const Date& d = 2023;

3.隐式类型转换的场景

第一种:string类的构造场景

C++有个string类,它的构造函数可以认为是这样的:string(const char* str = "");

一般我们是这么构造string类的:string str("hello world");

但是我们也可以这么构造:string str = "hello world";

这里就是涉及了隐式类型转换。

第二种:string对象的传参场景

还有一种场景,就是string类的传参,拿插入一个string对象为例:

一般来说Push只能这么使用:

vector<string> v;

string str("string");

v.Push(str);

但是函数接口这么写的话就不一样了:void Push(const string& str); 也就是要加上const,以前说过const有两个作用:

  1. 如果函数体中不用修改str对象,起到防止误操作修改str对象的作用。

  2. string类的const对象也可以传进来。

现在又多了一种:可以让 涉及隐式类型转换的临时对象(或者匿名对象)也传进来,也就是这种场景:

vector<string> v;

v.Push("string");

这里传参过程是这么传的:cosnt string& str = "string";中间会产生一个临时对象,临时对象具有常性,所有只能用const引用接收,如果函数接口只是正常的string引用是传不进来的。

5.2 explicit

用explicit修饰构造函数,将会禁止构造函数的隐式类型转换。

6.匿名对象

6.1 匿名对象的创建

//针对A类//普通对象创建:
A a;
//匿名对象创建:
A();A aa();//编译器识别成函数声明

注意:无参的匿名对象构造,也是要加()的。

6.2 匿名对象的特点

  1. 没有名字。

  2. 生命周期只有一行,下一行他就会自动调用析构函数。

  3. 使用匿名对象中间会先构造一个临时对象。这里是和隐式类型转换相似的,所以匿名对象也可以这么用:

    string str = string("hello world");

    const string& str = string("hello world");

6.3 匿名对象的场景

1.方便类中成员函数的调用

class Solution 
{
public:int Sum_Solution(int n) {//...return n;}
};int main()
{Solution().Sum_Solution(10);return 0;
}

这样就可以不创建对象来调用成员函数了。

2.某些传参场景(同上隐式类型转换)

上面我们说过,对于Push函数接口,如果Push的是string类对象,接口这么写:void Push(const string& str);

这样可以接受隐式类型转换的临时对象,但是他也是可以接受匿名对象的,因为我们可以认为匿名对象也有个临时对象

class StringContainer
{
public:void Push(const string& str){//....}
};int main()
{StringContainer sc;sc.Push("hello");sc.Push(string("world"));return 0;
}

匿名对象的生命周期只有一行,但是我们可以认为使用const引用过后可以延长他的生命周期。

7.拷贝对象时的编译器优化

7.1 连续的构造+拷贝->一次构造

场景1

string str = "hello world";

场景2

void fun(string str)
{//...
}f("hello");//隐式类型转换:string str = "hello";

场景3

void fun(string str)
{//...
}f(string("hello"));//匿名对象场景:string str = string("hello");

7.2 连续的拷贝+拷贝 ->一次拷贝

场景:传值返回的函数

string fun()
{string str("hello");return str;
}string str1 = fun();

7.3 连续的拷贝+赋值 ->不能优化

string fun()
{string str("hello");return str;
}string str1("hello");
str1 = fun();//这里的=是赋值运算符重载 ,不能优化

tmp临时对象的拷贝是拷贝构造,临时对象再赋值给str1对象是赋值运算符重载,两者不能优化成一次拷贝

注意:上述的两种优化都是在同一个表达式中才能优化的,跨表达式不能这样优化。

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

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

相关文章

linux进程调度(一)-进程概述

一、什么是进程 进程是指计算机已运行的程序。程序本身只是指令、数据及其组织形式的描述。进程就是一个程序的执行实例&#xff0c;也就是正在执行的程序。在linux操作系统的中&#xff0c;进程就是一个担当分配系统资源CPU时间、内存的实体。进程控制的主要功能是对系统中的所…

Shell循环:for(二)

一、通过用户列表文件创建用户 需求&#xff1a;通过用户列表文件创建用户 [rootlocalhost ~]# cat user.txt qian yoa huang演示&#xff1a; [rootlocalhost ~]# vim foruser.sh #编写脚本 #!/bin/bash for i in cat user.txt do useradd $i if [ $? -eq 0 ] thenech…

思科模拟器操作命令

模式 思科模拟器常见的模式有 用户模式 能够操作的命令比较少 特权模式特权模式下面可以操作的比较多 全局模式 接口模式 用户模式进入特权模式: 命令enable 特权模式进行全局模式命令: configure terminal 退出命令 exit命令&#xff1a;返回上一层&#xff0c;即一步一步…

canvas高级动画001:文字瀑布流

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 示例…

拿捏---JVM原理

文章目录 JVM内存划分JVM类加载为什么需要类加载&#xff1f;类加载的过程何时触发类加载&#xff1f;双亲委派模型 JVM的垃圾回收机制&#xff08;GC&#xff09;什么是垃圾回收&#xff1f;GC回收哪部分内存&#xff1f;回收机制怎么找出垃圾&#xff1f;引用计数可达性分析&…

物流实时数仓:数仓搭建(ODS)

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 文章目录 系列文章目录前言一、IDEA环境准备1.pom.xml2.目录创建 二、代码编写1.log4j.properties2.CreateEnvUtil.java3.KafkaUtil.java4.OdsApp.java 三、代码测试总结 前言 现在我们…

大模型推理加速框架vllm部署的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

堆的实现(堆的插入、堆的删除等)超级全

堆的实现&#xff08;堆的插入、堆的删除等&#xff09;超级全 文章目录 堆的实现&#xff08;堆的插入、堆的删除等&#xff09;超级全一、前期基础知识1.树结构①树的定义②树的相关概念③二叉树④满二叉树和完全二叉树a.满二叉树b.完全二叉树 ⑤二叉树的性质⑥二叉树顺序结构…

YOLOv8改进 | 2023 | LSKAttention大核注意力机制助力极限涨点

论文地址&#xff1a;官方论文地址 代码地址&#xff1a;官方代码地址 一、本文介绍 在这篇文章中&#xff0c;我们将讲解如何将LSKAttention大核注意力机制应用于YOLOv8&#xff0c;以实现显著的性能提升。首先&#xff0c;我们介绍LSKAttention机制的基本原理&#xff0c;…

深入理解 Django 中的事务管理

概要 在数据库操作中&#xff0c;事务是确保数据完整性和一致性的关键机制。Django 作为一个强大的 Python Web 框架&#xff0c;提供了灵活而强大的事务管理功能。理解和正确使用 Django 中的事务对于开发高质量的 Web 应用至关重要。本文将深入探讨 Django 的事务管理机制&a…

2021年12月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行下列程序,屏幕上可以看到几只小猫? A:1 B:3 C:4 D:0 答案:B 第2题 下列程序哪个可以实现:按下空格键,播放完音乐后说“你好!”2秒? A: B: C:

人工智能-注意力机制之注意力汇聚:Nadaraya-Watson 核回归

查询&#xff08;自主提示&#xff09;和键&#xff08;非自主提示&#xff09;之间的交互形成了注意力汇聚&#xff1b; 注意力汇聚有选择地聚合了值&#xff08;感官输入&#xff09;以生成最终的输出。 本节将介绍注意力汇聚的更多细节&#xff0c; 以便从宏观上了解注意力机…