Loading

c++0.1-----基于对象知识大综合(非指针篇)

  本文包含知识点有:预编译,访问权限,常成员函数,内联函数,构造函数,运算符重载函数,友元。

以代码为示范:

  文件名:ccompex.h  

  文件内容:定义一个简单的复数类。

 1 #ifndef __CCOMPLEX__
 2 #define __CCOMPLEX__
 3 #include <iostream>
 4 #include <ostream>
 5 using namespace std;
 6 class complex{
 7 friend complex& __doapl(complex& com1,const complex& com2);
 8 public:
 9     inline const double& real() const {return re;}
10     inline const double& image() const {return im;}
11     inline complex(double r=0.0,double i=0.0):re(r),im(i){}
12 private:
13     double re;
14     double im;
15 };
16 
17 inline complex & __doapl(complex &com1,const complex& com2)
18 {
19     com1.re+=com2.re;
20     com1.im+=com2.im;
21     return com1;
22 }
23 
24 inline complex & operator+=(complex &com1,const complex& com2)
25 {
26     return __doapl(com1,com2);
27 }
28 
29 inline complex operator+(const complex& com1,const complex& com2)
30 {
31     return complex(com1.real()+com2.real(),com1.image()+com2.image());
32 }
33 inline complex operator+(const complex& com1,const double& dou)
34 {
35     return complex(dou+com1.real(),com1.image());
36 }
37 inline complex operator+(const double& dou,const complex& com1)
38 {
39     return complex(dou+com1.real(),com1.image());
40 }
41 
42  ostream& operator<<(ostream &os,const complex& x)
43 {
44     os<<'('<<x.real()<<','<<x.image()<<')'<<endl;
45 }
46 
47 #endif // __CCOMPLEX__

 

 

这47行代码包含了几个c++精髓,下面让我娓娓道来~~~

一.预编译:#ifndef---#define---#endif 与#include

#ifndef---#define---#endif 代码在第1,2,47行,功能是避免头文件重复调用,在编译的时候合理将代码替换过来。

头铁不信系列:去掉#ifndef---#define---#endif 后,再多次在主文件包含这个头文件,进行编译,编译器给出以下错误信息:

error: redefinition of 'class complex'

错误解析:以上错误信息的意思是说对complex类重定义了。也就是说包含多次该文件,若没有预编译的功能,错误就会出现。

 

#include代码在第3,4行,表示引入一些头文件。

使用方法:系统头文件用<>包住,同时,不需要加.h后缀哦。例如:#include<iostream>

        自定义头文件用" "包住。例如:#include "ccomplex.h"。

 

二.访问权限:

 数据成员尽量都设置为私有变量(private),这样它就不会被外界访问。

成员函数方面,若是提供给用户访问的接口,则必须设置为public。若只是供这些内部调用,则设置为private比较好。

 

三.常成员函数:

常成员函数代码在第9,10行,其形式是在函数体大括号前面加上一个const限定符。其功能为强制命令该成员函数real()和image()不能够改变数据成员的值。若是头铁不信,只能bug伺候。

例如:我在const函数里修改数据成员,如下:

inline const double& real() const { re=1; return re;}

编译器提示错误信息:

error: assignment of member 'complex::re' in read-only object

意思是:给complex类型的只读对象的数据成员re赋值了,产生了错误。

由此可见,系统将const限定的成员变量限定成为只读的了,不能进行修改。那么这也就产生了一个问题,返回值得形式该怎么确定。

如果我返回的double的引用会怎样呢?例如:

inline double& real() const {return re;}

好吧,依然会报错,看看编译器暗示了什么?

error: binding 'const double' to reference of type 'double&' discards qualifiers

意思是:将const double 类型的数据 强行绑定到了 double 变量的引用上去,因为丢弃了限定符const而出错。

由此可见,不能将const变量赋值给非const变量。

本错误的修改方法1:将返回值类型改为const double&

inline const double& real() const { return re;}

修改方法2:将返回值类型修改为double (非引用)。

inline double real() const { return re;}

那么问题有来了,为什么能够返回引用和非引用两种方法?这就回到了返回引用和非引用的区别上来了:

首先,这两种修改方法存在的前提是,成员函数为const变量。也就是说返回的成员变量是只读类型,即常量。

那么,对于修改方法1。若返回引用,相当于直接可以在其他地方修改这个只读成员。显然出错,因此必须加上const

对于方法2,因为只返回double类型,相当于复制了一份re的值返回了回去,这样对于只读成员是没有影响的。

由此可见,两种方法都行得通。

 

有时候不得不将成员函数设置为const,例如:

const complex c1(2,1); 
c1.real();

它表示c1对象为常数的complex类型。

若它调用非常成员函数:

inline double real()  { return re;}

则会报错:

error: passing 'const complex' as 'this' argument discards qualifiers

它的意思是将const complex类型的变量传递给this,实参丢失了const修饰符。这样可能造成修改常对象c1的隐患,因此const对象必须调用常成员函数。

 

另外,只有成员函数才能被const修饰。所有在类外面定义的函数,都不能被const修饰。

头铁不信系列:

complex operator+(const complex& com1,const complex& com2) const
{
    return complex(com1.real()+com2.real(),com1.image()+com2.image());
}

注意:此时我将类外的+重载函数一const修饰,立马便已出现错误:

error: non-member function 'void fun(const complex&, const complex&)' cannot have cv-qualifier

其中cv指的是const和vilatile关键字简写,cv-qualifier指的是const和vilatile限定符。

