C++笔记--抽象机制

时间:2023-03-10 01:59:27
C++笔记--抽象机制
    •   一个类就是一个用户定义类型
    •   一个结构体也是一种类。(成员函数),因为不同的结构体中可能会有相同的名字的成员函数,所以我们在定义成员函数的时候就必须给出有关结构体的名字
      void Data::init(int dd,int mm,int yy){}
    •   非成员函数禁止访问私有成员
    •   类中的静态成员:
      •   静态成员--包括函数和成员都必须在类外的某个地方进行定义:格式例如     类型+类名+::+变量名;    double Data::time=1;
        初始化静态变量的时候,不管变量的限定词是什么,都是在类外进行初始化的,private也是如此
      •   静态成员的存取:两种情况。
        第一种:没有对象产生,直接调用。类名::变量名=值。这里必须保证该静态变量为public
        第二种:有对象产生,也是直接调用。类名.变量名=值。这里必须保证该静态变量为public
      • (1)出现在类体外的函数不能指定关键字static;

        (2)静态成员之间可以互相访问,包括静态成员函数访问静态数据成员和访问静态成员函数;

        (3)非静态成员函数可以任意地访问静态成员函数和静态数据成员;

        (4)静态成员函数不能访问非静态成员函数和非静态数据成员;

        (5)由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比,速度上会有少许的增长;

        (6)调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指调用静态成员函数。

    •   自引用
      •   每个非静态函数都知道他是为了哪个对象而调用的,因此可以显示的引用该对象。
        Date &Date::add_year(int n){
        if(d== && m== && ;leapyear(y+n)){
        d=;
        m=;
        }
        y+=n;
        return *this;
        }

        表达式中的 *this引用的就是找个函数的这次调用所针对的那个对象。大部分的this都是隐含的。对一个类中非静态的成员的引用都要依靠隐式的使用this。格式:this->变量名。。

    •   可变的mutable--强制去掉const属性
      •   如果在某个表示中只有一部分允许改变,将这些成员声明为mutable最合适了。
      •   如果一个对象在逻辑上表示为const,但是它的大部分数据成员需要改变,那么最好是将这些需要改变的数据成员放在另一个独立的对象中,简接访问。
    •   在类内部的函数定义
      •   一个函数如果在类的内部定义和声明,应该是短小简短额,就好像是在线(inline)的一样。
        • 但是在类内部定义的函数有可能会引起一些不必要的疑虑,所以可以用另一种的方式进行。例如:把该函数在类外定义成inline函数。
    •   高效的定义数据类型
      •   例如在类中的一个构造函数中写了一些判断这个传递的参数是否合法的判断语句。最好的方法是直接把这些代码写在该构造函数中,最好不要另外写一个函数去包含这些语句。不然太麻烦
      •   增加一些协助函数
      •   重载一些运算符
    •   对象
      •   析构函数
        •   每当一个位于*存储的对象被删除的时候,都会隐式的调用析构函数
      •   默认构造函数
        •   由于const和引用必须进行初始化,所以含有这两个类型的变量的类就不会有默认构造函数,所以必须自己定义默认构造函数。
        •   一个类如果定义了一个构造函数,就不会自动生成默认构造函数了,这个就必须自己定义默认构造函数了。(一般还是写上默认构造函数比较好)
      •   构造和析构
        •   局部变量:
          •   对于一个局部变量而言,每当控制走到该局部变量的声明处,构造函数开始作用,当走出局部变量的作用域,析构函数开始作用,(以和构造函数构造顺序相反的书序析构)
            例如 int a,b;构造a,b;析构b,a;
          • 赋值运算符和复制构造函数必须定义清楚,不然很容易造成内存泄漏,删除不干净的问题。
            •   复制构造函数
              table ::table(const table & t){
              p=new name(sz=t.sz);
              for(int i=;i<sz;i++)p[i]=t.p[i];
              } table &table::operator=(const table& t){
              if(this!=&t){//当心自赋值 t=t;
              delete [] p;
              p=new Name[sz=t.sz];
              for(int i=;i<sz;i++)p[i]=t.p[i];
              }
              return *this;
              }
        • 成员初始化列表
          •   
            #include<iostream>
            #include<vector>
            #include<stdio.h>
            using namespace std; class club{
            string name;
            Table members;
            Table ooficers;
            Data founded; club(const string &n,Data fd);
            };
            club::club(const string &n,Data fd):name(n),members(),ooficers(),founded(fd){
            }

            对于没有默认构造函数的类的成员变量,或者是cosnt,引用类型的成员变量,我们都要使用成员列表初始化的方式最好,它能够提高效率

      • 局部静态存储
        • 例如
          void f(int i){
          static table tal;
          if(i){
          static table tbl2;
          }
          }
          int main(){
          f();
          f();
          f();
          }
        • 静态变量只会被构造一次,之后便不会再调用构造函数进行构造,析构的方式也是和构造的方式相反
    •   忠告
      C++笔记--抽象机制
  • 运算符重载
    •   对于一元的运算符 @,@ a可以解释为a.operator@(),或者是operator@(a).对于后缀的一元的运算符例如++,可以表示为a.operator@(int),或者operator@(a,int)
    •   一个运算符函数要么是一个成员函数,要么至少有一个用户定义类型的参数。注意:如果某个运算符函数想要接受某个内部类型(常量或者是整数),那么这个运算符函数肯定不是成员函数。
      例如:加一个复数变量到一个整数a上面,a+2可以写成a.operator+(2),但是2+a写成2.operator+()这个就不对了,违反了+法的规定。
      解决这个问题可以把运算符函数定义成非成员函数,这样就可以十分轻松的解决这个问题。
      •   成员运算符和非成员运算符
        •   一般是只在类本身中定义一些本质上就是需要修改第一个参数值得运算符,例如+=,-=之类的,它的第一个参数一定是变量且需要修改。而像+,-这样的简单的参数运算产生新值得一般就是放在类外进行定义的。
        • C++笔记--抽象机制
    •   当我们遇到一些+法运算时,会出现各种各样的数相加,这个就要我们去设计不同的+运算符函数,这里就必须要把加法运算符函数定义为非成员函数
      •  C++笔记--抽象机制 
      •   上面的+=运算符中采用了优化的结构方式,通过减少了临时变量来提高了效率,值的效仿。
    •   复制构造函数
      •   一般比较喜欢采用默认的复制构造函数。这个函数格式:X X(const X &);参数采用的是引用对象,而其他的包含X对象的函数的参数都是采用值对象,不采用引用。
        X a=2;//这里是先建立了X(2),然后去初始化了a
      •   作用:无论是自己写的还是默认的复制构造函数,都被用来初始化变量(如上),参数传递,值返回,以及异常处理上面。
    •   友元
      •   该函数可以访问类声明的私有部分,friend声明放在private或者public中都能访问私有成员数据。
      •   最主要的是它能最为两个类的成员函数,从而很好的去实现两个类对象之间的操作。
        C++笔记--抽象机制
      • 一个类的成员也可以是另一个类的友元。
      • 友元函数的一些详细作用
      • 2.友元函数的使用

        2.1友元函数的参数:

        因为友元函数没有this指针,则参数要有三种情况:

        2.1.1 要访问非static成员时,需要对象做参数;

        2.1.2 要访问static成员或全局变量时,则不需要对象做参数;

        2.1.3 如果做参数的对象是全局对象,则不需要对象做参数;

        2.2友元函数的位置

        因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。

        2.3友元函数的调用

        可以直接调用友元函数,不需要通过对象或指针

        2.4友元函数的分类:

        根据这个函数的来源不同,可以分为三种方法:

        2.4.1普通函数友元函数

        2.4.1.1目的:使普通函数能够访问类的友元

        2.4.1.2语法:

        声明: friend + 普通函数声明

        实现位置:可以在类外或类中

        实现代码:与普通函数相同

        调用:类似普通函数,直接调用

        2.4.1.3代码:

        class INTEGER

         {

          friend void Print(const INTEGER& obj);//声明友元函数

         };

        void Print(const INTEGER& obj)

        {

           //函数体

        }

        void main()

        {

          INTEGER obj;

          Print(obj);//直接调用

        }

        2.4.2Y的所有成员函数都为类X友元函数友元类

        2.4.2.1目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。

        2.4.2.2语法:

        声明位置:公有私有均可,常写为私有(把类看成一个变量)

        声明: friend + 类名(不是对象哦)

        2.4.2.3代码:

        class girl;

        class boy

        {

        public:

          void disp(girl &);

        };

        void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数

        {

          cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl;//借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量

        }

        class girl

        {

        private:

          char *name;

          int age;

          friend boy; //声明类boy是类girl的友元

        };

        main函数就不写了和普通调用时一样的。

        2.4.3Y的一个成员函数为类X的友元函数

        2.4.3.1目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量

        2.4.3.2语法:

        声明位置:声明在公有中 (本身为函数)

        声明:friend + 成员函数的声明

        调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制

        2.4.3.3代码:

        实现代码和2.4.2.3中的实现及其相似只是设置友元的时候变为friend void boy::disp(girl &);自己解决喽……

        小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。

        4.友元函数和类的成员函数的区别

        4.1 成员函数有this指针,而友元函数没有this指针。

        4.2 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。

    • 针对大型的对象
      •   大型对象的+法可能会多次的调用复制构造函数,所以开销会十分的大。为了避免过度的复制,我们一般把对象的参数改为引用,这样开销会小弟
        C++笔记--抽象机制
      • 这样确实是减少了开销。
    •   C++笔记--抽象机制
  • 派生类
    •   派生类
      •   派生类中的基类与派生类的关系:如果drived有一个公共基类base,那么一定可以用drived* 对象给 base* 对象赋值,但是反过来不行,因为一个drived一定是一个base,但是反过来不一定(这里必须要使用强制转换)。
      •   用一个类做基类,这个类必须定义和声明。
      •   派生类可以使用基类中的public和protectd属性的成员函数,但是派生类不能使用基类中的private属性的成员。如果想访问private成员,我们可以设置为protectd属性的,这样除了派生类其他的类和函数都无法访问
      •   构造函数和析构函数:
        派生类中的构造函数:基类中的构造函数的参数应该要在派生类构造函数的定义中有明确的描述。
        • C++笔记--抽象机制
        • C++笔记--抽象机制
        • 派生类的构造函数只能描述它自己的成员和自己直接基类的初始式,不能直接去初始化基类的成员
        • C++笔记--抽象机制
        • 构派生类构造函数和析构顺序:构造:基类-成员(按照成员声明顺序)-派生类,,析构:派生-成员(成员声明的反顺序)-基类。
      •   虚函数(多态性)
        •   在某一个虚函数的第一个声明的那个类里面,这个虚函数必须给它定义。(当然除了纯虚函数以外)
        •   在派生类中如果不必用到这个虚函数的功能,我们也是可以不写这个虚函数的。
        •   在派生类中使用虚函数可以不用加上virturl这个关键字了。
        •   如果在派生类中存在一个和虚函数同名的、参数类型相同的函数,这个函数就会直接把虚函数的基类版本给覆盖了。调用的时候我们还是必须加上作用域限定符::这样才能调用我们想要的版本
    •   抽象类
      •   如果一个类中有一个或者几个纯虚函数,这个类就是抽象类。其中不能够去创建一个抽象类对应的对象。
      •   抽象类只能作为一个基类,最为一个界面。不去实现任何细节。
    •   一般的类的层次的结构的设计
      •   首先定义一个抽象类。(没有构造函数,定义一个虚析构函数,里面的成员函数全部声明为纯虚函数。)当然派生类是可以实现多重继承的。
      •   C++笔记--抽象机制