> 文章列表 > c++学习(day4)

c++学习(day4)

c++学习(day4)

文章目录

  • 一. 友元(friend)
    • 1 友元函数
      • 1.1 全局函数作为友元函数
      • 1.2 类的成员函数作为友元函数(了解)
    • 2. 友元类
    • 3. 使用友元的注意事项
  • 二. 常成员函数和常对象(const)
    • 1. 常成员函数
    • 2. 常对象
    • 3. mutable关键字
  • 三. 运算符重载
    • 1. 定义
    • 2. 重载的方法
    • 3. 运算符重载要求
    • 4. 调用时机及调用原则
    • 5. 运算符重载的格式
      • 5.1 算术类运算符重载(双目运算符)
      • 5.2 赋值类运算符重载
      • 5.3 关系运算符重载
      • 5.4 单目运算符重载
      • 5.4 单目运算符重载
      • 5.5 自增、自减运算符重载
      • 5.6 插入、提取运算符重载
      • 5.6 不能重载的运算符
      • 5.7 运算符重载注意事项
      • 5.6 不能重载的运算符
      • 5.7 运算符重载注意事项

一. 友元(friend)

友元是一种定义在类外部的普通函数或类

1 友元函数

1.1 全局函数作为友元函数

声明一个全局函数作为类的友元函数,则允许该全局函数,访问类中各个权限下的成员

在类中要将该函数进行声明:friend 全局函数头;

#include <iostream>
using namespace std;class Stu
{
private:string name;int age;
public:Stu() {}Stu(string n, int a) : name(n), age(a) {}void show(){cout << "name = " << name << "   age = " << age << endl;}friend void display(Stu s); // 在类中将全局函数声明成类的友元函数
};// 定义全局函数
void display(Stu s)
{cout << "name = " << s.name << "   age = " << s.age << endl; // 正常的全局函数不能访问类中私有属性
}int main()
{display(Stu("zhangpp", 18));return 0;
}

1.2 类的成员函数作为友元函数(了解)

  1. 声明一个其他类的成员函数作为自己类的友元函数,则允许让该函数访问自己类中的所有成员

  2. 要求该函数必须类内声明类外定义

2. 友元类

  1. 在一个类中声明另一个类当做友元类,则允许友元类中所有成员访问自己的所有权限下的成员

  2. 声明格式:friend class 类名;

#include <iostream>using namespace std;class Stu; // 类的前置声明class Teacher // 老师类
{
private:string sub;public:Teacher() {}Teacher(string s) : sub(s) {}void show(Stu s); // 类内声明
};class Stu // 学生类
{
private:string name;int age;public:Stu() {}Stu(string n, int a) : name(n), age(a) {}void show(){cout << "name = " << name << "   age = " << age << endl;}friend void display(Stu s); // 在类中将全局函数声明成类的友元函数// friend void Teacher::show(Stu s);             //声明老师类中的成员函数作为友元函数// 将整个Teacher类当做友元类friend class Teacher;
};// 定义全局函数
void display(Stu s)
{cout << "name = " << s.name << "   age = " << s.age << endl; // 正常的全局函数不能访问类中私有属性
}// 老师类中的成员函数的定义
void Teacher::show(Stu s)
{cout << "Teacher::sub = " << sub << endl;cout << "Stu::name = " << s.name << endl; // 其他类不能访问类中私有成员
}int main()
{display(Stu("zhangpp", 18));Teacher t1("C++");t1.show(Stu("zhangpp", 18));return 0;
}

3. 使用友元的注意事项

  1. 友元具有方向性,A把B当做朋友,允许B访问A的所有权限,但是A不一定能访问B的所有权限

  2. 友元不具有交换性:A是B的朋友,但B不一定是A的朋友

  3. 友元不具有传递性:A是B的朋友,B是C的朋友,则A不一定是C的朋友

  4. 友元不具有继承性:父类的朋友不一定是子类的朋友

  5. 由于友元的出现,破坏了了类的封装性,使得访问权限形同虚设。所以不在万不得已的情况下,尽可能少用友元

  6. 必须使用友元的情况:插入和提取运算符重载。(后期讲)

二. 常成员函数和常对象(const)

1. 常成员函数

由const修饰的成员函数叫常成员函数

格式:

  • 返回类型 成员函数名(参数表) const;

    例如:int function(int x) const

