C++和标准库速成(七)——类、作用域解析、统一初始化和指派初始化

发布时间:2026/9/15 21:48:41

C++和标准库速成(七)——类、作用域解析、统一初始化和指派初始化 目录1. 类1.1 定义类1.2 使用类2. 作用域解析3. 统一初始化(高度建议)4. 指派初始化参考1. 类1.1 定义类类定义了对象的特征。在C中类通常在模块接口文件中定义和被导出然而类的方法定义既可以在相同的模块接口文件中也可以在对应的模块实现文件中。下面定义了一个基本的机票类这个类可根据飞行的里程数以及顾客是不是“精英超级奖励计划”的成员计算票价。这个定义首先声明了一个类名在大括号内声明了类的数据成员以及方法。每个数据成员以及方法都具有特定的访问级别public、protected或private。这些标记可按任意顺序出现也可重复使用。访问级别访问范围public可在类的外部访问private不能在类的外部访问(推荐将所有的数据成员都声明为private在需要时可通过获取器和设置器访问它们)protected之后在讲继承的时候详细讲解当写一个模块接口文件时不要忘记使用export module声明表明你正在写哪个模块同时也不要忘记将那些你希望对模块使用者可用的类型显式导出。exportmoduleairline_ticket;// import string;importstd.core;// MSVC vs18之后需要改为import std;exportclassAirlineTicket{public:AirlineTicket();// constructor~AirlineTicket();// destructordoublecalculatePriceInDollars();std::stringgetPassengerName();voidsetPassengerName(std::string name);intgetNumberOfMiles();voidsetNumberOfMiles(intmiles);boolhasEliteSuperRewardsStatus();voidsetHasEliteSuperRewardsStatus(boolstatus);private:std::string m_passengerName;intm_numberOfMiles;boolm_hasEliteSuperRewardsStatus;};与类同名但没有返回类型的方法是构造函数当创建类的对象时会自动调用构造函数。~之后紧跟类名的方法是析构函数当销毁对象时会自动调用。模型接口文件定义了类然而本例中方法的实现在模块实现文件.cpp中。源文件以如下的模块声明开头告诉编译器这是airline_ticket模板的源文件。module airline_ticket;可通过几种方法初始化数据成员。一种方法是使用构造函数初始化器即在构造函数名称之后加上冒号。示例如下AirlineTicket::AirlineTicket():m_passengerName{Unknown Passenger},m_numberOfMiles{0},m_hasEliteSuperRewardsStatus{false}{}第二种方法是将初始化任务放在构造函数体中示例如下AirlineTicket::AirlineTicket(){// initialize data members.m_passengerNameUnknown Passenger;m_numberOfMiles0;m_hasEliteSuperRewardsStatusfalse;}然而如果构造函数只是初始化数据成员而不做其他事情实际上就没有必要使用构造函数因为可在类定义中直接初始化数据成员也称为类内初始化。例如不编写AirlineTicket构造函数而是直接修改类定义中数据成员的定义示例如下private:std::string m_passengerName{Unknown Passenger};intm_numberOfMiles{0};boolm_hasEliteSuperRewardsStatus{false};如果类还需要执行其他一些初始化类型如打开文件分配内存等则需要编写构造函数进行处理。下面是AirlineTicket类的析构函数AirlineTicket::~AirlineTicket(){// nothong to do in terms of cleanup.}这个析构函数什么都不做因此可从类中删除。如果需要执行一些清理如关闭文件、释放内存等则需要使用析构函数。AirlineTicket类方法的定义如下所示doubleAirlineTicket::calculatePriceInDollars(){if(hasEliteSuperRewardsStatus()){// elite super rewards customers fly for free!return0;}// the cost of the ticket is the number of milies times 0.1.// real airlines probably have a more complicated formula!returngetNumberOfMiles()*0.1;}std::stringAirlineTicket::getPassengerName(){returnm_passengerName;}voidAirlineTicket::setPassengerName(std::string name){m_passengerNamename;}intAirlineTicket::getNumberOfMiles(){returnm_numberOfMiles;}voidAirlineTicket::setNumberOfMiles(intmiles){m_numberOfMilesmiles;}boolAirlineTicket::hasEliteSuperRewardsStatus(){returnm_hasEliteSuperRewardsStatus;}voidAirlineTicket::setHasEliteSuperRewardsStatus(boolstatus){m_hasEliteSuperRewardsStatusstatus;}也可以直接将方法实现直接放在模块接口文件中。示例如下exportclassAirlineTicket{public:doublecalculatePriceInDollars(){if(hasEliteSuperRewardsStatus()){return0;}returngetNumberOfMiles()*0.1;};std::stringgetPassengerName(){returnm_passengerName;}voidsetPassengerName(std::string name){m_passengerNamename;}intgetNumberOfMiles(){returnm_numberOfMiles;}voidsetNumberOfMiles(intmiles){m_numberOfMilesmiles;}boolhasEliteSuperRewardsStatus(){returnm_hasEliteSuperRewardsStatus;}voidsetHasEliteSuperRewardsStatus(boolstatus){m_hasEliteSuperRewardsStatusstatus;}private:boolm_hasEliteSuperRewardsStatus{false};intm_numberOfMiles{0};std::string m_passengerName{Unknown Passenger};};1.2 使用类为了使用AirlineTicket类首先需要导入它的模块import airline_ticket;下面展示了AirlineTicket类的使用。AirlineTicket myTicket;myTicket.setPassengerName(Sherman T. Socketwrench);myTicket.setNumberOfMiles(700);doublecost{myTicket.calculatePriceInDollars()};std::coutstd::format(This ticket will cost ${}\n,cost);2. 作用域解析作为C程序员需要熟悉作用域的概念。程序中的每个名称都在某个作用域中。可以使用名称空间、函数定义、用花括号分隔的块和类定义来创建作用域。当你尝试访问一个变量、函数或类时首先在最近的封闭作用域内查找名称然后在下一个作用域内查找以此类推直到全局作用域。不在名称空间、函数、用花括号分隔的块或类中的任何名称都被视为在全局作用域中。如果在全局作用域内未找到则编译器将提示未定义的符号错误。有时作用域中的名称会覆盖其他作用域中相同的名称。有时你所需的作用域不是程序中某特定行的默认作用域。如果你不希望名称使用默认作用域解析则可以使用作用域解析运算符::限定特定作用域的名称。下面的示例展示了这一点该示例定义了一个Demo类类中有一个get()方法还定义了一个全局作用域下的get()函数以及一个NS名称空间里的get()函数。classDemo{public:intget(){return5;}};intget(){return10;}namespaceNS{intget(){return20;}}全局作用域是未命名的但你可以单独使用作用域解析运算符来专门访问它。可以按以下方式调用不同的get()函数。在此例中代码本身位于main()函数中该函数始终位于全局范围内。intmain(){Demo d;std::coutd.get()\n;// prints 5std::coutNS::get()\n;// prints 20std::cout::get()\n;// prints 10std::coutget()\n;// prints 10}请注意如果将名为NS的名称空间定义为未命名的/匿名的则以下代码将导致有歧义的名称解析的编译错误因为你会有一个定义在全局作用域中的get()以及一个定义在未命名的名称空间中的get()。std::cout get() \n;如果你在main函数之前使用了如下的using命令也会发生同样的错误。using namespace NS;3. 统一初始化(高度建议)在C11之前各类型的初始化并非总是统一的。例如考虑以下两个定义其中一个作为结构体另一个作为类。structCircleStruct{intx,y;doubleradius;};classCircleClass{public:CircleClass(intx,inty,doubleradius):m_x{x},m_y{y},m_radius{radius}{}private:intm_x,m_y;doublem_radius;};在C11之前CircleStruct类型变量和CircleClass类型变量的初始化是不同的。CircleStruct myCircle1{10,10,2.5};CircleClassmyCircle2(10,10,2.5);对于结构体版本可使用{…}语法。然而对于类版本需要使用函数符号(…)调用构造函数。自C11以后允许一律使用{…}语法初始化类型如下所示。CircleStruct myCircle3{10,10,2.5};CircleClass myCircle4{10,10,2.5};定义myCircle4时将自动调用CircleClass的构造函数。甚至等号也是可选的因此下面的代码与前面的代码等价CircleStruct myCircle5{10,10,2.5};CircleClass myCircle6{10,10,2.5};在结构体一节中出现的另一个例子中一个Employee结构用如下方法初始化。Employee anEmployee;anEmployee.firstInitalJ;anEmployee.lastInitalD;anEmployee.employeeNumber42;anEmployee.salary80000;使用统一初始化可以写成这样Employee anEmployee { J, D, 42, 80000 };使用统一初始化并不局限于结构和类它还可用于初始化C中的任何内容。例如下面的代码把所有4个变量都初始化为3。inta3;intb(3);intc{3};// uniform initializationintd{3};// uniform initialization统一初始化还可用对变量进行零初始化只需要指定一对空的大括号。例如int e {}; // uniform initialization, e will be 0.使用统一初始化的一个优点是可以阻止窄化。当使用旧式风格的赋值语法初始化变量时C隐式地执行窄化。例如voidfunc(inti){/* ... */}intmain(){intx3.14;func(3.14);}在main()的两行代码中C在对x赋值或调用func()之前会自动将3.14截断为3。注意有些编译器可能会针对窄化给出警告信息而另一些编译器则不会。在任何情况下窄化转换都不应被忽视因为它们可能会引起细微的错误。使用同一初始化如果编译器完全支持C11标准x的赋值和func()的调用都会生成编译错误。intx{3.14};// error because narrowing.func({3.14});// error because narrowing.如果你需要窄化建议使用准则支持库GSL中提供的gsl::narrow_cast()函数。统一初始化还可用来初始化动态分配的数组int* myArray new int [4] { 0, 1, 2, 3 };从C20开始可以省略数组的大小4像下面这样int* myArray new int[] { 0, 1, 2, 3 };统一初始化还可在构造函数初始化器中初始化类成员数组classMyClass{public:MyClass():m_array{0,1,2,3}{}private:std::arrayint,4m_array;};统一初始化还可用于标准库容器。4. 指派初始化C20引入了指派初始化器以使用它们的名称初始化所谓聚合的数据成员。聚合类型是满足以下限制的数组类型的对象或结构或类的对象仅public数据成员、无用户声明或继承的构造函数、无虚函数和无虚基类、private和protected的基类。指派初始化器以.开头后跟数据成员的名称。指派初始化的顺序必须与数据成员的声明顺序相同。不允许混合使用指派初始化器和非指派初始化器。未使用指派初始化器初始化的任何数据成员都将使用其默认值进行初始化这意味着1. 拥有类内初始化器的数据成员会得到该值。2. 没有类内初始化器的数据成员会被零初始化。下面修改了Employee结构以演示指派初始化。structEmployee{charfirstInitial;charlastInitial;intemployeeNumber;intsalary{75000};};在本节前面这种Employee结构是使用如下的统一初始化语法初始化的Employee anEmployee { J, D, 42, 80000 };使用指派初始化器可以写成这样Employee anEmployee{.firstInitialJ,.lastInitialD,.employNumber42,.salary80000};使用指派初始化器的好处是与统一初始化语法相比它更容易理解指派初始化器正在初始化的内容。使用指派初始化器如果对某些成员的默认值感到满意则可以跳过它们的初始化。例如在创建员工时可以跳过初始化employeeNumber在这种情况下employeeNumber初始化为零因为它没有类内初始化器。Employee anEmployee{.firstInitialJ,.lastInitialD,.salary80000};如果使用统一初始化语法这是不可以的必须像下面这样指定employeeNumber为0Employee anEmployee { J, D, 0, 80000 };如果你像下面这样跳过了初始化salary数据成员它就会得到它的默认值即它的类内初始化值75000。Employee anEmployee{.firstInitialJ,.lastInitialD};使用指派初始化器的最后一个好处是当新成员被增加到数据结构时使用指派初始化器的现有代码将继续起作用。新的数据成员将使用其默认值进行初始化。参考[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C20高级编程(第五版)
延伸阅读

