文章目录

  • 运算符重载
    • 运算符重载的基本概念
    • 赋值运算符的重载
      • 为什么引入赋值运算符重载?
      • 浅拷贝和深拷贝
      • 对operator=返回值的讨论
      • 复制构造函数的相同困境
    • 运算符重载为友元函数
    • 实例:可变长数组的实现
    • 流插入运算符和流提取运算符的重载
      • 流插入运算符(左移运算符):<<
    • 类型转换运算符的重载
    • 自增自减运算符的重载
    • 注意事项
  • 继承
    • 继承关系和复合关系
    • 覆盖和保护成员
    • 派生类的构造函数
    • 公有继承的赋值兼容规则

运算符重载

运算符重载的基本概念

  1. 为什么引入运算符重载?

    在数学上,两个复数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的。有时会希望,让对象也能通过运算符进行运算。这样代码更简洁,容易理解。

    例如:

    complex_a和complex_b是两个复数对象;

    求两个复数的和, 希望能直接写:
    complex_a + complex_b

  2. 运算符重载是什么?

    • 运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。

    • 运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象。

    • 同一个运算符,对不同类型的操作数,所发生的行为不同。

      complex_a + complex_b =新的复数对象

      5 + 4 = 9

  3. 运算符重载的形式?

    • 本质是函数重载

    • 可以重载为普通函数,也可以重载为成员函数

    • 把含运算符的表达式转换成对运算符函数的调用。

    • 把运算符的操作数转换成运算符函数的参数。

    • 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

      返回值类型 operator 运算符 (形参表)
      {...
      }
      

例子

class Complex
{public:double real,imag;Complex(double r=0.0,double r=0.0):real(r),imag(i){}//上方的构造函数添加了初始化列表,将real初始化为r,将imag初始化为i,这种风格比在函数体内直接用r,i赋值的风格更好
};//重载成普通函数,参数个数为运算符目数
Complex operator+(const Complex& a,const Complex& b)
{return Complex(a.real+b.real,a.imag+b.imag);}
//返回一个Complex类的临时对象//重载成成员函数,参数个数为运算符目数-1,因为此时一个参数已经确定了,就是这个重载归属的那个类定义的对象
Complex Complex::operator-(const Complex& c)
{return Complex(real-c.real,imag+c.imag);}
//返回一个Complex类的临时对象int main()
{Complex a(4,4),b(1,1),c;c=a+b;//等价于c=operator+(a,b);cout<<c.real<<","<<c.imag<<endl;cout<<(a-b).real<<","<<(a-b).imag<<endl;//a-b等价于a.operator-(b),operator-被固定的那个变量就是对象areturn 0;
}

Output:

5,5
3,3

小结:

  1. 重载成普通函数,参数个数为运算符目数;

    重载成成员函数,参数个数为运算符目数-1

  2. c=a+b等价于c=operator+(a,b);

    a-b等价于a.operator-(b)

赋值运算符的重载

为什么引入赋值运算符重载?

有时候希望赋值运算符两边的类型可以不匹配

比如,把一个int类型变量赋值给一个Complex对象, 或把一个 char * 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符“=”。

赋值运算符”=“只能重载为成员函数

例子

class String
{private:char* str;public:String():str(new char[1]){str[0]=0;}//上面的构造函数添加了初始化列表,which new了一个只有一个元素的字符串数组,然后用这个数组的地址初始化str。在构造函数中往str写入一个0。最终str指向一个空字符串const char* c_str(){return str;};//上面的成员函数没有参数,返回一个指向常量字符串的指针,也就是strString& operator=(const char* s);//将“=”重载为读取一个指向char型数据的指针,返回一个String类临时对象的引用String::~String(){delete[]str;} //由于str指向的字符串数组是被new出来的,所以删除时必须使用delete[]
};//下面的重载是为了使得obj="hello"能够成立
String& String::operator=(const char* s)
{delete[]str;//先删除对象String中变量str原本指向的字符串数组str=new char[strlen(s)+1];//初始化str,令str指向一个new出来的字符串数组,该数组大小为“=”参数数组长度+1strcpy(str,s);//上上句新建好str后,这句把s的内容拷贝到了str里面return *this;//返回这个成员函数作用的对象String的引用
}int main()
{String s;s="Good Luck,";//等价于s.operator=("Good Luck,");cout<<s.c_str()<<endl;//String s2="hello!";//这句话不注释掉就会出错,因为这句话不是赋值语句,而是初始化语句,会调用构造函数,但我们之前的构造函数不接受参数s="Shenzhou 8!";cout<<s.c_str()<<endl;return 0;
}

