C++学习笔记_complex类的实现

头文件中的防卫式声明

点击查看代码
#ifndef  __COMPLEX__
#define  __COMPLEX__
class complex
{

}
#endif

类的定义

点击查看代码
class complex//class head
{ 
    //class body
    //有些函數在此直接定义,另一些在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; }
private:
    double re, im;
    friend complex& __doapl (complex*, const complex&);
};

模板类:

点击查看代码
template<typename T>//T也可以是其他字母
class complex
{ 
public:
    complex (T r = 0, T i = 0)
        : re (r), im (i)
    { }
    complex& operator += (const complex&);
    T real () const { return re; }
    T imag () const { return im; }
private:
    T re, im;
    friend complex& __doapl (complex*, const complex&);
};

访问级别
public :外部能看到的
private:只有自己能看到的,一般只有内部函数处理的数据要用private
protect:受保护的

构造函数

构造函数在创建的时候就被调用起来,如果指明,就用指明的参数,如果没有,就要用默认值为参数

点击查看代码
    complex (double r = 0, double i = 0)//0为默认实参(default argument)
        : re (r), im (i)//初值列,初始列,initialization  list
    { }

初值化是只有构造函数享有的。

如上:re(r),im(i),效果和我们平时在大括号中赋值相同,即re=r,im=i;但是初值化效率更高,请充分利用这种特性。

不带指针的函数多半是不用写析构函数的。

c++容许有多个构造函数,但是上图这种新写的黄色加深的函数是不容许的,因为和最上面的构造函数相冲突,导致我们在构造的时候不知道用哪个构造函数来构造。

函数重载
函数名称一样,但是函数的参数个数或参数类型不一样或有无 const,与返回值类型无关。
单例模式中构造函数可能放到private中

点击查看代码
class A {
public:
    static A& getInstance();
    setup() { ... }
private:
    A();
    A(const A& rhs);
    ...
};
A& A::getInstance()
{
    static A a;
    return a;
}

如上图,在单例模式中,有一种需求,是把构造函数放到private中的,它不容许外界直接通过A a();来创建。

常量成员函数

当我们在设计接口的时候,这个函数要考虑到是否会对这个函数的返回值作出改变,如果不改变一定要加上const。

如果没加会造成什么后果呢,如果使用者在创建一个复数的对象时,声明这个对象时常量,是不可以被改变的,但是这个函数的内部中没有使用const,这就意味着这个函数有可能会被改变,那么编译器就会报错,使用者就会迷糊,为什么我定义了一个不可以被改变的常量都不能打印出来呢。虽然使用者在使用的时候可能不用const,但是我们要想的更周全。

参数传递

点击查看代码
class complex
{ 
public:
    complex (double r = 0, double i = 0)
        : re (r), im (i)
    { }
    complex& operator += (const complex&);//pass by reference
    double real () const { return re; }
    double imag () const { return im; }
private:
    double re, im;
    friend complex& __doapl (complex*, const complex&);
};

参数传递有两种方式,一种是by Value, 还有一种是by reference

Value 顾名思义就是值,但是当我们的Value大小很大时,假如有几十Kb,也用Value效率很低,很不美观,所以我们可以用by reference的形式来传递,这就是传递的一个指针指向的地址的引用,这个地址中报存的值就是我们想要传进去的东西,这种方式美观大气,如上图c2+=c1,其中传的就是c1指针指向的地址的引用。

传指针的方式很快,如果我们只想传这个数值,但是后续的操作可能会将这个地址中的值改变,然而我们构建这个函数的时候只想让你得到,但是不想让你改怎么办呢,就要加上const

所以在可以的情况下,我们能用by reference就多用by reference,这是一种高效的方法,是我们选择用c++来写程序的原因
返回值传递
同参数传递。

友元

在定义了友元函数后,友元函数可以直接拿到private中定义的数据

点击查看代码
class complex {
	...
	// 相同class的各个 objects 互为 friends(友员)
	int func(const complex &param) {
		return param.re + param.im; // 可直接访问 私有数据
	}
	private:
		double re, im;
};

