操作类对象

来源:互联网 发布:dwf转换cad软件 编辑:程序博客网 时间:2024/06/16 19:10
1. 类的指针和引用(1)指针和引用    在c语言中,指针是进行高效访问内存的手段,但是即便如此仍然涉及空间开辟和赋值    操作。在c++中,引入了比指针更加高效的传参方式,那就是引用,不需要任何的赋值    操作。指针和引用作为高效使用内存的方式,在c++中,对于类和对象来说同样有着频繁的使用。(2)引用并不能完全替代指针    实际上引用虽然比指针的效率更高,但是引用并不能完全的替代指针。引用有一个缺    点就是,引用只能初始化,不能赋值,但是指针不是的,指针可以通过重新赋值指针    任何需要的空间。(3)指针在类中的应用    (1)传参    (2)类的成员        (1)函数指针        (2)普通类型指针        (2)对象指针 2. 类的访问权限修饰符访问权限限制符有三种,public,private,protected,用于限制成员的访问权限,是类封装不可取少的关键字,前面说过,类成员默认就是private修饰。这三种符号在一起联合使用时,有九种组合,但实际上如果采用记忆的当时去记住这九种组合并不可取。如果想要完全弄清楚这三个符号作用的话,它们是有规律可循的,这个规律需要分为有继承和没有继承的两种情况来展开讲述。(1)没有继承时的情况(1)public    在没有继承时,public表示来成员可以被外部访问,外部只需要通过./->/::进行访问。(2)protected/private    在不考虑继承的情况下,private和protected权限的效果一样,表示隐藏成员,外    部不能直接通过./->/::对成员进行访问,只能通过公共成员函数接口访问隐藏成员。    private和protected到底有什么区别,涉及继承的时候才能完全的讲清楚。    类中的公共成员大多都是用作接口的成员函数,而类中几乎所有的数据成员都是使用    private或者protected进行隐藏的。(2)有继承时的情况当涉及继承时,这三个符号在修饰父类和子类的专属成员时(不包含子类继承自父类的成员),其作用与上面所描述的一致。子类分别有public/private/protected这三种继承父类的方式,哪些子类中继承自父类的成员,它们在子类中的新权限会受到继承方式的影响。public/private/protected这三种继承父类的方式具体是怎么影响继承的,我们讲到继承时再做详细讲解3. 动态分配对象空间(1)静态对象空间(2)局部对象空间(3)动态分配空间    这个在前面章节实际上已经讲到过。5. 类的嵌套(1)内部嵌套定义对象成员    例子:        #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    using namespace std;    class Birthday    {    public:            Birthday(const string year="2000", const string month="12", \                    const string day="12"):year(year), month(month), day(day) {}            string get_year() {                    return year;            }            string get_month() {                    return month;            }            string get_day() {                    return day;            }    private:            string year;            string month;            string day;      };      class Student    {    public:            Student(const string name="name", int num=1, const string year="2008", \                    const string month="1", const string day="1")                    : name(name), num(num), birthday(year, month, day) { }            Birthday &get_birthday() {                    return birthday;            }    private:            string name;            int num;            Birthday birthday;    };    int main(void)    {            Student stu1("zhangsan", 33);            cout<<"年:"<< stu1.get_birthday().get_year() << endl;            cout<<"月:"<< stu1.get_birthday().get_month() << endl;            cout<<"日:"<< stu1.get_birthday().get_day() << endl;            return 0;    }           例子分析:    本例子中,定义了两个类,一个是Student类,另一个是Birthday类,其中    在Student类中定义了一个Birthday类的成员。    注意本例子中对Student类中的Birthday成员的初始化方式。(2)使用对象指针的方式例子:仍然使用上面的例子,但是需要做些改动。Birthday birthday;改为Birthday *birthday;Birthday的构造函数的初始化列表中的birthday(year, month, day)改为birthday(new Birthday(year, month, day))将stu的定义和年月日的打印Student stu1("zhangsan", 33);    cout<<"年:"<< stu1.get_birthday().get_year() << endl;    cout<<"月:"<< stu1.get_birthday().get_month() << endl;    cout<<"日:"<< stu1.get_birthday().get_day() << endl;改为Student *stu1 = new Student("zhangsan", 33);    cout<<"年:"<< stu1->get_birthday()->get_year() << endl;    cout<<"月:"<< stu1->get_birthday()->get_month() << endl;    cout<<"日:"<< stu1->get_birthday()->get_day() << endl;最后别忘了加一个释放动态对象空间的语句。delete stu1;例子分析:以上改动完成之后就实现了动态分配对象空间,动态对对象空间开辟自堆空间,与c语言中利用malloc函数在堆中开辟空间没有任何区别。 实际上这个例子中存在一个程序隐患,因为stu1对象的birthday指向的对象空间并没有被释放,这就会导致内存泄漏,是非常大的隐患。(3)定义类的内部类(1)什么是内部类    直接通过一个例子理解什么是内部类,还是以上面的例子为例,我们将    Birthday定义为Student的内部类。    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    using namespace std;    class Student    {    private:            class Birthday            {            public:                    Birthday(const string year="2000", const string month="12", \                            const string day="12"):year(year), month(month), day(day) {}                    string get_year() {                            return year;                    }                    string get_month() {                            return month;                    }                    string get_day() {                            return day;                    }            private:                    string year;                    string month;                    string day;            };        public:            Student(const string name="name", int num=1, const string year="2008", \                    const string month="1", const string day="1")                    : name(name), num(num), birthday(new Birthday(year, month, day)) { }            Birthday *get_birthday() {                    return birthday;            }    private:            string name;            int num;            Birthday *birthday;    };    int main(void)    {            Student *stu1 = new Student("zhangsan", 33);            cout<<"年:"<< stu1->get_birthday()->get_year() << endl;            cout<<"月:"<< stu1->get_birthday()->get_month() << endl;            cout<<"日:"<< stu1->get_birthday()->get_day() << endl;            delete stu1;            return 0;    }       例子分析:在本例子中,我们将Birthday类定义为了Student类的私有内部    类,其他的操作与前面例子并没有什么区别。(2)内部类的意义    如果我们某写类需要使用属于自己的专属的内部子类时,我们就可以为其定    义内部类。(3)隐藏的和公共的内部类    (1)隐藏内部类        我们定义内部类时,主要就是为了专享使用该内部类,因此我们一        般都会将其修饰为private或者protected,将它内部类隐藏起来,        外部是不能使用该类来实例化对象的。        上面所举的内部类的例子就是典型的隐藏内部类。    (2)公共的内部类        内部类也可以将其声明为public,如果声明为public的话,在外部        就可以访问该内部类。        如果我们将上面例子中的内部类改为public的话,在main函数中就可        以使用定义的内部类Birthday来实例化对象,定义形式如下:        Student::Birthday brthday;              如果将内部类声明为public的话,与直接在外部定义该类的使用效果        差不多,所以都会将其直接定义为外部类。我们如使用的内部类时,        很多情况下都会将其隐藏。(4)通过友元实现专属内部类一样的效果    只用友元实现专属内部类的效果时,还是以上面的例子为例,实现步骤是:    (1)将内部Birthday类改为外部类    (2)将Birthday的所有成员全部定义为隐藏    (3)将Student类声明为Birthday类的友元    通过以上这三步,Birthday就变成了Student的专属类,因为Birthday的构造函数    是隐藏的,也没有其它实例化的接口,因此在外部无法实例化Birthday对象,    只有友元Student才能调用Birthday的构造函数实例化Birthday的对象。    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    using namespace std;    class Birthday    {                Birthday(const string year="2000", const string month="12", \                    const string day="12"):year(year), month(month), day(day) {}            string get_year() {                    return year;            }               string get_month() {                    return month;            }               string get_day() {                    return day;            }               string year;            string month;            string day;              friend class Student;    };    class Student    {    public:            Student(const string name="name", int num=1, const string year="2008", \                    const string month="1", const string day="1")                    : name(name), num(num), birthday(new Birthday(year, month, day)) { }            Birthday *get_birthday() {                    return birthday;            }            string get_year() {                    return birthday->get_year();            }            string get_month() {                    return birthday->get_month();            }            string get_day() {                    return birthday->get_day();            }    private:            string name;            int num;            Birthday *birthday;    };    int main(void)    {            Student *stu1 = new Student("zhangsan", 33);            cout<<"年:"<< stu1->get_year() << endl;            cout<<"月:"<< stu1->get_month() << endl;            cout<<"日:"<< stu1->get_day() << endl;            delete stu1;            return 0;    }    例子分析:在本例子中,由于Birthday类的成员全部设置为了隐藏,因此    main函数中的如下语句是无法通过编译的,            cout<<"年:"<< stu1->get_birthday()->get_year() << endl;            cout<<"月:"<< stu1->get_birthday()->get_month() << endl;            cout<<"日:"<< stu1->get_birthday()->get_day() << endl;    这是因为,我们试图在外部访问Birthday中私有的getter函数,这显然是不    行的,由于我们将Student设置为了Birthday的友元,因此我们需要完全借助    Student类的对象才能访问Birthday中的成员,因此我们在Student中加了三    个访问Birthday获取器的代理成员函数:        string get_year() {                    return birthday->get_year();            }            string get_month() {                    return birthday->get_month();            }            string get_day() {                    return birthday->get_day();            }    当然我们呢也可以直接将函数中的        return birthday->get_year();        return birthday->get_month();        return birthday->get_day();    改为        return birthday->year;        return birthday->month;        return birthday->day;    然后再main函数中通过调用Student中的三个代理访问Birthday成员的函数    才能将Birtday的成员数据打印出来,调用方式如下:        cout<<"年:"<< stu1->get_year() << endl;            cout<<"月:"<< stu1->get_month() << endl;            cout<<"日:"<< stu1->get_day() << endl;(5)当类进行嵌套时,构造函数的执行的顺序    (1)构造函数中涉及初始化和赋值的问题        在上一章中我们强调过,在构造函数的{ }涉及的是赋值操作,但是        构造函数中的初始化列表涉及的是初始化操作的,它们的区别是,        初始化是由编译器一早就安排好的,效果就是一旦开辟空间就立即        向空间写值。而的赋值操作是先开辟空间然后再向空间赋值。        构造函数中赋值与初始化的区别将直接影响类在嵌套时,它们的        构造函数被调用的顺序。    (2)例子        #include <stdio.h>        #include <stdlib.h>        #include <string.h>        #include <iostream>        #include <string>        #include <cassert>        using namespace std;        class A        {            public:                A(int a=1){                        cout<<"A的构造函数"<<endl;                }           };        class B        {                A *a;         public:                B(int b=1):a(new A(1)) {                        cout<<"B的构造函数"<<endl;                }                   };        class C        {                B *b;         public:                C(int c=1) {                        cout<<"C的构造函数"<<endl;                        b = new B(1);                }        };        int main(void)        {                C c(1);                return 0;        }    运行结果:        C的构造函数        A的构造函数        B的构造函数    例子分析:    例子中C类包含了B类的对象,B类包含了A类的对象,但是为什么构造函数    被执行的顺序是CAB呢?    执行的顺序如下:    当在main函数中执行C c(1);              回到main函数        |                           A        |                       |        V                       |    C类的构造函数被调用                  |    执行 cout<<"C的构造函数"<<endl;                |    执行 b = new B(1);                            C类的构造函数执行完毕        |                       A        V                       |    B类的构造函数被调用                  |    利用a(new A(1))初始化对象a                 执行 cout<<"B的构造函数"<<endl;        |                       A        V                       |    A类构造函数被调用                   |    执行 cout<<"A的构造函数"<<endl;                |    A构造函数执行结束--------------------------------------->    从上面的分析看出,构造函数的初始化列表优先于{ }中的的赋值语句的执行。    构造函数被调用执行是一个递归的过程。          4. 重要的析构函数(1)前面动态分配对象空间例子中的隐患在前面动态分配对象空间的例子中,其实是有问题的,那就是Student中Birthday指针指向的Birthday类对象,该对象空间分配于堆空间,因此需要释放,但是我们并没有释放它,这个问题将会留给后面需要讲到的析构函数去解决。   (2)析构函数 (1)析构函数的作用    每个对象空间被释放时,都会调用析构函数,析构函数的目的主要是为了    实现对对象的扫尾操作。    对于类中的分配于堆空间的类类型的成员对象来说,其空间是不会自动被释    放的,因为这是程序员的事情,而这些释放空间的操作往往就放在析构函数    中来做。(2)默认的析构函数    实际上,如果我们自己不定义析构函数的话,每个类都有一个默认的析构    函数,这个析构函数是在编译时提供的,这个析构函数是不可见的。(3)析构函数的格式    与构造函数几乎相同,只是需要在函数名亲前面加~,析构函数没有形参    ,但是实际上它会得到一个默认参数,就是this指针,因为析构函数需要    操作成员,这些成员需要使用this访问,只是一般情况下,并不会显式使    用this访问成员。    析构函数定义格式如下(以前面的Student类为例):    ~Student() {    ......//默认析构函数没有内容    }    或者    Student::~Student() {    ......//默认析构函数没有内容    }(4)调用析构函数    (1)析构函数什么时候被调用        当对象空间被释放时,析构函数将会被调用        (1)静态对象:当整个程序结束时,分配于静态空间的对象才会被释放            (1)全局变量            (2)静态局部变量        (2)自动局部对象            函数调用结束后,自动局部对象的空间即会被释放        (3)自动分配的对象            自动分配对象空间的释放            (1)程序结束后,一定会释放            (2)主动调用delete释放            而这的区别是delete释放空间时会调用析构函数,但是程序结束            后释放自动分配对象空间的方式则不会调用析构函数。            实际开发中,很多程序基本都会长时间运行,甚至说永远运行,            在这一类的程序中,如果存在大量的自动分对象的话,一定要主            动释放,否则严重的内存泄漏会直接导致程序崩溃。    (2)举例        还是前面的学生例子,但是将其main函数的内容改成如下形式。        #include <stdio.h>        #include <stdlib.h>        #include <string.h>        #include <iostream>        #include <string>        #include <cassert>        using namespace std;        class Birthday        {                    Birthday(const string year="2000", const string month="12", \                        const string day="12"):year(year), month(month), day(day) {}                string get_year() {                        return year;                }                   string get_month() {                        return month;                }                   string get_day() {                        return day;                }                   string year;                string month;                string day;                  friend class Student;        };        class Student        {        public:                Student(const string name="name", int num=1, const string year="2008", \                        const string month="1", const string day="1")                        : name(name), num(num), birthday(new Birthday(year, month, day)) { }            ~Student() {                cout << "Student的析构函数" << endl;                delete birthday;            }                Birthday *get_birthday() {                        return birthday;                }                string get_year() {                        return birthday->get_year();                }                string get_month() {                        return birthday->get_month();                }                string get_day() {                        return birthday->get_day();                }        private:                string name;                int num;                Birthday *birthday;        };        void fun() {                Student *stu1 = new Student("zhangsan", 33);                Student stu2("zhangsan", 33);            static Student stu3("zhangsan", 33);                cout<<"年:"<< stu1->get_year() << endl;                cout<<"月:"<< stu1->get_month() << endl;                cout<<"日:"<< stu1->get_day() << endl;                delete stu1;        }        Student stu4("zhangsan", 33);        int main(void)        {                fun();        //      while(1);                return 0;        }        例子分析:        fun函数中的stu1是自动分配的,需要delete才能释放,并会调用析构函数        fun函数中的stu2是fun函数的局部变量,当函数运行结束后会自动释放,并会调用析构函数        fun函数中stu3是静态局部变量,只有当整个程序运行结束之后才会释放,并会调用析构函数        全局变量stu4是静态变量,同样的,也只有当整个程序运行结束之后才会释放,并会调用析构函数        对他们进行空间释放并调用析构函数的顺序是:        先是stu2        再是stu1        最后程序结束时才是stu3和stu4    (5)类有嵌套时,析构函数的调用顺序        (1)当类嵌套时析构函数的调用顺序    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    using namespace std;    class A    {    public:            A(int a=1){ }            ~A() {                    cout<<"A的析构函数"<<endl;            }    };    class B    {            A *a;    public:            B(int b=1):a(new A(1)) { }                       ~B() {                    cout<<"B的析构函数"<<endl;                    delete a;            }       };    class C    {            B b;    public:            C(int c=1):b(1){            }            ~C() {                    cout<<"C的析构函数"<<endl;            }    };    int main(void)    {            C *c1 = new C(1);            delete c1;            cout<<"释放C1的堆空间并调用析构函数\n"<<endl;            C c2(1);            cout<<"程序结束,C1的静态空间被释放并调用析构函数"<<endl;            return 0;    }    运行结果:    释放C1的堆空间并调用析构函数        C的析构函数        B的析构函数        A的析构函数    程序结束,C1的静态空间被释放并调用析构函数        C的析构函数        B的析构函数        A的析构函数    例子分析:    从例子的运行结果来看,很容易发现,析构函数的调用是从最外层的对象开始的。    但是如果将B类的析构函数的delete a代码注释掉,你会发现A的析构函数没有被    调用,也就说明B类中的A类指针指向的对象成员的空间没有被释放。    因此可以看出,对于在类中有自动分配的对象成员时,在析构函数中显式调用    delete释放对内存。        (5)析构函数的权限    正常情况下需要将析构函数设置为public,否者将无法编译将无法通过。    但是有一种情况是可以将析构声明为隐藏的,比如A类的所有成员都是隐藏的,    包括析构函数也是隐藏的,但是另一个B类是A类的友元的时候,A类隐藏的析构    函数也可以被调用,               例子:    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    using namespace std;    class A    {           A(int a=1){ }            ~A() {                   cout<<"A的析构函数"<<endl;            }    };    class B    {            A *a;    public:           B(int b=1):a(new A(1)) { }                      ~B() {                    cout<<"B的析构函数"<<endl;                    delete a;            }       };    int main(void)    {            B b(1);            return 0;    }    编译结果,编译错误:    a.cpp:11: error: ‘A::A(int)’ is private    a.cpp:22: error: within this context    a.cpp: In destructor ‘B::~B()’:    a.cpp:13: error: ‘A::~A()’ is private    a.cpp:26: error: within this context    编译错误提示,A的构造函数和析构函数都是隐藏的,因此B类将不能调用    A类的构造函数进行进行初始化成员,也不能调用析构函数析构操作。    面对这个情况有两个解决办法:    办法1:将构造函数和析构函数都声明为public    办法2:将B类声明为A类的友元,即便A类的构造函数和析构函数是隐藏的    也没有关系。          class A        {            A(int a=1){ }                ~A() {                        cout<<"A的析构函数"<<endl;                }            friend class B;        };6. 再次探讨副本构造函数(拷贝构造函数)的重要性上一章节提到,当从A对象复制出完全相同的B对象时(比如对象的值传递),就会调用副本构造函数进行复制。但是默认的拷贝函数进行的只是浅拷贝操作,当类对象包含堆堆空间的成员时,默认的浅拷贝会带来隐患,需要进行深拷贝,这就需要我们重写副本构造函数。特别是在类对象包含自动分配的成员对象时,重写拷贝构造函数几乎是必须的操作。例子:#include <stdio.h>#include <stdlib.h>#include <string.h>#include <iostream>#include <string>#include <cassert>using namespace std;class Province{public:        string name;        Province(const string name=" "):name(name) { }         ~Province() { } };class Country{    public:        Province *province;        Country(const string name=" "):province(new Province(name)) { }         ~Country() {                delete province;        }   };int main(void){        Country c1("shandong");        Country c2 = c1;        cout<<"通过c1打印省名:"<<(c1.province)->name<<endl;        cout<<"通过c2打印省名:"<<(c2.province)->name<<endl;        c2.province->name = "hebei";        cout<<"\n通过c2改变省名后"<<endl;        cout<<"通过c1打印省名:"<<(c1.province)->name<<endl;        cout<<"通过c2打印省名:"<<(c2.province)->name<<endl;        return 0;}运行结果:通过c1打印省名:shandong通过c2打印省名:shandong通过c2改变省名后通过c1打印省名:hebei通过c2打印省名:hebei段错误例子分析:本例子使用的是默认副本构造函数,因此将c1赋值给c2时,是浅拷贝操作,所以你会发现在通过c2修改了省份名字后,c1打印的省份名字也修改了。实际上后面出现的段错误也是由浅拷贝引起的,因为c1和c2的pronvice成员指向的是同一个Province对象,因此在c1和c2空间被释放时,province成员指向自动分配的对象会被释放两次,因此造成段错误。修改方法:在Country中加入如下自定义的副本构造函数,把将pronvice指向新的自动分配的对象空间。    Country(const Country &country) {                this->province = new Province();                this->province->name = country.province->name;        } 7. 利用c++知识实现学生链表案例(1)c++实现链表的方式    很多数据结构的书都会讲解以c语言实现的链表,数据节点使用结构体实现,很明显也可以    利用c++来实现来链表,数据节点则使用类对象代替结构体变量。(2)c++中我们并不需要自己实现各种数据结构     当然在c++中我们实际上没有必要自己自己实现链表,因为在c++中的STL容器已经帮我们实    现了非常强大的链表数据结构,只需要学会使用即可。(3)自己实现c++链表的意义    (1)有利于理解对象和结构体的异同    (1)通过c和c++所实现链表的对比,也可以加深我们对于链表的理解    (2)同时也可以借机了解c++中STL容器的list是如何实现的(4)这里写的c++链表与STL容器的list的区别    这里实现的链表例子与STL的list最大区别在于,这里没有使用类模板(泛型),因此这里自    定义实现的链表只能用来存放确定类型的数据,无法存放任意类型的数据。    因此如果想实现一个完美的链表的话,就必须引入泛型(函数模板/类模板).(5)理解c中为什么没有统一的数据结构的实习那    因为c中缺乏泛型机制,所以c中没有定义统一的数据结构,在c中常见的情况就是针对不同的    结构体类型,总是要实现不同的链表等的数据结构图。    因此我们才说,数据结构这么课的内容在c语言中的使用是最为频繁的。(6)例子  如果涉及io操作,这里沿用c语言提供的io流的操作函数。写一个studata.h,定义存放数据类。studata.h    #ifndef H_STUDATA_H    #define H_STUDATA_H    using namespace std;    class Studata    {    public:            int num;            string name;            Studata(int num=0, const string name=" "):num(num), name(name) {}            int get_num() const{ return num; }            string get_name() const{ return name; }            void set_num(int num) { this->num = num; }            void set_name(string name) { this->name = name; }    };    #endif                                                                                                                         写一个stunode.h,节点类定义。 stunode.h    #ifndef H_STUNODE_H    #define H_STUNODE_H    #include "studata.h"    using namespace std;    class Stunode    {            Studata *studata;            Stunode *prev;            Stunode *next;    public:            Stunode(int num=0, const string name=" ", Stunode *prev=NULL, \                    Stunode *next=NULL):studata(new Studata(num, name)) {}            Stunode(const Studata &studata) {                    this->studata = new Studata();                    this->studata->set_num(studata.get_num());                    this->studata->set_name(studata.get_name());                    prev = next = NULL;            }            ~Stunode() {                    delete studata;            }            /*获取器*/            Stunode *get_prev() const {return prev;}            Stunode *get_next() const {return next;}            Studata *get_studata() const {return studata;}            /*设置器*/            void set_prev(Stunode *stunode) {this->prev = stunode;}            void set_next(Stunode *stunode) {this->next = stunode;}            void set_studata(Studata *studata) {this->studata = studata;}    };    #endif写一个dlist.h,定义封装整个链表操作的类。dlist.h    #ifndef H_DLIST_H    #define H_DLIST_H    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    #include <errno.h>    #include "stunode.h"    using namespace std;    class Dlist    {            Stunode *hp;            Stunode *current;            Stunode *tmp;    public:            Dlist() {                    hp = current = new Stunode();//空头节点                     hp->set_prev(hp);                    hp->set_next(hp);                    tmp = NULL;            }                void err_fun(string filename, int line, string funname, int err_no) {                    cerr<<filename<<" "<<line<<" "<<funname<<" fail"<<":"<<strerror(err_no)<<endl;                    exit(-1);            }            void init_Dlist(string filename);            void insert_tail();            void insert_head();            void display();            Studata find(int index);            //void operator[](int i);    };    #endif写一个dlist.cpp,操作链表的函数都定义在里面。dlist.cpp       #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    #include <errno.h>    #include "dlist.h"    using namespace std;    Studata Dlist::find(int index)    {            int i=0;            for(current=hp->get_next(); ; current=current->get_next(), i++)            {                    if(current == hp) return (Studata)0;                    else if(i == index) return *(current->get_studata());                    //cout<<current->get_studata()->get_num()<<"\t"<<current->get_studata()->get_name()<<endl;            }    }    void Dlist::display()    {            for(current=hp->get_next(); hp!=current; current=current->get_next())            {                    cout<<current->get_studata()->get_num()<<"\t"<<current->get_studata()->get_name()<<endl;            }    }    void Dlist::insert_tail()    {            hp->get_prev()->set_next(tmp);            tmp->set_prev(hp->get_prev());            hp->set_prev(tmp);            tmp->set_next(hp);    }    void Dlist::insert_head()    {            hp->get_next()->set_prev(tmp);            tmp->set_next(hp->get_next());            hp->set_next(tmp);            tmp->set_prev(hp);    }    void Dlist::init_Dlist(string filename)    {            FILE *fp = NULL;            fp = fopen(filename.c_str(), "r");            if(NULL == fp) err_fun(__FILE__, __LINE__, "fopen", errno);            while(1)            {                    int num;                    char name[40];                    fscanf(fp, "%d %s\n", &num, name);                    //printf("%d %s\n", num, name);                    tmp = new Stunode(num, name);                    insert_tail();                    if(1 == feof(fp)) break;            }    }main.cpp    #include <stdio.h>    #include <stdlib.h>    #include <string.h>    #include <iostream>    #include <string>    #include <cassert>    #include <errno.h>    #include "dlist.h"    using namespace std;    int main(void)    {            Studata data;            Dlist dlist;            dlist.init_Dlist("./stu.txt");            dlist.display();            data = dlist.find(2);            cout << data.get_num()<<data.get_name() <<endl;            return 0;    }/* 存放学生数据的文件 */stu.txt    1       aaa    4       fff    3       sss    5       www    2       vvv