C++入门☞关于类的一些特殊知识点

涉及的关于类中的默认成员函数的知识点可以看我的这篇博客哦~

C++入门必须知道的知识☞类的默认成员函数,一文讲透+运用

目录

初始化列表

类型转换

static成员 

友元

内部类

匿名对象

 对象拷贝时的一些编译器的优化


初始化列表

我们知道类中的构造函数的任务是完成对象的初始化,使用构造函数完成初始化的方式除了在函数体内对成员变量进行赋值的方式,如下:

class Date
{
public:// 构造函数Date(int year, int month, int day){// 函数体内赋值初始化_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

还有一种方式可以进行初始化——初始化列表

初始化列表的格式:

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

比如上面的构造函数就可以写成下面的样子: 

class Date
{
public:// 初始化列表的形式Date(int year,int month,int day):_year(year),_month(month),_day(day){}
private:int _yeaer;int _month;int _day;
};

两种初始化的区别:

使用函数体内赋值的方式时,成员变量会先进行默认初始化,然后再在构造函数体内被赋值。也就是在函数体内被赋值时不能称为"初始化",只能称为"赋值",初始化只能进行一次,而构造函数体内可以多次赋值;

如果用初始化列表,则直接跳过默认初始化步骤,一步到位完成初始化

默认初始化:指当对象被创建时,如果没有显式指定初始值,编译器会自动进行的初始化行为。具体行为取决于成员变量的类型。具体行为如下:

  1. 内置类型(如 intdouble, 指针等)

    • 如果变量在全局作用域(或静态存储区),默认初始化为 0/nullptr

    • 如果变量在局部作用域(如函数内、类构造函数体内),不初始化,值是未定义的(垃圾值)。

  2. 类类型(如 std::string, 自定义类)

    • 调用该类的默认构造函数(如果没有默认构造函数,会编译报错)。

两种方式在面对自定义类型的初始化时,还会有效率的差异,初始化列表的效率更高:

使用函数体内赋值的方式,会先去调用自定义类型的构造函数,然后再调用赋值重载将构造的值赋值给成员变量

使用初始化列表的方式,对于自定义类型只会调用一次拷贝构造函数一次性完成初始化

初始化列表的注意事项:

1、每个成员变量在初始化列表中只能出现一次(即初始化只能初始化一次) 

2、类中包含以下成员时,它们的初始化必须放在初始化列表位置进行(否则编译报错)

  • 引用成员变量(因为引用在定义初始化时必须赋值,且不能更改引用的对象)
  • const修饰的成员变量(const的原因与引用类似)
  • 自定义类型的成员变量,且该成员变量的类没有默认构造函数时(因为使用函数体内的方式时,对于自定义类型,会先去调用其构造函数)

3、成员变量在类中的声明顺序就是在初始化列表的初始化顺序,与其在初始化列表中的位置顺序无关(★),所以建议初始化列表的顺序和声明的顺序一致

关于第三条:如下述代码,会得到错误的结果,因为虽然初始化列表中的顺序是先初始化_a,再用_a的值初始化_b,看着是没错,但是因为成员变量声明时,是先声明的_b,再是_a,所以初始化时会先初始化_b,而不是_a,也就是初始化顺序只和声明的顺序相关,和初始化列表中的顺序无关,但是因为_a还未初始化,所以_b的值就会是随机数

class A
{
public:// 初始化列表A(int a):_a(a),_b(_a+1){}
private:int _b;int _a;
};

另外,C++11还支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的,如:

class A
{
public:A(int a):_a1(a){}
private:int _a2 = 2;int _a1;
};

此时,_a2初始化后的值就是2,相当于_a2也走了初始化列表,只不过用的声明时给的缺省值进行初始化,_a1初始化后的值就是传给参数a的值 

总结:

尽量使用初始化列表初始化,因为无论是否显示写初始化列表,每个构造函数都有初始化列表;⽆论是否在初始化列表显示初始化,每个成员变量都要走初始化列表初始化,如果这个成员在声明位置给了缺省值,初始化列表就会用这个值进行初始化。如果你没有给缺省值,那么编译器对其的初始化是不确定的,且对于自定义类型,使用初始化列表的方式会更加高效一点

