C++程序设计理论

Posted by PowderHan on March 29, 2019 已经被偷看过次啦QAQ

http://www.170mv.com/kw/other.web.rh01.sycdn.kuwo.cn/resource/n1/60/68/1671896015.mp3

C++程序设计笔记

By PowderHan

2019


Begining


$(1)$ 运算符重载

  • 运算符重载的实质是函数重载
  • 可以重载为普通函数,也可以重载为成员函数
  • 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数
返回值类型 operator 运算符(形参表) {
    ……
}
  • 普通函数又不能访问私有成员,所以,需要将运算符 $+$ 重载为友元。
  • 运算符重载为友元函数
class Complex {
private:
    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);
};

Complex operator+ (double r,const Complex & c) {  
    //能解释 5+c  

    return Complex( c.real + r, c.imag);
}
  • 流插入运算符和流提取运算符的重载
class Complex {
private:
    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,Complex & c);
};
ostream & operator<<( ostream & o,const CStudent & s) {
    o << s.nAge ;
    return o;
}
istream & operator>>( istream & is,Complex & c) {
    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;
}
  • 重载转换运算符
operator int ( ) { return n; }
Demo s;
(int) s ; //等效于 s.int();  

$int$ 作为一个类型强制转换运算符被重载

  • 重载运算符()、 []、 ->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。
  • 以下运算符不能被重载: $.\ .*\ ::\ ?\ :\ sizeof$

$(2)$ 静态成员

  • 静态成员:在定义前面加了 $static$ 关键字的成员
class CRectangle {
private:
    int w, h;
    static int nTotalArea; 
    //静态成员变量   

    static int nTotalNumber;
public:
    CRectangle(int w_,int h_);
    ~CRectangle();
    static void PrintTotal();  
    //静态成员函数  

};
  • 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份, 为所有对象共享
  • $sizeof$ 运算符不会计算静态成员变量。
  • 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在
  • 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。因此静态成员不需要通过对象就能访问。
  • 静态成员函数本质上是全局函数。
  • 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。(无具体对象实例)

访问静态成员的方法

1) 类名::成员名
CRectangle::PrintTotal();
2) 对象名.成员名
CRectangle r; r.PrintTotal();
3) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
4) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalArea = 0;
  • 必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过。

$(3)$ 常量成员

  • 如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加 $const$ 关键字
  • 常量对象只能使用构造函数、析构函数和有 $const$ 说明的函数(常量方法)
  • 在类的成员函数说明后面可以加 $const$ 关键字,则该成员函数成为常量成员函数
  • 常量成员函数内部不能改变属性的值,也不能调用非常量成员函数
  • 在定义常量成员函数和声明常量成员函数时都应该使用 $const$ 关键字
class Sample {
    private :
    int value;
    public:
    void PrintValue() const;
};
void Sample::PrintValue() const {  
    //此处不使用const会导致编译出错  

    cout << value;
}
void Print(const Sample & o) {
    o.PrintValue();  
    //若 PrintValue非const则编译错  

}
  • 如果一个成员函数中没有调用非常量成员函数,也没有修改成员变量的值,那么, 最好将其写成常量成员函数。

  • 常量成员函数的重载

    两个函数,名字和参数表都一样,但是一个是 $const$ ,一个不是,算重载

class CTest {
private :
    int n;
public:
    CTest() { n = 1; }
    int GetValue() const { return n; }
    int GetValue() { return 2 * n; }
};
int main() {
    const CTest objTest1;
    CTest objTest2;
    cout << objTest1.GetValue() << "," << objTest2.GetValue() ;
    return 0;
}

 输出 => 1,2
  • $mutable$ 成员变量

    可以在 $const$ 成员函数中修改的成员变量

class CTest {
public:
    bool GetData() const {
        m_n1++;
        return m_b2;
    }
private:
    mutable int m_n1;
    bool m_b2;
};

