小奥的学习笔记

  • Home
  • Learning & Working
    • Speech Enhancement Notes
    • Programming language
    • Computer & DL
    • MOOC
  • Life
    • Life Time
    • Thinking & Comprehension
    • Volunteer
    • Plan
    • Travel
  • Footprints
  • GuestBook
  • About
    • About Me
    • 个人履历
    • 隐私策略
  1. 首页
  2. Study-notes
  3. Programming language
  4. C/C++
  5. 正文

C++面向对象程序设计课程笔记(第四周)

2018年9月11日 1300点热度 0人点赞 0条评论

第一节 运算符重载的基本概念

C++预定义的运算符,只能用于基本数据类型的运算。基本数据类型包括:整型、实型、字符型、逻辑型等。

在数学上,两个复数可以直接进行+、-运算,但是在C++中,直接将+、-用在复数对象是不允许的。

有时候也会希望让对象也能通过运算符进行运算,这样代码更简洁、更容易理解,这个时候就需要运算符的重载了。

运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象。

它的实质是函数重载。可以重载为普通函数,也可以成员函数。

把含运算符的表达式转换成对运算符函数的调用,把运算符的操作数转换成运算符函数的参数。

运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

运算符重载的形式:

返回值类型 operator 运算符(形参表)

{

//函数体

}

如下例:

class Complex{

public:

double real,imag;

Complex(double r = 0.0, double i = 0.0):real(r),imag(i){ }

Complex operator-(const Complex & c);

};

Complex operator+(const Complex &a, const Complex &b){

return Complex(a.real + b.real,a.imag + b.imag);//返回一个临时对象

}

Complex Complex::operator-(const Complex &c)

{

return Complex(real - c.real, imag - c.imag);//返回一个临时对象

}

int main(){

Complex a(4,4),b(1,1),c;

c = a + b;//等价于c = operator+(a,b);

cout << c.real <<"," <<c.imag << endl;

cout<<(a-b).real << "," << (a-b).imag << endl;

//a-b等价于a.operator-(b)

return 0;

}注意:重载为成员函数时,参数个数为运算符目数减一(另一个就是我调用这个成员函数的对象);重载为普通函数时,参数个数为运算符目数。

 

第二节 赋值运算符的重载

1.赋值运算符“=”的重载

有时候希望赋值运算符两边的类型可以不匹配,此时就需要重载赋值运算符“=”。赋值运算符“=”只能重载为成员函数。

例程如下:

class String{

private:

char * str;//指向动态分配的数组

public:

String():str(new char[1]){str[0] = 0;}

const char * c_str(){return str;};

String & operator = (const char*s);

String::~String(){delete [] str;}

};

String &String::operator = (const char *s)

{

//重载=以使obj="hello"能够成立

delete[] str;

str = new char[strlen(s)+1];

strcpy(str,s);

return * this;

}

 

int main()

{

String s;

s = "Good luck,";//等价于s.operator=("Good Luck,");

cout<<s.c_str()<<endl;

//String s2 = "hello!";//这条语句不注释掉就会出错

s = "Shenzhou 8!";//等价于s.operator=("Shenzhou 8!");

cout <<s.c_str()<<endl;

return 0;

}

对重载函数进行解释。首先把str给delete掉,然后给str重新分配一个空间,大小为s字符串的大小+1,然后把s的值复制给str,返回一个本身的引用。要注意,“=”已经被重载,再编写String s2 = "hello!"后,=已经不是赋值语句,所以必然会出错。

2.相关其它内容

如下代码:

class String{

private:

char * str;//指向动态分配的数组

public:

String():str(new char[1]){str[0] = 0;}

const char * c_str(){return str;};

String & operator = (const char*s){

delete [] str;

str = new char[strlen(s)+1];

strcpy(str,s);

return * this;

};

String::~String(){delete [] str;}

};

我们在主函数里面要实现下面功能:

String S1,S2;

S1="this";

S2="that";

S1=S2;

在没有重载“=”的时候,S1=S2也可以编译通过,因为它们类型完全相同的。但是,这个“=”会使S1每一点都和S2一样。那么,这会有什么问题呢?让我们一步一步分解来看。

首先执行String S1,S2;S1="this";S2="that";那么就会实现这样的效果:

图2.1 S1="this";S2="that";

再执行S1=S2,我们发现成了这个样子:

图2.2 S1=S2的结果

    即S1实际上是指向了S2,两者实际上只指向的一个,原来S1的空间失去了指向,与我们想让S1中的内容(所指向的空间的内容)和S2一样的想法完全不一样。

如果S1对象消亡,析构函数将释放 S1.str 指向的空间,则S2消亡时还要释放一次,相当于delete S2了两次。

如果执行S1="other",会导致S2.str指向的地方被delete掉。所以重载之后,可以避免这样的问题。