 成员变量初始化思维导图:

类型转换

C++支持内置类型隐式类型转换为类类型的对象,需要有相关内置类型为参数的构造函数

如下:使用一个int类型的值构造了一个A类型的对象,其中发生了隐式类型转换,将int类型转为自定义类型A

class A
{
public:// 以int类型为参数的相关构造函数A(int a):_a(a){}
private:int _a;
};
int main()
{// 使用int类型的1构造了一个A类型的对象A b(1);return 0;
}

explicit关键字 

但是上述的隐式类型转换,如果你不想让它发生可以使用explicit关键字对构造函数进行修饰,那么这种隐式类型就会被禁止

// 此时再想用一个数字去构造A类型的对象就会编译报错
class A
{
public:// 以int类型为参数的相关构造函数explicit A(int a):_a(a){}
private:int _a;
};

对于内置类型隐式类型转换构造自定义类型时,相关内置类型的构造函数必须要有,且需要保证传入该内置类型参数时,可以构造出一个对象

除了内置类型类类型的隐式类型转换,类类型的对象之间也可以隐式转换,需要相应的构造函数的支持

如下方的将A类型的对象隐式类型转换为B类型的对象进行构造,构造出一个临时对象用来拷贝构造B类型的对象b,但是编译器会优化,所以就变成了直接用A类型的对象构造出了一个B类型的对象,但这个过程是因为有相关类型参数的构造函数支持才完成的

class A
{ 
public:A(int a1, int a2):_a1(a1), _a2(a2){}int Get() const{return _a1 + _a2;}
private:int _a1 = 1;int _a2 = 2;
};
class B
{ 
public:// 临时对象具有常性,所以需要用const接收B(const A& a):_b(a.Get()){}
private:int _b = 0;
};
int main()
{// { 2,2 }构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造a// 编译器遇到连续构造+拷⻉构造->优化为直接构造A a = { 2,2 };   // C++11之后才⽀持的多参数转化// a 隐式类型转换为b对象// 原理跟上⾯类似B b = a;return 0;
}

static成员

 定义:

被static修饰的成员称为static成员,也叫类的静态成员

