<C++>类和对象下|初始化列表|explicit static|友元|内部类|匿名对象|构造函数的优化

文章目录

  • 1. 初始化列表
  • 2. explicit关键字
  • 3. 友元
    • 3.1 友元函数
    • 3.2 友元类
  • 4. static关键字
    • 4.1 概念
    • 4.2 特性
  • 5.内部类
    • 5.1 概念
    • 5.2 特性
  • 6. 匿名对象
  • 7. 拷贝构造时的优化

1. 初始化列表

在类的构造函数体中,对成员属性写的操作叫做赋值,那么成员的初始化是在哪里进行呢?

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){//以下全部都是赋值,不是初始化_year = year;_month = month;_day = day;}
private://以下全部都是声明int _year;int _month;int _day;
};

那我们定义对象时,成员属性是在那里定义的呢?
成员属性在初始化列表中定义。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式

	Date(int year = 1, int month = 1, int day = 1)//成员变量在初始化列表中定义:_year(year),_month(month),_day(day){//以下全部都是赋值,不是初始化_year = year;_month = month;_day = day;}

上述是我们显示写的初始化列表,若没有显示写,初始化列表会将内置类型变量设为初始值

image-20230730095521005

显示写初始化列表image-20230730095732735

注意:

  1. 每个成员变量只能初始化一次,只能在初始化列表中出现一次

  2. 类中若包含以下成员,必须放在初始化列表中进行初始化

    • 引用成员变量

    • const成员变量

    • 无默认构造函数的自定义类型成员变量

      引用和const变量都需要在定义时初始化,若自定义类型对象无默认构造函数则必须在初始化列表中显示传参调用构造函数。

总结: 成员属性的初始化是在初始化列表中完成的,若没有写初始化列表则默认以随机值初始化内置类型,自定义类型变量若有默认构造函数则会调用默认构造函数;构造函数体内完成的是对成员属性的二次赋值

**注意:**C++11打的补丁在声明时为变量设置缺省值,本质上就是在初始化列表中为成员设置初始值。


2. explicit关键字

class A
{
public://单参数的构造函数可以发生隐式类型转换A(int a) :_a(a){	cout << "A(int a)\n";}
private:int _a;
};
int main()
{A a1(1);//调用构造函数//类型不匹配时内置类型会隐式转换为自定义类型 即1转换为A(1) 再通过拷贝构造函数用A(1)构造a3//支持类型转换的前提是A具有单参数构造函数A a3 = 1const A& ref = 1;//将ref绑定构造出来的临时对象,延长了临时对象的生命周期
} 

如果加上explicit关键字则不会发生隐式类型转换(不影响显式类型转换)image-20230730105707505

对于多参数构造函数,C++98及以前不支持隐式类型转换,C++11以后支持了

class B
{
public://C++11支持多参数构造函数的隐式转换B(int b1, int b2): _b1(b1), _b2(b2){cout << "B(int,int)\n";}
private:int _b1;int _b2;
};
int main()
{B b1(1, 2);//构造函数B b2 = { 1,2 };//隐式类型转换为B tmp(1,2),在将tmp拷贝给b2,编译器可能会优化为直接构造const B& rb  = {1, 2};//rb引用的是临时对象tmp(1,2)return 0;
}

运行结果image-20230730110044549


3. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元分为:友元函数友元类

3.1 友元函数

当我们想要重载操作符<<时,没有办法重载为成员函数,因为成员函数的第一个操作数为this指针,因此<<的左操作数不是cout,解决该方法只有将<<重载为全局函数,重载为全局函数时第一个参数类型为ostream&,第二个参数就是需要操作的对象类型,举例Date类<<运算符重载的定义应该是如下

ostream& operator<<(ostream& out, const Date& date)
{out << date._year << "/" << date._month << date._day << endl;
}

这里我们需要在函数体中访问Date类的私有成员,可以将operator<<定义为Date类的友元函数

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

