1. 结构体定义

结构体是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。为了定义结构,必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct tag {
    member_type1 member_name1;
    member_type2 member_name2;
    member_type3 member_name3;
    ...
} object_names;
  • tag 是结构体类型的标签;
  • member_type1 member_name1 是标准的变量定义,比如 int i;或者 float f
  • 在结构定义的末尾,最后一个分号之前,您可以指定一个或多个结构变量,这是可选的;
struct 
{
    int a;
    char b;
    float c;
} x;

这个声明创建了一个名叫 x 的变量 , 它包含三个成员:一个整数、一个字符和一个浮点数 。

struct 
{
    int a;
    char b;
    float c;
} y[20], *z;

这个声明创建了 yzy 是一个数组,它包含了 20 个结构。 z 是一个指针, 它指向这个类型的结构 。

这两个声明被编译器当作两种截然不同的类型,即使它们的成员列表完全相同 。 因 此,变量 yz 的类型和 x 的类型不同 ,所以下面这条语句

z = &x

是非法的。

标签 tag 字段允许为成员列表提供一个名字 , 这样它就可以在后续的声明中使用。标签允许多个声明使用同一个成员列表 , 并且创建同一种类型的结构。这里有个例子。

struct Books
{
   int a;
   char b;
   float c;
};

这个声明把标签 Books 和这个成员列表联系在一起。该声明并没有提供变量列表 , 所以它并未创建任何变量 。

下面是 C 语言的写法:

struct Books x;
struct Books  y[20], *z;

这些声明使用标签来创建变量。它们创建和最初两个例子一样的变量,但存在一个重要的区别,现在 xyz 都是同一种类型的结构变量。

声明结构时可以使用的另一种良好技巧是用 typedef 创建一种新的类型 , 如下面的例子所示 。

typedef struct 
{
    int a;
    char b;
    float c;
} Books;

这个技巧和声明一个结构标签的效果几乎相同 。 区别在于 Books 现在是个类型名而不是个结构标签 ,所以后续的声明可能像下面这个样子 :

Books x;
Books y[20], *z;

如果你想在多个源文件中使用同一种类型的结构,你应该把标签声明或 typedef 形式的声明放在一个头文件中 。 当源文件需要这个声明时可以使用 #include 指令把那个头文件包含进来 。

2. 结构体访问

为了访问结构的成员,我们使用成员访问运算符 .。看示例代码

#include <iostream>
using namespace std;

struct Student
{
    string id;
    string name;
    int age;
};

int main() 
{
    Student student;
    student.age = 19;
    student.id = "001";
    student.name = "Jack";
    cout << "student id is " << student.id << endl; // student id is 001
    cout << "student name is " << student.name << endl; // student name is Jack
    cout << "student age is " << student.age << endl;   // student age is 19
    return 0;
}

3. 结构体作为函数参数

不同于数组,结构体是按值传递的。也就是说整个结构体的内容都复制给了形参,即使某些成员数据是一个数组。

定义结构体类型的位置必须在首次使用该类型名之前,否则程序将无法正确识别该类型。要注意,定义完结构体类型后的分号是必不可少的,否则将会引起错误。

#include <iostream>
using namespace std;


struct Student
{
    string id;
    string name;
    int age;
    int course[2] = {0};
    
};

void printStudent(Student stu);

int main() 
{
    Student student;
    student.age = 19;
    student.id = "001";
    student.name = "Jack";
    for (int i=0; i<2; i++)
    {
        student.course[i] = i;
    }

    printStudent(student);

    for (int i=0; i<10; i++)
    {
        cout << "student course is " << student.course[i] << endl;
    }
    return 0;
}

void printStudent(Student stu) 
{
    cout << "student id is " << stu.id << endl; 
    cout << "student name is " << stu.name << endl; 
    cout << "student age is " << stu.age << endl; 
    

    // 企图修改 stu 的 course
    for (int i=0; i<2; i++)
    {
        cout << "student course is " << stu.course[i] << endl;
        stu.course[i] = 0;
    }

}

如果我们希望能在函数修改实参,则可以使用引用的方法。由于结构往往整合了许多的成员数据,它的数据量也绝对不可小觑。使用值传递虽然能够保护实参不被修改,但是却会或多或少地影响到程序的运行效率。

所以,一般情况下,如果要修改实参那么可以选择引用传递结构体的方法。

void printStudent(Student &stu);

int main() 
{
    Student student;
    student.age = 19;
    student.id = "001";
    student.name = "Jack";


    printStudent(student);
    cout << "student age is " << student.age << endl;   // student age is 20

    return 0;
}

void printStudent(Student &stu) 
{
    cout << "student id is " << stu.id << endl; 
    cout << "student name is " << stu.name << endl; 
    cout << "student age is " << stu.age << endl;   // student age is 19
    stu.age = 20;
}

4. 结构体作为返回值

一般情况下,函数只能返回一个变量。如果要尝试返回多个变量,那么就要通过在参数中使用引用,再把实参作为返回值。然而,这种方法会导致一大堆参数,程序的可读性也较差。

当结构体出现以后,我们可以把所有需要返回的变量整合到一个结构中来,问题就解决了。我们通过一段程序来了解如何让函数返回一个结构体:

struct Student
{
    string id;
    string name;
    int age;
};


Student init()
{
    Student student;
    student.age = 19;
    student.id = "001";
    student.name = "Jack";

    return student;
}


