目录
重载
函数重载
作用:能使用多个同名的函数,即允许函数有多种形式。
重载的关键是函数的形参列表—也称为函数特征标(function signature)。如果两个函数的参数数目,参数类型,参数的排列顺序都相同,则他们的特征标相同。C++允许定义同名的函数前提是他们的特征标不同,只要上述特征标有一个不同则函数可重名。
例如下面的都是可重载的同名函数:
void print(const char* str, int width);
void print(char* str, int width);
void print(double length, int width);
void print(int i, int width);
void print(unsigned int i, int width);
void print(const char* str);
编译器将根据所采取的用法即函数实参的类型,数目,顺序来匹配相应的函数。使用被重载函数时必须使用目标函数对应的特征标,保证三个相同。注意返回值不是特征标!!
注意:如果没有完全匹配的函数,就可能会有类型强制转换进行匹配。
类型本身和类型引用对编译器来说是相同的,所以不可以这样重载
例如:
double foo(double x);
double foo(double& x);
匹配函数是会区分const和非const,但是非const会强制转换为const进行匹配,c++不允许const转化为非const,所以不匹配。换句话说就是将非const值赋给const变量是合法的,反之是不合法的。
例如:
void foo1(char* bits);
void foo1(const char* bits);
void foo2(char* bits);
void foo3(const char* bits);
//下面为调用
const char str1[5]="Over"; //const
char str2[5]="Load"; //非const
foo1(str1); // foo1(const char* bits);
foo1(str2); //foo1(char* bits);
foo2(str1); //不合法
foo3(str2); //foo3(const char* bits);
重载引用参数:
double foo(double& x1);
double foo(const double& x2);
double foo(double&& x3);
- 左值引用参数 x1 与可修改的左值参数匹配。
- const左值引用参数 x2 与可修改的左值引用参数、const左值参数和右值参数匹配。
- 右值引用参数 x3 与右值匹配。
double a=1.1;
const double b=2.2;
foo(a); //foo(double& x1);
foo(b); //foo(const double& x2);
foo(a+b); //foo(double&& x3);
何时使用函数重载:
当两个函数的参数列表只有数目不同时,可以考虑使用默认形参,这样可以减少程序所需要的内存。而当参数类型不同时只能使用重载了。
C++如何实现重载:
编译器采用名称修饰(name decoration)或叫名称矫正(name mangling)来区分不同的重载函数,可以这么理解:函数 int foo( int a, double b) 在编译时会被改名成int foo_int_double(int a,double b),于是便区分出了不同的重载函数。编译器再调用时会把main函数里的重载函数根据参数列表改名成相应的重载函数,然后才调用。
运算符重载
将函数重载的思想运用到运算符上,就是运算符的重载,这使得一个运算符根据参数的不同而具有不同的意义。比如一只狗加一只狗等于两只狗,那一只狗加一只猫会等于什么呢,这就由运算符重载来定义啦。
重载格式:
返回值 类名::operator 待重载的运算符 ( 运算符的右操作数 )
这时左操作数数为该类对象本身,通过this指针隐式传递(因为是类成员函数),右操作数如果没有可以不写(当重载的是一元运算符时)。
比如定义如下 Object 类,在其中重载(+)和(=),具体实现省略。这样便实现了让两个 Object相加 以及 将一个 Object 赋值给另一个 Object 的操作。
class Object{
private:
int width;
int length;
public:
Object();
Object operator=(Object& temp);
Object operator+(Object& temp);
};
int main(){
Object obj1,obj2,obj3;
obj1=obj2;
obj3=obj1+obj2;
}
1.重载的运算符不一定必须是成员函数,但必须有一个操作数是用户自己定义的类型,这样可以防止对标准类型进行重载。比如,不能把(-)重载为计算两int之和
2.重载不能改变运算符原来的句法规则,比如求模运算符(%),不能重载为只有一个操作数的运算符。
3.不改变原来运算符的优先级,重载的(+)与原来的(+)有相同的优先级
4.不创建新的运算符,比如不能定义一个operator**()来求幂(c++没有求幂运算符哦)
不能被重载的运算符:
sizeof | sizeof 运算符 |
. | 成员运算符 |
.* | 成员指针运算符 |
:: | 作用域解析运算符 |
?: | 条件运算符 |
typeid | 一个RTTI运算符 |
const_cast | 强制类型转换运算符 |
dynamic_cast | 强制类型转换运算符 |
reinterpret_cast | 强制类型转换运算符 |
static_cast | 强制类型转换运算符 |
可重载的运算符:
+ | - | * | / | % | ^ |
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | -- | , | ->* | -> |
() | [ ] | new | delete | new[ ] | delete[ ] |
自增运算符与自减运算符:
这两个运算符有两种模式,前缀和后缀,在重载时也要注意区分
返回值 类名::operator ++( ){ } 前缀模式
返回值 类名::operator ++( int ){ } 后缀模式
这里的 int 是一个虚拟形参在函数实体中没有什么实际作用,只是一个约定告诉编译器这是再后缀模式下重载。
#include <iostream>
using namespace std;
class integer{
private:
int x;
public:
integer(int xx=0):x(xx){}
integer& operator ++(){
cout<<"前缀重载++"<<' ';
++x;
return *this;
}
integer operator ++( int ){
cout<<"后缀重载++"<<' ';
return integer(x++);
}
void print(){
cout<<"x的值是:"<<x<<endl;
}
};
int main(){
integer a(1);
a.print();
integer b=a++;
b.print();
integer c=++a;
c.print();
}
上面并没有重载赋值运算符,因为会有一个默认的赋值运算符,仅仅是把所有成员变量的值复制过去,一旦涉及到指针又可能会出现深拷贝与浅拷贝的问题。
根据前面我们可以重载运算符使得运算符可以进行我们定义的 比如,Object+Object,Object+2等等的运算,但是我要计算2*Object呢,这显然用之前的方法是行不通的,因为要求左操作数必须是用户自己定义的类。那我在类外定义一个重载函数不就行了?但这产生了一个新问题,类外函数无法访问类内私有成员。
友元——完美解决上述问题
把类外的函数声明为友元,使得它可以访问类内的私有成员。只需要在函数前加 friend 关键词就可以了,注意是在类内声明的时候加,类外定义的时候不加。
使用了friend后,就告诉类说这个函数是我们的好朋友,不用藏着掖着让我康康!
友元机制是否违背了OOP数据隐藏的原则?
这种观点未免太片面……类声明仍然控制了哪些函数可以访问私有数据……类方法和友元只是表达接口的两种不同的机制。——《C++ Primer Plus》
常用的友元:重载<<,>>
Ostream,istream 返回类型为对应引用,记得return 相应对象
例如:
class Object{
private:
int width;
int length;
public:
Object();
Object operator=(Object& temp);
Object operator+(Object& temp);
friend ostream& operator<<(ostream& os, const Object temp);
friend istream& operator>>(istream& is, Object temp);
};
ostream& Object::operator<<(ostream& os, const Object temp){
os << temp.width << temp.length << std::endl;
return os;
}
istream& Object::operator>>(istream& is, Object temp){
is >> temp.width >> temp.length ;
return is;
}
隐藏,屏蔽overwrite
屏蔽基类的函数定义
• 派生类的函数与基类的函数同名,但是参数列表有所差异。
• 派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字
定义如下类,以及类内相同名称的函数 void say(); 打印对应类名。
#include <iostream>
class Fruit{
public:
void say() {
printf("I'm a fruit!\n");
}
};
class Apple : public Fruit{
public:
void say() {
printf("I'm an apple!\n");
}
};
class BlueApple : public Apple{
public:
void say(){
printf("I'm a blueapple!\n");
}
};
int main(){
Fruit a;
Apple b;
BlueApple c;
a.say();
b.say();
c.say();
}
上述程序运行结果为
当调用 c.(); 时,编译器会沿着BlueApple的继承树向上寻找第一个 say() 来调用,假如此时我们不在BlueApple类中实现 say() 函数,那么执行c.say()时就会执行Apple类中的 say() 函数。
运行结果变为:
当我们使用指针来访问 say() 时,会调用指针对应的类成员函数
当使用基类指针指向派生类时,如下面的base指针,调用say()会调用基类中的say()函数。
int main(){
Fruit a;
Apple b;
BlueApple c;
Fruit* aa=&a;
Apple* bb=&b;
BlueApple* cc=&c;
Fruit* base=&b;
aa->say();
bb->say();
cc->say();
base->say();
}
int main(){
Fruit a;
Apple b;
BlueApple c;
Fruit& aa=a;
Apple& bb=b;
BlueApple& cc=c;
Fruit& base=b;
aa.say();
bb.say();
cc.say();
base.say();
}
使用引用来调用函数时情况与使用指针时相同,会调用对应类的say()函数。
重写,覆盖override
修改基类函数定义
• 基类或非直接基类,至少有一个成员函数被 virtual 修饰
• 派生类虚函数必须与基类虚函数有同样签名(或特征标),即函数名,参数类型,顺序和数量都必须相同。
一旦函数被virtual修饰,在其今后的派生类中始终为虚函数。
函数被virtual修饰后,只会保留最后一个同特征标函数,不管用指针还是引用都将无法访问到被覆盖的函数,如下代码将全部调用最后 BlueApple 类中的 say() 函数。
#include <iostream>
class Fruit{
public:
virtual void say() {
printf("I'm a fruit!\n");
}
};
class Apple : public Fruit{
public:
void say() {
printf("I'm an apple!\n");
}
};
class BlueApple : public Apple{
public:
void say(){
printf("I'm a blueapple!\n");
}
};
int main(){
Fruit a;
Apple b;
BlueApple c;
Fruit* baseptr = &c;
Fruit& base=c;
c.say();
baseptr->say();
base.say();
}
当然如果一定要访问基类中的函数也是可以做到的,使用作用域解析运算符(::)来选择想要调用的类中的函数即可。
int main(){
Fruit a;
Apple b;
BlueApple c;
Fruit* baseptr = &c;
Fruit& base=c;
c.Fruit::say();
baseptr->Fruit::say();
base.Fruit::say();
}
但是这个时候又要注意一个问题:作用域的类 必须是 对象、其指针或其引用的基类,如果不是就会报错,如下:
int main(){
Fruit a;
Apple b;
BlueApple c;
Fruit* baseptr = &c;
Fruit& base=c;
a.Apple::say();
baseptr->Apple::say();
base.Apple::say();
}