class Date
{friend ostream& operator<<(ostream& out, const Date& date);//声明为友元函数,该函数可以访问Date的私有成员
public:Date(int year = 2023, int month = 7, int day = 30):_year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};

注意:

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

  2. 友元函数不能用const修饰(没有this指针)

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

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

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

3.2 友元类

A类在B类中被声明为友元的,称A类是B类的友元类,A类中可以访问B类的私有成员。

class Time
{friend class Date;
public:Time(int hour = 0, int minute = 0, int sec = 0):_hour(hour),_minute(minute),_sec(sec){}
private:int _hour;int _minute;int _sec;
};
class Date
{friend ostream& operator<<(ostream& out, const Date& date);//声明为友元函数,该函数可以访问Date的私有成员
public:Date(int year = 2023, int month = 7, int day = 30):_year(year), _month(month), _day(day){}void SetTime(int hour, int minute, int sec){//访问Time类的私有成员必须将Date类声明为Time类的友元类_t._hour = hour;_t._minute = minute;_t._sec = sec;}
private:int _year;int _month;int _day;Time _t;
};

注意:

  • 友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承,在继承位置再给大家详细介绍。
  • 友元是一种高耦合的状态,如果一个函数的成员改变了可能会影响到与之相关的友元函数

4. static关键字

4.1 概念

声明为static的成员称为类的静态成员,static修饰类成员属性则称该属性为静态成员变量static修饰类成员函数称该函数为静态成员函数

设计一个类,统计该类创建过多少个对象和当前存在的对象个数

class A
{
public:A(int a = 1){m++;n++;}~A(){n--;}A(const A& a){m++;n++;}static int m;//记录创建对象的个数static int n;//记录当前存在对象的个数
};
int A::m = 0;
int A::n = 0;
A fun(A tmp)
{return tmp;
}
int main()
{A a1(1);A a2(2);fun(a1);cout << A::m <<" " << A::n << endl;//访问静态成员变量时需要指定类域
}

上述设计可以完成任务,但是静态成员变量m和n是public的,因此我们在类外部可以直接修改导致结果误差,可以将static成员属性设置为private对外部提供一个静态成员函数来获取静态成员变量

class A
{
public:A(int a = 1){m++;n++;}~A(){n--;}A(const A& a){m++;n++;}static int GetM(){return m;}static int GetN(){return n;}
private:static int m;//记录创建对象的个数static int n;//记录当前存在对象的个数
};
int A::m = 0;
int A::n = 0;
A fun(A tmp)
{return tmp;
}int main()
{A a1(1);A a2(2);fun(a1);//cout << A::m <<" " << A::n << endl;cout << A::GetM() << " " << A::GetN() << endl;//调用静态成员函数时需要指定类域
}

4.2 特性

  1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区,类似于成员函数存放在公共代码区。
  2. 静态成员变量必须在类外部定义,定义时不添加static关键字,类中只是声明(因为静态变量不属于对象,所以不会调用构造函数在初始化列表中定义)
  3. 类静态成员名即可用类名::静态成员 或者 对象.静态名 来访问
  4. 静态成员没有this指针,不可以访问任何非静态成员
  5. sizeof不会计算静态成员的大小
  6. 静态成员也是类的成员,受public、protected、private访问限定符的限定
  7. 静态成员函数不可以调用非静态成员函数
  8. 非静态成员函数可以调用静态成员函数

5.内部类

5.1 概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象访问内部类的成员。外部类对内部类没有任何优越的访问权限。

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

class A
{
public:class B		//B是A的内部类{public:void fun(A& a){a._a = 1;//B是A的友元,可以访问A的私有成员_b = 2;s_member = 2;//内部类和成员函数一样可以直接访问静态数据成员}private:int _b;};void SetA(int a){_a = a;}int GetStaticMember(){return s_member;}
private:static int s_member;//声明静态成员变量int _a;
};
int A::s_member = 1;//静态成员变量定义在类的外部int main()
{A a;a.SetA(10);A::B b;//想要使用内部类必须先指定外部类域b.fun(a);//内部类可以访问外部类类的private成员return 0;
}

静态成员变量不在对象中,因此静态成员变量不能在初始化列表中初始化,需要在类外部通过类域::变量名初始化

5.2 特性