特点:

  1. 在常成员函数中,不能修改成员变量的值(保护成员变量不被修改)

  2. 类中同名的常成员函数和非常成员函数构成重载关系,原因是隐藏的this指针的类型不同

    非常成员函数中: Stu * const  this
    常成员函数中:	  const Stu * const this
    
  3. 非常对象,优先调用非常成员函数,如果没有非常成员函数,则去调用同名的常成员函数

  4. 常对象只能调用常成员函数,没有常成员函数会报错

2. 常对象

const修饰的对象为常对象

  1. 常对象只能调用常成员函数,不能调用非常成员函数

  2. 当一个常引用的目标为非常对象,则通过引用只能调用常成员函数,通过对象优先调用非常成员函数

3. mutable关键字

功能:

取消成员常属性

使用方式:

​ 定义变量前加mutable,那么,该变量就能在常成员函数中被修改

#include <iostream>using namespace std;class Stu
{
private:string name;mutable int age; // 由mutable关键字修饰的成员变量,能在常成员函数中被修改double score;public:Stu() {}Stu(string n, int a, double s) : name(n), age(a), score(s) {}// 有const修饰的成员函数就是常成员函数void show() const // const Stu * const this;{// this = nullptr;// this->score = 50; // 在常成员函数中不能修改成员变量的值this->age = 100; // 可以更改,因为有关键字修饰cout << "name = " << name << endl;cout << "age = " << age << endl;cout << "score = " << score << endl;cout << "AAAAAAAAAAAAAAAAAAAAAA" << endl;}void show() // Stu * const this;{this->score = 50; // 在常成员函数中不能修改成员变量的值cout << "name = " << name << endl;cout << "age = " << age << endl;cout << "score = " << score << endl;cout << "BBBBBBBBBBBBBBBBBBBBBBBB" << endl;}
};int main()
{Stu s1("张三", 20, 99);s1.show(); // 50const Stu &r = s1;r.show();  // 常成员函数s1.show(); // 优先调用非常成员函数return 0;
}

三. 运算符重载

单、算、关、逻、条、赋、逗

c++学习(day4)

1. 定义

所谓运算符重载,就是给运算符新的含义,能够实现“一符多用”,也是属于静态多态的一种,

他能够实现将原本加载到基本数据类型的运算符,在自定义类对象减使用。

好处:能够使得代码更加简洁、易懂,优雅好看

2. 重载的方法

统一的名称:

operator# //#表示运算符

3. 运算符重载要求

功能:

​ 实现运算符对应的操作

参数:

​ 由运算符本身决定

返回值:

​ 由用户自己决定

4. 调用时机及调用原则

调用时机:当使用该运算符时,系统自动调用,无需手动调用

调用原则:左调右参 //a = b; a.operator=(b)

5. 运算符重载的格式

每种运算符都有两个版本的格式:

  • 成员函数版本,类对象本身就是一个参数,形参个数是操作数个数-1

  • 全局函数版,需要使用友元完成,此时参数个数等于操作数个数

以上两个版本的重载函数,只能实现一个,否则调用时会混乱报错

5.1 算术类运算符重载(双目运算符)

种类:

​ +、-、*、/、%。。。

表达式:

​ L#R //L表示左操作数,#表示运算符,R表示右操作数

  • 左操作数:既可以是左值也可以是右值

  • 右操作数:既可以是左值也可以是右值

  • 结果:右值

定义格式:

  • 成员函数版:const 类名 operator#( const 类名 &R ) const

第一个const:保护返回结果不被改变

第二个const:保护右操作数不被修改

第三个const:保护左操作数不被修改

  • 全局函数版:const 类名 operator#(const 类名 &L, const 类名 &R)

5.2 赋值类运算符重载

种类:

​ =、-=、*=、/=、%=、+=。。。

表达式:

​ L#R //L表示左操作数,#表示运算符,R表示右操作数

  • 左操作数:只能是左值

  • 右操作数:既可以是左值也可以是右值

  • 结果:右值

定义格式:

  • 成员函数版: 类名 & operator#( const 类名 &R )

  • 全局函数版: 类名 & operator#(类名 &L, const 类名 &R)

5.3 关系运算符重载

种类:

​ >=、<=、!=、>、<、==。。。

表达式:

​ L#R //L表示左操作数,#表示运算符,R表示右操作数

  • 左操作数:既可以是左值也可以是右值

  • 右操作数:既可以是左值也可以是右值

  • 结果:bool类型的右值