$(4)$ 封闭类

  • 有成员对象的类叫封闭($enclosing$)类。
  • 任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的。

    封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数。
    对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
    当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。

  • 封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象,也会用复制构造函数初始化。

$(5)$ 友元

  • 友元分为友元函数和友元类两种

$1. $ 友元函数: 一个类的友元函数可以访问该类的私有成员

  • 可以将一个类的成员函数(包括构造、析构函数)说明为另一个类的友元。
class B {
    public:
    void function();
};
class A {
    friend void B::function();
};

$2. $ 友元类:如果$A$是$B$的友元类,那么$A$的成员函数可以访问$B$的私有成员

  • 友元类之间的关系不能传递,不能继承。

$(6)$ $public$ 继承的赋值规则

  • 派生类的对象可以赋值给基类对象
base b = d; 
 
  • 派生类对象可以初始化基类引用
base &br = d;
 
  • 派生类对象的地址可以赋值给基类指针
 base *pb = & d;
 
  • 如果派生方式是 $private$ 或 $protected$ ,则上述三条不可行。

  • 将派生类引用或指针转换为基类引用或指针被称为向上强制转换,可以实现自动转换。
  • 将基类指针或引用转换为派生类指针或引用被称为向下强制转换,需要进行强制类型转换才可实现。

  • 派生类的成员函数可以访问基类的 $protected$ 成员

$(7)$ $protected$ 继承和 $private$ 继承

  • $protected$ 继承时,基类的 $public$ 成员和 $protected$ 成员成为派生类的 $protected$ 成员。
  • $private$ 继承时,基类的 $public$ 成员成为派生类的 $private$ 成员,基类的 $protected$ 成员成为派生类的不可访问成员。
  • $protected$ 和 $private$ 继承不是“是”的关系。

$(8)$ 基类指针

  • 即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。

$(9)$ 虚函数

  • 在类的定义中,前面有 $virtual$ 关键字的成员函数就是虚函数。
class base {
    virtual int get() ;
};

int base::get() {

}
  • $virtual$ 关键字只用在类定义里的函数声明中,写函数体时不用。

  • 工作原理

    编译器处理虚函数时,会给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针,这种数组被称为虚函数表($virtual \ function \ table$)

  • 虚函数成本

    $(1)$ 每个对象的空间将增大,增大量为存储地址的空间
    $(2)$ 对于每个类,编译器都将创建一个虚函数地址表(数组)
    $(3)$ 对于每个函数调用,都需要执行一项额外的操作:即到地址表中查找地址

  • 虚函数最重要的优点是可以实现动态联编(晚期联编)

  • 一些特殊函数的虚函数关系

    $(1)$ 构造函数不能是虚函数
    $(2)$ 析构函数应该是虚函数,除非不作为基类
    $(3)$ 友元函数不是类成员,所以不能是虚函数
    $(4)$ 如果重新定义继承的方法,应确保与原来的原型完全相同。(如果返回值类型是基类的引用或指针,可以修改为指向派生类的引用和指针,这种特性叫做返回类型协变),如果不相同,则会将基类中的所以同名函数隐藏。故如果基类声明被重载了,则应该在派生类中重新定义所有的基类版本


$(10)$ 多态

$一. $ 编译时多态
        函数重载

$二. $ 运行时多态

多态的表现形式(一)

  • 派生类的指针可以赋给基类指针
  • 通过基类指针调用基类和派生类中的同名虚函数时:
    $1. $ 若该指针指向一个基类的对象,那么被调用是基类的虚函数;
    $2. $ 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数

多态的表现形式(二)

  • 派生类的对象可以赋给基类引用
  • 通过基类引用调用基类和派生类中的同名虚函数时:
    $1. $ 若该引用引用的是一个基类的对象,那么被调用是基类的虚函数
    $2. $ 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数


$Not\ Ending…$




The total number of English words:1402
The total number of Chinese words:5644
欢迎点击下方的知乎图标关注我的知乎QAQ
毕生梦想-成为知乎大V
蟹蟹~