  1. 内部类定义为public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3. 内部类只是被封装了(需要通过外部类访问),内部类不属于外部类
  4. 内部类是外部类的友元类

练习image-20230801094651169

class Solution {
public:class Sum//Sum是Solution类的友元,Sum内部可以访问Solution的私有成员{public:Sum(){_ret += _i;_i++;}};int Sum_Solution(int n) {Sum s[n];return _ret;}private:static int _ret;//Solution类的静态成员:用来记录结果static int _i;//Solution类的静态成员:用来记录当前加法因子
};
int Solution::_ret = 0;
int Solution::_i = 1;

6. 匿名对象

匿名对象是没有名字的对象,例如上述class A可以通过A();定义一个匿名对象,匿名对象的生命周期只有它所在的一行,定义完后立马会调用析构函数

class A
{
public:A(int a = 1){_a = a;cout << "A()->" << _a << endl;}~A(){cout << "~A()->" << _a << endl;}void Print(){cout << "void Print()\n";}
private:int _a;
};int main()
{A();//调用构造函数后立马调用析构函数return 0;
}

运行结果image-20230801095627239

匿名对象和正常对象一样可以调用函数、传参,仅仅生命周期与普通对象不同而已

匿名对象可以调用函数

	A().Print();

image-20230801100008300

匿名对象可以传参

void fun(const A& a)
{a.Print();
}
int main()
{//A();//调用构造函数后立马调用析构函数//A().Print();fun(A(2));return 0;
}

注意:匿名对象和临时对象一样具有常性,需要使用常引用来绑定匿名对象,相应的Print成员函数需要定义为const成员函数。


7. 拷贝构造时的优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

不同版本的编译器所作的优化不同,下面介绍主流编译器对于拷贝构造时常见的优化

同一个表达式中,连续的构造函数+构造函数/构造函数+拷贝构造函数/拷贝构造函数+拷贝构造函数会合并为一个构造函数/拷贝构造函数

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << _a << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << _a << 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;
};
  • 构造函数+拷贝构造函数->构造函数

    int main()
    {A a = 3;//构造+拷贝构造->构造 return 0;
    }
    

    运行结果:
    image-20231118230421184

    编译器先用3构造临时对象,将临时对象拷贝构造给a,优化为直接用调用构造函数构造a

    void f1(A aa)
    {}
    int main()
    {f1(A(2));//构造+拷贝构造->构造
    }
    image-20231118230855193
    void f1(A aa)
    {}
    int main()
    {f1(3);//构造(隐式类型转换)+拷贝->构造
    }
    
    image-20231118231030261
  • 拷贝构造函数+拷贝构造函数->拷贝构造函数

    A f2()
    {A aa(1);return aa;//返回时会调用拷贝构造函数
    }
    int main()
    {A a = f2();//拷贝构造+拷贝构造->拷贝构造return 0;
    }
    
    image-20231118231234281 aa拷贝给临时变量,临时变量拷贝给a优化为aa拷贝给a

注意:若编译器太新或在release版本下对构造函数的优化可能更极端,可以跨表达式进行合并优化.


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

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

相关文章

[CISCN 2023 初赛]ezbyte

从字符串找到%100s&#xff0c;发现下面有个yes 跟踪yes 、 yes之前有个jmp 看上面的代码&#xff0c;要想跳转到含有yes这一块&#xff0c;需要r13等于r12 xor r13&#xff0c;r13说明r13是0&#xff0c;但是找不到r12的操作代码 实际着这个关键的操作r12的加密逻辑&…

Android 弹出自定义对话框