考虑下面的语句:

String s;

s = "Hello";

s = s;

会有什么问题呢?我们重新看重载“=”的成员函数,发现函数第一句和就是把等号左侧对象的空间给delete掉了,这在普通的语句下没有什么问题,但是在这里,赋给等号左边对象值的那个对象也是它本身,这样delete掉之后,后面的strcpy函数就无法复制正确的值给左侧的对象了。为了解决这个问题,需要在这个重载成员函数的函数体开头添加以下语句:

if(this == &s)

return *this;

接下来对operator=的返回值类型进行讨论。当对运算符进行重载的时候,好的风格是应该尽量保留运算符原来的特性。

我们考虑a=b=c,若是void,那么b=c返回值类型就是void,就没办法再执行a=操作了,所以可以用String类型。

再考虑(a=b)=c。先执行a=b,在C++里面,执行=的返回值是左侧元素的引用,所以(a=b)的结果是一个a的引用,对a的引用赋值为c,那么这个b毫无用处。因此不能用String,而是用String &这样一个引用格式。this是当前对象的地址,那么*this就是当前对象,这解释了为什么要用*this的原因。

为String类编写复制构造函数的时候,会面临和“=”同样的问题(两个对象指向同一个空间),用他同样的方法处理:

String(String &s)

{

str = new char[strlen(s.str)+1];

strcpy(str,s.str);

}

关于浅拷贝和深拷贝待补充

第三节 运算符重载为友元

一般情况下,将运算符重载为类的成员函数是较好的选择。但有时,重载为成员函数并不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

如下代码:

class Complex

{

double real,imag;

public:

Complex(double r, double i):real(r),imag(i){};

Complex operator+(double r);

};

Complex Complex::operator+(double r)

{

return Complex(real+r,imag);

}

经过重载以后,c=c+5有意义,相当于c=c.operator+(5)

但是,c=5+c就会出错。所以,为了使得上述表达式成立,需要将+重载为普通函数,这样c=5+c就可以通过了。但是普通函数又不能访问私有成员,即不能计算c=c+5。这样,我们只能用重载为友元函数了。如下:

class Complex

{

double real,imag;

public:

Complex(double r, double i):real(r),imag(i){};

friend Complex operator + (double r, const Complex & c);

};

Complex Complex::operator+(double r)

{

return Complex(real+r,imag);

}

第四节 运算符重载实例:可变长整型数组

如下代码:

int main(){//要编写可变长整型数组类,使之能如下使用

CArray a;//开始数组是空的

for(int i = 0; i < 5;++i)

a.push_back(i);//要用动态分配的内存来存放数组元素需要一个指针成员变量

CArray a2,a3

a2 = a;//要重载"=",把a中的值复制给a2

for(int i = 0; i<a.length;++i)

cout<<a2[i]<<"";//要重载[],因为a2原来是一个对象

a2 = a3;//a2是空的,因为原来的空间被释放了

for(int i = 0;i<a2.length;++i)//a2.length()返回0

cout<<a2[i]<<"";

cout<<endl;

a[3]=100;

CArray a4(a);

CArray A4(A);//要自己写复制构造函数

for(int i = 0; i<a4.length;++i)

cout<<a4[i]<<"";

return 0;

}

class CArray{

int size;//数组元素的个数

int *ptr;//指向动态分配的数组

public:

CArray(int s = 0);//s代表数组元素的个数

CArray(CArray &a);

~CArray();

void push_back(int v);//用于在数组尾部添加一个元素v

CArray & operator=(const CArray &a);

//用于数组对象间的赋值

int length(){return size;}//返回数组元素个数

int & CArray::operator[](int i)

//返回值不能为int,不支持a[i]=4,双目运算符,但是在类内,只有一个运算符

{//用以支持根据下标访问数组元素,如n=a[i]和a[i]=4这样的语句

return ptr[i];

}

};

 

CArray::CArray(int s):size(s)

{//构造函数

if(s ==0)

ptr = NULL;

else

ptr = new int[s];

}

CArray::CArray(CArray &a){//复制构造函数,要实现深复制

if(!a.ptr){

ptr = NULL;

size = 0;

return;

}

ptr = new int [a.size];

memcpy(ptr,a.ptr,sizeof(int)*a.size);

size = a.size;

}

CArray::~CArray()

{

if(ptr) delete [] ptr;

}

CArray & CArray::operator=(const CArray &a)//深拷贝,而不是浅拷贝

