美文网首页
操作类对象

操作类对象

作者: gpfworld | 来源:发表于2018-12-31 22:39 被阅读0次
    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
    

    相关文章

      网友评论

          本文标题:操作类对象

          本文链接:https://www.haomeiwen.com/subject/tgcglqtx.html