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 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++的结构体还具体其它高级特性,这里不一一介绍,好戏还在后头……

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值