C++:新枚举与新结构

news2024/12/28 10:31:40

一、枚举

(一)C枚举?真整数!

        考虑下面的程序

#include <stdio.h>
#include <stdlib.h>

typedef enum  {spring, summer, autumn, winter} Season;

void printSeason(Season season){
	
	switch(season){
		case spring:
			printf("spring");
			break;
		case summer:
			printf("summer");
			break;
		case autmn:
			printf("autmn");
			break;
		case winter:
			printf("winter");
			break;
		default:
			printf("Not Season");
	}
}

int main() {

	printSeason(0);
	
	return 0;

}

        因为spring就相当于0,所以完全没问题,可是这完全不符合语义,并且,如果仔细看,我写错了一个季节,这样我还不如写数字,另外,如果我想得到枚举的字面字符串,我必须还得像这样打印,这些在C++必须有所改变。

(二)改进C枚举

1、更严格的类型检查

        在 C++ 中,枚举类型(enum class)引入了更严格的类型检查机制,与 C 语言的枚举相比,这是一个显著的改进。在 C 语言中,枚举值可以被隐式地转换为整数,这可能导致意外的类型错误。而 C++ 的枚举类则避免了这种情况,它不会自动转换为整数类型,只有通过显式的类型转换才能进行转换。这种严格的类型检查增强了代码的安全性,减少了由于类型不匹配而导致的错误。

        比如

#include <iostream>

enum Color { RED, GREEN, BLUE };

void printColor(Color color) {
    switch (color) {
        case RED:
            std::cout << "Red" << std::endl;
            break;
        case GREEN:
            std::cout << "Green" << std::endl;
            break;
        case BLUE:
            std::cout << "Blue" << std::endl;
            break;
        default:
            std::cout << "Unknown color" << std::endl;
            break;
    }
}

int main() {


    printColor(0);
    return 0;

}

2、更灵活的枚举类型设定

        C++ 中的枚举类型默认为int,但是允许设定其它整型类型,这为编程带来了极大的便利性。

enum Season: unsigned char { SPRING = 'S', SUMMER = 'M', AUTUMN = 'A', WINTER = 'W' };
enum Size: unsigned int { SMALL = 1, MEDIUM = 2, LARGE = 3 };

3、增强的作用域控制

        C++ 枚举具有明确的作用域规则,这有效地避免了命名冲突。在 C 语言中,枚举值是全局可见的,可能与其他标识符发生冲突。但在 C++ 的枚举中,枚举值的作用域可以被限制在枚举类内部。

        正常情况下,可以像C语言一样引用枚举值,也可以通过枚举的作用域

printColor(RED);  // C style enum
printColor(Color::BLUE);  // C++11 scoped enum

        如果想要禁止直接引用枚举值,可以使用枚举类,像是

enum class Color { RED , GREEN, BLUE };

 

        在上面,仅仅enum \ name定义的枚举类型称之为unscoped \ enumeration \ type

(非作用域枚举类型),与之相反的是下面的在enum关键字后面加上了class、也可加struct(与class等价),称之为scoped \ enumeration \ type(作用域枚举类型)。

        

二、结构体

        同样考虑下面的程序        

#include <stdio.h>

struct Student {

    char * name;
    int age;
    float gpa;

};

void printStudent(struct  Student s) {

    printf("Name: %s \n", s.name );
    printf("Age: %d \n", s.age);
    printf("GPA: %d \n",s.gpa );

}

int main() {
    
    struct Student s1  = { .name = "John",.age = 20,.gpa = 3.5 };

    printStudent(s1);

    return 0;

}

        定义了一个Student结构体,还设计了一个操作函数,这两者应当是一体的,但是这仅仅是语义上,在代码层面上,这两者并没有太大关系,最多依赖关系 ,我们必须手动处理它们之间的关系,在使用C语言设计数据结构的过程中,这点尤其明显,倘若,结构体本身变了,那么所有关联的配套函数可能都要改变。

        另一方面,我们设计不了结构体的默认值,它们只能是单纯的基础类型默认值,倘若,我们想要名字默认张三,这点我们做不到。

        其次,struct关键字很突兀,一不小心就忘写了,必须使用typedef才能不写,但也不一定。

        再者,像是数据结构中,我们一般都在堆内存申请空间,我们必须要手动管理对应的堆空间。

        这些都显得C语言的结构体有点笨重。

