C++笔记[结构体]
2020-11-29 03:06:08
标签: 笔记
分类 技术C++
这是第二篇c++笔记,原因是写在纸质笔记本上我并不会去看,写得慢还写得很乱写在博客你可能也不会去看
这一次是结构体,为什么连这个都要写呢?因为结构体里会有比较多容易混淆的东西,就比如我以为struct是可以换成别的东西,而没有认识到这是个关键字
而我刚开始就是轻视了这一块,独自去编译器里探索
探索了个寂寞
再然后,只能老老实实看老师的视频
在此写下笔记,以记录一些易忘点、易漏点,以及一些认识结构体的关键突破点。
当且仅当两个结构体类型相同时,才可以相互赋值。成员未初始化值的,在函数(包括main函数)外默认为0,否则为伪随机值(存储单元的值)。
这种赋值也叫位拷贝,即按位拷贝,逐位逐位地拷贝(其实只是和值拷贝区分)
结构体的成员在函数中作为参数时,有三种方法可以将其“导入”
一是用“类型+变量名”,得到一份copy,不操作原变量,函数内的拷贝操作完即销毁,存在开销大、用途有限(比如我只是想把数据拿出来看看,不对数据进行操作),缺点明显。
二是用指针,也就是结构体的首地址,拿根针在结构体的某个成员上移来移去表示对应的值,缺点是不够直观。
三是使用引用,对,这是福音,结构体可以使用引用,操作起来跟main函数内部没什么两样。当然了,会改变原结构体的信息,注意备份。
第三种在平常操作中很推荐,不过如果仅仅是打印、得出结论,不想改变原数据,那么第一种可以尝试。
初学还是建议非必要情况不要使用第一种。第二种为了练习指针的话,也浅尝辄止。
1.4
结构体中一般不考虑成员顺序对结构体占用字节的影响,而更加考虑代码的可维护性。至于代码的可维护性,局部的代码不太能阐述这件事情。成员顺序不同,结构体占用字节也有所不同,其原因是字节对齐
有关字节对齐,只要知道对齐是为了机器读取方便而进行的一种操作就可以了。
形如new int、new double、new char('a'),会开辟空间并返回地址,用delete 地址 来删除。
形如new int [10]、new char [20]等等,会开辟数组(包括字符数组)并返回数组首地址,用delete 首地址来删除
具体使用:*int p = new 类型[(初值)] [长度];
delete p;
delete后,指针p仍存在,仅仅是"new 类型[(初值)] [长度] "不见了。
这个指针可以通过p=nullptr清理回收。
这里强调一下,用nullptr,不用0,也不用NULL,nullptr是为了避免0和NULL在指针上产生问题而生的,用它就没错,而以后进一步学习,再来区分NULL、0、nullptr三者区别。
删除数组如果使用
注意,new的存储单元在运行时堆上,默认为随机值
头指针head:在结构体定时,最后一个成员后面,加上一个指针,便可以把每个具体的结构体连接起来
(请注意这仅仅是思想,具体步骤,请看Part II)
c++
//这是一段伪代码,visit和next均为特定的操作,并非c++内置。
Student* p =head;
while(p)//也就是p不为NULL时,就继续循环。结构体的最后一个是NULL。
{
    visit(p);
    p=p->next;//走向下一个
}
有BUG的代码
c++
void output(Student& s)
{
Student* p = &s;
for (int i = 0; i < 5; i++)
{
cout << p->No << ' ' << p->Name << ' ' << p->score[0] << ' ' << p->score[1] << ' ' << p->score[2] << ' ' << p[i].score[0] + p[i].score[1] + p[i].score[2] << 'n';//p是由s开始的结构体的(亦是首地址),后面调用时为s1
p++;
}
问题发现了;指针隔着跳了,结果总分是第1、3、5名的,显示在1 2 3名的总分上,而4 5名的总分是第7 9名的,并不存在,显示为0和-nan。-nan意为Not a Number,即NaN。
具体体现为,i=3时,p便已经在s5上了。
问题又来了,指针为什么会跳呢?
观察发现,input()和output()表示分数的方式,不一样,input()为p->score[数字]格式,output()的总分为p[i].score[数字]格式。
调试中看到,有关p[i]的描述在第四名和第五名上,都是错误的,没有指向正确对象。
再联想下,p和i都是每次循环自增1,在仅仅循环一次的情况下,怎么样才能让p移动2呢?那就是p和i同时增加1了,代码中确实有这个操作。
那问题就解决了,表示分数p自增1即可,跟i没有实质上的关系,i是个计数用的。
这应该是源于习惯问题,习惯上用计数器作移动的指标。
结果碰上格式混用,前面一部分纯指针,用->,没出问题,后一半用p[i],出问题了,双倍自增。
于是去掉[i],换用(*p)即可。(注意"."运算符比"*"运算符优先级高,括号不能去掉,否则是"地址.成员",而正确格式是"结构体.成员")
所以把总分的显示格式与i无关化即可,随便选择一种格式均可。如p->score[0],(*p).socre[0]。
这里另外一提,目前见过好几次while(变量)的结构,也就是等待变量归零再退出循环,常见的有:变量最终归为链表的最后一节nullptr。不过不知怎么的,虽然能用,感觉这么用相当不规范,而且首次见到可能不大好理解——while(next!=0)比这个好理解多了。老师的视频里出现了这种写法,暂且视作规范吧。(或许这就是简化的写法吧)
这是我个人给它的一个定义,给一个可用、实用的链表下的一个定义。
链表有三个要素:头指针(用于寻找数据位置),其他成员(用于数据储存),节点指针(形成链表的前提)。
整体来看,链表包括头指针、成员、节点指针和尾指针(一般值为nullptr,当然,你想做头尾相接的也行);而从结构体的角度来看,链表由一or多个结构体组成,链表中每个结构体尾部都有一个指针指向下一个结构体。
整体思想:每建立一个结构体,都让尾指针“next = new 结构体类型”;复制头指针,让这个复制的指针在链表上移动,以取得自己想要的数据。
具体操作,举例
c++
#include<iostream>
using namespace std;

struct Student
{
    int No;
    char Name[20];//名字最多9个汉字
    double score[3];//三门成绩
    Student* next;//节点指针,如果这个链表到尾部了,那么这是尾指针(暂时只考虑单向链表,也就是从头走到尾的)
}

int main()
{
    Student ChainList;//先在运行时栈创建一个结构体,其实head = new Student也可以
    Student* head = ChainList;//获取头指针
    ---录入数据---(链表操作在后面说,先创建,才能操作)
    head.next = new Student;//在运行时堆里new一个新的空结构体
}
到这里,虽然我们只创建了含两个结构体的链表,而且第二个还是空的,但接下来的事,其实也就在重复以上的操作
这就是连续输入数据。
创建函数时,十分建议引用,但指针也不差。所以刚刚上面给出了两种创建方式:在运行时栈里创建and运行时堆里创建。
前一种可以有名字,调用函数时,参数位置直接填写链表名,在函数里使用“链表名.成员= 想要赋的值”完事(也就是第一个结构体的名称,上例为ChainList,则如果我想录入分数,只需要:
c++
ChainList.score[0]=第一门成绩;
ChainList.score[1]=第二门成绩;
ChainList.score[2]=第三门成绩;
(其中,点"."是成员运算符,接下来的“->”是指向运算符)
后一种写个指针,然后input函数中使用“head->成员=想要赋的值”的格式给成员赋值。
c++
head->score[0]=第一门成绩;
head->score[1]=第二门成绩;
head->score[2]=第三门成绩;
但接下来两点才是重点。
①为了链表可以重复操作,你必须额外再创建两个指针,分别用于在链表的头指针处和最前端处,不妨先叫它们:moving_pointer和pioneer_pointer(移动指针和先锋指针,当然,是我瞎编的)。其中moving_pointer初值为head,并且在每次操作完链表后,加一句moving_pointer = head,将其值自动归为头指针的地址,以备下一次使用;另一个pioneer_pointer,一般为最后一个结构体的头指针,而在每次创建一个结构体时,更新为尾指针,然后获得的返回值,再次成为最后一个结构体的头指针,即
c++
pioneer_pointer = pioneer_pointer->next;
pioneer_pointer = new Student;
一般接着输入函数,请看例子:
c++
case 3://插入新的学生信息
if (n == 0)
{

cout << "[添加]请输入学生信息:n";
input(stu);
n++;
}
else if (n > 0)
{
cout << "[添加]请输入学生信息:n";

(*p).next = new Student;//此处的p即为pioneer_pointer
p = p->next;
input(*p);
}
p->next = nullptr;
if (moving_pin == nullptr)head = p;
break;
为什么需要它们?想一想,第一次输入完成时,对于引用型函数来说,下一次应该在它的参数位置填什么?还是ChainList吗?
显然不是。
上面代码的input()函数就是引用型的,我更换成了*p(先锋指针作为最后一个结构体的头指针,那么*p发挥了类似ChainList的作用),不然的话,下一次赋值将直接覆盖第一次的数据,并没有形成链表
类似于input()函数,只不过关于char字符数组的部分,需要用strcpy_s(字符串1,字符串2)。这里不知道为什么strcpy()不行,会报错,而strcpy_s()没有这个问题,以下input()函数仅供参考:
c++
void input(Student& v_stu)//我也忘记v是什么意思了,virtual?但形参是parameter。
{
cin >> v_stu.No >>v_stu.Name>> v_stu.score[0] >> v_stu.score[1] >> v_stu.score[2];
}
这里只简略说明下思路,代码放到另一篇文章里了(参考篇)。
插入,因为暂时不需要排序,所以每次往new出来的新空间里input就可以了(有排序那另说,其实也只不过是next指针的调换)。
删除,要分三种情况
一是去头,二是去尾,三是去中间。去头需要调动头指针,让头部往后挪;去尾在删掉数据后,让倒数第二个结构体的next指向新的空间即可;去中间,将前一个结构体的next指针赋值为将要被删的结构体的next指针,然后删除想删除的结构体成员即可。(将要被删除结构体的next指针其实就是它下一个结构体的头指针,这样就依然可以连起来)
参考数组排序的做法即可,就是指针交换那儿有点绕,多想想就好。
具体一点,我的方法是选择排序,一定一动,定与动比较,需要交换时,我们假设个结构体a和b吧,先给个中间变量t(是指针),先让t= a.next,再使a.next = b.next,最后b.next = t,()
选择排序,冒泡排序,目前我就学了这两种,而我还需要学八种。先放上典图。
Loading...
_20201211173838.png)
我也并不急于去理解它们,等到了时候再说吧。这里上代码,看起来就像把平面图形拉成了立体图形。(其实就是多输入几个)
这里给出的代码,要注意,我是另外备份了一个链表来排序的。
c++
void swap_not_including_pin(Student& a, Student& b)
{
Student T;//中间变量
strcpy_s(T.Name, a.Name); T.No = a.No; T.score[0] = a.score[0]; T.score[1] = a.score[1]; T.score[2] = a.score[2];//T = a
strcpy_s(a.Name, b.Name); a.No = b.No; a.score[0] = b.score[0]; a.score[1] = b.score[1]; a.score[2] = b.score[2];//a = b;
strcpy_s(b.Name, T.Name); b.No = T.No; b.score[0] = T.score[0]; b.score[1] = T.score[1]; b.score[2] = T.score[2];//b = t;
}
对比下简单变量的交换,就明白上面是什么意思了。
c++
void swap_not_including__pointer(int &a,int &b)
{
    int T;
    T = a;
    a = b;
    b = t;
}
以上,写得比较菜,定有疏漏之处,还恳请各位轻喷。比如链表的那两个“必须额外再复制两个指针”是否可以简化点思路?是否尝试过复制结构体的方法?
文章实质上可能是期末复习笔记之类的,凑合着像一篇教程