Output:

Good Luck,
Shenzhou 8!

浅拷贝和深拷贝

class String
{private:char* str;public:String():str(new char[1]){str[0]=0;}const char* c_str(){return str;};String& operator=(const char* s);String::~String(){delete[]str;}
};String& String::operator=(const char* s)
{delete[]str;str=new char[strlen(s)+1];strcpy(str,s);return *this;
}

还是这个例子,但此时我们想要实现:

String S1,S2;

S1=“this”;

S2=“that”;

S1=S2;

如果不改变上面的代码(也就是浅拷贝),实际执行情况是下面这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7eshIldp-1636871571879)(assets/2020-10-09-12-21-54.png)]

这导致以下几种问题:

  • 如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str 指向同一地方。原先S1指向的地方无法删除被浪费掉。
  • 如果S1对象消亡,析构函数将释放 S1.str指向的空间,则S2消亡时还 要释放一次,但一片被new出来的空间只能被delete一次。
  • 另外,如果执行 S1 = “other”;会导致S2.str指向的地方被delete

因此要在 class String里添加成员函数:

String& operator=(const String& s)
{delete[]str;str=new char[strlen(s.str)+1];strcpy(str,s.str);return *this;
}

但是这样还不够,考虑下面的语句

String s;

s = “Hello”;

s = s;

如果等号两边的对象一样,=应该什么都不做。所以重载“=”应为:

String& operator=(const String& s)
{if(this==&s)return *this;delete[]str;str=new char[strlen(s.str)+1];strcpy(str,s.str);return *this;
}

上面的重载即实现了深拷贝

扩展:https://www.zhihu.com/question/36370072/answer/67181275

那么如果原来的物体销毁了,但是现在拷贝的物体还在,那么这时候你拷贝后的物体的成员指针就是一个悬挂指针,指向了不再存在的物体,那么你访问的话,那就不知道会发生什么了。

而对于深拷贝,这一个勤奋的人,他不会只做表面,他会把每一个细节都照顾好。于是,当他遇到指针的时候,他会知道new出来一块新的内存,然后把原来指针指向的值拿过来,这样才是真正的完成了克隆体和原来的物体的完美分离,如果物体比作人的话,那么原来的人的每一根毛细血管都被完美的拷贝了过来,而绝非只是表面。所以,这样的代价会比浅拷贝耗费的精力更大,付出的努力更多,但是是值得的。当原来的物体销毁后,克隆体也可以活的很好。

对operator=返回值的讨论

对运算符进行重载的时候,好的风格应该尽量保留运算符原本的特性

  • 返回值为什么不能是void?

    a=b=c等价于a.operator=(b.operator=(c))

    b.operator=(c)返回值为void,则a=void,不可

  • 返回值为什么不能是String?

    (a=b)=c等价于(a.operator=(b)).operator=(c)

    a.operator=(b)返回值是a,下一步就会让a=c的值。也就是这句话先让a=b的值,再让a=c的值,最终b并没有等于a和c,不可

复制构造函数的相同困境

为 String类编写复制构造函数的时候,会面临和 = 同样的问 题,用同样的方法处理。

String( String & s) {str = new char[strlen(s.str)+1];              strcpy(str,s.str);
}

