首页 > 编程语言 >C++:新枚举与新结构

C++:新枚举与新结构

时间:2024-08-18 22:54:44浏览次数:8  
标签:初始化 name gpa C++ 枚举 Student 结构

一、枚举

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

标签:初始化,name,gpa,C++,枚举,Student,结构
From: https://blog.csdn.net/m0_63684047/article/details/141094776

相关文章

  • C++:从Type到Control
    一、基本数据类型     计算机的存储空间由最基本的二进制数(比特)组成,若干连续的二进制位(一般为8位)组成一个字节并被分配一个内存地址(),所以单独的比特没有地址,通常情况下CPU也不会一个比特一个比特读取数据,相反,字节被当作基本操作单位。在此前提下,一切要存储在计算机上的......
  • C++:函数
         FunctionsareC++entitiesthatassociateasequenceofstatements(afunctionbody)withanameandalistofzeroormorefunctionparameters.        函数是C++中的实体,它将一系列语句(一个函数体)与一个名称和零个或多个函数参数列表相关......
  • 基础数据结构回顾记录
    数组初始化两种方式声明和初始化数组——使用new关键字和使用大括号。refer:https://www.freecodecamp.org/chinese/news/java-array-declaration-how-to-initialize-an-array-in-java-with-example-code/先声明,后赋值//数组声明dateType[]nameOfArray=newdataType......
  • 关于c++使用toml plusplus(俗称toml++)的使用
    链接toml++-githubtoml++-帮助文档使用要求:c++17及以上版本toml语法-英文toml语法-中文toml读取参见官方给出的范例toml写入一个范例,一个开胃菜toml文件待生成的目标文件内容为[NET_INTERFACE]bool=falseinteger=1234567890string='thisis......
  • 【C++学习笔记 18】C++中的隐式构造函数
    举个例子#include<iostream>#include<string>usingString=std::string;classEntity{private: Stringm_Name; intm_Age;public: Entity(constString&name) :m_Name(name),m_Age(-1){} Entity(intage) :m_Name("UnKnown")......
  • C/C++内存管理
    一、目标1.C/C++内存分布2.C语言中动态内存管理方式3.C++中动态内存管理4.operatornew与operatordelete函数5.new和delete的实现原理6.常见面试题二、个人见解1.C/C++内存分布【说明】1.栈又叫堆栈--非静态局部变......
  • c++ 获取文件夹目录名字
        main.cpp#ifndefPHOTO_FILE_PROCESSOR_H#definePHOTO_FILE_PROCESSOR_H#include<iostream>#include<string>#include<vector>#include<dirent.h>#include<algorithm>#include<stdexcept>classPhotoFilePro......
  • C++ 设计模式——建造者模式
    建造者模式建造者模式组成部分建造者模式使用步骤1.定义产品类2.创建具体产品类3.创建建造者接口4.实现具体建造者5.创建指挥者类6.客户端代码建造者模式UML图建造者模式UML图解析建造者模式的优缺点建造者模式的适用场景完整代码建造者模式建造者模式(B......
  • typedef在C/C++的用法
    typedef是C和C++中的一个关键字,用于为已有的数据类型创建新的类型名。它的主要用途如下:1.定义别名typedef最基本的功能是为一个现有的类型定义一个别名,使代码更简洁或更具可读性。例如:typedefunsignedlongulong;ulonga,b;这段代码将unsignedlong类型重......
  • extern在头文件中添加是否必要?(C/C++)
    在C和C++编程中,extern关键字通常用于表示函数或变量的声明(而非定义),特别是在跨文件使用时。尽管在函数声明中使用extern不是强制性的,但它有特定的作用,尤其在变量声明方面。让我们深入探讨一下。1.函数声明的基本概念当你在头文件中声明一个函数时,通常只需要提供函......