Android在任意Activity界面弹出一个自定义的对话框&#xff0c;效果如下图所示: 准备一张小图片&#xff0c;右上角的小X图标64*64&#xff0c;close_icon.png&#xff0c;随便找个小图片代替&#xff1b; 第一步&#xff1a;样式添加&#xff0c;注意&#xff1a;默认在value…

基于SSM的在线投稿系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

RAAGR2-Net:一种使用多个空间帧的并行处理的脑肿瘤分割网络

RAAGR2-Net: A brain tumor segmentation network using parallel processing of multiple spatial frames RAAGR2-Net&#xff1a;一种使用多个空间帧的并行处理的脑肿瘤分割网络背景贡献实验N4 bias-field-correction 数据预处理Z-score and re-sampling Z-score归一化&#…

快速入门:构建您的第一个 .NET Aspire 应用程序

##前言 云原生应用程序通常需要连接到各种服务&#xff0c;例如数据库、存储和缓存解决方案、消息传递提供商或其他 Web 服务。.NET Aspire 旨在简化这些类型服务之间的连接和配置。在本快速入门中&#xff0c;您将了解如何创建 .NET Aspire Starter 应用程序模板解决方案。 …

Ubuntu 22.04安装Rust编译环境并且测试

我参考的博客是《Rust使用国内Crates 源、 rustup源 |字节跳动新的 Rust 镜像源以及安装rust》 lsb_release -r看到操作系统版本是22.04,uname -r看到内核版本是uname -r。 sudo apt install -y gcc先安装gcc&#xff0c;要是结果给我的一样的话&#xff0c;那么就是安装好了…

NPM 与 XUI 共存!Nginx Proxy Manager 搭配 X-UI 实现 Vless+WS+TLS 教程!

之前分享过搭建可以与宝塔共存的一个 “魔法” 服务器状态监控应用 ——xui&#xff0c;支持 VmessWSTLS。 最近 Docker 视频出的比较多&#xff0c;前阵子又出现了宝塔国内版存在隐私泄露的问题&#xff0c;很多小伙伴其实都不用宝塔了&#xff0c;那么&#xff0c;在我们现在…

基于django的在线教育系统

基于python的在线教育系统 摘要 基于Django的在线教育系统是一种利用Django框架开发的现代化教育平台。该系统旨在提供高效、灵活、易用的在线学习体验&#xff0c;满足学生、教师和管理员的需求。系统包括学生管理、课程管理、教师管理、视频课程、在线测验等核心功能。系统采…

python趣味编程-5分钟实现一个打字速度测试(含源码、步骤讲解)

Python速度打字测试是用 Python 编程语言编写的,速度打字测试 Python项目理念,我们将构建一个令人兴奋的项目,通过它您可以 检查 甚至 提高 您的打字速度。 为了创建图形用户界面(GUI),我们将使用 用于处理图形的pygame库。 Python 打字速度测试有利于学生或初学者提高…

python中的NumPy和Pandas往往都是同时使用,NumPy和Pandas的在数据分析中的联合使用

文章目录 前言一、numpy的介绍与用法二、pandas的介绍与用法三、numpy与pandas的联合使用说明四、numpy与pandas的联合使用程序代码4.1 读取CSV文件并进行数据清洗&#xff0c;如去除NaN值4.2 矩阵操作和特征工程&#xff0c;如标准化处理4.3 使用Pandas进行数据筛选和分组聚合…

Android resource/drawable转换成Uri,Kotlin

Android resource/drawable转换成Uri&#xff0c;Kotlin private fun convertResource2Uri(resId: Int): Uri {return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE "://" resources.getResourcePackageName(resId) / resources.getResourceTypeName(resI…

golang学习笔记——接口

文章目录 Go 语言接口例子空接口空接口的定义空接口的应用空接口作为函数的参数空接口作为map的值 类型断言接口值 类型断言例子001类型断言例子002 Go 语言接口 接口&#xff08;interface&#xff09;定义了一个对象的行为规范&#xff0c;只定义规范不实现&#xff0c;由具…