C++视频课程小结(3)

C++远征之封装篇(上)

章节介绍:

每章小结:

  • 第一章:课程介绍。
    按照惯例是章节的总介绍,内容明显多了很多(为了做作业我还要赶进度的说),主要说了:类和对象是本章的主角,然后还有很多配角,像数据成员和成员函数、构造函数和析构函数、对象复制和对象赋值、对象数组和对象指针,最后还有一个this指针。视频的最后还说我们学完就可以自娱自乐,举了一个小人走迷宫的游戏,好像很厉害的样子。

  • 第二章:类和对象的初体验。
    视频说类是抽象的东西,而对象则是具体的,实际存在的东西。要定义一个类的话,具体方法如下(看起来好像结构体那什么,记得加分号):

    根据不同的目的,抽取到类里的信息也会不同。此外还有一个被称为访问限定符的东西:

    还提到了一点封装的概念,就是像这样把操作的细节藏起来。接下来就是对象的实例化,具体来说有两种:

    越看越像定义一个新的类型那啥的。

    这个还用到了内存申请,要记得检查是否申请成功,还要记得释放内存并把指针置空。这两种不同的实例化分别有着自己的对象成员的访问方式(还是很像结构体。。。):

    视频的大致内容就是这些,然后是我查的一些东西:

    • 1、关于C++中结构体和类的区别:结构是C的一部分,C++从C中继承了结构,在语法上,类与结构十分相似,在关系上,这两者也很接近,在C++中,结构的作用被拓宽了,进而使结构成为了类的一种替代方法。实际上,类与结构的惟一区别在于:在默认状态下,结构的所有成员均是公有的,而类的所有成员是私有的。除此之外,类与结构是等价的,也就是说,一个结构定义了一个类的类型。C++同时包含这两个等价的关键字struct与class基于3个方面的原因。第一,加强结构的能力。在C中,结构提供了一种数据分组方法,因而让结构包含成员函数是一个小小的改进。第二,由于类与结构是相互关联的,所有现有C代码到C++的移植变得更容易。第三,由于类与结构的等价性,提供两个不同的关键字可以使类定义自由发展,为了保持C++与C的兼容性,结构定义必须始终受它的C定义的结束。即使在有些地方可以使用结构来代替类,但尽量不要这么做,为了清楚起见,该用类的地方就用class关键字,该用C结构的地方就用struct关键字。(来自http://www.cnblogs.com/ark-zhang/p/3197495.html
    • 2、内存堆和栈的区别。具体请看:http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html

    然后还是来打点代码好了:

          #include<stdlib.h>
          #include<iostream>
          using namespace std;
    
          class Coordinate
          {
              public:
                 int x;
                 int y;
    
                 void printfx()
                 {
                     cout<<x<<endl;
                 } 
                 void printfy()
                 {
                     cout<<y<<endl;
                 }
          };
    
          int main()
           {
               Coordinate C1;
               C1.x=10;
               C1.y=20;
               C1.printfx();
               C1.printfy();
            
               Coordinate *p=new Coordinate;
    
               if(p==NULL)
               {
                 system("pause");
                 return 0;
               }
    
               p->x=100;
               p->y=200;
               p->printfx();
               p->printfy();
    
               delete p;
               p=NULL;
    
               system("pause");
               return 0;
           }
    
  • 第三章:初始字符串类型。
    这章介绍了一个很好用的字符串类型string,只要加上#include<string>就可以使用(话说它没有.h也不用写using namespace 什么的啊)。以前在C里处理字符串都要用到字符数组,还有一堆的字符串处理函数(都是用到的时候去翻书。。。),现在可就方便多了。像初始化就很方便:

    上图中最后一个如果n=3的话,s4就会是ccc。输入字符串的话视频里提到了getline(cin,str),这样就可以输入字符串到字符型变量str里。还有各种操作也是方便了很多:

    需要注意的是第一个和第二个要记得加括号,还有就是将两个字符串连接起来的时候:

    最后一个操作是错误的,就是加号的两边至少要有一个string类型的变量(刚开始还以为会是终止符什么的,视频也没说为什么非法,也没找到合适的答案。。。)。

  • 第四章:属性封装的艺术。
    这章只是初步的说了点封装的事,只记得说对象的各种操作要通过调用自身的函数来实现,这样才符合面向对象这一基本思想(至于为什么,我是没听懂)。视频一开始就说下图的做法不符合面向对象这一基本思想(我倒是看着很顺眼):

    正确应该是这样(就是换成一堆函数的意思?):

    接下来视频自然是开始说封装的好处:一个是要是输入的学生年龄是1000的话,第一种就照输不误,而第二种就可以在setAge函数里进行控制。还有一个是第二种可以弄出只读属性的东西,就是public里只有get而没有set这样子,然后你就只能看而不能改。最后还有一点,视频提到了类里面设置变量名和函数名的一般规则(或是默认的习惯之类的东西),这个在下面的代码练习中会有体现。然后接下来是我查的一些没听懂的东西:

    最后再来打点代码:

        #include<stdlib.h>
        #include<iostream>
        using namespace std;
    
        class Student
        {
            public:  
                void setName(string _name)
                {
                    m_strName=_name;
                }
    
                string getName()
                {
                    return m_strName;
                }
    
                void setAge(int _age)
                {
                    m_iAge=_age;
                }
    
                int getAge()
                {
                    return m_iAge;
                }
    
                int getScore()
                {
                    return m_iScore;
                }
    
                void study(int _score)
                {
                    m_iScore += _score;
                }
    
                void initScore()
                {
                    m_iScore=0;
                }
    
            private:
                 string m_strName; 
                 int m_iAge;
                 int m_iScore;          //大概就是命名成这样
        }; 
    
        int main()
        {
            Student stu;
    
            stu.initScore();            //关于初始化,说以后会用到构造函数
            stu.setName("zhangsan");
            stu.setAge(18);
            stu.study(5);
    
            cout<<stu.getName()<<" "<<stu.getAge()<<" "<<stu.getScore()<<endl;
    
            system("pause");
            return 0;
        }
    

    (打成这样真的好麻烦。。。)然后是运行结果:

  • 第五章:精彩的类外定义。
    就是把类里面的成员函数放到类外面来定义,还可以在不同的文件里(感觉作业里也是这个思想,把类放项目里,所以要建的是项目而不是源代码),具体来说分为同文件类外定义和分文件类外定义两种。同文件类外定义长这样:

    需要注意的也标注出来了。然后分文件类外定义长这样:

    有个#include"Car.h"要特别强调,是引号而不是尖括号,还有文件的后缀也要注意。说到这里又想起using namespce std;的问题,明明有#include<iostream>这个声明了,像#include<string>就可以,然后上图也还是要有Car::。查了之后才发现,std这个空间不止给输入输出用,字符串也在用。。。

    • (1)如果C++程序中使用了带后缀".h"的头文件,那么不必在程序中声明命名空间,只需要文件中包含头文件即可。
    • (2)C++标准要求系统提供的头文件不带后缀".h",但为了表示C++与C的头文件既有联系又有区别,C++中所用头文件不带后缀".h",而是在C语言的相应头文件名之前加上前缀c。
    • (3)自定义的头文件通常带后缀“.h",系统标准库文件不带后缀“.h"。
    • (4)因为标准库非常的庞大,所程序员在选择的类的名称或函数名时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切h标识符都被放在名字空间std中。
      (来自http://www.ithao123.cn/content-7359384.html)
  • 第六章:对象的生离死别。(内容比较丰富。。。)

    • 6-1: 构造函数讲解。
      视频刚开始提到了对象的存储,还有一些关于内存的东西(可以参考http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html):

      说是定义类时并不占用内存,实例化对象时系统才会分配内存,还有就是不同的对象是共用储存在代码区的成员函数的。然后视频才开始讲构造函数:就是用来给对象里的一些数据成员初始化的东西(毕竟无法保证原来内存存了什么,所以要初始化),可以帮我们避免忘记初始化和重复初始化之类的错误,可分为无参构造函数、有参(可以设默认值)构造函数和重载构造函数三种,具有以下几个特点:

      • 1、构造函数在对象实例化时被自动调用(初始化有且只有一次);
      • 2、构造函数与类同名(为了偷偷初始化);
      • 3、构造函数没有返回值;
      • 4、构造函数可以有多个重载形式(确保可以分辨,以下代码有举例);
      • 5、实例化对象时仅用到一个构造函数;
      • 6、当用户没有定义构造函数时,编译器自动生成一个构造函数。

      代码练习(这个用时最多。。。):

        #include<stdlib.h>
        #include<iostream>
        using namespace std;
      
        class Student
        {
            public:  
                Student()
                {
                    m_iAge=0;
                    m_strName="imooc"; 
                    cout<<"Student()"<<endl;                      //调用的证明
                }
            
                Student(string name,int age=5)                    //要是全部设默认值的话,stu1就会不知道调用哪个
                {
                	m_iAge=age;
                    m_strName=name;
                    cout<<"Student(string name,int age=5)"<<endl; //调用的证明
                }
            
                void setName(string _name);            
                string getName();               
                void setAge(int _age);               
                int getAge();               
            private:
                    string m_strName; 
                    int m_iAge;
        }; 
      
        void Student::setName(string _name)
        {
             m_strName=_name;
        }
      
        string Student::getName()
        {
            return m_strName;
        }
      
        void Student::setAge(int _age)
        {
            m_iAge=_age;
        }
      
        int Student::getAge()
        {
            return m_iAge;
        }                                                        //为了突出构造函数其他成员函数用了同文件类外定义,视频里则都是分文件
      
        int main()
        {
            Student stu1;
            Student stu2("Lucy");
            Student stu3("marry",15);
        
            cout<<stu1.getName()<<" "<<stu1.getAge()<<endl;
            cout<<stu2.getName()<<" "<<stu2.getAge()<<endl;
            cout<<stu3.getName()<<" "<<stu3.getAge()<<endl;
          
            system("pause");
            return 0;
        }
      

    运行结果:

    • 6-2:构造函数初始化列表。
      主要就是讲了对类里面的数据成员的一种新的初始化方法,说是以后经常用这种,自然是有一定的好处,这在下面会讲到。视频刚开始提到了默认构造函数,就是在调用构造函数(也就是实例化对象)时可以不用输入参数的构造函数,像无参构造函数或是都有默认值的有参构造函数(小心不要同时出现让编译器无法识别调用哪个)。初始化列表具有如下三个特性:1、初始化列表先于构造函数执行;2、初始化列表只能用于构造函数;3、初始化列表可以同时初始化多个数据成员,然后初始化列表具体长这样(主要注意一下格式就好):

      至于初始化列表的好处就是可以初始化const定义的变量,举个例子(编译可以通过的):

      刚开始觉得直接定义的时候赋值不就好了,但是构造函数的默认值也是可以被实参覆盖的啊,那要改常量的时候不是方便很多,不要再跑到类里去改数据成员。

    • 6-3:拷贝构造函数。
      前面的课程说实例化对象的时候都会调用到构造函数,就算你没定义系统也会自己生成一个,但是以下的代码:

                #include<stdlib.h>
                #include<iostream>
                #include<string>
                using namespace std;
      
                class Student
                {
                    public:
                       Student()
                       {
                        cout<<"Student()"<<endl;
                       }
                    private:
                        string m_strName;
                        int m_iAge;
                };
      
                int main()
                {
                    Student stu1;
                    Student stu2=stu1;
                    Student stu3(stu1);
       
                    system("pause");
                    return 0; 
                } 
      

      运行结果长这样:

      按道理说应该调用三次构造函数,但却只有一行输出,这是为什么呢。视频说stu1和stu2调用的是拷贝构造函数,当采用直接初始化或复制初始化实例化对象时系统自动调用拷贝构造函数。定义拷贝构造函数略有不同,然后你不定义的话系统也会自动生成一个默认的拷贝构造函数,具体长这样:

      其中变量名可写可不写,然后上面的代码加上Student(const Student & stu){cout<<"Student(const Student & stu)"<<endl;}的话,运行结果如下:

      还有视频说拷贝构造函数在参数传递的时候也会被调用,举个例子来说,有如下代码:

           #include<stdlib.h>
           #include<iostream>
           #include<string>
           using namespace std;
      
           class Student
          {
               public:
                   Student()
                   {
                       cout<<"Student()"<<endl;
                   }
                   Student(const Student & stu)
                   {
                       cout<<"Student(const Student & stu)"<<endl;
                   }
               private:
                   string m_strName;
                   int m_iAge;
           };
           
           int main()
           {
               void fun(Student stu);
               Student stu1;
               fun(stu1);
       
               system("pause");
               return 0; 
           }
      
           void fun(Student stu){}
      

      运行结果长这样:

    • 6-4: 析构函数。
      与构造函数相反,析构函数在对象被销毁时自动调用,主要用途是释放资源。至于为什么一定要用析构函数来释放资源,视频里举了下面这个例子:

      销毁对象时指针并不会被置空,可能会造成迷之错误,析构函数的必要性就体现在这里。然后析构函数的定义也如上图所示,需要注意的是括号里不能填参数。再来总结一下析构函数的特点:1、不定义系统也会自动生成;2、对象被销毁时自动调用;3、析构函数没有返回值;4、析构函数没有参数,也就不能重载。最后附上一张·对象的历程图:

附1:C++远征之模板篇第六章:模板函数和模板

  • 1、函数模板。
    有时我们会遇到一些长得很像的函数,就像下面的图这样子的:

    这些函数代码部分都一样,就是数据类型不一样,而模板函数就能把类型当作参数从而只写一次代码部分。首先我们要定义一个函数模板,这要用到关键字template,typenameclass,至于定义的方法,直接上图:

    使用的方法也在上图中体现了,最好加尖括号说明一下。还有就是这里的class可以用typename替换,效果是一样的,视频推荐用后者,毕竟定义类的时候也用到了class。还有就是尖括号里可以写很多东西,可以写多个个类型参数,变量也可以做参数(写出模板函数时就被当成常量了),这样才能更好地满足实际的需求。
  • 2、类模板。
    和函数模板差不多,定义类的时候加上一行template< >就好。需要注意的是当成员函数用类外定义时,要写成这个样子:

    不仅要加上template的那一行(每个成员函数都要写一个),还要在类名后面也要加上说明的尖括号。最后视频还说:模板代码不能分离编译,大概就是不能分文件类外定义(解释什么的我是没听懂)。

附2:关于队列

queue 模板类的定义在头文件中。
与stack 模板类很相似,queue 模板类也需要两个模板参数,一个是元素类型,一个容器类
型,元素类型是必要的,容器类型是可选的,默认为deque 类型。
定义queue 对象的示例代码如下:
queue q1;
queue q2;

queue 的基本操作有:
入队,如例:q.push(x); 将x 接到队列的末端。
出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。
访问队首元素,如例:q.front(),即最早被压入队列的元素。
访问队尾元素,如例:q.back(),即最后被压入队列的元素。
判断队列空,如例:q.empty(),当队列空时,返回true。
访问队列中的元素个数,如例:q.size()
(来自http://www.cnblogs.com/mfryf/archive/2012/08/09/2629992.html)

posted @ 2016-02-15 21:32  archeroc  阅读(215)  评论(0编辑  收藏  举报