C++ - 用户自定义数据类型

C++允许用户根据需要自己声明一些类型,包括数组、结构体类型、共用体类型、枚举类型、类类型

结构体类型

在一个组合项中包含若干个类型不同的数据项,C/C++允许用户自己指定这样一种数据类型,成为结构体

比如声明一个Student结构体类型

struct Student  // 声明一个Student结构体类型
{
    int num; 
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
}; //最后有一个分号

struct是声明结构体类型时所必须的关键字,它向编译系统声明:这是一种结构体类型,它包括num、name、sex、age、score、address等不同类型的数据项

声明一个结构体类型的一般形式为:

struct type_name { //type_name表示结构体类型名
    member_type1 member_name1; // member_type1成员类型 member_name1成员名
    member_type2 member_name2;
    member_type3 member_name3;
    ...
} object_names;
  • type_name 是结构体类型的名称;
  • member_type1 member_name1 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义;
  • 在结构定义的末尾,最后一个分号之前,可以指定一个或多个结构变量,这是可选的。
结构体类型变量的定义方法极其初始化

前面只是指定了一种结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元。为了能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据。

定义结构体类型变量的方法

可以采取以下3种方法定义结构体类型变量

1、先声明结构体类型再定义变量

如上面已经定义了一个结构体类型Student,可以用它来定义结构体变量。如:

Student student1, student2; //Student 结构体类型名,student1, student2结构体变量名

C语言中,在定义结构体变量时,要在结构体类型名前面加上关键字structC++保留了C的用法,如下:

struct Student student1, student2;

但是C++提出来新的方法,可以不必在定义结构体变量时加上struct关键词,所以建议省略struct,这样使用更加方便,简单。

在定义了结构体变量后,系统会为之分配内存单元,student1student2在内存中各占63个字节(4+20+1+4+4+30=63)

2、在声明类型的同时定义变量

struct Student  // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
} student1, student2; //定义两个结构体类型Student的变量student1, student2

这种形式的定义的一般形式为:

struct 结构体类型名
{
   成员表
} 变量名表;

3、直接定义结构体类型变量,其一般形式如下

struct     // 注意没有结构体类型名
{
   成员表
} 变量名表;

这种方法虽然合法,但是很少使用。建议先定义类型后定义变量的第一种方法,将声明结构体类型和定义结构体变量分开,便于不同的函数甚至不同的文件都能使用所声明的结构体类型。

若程序规模比较大,往往将若干个结构体类型的声明几种放到一个头文件中,如果哪个源文件需要用到此结构体类型,则用#include指令将该文件包含到本文件中。这样做便于修改和使用。在程序叫简单,结构体类型只在本文件中使用,也可以使用第二种方法

关于结构体,说明几点:

  • 结构体类型的结构根据需要进行设计,可以设计出多种不同的结构体类型。

  • 类型与变量是不同的概念,不要混淆。只能对结构体变量中的成员赋值,而不能对结构体类型赋值。在编译时,对类型是不分配空间的,只对变量分配空间

  • 对结构体中的成员可以单独使用,它的作用与地位相当于普通变量

  • 成员也可以是结构体变量,如:

struct Date // 声明一个结构体类型Date
{
    int year;
    int month;
    int day;
};

struct Student  // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
    Date birthday; // Date是结构体类型,birthday是Date类型的成员
} student1, student2; //定义两个结构体类型Student的变量student1, student2
结构体变量的初始化
  • 和其他类型变量一样,对结构体变量可以在定义时指定初始值。如:
struct Student  // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
} student1 = { 10001, "Zhang", 'M', 21, 92, "Shanghai"}; 
  • 也可以采用类型与定义变量分开的形式,在定义变量时进行初始化
Student student2 = { 10002, "Wang", 'F', 23, 88, "Shanghai"};

引用结构体变量

在定义结构体变量之后,当然可以引用这个变量。

1)可以将一个结构体变量的只赋给另一个具有系统结构的结构体变量。如上面的student1student2都是Student类型的变量,可以互相赋值

student1 = student2;

赋值时,结构体变量student2中的各个成员的值分别赋给结构体变量student1中相应的成员

2)可以引用一个结构体变量中的一个成员的值。例如:student1.num表示结构体变量student1中的成员num的值,引用结构体变量中成员的一般形式为:

student1.num = 10010;

.是成员运算符,它在所有的运算符中优先级最高,因此可以把student1.num作为一个整体来看待。上面的赋值语句的作用是将整数10010赋值给student1变量中的整型成员num