运算符重载为友元函数

  1. 为什么要将运算符重载为友元?

    有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

    例子

    class Complex {double real,imag; public: Complex( double r, double i):real(r),imag(i){ };              Complex operator+( double r );
    };
    Complex Complex::operator+( double r )
    { //能解释 c+5 return Complex(real + r,imag);
    }
    

    经过上述重载后:

    Complex c ;

    c = c + 5; //有定义,相当于 c = c.operator +(5);

    c = 5 + c; //编译出错

    所以,为了使得上述的表达式能成立,需要将 + 重载为普通函数。

    Complex operator+ (double r,const Complex & c)
    {//能解释 5+c return Complex( c.real + r, c.imag);
    }
    

    但是普通函数又不能访问私有成员,所以,需要将运算符 + 重载为友元。

    class Complex {double real,imag; public:Complex( double r, double i):real(r),imag(i){ };              Complex operator+( double r ); friend Complex operator + (double r,const Complex & c);
    };
    

实例:可变长数组的实现

要编写可变长整型数组类,使之能如下使用

int main()
{CArray a;//开始时数组是空的for(int i=0;i<5;++i)a.push_back(i);//❗要用动态分配的内存来存放数组元素,需要一个指针成员变量CArray a2,a3;a2=a;//❗要重载“=”for(int i=0;i<a.length();++i)cout<<a2[i]<<" ";//❗要重载中括号[]a2=a3;//a2变成空的for(int i=0;i<a2.length();++i)//此时a2.length返回0cout<<a2[i]<<" ";cout<<endl;a[3]=100;//将数组a的第三个数改为100CArray a4(a);//❗要自己写一个复制构造函数,不能用缺省的for(int i=0;i<a4.length();++i)cout<<a4[i]<<" ";return 0;
}

Output:

0 1 2 3 4
0 1 2 100 4

该怎么写这个数组类??

class CArray
{int size;//数组元素的个数int* ptr;//指向动态分配的数组publicCArray(int s=0);//构造函数,s代表数组元素的个数CArray(CArray& a);//复制构造函数~CArray();//析构函数void push_back(int v);//用于在数组尾部添加一个元素vCArray& operator=(const CArray& a);//用于数组对象间的赋值int length(){return size;}//返回数组元素个数int& CArray::operator[](int i){return ptr[i];}//用于支持根据下标访问数组元素,如n=a[i]和a[i]=4这样的语句//❗❗❗对于返回值的解释,看下面解释
}

对于int& CArray::operator[](int i){return ptr[i];}要注意:

返回值类型必须是int&,不能是int!!!这是因为如果一个函数的返回值不是引用,不能将它写在等号左边,所以a[i]=4这句话将编译出错