错误信息的意思是:非成员函数 'void fun(const complex&, const complex&)' 不能有const和vilatile限定符。

 

 最后,附上传引用和传值(pass by value vs. pass by reference(to const))的区别:

  传值:将值全部传过去,值多大字节,那么就传多少字节。

  传引用:引用相当于指针。速度很快。若希望引用传过去后不希望它改,那么可以改成const

返回值传递和引用传递:(return by value vs. Return by reference)

  若在函数内创建对象,则因为函数一结束,对象就被析构了,则不能够传引用。除了这种情况,都可以传引用。

  传送者无需知道接收者是否以reference形式接收。

  返回类型不能是void,因为使用者可能连续使用操作运算符。例如:c1+=c2+=c3

当然要注意:引用&跟在类型后面,取地址放在变量前面。

 

建议:尽量使用引用传递。

 

四.内联函数:

出现inline关键字的函数都是内联函数。内联函数是c++特色之一。目的是提高程序运行的速度,当然也有缺点。

内联函数的用法是将inline关键字放在函数返回类型的最前端,例如:

inline complex operator+(const complex& com1,const complex& com2)
{
    return complex(com1.real()+com2.real(),com1.image()+com2.image());
}

内联函数的具体实现方法就是:在内联函数的调用处,在编译器编译期间,将内联函数的代码恰当地替换到该出。这样做的好处是,避免了程序运行时调用函数进行压栈,出栈等等繁琐的事情,省出了很多时间。但是相对的,缺点就是,每次调用处的一行代码变成了多行函数的实现代码,这样显然增加了源程序的代码量,使源程序占用的内存空间更大。

使用内联函数的准则:

  内联函数里面不能出现循环语句和switch-case开关语句。

  在类里面定义并实现的成员函数默认是内联函数。

  在类里面声明,在类外面实现,只要出现了inline,就是内联函数

  在类里面声明,在类外面实现,若没有出现一个inline,就不是内联函数。

  inline关键字只是表示对编译器的建议。若不能够内联而强制写上inline,编译器并不会报错,只是它会忽略inline这个关键字而已。

 

五.构造函数:

构造函数是在对象被创建后,用来给数据成员初始化用的成员函数。其形式为:

complex(double r=0.0,double i=0.0):re(r),im(i){}

构造函数应该注意的几点:

  1.在对象创建后调用。

  2.构造函数名和类名相同。

  3.可以拥有参数。默认参数既可以在类中声明,也可以在定义处声明,但不能两处都声明~~~

  4.参数可以有默认值。

  5.没有返回值。

强烈建议用上面初始化的形式,若是直接在括号里面赋值:

complex(double r=0.0,double i=0.0)
{
    re=r;
    im=i;
}

这样也行,但是缺点是放弃初始化,去进行赋值,这样效率会比较低。

简单成员函数可以在类里直接定义,复杂一点的成员在在类里面声明,在类外实现。

 在调用构造函数是,若不传实参,则只需要写:

 

string s1;//无参数的默认构造函数。

 

写成以下形式就是错的:

string s1();//它是对函数声明的格式,不是调用默认构造函数哦!!!

 

 

六.运算符重载函数:

起源:我们希望自定义的复数类对象能够通过+=,+,<<等等这些运算符直接进行赋值,加法或者输出等等,那么用运算符成员函数吧。在任何出现运算符的地方,它都可以直接被调用。其形式为:

inline complex operator+(const double& dou,const complex& com1)
{
    return complex(dou+com1.real(),com1.image());
}

 ostream& operator<<(ostream &os,const complex& x)
{
    os<<'('<<x.real()<<','<<x.image()<<')'<<endl;
}

当执行类似以下的语句时,运算符重载函数就会被自动调用:

complex c1(2,1);
complex c2(4);
c2+=c1;

这时调用的时+=运算符重载函数。+=会作用于c2对象。也就是说c2会调用 += 操作符重载函数,c2会传给this指针,c1会传给形参。注意,os不能设置为const,因为os流对象会在每次给它传值的时候它的状态会改变。另外,运算符重载函数放在类里面定义或在类外面定义都可以,主要看哪种行得通。在类外面定义更为通用,因为若是在类里面定义运算符重载函数,那么最左边的操作数一定要是complex类对象,这样显然局限性太大。在类里面定义重载函数,要省略最左边的complex形参。然而,在类外面定义的重载函数,不能省略调用重载函数的形参。

 

关于重载函数,可以总结一些规律出来:

 

  c++允许出现多个函数名相同的函数。但是要求参数的个数或者参数的类型不同。这种出现多个同名函数的情况就是函数的重载。满足以上条件,经过编译器编译之后,这些同名函数实际上名字就不一样了。

注意:构造函数重载时,若出现了默认值,则有可能报错哦~,例如:

complex(double r=0,double i=0):re(r),im(i){}
complex(){re=0;im=0;}

这两种构造函数的重载,若出现以下创建对象的形式:

complex com;

就会报错:

error: call of overloaded 'complex()' is ambiguous

note: candidate: complex::complex(double, double)

     complex(double r = 0, double i = 0) :re(r), im(i) {}

也就是说,这两个构造函数是一样的,系统不知道该选择哪个构造函数进行初始化。其实将第二个构造函数删掉就解决问题了。第一个构造函数更通用一点。

 

七.友元:

在成员函数最前面加上friend 的函数就是友元。例如:

friend complex& __doapl(complex& com1,const complex& com2);

它的功能是,complex声明,complex类外面有一个函数叫__dopal 。__doapl这个函数是我的朋友,这个函数里面的complex对象可以访问它的私有变量。也就是是说,友元破坏了对象的封装性,强行允许类外的对象直接访问并修改其私有变量。

 

posted @ 2018-07-01 21:10  FishLight  阅读(687)  评论(0编辑  收藏  举报