定义格式:

  • 成员函数版: const bool operator#( const 类名 &R ) const

  • ==全局函数版: const bool operator#( const 类名 &L,const 类名 &R ) ==

5.4 单目运算符重载

种类:

​ &、!、-(负号)。。。

表达式:

​ #O //#表示运算符 O表示操作数

  • 操作数:既可以是左值也可以是右值

  • 结果:右值

定义格式:

  • 成员函数版: const 类名 operator#( void ) const

  • 全局函数版: const 类名 operator#( conts 类名 &O)

5.4 单目运算符重载

种类:

​ &、!、-(负号)。。。

表达式:

​ #O //#表示运算符 O表示操作数

  • 操作数:既可以是左值也可以是右值

  • 结果:右值

定义格式:

  • 成员函数版: const 类名 operator#( void ) const

  • 全局函数版: const 类名 operator#( conts 类名 &O)

5.5 自增、自减运算符重载

前置自增:++a

表达式:

​ #O //#表示运算符 O表示操作数

  • 操作数:只能是左值

  • 结果:左值,自身的引用

定义格式:

  • 成员函数版: 类名 & operator#( )

  • 全局函数版: 类名 & operator#( 类名 &O )

后置自增:a++

表达式:

​ O# //#表示运算符 O表示操作数

  • 操作数:只能是左值

  • 结果:右值

定义格式:

  • 成员函数版: 类名 operator#( int )

  • 全局函数版: 类名 operator#( 类名 &O , int)

5.6 插入、提取运算符重载

  1. cin和cout的来源
namespace std
{extern istream cin;         /// Linked to standard inputextern ostream cout;        /// Linked to standard output
}
  1. 从这两个类对象可以看出,cin和cout来自于内置对象,想要对<<和>>进行重载时,如果想要实现成员函数版,则需要多istream和ostream类进行修改,难度较大。

  2. 此时,我们可以使用全局函数版实现这两个运算符的重载,将该全局函数设置成友元函数即可

  3. 因为要用到istream和ostream的类对象和自定义类对象,原则上来说需要在两个类中都要设置成友元函数

  4. 但是,在运算符重载函数中,只会对自定义的类对象访问私有成员,而不会对istream和ostream的类访问私有成员

  5. 所以,最终只需要在自定义类中,将全局函数设置成友元函数即可

表达式:

​ L#R (L是cin或cout #是<<或者>> R是自定义的类对象)

  • 左操作数:istream和ostream的类对象

  • 右操作数:自定义的类对象

  • 结果:左操作数自身的引用

格式:

  • ostream &operator<<(ostream &L, const 类名 &O);

  • istream &operator>>(istream &L, const 类名 &O);

5.6 不能重载的运算符

  1. 成员运算符 .

  2. 成员指针运算符 .*

  3. 条件表达式 ?:

  4. 求字节运算 sizeof

  5. 作用域限定符 ::

5.7 运算符重载注意事项

  1. 运算符重载只能在已有的运算符基础上进行重载,不能凭空捏造一个运算符

  2. 运算符重载不能更改运算符的本质:如不能在加法运算符重载中实现减法

  3. 运算符重载不能改变运算符的优先级

  4. 运算符重载不能改变运算符的结合律

  5. 运算符重载函数不能设置默认参数

​ L#R (L是cin或cout #是<<或者>> R是自定义的类对象)

  • 左操作数:istream和ostream的类对象

  • 右操作数:自定义的类对象

  • 结果:左操作数自身的引用

格式:

  • ostream &operator<<(ostream &L, const 类名 &O);

  • istream &operator>>(istream &L, const 类名 &O);

5.6 不能重载的运算符

  1. 成员运算符 .

  2. 成员指针运算符 .*

  3. 条件表达式 ?:

  4. 求字节运算 sizeof

  5. 作用域限定符 ::

5.7 运算符重载注意事项

  1. 运算符重载只能在已有的运算符基础上进行重载,不能凭空捏造一个运算符

  2. 运算符重载不能更改运算符的本质:如不能在加法运算符重载中实现减法

  3. 运算符重载不能改变运算符的优先级

  4. 运算符重载不能改变运算符的结合律

  5. 运算符重载函数不能设置默认参数

  6. 成员函数版的参数要比全局函数版的参数少一个,原因是对象本身就是一个参数