什么样的情况是不可以用by reference来返回的
如果是在函数中创建的对象,那么这个对象就不能用引用的方式来传递,理解为本体已经死掉了,这地址中的东西被回收,就不是我们要看的东西,就不可以传引用。

在之前的博客中有专门一章讲述在C++中为什么尽量在传参时用pass-by-reference取代pass-by-value,其主要目的是为了节省构造函数和析构函数的成本,但这不能完全的取代,假如有下面的一个类:

1 class Rational
2 {
3     public:
4         Rational(int numerator = 0, int denominator = 1);
5     private:
6         int n, d;
7 };

现在为此类添加成员函数operator*,不妨先找找以下的代码有什么问题:

const Rational& operator*(Rational& a, Rational& b)
{
    Rational result(a.n*b.n, a.d*b.d);
    return result;
}

这段代码有两个问题需要注意:第一 在函数内部定义了一个local对象,然后返回它的地址,而我们应该指导local对象会在退出函数时被销毁,所以如果用户使用这个reference指向的对象会出现”未定义错误“;第二 result的创建还是逃避不了构造函数调用的成本。

总结:

  • 数据一定放在private中
  • 构建函数的写法尽量用初值化写法
  • 参数的传递能用引用就用引用(先考虑by reference)
  • 类名相同时他们互为友元。
  • 不希望改变的值一定要加const
  • 构建函数可以有多个
  • 函数重载只是我们看着名称一样,但是机器看是不同的。

操作符重载

this是这个编译器给我们的一个特殊的指针,这个指针从的是调用者的地址,如c2+=c1,“+=”在编译器眼里相当于一个函数,那么是谁来调用呢,c2,即this指针指向的就是c2。this一般是隐藏起来的,它不能在参数列写出来,但是你可以使用,它可能在参数区前面也可能在参数区后面。 同时我们要保存好习惯,c1我们是不希望改变的,所以要加上const 符号。

1、_doapl这个函数的返回值是一个value ,接收方是complex &,我们在用return by reference的时候传送的一方不需要知道接收者是by reference的方式接收的。

2、我们在设计函数的时候要考虑到连加连减的情况发生,虽然说这个里面参数的value已经发生变化。只是单一的运算是不需要返回值的,但是一旦连续运算,就会出错。

头文件的布局

typename()是临时对象temp object,没有名称,在它的下一行就会被释放吧,如上图所示,我们要传出去的是一个value, 这个complex类型的value就是用的临时对象语法

这里对“<<”进行操作符的重载,首先想到我们传进去的参数是否可以用by reference,好的,可以,这个值会不会被改变,这个参数是我们不想改变的,前面加上const,那么<<是作用到什么上的,作用到cout上,cout是什么,是一个ostream类型的东西,这个东西可不可以传引用,可以,这个东西我们要不要更改,因为每次输出都会改变状态,所以不能加const,它的返回有用吗,没有用,好像可以用来接收,但是如果有多个<<连着输出呢,好的不能用void,我们得返回一个相同类型的对象,这个可以用引用吗,可以。

incline 的使用说明:

incline 意为内联 其目的是提高函数执行效率。在 C程序中,可以用宏代码提高执行效率,内联函数相对于宏来说,增加了对类型的检查,使用起来更安全。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行return等过程,从而提高了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。如果检查函数正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。

C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在C++ 程序中,应该用内联函数取代所有宏代码,“断言assert”恐怕是唯一的例外。assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert不是函数,而是宏。

  • 内联可调试
  • 内联可进行安全类型检查或自动类型转换
  • 可访问成员变量
  • 定义在类声明中的成员函数自动转化为内联函数

内联函数的编程风格

关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。

//如下风格的函数Foo不能成为内联函数:
inline void Foo(int x, int y); // inline仅与函数声明放在一起
void Foo(int x, int y)
{
…
}
//而如下风格的函数Foo则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y) // inline与函数定义体放在一起
{
…
}

所以说,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了inline关键字,但我认为inline不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

适用范围:

1、如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
2.如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

