首页 > 其他分享 >【cpluscplus教程翻译】Classes (II)

【cpluscplus教程翻译】Classes (II)

时间:2023-05-22 18:36:27浏览次数:35  
标签:const CVector int 成员 II Classes cpluscplus class 函数

操作符重载

本质上,类就是在C++代码里定义了新的类型,在代码中,类型不仅用来构造和赋值,还可以用操作符进行运算,考虑基础类型的加减乘除

int a, b, c;
a = b + c;

上面这个例子用了加法操作符和赋值操作符,对于基础类型,这些操作的含义非常显而易见且无歧义,但是对自定义类型来说,不见得是同一回事

struct myclass {
  string product;
  float price;
} a, b, c;
a = b + c;

显然,b和c的加法具体干什么不得而知,事实上,这段代码会编译报错,因为myclass没有定义加法的行为,然后C++允许重载大多数操作符,不光是类,基础类型也可以,下面是可以重载的操作符

使用operator函数就可以重载操作符,操作符函数和普通函数的形式差不多,只不过函数名需要用operator关键字开头,然后才是操作符,语法为type operator sign (parameters) { /*... body ...*/ }
考虑笛卡尔坐标系下的向量,两个向量相加可以认为是对应的x和y坐标相加,例如向量(3,1)和向量(1,2)相加的结果是(3+1, 1+2)=(4,3),对应的代码为

// overloading operators example
#include <iostream>
using namespace std;

class CVector {
  public:
    int x,y;
    CVector () {};
    CVector (int a,int b) : x(a), y(b) {}
    CVector operator + (const CVector&);
};

CVector CVector::operator+ (const CVector& param) {
  CVector temp;
  temp.x = x + param.x;
  temp.y = y + param.y;
  return temp;
}

int main () {
  CVector foo (3,1);
  CVector bar (1,2);
  CVector result;
  result = foo + bar;
  cout << result.x << ',' << result.y << '\n';
  return 0;
}

CVector出现了这么多次,容易让人困惑,有些是类名,有些函数返回类型,有些是构造函数名,我们解析一下

CVector (int, int) : x(a), y(b) {}  // function name CVector (constructor)
CVector operator+ (const CVector&); // function that returns a CVector 

加法操作符重载后,+的效果和显式调用本质上是一样的

c = a + b;
c = a.operator+ (b);

重载的运算符实际上可以有任何行为,不一定需要和数学含义一致,例如加法运算符可以实际上是减法,判断运算符实际上可以是填零,不过推荐和数学含义一致。
重载运算符函数的参数很自然地被认为是操作符的右边部分,对于二元运算符来说,这很常见(操作符左右各一个操作数)。不过运算符可以有各种各样的形式,下面是汇总表格(@替换成对应的操作符)

