C++:新枚举与新结构

一、枚举

(一)C枚举?真整数!

考虑下面的程序

cpp 复制代码
#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;

}

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

(二)改进C枚举

1、更严格的类型检查

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

比如

cpp 复制代码
#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++ 中的枚举类型默认为,但是允许设定其它整型类型,这为编程带来了极大的便利性。

cpp 复制代码
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语言一样引用枚举值,也可以通过枚举的作用域

cpp 复制代码
printColor(RED);  // C style enum
printColor(Color::BLUE);  // C++11 scoped enum

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

cpp 复制代码
enum class Color { RED , GREEN, BLUE };

在上面,仅仅定义的枚举类型称之为

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

二、结构体

同样考虑下面的程序

cpp 复制代码
#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;

}

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

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

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

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

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

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

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

cpp 复制代码
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)相关的操作直接与结构体绑定在一起。

cpp 复制代码
#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++自动为我们默认提供了这个参数,我们可以通过名为的指针,这个指针指向本身,比如

cpp 复制代码
#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指针,这对于栈的相关操作十分关键,但是外部可以轻易改变。

cpp 复制代码
typedef struct {
    int data[MAX_SIZE];
    int top;
} Stack;

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

cpp 复制代码
// 判断栈是否为空
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 只能在结构体中访问
cpp 复制代码
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;

};

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

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

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

cpp 复制代码
#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.

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

非用户提供的,比如

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

这时候,就可以了

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

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

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

2、成员初始化列表

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

cpp 复制代码
Student(): name( "jack" ), age( 0 ), gpa( 0.0 ) {};

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

,也就是赋予默认值,如果有值,进行直接初始化


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

相关推荐
ragnwang3 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
lqqjuly6 小时前
特殊的“Undefined Reference xxx“编译错误
c语言·c++
冰红茶兑滴水7 小时前
云备份项目--工具类编写
linux·c++
刘好念7 小时前
[OpenGL]使用 Compute Shader 实现矩阵点乘
c++·计算机图形学·opengl·glsl
酒鬼猿7 小时前
C++进阶(二)--面向对象--继承
java·开发语言·c++
姚先生978 小时前
LeetCode 209. 长度最小的子数组 (C++实现)
c++·算法·leetcode
小王爱吃月亮糖9 小时前
QT开发【常用控件1】-Layouts & Spacers
开发语言·前端·c++·qt·visual studio
aworkholic9 小时前
opencv sdk for java中提示无stiching模块接口的问题
java·c++·opencv·jni·opencv4android·stiching
程序员老冯头9 小时前
第十六章 C++ 字符串
开发语言·c++
Xenia2239 小时前
复习篇~第二章程序设计基础
c++·算法