3)如果成员本身也是一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员。例如,已定义结构体变量student1,如果想引用student1变量中的birthday成员中的month成员,则需要逐级引用

student1.birthday.month //表示引用结构体变量student1中的birthday成员中的month成员

4)不能将一个结构体变量作为一个整体进行输入和输出。即不能进行如下操作:

cout<<student1; // 错误使用

只能对结构体变量中的各个成员分别进行输入和输出

cout<<student1.age;

5)对结构体变量的成员可以像普通变量一样进行有关运算。例如:

student2.score = student1.score;
student1.age++;

6)可以引用结构体变量成员的低下,也可以引用结构体变量的地址

cout<<&student1;     // 输出结构体变量student1的起始地址
cout<<&student1.age; // 输出student1.age的地址

结构体数组

一个结构体变量中可以存放一组数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要参加运算,显然应该使用数组,这就是结构体数组。结构体数组与数值型数组不同之处在于:每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项

定义结构体数组

和定义结构体变量的方法相似,只须指出其为数组即可。如

struct Student  // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
};

int main(int argc, const char * argv[]) {

    Student stu[3]; // 定义Student类型的数组
    return 0;
}

或者直接定义结构数据

struct Student  // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
}stu[3];

或者

struct   // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
}stu[3];
结构体数组的初始化

与其他类型的数组一样,对结构体数组可以初始化,如:

struct  Student // 声明一个Student结构体类型
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char address[30];
} stu[3] = { {10101, "Li", 'M', 19, 88, "Shanghai"},
    {10102, "Wang", 'F', 18, 85, "Shanghai"},
    {10103, "Zhang", 'M', 17, 83, "Shanghai"} };

一个结构体常量应包含结构体中全部成员的值,以上是把声明结构体类型和定义结构体数组以及数组的初始化同时完成了。定义数组stu时也可以不指定元素格式,下成以下形式

 stu[] = { {...}, {...}, {...}};

编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数,上面外层括号中有3个大括号,提供了3组初始值,系统会把stu数组的元素个数定为3.

当然,声明结构体类型和定义结构体数组及初始化也可以分开进行

 Student  stu[] = { {...}, {...}, {...}};

指向结构体变量的指针

一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素

1、通过指向结构体变量的指针引用结构体变量中的成员

看一个简单的例子来说明指向结构体变量的指针变量的应用

定义一个结构体变量stu,成员包括学号、姓名、性别、成绩。定义一个指针变量p指向该结构体变量stu,通过指针变量输出各成员的值

#include <iostream>
#include <string>
using namespace std;

int main(int argc, const char * argv[]) {

    struct  Student
    {
        int num;
        string name;
        char sex;
        float score;
    };

    Student stu;
    Student *p = &stu;
    stu.num = 10001;
    stu.name = "wang";
    stu.sex = 'f';
    stu.score = 90;

    cout<<stu.num<<" "<<stu.name<<" "<<stu.sex<< " "<<stu.score<<endl;
    cout<<(*p).num<<" "<<(*p).name<<" "<<(*p).sex<< " "<<(*p).score<<endl;
    cout<<p->num<<" "<<p->name<<" "<<p->sex<< " "<<p->score<<endl;

    return 0;
}

在主函数中声明了Student类型,然后定义一个Student类型的变量stu。同时又定义一个指针变量p,它指向一个Student类型的数据。将结构体变量stu的起始地址赋给指针变量p,也就是使p指向stu,然后对stu中的各个成员赋值。

第一个cout语句的作用是输出stu的各个成员,用stu.num表示,其余类推

第二个cout语句使用(*p)输出stu各成员的值,(*p)表示p指向的结构体变量,(*p).num是p指向的结构体变量中的成员num,即stu.num。注意,*p两侧的括号不能省略,因为成员运算符.优于*运算符

第三个cout语句是使用->运算符,形象的表示指向的关系。p->num表示指针p当前指向的结构体变量中的成员num,等价于(*p).num

也就是说以下3种形式等价

  • 结构体变量.成员名,如:stu.num
  • (*p).成员名,如:(*p).num
  • p->成员名,如:p->num

2、用结构体变量和指向结构体变量的指针构成链表

链表是一种常见的重要的数据结构

结构体类型数据作为函数参数

将一个结构体变量中的数据传递给另一个函数,有以下3种方法

1)用结构体变量名作为参数

例如,用结构体变量stu做参数实参,将实参值传给形参。用结构体变量做实参,采取的是“值传递”的方式,将结构体变量所占的内存单元的内容全部顺序传递给形参。形参也必须是同类型的结构体变量,在函数调用期间形参也占内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很重要的。此外,由于采用值传递方式,如果在执行被调用函数期间改变了形参的值,该值不能返回主调函数,这往往不能满足需求。因此一般很少使用