  • 静态成员也是类的成员,受public、protected、private访问限定符的限制
  • 突破类域可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数

由static修饰的成员变量称为静态成员变量,由static修饰的成员函数称为静态成员函数。

静态成员变量: 

静态成员变量一定要类外进行初始化,即不在构造函数初始化

静态成员变量为类的所有对象共享,不属于某个具体的对象,不存在对象中,存放在静态区

静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表初始化的,静态成员变量不属于某个对象,不走构造函数的初始化列表

静态成员函数: 

静态成员函数没有this指针

静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针

非静态成员函数可以访问任意的静态成员变量和静态成员函数

场景:统计一个类实例化了多少个对象——使用静态成员变量

#include<iostream>
using namespace std;
class A
{
public:A(){count++;}static int getCount(){return count;}
private:static int count;
};
int A::count = 0;
int main()
{A a1;A a2;A a3;cout << "创建了"<< A::getCount() << "个A类型对象" << endl;return 0;
}

在A类型的构造函数中使用静态成员变量count++进行计数,则每初始化一个对象就会对其进行一次++,最终就会得到实例化出的对象个数,同时注意count变量是在类外进行初始化的,只是在类内声明,且在访问静态成员时都使用了类域::静态成员变量的方式(A::count、A::getCount)

上方代码,如果是非静态的成员函数getCount()的话,就通过对象.静态成员函数的方式获得静态成员变量count的值

// 非静态成员函数
int getCount()
{return count;// 返回静态成员变量
}
// 其余代码一致....
int main()
{A a1;A a2;A a3;cout << "创建了"<< a3.getCount() << "个A类型对象" << endl;return 0;
}

友元

定义:

友元分为友元函数和友元类,在一个类里,给函数声明或者类的声明前面加上friend的修饰,则称其为类的友元函数或者友元类,成为类的友元后可以访问类中的私有成员和保护成员

友元类:
class A
{// 友元声明,B这个类变成A的友元类friend class B;
private:int _a1 = 1;
};class B
{
public:void func(const A& aa){// 友元类B就可以访问A类的私有成员cout << aa._a1 << endl;cout << _b1 << endl;}
private:int _b1 = 3;
};
int main()
{A aa;B bb;bb.func(aa);return 0;
}

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

  • 友元关系是单向的,不具有交换性。(比如上面的B类可以访问A类的私有成员,但是A类就不可以反过来访问B类的非公有成员,因为A类不是B类的友元类)
  • 友元关系不能传递(如果C是B的友元, B是A的友元,则不能说明C时A的友元)
  • 友元关系不能继承(涉及继承知识)
友元函数:

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

class Date
{// 友元函数声明friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
// 自定义Date类的输入输出
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}

1、友元函数可以在类定义的任意地方声明(注意:是在类定义的地方声明,类的定义有声明和定义都在一个文件中实现的方式,也有声明定义分离的方式,如果是分离的方式,要在定义的地方声明友元函数),且它不受任何访问限定符的限制

2、一个函数可以是多个类的友元函数

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


内部类

定义:

如果一个类定义另一个类的内部,这个内部的类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:

  • 内部类默认就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员但是外部类不是内部类的友元
  • 内部类可以访问的外部的所有成员中包括外部类的静态成员,不需要外部类的对象/类名来突破类域访问
  • sizeof(外部类)=外部类,和内部类没有任何关系。

内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要是给B类使用时,可以考虑将其设为B类的内部类, 如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。


匿名对象

定义:

类型(实参) 定义出来的对象叫做匿名对象,前面我们使用 类型 对象名(实参) 格式定义出来的叫有名对象

// 假设有一个名为A的类,且可以用int类型隐式转换初始化
int main()
{A aa1(1);// 有名对象A(1);// 匿名对象
}

如果A类的构造是可以无参的,那么A类的匿名对象的创建也不可以省略掉括号,否则语法出错

A();// 无参的A类型匿名对象

注意:匿名对象的生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以使用匿名对象

 匿名对象调用场景:

 ①成员函数调用:

class Solution {
public:int Sum_Solution(int n) {//...return n;}
};
int main()
{// 没有匿名对象时候,需要写两行调用Solution s1;cout << s1.Sum_Solution(10) << endl;// 有了匿名对象直接写成一行调用cout << Solution().Sum_Solution(10) << endl;
}

 ②函数参数自定义类型,需要给参数缺省值:给匿名对象

void func(A aa = A(1))
{}

 延长匿名对象生命周期:

 引用匿名对象,延长到和引用的生命周期一致,

 但是匿名对象和临时对象一样具有常性,要加const

const A& r = A();

匿名对象就像一个一次性的事物,用完就可丢~


 对象拷贝时的一些编译器的优化

现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷贝

假设一个类的定义如下:

class A
{
public:// 构造函数A(int a = 0):_a1(a){cout << "A(int a)" << endl;}// 拷贝构造A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}// 赋值重载A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a1 = aa._a1;}return *this;}// 析构函数~A(){cout << "~A()" << endl;}private:int _a1 = 1;
};
传参时的编译器优化:

连续构造+拷贝构造优化——>直接构造,省略了一次拷贝

void f1(A aa)
{}
int main()
{// 优化// 按理这里会先构造临时对象,再把临时对象拷贝构造给aa0// 但是这里优化成直接用1进行构造了A aa0 = 1;// 隐式类型转换cout << endl;// 优化// 按理是用1构造一个匿名对象,再把匿名对象拷贝构造给aa// 隐式类型转换,连续构造+拷贝构造->优化为直接构造f1(1);// 优化// 一个表达式中,连续构造+拷贝构造->优化为一个构造f1(A(2));cout << endl;return 0;
}
传值返回时的编译器优化:

两次连续的拷贝构造优化成一次拷贝构造

A f2()
{A aa;return aa;// 不会返回aa,会用aa创建一个临时对象,返回临时对象
}
// 传值返回的优化
int main()
{// 按理需要先用返回值aa拷贝构造一个临时对象,再用临时对象拷贝构造aa2// 优化:两个拷贝构造合二为一为一个拷贝构造// 更新的编译器优化:直接优化成一次构造,直接用aa的值构造aa2A aa2 = f2();cout << endl;// 按理是返回值aa拷贝构造一个临时对象,再用临时对象赋值拷贝给aa1// 优化:直接用aa的值赋值拷贝给aa1A aa1 = 1;aa1 = f2();cout << endl;return 0;
}

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

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

相关文章

只用Prettier进行格式化项目

1.下载Prettier插件&#xff0c;禁用ESlint 2.在项目根目录新建.prettierrc文件 {"singleQuote": true,"jsxSingleQuote": true,"printWidth": 100,"trailingComma": "none","tabWidth": 2,"semi": f…

XXL-TOOL v1.4.0 发布 | Java工具类库

Release Notes 1、【新增】JsonRpc模块&#xff1a;一个轻量级、跨语言远程过程调用实现&#xff0c;基于json、http实现&#xff08;从XXL-JOB底层通讯组件提炼抽象&#xff09;。2、【新增】Concurrent模块&#xff1a;一系列并发编程工具&#xff0c;具备良好的线程安全、高…

基于LVGL的登录界面设计

目录 一、演示 二、前言 三、部件知识 3.1 图片按钮部件 3.1.1 图片按钮部件的组成 3.1.2 图片的来源 3.1.3 添加/清除的状态 3.1.4 图片按钮部件 API 函数 3.2 键盘部件(lv_keyboard) 3.2.1 键盘部件的组成 3.2.2 键盘部件的相关知识 3.2.2.1 键盘部件模式 3.…

S3 跨账户复制:增强云中的灾难恢复计划

您准备好提升您的云和 DevOps 技能了吗&#xff1f; &#x1f425;《云原生devops》专门为您打造&#xff0c;我们精心打造的 30 篇文章库&#xff0c;这些文章涵盖了 Azure、AWS 和 DevOps 方法论的众多重要主题。无论您是希望精进专业知识的资深专业人士&#xff0c;还是渴望…

线程与进程深度解析:从fork行为到生产者-消费者模型

线程与进程深度解析&#xff1a;从fork行为到生产者-消费者模型 一、多线程环境下的fork行为与线程安全 1. 多线程程序中fork的特殊性 核心问题&#xff1a;fork后子进程的线程模型 当多线程程序中的某个线程调用fork时&#xff1a; 子进程仅包含调用fork的线程&#xff1…

Circular Plot系列(五): circle plot展示单细胞互作

这是我们circle系列的最后一节&#xff0c;我想常见的弦图是绕不开的&#xff0c;所以最后从前面介绍的circle plot思路&#xff0c;做一遍弦图。其实前面的内容如果消化了&#xff0c;plot互作弦图也就不成什么问题了。 效果如下&#xff1a; #cellchat提取互作结果&#xff…

(11)Vue-Router路由的详细使用

本系列教程目录&#xff1a;Vue3Element Plus全套学习笔记-目录大纲 文章目录 第2章 路由 Vue-Router2.1 Vue路由快速入门2.1.1 创建项目2.1.2 路由运行流程 2.2 传递参数-useRoute2.2.1 路径参数-params1&#xff09;普通传参2&#xff09;传递多个参数3&#xff09;对象方式传…

react + antd 实现后台管理系统

文章目录 完整路由搭建Layout 和 Aside组件引入 AntdAside组件实现 项目效果图 项目完整代码地址 https://gitee.com/lyh1999/react-back-management 项目完整代码地址 react依赖安装 最好采用yarn 安装 react-router 安装依赖 配置路由 history模式 / // src/router/…

基于AWS Marketplace的快速解决方案:从选型到部署实战

1. 引言&#xff1a;为什么选择AWS Marketplace&#xff1f; 在数字化转型的背景下&#xff0c;企业需要快速获取成熟的软件工具和服务以降低开发成本。AWS Marketplace 作为亚马逊云科技的官方应用商店&#xff0c;提供超过万款预配置的第三方和AWS原生解决方案&#xff0c;涵…

2021年第十二届蓝桥杯省赛B组C++题解

2021年第十二届蓝桥杯省赛B组C题解 关键词&#xff1a;蓝桥杯、省赛、题解、C、算法 一、个人见解 第十二届蓝桥杯省赛B组共有10道题目&#xff0c;包含5道填空题&#xff08;T1-T5&#xff09;和5道编程题&#xff08;T6-T10&#xff09;&#xff0c;总分150分。比赛时长4小…

日语学习-日语知识点小记-进阶-JLPT-N1阶段(1):语法单词

日语学习-日语知识点小记-进阶-JLPT-N1阶段&#xff08;1&#xff09;&#xff1a;语法单词 1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰&#xff08;3&#xff09;高级语法N1语法和难点一、N1语法学习内容&#xff08;高级语法&#xff…

Python|Pyppeteer实现自动登录小红书(32)

前言 本文是该专栏的第32篇,结合优质项目案例持续分享Pyppeteer的干货知识,记得关注。 本文中,笔者以小红书为例,基于Pyppeteer实现自动登录“小红书”。 需要注意的是,对Pyppeteer不太熟悉的同学,可往前翻阅本专栏前面介绍的Pyppeteer知识点,本专栏将带你了解并熟练使…

【翻译、转载】【转载】LLM 的函数调用与 MCP

来源&#xff1a; https://www.dailydoseofds.com/p/function-calling-mcp-for-llms/ 【代码以图像显示的是原文内容&#xff0c;以代码形式显示的是大模型给出的参考】 LLM 的函数调用与 MCP 在 MCP 变得像现在这样主流&#xff08;或流行&#xff09;之前&#xff0c;大多…

【QT】QT中http协议和json数据的解析-http获取天气预报

QT中http协议和json数据的解析 1.http协议的原理2.QT中http协议的通信流程2.1 方法步骤 3.使用http协议&#xff08;通过http下载图片和获取天气预报信息&#xff09;3.1 http下载网络上的图片(下载小文件)3.1.1 示例代码3.1.2 现象 3.2 获取网络上天气预报3.2.1 免费的天气预报…

hot100:链表倒数k个节点- 力扣(LeetCode)

题目&#xff1a; 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该链表中倒数第k个节点。 示例一&#xff1a; 输入&#xff1a;{1,2,3,4,5},2 返回值&#xff1a;{4,5} 说明&#xff1a;返回倒数第2个节点4&#xff0c;系统会打印后面所有的节点来比较。 …

Spring AI 实战:第十一章、Spring AI Agent之知行合一

引言:智能体的知行辩证法 “知为行之始,行为知之成”,王阳明的哲学智慧在AI时代焕发光彩。智能体(LLM Agent)的进化之路,正是"认知-决策-执行"这一闭环的完美诠释: 知明理:融合大语言模型的推理能力与知识图谱的结构化认知行致用:基于ReAct模式的动态工具调…

365打卡第R6周: LSTM实现糖尿病探索与预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 &#x1f3e1; 我的环境&#xff1a; 语言环境&#xff1a;Python3.10 编译器&#xff1a;Jupyter Lab 深度学习环境&#xff1a;torch2.5.1 torchvision0…

W-TinyLFU缓存驱逐算法解析

文章目录 1. 背景与概述1.1 什么是缓存驱逐算法1.2 W-TinyLFU 的定义与价值 2. 核心思想与设计理念2.1 时间局部性与频率局部性的结合2.2 高效的频率统计2.3 窗口机制的引入 3. 架构设计与组件3.1 整体架构3.2 窗口缓存&#xff08;Window Cache&#xff09;3.3 主缓存&#xf…

[特殊字符] 人工智能大模型之开源大语言模型汇总(国内外开源项目模型汇总) [特殊字符]

Large Language Model (LLM) 即大规模语言模型&#xff0c;是一种基于深度学习的自然语言处理模型&#xff0c;它能够学习到自然语言的语法和语义&#xff0c;从而可以生成人类可读的文本。 所谓 "语言模型"&#xff0c;就是只用来处理语言文字&#xff08;或者符号…

文章记单词 | 第60篇(六级)

一&#xff0c;单词释义 liar&#xff1a;英 [ˈlaɪə(r)]&#xff1b;美 [ˈlaɪər]&#xff1b;n. 说谎者verbal&#xff1a;英 [ˈvɜːbl]&#xff1b;美 [ˈvɜːrbl]&#xff1b;adj. 言语的&#xff1b;文字的&#xff1b;口头的&#xff1b;动词的comprehension&…