int main() 
{
    Student stu;
    stu = init(); // 初始化并返回一个结构体
    cout << "student id is " << stu.id << endl; 
    cout << "student name is " << stu.name << endl; 
    cout << "student age is " << stu.age << endl;   

    return 0;
}

5. 结构体数组

结构体是一种数据类型,因此它也有对应的结构体数组和指向结构体的指针。定义结构体数组和定义其他类型的数组在语法上并无差别。需要注意的是,在定义结构体数组之前,我们必须先定义好这个结构体。

struct Student
{
    string id;
    string name;
    int age;
};


int main() 
{

    Student stu[2] = {{"0001", "Jack", 18},
               {"0002", "Tom", 20}};

    for (int i = 0; i< 2; i++)
    {
        cout << "student id is " << stu[i].id << endl; 
        cout << "student name is " << stu[i].name << endl; 
        cout << "student age is " << stu[i].age << endl; 
    }
  
    return 0;
}

因为结构体的成员默认是公共的,所以不需要像使用类对象一样使用一个函数来访问它们,而是可以通过简单地将点运算符和成员名称放在下标后面来访问任何元素的成员。

可以使用构造函数初始化一个结构体数组,就像初始化一个类对象数组一样。以下结构体声明取自上边程序,但是进行了修改,使它包含了一个构造函数。它接受三个参数,但是, 如果在创建结构体变量时未传递任何值给构造函数,那么它也有默认值。

#include <iostream>
using namespace std;


struct Student
{
    string id;
    string name;
    int age;

    // 如果形参和结构体成员变量名称相同时,需要使用  this 指针
    // Student(string id="", string name="", int age=0) 
    // {
    //     this->id = id;
    //     this->name = name;
    //     this->age = age;
    // }

    // 构造函数,接收 3 个参数
    Student(string i="", string n="", int a=0) 
    {
        id = i;
        name = n;
        age = a;
    }

};

int main() 
{
	// 结构体数组使用结构体构造函数进行初始化
    Student stu[2] = {Student("0001", "Jack", 18), Student("0002", "Tom", 20)};

    for (int i = 0; i< 2; i++)
    {
        cout << "student id is " << stu[i].id << endl; 
        cout << "student name is " << stu[i].name << endl; 
        cout << "student age is " << stu[i].age << endl; 
    }
  
    return 0;
}

6. 结构体指针

所谓结构体指针就是指向结构体的指针。定义好一个结构体之后,定义一个结构体指针变量的语法格式为:

// 结构类型名 *指针变量名;
struct_type *struct_pointer

可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &stu;

为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符,如下所示:

// 指针变量名->成员数据
struct_pointer->name;

需要注意的是,箭头操作符的左边一定是一个结构指针,而成员操作符的左边一定是一个结构变量,两者不能混淆使用。

#include <iostream>
using namespace std;


struct Student
{
    string id;
    string name;
    int age;

    // 构造函数,接收 3 个参数
    Student(string i="", string n="", int a=0) 
    {
        id = i;
        name = n;
        age = a;
    }

};

// 结构体指针作为函数参数
void displayStudent(Student *p_stu);

int main() 
{
    // 初始化结构体变量
    Student stu = Student("0001", "Jack", 18);
    // 定义结构体指针变量,并把 stu 的地址赋值给 p_stu
    Student *p_stu = &stu;
    displayStudent(p_stu);
    return 0;
}


void displayStudent(Student *p_stu)
{
    cout << "student id is " << p_stu->id << endl; 
    cout << "student id is " << (*p_stu).id << endl; 
    cout << "student name is " << p_stu->name << endl; 
    cout << "student age is " << p_stu->age << endl; 
}

考虑下面问题:

int a = 3;
    int *p_a = &a;
    cout << "a is " << *p_a << endl;

我们可以看到 *p_a 就是变量 a 的值,所以我们可以使用 (*p_stu).id 来代替 -> 操作符。

简而言之,访问结构的成员时使用 .运算符,而通过指针访问结构的成员时,则使用箭头 ->运算符。也就是说,用结构体定义了一个实体,那么这个实体要引用他里面的成员,就用 .操作符,如果用结构体定义的是一个结构指针,那么要引用他里面的成员就用 ->

7. 类与结构体的区别

类与结构体在 C++ 中区别:

  • class 中默认的成员访问权限是 private 的,而 struct 中则是 public 的;
  • class 继承默认是 private 继承,而从 struct 继承默认是 public 继承;
  • class 可以定义模板,而 struct 不可以;

结构体变量和结构体类型的定义

8. typedef 定义结构体

typedef struct Student_tag
{
    string id;
    string name;
    int age;

    // 构造函数,接收 3 个参数
    Student_tag(string i="", string n="", int a=0) 
    {
        id = i;
        name = n;
        age = a;
    }

}Student;

关于 C++ 中的 typedef 可以省略问题:

  • C 语言的 struct 定义了一组变量的集合,C 编译器并不认为这是一种新的类型。
  • C++ 中的 struct 是一个新类型的定义声明, 所以可以省略 typedef, 定义变量的时候也可以省略 struct, 而不用向 C语言那样没用 typedef 取新名字,就需要用 struct 结构体名 这种形式定义变量。


C++ 中的 typedef struct 是会对最后的 object_names 部分产生两种影响:

  • 不用 typedefobject_names 就相当于一个变量了,可以直接调用结构体中的内容;
  • typedefobject_names 部分是个结构体类型,用于创建结构体的变量;