(一)C++结构体是新类型

        在 C++ 中,结构体被明确视为一种新的类型。这与 C 语言存在显著差异。在 C 语言中,结构体更多地被看作是一组数据的集合,而在 C++ 里,结构体具有了更独立和明确的类型特征。这意味着在 C++ 中,可以像使用内置类型一样直接定义结构体变量,无需再使用struct关键字。

struct Student {

    char name[50];
    int age;
    float gpa;

};

void printStudent(Student s) {

    std::cout << "Name: " << s.name << std::endl;
    std::cout << "Age: " << s.age << std::endl;
    std::cout << "GPA: " << s.gpa << std::endl;

}

(二)成员函数的添加

        C++ 中的结构体可以包含函数(称之为成员函数member function),这大大增强了结构体的功能性。通过在结构体内部定义成员函数,可以将与数据(结构体内,函数外定义的变量,称之为数据成员data member)相关的操作直接与结构体绑定在一起。

#include <iostream>

struct Student {

    char name[50];
    int age;
    float gpa;
    void printStudent(Student s) {

        std::cout << "Name: " << s.name << std::endl;
        std::cout << "Age: " << s.age << std::endl;
        std::cout << "GPA: " << s.gpa << std::endl;

    }

};



int main() {

    Student s1  = { .name = "John",.age = 20,.gpa = 3.5 };

    s1.printStudent(s1);

    return 0;

}

        成员函数能够直接访问结构体的成员变量,使得数据的处理更加紧密和高效。这使得结构体不仅仅是数据的容器,还具备了一定的行为能力,更符合面向对象编程的思想。

(三)隐含指针:this

        一般而言,以C语言实现下的数据结构为例,配套的函数都有一个参数是结构体,这点在C语言中很合理,这算是一种手动联系,但是在C++中,既然函数都放进结构体中了,那么还需要手动联系吗?就比如上面的s1.printStudent(s1);就很突兀,所以,C++自动为我们默认提供了这个参数,我们可以通过名为this的指针,这个指针指向本身,比如

#include <iostream>

struct Student {

    char name[50];
    int age;
    float gpa;
    void printStudent() {

        std::cout << "Name: " << this->name << std::endl;
        std::cout << "Age: " << this->age << std::endl;
        std::cout << "GPA: " << this->gpa << std::endl;

    }

};



int main() {

    Student s1  = { .name = "John",.age = 20,.gpa = 3.5 };

    s1.printStudent();

    return 0;

}

(四)为了安全:访问控制

        C语言数据结构中一般存在一些结构体,都有一个成员用于记录某些状态,比如栈的top指针,这对于栈的相关操作十分关键,但是外部可以轻易改变。

typedef struct {
    int data[MAX_SIZE];
    int top;
} Stack;

        我所见过多数的C语言栈的数据结构实现,将对top的检验抛之事外,这相当危险 

// 判断栈是否为空
int isEmpty(Stack* stack) {
    return stack->top == -1;
}

// 判断栈是否已满
int isFull(Stack* stack) {
    return stack->top == MAX_SIZE - 1;
}
// 入栈
void push(Stack* stack, int value) {
    if (isFull(stack)) {
        printf("Stack is full.\n");
        return;
    }
    
    stack->top++;
    stack->data[stack->top] = value;
}

         将内存安全依赖于自觉性,希冀一切都是正常,是不合理的。当然这也可以在配套函数中检验,但是在某些情况,你无法检验一切,或者说你无法完全不相信所有,你需要相信某一些而去检验另一些。

        为此,C++的结构体提供有访问控制,使用三个类似于C语言标签的访问修饰符

