第一周:C++实现一个不带指针成员变量的类【Boolean】

实现一个complex复数类

示例代码:

#ifndef __COMPLEX__
#define __COMPLEX__

#include <cmath>


class ostream;
class complex       //   class head
{                   //{} class body
public:
    complex(double r = 0, double i = 0)
        :re(r), im(i)
    {}
    complex& operator += (const complex&);
    double real() const { return re; }
    double imag() const { return im; }

    friend complex & __doapl(complex*, const complex&);

    int func(const complex &param)
    {
        return param.re + param.im;
    }
private:
    double re, im;
};


inline complex&
__doapl(complex*ths, const complex&r)
{
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

inline complex&
complex::operator +=(const complex &r)
{
    return __doapl(this, r);
}

#endif // !__COMPLEX__

1.在.h头文件中添加“防御式声明“
这是为了防止头文件被多重包含(multiple inclusion),引发的编译问题

#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif  //__COMPLEX__

2.内联函数 inline
函数直接在class声明内定义完成,会自动声明为inline函数,例如real(),image()就是内联函数
如果函数在class体外声明,需要加上inline关键字,但是否是按照inline方式编译,由编译器决定的,我们只是建议编译器这做。

inline double 
imag(const complex& x)
{    
    return x.imag();  
}

3.ctor 构造函数

 complex(double r = 0, double i = 0)
   :re(r), im(i) {} //  Initialization list(初始化列表)

or

complex(double r = 0, double i = 0){
    re = r;
    im = i;
    } // 不使用初始化列表,在构造函数体内初始化

注:

  1. 如果不显示声明“构造函数”(即:不写构造函数),C++会默认创建“构造函数”,但它是 无参数、无返回值、可重载(overlaod)的。
  2. Initialization list(初始化列表):是构造函数独有的,建议把成员变量放到这里初始化。
  3. 成员变量放在初始化列表中 和 放在函数体内部的区别:
    • 初始化列表是在成员变量初始化的时候就已经改好了
    • 函数体内部是赋值,是成员变量先初始化,然后再把赋值给成员变量,这样就多了一个步骤

注意:构造函数的访问权限,大多数情况下,都是public的,但是在设计模式中的单例模式下,通常把构造函数设为private

4.类 成员函数

double real()  { return re; }
double imag()  { return im; }

5.常量成员函数
——使用Const 修饰成员函数

double real() const { return re; }
double imag() const { return im; }

在类成员函数名称后面加上const,限定函数体内部不可改变本类的成员变量。
注意:

  1. 常量成员函数内部不能调用非常量成员函数
  2. const类型的对象实例只能调用const声明过的成员函数

如果获取成员变量的函数不添加const,那按照下列使用是就会出错,
报错是因为 使用const创建 对象实例 c1,那就意味着c1所有的成员变量都是不可改变的,但是real(),image()并没有声明称cosnt常量成员函数,这就说明,在函数体内部是允许改变成员变量的, 存在歧义,故而报错
但是此种用法是合理的,报错就是因为我们设计的不合理,所以,强烈建议把不需要改变成员变量的成员函数使用const修饰

class complex
{
...
double real()  { return re; }
double imag()  { return im; }
}
...


int main()
{
    const complex c1(1, 2); // c1是complex类型的常量
    std::cout << c1.real() << std::endl;
    std::cout << c1.imag() << std::endl;
    ...
    ...
    ...
}
// “double complex::real(void)”: 不能将“this”指针从“const complex”转换为“complex &”  
// “double complex::imag(void)”: 不能将“this”指针从“const complex”转换为“complex &”

6.参数传递(值传递 pass by value 和 引用传递 pass by reference)

(pass by value[值传递] vs pass by reference[引用传递](to const))

在函数参数传递中,使用引用传递,能够有效的提高传递过程中堆栈利用率,所以建议C++中进行函数参数传递的时候尽量使用引用,能够有效地节约堆栈空间
但是为了避免传入参数的值被意外修改,可以使用const complex &,const类型的引用
eg:

complex & __doapl(complex*, const complex&);

7. friend (友元)

class complex
{   ...
    friend complex & __doapl(complex*, const complex&);
    int func(const complex &param)
    {   return param.re + param.im;    }
    ...
};
inline complex&
__doapl(complex*ths, const complex&r)
{
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

//注意: 相同class的各个object互为friends(友元)
complex c1(2,3);
complex c2;

c2.func(c1);

8.操作符重载

  1. 重载操作符可以是成员函数,还可以是友元函数。
  2. 重载操作符函数为成员函数主要是你需要操作类内部的成员,
    必须是成员函数或友元函数才行。
  3. 对于不带指针的成员变量的类来说不存在深拷贝浅拷贝的问题。
  4. 如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。

如果运算符被重载为类的成员函数:

  • 一元运算符没有参数
  • 二元运算符只有一个右侧参数,因为对象自己成了左侧参数(cosnt T *this)。

文献[Murray , p44-p47]对此问题作了较多的阐述,并总结了表8-4-1的规则。

运算符规则
所有的一元运算符建议重载为成员函数
= () [] ->只能重载为成员函数
所有其它运算符建议重载为全局函数

对于赋值操作符(=),这个比较特别,因为任何类如果不提供显示的拷贝赋值(即重载=),则编译器会隐式地提供一个。这样的话,如果你再通过友元声明,进行全局的定义会造成调用二义性(即使允许,编译也会出错)。

对于操作符(=,[],(),->),只能声明为成员函数是为了避免不合法的书写通过编译(这是推测出的原因,更深层的可能要研究 C++ 的设计了)。这涉及到 C++ 中类型的隐式转换。下面通过代码例子说明:

#include <iostream>
class X 
{
public:
    X(){}
    X(int){} // int 类型可以被隐式转换成 X
    friend bool operator<(const X& x1, const X& x2) { return true; } // 只是测试,无意义
};

class Y 
{
public:
    Y(){}
    Y(int){} // int 类型可以被隐式转换成 Y
    bool operator<(const Y& y) const { return true; } // 只是测试,无意义
};
int main()
{
    X x;
    if(1 < x) // 合法,使用友元重载函数,1 可以被隐式地转换为 X 类型 --友元函数的第一个参数
    {}

    Y y;
    if(1 < y) // 不合法,使用成员重载函数,函数的第一个参数是 const *this,1 不能被隐式转换
    {}
    return 0;

}

// 注:编译的时候可以通过注释掉不同的代码来查看错误(即合法性),后面解释不能作为友元全局重载的原因

由上面的代码可以知道,如果将 =,[],(),-> 进行友元全局重载,那么就会出现 1=x; 1[x]; 1->x; 1(x); 这样的合法语句(起码编译器认为这些是合法的)
参考代码中的 if(1 < x) 合法的片段,但显然这些是需要避免的,当然这些不是我们程序员的责任,应该有语言的设计者来实现,所以,就……。

#include <iostream>

class X 
{
public:
    X(){}
    X(int){} // int 类型可以被隐式转换成 X
    friend const X& operator+=(const X& x1, const X& x2) { return x1; } // 只是测试,无意义
};

int main()
{
    X x;
    1 += x;// 合法,使用友元重载函数,1 可以被隐式地转换为 X 类型 --友元函数的第一个参数
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值