C/C++中结构体(struct)知识点强化

  在上一个教程中我们已经简单的阐述了什么是结构体了,为了进一部的学习结构体这一重要的知识点,我们今天来学习一下链表结构。

  结构体可以看做是一种自定义的数据类型,它还有一个很重要的特性,就是结构体可以相互嵌套使用,但也是有条件的,结构体可以包含结构体指针,但绝对不能在结构体中包含结构体变量。

struct test 

    char name[10]; 
    float socre; 

    test *next; 
};//这样是正确的!

struct test 

    char name[10]; 
    float socre; 

    test next; 
};//这样是错误的!

 利用结构体的这点特殊特性,我们就可以自己生成一个环环相套的一种射线结构,一个指向另一个。

  链表的学习不像想象的那么那么容易,很多人学习到这里的时候都会碰到困难,很多人也因此而放弃了学习,在这里我说,一定不能放弃,对应它的学习我们要进行分解式学习,方法很重要,理解需要时间,不必要把自己逼迫的那么紧,学习前你也得做一些最基本的准备工作,你必须具备对堆内存的基本知识的了解,还有就是对结构体的基本认识,有了这两个重要的条件,再进行分解式学习就可以比较轻松的掌握这一节内容的难点。

  下面我们给出一个完整的创建链表的程序,不管看的懂看不懂希望读者先认真看一下,想一想,看不懂没有关系,因为我下面会有分解式的教程,但之前的基本思考一定要做,要不即使我分解了你也是无从理解的。

  代码如下,我在重要部分做了注解:

#include <iostream> 
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 

    test *next; 
}; 
 
test *head;//创建一个全局的引导进入链表的指针 
 
test *create() 


    test *ls;//节点指针 
    test *le;//链尾指针 
    ls = new test;//把ls指向动态开辟的堆内存地址 

    cin>>ls->name>>ls->socre; 

    head=NULL;//进入的时候先不设置head指针指向任何地址,因为不知道是否一上来就输入null跳出程序 

    le=ls;//把链尾指针设置成刚刚动态开辟的堆内存地址,用于等下设置le->next,也就是下一个节点的位置 
 

    while(strcmp(ls->name,"null")!=0)//创建循环条件为ls->name的值不是null,用于循环添加节点 
    { 

        if(head==NULL)//判断是否是第一次进入循环 
        { 
            head=ls;//如果是第一次进入循环,那么把引导进入链表的指针指向第一次动态开辟的堆内存地址 
        } 

        else 
        { 

            le->next=ls;//如果不是第一次进入那么就把上一次的链尾指针的le->next指向上一次循环结束前动态创建的堆内存地址 

        } 
        le=ls;//设置链尾指针为当前循环中的节点指针,用于下一次进入循环的时候把上一次的节点的next指向上一次循环结束前动态创建的堆内存地址 

        ls=new test;//为下一个节点在堆内存中动态开辟空间 
        cin>>ls->name>>ls->socre; 
    } 

 
    le->next=NULL;//把链尾指针的next设置为空,因为不管如何循环总是要结束的,设置为空才能够在循环显链表的时候不至于死循环 

    delete ls;//当结束的时候最后一个动态开辟的内存是无效的,所以必须清除掉 
    return head;//返回链首指针 

 

void showl(test *head) 

    cout<<"链首指针:"<<head<<endl; 
    while(head)//以内存指向为null为条件循环显示先前输入的内容 
    { 
        cout<<head->name<<"|"<<head->socre<<endl; 

        head=head->next; 
    } 

 
void main() 


    showl(create()); 
    cin.get(); 

    cin.get(); 
}

  上面的代码我们是要达到一个目的:就是要存储你输入的人名和他们的得分,并且以链状结构把它们组合成一个链状结构。

  程序种有两个组成部分

test *create()

  和