#include <iostream>
#include <string>
using namespace std;

struct  Student
{
    int num;
    string name;
};

int main(int argc, const char * argv[]) {

    void print(Student);

    Student stu;
    stu.num = 10001;
    stu.name = "wang";
    print(stu);

    return 0;
}

void print(Student stu)
{
    cout<<stu.num<<" "<<stu.name<<endl;
}

2)用结构体变量的指针做实参,将结构体变量的地址传给形参

int main(int argc, const char * argv[]) {

    void print(Student *);

    Student stu;
    Student *P= &stu;
    stu.num = 10001;
    stu.name = "wang";
    print(P);

    return 0;
}

void print(Student *stu)
{
    cout<<stu->num<<" "<<stu->name<<endl;
}

3)用结构体变量的引用做函数参数,它就成为实参(是结构体变量)的别名

int main(int argc, const char * argv[]) {

    void print(Student &);

    Student stu;
    stu.num = 10001;
    stu.name = "wang";
    print(stu);

    return 0;
}


void print(Student &stu)
{
    cout<<stu.num<<" "<<stu.name<<endl;
}

3种方法的比较:

程序(1)用结构体变量做实参和形参,程序直观易懂,但是在调用函数实要单独开辟内存单元,实参中全部内容通过值传递方式一一传递给形参,如果结构体变量占的存储空间很大,则在虚实结合时空间和时间的开销都比较大,效率不高

程序(2)采用指针变量做实参和形参,在调用函数时形参只占4个字节,时参只是将stu的起始地址传给形参,而不是将结构体变量的各成员的值一一传给形参,因而空间和时间都开销很小,效率较高。

线程(3)的实参是结构体Student类型变量,而形参用Student类型的引用,虚实结合时传递的是stu的地址,因而效率高。

用new和delete运算符进行动态分配和撤销存储空间

在软件开发中,常常需要动态地分配和撤销内存空间,例如:对动态链表中结点的插入与删除。在C语言是利用库函数mallocfree来分配和撤销内存空间。C++提供了较简单功能较强的运算符newdelete来取代mallocfree函数

注意:new和delete是运算符不是函数,因此执行效率高.虽然为了与 C兼容,C++仍然保留了malloc和free函数,但建议不用malloc和free函数,而用new和delete是运算符

new运算符

new int; // 开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100); // 开辟存放一个整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10]; // 开辟一个存放字符数组(包括10个元素)的空间,返回字符数组首元素的地址
new char[5][4]; // 开辟一个存放二维数组(大小为5*4)的空间,返回首元素地址
float *p = new float(3.14159); // 开辟一个存放单精度数的空间,并指定该数的初值为3.14159,将返回的该空间的地址赋给指针变量p

new运算符使用的一般格式为

new 类型 [初值]

注意:用new分配数组空间时不能指定初值。如果由于内存不足等原因无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功

delete运算符

delete运算符的一般格式

delete 指针变量 (对变量)
或
delete [] 指针变量 (对数组)

例如要撤销上面用new开辟的存放单精度的空间,应该

delete p;

对于数组

char *p = new char[10];
delete [] p;

临时开辟一个存储空间存放一个结构体数据

#include <iostream>
#include <string>
using namespace std;

struct  Student
{
    int num;
    string name;
    char sex;
};

int main(int argc, const char * argv[]) {

    Student *p; // 定义指向结构体类型Student的指针变量p
    p = new Student; // 用new关键字开辟一个存放Student型数据的空间,把地址给p
    p->name = "wang"; // 向结构体成员赋值
    p->num = 10001;
    p->sex = 'm';
    cout<< p->name<<" "<< p->num<<" "<<p->sex<<endl; // 输出成员值
    delete p; // 释放空间

    return 0;
}

new关键字开辟一个存放Student型数据的空间,空间的大小由系统根据Student自动算出,不必指定。并赋值给p,这样p指向该空间。虽然没有定义结构体变量,但是可以通过指针变量p访问该空间,可以对该空间中各成员赋值,并输出它们的值。最后使用delete释放空间。

共用体类型

C++除了允许用户直接声明结构体类型外,还可以声明共用体类型,也叫联合类型。从形式上看,它与结构体有些相似,如下声明一个名为Data的共用体类型并定义3个共用体变量a、b 、c

union Data
{
    int i;
    char ch;
    double d;
} a,b,c;

