c++this_货物链表类_友元函数_操作符重载_const


站在编译器和C的角度剖析c++原理, 用代码说话


this强化

我们上一篇中最后提到了this指针的本质,这次我们稍微做一下回顾和强化:

class Test
{
public:
    Test(int a, int b) //有参构造函数
    {
        this->a = a;
        this->b = b;
    }
    int getA()
    {
        return a;
    }
    int getB()
    {
        return b;
    }
public:
    Test& add(Test &t2) //*this
    {
        this->a = this->a + t2.getA();
        this->b = this->b + t2.getB();
        return *this; //*操作让this指针回到元素状态
    }

    Test add2(Test &t2) //*this
    {
        Test t3(this->a+t2.getA(), this->b + t2.getB()) ;//调用了构造器,初始化了Test
        return t3;//返回Test对象
    }
public:
    void printAB()
    {
        cout<<"a:"<<a<<"b:"<<b<<endl;
    }
protected:
private:
    int a;
    int b;
};

//全局函数
//如果把全局函数转成成员函数,少了一个操作数,通过this指针,被隐藏。
Test add(Test &t1, Test &t2)
{
    Test t3(t1.getA()+t2.getA(), t1.getB() + t2.getB());
    return t3;//返回的是t3对象,赋值给匿名对象。
}

//把成员函数转成全局函数,需要多一个参数。。。。
void printAB(Test *pthis)
{
    cout<<"a:"<<pthis->getA()<<"b:"<<pthis->getB()<<endl;
}
int main()
{
    Test t1(1, 2); //Test(&t1, 1, 2);
    Test t2(3, 4);
    Test t3 = add(t1, t2);
    t1.add(t2); //---->add(&t1, t2);
    t1.printAB();
    return 0;
}

我们可以看出当我们使用Test t1(1, 2);去调用类的构造函数初始化的时候,如果形参和类的私有属性没有重名的话可以不用this的,但是我们要清楚本质,所以开始学习这些的时候都写全吧. 总结一点就是说,当我们调用类中的成员方法,不管是构造函数还是什么,都会隐藏的传入this指针的.

货物链表类

我们来进行一个小型的综合案例. 这个例子就是说维护一个链表,当你补充的时候就在链表尾不断的加,如果买走一个,就是头节点走,然后往后移动:

class Goods
{
    public:
        Goods ( int  w) {
            weight = w ;
            total_weight += w ;
        }
        ~Goods() {
            total_weight -= weight ;
        }
        int  Weight() {
            return  weight ;
        };
        static  int  TotalWeight() {
            return  total_weight ;
        }
        Goods *next ;
    private:
        int  weight ;
        static  int  total_weight ;
};
int  Goods::total_weight = 0 ;
//业务操作函数 通过全局函数实现
void purchase( Goods * &f, Goods *& r, int w )
{
    Goods *p = new Goods(w) ; //用new 在堆中创建。
    p -> next = NULL ;
    if ( f == NULL )  {
        f = r = p ;
    }
    else {
        r -> next = p ;
        r = r -> next ;
    }
}

//业务操作函数
void sale( Goods * & f , Goods * & r )
{
    if ( f == NULL ) { cout << "No any goods!\n" ;  return ; }
    Goods *q = f ;  f = f -> next ;  delete q ;//从堆上删除。
    cout << "saled.\n" ;
}

int main()
{
    Goods * front = NULL /*头*/, * rear = NULL ;
    int  w ;  int  choice ;
    do
    {
        cout << "Please choice:\n" ;
        cout << "Key in 1 is purchase,\nKey in 2 is sale,\nKey in 0 is over.\n" ;
        cin >> choice ;
        switch ( choice )        // 操作选择
        {
            case 1 :    // 键入1,购进1箱货物
            {
                cout << "Input weight: " ;
                cin >> w ;
                purchase( front, rear, w ) ;        // 从表尾插入1个结点
                break ;
            }
            case 2 :                     // 键入2,售出1箱货物
            {
                sale( front, rear ) ;
                break ;
            }       // 从表头删除1个结点
            case 0 :
                break ;                     // 键入0,结束
        }
        cout << "Now total weight is:" << Goods::TotalWeight() << endl ;
    } while ( choice ) ;
    return 0;
}