void showl(test *head)

  这两个函数,create是用来创建链表的 ,showl是用来显示链表的。

  create函数的返回类型是一个结构体指针,在程序调用的时候我们用了showl(create());,而不用引用的目的原因是引导指针是一个全局指针变量,我们不能在showl()内改变它,因为showl()函数内有一个移动操作head=head->next;,如果是引用的话我们就破坏了head指针的位置,以至于我们再也无法找会首地址的位置了。

  下面我们来分解整个程序,以一个初学者的思想来思考整个程序,由浅入深的逐步解释。

  首先,我们写这个程序,要考虑到由于是一个链表结构,我们不可能知道它的大小到底是多大,这个问题我们可以用动态开辟堆内存来解决,因为堆内存在程序结束前始终是有效的,不受函数栈空间生命期的限制,但要注意的是我们必须有一个指针变量来存储这一链状结构的进入地址,而在函数内部来建立这一指针变量显然是不合适的,因为函数一旦退出,这个指针变量也随之失效,所以我们在程序的开始声明了一个全局指针变量。

test *head;//创建一个全局的引导进入链表的指针

  好解决了这两个问题,我们接下去思考

  有输入就必然有输出,由于输出函数和输入函数是相对独立的,为了不断测试程序的正确性好调试我们先写好输出函数和main函数捏的调用,创建函数我们先约定好名为create。

  我们先写出如下的代码:

#include <iostream> 
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 

    test *next; 
}; 
 
test *head;//创建一个全局的引导进入链表的指针 
 
test *create() 

 

    return head;//返回链首指针 


 
void showl(test *head) 


    cout<<"链首指针:"<<head<<endl; 

    while(head)//以内存指向为null为条件循环显示先前输入的内容 
    { 
        cout<<head->name<<"|"<<head->socre<<endl; 

        head=head->next; 
    } 

 
void main() 


    showl(create()); 
    cin.get(); 

    cin.get(); 
}

  程序写到这里,基本形态已经出来,输入和调用我们已经有了。

  下面我们来解决输入问题,链表的实现我们是通过循环输入来实现的,既然是循环我们就一定得考虑终止循环的条件,避免死循环和无效循环的发生。

  在create()函数内部我们先写成这样:

test *create() 

    test *ls;//节点指针 
    test *le;//链尾指针 

    ls = new test;//把ls指向动态开辟的堆内存地址 
    cin>>ls->name>>ls->socre; 

    head=NULL;//进入的时候先不设置head指针指向任何地址,因为不知道是否一上来就输入null跳出程序 

    le=ls;//把链尾指针设置成刚刚动态开辟的堆内存地址,用于等下设置le->next,也就是下一个节点的位置 
 

 
    le->next=NULL;//把链尾指针的next设置为空,因为不管如何循环总是要结束的,设置为空才能够在循环显链表的时候不至于死循环 

    delete ls;//当结束的时候最后一个动态开辟的内存是无效的,所以必须清除掉 
    return head;//返回链首指针 
}

  在循环创建之前我们必须考虑一个都不输入的情况。

  程序一单进入create函数我们首先必然要创建一个节点,我们先创建一个节点指针,后把者个节点指针指向到动态开辟的test类型的动态内存地址位置上。

  所以我们有了

test *ls;
ls = new test;

  程序既然是循环输入,而结构成员test
*next又是用来存储下一个接点的内存地址的,每次循环我们又要动态创建一个新的内存空间,所以我们必须要有一个指针来存储上一次循环动态开辟的内存地址,于是就有了

test *le;

  接下来在进入循环前我们要创建链表的第一个节点,第一个节点必然是在循环外创建,于是就有了

cin>>ls->name>>ls->socre;

  程序执行者的情况是位置的,所以我们必然要考虑,一上来就不想继续运行程序的情况,所以我们一开始先把head引导指针设置为不指向任何地址也就是

head=NULL;

  为了符合le也就是链尾指针的设计思路,我们在循环前一定要保存刚刚动态开辟的内存地址,好在下一次循环的时候设置上一个节点中的next成员指向,于是我们便有了:

le=ls;

  为了实现循环输入我们又了下面的代码:

while(strcmp(ls->name,"null")!=0)
{
if(head==NULL)

{
head=ls;
}
else
{
le->next=ls;
}
le=ls;

ls=new test;
cin>>ls->name>>ls->socre;
}

  程序是循环必然要有终止循环的条件,所以我们的循环条件是:

while(strcmp(ls->name,"null")!=0)

  输入的名字是null的时候就停止循环。

  为了保证第一次进入循环,也就是在循环内准备创建第二个节点前,设置引导指针的指向我们有了如下的判断代码:

if(head==NULL)
{
head=ls;
}
else
{

le->next=ls;
}

  代码中的else条件是为了设置前一个节点next指向而写的,这点我们记住先看下面的代码,稍后大家回过头想就明白了

le=ls;
ls=new test;

cin>>ls->name>>ls->socre;

  le=ls;这么写就是为了保存上一次循环指针的位置而设的,正是为了上面的else代码而做的预先保留

ls=new test;
cin>>ls->name>>ls->socre;

  这两行代码的意思就是继续开辟下一个节点空间,和输入节点内容!

  循环一旦结束也就结束了程序,为了保持程序不出错,也就是最后一个节点的next成员指向为空我们有了下面的代码

le->next=NULL;

  程序的思路始终是以先开辟后判断为思路的,所以到最后一个不成立的时候总会有一个多开辟的内存空间,为了删除掉它,我们有了下面的代码

delete ls;

  程序到最后由于返回head指针

return head;

  显示链表的函数没有什么太多特别的也只需要注意下面这样就可以了!

head=head->next;

  我们之所以不用head+=1;来写就是因为链表是我们动态开辟的,而每一个节点的位置并不是相连的,next成员指针的意义也就是下一个节点的内存地址。

  到这里整个创建函数的设计思路也都说完了,笔者不一定说的很好,但基本思路是这样的,希望读者多思考,多对比,相信此教程还是对大家有帮助的,程序设计就是利用逐步思考的方式进行的,写好的代码往往直接看看不懂就是因为中间的细节并不是一次都能够想到的。

  下面我们来说一下链表节点的删除!

  我们以上面的程序为基础,但为了我们方便学习删除我们休整结构体为

struct test
{
int number;
float socre;
test *next;

};

  number为唯一的编号每一个节点的。

  删除的我就不多说了,里面重要部分有注解。

iostream> 
using namespace std; 
struct test 

    int number; 
    float socre; 

    test *next; 
}; 
test *head;//创建一个全局的引导进入链表的指针 
 
test *create() 


    test *ls;//节点指针 
    test *le;//链尾指针 
    ls = new test;//把ls指向动态开辟的堆内存地址 

    cin>>ls->number>>ls->socre; 

    head=NULL;//进入的时候先不设置head指针指向任何地址,因为不知道是否一上来就输入null跳出程序 

    le=ls;//把链尾指针设置成刚刚动态开辟的堆内存地址,用于等下设置le->next,也就是下一个节点的位置 

    while(ls->number!=0)//创建循环条件为ls->number的值不是null,用于循环添加节点 
    { 

        if(head==NULL)//判断是否是第一次进入循环 
        { 
            head=ls;//如果是第一次进入循环,那么把引导进入链表的指针指向第一次动态开辟的堆内存地址 
        } 

        else 
        { 

            le->next=ls;//如果不是第一次进入那么就把上一次的链尾指针的le->next指向上一次循环结束前动态创建的堆内存地址 

        } 
        le=ls;//设置链尾指针为当前循环中的节点指针,用于下一次进入循环的时候把上一次的节点的next指向上一次循环结束前动态创建的堆内存地址 

        ls=new test;//为下一个节点在堆内存中动态开辟空间 
        cin>>ls->number>>ls->socre; 
    } 

    le->next=NULL;//把链尾指针的next设置为空,因为不管如何循环总是要结束的,设置为空才能够在循环显链表的时候不至于死循环 

    delete ls;//当结束的时候最后一个动态开辟的内存是无效的,所以必须清除掉 
    return head;//返回链首指针 


