操作类对象

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值