/**********************构造函数**********************************/
CArray::CArray(int s):size(s)
//这个初始化列表用s初始化size,s的缺省值是0(即不给参数时使用的s值)
{if(s==0)ptr=NULL;elseptr=new int[s];
}/**********************复制构造函数*******************************/
CArray::CArray(CArray& a)
{//如果a.ptr指向空数组,就令ptr指向空数组if(!a.ptr){ptr=NULL;size=0;return;}//如果a.ptr指向非空数组,就创建一个同样大小的空间复制上a.ptr的内容并将地址赋给ptrptr=new int[a.size];memcpy(ptr,a.ptr,sizeof(int)*a.size);size=a.size;
}//上面这个复制构造函数完成了深拷贝的工作/**********************析构函数**********************************/
CArray::~CArray()
{if(ptr)delete[]ptr;
}/**********************“=”的重载函数*****************************/
//赋值号的作用是使“=”左边对象里存放的数组,大小和内容都和右边的对象一样
CArray& CArray::operator=(const CArray& a)
{if(ptr==a.ptr)//防止a=a这样的赋值出错return *this;if(a.ptr==NULL){//如果a里面的数组是空的if(ptr)//如果ptr指向的数组不是空的delete[]ptr;ptr=NULL;size=0;return *this;}if(size<a.size){//如果原有空间不够用,就需要分配新的空间if(ptr)delete[]ptr;ptr=new int[a.size];}memcpy(ptr,a.ptr,sizeof(int)*a.size); //如果原有空间够大,就不分配新的空间size=a.size;return *this;
}/**********************push_back函数*****************************/
void CArrary::push_back(int v)
{/*下面做分配空间的工作*/if(ptr){//原数组不空int* tmpPtr=new int[size+1];//创造一个比原数组多一个空间的数组memcpy(tmpPtr,ptr,sizeof(int)*size);//拷贝原数组到tmpPtr里delete[]ptr;//原数组被复制好后就可以删除释放空间ptr=tmpPtr;//现在的ptr指向的空间比原来的大1}else//原数组是空的ptr=new int[1];/*下面做加入新的数组元素的工作*/ptr[size++]=v;
}

流插入运算符和流提取运算符的重载

流插入运算符(左移运算符):<<

cout是在iostream中定义的,ostream类的对象。“<<”能用在cout上是因为再iostream中对“<<”进行了重载。

  1. ostream类中对<<的重载(头文件中别人已经写好的代码)

    考虑怎么重载才能使得下列语句成立:

    cout<<5;

    cout<<“this”;

    cout<<5<<“this”;

    按照以下方式重载成ostream类的成员函数返回值是ostream类的引用

    ostream& ostream::operator<<(int n)
    {...//输出n的代码return *this;
    }ostream& ostream::operator<<(const char* s)
    {...//输出s的代码return *this;
    }
    

    cout<<5<<"this"等价于cout.operator<<(5).operator<<("this")

  2. 将<<重载为全局函数(需要自己写的)

    假定下面程序输出为5hello,我们该如何补写?

    class CStudent{ public:int nAge;
    };
    int main()
    { CStudent s ; s.nAge = 5; cout << s <<"hello"; return 0;
    }
    
    ostream& ostream<<(ostream& o,const CStudent& s)
    {o<<s.nAge;return o;
    }
    

    正如:

    重载成普通函数,参数个数为运算符目数;

    重载成成员函数,参数个数为运算符目数-1

    <<被重载成全局函数,第一个参数就是cout,因此第一个参数类型必须为ostreamostream&

    由于我们需要继续输出“hello”,因此返回值必须为cout,故返回值类型为ostream&

  3. 将<<重载为全局函数,且定义成相关类的友元函数(需要自己写的)

    这样可以访问指定类的私有成员

    假定c是Complex复数类的对象,现在希望 写cout << c;,就能以a+bi的形 式输出c的值,写cin>>c;,就能从键 盘接受a+bi形式的输入,并且使得
    c.real = a,c.imag = b

    int main()
    {Complex c;
    int n;
    cin>>c>>n;
    cout<<c<<","<<n;
    return 0;
    }
    

    示例输入/输出

    input:13.2+133i 87
    output:13.2+133i,87
    

    我们编写Complex类如下:

    #include<iostream>
    #include<string>
    #include<cstdlib>
    using namespace std;
    class Complex{double real,imag;public:Complex(double r=0,double i=0):real(r),imag(i){};friend ostream& operator<<(ostream& os,const Complex& c);friend istream& operator>>(istream& is,const Complex& c);//上面语句将<<,>>重载为Complex类的友元,可以访问Complex类的私有成员real,imag
    };/****************对<<的重载***********************************/
    ostream& operator<<(ostream& os,Complex& c)
    {os<<c.real<<"+"<<c.imag<<"i";//以“a+bi”的形式输出return os;
    }/****************对>>的重载***********************************/
    istream& operator>>(istream& is,Complex& c)
    {//将“a+bi”作为字符串读入,“a+bi”中间不能有空格string s;is>>s;//确定实部和虚部的分界点int pos=s.find("+",0);//分离出代表实部的字符串string sTmp=s.substr(0,pos);c.real=atof(sTmp.c_str());//atof库函数能将const char*指针指向的内容转换成float//分离出代表虚部的字符串sTmp=s.substr(pos+1,s.length()-pos-2);c.imag=atof(sTmp.c_str());return is;
    }
    

    ❗❗❗c_str()

    该函数返回一个指向正规C字符串的指针常量, 内容与本string串相同。 这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串

类型转换运算符的重载

#include<iostream>
using namespace std;
class Complex
{double real,imag;public:Complex(double r=0,double i=0):real(r),imag(i){};operator double(){return real;}//重载了 强制类型转换运算符 double
};int main()
{Complex c(1.2,3.4);/*显式转换*/cout<<(double)c<<endl;//输出1.2/*隐式转换*/double n=2+c;//等价于double n=2+c.operator double()cout<<n;//输出3.2
}

自增自减运算符的重载

  1. 如何将前置/后置的++,–区分开?

    自增运算符++、自减运算符–有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:

    • 前置运算符作为一元运算符重载

      • 重载为成员函数

        T& operator++()
        T& operator--()
        
      • 重载为全局函数

        T1& operator++(T2);
        T1& operator--(T2);
        //重载为全局函数时需要的参数个数比成员函数时多一个
        
    • 后置运算符作为二元运算符重载,要多写一个没用的参数

      • 重载为成员函数

        T operator++(int);
        T operator--(int);
        
      • 重载为全局函数

        T1 operator++(int,T2);
        T1 operator--(int,T2);
        //重载为全局函数时需要的参数个数比成员函数时多一个
        
  2. 重载运算符的返回值

    • 重载的原则:对运算符的重载要尽量维持运算符原本的属性
    • c++中内置的++a返回值是a的引用, a++返回值是临时变量a
      这也是为什么可以有(++a)=1,但不能有(a++)=1,(函数的返回值如果不是引用,不能放在等好的左边)
    • 为了维持上面那种性质,前置运算符的返回值是对象,后置运算符的返回值是临时变量

例子:

int main()
{CDemo d(5);cout<<(d++)<<",";cout<<d<<",";cout<<(++d)<<",";cout<<d<<"endl";cout<<(d--)<<",";cout<<d<<",";cout<<(++d)<<",";cout<<d<<"endl";return 0;
}

要求输出结果为

5,6,7,7
7,6,5,5

该如何编写CDemo?

class Demo
{private:int n;public:CDemo(int i=0):n(i){}//初始化列表用i初始化noperator int(){return n;}s.int()//强制类型转换运算符的重载,使得(int)s等价于s.int()//类型强制转换运算符重载时不能写返回值类型,实际上其返回值类型就是该运算符代表的类型CDemo& operator++();//前置成员CDemo operator++(int)//后置成员friend CDemo& operator--(CDemo&);//前置全局friend CDemo operator--(CDemo&,int);//后置全局
};
/*************************++a重载为成员函数*********************************/
CDemo& CDemo::operator++()
{n++;//这个n是operator++()作用的那个对象的私有变量nreturn *this;//返回修改后的对象的引用
}
//++s等价于s.operator++()/*************************a++重载为成员函数*********************************/
CDemo CDemo::operator++(int k)//k是一个没用的参数
{CDemo tmp(*this);//用复制构造函数构造一个临时对象,将修改前的对象的n值赋给他n++;return tmp;//返回修改前的对象
}//s++等价于s.operator++(0)/*************************--a重载为全局函数*********************************/
CDemo& operator--(CDemo& d)//对一个全局函数,传进来的参数必须是引用才能修改他的值
{d.n++;return d;
}//--s等价于operator--(s)/*************************a--重载为全局函数*********************************/
CDemo operator--(CDemo& d)
{CDemo tmp(d);n++;return tmp;
}//s--等价于operator--(s,0)

注意事项

  1. C++不允许定义新的运算符 ;
  2. 重载后运算符的含义应该符合日常习惯:
    • complex_a + complex_b
    • word_a > word_b
    • date_b = date_a + n
  3. 运算符重载不改变运算符的优先级;
  4. 以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;
  5. 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为
    类的成员函数。

继承

  • 继承:

    • 在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么可以把A作为一个基类而把B作为基类的一个派生类

    这是为了避免重复定义相似的类的麻烦

  • 派生类的性质:

    • 派生类中可以添加新的成员变量和成员函数

    • 派生类一经定义可以独立使用

    • 派生类拥有基类的全部成员**(但是依旧不能访问private)**

  • 例子-学生管理系统

    class CStudent{private:string sName;int nAge;public:bool IsThreeGood(){};void SetName(const string &name)//&表示引用{sName=name;}
    };//派生类的写法:类名:public基类名
    class CundergraduateStudent:public CStudent{private:int nDepartement;public:bool IsThreeGood(){...};//这个新的成员函数将基类的覆盖了bool CanBaoYan(){...};
    };class CGraduatedStudent:public CStudent{private:int nDepartement;char szMentorName[20];public:int CountSalary(){...};
    };
    
  • 派生类对象的内存空间:

    派生类对象体积=基类对象体积+派生类对象自己的成员变量体积

    基类对象的存储位置位于派生类对象新增成员变量之前

    class CBase{int v1,v2;
    };class CDerived:public CBase{int v3;
    }//CDerived体积为12个字节
    
  • 继承示例程序:学籍管理

    #include<iostream>
    #include<string>using namespace std;class CStudent{private:string name;string id;char gender;int age;public:void PrintInfo();void Setnfo(const string & name_,const string & id_,int age_.char gender_);//&参数是引用string GetName(){return name;}
    };class CUndergraduateStudent:public CStudent{private:string department;public:void QualifiedForBaoyan(){cout<<"qualified for baoyan"<<endl;}//PrintInfo对于基类的同名函数是覆盖的关系void PrintInfo(){CStudent::PrintInfo();//调用基类的cout<<"Department:"<<departement<<endl;void SetInfo(const string& name_,const string& id_,int age_,char gender_,const string& department_){CStudent::SetInfo(name_,id_,age_,gender_);//调用基类的department=department_;}}
    };
    