void showl(test *head) 

    cout<<"链首指针:"<<head<<endl; 
    while(head)//以内存指向为null为条件循环显示先前输入的内容 
    { 
        cout<<head->number<<"|"<<head->socre<<endl; 

        head=head->next; 
    } 

void deletel(test *&head,int number)//这里如果参数换成test *head,意义就完全不同了,head变成了复制而不是原有链上操作了,特别注意,很多书上都不对这里 


    test *point;//判断链表是否为空 
    if(head==NULL) 
    { 
        cout<<"链表为空,不能进行删除工作!"; 
        return; 
    } 
    if(head->number==number)//判删除的节点是否为首节点 
    { 
        point=head; 

        cout<<"删除点是链表第一个节点位置!"; 

        head=head->next;//重新设置引导指针 

        delete point; 
        return; 
    } 
    test *fp=head;//保存连首指针 
    for(test *&mp=head;mp->next;mp=mp->next) 
    { 

        if(mp->next->number==number) 

        { 
            point=mp->next; 

            mp->next=point->next; 
            delete point; 
            head=fp;//由于head的不断移动丢失了head,把进入循环前的head指针恢复! 
            return; 
        } 
    } 

void main() 


    head=create();//调用创建 
    showl(head); 

    int dp; 
    cin>>dp; 
    deletel(head,dp);//调用删除 
    showl(head); 
    cin.get(); 
    cin.get(); 

}

  最后我学习一下如何在已有的链表上插入节点

  我们要考虑四中情况,

  1.链表为空!

  2.插入点在首节点前

  3.插入点找不到的情况我们设置放在最后!

  4.插入点在中间的情况!

  今天的程序在昨天的基础上做了进一步的修改,可以避免删除点找不到的情况,如果找不到删除点就退出函数!

#include <iostream> 
using namespace std; 
struct test 

    int number; 
    float socre; 

    test *next; 
}; 
test *head;//创建一个全局的引导进入链表的指针 
 
test *create() 


    test *ls;//节点指针 
    test *le;//链尾指针 
    ls = new test;//把ls指向动态开辟的堆内存地址 

    cout<<"请输入第一个节点number和节点score,输入0.0跳出函数"<<endl; 

    cin>>ls->number>>ls->socre; 

    head=NULL;//进入的时候先不设置head指针指向任何地址,因为不知道是否一上来就输入null跳出程序 

    le=ls;//把链尾指针设置成刚刚动态开辟的堆内存地址,用于等下设置le->next,也就是下一个节点的位置 

    while(ls->number!=0)//创建循环条件为ls->number的值不是null,用于循环添加节点 
    { 

        if(head==NULL)//判断是否是第一次进入循环 
        { 
            head=ls;//如果是第一次进入循环,那么把引导进入链表的指针指向第一次动态开辟的堆内存地址 
        } 

        else 
        { 

            le->next=ls;//如果不是第一次进入那么就把上一次的链尾指针的le->next指向上一次循环结束前动态创建的堆内存地址 

        } 
        le=ls;//设置链尾指针为当前循环中的节点指针,用于下一次进入循环的时候把上一次的节点的next指向上一次循环结束前动态创建的堆内存地址 

        ls=new test;//为下一个节点在堆内存中动态开辟空间 
        cout<<"请下一个节点number和节点score,输入0.0跳出函数"<<endl; 

        cin>>ls->number>>ls->socre; 
    } 

    le->next=NULL;//把链尾指针的next设置为空,因为不管如何循环总是要结束的,设置为空才能够在循环显链表的时候不至于死循环 

    delete ls;//当结束的时候最后一个动态开辟的内存是无效的,所以必须清除掉 
    return head;//返回链首指针 


void showl(test *head) 

    cout<<"链首指针:"<<head<<endl; 
    while(head)//以内存指向为null为条件循环显示先前输入的内容 
    { 
        cout<<head->number<<"|"<<head->socre<<endl; 

        head=head->next; 
    } 