点击查看代码
#ifndef _COMPLEX_
#define _COMPLEX_
#include <cmath>
#include <iostream>
using namespace std;
class Complex
{
private:
    double   re;
    double   im;
    friend Complex& _doapl(Complex*,const Complex& );
    friend Complex &__doami(Complex *, const Complex &);
    friend Complex &__doaml(Complex *, const Complex &);
public:
    Complex (double r = 0, double i = 0) 
        :re(r),im(i)  {} 
     
    double real() const { return re; }
    double imag() const { return im; }
    Complex& operator+=(const Complex&c);
    Complex& operator-=(const Complex&c);
    Complex& operator*=(const Complex&c);
};

inline Complex & 
Complex::operator += (const Complex& r){  
    return _doapl(this,r);     //+=的操作符重载交给友元函数做
}

inline Complex &
Complex::operator-=(const Complex &r){
    return __doami(this, r);    //-=的操作符重载交给友元函数做
}

inline Complex &
Complex::operator*=(const Complex &r){
    return __doaml(this, r);     //*=的操作符重载交给友元函数做
}

inline Complex&   
_doapl(Complex* ths,const Complex& r){   //实现+=的重载
    ths->re += r.re;
    ths->im += r.im;
    return *ths;     
}         
inline Complex &
__doami(Complex *ths, const Complex &r)  //实现-=的重载
{
    ths->re -= r.re;
    ths->im -= r.im;
    return *ths;
}

inline Complex &
__doaml(Complex *ths, const Complex &r)  //实现*=的重载
{
    double f = ths->re * r.re - ths->im * r.im;
    ths->im = ths->re * r.im + ths->im * r.re;
    ths->re = f;
    return *ths;
}

inline double
real(const Complex &r)
{
    return r.real();   
}

inline double
imag(const Complex &i)
{
    return i.imag();
}

inline Complex
operator + (const Complex&l,const Complex&r){      //复数加复数
   return Complex( real(l) + real(r), imag(l) + imag(r) );     
}

inline Complex
operator + (const Complex&l, double r){           //复数+实数
    return Complex( real(l)+r , imag(l));
}
inline Complex
operator + (double l,const Complex&r){              //实数加复数
    return Complex(l+real(r) , imag(r) );
}

inline Complex
operator - (const Complex&l,const Complex&r){
   return Complex(real(l) - real(r), imag(l) - imag(r) );   //复数-复数
}

inline Complex
operator - (const Complex&l, double r){     //复数-实数
    return Complex( real(l)-r , imag(l) );
}
inline Complex
operator - (double l,const Complex&r){      //实数-复数
    return Complex( l-real(r) , -imag(r) );
}

inline Complex
operator*(const Complex &left, const Complex &right)
{
    return Complex(real(left) * real(right) - imag(left) * imag(right),
                   real(left) * imag(right) + imag(left) * real(right));
}

inline Complex
operator*(const Complex &left, double right)
{
    return Complex(real(left) * right, imag(left) * right);
}

inline Complex
operator*(double left, const Complex &right)
{
    return Complex(left * real(right), left * imag(right));
}

inline Complex
operator/(const Complex &left, double y)
{
    return Complex(real(left) / y, imag(left) / y);
}

inline Complex
operator+(const Complex &x) // ??
{
    return x;
}

inline Complex
operator-(const Complex &x)
{
    return Complex(-real(x), -imag(x));
}

inline bool
operator==(const Complex &left, const Complex &right)
{
    return real(left) == real(right) && imag(left) == imag(right);
}

inline bool
operator==(const Complex &left, double right)
{
    return real(left) == right && imag(left) == 0;
}

inline bool
operator==(double left, const Complex &right)
{
    return left == real(right) && 0 == imag(right);
}

inline bool
operator!=(const Complex &left, const Complex &right)
{
    return real(left) != real(right) || imag(left) != imag(right);
}

inline bool
operator!=(const Complex &left, double right)
{
    return real(left) != right || imag(left) != 0;
}

inline bool
operator!=(double left, const Complex &right)
{
    return left != real(right) || 0 == imag(right);
}

inline ostream&
operator <<(ostream& o,const Complex& c){   
  return   o<<c.real()<<","<<c.imag();
}


#endif
posted @   OLK~BW  阅读(275)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示