需要注意的是:操作符既可以作为成员函数也可以作为非成员函数进行重载,非成员函数形式重载需要注意参数类型及个数(本质上是命令空间发生了变化

// non-member operator overloads
#include <iostream>
using namespace std;

class CVector {
  public:
    int x,y;
    CVector () {}
    CVector (int a, int b) : x(a), y(b) {}
};


CVector operator+ (const CVector& lhs, const CVector& rhs) {
  CVector temp;
  temp.x = lhs.x + rhs.x;
  temp.y = lhs.y + rhs.y;
  return temp;
}

int main () {
  CVector foo (3,1);
  CVector bar (1,2);
  CVector result;
  result = foo + bar;
  cout << result.x << ',' << result.y << '\n';
  return 0;
}

this关键字

关键字this表示成员函数正在执行的哪个对象的指针,主要用在成员函数里表示当前对象(思考类对象模型,哪些放在对象里,哪些放在类里
用法之一是确定参数是不是对象本身(节省返回开销

// example on this
#include <iostream>
using namespace std;

class Dummy {
  public:
    bool isitme (Dummy& param);
};

bool Dummy::isitme (Dummy& param)
{
  if (&param == this) return true;
  else return false;
}

int main () {
  Dummy a;
  Dummy* b = &a;
  if ( b->isitme(a) )
    cout << "yes, &a is b\n";
  return 0;
}

也常用在赋值运算符中,因为赋值运算符返回的是对象的引用(实现链式运算a=b=c=d),继续研究笛卡尔向量,它的赋值运算符函数可以是

CVector& CVector::operator= (const CVector& param)
{
  x=param.x;
  y=param.y;
  return *this;
}

事实上,编译器隐式生成的代码就和上面差不多

静态成员(static members)

类也可以有静态成员,数据或函数
类的静态成员变量也叫做类变量,因为这个类的所有对象共用这一个变量
常见用法是,用类变量记录有多少个对象

// static members in classes
#include <iostream>
using namespace std;

class Dummy {
  public:
    static int n;
    Dummy () { n++; };
};

int Dummy::n=0;

int main () {
  Dummy a;
  Dummy b[5];
  cout << a.n << '\n';
  Dummy * c = new Dummy;
  cout << Dummy::n << '\n';
  delete c;
  return 0;
}

事实上,静态成员和非静态成员是一样的属性,只不过静态成员的作用域是类,因为这个原因,并且避免声明多次,他们不能再类里初始化,必须要在类外面int Dummy::n=0;
因为静态变量由所有对象共享,因此任何对象都可以访问这个变量,甚至只通过类名就行
cout << a.n;cout << Dummy::n;这两句话使用的是同样的变量
静态成员函数也是一样的:可以有所有对象访问,与非静态成员函数不同,静态成员函数不能访问非静态成员变量(显然,如果通过类名使用,根本没有对象,就没办法访问非静态成员变量,没有this指针

常成员函数(const member functions)

当一个对象是常量const MyClass myobject;使用它的成员变量必须确保只读,就好像它的所有成员变量都是常量,需要注意的是,构造函数被允许调用并修改这些成员变量(可以认为构造函数的函数体实际上是赋值运算符,初始化列表才是真正的构造过程

// constructor on const object
#include <iostream>
using namespace std;

class MyClass {
  public:
    int x;
    MyClass(int val) : x(val) {}
    int get() {return x;}
};

int main() {
  const MyClass foo(10);
// foo.x = 20;            // not valid: x cannot be modified
  cout << foo.x << '\n';  // ok: data member x can be read
  return 0;
}

常量对象的成员函数只有被指定成常成员函数才可以被调用(上面只保证不在外面被修改,如果是成员函数内被修改呢,这个是运行期过程,需要在编译器加约束才能避免),上面这个例子中,get函数因为没有用const修饰,因此foo对象不能调用get函数,声明一个函数为常成员函数的语法为int get() const {return x;}
需要注意的是,const也可以用来修饰返回值,不要被各种const混淆

int get() const {return x;}        // const member function
const int& get() {return x;}       // member function returning a const&
const int& get() const {return x;} // const member function returning a const&

常成员函数不能修改非静态成员变量也不能调用其他非常成员函数,本质上,常函数不能修改对象的状态
常对象只能调用常成员函数,非常对象没有这个限制。
你可能会认为你很少声明常对象,因此认为把所有不修改对象的成员函数标记为常成员函数没有意义,但是实际上常对象很常见,大多数参数为类的函数实际上接收的是常引用(避免开销),因此这些函数只能使用他们的常成员

// const objects
#include <iostream>
using namespace std;

class MyClass {
    int x;
  public:
    MyClass(int val) : x(val) {}
    const int& get() const {return x;}
};

void print (const MyClass& arg) {
  cout << arg.get() << '\n';
}

int main() {
  MyClass foo (10);
  print(foo);

  return 0;
}

同上分析,如果get不标记为常,那么print函数无法实现
成员函数也可以基于常量进行重载:例如两个函数参数相同,但是一个是常函数一个不是,那么常函数版本只能由常对象调用,非常对象可以调用非常版本

// overloading members on constness
#include <iostream>
using namespace std;

class MyClass {
    int x;
  public:
    MyClass(int val) : x(val) {}
    const int& get() const {return x;}
    int& get() {return x;}
};

int main() {
  MyClass foo (10);
  const MyClass bar (20);
  foo.get() = 15;         // ok: get() returns int&
// bar.get() = 25;        // not valid: get() returns const int&
  cout << foo.get() << '\n';
  cout << bar.get() << '\n';

  return 0;
}

类模板(class template)

就像函数模板一样,也可以新建类模板,如下

template <class T>
class mypair {
    T values [2];
  public:
    mypair (T first, T second)
    {
      values[0]=first; values[1]=second;
    }
};

我们定义的这个类可以存储两个相同类型的值,例如,如果想存两个整数mypair<int> myobject (115, 36);或者两个浮点数mypair<double> myfloats (3.0, 2.18);
构造函数是唯一的成员函数,因此在类里直接作为内联函数,如果在类模板外定义,需要加上templlate<>前缀

// class templates
#include <iostream>
using namespace std;

template <class T>
class mypair {
    T a, b;
  public:
    mypair (T first, T second)
      {a=first; b=second;}
    T getmax ();
};

template <class T>
T mypair<T>::getmax ()
{
  T retval;
  retval = a>b? a : b;
  return retval;
}

int main () {
  mypair <int> myobject (100, 75);
  cout << myobject.getmax();
  return 0;
}

注意语法(之所以这样的语法 scope避免重复
template <class T> T mypair<T>::getmax ()
第一个T是模板参数,第二个T是函数返回值,第三个T用来标记实例化的类(避免命名冲突)

模板特化(template specialization)

可以在模板参数为不同类型时,修改模板的定义,这个被称为模板特化
例如mycontainer是一个模板类,只存一个元素,且只有一个函数increase,我们想在存char时增加一个大写函数uppercase,例子如下:

// template specialization
#include <iostream>
using namespace std;

// class template:
template <class T>
class mycontainer {
    T element;
  public:
    mycontainer (T arg) {element=arg;}
    T increase () {return ++element;}
};

// class template specialization:
template <>
class mycontainer <char> {
    char element;
  public:
    mycontainer (char arg) {element=arg;}
    char uppercase ()
    {
      if ((element>='a')&&(element<='z'))
      element+='A'-'a';
      return element;
    }
};

int main () {
  mycontainer<int> myint (7);
  mycontainer<char> mychar ('j');
  cout << myint.increase() << endl;
  cout << mychar.uppercase() << endl;
  return 0;
}

特化的语法 template <> class mycontainer <char> { ... };,模板参数列表为空,这是因为所有的参数都被特化了,需要跟在类名后,表明这是一个特化,注意不同

template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };

第一个是通用类模板,第二个是特化
当我们声明特化时,必须定义想要的所有成员,即使是和通用类一样的,因为特化类和模板类没有继承关系

标签:const,CVector,int,成员,II,Classes,cpluscplus,class,函数
From: https://www.cnblogs.com/xiaoweing/p/17421005.html

相关文章

  • III.追想 题解
    原题链接我第一次出的一道比较正经的菜题,欢迎大家来切哦。感谢魔法少女老干妈GM_Joanna_的支持对于操作1,3:注意到1e9的数据至多5此操作就能把一个位置变为0,这个次数可视为常数。考虑每个位置暴力改,也只会递归\(5\timesn\logn\)次。对于3操作,考虑最坏的情况,每......
  • 剑指 Offer 58 - II. 左旋转字符串
    剑指Offer58-II.左旋转字符串</br></br>题目:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。示例1:输入:s="abcdefg",k......
  • WinServer2008下IIS8如何给网站配置域名/IP来访问
    Windows2008下IIS7主机头如何配置,IIS7主机头编辑绑定设置Windows2008r2搭建网站服务器,对于IIS6如何添加主机头,小编之前介绍了方法。下面本经验演示一下IIS7怎么添加主机头,如何配置网站添加主机头设置1.主机头查看,在“开始---运行”中输入inetmgr命令之后,点击“确定”按钮即......
  • Nas Docker 安装个人记账web项目:firefly_iii &beancount-gs
    NasDocker安装个人记账web项目:firefly_iii&beancount-gs1.经过搜索以及GPT的询问,通过预览界面感觉firefly_iii官方示例demo:https://demo.firefly-iii.org/官方安装文档:https://docs.firefly-iii.org/firefly-iii/installation/docker/本人采用的是群晖Nasdocker安装:这个......
  • LeetCode 113. 路径总和 II
    题目链接:LeetCode113.路径总和II题意:给你二叉树的根节点root和一个整数目标和targetSum,找出所有从根节点到叶子节点路径总和等于给定目标和的路径。解题思路:与LeetCode112.路径总和相似,在遍历过程中,记录遍历过的每一个点即可。递归法递归代码:varres[][]intva......
  • 记一次将 .netcore 项目用 IIS 进程调试
    环境:win10,VisualStudio2022 在.netframework年代,我们都习惯用iis进程调试代码。因为用F5调试代码效率太低下。现在.netcore时代,这种好习惯可不能丢。简单记录一下,我的操作过程。 1.首先用IIS挂载网站,看能不能把发布的好的网站跑起来2.其次用IIS增加网站,......
  • 海贝R2 II wifi与蓝牙冲突
    入手了这个海贝R2二代播放器后发现以下几点问题:1、蓝牙和wifi同时开启,会导致蓝牙出现问题,关不掉蓝牙也连不上蓝牙。需要重启机器直后再重新连接蓝牙。所以启用wifi的模式下,最好是连接有线耳机。2、如果在播放歌曲的时候打开录音机,此时机器会自动停止播放音乐,之后继续播放音乐会重......
  • #yyds干货盘点# LeetCode程序员面试金典: 二叉树的层序遍历 II
    1.简述:给你二叉树的根节点root,返回其节点值自底向上的层序遍历。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 示例1:输入:root=[3,9,20,null,null,15,7]输出:[[15,7],[9,20],[3]]示例2:输入:root=[1]输出:[[1]]示例3:输入:root=[]输出:[]2.代码实现:classSolution......
  • IIS安装与配置
    一、环境介绍WindowsServer201964位标准版二、IIS安装2.1、打开服务器管理器,单击添加角色和功能在WindowsServer2019服务器管理中,点击角色和功能。2.2、打开添加角色和功能向导】对话框,开始安装默认选择,直接下一步。2.3、打开安装类型选项卡安装类型,选择......
  • PB6接SCL,PB7接SDA,IIC通信访问 24C02, STM32F103的HAL版本
    在野火霸道板子上,已有at24c02,256个字节。/*测试代码*/staticuint8_tflag;uint8_tdata_w[]={1,2,3,4,5};uint8_tdata_r[5]={0};AT24CXX_Init();//初始化IIC接口flag=AT24CXX_Check();//检查器件if(flag==0){u16Writ......