void deletel(test *&head,int number)//这里如果参数换成test *head,意义就完全不同了,head变成了复制而不是原有链上操作了,特别注意,很多书上都不对这里 


    test *point;//判断链表是否为空 
    if(head==NULL) 
    { 
        cout<<"链表为空,不能进行删除工作!"; 
        return; 
    } 
 
    int derror=1;//设置找不到的情况的条件,预先设置为真 

    test *check=head; 
    while(check)//利用循环进行查找 
    { 
        if (check->number==number) 
        { 

            derror=0;//条件转为假 
        } 

        check=check->next; 
    } 
    if(derror)//如果为假就跳出函数 
    { 

        return; 
    } 
 
    if(head->number==number)//判删除的节点是否为首节点 
    { 
        point=head; 

        cout<<"删除点是链表第一个节点位置!"; 

        head=head->next;//重新设置引导指针 

        delete point; 
        return; 
    } 
    test *fp=head;//保存连首指针 
    for(test *&mp=head;mp->next;mp=mp->next) 
    { 

        if(mp->next->number==number) 

        { 
            point=mp->next; 

            mp->next=point->next; 
            delete point; 
            head=fp;//由于head的不断移动丢失了head,把进入循环前的head指针恢复! 
            return; 
        } 
    } 

 
void insterl(int number) 


    test *point=new test; 
    cout<<"请输入节点number和节点score"<<endl; 

    cin>>point->number>>point->socre; 
 

    if(head==NULL)//链表为空的情况下插入 
    { 
        head=point; 

        point->next=NULL; 
        return; 

    } 
 
    int ierror=1;//设置找不到的情况的条件,预先设置为真 
    test *le; 

    test *check=head; 
    while(check)//利用循环进行查找 
    { 
        if (check->number==number) 
        { 

            ierror=0;//条件转为假 
        } 

        le=check; 
        check=check->next; 
    } 

    if(ierror) 
    { 
        cout<<le->number; 
        le->next=point; 

        point->next=NULL; 
        return; 

    } 
 
   if(head->number==number)//检测是否是在第一个节点处插入 
    { 

        point->next=head; 
        head=point; 
        return; 
    } 
 
    for(test *&mp=head;mp->next;mp=mp->next)//在链表中间插入 
    { 
        if(mp->next->number==number) 
        { 

            point->next=mp->next; 
            mp->next=point; 

            return; 
        } 
    } 
 


void main() 


    head=create();//调用创建 
    showl(head); 

    int dp; 
    cout<<"请输入删除点如果找不到就跳出函数"<<endl; 
    cin>>dp; 
    deletel(head,dp);//调用删除 
    showl(head); 
    int ip; 
    cout<<"请输入插入点如果找不到就在链尾添加"<<endl; 
    cin>>ip; 
    insterl(ip); 
    showl(head); 

    cin.get(); 
    cin.get(); 
}

  到此关于结构体的内容已经全部讨论结束,链表的建立删除插入操作可以很好的对前面所学知识进行一个总结,它既考察了程序员对内存大理解(堆内存操作、指针操作)也考察了对结构化编程掌握的熟悉程序。

  以后的教程我们将着重训练面向对象的编程的相关知识点。

 
 