{//赋值号的作用是使“=”左边对象里存放的数组,大小和内容都和右边的对象一致。

if(ptr == a.ptr)

return *this;//防止前文所述的出错

if(a.ptr == NULL){//如果a里面的数组是空的

if(ptr) delete[] ptr;

ptr = NULL;

size =0;

return *this;

}

if(size <a.size){//如果原有空间不够,则新建一个足够大的空间

//如果足够大,就不分配新的空间直接执行if后面的语句

if(ptr)

delete [] ptr;

ptr = new int[a.size];

}

memcpy(ptr,a.ptr,sizeof(int)*a.size);//空间大小为数目*一个int的字节数

size = a.size;

return *this;

}

void CArray::push_back(int v)

{//在数组尾部添加一个元素。先判断原来是否有元素,如果有元素,就新建一个临时空间,

//然后把原来的元素复制过来,然后删除原来的空间,然后把ptr指针指向了tmpPtr这个临时空间

//这个元素非常浪费资源

if(ptr){

int *tmpPtr = new int[size+1];//重新分配空间

memcpy(tmpPtr,ptr,sizeof(int)*size);//拷贝原数组内容

delete[] ptr;

ptr = tmpPtr;

}

else

ptr = new int[1];//数组原来是空的

ptr[size++] = v;//加入新的数组元素

}

 

第五节 流插入运算符和流提取运算符的重载

问题1:cout<<5<<”this”为什么能够成立?

问题2:cout是什么?<<为什么能用在cout上?

1.流插入运算符的重载

cout是在iostream中定义的ostream类的对象。之所以<<能用在cout上是因为,在iostream中对<<进行了重载。

考虑到我们要执行对5的操作也要执行对this的操作,如果我们定义的重载函数返回值为void或者int类型,都无法保证后面的两次甚至更多输出能够成立。但是如果我们将其定义为ostream类型的话,那么对5操作后,还是ostream类型,那么就可以继续对this操作了,因此要把返回值类型定义为ostream。即下面的格式:

ostream & ostream::operator<<(int n)

{

//代码

return *this;

}

 

ostream & ostream::operator<<(const char *s)

{

//代码

return *this;

}

cout<<5<<”this”本质上的函数调用形式是:

cout.operator<<(5).operator<<("this");

例1:假定下面程序输出为5hello,该补写些什么?

class CStudent{

public: int nAge;

};

int main(){

CStudent s;

s.nAge = 5;

cout << s <<"hello";

return 0;

}

需要重载左移运算符,如下:

由于<<已经在ostream中成员函数重载,因此在这里我们只能定义为全局函数进行重载,所以需要两个参数。如下面代码所示,o其实就是对象cout。

ostream & operator<<(ostream & o, const CStudent & s){

o<<s.nAge;

return o;

}

例题2:假定c是Complex复数类对象,现在希望写"cout << c;",就能以"a+bi"的形式输出c的值;写“cin>>c”就能从键盘接受“a+bi”形式的输入,并且使得c.real = a,c.imag = b。

int main(){

Complex c;

int n;

cin >> c >> n;

cout << c << "," <<n;

return 0;

}

程序运行结果可以如下:

输入:13.2+133i 87

输出:13.2+133i,87

代码如下:

#include <iostream>

#include <string>

#include <cstdlib>

using namespace std;

class Complex{

double real,imag;

public:

Complex(double r=0,double i =0):real(r),imag(i){};

friend ostream & operator<<(ostream & os, const Complex &c);

friend istream & operator >>(istream & is, Compex & c);

};

 

ostream & operator <<(ostream & os, const Complex & c)

{

os<<c.real<<"+"<<c.imag<<"i";

return os;

}

istream & operator >>(istream & is, Complex & c)

{

string s;

is >> s;//将"a+bi"作为字符串读入,中间不能有空格

int pos = s.find("+",9);

string sTmp = s.substr(0,pos);//分离出代表实部的字符串

c.real = atof(sTmp.c_str());//atof库函数能讲const char*指针指向的内容转换成float

sTmp = s.substr(pos+1,s.length()-pos-2);//分离出代表虚部的字符串

c.imag = atof(sTmp.c_str());

return is;

}

int main(){

Complex c;

int n;

cin >> c >> n;

cout << c << "," <<n;

return 0;

}

选择题:重载“<<”用于将自定义的对象通过cout输出时,以下说法正确的是:

C 可以将“<<”重载为全局函数,第一个参数以及返回值,类型都是ostream &。

第六节 类型转换运算符的重载

代码如下:

#include <iostream>

 

using namespace std;

class Complex{

double real,imag;

public:

Complex(double r=0,double i =0):real(r),imag(i){};

operator double(){return real;}//类型转换运算符重载时不写返回值类型,因为返回值类型就是它本身

};

int main()

{

Complex c(1.2,3.4);

cout << (double)c <endl;//输出1.2

double n = 2 + c;//c被自动用类型转换运算符,等价与double n = 2+c.operator double()

cout << n;//输出3.2

}

第七节 自增自减运算符的重载

