七、结构体与C++引用

七、结构体与C++引用

1. 结构体的定义、初始化、结构体数组

有时候需要将不同类型的数据组合为一个整体,以便于引用。例如, 一名学生有学号、姓名、性别、年龄、地址等属性,如果针对学生的学号、姓名、年龄等都单独定义一个变量,那么在有多名学生时,变量就难以分清。为此,C语言提供结构体来管理不同类型的数据组合。

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

struct   结构体名
        {成员表列}
struct student
{
    int num;char name[20];char sex;
    int age;float score;char addr[30];
};

先声明结构体类型, 再定义变量名。

struct student student1, student2;

【例】结构体的 scanf读取和输出。

#include <stdio.h>

struct student{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};//结构体类型声明,注意最后一定要加分号

int main() {
    struct student s={1001,"lele",'M',20,85.4,"Shenzhen"};
    struct student sarr[3];//定义一个结构体数组变量
    int i;
    //结构体输出必须单独去访问内部的每个成员
    s.num=1003;
    printf("%d %s %c %d %f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
    printf("--------------------------------------\n");
//    scanf("%d%s %c%d%f%s",&s.num,s.name,&s.sex,&s.age,&s.score,s.addr);
    for(i=0;i<3;i++)
    {
        scanf("%d%s %c%d%f%s",&sarr[i].num,sarr[i].name,&sarr[i].sex,&sarr[i].age,&sarr[i].score,sarr[i].addr);
    }
    for(i=0;i<3;i++)//结构体数组的输出
    {
        printf("%d %s %c %d %f %s\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score,sarr[i].addr);
    }

    return 0;
}

结构体类型声明要放在main函数之前,这样main函数中才可以使用这个结构体,工作中往往把结构体声明放在头文件中,注意,结构体类型声明最后一定要加分号, 否则会编译不通。另外, 定义结构体变量时, 使用 struct student来定义, 不能只有struct或student, 否则也会编译不通,sarr是结构体数组变量. 结构体的初始化只能在一开始定义,如果 struct students={1001,”lele”,’M’,20,85.4,”Shenzhen”}已经执行, 即 struct student s已经定义, 就不能再执行s={1001,”lele”,’M’,20,85.4,”Shenzhen”}. 如果结构体变量已经定义, 那么只能对它的每个成员单独赋值, 如 s. num=1003.

采用“结构体变量名.成员名”的形式来访问结构体成员,例如用s.num访问学号。在进行打印输出时,必须访问到成员,而且printf中的%类型要与各成员匹配,使用scanf读取标准输入时,也必须是各成员取地址,然后进行存储,不可以写成&s,即不可以直接对结构体变量取地址.整型数据(%d)、浮点型数据(%f) 、字符串型数据(%s)都会忽略空格,但是字符型数据(%c)不会忽略空格,所以如果要读取字符型数据,那么就要在待读取的字符数据与其他数据之间加入空格.

例中代码的运行结果如下

1003 lele M 20 85.400002 Shenzhen
--------------------------------------
1001 Lele M 20 85.4 Shenzhen
1005 leli M 21 86.4 Shenzhen
1002 lile M 25 81.4 Shenzhen
1001 Lele M 20 85.400002 Shenzhen
1005 leli M 21 86.400002 Shenzhen
1002 lile M 25 81.400002 Shenzhen

2. 结构体对齐

结构体本身的对齐规则有好几条,考研初试只需要记住一条,结构体的大小必须是其最大成员的整数倍!

#include <stdio.h>

struct student_type1{
    double score;//double是一种浮点类型,8个字节,浮点分为float和double,记住有这两种即可
    short age;//short 是整型,占2个字节
};

struct student_type2{
    double score;
    int height;//如果两个小存储之和是小于最大长度8,那么它们就结合在一起
    short age;
};

struct student_type3{
    int height;
    char sex;
    short age;
};
//结构体对齐
int main() {
    struct student_type1 s1;
    struct student_type2 s2={1,2,3};
    struct student_type3 s3;
    printf("s1 size=%d\n",sizeof(s1));
    printf("s2 size=%d\n",sizeof(s2));
    printf("s3 size=%d\n",sizeof(s3));
    return 0;
}
s1 size=16
s2 size=16
s3 size=8

3. 结构体指针

一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设置一个指针变量,用它指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素, 从而能够通过结构体指针快速访问结构体内的每个成员。

【例】结构体指针的使用。

#include <stdio.h>

struct student{
    int num;
    char name[20];
    char sex;
};
//结构体指针的练习
int main() {
    struct student s={1001,"wangle",'M'};
    struct student sarr[3]={1001,"lilei",'M',1005,"zhangsan",'M',1007,"lili",'F'};
    struct student *p;//定义了一个结构体指针变量
    p=&s;
    printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1访问通过结构体指针去访问成员
    printf("%d %s %c\n",p->num,p->name,p->sex);//方式2访问通过结构体指针去访问成员,用这种
    p=sarr;
    printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1访问通过结构体指针去访问成员
    printf("%d %s %c\n",p->num,p->name,p->sex);//方式2访问通过结构体指针去访问成员,用这种
    printf("------------------------------\n");
    p=p+1;
    printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1访问通过结构体指针去访问成员
    printf("%d %s %c\n",p->num,p->name,p->sex);//方式2访问通过结构体指针去访问成员,用这种
    return 0;
}
1001 wangle M
1001 wangle M
1001 lilei M
1001 lilei M
------------------------------
1005 zhangsan M
1005 zhangsan M

p就是一个结构体指针, 可以对结构体s取地址并赋给 p,这样借助成员选择操作符,就可以通过 p访问结构体的每个成员,然后进行打印。我们知道数组名中存储的是数据的首地址,所以可以将sarr赋给p,这样就可以通过两种方式访问对应的成员。

使用(*p). num访问成员为什么要加括号呢? 原因是“.”成员选择的优先级高于“*”(即取值) 运算符, 所以必须加括号, 通过*p 得到 sarr[0], 然后获取对应的成员。

4. typedef 的使用

前面定义结构体变量时使用的语句是 struct students,有些麻烦,可以选择使用 typedef 声明新的类型名来代替已有的类型名

例。

#include <stdio.h>

//stu 等价于 struct student,pstu等价于struct student*
typedef struct student{
    int num;
    char name[20];
    char sex;
}stu,*pstu;

typedef int INGETER;//特定地方使用
//typedef的使用,typedef起别名
int main() {
    stu s={1001,"wangle",'M'};
    stu *p=&s;//定义了一个结构体指针变量
    pstu p1=&s;//定义了一个结构体指针变量
    INGETER num=10;
    printf("num=%d,p->num=%d\n",num,p->num);
    return 0;
}
num=10,p->num=1001

使用 stu定义结构体变量和使用 struct student定义结构体变量是等价的; 使用 INTEGER定义变量i和使用 int定义变量i是等价的; pstu等价于 struct student*, 所以p 是结构体指针变量.

5. C++的引用

对于 C++, 首先新建源文件时, 名字需要叫main. cpp,以cpp后缀结尾

使用了引用后,在子函数内的操作和函数外操作手法一致,这样编程效率较高,对于初学者理解也非常方便, 王道数据结构书籍中均采用了这种手法。

那这种C++的写法, 和C语言的代码又是如何对应的呢? 下面我们来看一下代码对应关系。

【例】在子函数内修改主函数的普通变量

#include 

//当你在子函数中要修改主函数中变量的值,就用引用,不需要修改,就不用
void modify_num(int &b)//形参中写&,要称为引用
{
    b=b+1;
}
//C++的引用的讲解
//在子函数内修改主函数的普通变量的值
int main() {
    int a=10;
    modify_num(a);
    printf("after modify_num a=%d\n",a);
    return 0;
}

改为纯c

#include <stdio.h>
void modify_num(int *b)
{
*b=*b+1;
}
int main(){
int a=10;
modify_num(&a);
printf("after modify_num a=%d\n",a);
return 0;
}

【例】子函数内修改主函数的一级指针变量(重要!)

#include 

void modify_pointer(int *&p,int *q)//引用必须和变量名紧邻
{
    p=q;
}
//子函数内修改主函数的一级指针变量
int main() {
    int *p=NULL;
    int i=10;
    int *q=&i;
    modify_pointer(p,q);
    printf("after modify_pointer *p=%d\n",*p);
    return 0;//进程已结束,退出代码为 -1073741819 ,不为0,那么代表进程异常结束
}
after modify_pointer *p=10

改为纯c,就需要使用到二级指针(可以不理解)

#include <stdio.h>
void modify_pointer(int **p, int *q)//相对于 C++这里是 int **p;
{
    *p=q;//这里的写法和例中的是非常类似的
}
int main(){
    int*p=NULL;
    int i=10;
    int*q=&i;
    modify_pointer(&p,q);//相对于 C++这里是&p
    printf("after modify_pointer *p=%d\n",*p);
    return 0;
}

6. C++的布尔类型

布尔类型在C语言没有, 是C++的, 有 true和 false

【例】 布尔类型也是有值的

#include <stdio.h>

int main() {
    bool a= true;
    bool b= false;
    printf("a=%d,b=%d\n",a,b);
    return 0;
}
a=1,b=0

7.例题

使用C++的引用,注意提交时把代码选为C++;在主函数定义字符指针 char *p,然后在子函数内malloc申请空间(大小为100个字节),通过fgets读取字符串,然后在主函数中进行输出;要求子函数使用C++的引用,注意在C++中从标准输入读取字符串,需要使用fgets(p,100,stdin)

Sample Input 1 Sample Output
I love C language I love C language
how are you how are you
#include <stdio.h>
#include <stdlib.h>

//当子函数要修改主函数中的p,那么就加引用。引用如何实现的,完全不需要去关心
void modify_pointer(char *&p)
{
    p=(char*)malloc(100);//申请100个字节大小的空间
    fgets(p,100,stdin);//stdin代表标准输入,fgets是安全的
}

//课时8作业2 练习C++的引用的使用
int main() {
    char *p=NULL;
    modify_pointer(p);
    puts(p);
    free(p);//申请的空间不使用后,记得free,避免扣分
    return 0;
}