《挑战30天C++入门极限》C/C++中结构体(struct)知识点强化的更多相关文章

  1. 《挑战30天C++入门极限》C++中利用构造函数与无名对象简化运算符重载函数

        C++中利用构造函数与无名对象简化运算符重载函数 在完整描述思想之前,我们先看一下如下的例子,这个例子中的加运算符重载是以非成员函数的方式出现的: //程序作者:管宁  //站点:www.cn ...

  2. 《挑战30天C++入门极限》c++中指针学习的两个绝好例子

        c/c++中指针学习的两个绝好例子 对于众多人提出的c/c++中指针难学的问题做个总结: 指针学习不好关键是概念不清造成的,说的简单点就是书没有认真看,指针的学习犹如人在学习饶口令不多看多学多 ...

  3. 《挑战30天C++入门极限》C++面向对象编程入门:类(class)

        C++面向对象编程入门:类(class) 上两篇内容我们着重说了结构体相关知识的操作. 以后的内容我们将逐步完全以c++作为主体了,这也意味着我们的教程正式进入面向对象的编程了. 前面的教程我 ...

  4. 《挑战30天C++入门极限》C++类静态数据成员与类静态成员函数

        C++类静态数据成员与类静态成员函数 在没有讲述本章内容之前如果我们想要在一个范围内共享某一个数据,那么我们会设立全局对象,但面向对象的程序是由对象构成的,我们如何才能在类范围内共享数据呢? ...

  5. 《挑战30天C++入门极限》新手入门:C/C++中的结构体

        新手入门:C/C++中的结构体 什么是结构体? 简单的来说,结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构 ...

  6. 《挑战30天C++入门极限》新手入门:关于C++中的内联函数(inline)

        新手入门:关于C++中的内联函数(inline) 在c++中,为了解决一些频繁调用的小函数大量消耗栈空间或者是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数. 可能说到这里,很 ...

  7. 《挑战30天C++入门极限》新手入门:C/C++中枚举类型(enum)

        新手入门:C/C++中枚举类型(enum) 如果一个变量你需要几种可能存在的值,那么就可以被定义成为枚举类型.之所以叫枚举就是说将变量或者叫对象可能存在的情况也可以说是可能的值一一例举出来. ...

  8. 《挑战30天C++入门极限》新手入门:C/C++中数组和指针类型的关系

        新手入门:C/C++中数组和指针类型的关系 对于数组和多维数组的内容这里就不再讨论了,前面的教程有过说明,这里主要讲述的数组和指针类型的关系,通过对他们之间关系的了解可以更加深入的掌握数组和指 ...

  9. 《挑战30天C++入门极限》入门教程:实例详解C++友元

        入门教程:实例详解C++友元 在说明什么是友元之前,我们先说明一下为什么需要友元与友元的缺点: 通常对于普通函数来说,要访问类的保护成员是不可能的,如果想这么做那么必须把类的成员都生命成为pu ...

随机推荐

  1. 可分离滤波器设计高斯滤波 CUDA程序优化, 实验记录

    环境:RTX2060 ,1920X1080p ,循环10次, kernal_size=8 一 .测试前128个线程拷贝到dst数据的性能  ,只测试行卷积, block=(128+2r)X1 1. 使 ...

  2. Flutter Animation AnimatedBuilder

    Flutter AnimatedBuilder 创建动画的widget Key key, @required Listenable animation, @required this.builder, ...

  3. 关于Eclipse导入maven项目报空指针异常

    今天新建了一个maven项目,因为是通过公司的工具新建的,代码拉下来就有src.pom.xml文件. 导入Eclipse却报空指针异常.具体如下: An error has occurred. See ...

  4. react之高阶组件(二)

    高阶组件的使用 接上文———— 一.像函数一样直接调用 import React, { Component } from 'react' import A from './A' class C ext ...

  5. require.context实现前端工程自动化

    require.context是什么 一个webpack的api,通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多 ...

  6. Oracle 11g 物理存储结构

    Oracle 系统的物理存储结构比较具体和直观,它用来描述 Oracle 数据在磁盘上的物理组成情况.Oracle 系统的数据在逻辑上存储在表空间中,而在物理上存储在表空间所包含的物理文件(即数据文件 ...

  7. Python学习笔记【1】

    1.%r和%s的区别 (1)stackflow 上面的一个解答 (2) x = "There are %d types of people." %10 binary = " ...

  8. flask_mail使用

    python3里发送邮件使用smtplib模块,内置得,不用下载安装,直接导入即可 smtplib使用实例 import smtplib from email.mime.text import MIM ...

  9. robot framework笔记(二):在RF中自定义chrome启动参数

    (一)在RF中自定义chrome启动参数 这里主要是实现下面2个功能 1.禁用chrome正受自动测试软件控制的提示 2.设置默认的下载路径(一些导出.下载类的功能,将文件下载到指定路径下) 自定义一 ...

  10. redis 异常 MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk

    MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. 解决方 ...