本文包含知识点有:预编译,访问权限,常成员函数,内联函数,构造函数,运算符重载函数,友元。
以代码为示范:
文件名: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对象可以访问它的私有变量。也就是是说,友元破坏了对象的封装性,强行允许类外的对象直接访问并修改其私有变量。