C++学习笔记(九)

一、结构体

1. 结构体概述

有时我们需要将不同类型的数据组合成一个有机的整体,如:

一个学生有学号,姓名,性别,年龄,地址等属性:

int num;
char name[20];
char sex;
int age;
char addr[40];

单独定义以上变量比较繁琐,数据不便于管理。

C++提供struct关键字,可以将不同类型封装在一起,形成新的结构叫做“结构体”

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    char addr[40];
};

2. 结构体定义

struct 结构体类型名
{
    成员列表;
}; // 注意此处有分号

如:

// 定义结构体类型
// 系统不会为结构体类型开辟空间,只会为结构体类型定义的变量开辟空间
struct Student
{
    // int num = 10; // 定义结构体类型时 不要给成员初始化值
    int num; // 结构体成员
    char name[20]; 
};
// 结构体中的成员拥有独立的空间

// 结构体定义变量
Student lucy; // lucy为结构体变量名
Student bob; // bob为结构体变量名

访问结构体变量中成员的方法:变量.成员名

结构体类型的三种定义方法:

①.  先定义结构体类型,再定义结构体变量(推荐)

struct student
{
    int num;
    char name[20];
};

student lucy;

②. 定义结构体类型的同时,定义结构体变量

struct student
{
    int num;
    char name[20];
}lucy;

student bob;

③. 定义一次性结构体类型

struct
{
    int num;
    char name[20];
}lucy;

3. 结构体变量的操作

①. 结构体变量的初始化

结构体变量如果不初始化,变量中的成员内容不确定(随机值)

结构体变量的初始化必须遵循成员的顺序以及成员自身的数据类型

#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

int main(int argc, char *argv[])
{
    Stu lucy = {"lucy",20};
    cout << lucy.name << "的年龄是:" << lucy.age << endl;
    return 0;
}

②. 清空整个结构体变量

有时候不需要给结构体变量初始化,但又不希望其成员内容随机,就需要使用memset清空结构体变量

void *memset(void *dest, int val, size_t n);
将地址从dest开始长度为n的所有字节赋值为val
#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

int main(int argc, char *argv[])
{
    Stu lucy;
    // 清空整个结构体变量
    memset(&lucy,0,sizeof(lucy));
    cout << lucy.name << "的年龄是:" << lucy.age << endl; // 的年龄是:0
    return 0;
}

③. 键盘给结构体变量中成员赋值

#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

int main(int argc, char *argv[])
{
    Stu lucy;
    // 清空整个结构体变量
    memset(&lucy,0,sizeof(lucy));
    cout << "请输入学生的姓名和年龄:";
    cin >> lucy.name >> lucy.age;
    cout << lucy.name << "的年龄是:" << lucy.age << endl;
    return 0;
}

④. 单独操作结构体中成员

单独操作结构体中成员必须遵循结构体自身的类型

#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

int main(int argc, char *argv[])
{
    Stu lucy = {"lucy",20};
    // name成员是数组名,符号常量,不允许使用=给name赋值
    // lucy.name = "bob"; // erro
    strcpy(lucy.name,"bob");
    lucy.age += 10;
    cout << lucy.name << "的年龄是:" << lucy.age << endl;
    return 0;
}

⑤. 相同类型的结构体变量之间赋值的方法

三种方法

#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

int main(int argc, char *argv[])
{
    Stu lucy = {"lucy",20};
    Stu bob1,bob2,bob3;

    // 第一种方法:逐个成员赋值(遵循成员类型)
    strcpy(bob1.name,lucy.name);
    bob1.age = lucy.age;

    // 第二种方法:相同类型的结构体变量可以直接赋值(推荐)
    bob2 = lucy;

    // 第三种方法:内存拷贝(第二种的底层实现)
    memcpy(&bob3,&lucy,sizeof(Stu));

    cout << "bob1:" << bob1.name << "的年龄是:" << bob1.age << endl;
    cout << "bob2:" << bob2.name << "的年龄是:" << bob2.age << endl;
    cout << "bob3:" << bob3.name << "的年龄是:" << bob3.age << endl;
    return 0;
}

⑥. 结构体嵌套结构体