自增运算符++、自减运算符--有前置/后置之分,为了区别所重载的是前置运算符还是后置运算符,C++规定:

(1)前置运算符作为一元运算符重载:

重载为成员函数时:

T & operator++()

T & operator—()

重载为全局函数时:

T1 & operator++(T2)

T1 & operator—(T2)

(2)后置运算符作为二元运算符重载,多写一个没用的参数int:

重载为成员函数时:

T operator++(int)

T operator--(int)

重载为全局函数时:

T1 operator++(T2, int)

T1 operator--(T2, int)

但是在没有后置运算符重载而有前置运算符重载的情况下,在vs中,obj++也调用前置重载,而dev则令obj++编译出错。

例题1:

int main()

{

CDemo d(5);

cout<(d++) <<",";//等价于d.operator++(0);

cout << d << ",";

cout << (++d) << ",";//等价于d.operator++();

cout << d << endl;

cout << (d--) << ",";//等价于d.operator--(0);

cout << d << ",";

cout << (--d) << ",";//等价于d.operator--();

cout << d << endl;

return 0;

}

输出结果:

5,6,7,7

7,6,5,5

如何编写CDemo?

class CDemo{

int n;

public:

CDemo(int i=0):n(i){}

CDemo & operator++();//前置形式++n返回值就是n的引用,所以这里要用引用

CDemo operator++(int);//后置形式,n++返回的是一个临时变量,所以这里不能用引用

operator int(){return n;}

friend CDemo & operator--(CDemo &);

friend CDemo operator--(CDemo &, int);

};

 

CDemo & CDemo::operator++():

{//前置

n ++;

return *this;

}

CDemo CDemo::operator++(int k):

{//后置

CDemo tmp(*this);//记录修改前的对象

n++;

return tmp;//返回修改前的对象

}//s++即为s.operator++(0);

CDemo & operator--(CDemo & d){//前置

d.n--;

return d;

}

CDemo operator--(CDemo &d, int){//后置

CDemo tmp(d);

d.n--;

return tmp;

}//s--即为operator--(s,0)

可以看出,前置操作因为少一个步骤,所以运算速度快于后置操作。所以提倡写++i。

运算符重载的注意事项:

1.C++不允许定义新的运算符;

2.重载后运算符的含义应该符合日常习惯;

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

4.以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;

5.重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须生命为成员函数。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: C++学习笔记 C++慕课
最后更新:2018年9月11日

davidcheung

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

搜索
欢迎关注我的个人公众号
最新 热点 随机
最新 热点 随机
DEEPFILTERNET:一种基于深度滤波的全频带音频低复杂度语音增强框架 奥地利匈牙利九日游旅程 论文阅读之Study of the General Kalman Filter for Echo Cancellation 小奥看房之鸿荣源珈誉府 杭州往返旅途及西溪喜来登和万怡的体验报告 2022年的第一篇碎碎念
奥地利匈牙利九日游旅程小奥看房之鸿荣源珈誉府论文阅读之Study of the General Kalman Filter for Echo CancellationDEEPFILTERNET:一种基于深度滤波的全频带音频低复杂度语音增强框架
2011年12月voa常速英语mp3打包下载 课上阅读材料有感 航空节开幕式观后自己的感悟 Ultraman China第四话——《Shine的名字》(あのウルトラマンの第4話を調べます) 下学期微机课的第一篇日志 新建济南至莱芜高速铁路工程(不含先期开工段)站前工程施工招标中标候选人公示
标签聚合
算法 leetcode 高中 python学习 Java 生活 linux 学习 鸟哥的linux私房菜 Python
最近评论
davidcheung 发布于 5 个月前(02月09日) The problem has been fixed. May I ask if you can s...
tk88 发布于 5 个月前(02月07日) Hmm is anyone else having problems with the pictur...
cuicui 发布于 9 个月前(10月20日) :wink:
niming 发布于 10 个月前(09月19日) 同级校友,能刷到太巧了
davidcheung 发布于 2 年前(08月16日) 我得找一下我之前整理的word文档看一下,如果找到了我就更新一下这篇文章。
Nolan 发布于 2 年前(07月25日) 您的笔记非常有帮助。贴图不显示了,可以更新一下吗?
davidcheung 发布于 3 年前(06月19日) 到没有看webrtc的代码。现在主要在看我们公司的代码了。。。只是偶尔看一看webrtc的东西。。。
aobai 发布于 3 年前(03月13日) gain_change_hangover_ 应该是每三个block 只能够调整一次,这样保证每帧...
匿名 发布于 5 年前(12月30日) 烫
小奥 发布于 5 年前(12月12日) webRTC里面的NS本身我记得就是在C++里面呀

COPYRIGHT © 2025 小奥的学习笔记. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

陕ICP备19003234号-1

鲁公网安备37120202000100号