修饰符访问范围
public默认,在程序任何地方,就像是C语言
protected只允许结构体内部或者子结构体访问
private只能在结构体中访问
struct Student {

private:  // private access specifier , can only be accessed within the struct
    char name[50];
    int age;
    float gpa;
public: // public access specifier , can be accessed anywhere in the program
    Student() {
        name[0] = '\0';
        age = 0;
        gpa = 0.0;
        std::cout << "Default Constructor" << std::endl;
    }
    ~Student() {
        std::cout << "Destructor" << std::endl;
    }
    void printStudent() {

        std::cout << "Name: " << this->name << std::endl;
        std::cout << "Age: " << this->age << std::endl;
        std::cout << "GPA: " << this->gpa << std::endl;

    }
protected: // protected access specifier , can be accessed within the class and its derived classes
        int id;

};

(三)自主能力:构造与析构函数

       构造函数和析构函数是特殊的成员函数,前者用于在创建对象时初始化对象的数据成员,后者用于在对象销毁时释放对象所占用的资源。

        简单来说,定义一个结构体变量时,构造函数被自动调用,当结构体变量消亡,诸如生命周期结束,自动调用析构函数,比如

#include <iostream>

struct Student {

private:  // private access specifier , can only be accessed within the struct
    char name[50];
    int age;
    float gpa;
public: // public access specifier , can be accessed anywhere in the program
    Student() {
        name[0] = '\0';
        age = 0;
        gpa = 0.0;
        std::cout << "Default Constructor" << std::endl;
    }
    ~Student() {
        std::cout << "Destructor" << std::endl;

    }
    void printStudent() {

        std::cout << "Name: " << this->name << std::endl;
        std::cout << "Age: " << this->age << std::endl;
        std::cout << "GPA: " << this->gpa << std::endl;

    }
protected: // protected access specifier , can be accessed within the class and its derived classes
        int id;

};



int main() {

    Student s1 ;

    s1.printStudent();

    return 0;

}

          

1、初始化问题

        无参构造和析构是默认存在的,它们的函数名字固定为前者是结构体名,另一个是~结构体名。

        If the constructor is implicitly-declared  or explicitly default constructor is not defined as deleted, it is defined (that is, a function body is generated and compiled) by the compiler , and it has the same effect as a user-defined constructor with empty body and empty initializer list.

        如果构造函数被隐式声明或显式默认构造函数未定义为已删除,则由编译器定义(即生成并编译一个函数体),它与空主体和空初始化列表的用户定义构造函数具有相同的效果。

        如果显式创建了无参构造函数像上面那样,你就不能

        因为无参构造就是不使用参数构造,那为什么没有显式声明就行呢?这涉及到一些概念,简单来说,这种初始化叫做聚合初始化 (Aggregate initialization),没有等号也是,属于列表初始化的一种形式,前面提过。聚合初始化用于初始化“聚合”类型,如果是“”类型,就像是struct,要想成为“聚合”就不能有三种构造函数

        no user-provided, inherited, or explicit constructors

        用户提供的、继承的或者explicit修饰的构造器

        其中用户提供的,指的是

        A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

        如果一个函数是由用户声明的,并且在首次声明时没有被明确地设置为默认或删除,那么它就是用户提供的函数。

        非用户提供的,比如

Student()  = default; // 设置为默认,和不写一样的效果
//Student() = delete; // 设置为删除,意思是不存在默认构造
  

         这时候,就可以了

        另外需要注意的是,这种初始化,不像是C语言的指定器初始化,这种初始化的初始化顺序必须和声明顺序一致        

        如果不想默认,就自己提供三参的构造函数

        这时候默认构造函数不复存在,也可再次声明,构造函数允许重载。

2、成员初始化列表

        因为构造函数本意就是用来初始化,所以C++提供了更加高效的初始化方案——成员初始化列表

:class-or-identifier ( expression-list (optional) )