更多相关文章

2026/9/15 21:48:41

图像加密新方案:压缩感知与DNA编码的Python实现

1. 项目概述:当图像加密遇上压缩感知与DNA编码在信息安全领域,图像加密一直是个既基础又关键的课题。传统的AES、DES等加密算法虽然成熟,但面对图像这类具有高冗余度、大数据量的特殊载体时,往往显得笨重且效率不足。三年前我在开…

2026/9/15 21:48:41

C/C++的指针与函数(指针函数与函数指针辨析)

文章目录指针函数语法指针函数的工程应用---malloc与new函数指针函数的地址函数的地址与函数的返回值函数指针的声明使用函数指针的调用函数函数指针数组使用typedef为函数指针取别名函数指针的工程应用---回调函数callback指针函数 指针函数就是指针的函数,是个函…

2026/9/15 21:48:41

C/C++的指针与常量const

指针与常量常规变量的地址赋给const修饰的指针指向常量的指针指针常量指向常量的指针常量总结const修饰的常量的地址赋给const修饰的指针指向常量的指针指向常量的指针常量总结常规变量的地址赋给const修饰的指针 注意,以下三种情况中的例子中的a和b都没有被const修…

2026/9/15 22:28:48

2026大折叠屏选购指南:从能用到该买的分水岭

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/15 22:28:48

DeepSeek Harness通用设置与Agent预设详解:从配置到实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/15 22:23:47

Claude Code 终端 Agent 实操:自主执行测试、排查编译错误并提交 PR

Claude Code 终端 Agent 实操:自主执行测试、排查编译错误并提交 PR随着 AI 辅助开发工具向终端命令行下沉,Claude Code 等终端 Agent 具备了直接感知整个工程上下文、执行任意 Shell 指令、捕获错误输出并自主发起 Git 提交的能力。相较于图形界面内的代…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/15 21:31:11

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码