#include 
#include

using namespace std;

struct Birthday
{
    int year;
    int month;
    int day;
};

struct Stu
{
    char name[20];
    int age;
    Birthday bd;
};

int main(int argc, char *argv[])
{
    Stu lucy = {"lucy",20,{2002,12,9}};

    cout << lucy.name << "的生日是:" << lucy.bd.year << "年" << lucy.bd.month << "月" << lucy.bd.day << "日" << endl;
    cout << lucy.name << "的年龄是:" << lucy.age << endl;
    return 0;
}

结构体嵌套结构体注意访问到最底层

4. 结构体数组

结构体数组:本质是数组,只是数组的每个元素为结构体变量

①. 结构体数组初始化

#include 
#include

using namespace std;

struct Birthday
{
    int year;
    int month;
    int day;
};

struct Stu
{
    char name[20];
    int age;
    Birthday bd;
};

int main(int argc, char *argv[])
{
    Stu student[3] = {{"lucy",20,{2002,12,9}},{"bob",22,{2000,3,11}},{"poppy",18,{2004,6,6}}};

    // 清空数组
    // memset(student,0,sizeof(student));

    int n = sizeof(student)/sizeof(student[0]);
    for(int i=0;i

②. 键盘给结构体赋值

#include 
#include

using namespace std;

struct Birthday
{
    int year;
    int month;
    int day;
};

struct Stu
{
    char name[20];
    int age;
    Birthday bd;
};

int main(int argc, char *argv[])
{
    Stu student[3];
    // {"lucy",20,{2002,12,9}},{"bob",22,{2000,3,11}},{"poppy",18,{2004,6,6}}
    memset(student,0,sizeof(student));
    int n = sizeof(student)/sizeof(student[0]);
    for(int i=0;i> student[i].name >> student[i].bd.year >> student[i].bd.month >> student[i].bd.day;
        student[i].age = 2022 - student[i].bd.year;
    }

    for(int i=0;i

5. 结构体指针变量

结构体的指针变量:本质是变量,只是该变量保存的是结构体变量的地址

①. 结构体指针变量的定义

#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

int main(int argc, char *argv[])
{
    Stu lucy = {"lucy",20};
    Stu *p = &lucy;

    // *p == lucy
    cout << lucy.name << "的年龄是:" << lucy.age << endl;
    cout << (*p).name << "的年龄是:" << (*p).age << endl;

    // 通过指针变量使用 -> 访问成员
    cout << p->name << "的年龄是:" << p->age << endl;
    cout << (&lucy)->name << "的年龄是:" << (&lucy)->age << endl;

    // 如果是地址可以直接使用 -> 访问成员;如果是结构体变量,使用 . 访问成员
    return 0;
}

②. 结构体数组元素的指针变量

指针变量保存结构体数组元素的地址

#include 
#include

using namespace std;

struct Stu
{
    char name[20];
    int age;
};

void inputStu(Stu *arr, int n)
{
    for(int i=0;i> arr[i].name >> arr[i].age;
    }
}

void sortStu(Stu *arr, int n)
{
    cout << "正在按年龄大小排序中,请稍候... ..." << endl;
    for(int i=0;iarr[j+1].age)
            {
                Stu temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    cout << "排序完成... ..." << endl;
}

void outputStu(Stu *arr, int n)
{
    for(int i=0;i

6. 结构体的指针成员

①. 结构体的指针成员定义

struct Stu
{
    int num;
    char *name;
};

Stu lucy = {100,"hello world"};

指针变量只有四字节,无法保存字符串整体,所以lucy.name保存时“hello world”的首元素地址,而“hello world”字符串本身存储再文字常量区

②. 结构体指针成员指向堆区

#include 
#include

using namespace std;

struct Stu
{
    int age;
    char *name;
};


int main(int argc, char *argv[])
{
    Stu lucy;
    lucy.age = 100;
    lucy.name = new char[32];
    strcpy(lucy.name,"hello world");
    cout << lucy.age << " " << lucy.name << endl;
    delete [] lucy.name;
    return 0;
}

③. 结构体的浅拷贝

相同类型的结构体变量可以整体赋值,默认赋值方式为:浅拷贝

浅拷贝:将结构体变量空间内容赋值一份到另一个相同类型的结构体变量空间中

如果结构体中没有指针成员,浅拷贝不会带来问题

如果结构体中有指针成员,浅拷贝会带来多次释放空间的问题(多个指针变量指向同一空间,会导致同一空间被释放多次)

struct Stu
{
    char *name;
    int age;
};

Stu lucy;
lucy.name = new char[32];
strcpy(lucy.name,"lucy");
lucy.age = 20;

Stu bob;
bob = lucy;

delete [] lucy.name;
delete [] bob.name;

④. 结构体的深拷贝

如果结构体中有指针成员,尽量使用深拷贝

深拷贝:为结构体的指针成员分配独立空间,再将内容拷贝

struct Stu
{
    char *name;
    int age;
};

Stu lucy;
lucy.name = new char[32];
strcpy(lucy.name,"lucy");
lucy.age = 20;

Stu bob;
bob.age = lucy.age;
bob.name = new char[32];
strcpy(bob.name,lucy.name);

delete [] lucy.name;
delete [] bob.name;

⑤. 结构体变量在堆区,结构体的指针成员也指向堆区

#include 
#include

using namespace std;

struct Stu
{
    int age;
    char *name;
};


int main(int argc, char *argv[])
{
    // 结构体在堆区
    Stu *p = new Stu;

    // 结构体中指针成员指向堆区
    p->name = new char[32];

    // 赋值
    p->age = 20;
    strcpy(p->name,"lucy");

    cout << p->name << "的年龄是:" << p->age << endl;

    // 释放空间,显示放成员指向,再释放结构体
    delete [] p->name;
    delete p;

    return 0;
}

7. 结构体对齐规则

struct Data
{
    char a;
    int b;
};

不对齐:

a b b b b      
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07

访问a:只需要一个周期 0x00 —0x03,数据只需要0x00

访问b:需要两个周期

                 第一个周期:0x00—0x03,数据需要 0x01—0x03

                 第二个周期:0x04—0x07,数据需要0x04,最后和0x01—0x03拼接

对齐:

a b b b b
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07

访问a:只需要一个周期 0x00 —0x03,数据只需要0x00

访问b:只需要一个周期 0x04—0x07

结构体自动对齐规则

1. 确认分配单位(一行分配多少字节)

结构体中最大的基本类型长度决定

2. 确认成员的偏移量

成员偏移量 = 成员自身类型的整数倍

3. 收尾工作

结构体的总大小 = 分配单位的整数倍

强制对齐规则

#program pack(value) 时的指定对齐值value

注意value值为1 2 4 8 16

1. 确定分配单位(一行分配多少字节)

分配单位 = min(结构体中最大的基本类型,value)

2. 确认成员的偏移量

成员偏移量 = 成员自身类型的整数倍

3. 收尾工作

结构体的总大小 = 分配单位整数倍

#program pack(8)
struct A
{
    char a;
    int b;
    short c;
};

min(结构体中最大的基本类型,value) = 4

a
b b b b
c c
#program pack(2)
struct A
{
    char a;
    int b;
    short c;
};

min(结构体中最大的基本类型,value) = 2

a
b b
b b
c c

8. 结构体的位域

①. 结构体位域的概述

在结构体中,以位为单位的成员,称之为位段(位域)

struct packed_data
{
    unsigned int a:2;
    unsigned int b:6;
    unsigned int c:4;
    unsigned int d:4;
    unsigned int i;
}data;

a的类型是 unsigned int a 的大小只占2位二进制位

没有非位域隔开的位域叫相邻位域

相同类型的相邻位域可以压缩,但是压缩的位数不能超过自身类型的大小

#include 
#include

using namespace std;

struct A
{
    unsigned char a:2;
    unsigned char b:2;
    unsigned char c:4;
};

int main(int argc, char *argv[])
{ 
    cout << "结构体A的大小为:" << sizeof(A) << endl; // 1
    return 0;
}

a占一个字节的两位,b占一个字节的两位,c占一个字节的三位,相同类型的相邻位域压缩后大小为一字节

不要对位域取地址

地址是以字节为单位分配地址编号的,最少为一个字节

对位域的操作不要超过自身的宽度

#include 
#include
#include

using namespace std;

struct A
{
    unsigned char a:2;
    unsigned char b:2;
    unsigned char c:4;
};

int main(int argc, char *argv[])
{
    A num;
    num.a = 11; // 1011
    cout << "输出是:" << bitset<2>(num.a) << endl; // 11
    // 只输出了低二位
    return 0;
}

②. 另起一个存储单元

#include 
#include

using namespace std;

struct A
{
    unsigned char a:4;
    unsigned char b:4;
};

struct B
{
    unsigned char a:4;
    unsigned char :0; // 另起一个存储单元
    unsigned char b:4;
};
int main(int argc, char *argv[])
{
    cout << "结构体A的大小为:" << sizeof(A) << endl; // 1
    cout << "结构体B的大小为:" << sizeof(B) << endl; // 2
    return 0;
}

③. 无意义位段

#include 
#include

using namespace std;

struct A
{
    unsigned char a:4;
    unsigned char :2;
    unsigned char b:2;
};

int main(int argc, char *argv[])
{
    cout << "结构体A的大小为:" << sizeof(A) << endl; // 1
    return 0;
}

④. 案例

8位寄存器

addr addr      opt  opt       data data
x y a b m n

addr:

00 设备1
01 设备2
10 设备3
11 设备4

opt:

00 建立连接
01 发送
10 接收
11 关闭连接

data:

00
01

需求:给设备3发送开灯指令

1 0 x 0 1 x 0 1
#include 
#include

using namespace std;

struct REG
{
    unsigned char data:2;
    unsigned char :1;
    unsigned char opt:2;
    unsigned char :1;
    unsigned char addr:2;
};

int main(int argc, char *argv[])
{
    REG reg;
    reg.addr = 2;
    reg.opt = 1;
    reg.data = 1;
    return 0;
}

二、共用体

结构体:所有成员拥有独立空间

共用体:所有成员共享一块空间

共用体关键字:union

共用体的空间是由最大的成员类型决定的

#include 
#include

using namespace std;

union Data
{
    char a;
    short b;
    int c;
};

int main(int argc, char *argv[])
{
    Data data;
    data.a = 10;
    data.b = 20;
    data.c = 30;
    cout << data.a+data.b+data.c << endl; // 30+30+30=90
    // 共用体共用同一空间
    return 0;
}

成员a,b,c共享同一块空间,但是每个成员能操作的空间的范围是由成员自身类型长度决定的

#include 
#include
#include
using namespace std;

union Data
{
    char a;
    short b;
    int c;
};

int main(int argc, char *argv[])
{
    Data data;
    data.c = 0x01020304;
    data.b = 0x0102;
    data.a = 0x01;
    cout << hex << data.a+data.b+data.c << endl; // 0x01020203
    return 0;
}
data.c 04 03 02 01
data.b 02 01 02 01
data.a 01 01 02 01
data.a 01 00 00 00
data.b 01 01 00 00
data.c 01 01 02 01
data.a+data.b+data.c 03 02 02 01

答案为:0x01020203

三、枚举

枚举:将枚举变量要赋的值一一列举出来

#include 
#include
#include

using namespace std;

enum POKER_COLOR{HongTao,MeiHua,FangKuai,HeiTao};

int main(int argc, char *argv[])
{
    POKER_COLOR my = HongTao;
    cout << my << endl; // 0
    cout << HongTao << endl << MeiHua << endl << FangKuai << endl << HeiTao << endl;
    // 0 1 2 3,其值是默认从0开始递增
    return 0;
}

如果修改某个枚举列表的值

#include 
#include
#include

using namespace std;

enum POKER_COLOR{HongTao,MeiHua=10,FangKuai,HeiTao};

int main(int argc, char *argv[])
{
    POKER_COLOR my = HongTao;
    cout << my << endl; // 0
    cout << HongTao << endl << MeiHua << endl << FangKuai << endl << HeiTao << endl;
    // 0 10 11 12,递增
    return 0;
}

你可能感兴趣的:(C++,c++)