继承关系和复合关系

  • 继承:""关系

    A是基类,B是A的派生类

    逻辑上要求:一个B对象也是一个A对象

  • 复合:""关系

    逻辑上要求:A对象是B对象的成员变量

    例子:几何形体程序中,需要写"点"类,也需要写"圆"类,两者的关系就是复合关系,每一个圆对象内都包含一个点对象,这个点对象就是圆心

class CPoint{double x,y;friend class CCircle;//便于CCircle类操作其圆心
};class CCircle{double r;CPoint center;
};
  • 复合关系的使用

    如果要写一个小区养狗管理程序, 需要写一个“业主”类,还需要写一个“狗”类。
    而狗是有 “主人” 的,主人当然是业主(假定狗只有
    一个主人,但一个业主可以有最多10条狗)

  1. 凑合的写法

    //为狗类设一个业主类的对象指针
    //为业主类设一个狗类的对象数组class CMaster;
    //CMaster必须提前声明,不能先写CMaster再写CDog类class CDog{CMaster* pm;
    };
    class CMaster{CDog dogs[10];
    };
    /*这种写法的缺陷:
    1.对象的成员变量理论上应是该对象的不可分割的组成部分,但主人对于狗并不是这种关系
    2.所有的狗对象都被放在一个数组中,对狗的操作必须通过主人来进行
    
  2. 正确的写法

    //为狗类设一个业主类对象指针
    //为业主类设一个狗类对象指针数组class CMaster;class CDog{CMaster* pm;
    };
    class CMaster{CDog* dogs[10]
    };
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RrOfSAFr-1636871571881)(assets/image-20210304144737070.png)]

覆盖和保护成员

覆盖

派生类可以定义一个和基类成员同名的成员,这叫做覆盖

在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员

要在派生类中访问由基类定义的同名成员时,要使用作用域符号::

类的保护成员

  • 基类的private成员:可以被下列函数访问

    – 基类的成员函数

    – 基类的友元函数

  • 基类的public成员:可以被下列函数访问

    – 基类的成员函数

    – 基类的友元函数

    – 派生类的成员函数

    – 派生类的友元函数

    – 其他的函数

  • 基类的protected成员:可以被下列函数访问

    – 基类的成员函数

    – 基类的友元函数

    – 派生类的成员函数可以访问当前对象的基类的保护成员

派生类的构造函数

class Bug{private:int nlegs;int ncolor;public:int ntype;Bug(int legs,int color);void PrintBug(){};
};class FlyBug:public Bug{private:int nwings;public:FlyBugs(int legs,int color,int wings);
}Bug::Bug(int legs,int color)
{nlegs=legs;ncolor=color;
}

错误的FlyBug构造函数写法:

FlyBug::FlyBug(int legs,int color,int wings)
{nlegs=legs;//不能访问ncolor=color;//不能访问//上面的操作是错误的!!!!nlegs,ncolor是基类的私有成员,不能被派生类的成员函数访问!ntypes=1;//okknwings=wings;
}

正确的FlyBug构造函数写法:

FlyBug::FlyBug(int legs,int color,int wings):Bug(legs,color)
//初始化列表
{nlegs=legs;ncolor=color;//上面的操作是错误的!!!!nlegs,ncolor是基类的私有成员,不能被派生类的成员函数访问!nwings=wings;
};
  • 在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数 之前,总是先执行基类的构造函数。

  • 调用基类构造函数的两种方式

    • 显式方式:在派生类的构造函数中,为基类的构造函数提供参数.

      derived::derived(arg_derived-list):base(arg_base-list)

    • 隐式方式:在派生类的构造函数中,省略基类构造函数时, 派生类的构造函数则自动调用基类的默认构造函数.

  • 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。

公有继承的赋值兼容规则

公有继承:class derived: public base{ };

class base{};
class derived:public base{};
base b;
derived d;

规则:

  1. 派生类的对象可以赋值给基类对象

    b=d;
    
  2. 派生类对象可以初始化基类的引用

    base & br=d;
    
  3. 派生类对象的地址可以赋值给基类指针

    base * pb=&d;
    

直接基类和间接基类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dcrtg4Og-1636871571881)(assets/image-20210307215222740.png)]

  • 声明派生类时,只需要列出其直接基类

  • 派生类沿着类的层次向上自动继承他的间接基类

  • 派生类的成员包括:

    • 直接基类的成员

    • 所有间接基类的所有成员

    • 自己的成员

#include<iostream>
using namespace std;class base{public:int n;base(int i):n(i){//构造函数有参数,也有初始化列表//n是成员变量,i是参数表cout<<"base"<<n<<"constructed"<<endl;}~base(){cout<<"base"<<n<<"destructed"<<endl;}
};class derived:public base{public:derived(int i):base(i){//构造函数有参数,也有初始化列表cout<<"derived constructed"<<endl;}~derived(){cout<<"derived destructed"<<endl;    }
};class morederived:public derived{public:morederived():derived(4){//构造函数有参数,也有初始化列表//只需要直接基类的初始化列表cout<<"morederived constructed"<<endl;}~morederived(){cout<<"morederived destructed"<<endl;    }
};int main()
{morederived obj;return 0;
}

output:

base4constructed
derived constructed
morederived constructed
morederived destructed
derived destructed
base4destructed

北大郭炜《程序设计与算法(三)》Mooc笔记:运算符重载和继承相关推荐

  1. 北大郭炜慕课程序设计与算法(一)C++的OpenJudge题目答案

    北大郭炜慕课程序设计与算法(一)C++的OpenJudge题目答案 学习心得 题目答案 001 输出第二个整数 002 字符菱形 003 打印ASCII码 004 打印字符 005 整型数据类型存储空 ...

  2. 程序设计与算法 | (3) 输入输出与运算符、表达式

    本专栏主要基于北大郭炜老师的程序设计与算法系列课程进行整理,包括课程笔记和OJ作业.该系列课程有三部分: (一) C语言程序设计:(二) 算法基础:(三) C++面向对象程序设计 (一) C语言程序设 ...

  3. 面向对象程序设计上机练习十二(运算符重载)

    面向对象程序设计上机练习十二(运算符重载) Time Limit: 1000MS  Memory Limit: 65536KB Submit  Statistic Problem Descriptio ...

  4. 程序设计与算法三~C++面向对象程序设计~北大郭炜MOOC学习笔记~第二章:类和对象初步(新标准C++程序设计)

    以下内容为笔者手打,望读者珍惜,如有转载还请注明. chapter2:类和对象初步 数据结构+算法=程序 $2.1结构化程序设计的不足     结构化程序设计也称面向过程的程序设计,过程是用函数实现的 ...

  5. 程序设计与算法三~C++面向对象程序设计~北大郭炜MOOC学习笔记~第三章:类和对象进阶(新标准C++程序设计)

    以下内容为笔者手打,望读者珍惜,如有转载还请注明. chapter 3:类和对象进阶 $3.1构造函数 $3.1.1 构造函数的概念和作用     全局变量在程序装入内存时就已经分配好了存储空间,程序 ...

  6. 程序设计与算法三~C++面向对象程序设计~北大郭炜MOOC学习笔记~第五章:继承与派生(新标准C++程序设计)

    以下内容为笔者手打,望读者珍惜,如有转载还请注明. 第五章 继承与派生 $5.1 继承与派生的概念 $5.1.1 基本概念     在C++中,当定义一个新的类B时,如果发现类B拥有某个已经写好的类A ...

  7. 程序设计与算法三~C++面向对象程序设计~北大郭炜MOOC学习笔记chapter1第一章(新标准C++程序设计)

    以下内容为笔者手打,望读者珍惜,如有转载还请注明. $1.4强制类型转化运算符的新形式:     类型名(待转化的表达式),如double(a),int(3.5) $1.5函数参数的默认值     在 ...

  8. 北大郭炜教授《程序与算法(二)算法基础》学习笔记

    目录 第一章 枚举 例题一 完美立方 例题二 生理周期 例题三 称硬币 例题四 熄灯问题 第二章 递归(一) 例题一 求阶乘 例题二 汉诺塔 例题三 n皇后问题 例题四 逆波兰表达式 补充笔记(fro ...

  9. 北大郭炜算法课笔记整合

    一.枚举: 枚举重点关注三个方面:顺序(循环的内外关系.以及枚举时的大小顺序).范围(循环的范围,往往是作优化用).变量的变化方式(从大到小&从小到大.※以及一次变化多少) 例1,对于这个题目 ...

最新文章

  1. linux 唯一行数量,linux – 确定bash中具有awk或类似内容的唯一行数
  2. VC++连接Mysql
  3. JDEManual2 Overview
  4. 计算机教师资格考试试题,全国教师资格考试信息技术练习题(二)
  5. 【BZOJ1188】分裂游戏,博弈
  6. What is Equivocation in Byzantine Fault Tolerance?
  7. python 的内置方法zip()介绍
  8. PCL之ubuntu安装CloudCompare
  9. 计算机维护费可以跨年吗,航天信息服务费可以跨年抵扣吗
  10. 解决办法:C代码中明明有,为什么编译时提示未定义的引用
  11. Oracle SQL语句优化【4】之使用SQL优化工具
  12. 数据恢复哪家强?四大数据恢复类软件评测
  13. java pojo生成_使用maven根据JSON文件自动生成Java POJO类(Java Bean)源文件
  14. scrapy分布式写入到mysql_scrapy-redis分布式爬虫去重异步写入mysql数据库实例代码...
  15. raise_for_status()方法
  16. 香蕉树上第六根芭蕉——PCA算法python实现和思考-站在巨人肩膀上
  17. 【Python数据挖掘课程】八.关联规则挖掘及Apriori实现购物推荐
  18. 浅谈嵌入式MCU软件开发之S32K1xx系列MCU启动过程及重映射代码到RAM中运行方法详解
  19. 计算机毕业设计JAVA高校体育场馆预约管理系统设计与实现mybatis+源码+调试部署+系统+数据库+lw
  20. 扇形图形用html,css如何画扇形?

热门文章

  1. 计算机毕业设计(9)python毕设作品之校园失物招领系统
  2. 电容充电时间计算公式
  3. 也许履历表可以这样填
  4. Day20200713—点在三角形内
  5. Android中PIN和PUK码解锁研究
  6. 【LLC原理与设计】仙童半导体 LLC原理与设计
  7. 新版正方教务系统Java爬取_正方教务系统成绩爬取(仅个人)+tk可视化
  8. 2020-10-16 js实现模拟双色球摇号
  9. c语言已知加速度求位移速度,已知初速度,加速度,时间,求位移
  10. 梦饮酒者,旦而哭泣;梦哭泣者,旦而田猎。方其梦也,不知其梦也。 ------庄子.齐物论-节选