欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > C++:新枚举与新结构

C++:新枚举与新结构

2024/10/24 23:25:51 来源:https://blog.csdn.net/m0_63684047/article/details/141094776  浏览:    关键词:C++:新枚举与新结构

一、枚举

(一)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 };

 

 

二、结构体

        同样考虑下面的程序        

#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 structchar name[50];int age;float gpa;
public: // public access specifier , can be accessed anywhere in the programStudent() {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 classesint id;};

 

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

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

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

#include <iostream>struct Student {private:  // private access specifier , can only be accessed within the structchar name[50];int age;float gpa;
public: // public access specifier , can be accessed anywhere in the programStudent() {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 classesint 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++的结构体还具体其它高级特性,这里不一一介绍,好戏还在后头……

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com