Student(): name( "jack" ), age( 0 ), gpa( 0.0 ) {};

        在构造函数的括号后面操作,memeber(value),如果没有值,为空,就会进行值初始化

\textup{ value-initialization },也就是赋予默认值,如果有值,进行直接初始化\textbf{direct-initialization }                 

         


        C++的结构体还具体其它高级特性,这里不一一介绍,好戏还在后头……

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

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

相关文章

【Java学习】实现图书管理系统

所属专栏&#xff1a;Java学习 &#x1f341;1. 功能演示 用户分为普通用户和管理员&#xff0c;登录进系统之后可以对图书进行一系列操作&#xff0c;此时我们要明白&#xff0c;对图书的操作是通过书架来执行的&#xff0c;我们平常在图书馆上借书就是在书架上 &#x1f…

简简单单用用perf

实践前提&#xff1a;正确安装 perf 和 FlameGrap。若没安装&#xff0c;心领神会亦可。 1 示例程序 #define m_loop() ({ for(int i0; i < 1000000; i); })void fb(void) {m_loop(); }void fj(void) {fb(); }void fy(void) {m_loop(); }void loop(void) {for (;;) {fy();…

实习教训,永远铭记

1.毕了业宁宁可收入低一点&#xff0c;但一定去那种开发产品有规章制度&#xff0c;有流程规范的公司。 永远不接受小公司&#xff0c;没有任何规范可言的小公司&#xff01;&#xff01;&#xff01;&#xff01; 永远不接受小公司&#xff0c;没有任何规范可言的小公司&…

Docker手动在虚拟机上部署前端、后端和数据库

&#x1f4a5; 该系列属于【SpringBoot基础】专栏&#xff0c;如您需查看其他SpringBoot相关文章&#xff0c;请您点击左边的连接 目录 一、项目准备 二、MySQL数据库部署 三、SpringBoot后端部署 四、Vue前端部署 一、项目准备 准备数据库、前端项目、后端项目。 准备一…

YOLOv8跑通POSE分类--姿态检测coco8-pos数据集

目录 1.数据集格式如下 2.训练的代码如下 3.训练的网络如下 4.训练的结果如下 简单留个备注&#xff0c;方便自己以后查找 1.数据集格式如下 txt里面的格式 类别 中心点x,y 宽高 姿态1的x,姿态1的y,姿态可见度。。。。 <class-index> <x> <y> <widt…

使用dockerDesktop下载x86,amd64,arm64镜像

开启梯子 修改dockerDesktop配置&#xff0c;将experimental的值设置成 true&#xff0c;意思是&#xff1a;开启manifest实验特性 重启docker后下载镜像 –platform后面就是架构版本&#xff0c;x86,amd64,arm64 C:\Users\dell> docker pull --platformarm64 nginx:late…

Linux--C语言之指针

文章目录 一、指针的引入二、指针概述三、指针变量&#xff08;一&#xff09;指针变量的定义语法&#xff1a;举例&#xff1a;注意&#xff1a; &#xff08;二&#xff09;指针变量的使用1. 指针变量的赋值2. 操作指针变量的值3. 操作指针变量指向的值4. 两个有关运算符的使…

【DA】《Augmentation for small object detection》

arXiv-2019 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method4.1 Oversampling4.2 Augmentation4.3 Copy-Pasting Strategies 5 Experiments5.1 Datasets and Metrics5.2 Oversampling5.3 Augmentation5.4 Copy-Pasting strategies5.4…

Python入门级[ 基础语法 函数... ] 笔记 例题较多

本文是刚学习Python的笔记&#xff0c;当时使用的编辑器是交互式编程&#xff0c;所以很多代码可能在你们的编译器上面不能运行&#xff0c;我用快引用引起来了&#xff0c;还需要大家自己动手试一试。 内容涉及的比较简单&#xff0c;主要还是Python的语法部分&#xff1a;三…

Ubuntu20.04.4.LTS系统如何下载安装VirtualBox虚拟机?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

Solidworks二次开发:变螺距螺旋线