我们会发现没有拷贝构造函数,那么在进行引用传递的时候,只能进行浅拷贝,但是我们进行的是指针的引用传递, 当作用域结束后,栈中的指针就会释放掉了, 所以能够修改也不影响什么.

友元函数

什么是友元函数,这是一种用friend关键词定义的函数,说白了就是该类外的函数无法使用到该类的私有变量,但是在类中用friend申明后就表示是类的好朋友了,就能够使用类中的私有属性了:

class A1
{
public:
    A1()
    {
        a1 = 100;
        a2 = 200;
    }
    int getA1()
    {
        return this->a1;
    }
    //声明一个友元函数
    friend void setA1(A1 *p, int a1);
protected:
private:
    int a1;
    int a2;
};
void setA1(A1 *p, int a1)
{
    p->a1 = a1;
}
int main()
{
    A1 mya1;
    cout<<mya1.getA1()<<endl;
    setA1(&mya1, 300);
    cout<<mya1.getA1()<<endl;
    return 0;
}

应为既然用到友元,那么一般就和类有关系了,所以在友元函数的参数中一般会传入类.
友元函数破坏了类的封装性, 为什么要定义个这东西呢?在java中如果想要访问私有属性,可以通过反射机制, 但是对于c++来说,这个难度很大,因为: java的编译过程是: java—》1.class==》class==>java类, 但是c++的编译过程是: cc++ 1预编译gcc -E 2汇编 gcc -i 3编译gcc -c 3、链接ld ===》汇编代码.
我们再来个友元类的例子:

class A
{
    //b是a的好朋友
    friend class B;
public:
    void display()
    {
        cout<<x<<endl;
    }  
protected:
private:
    int x;
};
class B
{
public:
    void setA(int x)
    {
        Aobj.x = x;//在B类中能直接使用A类的私有成员
    }
    void printA()
    {
        cout<<Aobj.x<<endl;
    }
protected:
private:
    A Aobj;
};
int main()
{
    B b1;
    b1.setA(100);
    b1.printA();
    return 0;
}

操作符重载

class Complex{
public:
    Complex(int a, int b){
        this->a = a;
        this->b = b;
    }
private:
    int a;
    int b;
};
int main(void){
    Complex c1(1, 2), c2(3, 4);
    int a = 0;
    int b = 0;
    int c = a + b;
    Complex c3 = c1 + c2;
    return 0;
}

首先对于c来说是没问题的,但是c3是编译不通过的,这是为什么? 因为编译器对基础数据类型知道如何去运算的,但是对于用户自定义类型编译器并不知道如何去进行加减. 所以我们必须对这些自定义类型使用的操作符进行重载. 为了方便起见我们先将私有属性移到public区:

class Complex{
public:
    Complex(int a, int b){
        this->a = a;
        this->b = b;
    }
    int a;
    int b;
private:
};
Complex add(Complex &c1, Complex &c2){//通过全局函数完成对象加减
    Complex c3(c1.a + c2.a, c1.b + c2.b);
    return c3;
}
int main(void){
    Complex c1(1, 2), c2(3, 4);
    int a = 0;
    int b = 0;
    int c = a + b;
    //Complex c3 = c1 + c2;
    Complex c3 = add(c1, c2);
    return 0;
}

这样我们的c3就没问题了,就是写了个全局方法,那么这还只是真正操作符重载路上的一步,接下来我们就进一步演变:

class Complex{
public:
    friend Complex operator+(Complex &c1, Complex &c2);
    Complex(int a, int b){
        this->a = a;
        this->b = b;
    }
private:
    int a;
    int b;
};
Complex operator+(Complex &c1, Complex &c2){
    Complex c3(c1.a + c2.a, c1.b + c2.b);
    return c3;
}
int main(void){
    Complex c1(1, 2), c2(3, 4);
    int a = 0;
    int b = 0;
    int c = a + b;
    Complex c3 = c1 + c2;
    //Complex c3 = add(c1, c2);
    return 0;
}

我们首先将属性恢复了正常, 然后我们写了一个全局的operator+的方法,因为要用到类中的属性,所以在类中建立好朋友关系. 这就是加号的重载就完成了. Complex c3 = c1 + c2;的本质就是Complex c3 = operator+(c1, c2);直接返回了匿名对象,用匿名对象进行初始化,不需要走拷贝构造和构造效率瞬间提高. 也就是我们之前说到的,这种方式相当于c3接手了函数作用域中的c3,而不仅仅是浅拷贝了. 如果是先定义了c3然后再从匿名函数赋值,那就是浅拷贝了,如果里面有指针指向堆中就存在很大的问题了.
我们认识了操作符重载本质就是函数后,我们来进行”*”和前置”++”的重载, 在实际中,这些简单操作符都作为成员函数存在的, 所以我们全局函数和成员函数都试验一下:

class Complex
{
public:
    friend Complex operator+(Complex &c1, Complex &c2);
    friend Complex operator*(Complex &c1, Complex &c2);
    friend Complex& operator++(Complex &c1); //前置++
    Complex(int a, int b)
    {
        this->a = a;
        this->b = b;
    }
    void printCom()
    {
        cout<<a<<" + "<<b<<"i "<<endl;
    }
    //通过类成员函数完成-操作符重载
    Complex operator-(Complex &c2)
    {
        Complex tmp(a - c2.a, this->b - c2.b);
        return tmp;
    }
    //通过成员函数完成前置--
    Complex& operator--()
    {
        this->a--;
        this->b--;
        return *this;
    } 
private:
    int a;
    int b;
};
Complex operator+(Complex &c1, Complex &c2)
{
    Complex tmp(c1.a + c2.a, c1.b + c2.b);
    return tmp;
}
Complex operator*(Complex &c1, Complex &c2)
{
    Complex tmp(c1.a*c2.a, c1.b*c2.b);
    return tmp;
}
//前置++
Complex& operator++(Complex &c1)
{
    c1.a ++;
    c1.b ++;
    return c1;
}
int main()
{ 
    Complex c1(1, 2), c2(3, 4);
    Complex c3 = c1 + c2;
    //用类成员函数实现-运算符重载
    Complex c4 = c1 - c2;
    c4.printCom();
    //等价于Complex c4 = c1.operator-(c2);
    //前置++ 全局函数
    ++c1;
    c1.printCom();
    //前置-- 成员函数
    --c1;
    c1.printCom();
    //c1.operator--()
    return 0;
}

需要注意的就是双元操作符返回的是匿名对象,疑问有对象去接,但是单元操作符返回的是引用,也就是要返回对象本身, 因为好多时候是不需要对象去接的,如果返回匿名对象,没人接会直接收走的. 并且成员函数中的单元运算符中是不需要形参的,但是编译器会默认给个this指针过去. 于是我们就得用this指针去操作,前置是先加减再返回,这个还是简单的,那么我们进一步延伸:

class Complex
{
public:
    friend Complex operator++(Complex &c1, int);
    Complex(int a, int b)
    {
        this->a = a;
        this->b = b;
    }
    void printCom()
    {
        cout<<a<<" + "<<b<<"i "<<endl;
    }
    //通过类成员函数完成后置--
    Complex operator--(int)
    {
        Complex tmp = *this;
        this->a--;
        this->b--;
        return tmp;
    }

private:
    int a;
    int b;
};
//后置++ 全局函数
Complex operator++(Complex &c1, int)
{
    Complex tmp = c1;
    c1.a++;
    c1.b++;
    return tmp;
}