共用体与结构体的不同在于:系统为结构体中各成员分别分配存储单元,而共用体则是各成员公占一段存储单元,例如上面的定义是把一个整型成员、一个字符型成员、一个双精度成员安排在同一个地址开始的内存单元中

枚举类型

如果一个变量只能有几种可能的值,可以定义为枚举类型。所谓“枚举”是将变量的值一一列举出来,变量的值只能在列举出来的值的范围内

声明枚举类型用enum开头,例如:

enum weekday {
    sun,
    mon,
    tue,
    wed,
    thu,
    fri,
    sat
};

上面声明了一个枚举类型weekday,花括号中 sun,mon,...sat等称为枚举元素或枚举常量

声明枚举类型的一般形式为:

enum 枚举类型名 { 枚举常量表 };

在声明枚举类型后,可以用它来定义变量,如:

weekday workday, week_end;

这样 workday, week_end就被定义为枚举类型weekday的变量

在C语言中,枚举类型名包括关键字enum,以上的定义也可以写为:

enum weekday workday, week_end;

C++中允许不写enum,但是保留了C的写法

根据以上对枚举类型weekday的声明,枚举变量的值只能是sun到sat之一。例如:

workday = mon;
week_end = sun;

也可以在声明枚举类型时同时定义枚举变量

enum weekday {
    sun,
    mon,
    tue,
    wed,
    thu,
    fri,
    sat
} workday, week_end;

需要注意:枚举元素的名字本身没有特定的含义。例如不因写成sun或sunday就自动表示星期天,它只是一个符号,究竟用来代表什么含义,完全由程序员考虑,并在程序中对它们做相应的处理

说明:

  • 枚举元素按常量处理,故称枚举常量。它们不是变量,不能对它们赋值,即枚举元素的值是固定的。例如:
sun = 0; mon = 0; //错误,不能用赋值语句对枚举常量赋值
  • 枚举元素作为常量,它们是有值的,其值是一个整数,编译系统按定义时的顺序对它们赋值为0,1,2,3...。在上面的说明中,sun的值为0,mon的值为1.......sat的值为6,如果有赋值语句
workday = mon; //把枚举常量值赋给枚举变量workday,workday的值等于1

也可以在声明枚举类型时自己指定枚举元素的值,如:

enum weekday {
    sun = 7,
    mon = 1,
    tue,
    wed,
    thu,
    fri,
    sat
};

表示指定sun的值为7,mon的值为1,以后按顺序加1,sat为6

  • 枚举值可以用来做判断比较,按整数比较规则进行比较。如:
if (workday == mon) // 判断workday的值是否等于mon
{

}
if (workday > sun) // 判断workday的值是否大于sun
{

}

按其在声明枚举类型时的顺序比较,如果定义时未另行指定,则第一个枚举元素的值为0.

  • 不能把一个整数直接赋给一个枚举变量,枚举变量只能接受枚举类型数据,如:
workday = mon;  // 正确
week_end = 1;  //错误,它们属于不同的类型

应该先进行强制转换才能赋值

week_end = (weekday)1;  //  C语言继承下来的强制类型转换形式
或者
week_end = weekday(1);  // C++风格的强制类型转换

用typedef声明新的类型名

除了可以用以上方法声明结构体、共用体、枚举等类型外,还可以用typedef声明一个新的类型名来代替已有的类型名。如:

typedef int INTEGER; // 指定INTEGER代表int类型
typedef float REAL; // 指定REAL代表float类型

这样,以下两行就是等价的

int i,j; float a,b;
INTEGER i,j; REAL a.b;

为结构体类型声明一个新的名字

typedef struct Student
{
    int num;
    string name;
    char sex;
} STUDENT;

STUDENT stu; // 声明变量

还可以进一步用typedef声明一个新的类型名,例如:

typedef int NUM[100]; // 声明NUM为整型数组类型,包含100个元素
NUM n; // 定义n为包含100个整型元素的数组

typedef char * STRING; // 声明STRING为 char * 类型,即字符指针类型
STRING p, s[10]; // 定义p为char *型指针变量,s为char *类型的指针数组(有10个元素)

typedef int(*POINTER)(); //声明POINTER为指向函数的指针类型
POINTER p1,p2; //p1,p2为POINTER类型的指针变量

归纳起来,声明一个新的类型名的方法:

1、先按定义变量的方法写出定于语句(如: int i;)
2、将变量名换成新类型名(如将i换成COUNT, 即 int COUNT)
3、在最前面加typedef(如:typedef int COUNT)
4、然后可以用新类型名( 如:COUNT)去定义变量了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容