在帮助文档中&#xff0c;找到变螺距螺旋线的VBA代码&#xff0c;如下&#xff1a; --------------------------------------------------------------Preconditions: Verify that the specified part document template exists.Postconditions:1. Creates a variable-pitch h…

Nginx 学习之 配置支持 IPV6 地址

目录 搭建并测试1. 下载 NG 安装包2. 安装编译工具及库文件3. 上传并解压安装包4. 编译5. 安装6. 修改配置7. 启动 NG8. 查看 IP 地址9. 测试 IP 地址9.1. 测试 IPV4 地址9.2. 测试 IPV6 地址 IPV6 测试失败原因1. curl: [globbing] error: bad range specification after pos …

SQL— DDL语句学习【后端 9】

SQL— DDL语句学习 在数据管理的广阔领域中&#xff0c;SQL&#xff08;Structured Query Language&#xff09;作为操作关系型数据库的编程语言&#xff0c;扮演着举足轻重的角色。它不仅定义了操作所有关系型数据库的统一标准&#xff0c;还为我们提供了强大的工具来管理、查…

20240819用SDDiskTool_v1.72写IMG固件到256GB的TF卡后再用它给飞凌OK3588-C核心板刷机

20240819用SDDiskTool_v1.72写IMG固件到256GB的TF卡后再用它给飞凌OK3588-C核心板刷机 2024/8/19 10:35 1、精简的配置HDMI0为主显示屏的步骤&#xff1a; 在串口终端中启动到uboot阶段&#xff0c;按空格进入 显示配置模式。 按 2 进入&#xff1a;2:Display type 按 a 两次…

2、Future与CompletableFuture实战

Future与CompletableFuture实战 Callable与Future与FutureTask介绍Callable详解Future介绍FutureTask使用使用案例&#xff1a;促销活动中商品信息查询 Future的局限性 CompletableFuture使用详解应用场景创建异步操作runAsync&supplyAsync 获取结果join&get 结果处理w…

《亿级流量系统架构设计与实战》第十一章 Timeline Feed服务

Timeline Feed服务 一、概述1、分类2、功能 二、设计原理1、拉模式与用户发件箱2、推模式与用户收件箱3、推拉模式结合 三、关键技术1、内容与用户收件箱的交互&#xff08;推模式&#xff09;2、推送拆分子任务3、收件箱模型设计 内容总结自《亿级流量系统架构设计与实战》 一…

[linux#39][线程] 详解线程的概念

线程&#xff1a;是进程内的一个执行分支。线程的执行粒度比进程要细 什么是线程&#xff1f; • 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程 是“一个进程内部的控制序列” • 一切进程至少都有一个执行线程 • …

使用Virtio Driver实现一个计算阶乘的小程序——QEMU平台

目录 一、概述 二、代码部分 1、Virtio 前端 (1) User Space (2) Kernel Space 2、Virtio 后端 三、运行 QEMU Version&#xff1a;qemu-7.2.0 Linux Version&#xff1a;linux-5.4.239 一、概述 本篇文章的主要内容是使用Virtio前后端数据传输的机制实现一个计算阶乘的…

基于vue框架的爱喵星人服务平台设计与实现80sgi(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,猫食分类,猫粮信息,养护知识,流浪猫信息,申请领养,志愿者招募,申请加入,猫咪品种,团队活动 开题报告内容 基于Vue框架的爱喵星人服务平台设计与实现 开题报告 一、研究背景与意义 1.1 研究背景 随着社会的快速发展和人们生活水…

使用 onBeforeRouteUpdate 组合式函数提升应用的用户体验

title: 使用 onBeforeRouteUpdate 组合式函数提升应用的用户体验 date: 2024/8/15 updated: 2024/8/15 author: cmdragon excerpt: 摘要&#xff1a;本文介绍如何在Nuxt 3开发中使用onBeforeRouteUpdate组合式函数来提升应用用户体验。通过在组件中注册路由更新守卫&#xf…