int main()
{
    Complex c1(1, 2), c2(3, 4);
    //后置++ 全局函数
    c1++; //operator++(c1);
    c1.printCom();

    //后置-- 类成员函数
    c1--;  //c1.operator--()
    c1.printCom();
    return 0;
}

这里有几点需要说明,后置操作和前置操作的函数名是一样的,但是他们的返回值是不一样的,那这就不属于是函数重载,那么这样写,编译器就会报错,那么怎么样能让编译器区分出是后置还是前置呢?C++创造者们就用占位符在形参中表示这样的方法就是后置操作符. 然后因为后置的原则是先执行后加减, 那么我就定义一个临时对象来保存这个当前的状态(再回顾一点,当进行引用传参的时候,并不是执行拷贝构造,而是常指针的拷贝,和用对象记性另一个对象是不一样的,后者就是执行了拷贝构造函数,如果没有写拷贝构造函数的话,就是浅拷贝),然后加减后我就以匿名对象的身份返回这个临时对象,如果有人接,那么我这个临时对象(因为之前进行了浅拷贝,所以tmp是保持原来的值不变的)就直接给了那个对象,但是如果没人接的话这个临时对象就消失,那么就使用加减后的值了. 这和Java/C中的++和–的运算是一样的.
接下类我们再进行操作符重载的拓展:
我们了解了前置后置等这些操作符后我们来进行c++中特有的”<<”输入输出流进行操作符重写:

class Complex
{
public:
    friend ostream& operator<<(ostream &out, Complex &c1);
    Complex(int a, int b)
    {
        this->a = a;
        this->b = b;
    }
    void printCom()
    {
        cout<<a<<" + "<<b<<"i "<<endl;
    }
private:
    int a;
    int b;
};
ostream& operator<<(ostream &out, Complex &c1)
{
    out<<c1.a<<" + "<<c1.b<<"i "<<endl;
    return out;
}
int main()
{
    Complex c1(1, 2), c2(3, 4);
    int a = 10;
    char *p = "addddd";
    cout<<"a"<<a<<endl;
    cout<<"p"<<p<<endl;

    //Complex自定义类型
    cout<<c1;

    //全局函数
    //cout<<c1;
    //operator<<(cout, c1);

    //2 支持链式编程
    cout<<c1<<"abcc";
    //函数返回值当左值,要求返回一个引用。。。。。
    //cout.operator<<(c1).operator<<("abcd");
    //s<<"abcd"
    //s cout.operator<<(c1);
    //s.operator<<("abcd");   
    return 0; 
}

当我们执行cout<<c1;的时候,调用者是cout,那么cout是哪个类呢?
这里写图片描述
我们会发现是ostream这个类,但是我们并没有这个类的源码,所以并不能写成成员函数的形式,所以说像重载输入输出流一般情况下都会写在全局函数上,其他的操作符一般都会写为成员函数. 然后我们为了支持链式编程,所以必须返回对象本身才能当左值.
到这里我们就将操作符重载搞完了,当然这里有Array.cpp, Array.hppArrayTest.cpp三个小例子综合性很不错,可以参考.

const延伸

想必大家见过int A() const{}这种方式的成员函数吧?这个const是什么鬼呢?我们这里就帮您解释这的本质.

class Test
{
public:
protected:
private:
    int a;
    const int A()
    {
         a++;
        //b++;
        return a;
    }
    void A(int val)
    {
        a = val;
    }
    int BBB()
    {
        return a;
    }
    int b;
};
int main()
{
    Test t1;
    return 0;
}

观点1:const是修饰a,但是通过测试,我们发现,b++也不能编译通过, 这说明:const把a 和 b都修饰了, 所以我们得出const是修饰this, 但是因为this作为函数的第一个参数,被隐藏, const没地方放,就放在了外面. 这就相当于是const Test *pthis, 因为const修饰的this指针指向的内存空间而不是常指针.


联系方式: reyren179@gmail.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值