这一次讲类的强化,上一节都是类的一些基本操作。
4.1 this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
this指针是隐含参数,也就是每一个类都有,还是看例子吧:
看看图片的例子,左边的是我们目前写的,是不是看招毫无违和感,右边的是我们在c语言中,写面向对象时候的用法,把自己的对象作为参数传入,函数就可以根据对象的指针去访问对象的数据。
左右两边是什么关系呢?其实右边这个代码就是左边的代码被编译器翻译后的样子,也就是和编译器给我们包装了一个语法糖,底层实现其实都是c语言,只不过c++编译器在c语言的基础上再做封装。
this指针其实是一个常指针,Test *const this,也就是this指向的内容可以被修改,但是this自己不可以被修改。
如果我们写一个get方法,不希望this指针指向的内容被修改,那怎么办?
办法是有的,这是一个c++的坑,我们要在函数的后面加一个const的,例:
int getK() const { //成员函数尾部出现const 修饰的是this指针
}
//这时候的this指针变成了 const Test *const this
静态函数没有this指针,静态函数不能使用this指针
this使用方法
class Haha
{
public:
Haha(int a, int b)
{
this->a = a;
this->b = b;
}
private:
int a;
int b;
};
this是这个对象的指针tihs->a是这个对象中的a, 后面的a是传参进来的a
4.2 返回对象本身
要返回对象本身,就需要:
Haha *add(Haha * &an)
{
this->a += an->a;
return this;
}
Haha *h = new Haha(1, 20);
Haha *h2 = new Haha(1, 20);
h->add(h2)->add(h2);
先来一个指针版的,这是c语言我们以前都是这么玩的。
Haha &add(Haha * &an)
{
this->a += an->a;
return *this;
}
Haha *h = new Haha(1, 20);
Haha *h2 = new Haha(1, 20);
h->add(h2).add(h2);
返回对象的本身,this== &对象 所以需要取值才能获取到对象。
Haha add(Haha * &an)
{
this->a += an->a;
return *this;
}
返回一个临时变量,而不是对象的本身,所以用这个返回值操作是无意义的。
*return this返回的是当前对象的克隆或者本身(若返回类型为A, 则是克隆, 若返回类型为A&, 则是本身 )。return this返回当前对象的地址(指向当前对象的指针)
如果相对一个对象连续调用成员方法,每次都会改变对象本身,成员方法需要返回引用。
4.3 友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
class Haha
{
public:
Haha(int a, int b)
{
this->a = a;
this->b = b;
}
Haha &add(Haha * &an)
{
this->a += an->a;
return *this;
}
friend void printf_haha(Haha &ha); //在这个类中声明友元函数
private:
int a;
int b;
};
//友元函数的定义
void printf_haha(Haha &ha){
printf("ha %d\n", ha.a);
}
上面的例子表明,友元函数是在类的外面定义的,通过类外部的函数访问类的私有成员和保护成员。这样可以提高效率,而不用去调用get set方法,然后进栈出栈的,缺点就是打破了类的封装性,并且这个友元函数只有c++有,所以还是要慎用。
另外一个类中的成员函数也可以做为友元函数,写的方式跟上面一样,但是编译的可能会出现找不到定义,(可以利用多文件写,或者声明和函数体分离的方式通过编译),但是不要轻易的引用另一个类中的成员函数。
4.4 友元类
类A是类B的友元类,则A就可以访问B的所有成员(成员函数,数据成员)。(类A,类B无继承关系)
class Haha
{
public:
Haha(int a, int b)
{
this->a = a;
this->b = b;
}
Haha &add(Haha * &an)
{
this->a += an->a;
return *this;
}
//friend void printf_haha(Haha &ha); //在这个类中声明友元函数
friend class Haha1; //在这个类中声明友元类
private:
int a;
int b;
};
//这个类就可以访问Haha的所有私有成员和私有函数
class Haha1
{
public:
void printf_haha(Haha &ha){
printf("ha %d\n", ha.a);
}
};
注意事项:
(1)友元关系不能被继承。
(2)友元关系是单向的,不具有交换性。类B是类A的友元,但是类A不一定是类B的友元,要看类A有没有类B的友元声明
(3)友元关系不具有传递性。类B是类A的友元,类C是类B的友元,但是类C不一定是类A的友元。
4.5 操作符重载
您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Haha operator+(Haha &another);
上面这个是类函数中,操作符重载,因为类中是有this指针,所以只要传一个参数进来。
也可以定义为非成员函数:
Haha operator+(Haha &another1, Haha &another2);
这个就是全局函数,需要传两个参数,分别是两个对象。
下面是具体例子:
class Haha
{
public:
Haha(int a, int b)
{
this->a = a;
this->b = b;
}
Haha &add(Haha * &an)
{
this->a += an->a;
return *this;
}
Haha operator+(Haha &another)
{
printf("操作符重载\n");
Haha temp(this->a + another.a, this->b + another.b);
return temp;
}
//friend void printf_haha(Haha &ha); //在这个类中声明友元函数
friend class Haha1;
int getA() const
{
return a;
}
private:
int a;
int b;
};
int main(int argc, char **argv)
{
cout << "hello c++ " << my_spaceA::my_spaceB::haha << endl;
Haha *h = new Haha(1, 20);
Haha *h2 = new Haha(1, 20);
h->add(h2).add(h2).add(h2);
//printf_haha(*h);
Haha h3 = *h + *h2; // 等价于 *h.operator+(*h2)
printf("h3 = %d\n", h3.getA());
return 0;
}
c++重载规则:
(1)C++不允许用户自己定义新的运算符,只能对已有的c++运算符进行重载。
(2)c++运算重载的运算符:
可重载运算符
下面是可重载的运算符列表 | |
---|---|
双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),–(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
不可重载运算符
下面是不可重载的运算符列表 | |
---|---|
. | 成员访问运算符 |
.*, ->* | 成员指针访问运算符 |
:: | 域运算符 |
sizeof | 长度运算符 |
?: | 条件运算符 |
# | 预处理符号 |
参考链接添加链接描述
(3)重载不能改变运算符运算的对象(即操作数个数)
“>”和“<”是双目运算符,就不能被重载为单目
(4)重载不能改变运算符的优先级别。
(5)重载不能改变运算符的结合性。
如:复制运算符“=”是右结合性(自右自左),重载后仍为右结合性。
(6)重载运算符的函数不能有默认参数。
(7)重载运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类和对象(或类对象的引用).
int operator+(int a, int b){
return (a-b);
}
这种就是跟c++的语法有冲突,要禁止的。
(8)用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和运算符“&”不必用户重载。
但是类中如果有指针变量,需要深拷贝,还是要重载“=”运算符。
(9)应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。
如:如果是+法,重载使用一般都要+。
(10)运算符重载函数可以是类的成员函数,也可以是类的友元函数,也可以是普通函数。
4.6 操作符重载例子
c++的语法的坑还真不少
4.6.1 单目运算符++重载例子:
//c++中的 ++a是可以连加的所以返回的是this的引用,并且是修改自己类的值
Haha& operator++()
{
printf("++\n");
this->a++;
return *this;
}
上面是++a的,a++又不一样
//后++的时候,参数是要填占位参数 int 表示这个是后++
//后++不能连加,所以用const修饰,不是很理解为什么要申请一个临时变量?
const Haha operator++(int)
{
printf("后++\n");
Haha temp(this->a, this->b);
this->a++;
this->b++;
return temp;
}
这些函数也可以写成全局函数的, 这里就不写了,因为感觉类的成员会使用多一点。
4.6.2 左右移操作符重载:
//右移操作符重载
istream& operator>>(istream& is, Haha& h)
{
cout << "a= ";
is >> h.a;
return is;
}
//左移操作符重载,就想是打印类的变量一样。
ostream& operator<<(ostream& os, Haha& h)
{
os << "a = " << h.a << endl;
return os;
}
//使用方法:
cout << h3 << h3;
cin >> h3;
printf("h3 = %d\n", h3.getA());
<< >>操作符只能写全局函数,不能写成类中的函数
4.6.3 等号操作符重载:
等号操作符原来不是以前写的那么简单
要有几个步骤:
- 防止自身赋值。
- 先将自身的额外开辟空间回收掉。
- 执行深拷贝。
代码就不写了,比较懒。
4.6.4 小括号操作符重载
c++中的小括号也可以重载,想想都觉得c++是不是给程序员的权限太大了。
都可以随便瞎搞了。
//重载小括号,无参数
int operator()()
{
return a * a;
}
//重载小括号,有参数
int operator()(int a)
{
return this->a * a;
}
//使用
int value = h3();
printf("value = %d\n", value);
value = h3(10);
printf("value = %d\n", value);
将一个对象当成一个普通函数来调用,称这种对象是仿函数或伪函数,伪函数是需要重载小括号才能使用。仿函数也可以函数重载。
4.6.5 不建议重载&& ||
因为标准的&& ||有短路现象,就是第一个不满足的了,就不会执行第二个了,但是重载之后的运算符就不满足这种现象了,所以不适合重载。