C++程序设计(三)
1. 运算符重载
目的:对抽象数据类型也能够直接使用C++提供的运算符。使得程序更简洁,代码更容易理解。
运算符重载的实质是函数重载
返回值类型 operator 运算符(形参表)
{
……
}
运算符可以被重载为普通函数(友元函数),也可以被重载为类的成员函数
// 运算符重载为普通函数(友元函数)
class Complex {
public:
Complex(double r = 0.0, double i = 0.0){
real = r;
imaginary = i;
}
friend Complex operator+ (const Complex & a, const Complex & b);
private:
double real; // real part
double imaginary; // imaginary part
};
Complex operator+ (const Complex & a, const Complex & b)
{
return Complex(a.real + b.real, a.imaginary + b.imaginary);
} // “类名(参数表)” 就代表一个对象
Complex a(, ), b(, ), c;
c = a + b;
重载为普通函数时, 参数个数为运算符目数.
// 运算符重载为成员函数
class Complex2 {
public:
Complex2(double r = 0.0, double m = 0.0) :
real(r), imaginary(m) { } // constructor
Complex2 operator+ (const Complex2 &); // addition
Complex2 operator- (const Complex2 &); // subtraction
private:
double real; // real part
double imaginary; // imaginary part
};
// Overloaded addition operator
Complex2 Complex2::operator+(const Complex2 & operand2) {
return Complex2(real + operand2.real,
imaginary + operand2.imaginary);
}
// Overloaded subtraction operator
Complex2 Complex2::operator- (const Complex2 & operand2){
return Complex2(real - operand2.real,
imaginary - operand2.imaginary);
}
Complex2 a(, ), b(, ), c;
c = a + b;
c = b - a;
重载为成员函数时, 参数个数为运算符目数减一
- 一般情况下,单目运算符最好重载为类的成员函数,双目运算符则最好重载为类的友元函数;
- 双目运算符"=、()、[]、->"不能重载为类的成员函数。
- 类型转换函数只能定义为一个类的成员函数。 C++提供4个类型转换函数:reinterpret_cast(在编译期间实现转换)、const_cast(在编译期间实现转换)、stactic_cast(在编译期间实现转换)、dynamic_cast(在运行期间实现转换,并可以返回转换成功与否的标志)。
- 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。
- 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。
- 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部 类型的对象,该运算符函数必须作为一个友元函数来实现。
- 当需要重载运算符具有可交换性时,选择重载为友元函数。
- 除了类属关系运算符”.“、成员指针运算符”.*“、作用域运算符”::“、sizeof运算符和三目运算符”?:“以外,C++中的所有运算符都可以重载。
- 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。
- 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
- 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。
- 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。
- 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。
以赋值运算符重载为例
class String{
private:
char *str;
public:
String() : str(NULL){}
const char * c_str(){ return str; }
char * operator=(const char *s);
~String();
};
char * String::operator = (const char * s){
if (str) delete[] str;
if (s) { //s不为NULL才会执行拷贝
str = ];
strcpy(str, s);
}
else
str = NULL;
return str;
}
String::~String(){
if (str) delete[] str;
};
int main(){
String s;
s = "Good Luck";
cout << s.c_str() << endl;
//String s2 = "hello!"; //这条语句要是不注释掉就会出错
s = "Shenzhou8!";
cout << s.c_str() << endl;
;
}
这里注意一点:非const 变量不能赋值给const,const指针不能赋值给非const指针。
浅复制和深复制
浅复制:执行逐个字节的复制工作,默认复制构造函数,会导致复制结果为两个都指向同一片内存。
深复制:将一个对象中指针变量指向的内容,复制到另一个对象中指针成员对象指向的地方
String & operator = (const String & s) {
if(str) delete [] str;
str= ];
strcpy(str, s.str);
return * this;
}
为避免自身复制中出现的问题
String & String::operator = (const String & s){
if(str== s.str)return * this;
if(str) delete [] str;
if(s.str) {//s.str不为NULL才会执行拷贝
str= ];
strcpy( str,s.str);
}
else
str= NULL;
return * this;
}
2. 可变长数组
int main() { //要编写可变长整型数组类,使之能如下使用:
CArray a; //开始里的数组是空的
; i < ; ++i)
a.push_back(i);
CArray a2, a3; // 要用动态分配的内存来存放数组元素,需要一个指针成员变量
a2 = a; // 要重载“=”
; i < a.length(); ++i)
cout << a2[i] << " "; // 要重载“[ ]”
a2 = a3; //a2是空的
; i < a2.length(); ++i) //a2.length()返回0
cout << a2[i] << " ";
cout << endl;
a[] = ;
CArray a4(a); // 要自己写复制构造函数
; i < a4.length(); ++i)
cout << a4[i] << " ";
;
}
class CArray {
int size; //数组元素的个数
int *ptr; //指向动态分配的数组
public:
CArray(); //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) //要实现a[i] = 4,只有返回值为引用类型的函数才可以作为左值使用
{//用以支持根据下标访问数组元素,
// 如n = a[i] 和a[i] = 4; 这样的语句
return ptr[i];
}
};
CArray::CArray(int s) :size(s)
{
)
ptr = NULL;
else
ptr = new int[s];
}
CArray::CArray(CArray & a) {// 深复制
if (!a.ptr) {
ptr = NULL;
size = ;
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) //防止a=a这样的赋值导致出错
return *this;
if (a.ptr == NULL) { //如果a里面的数组是空的
if (ptr) delete[] ptr;
ptr = NULL;
size = ;
return *this;
}
if (size < a.size) { //如果原有空间够大,就不用分配新的空间
if (ptr)
delete[] ptr;
ptr = new int[a.size];
}
memcpy(ptr, a.ptr, sizeof(int)*a.size);
size = a.size;
return *this;
} // CArray & CArray::operator=( const CArray & a)
void CArray::push_back(int v)
{ //在数组尾部添加一个元素
if (ptr) {
]; //重新分配空间
memcpy(tmpPtr, ptr, sizeof(int)*size); //拷贝原数组内容
delete[] ptr;
ptr = tmpPtr;
}
else //数组本来是空的
ptr = ];
ptr[size++] = v; //加入新的数组元素
}
3. 流插入运算符和流提取运算符的重载
cout 是在 iostream 中定义的,ostream 类的对象。
cin 是在 iostream 中定义的,istream 类的对象。
class CStudent{
public:
int nAge;
};
ostream & operator<<( ostream & o,const CStudent & s){
o << s.nAge ;
return o;
}
int main(){
CStudent s ;
s.nAge = ;
cout << s <<"hello";
;
}
4. 自加/自减运算符的重载
自加++/自减--运算符有前置/后置之分
前置运算符作为一元运算符重载
重载为成员函数:
T &operator++();
T &operator--();
重载为全局函数:
T &operator++(T &);
T &operator—(T &);
后置运算符作为二元运算符重载
重载为成员函数:
T operator++(int);
T operator--(int);
重载为全局函数:
T operator++(T &, int);
T operator--(T &, int);
int main(){
CDemo d();
cout << (d++) << ","; //等价于d.operator++(0);
cout << d << ",";
cout << (++d) << ","; //等价于d.operator++();
cout << d << endl;
cout << (d--) << ","; //等价于operator--(d,0);
cout << d << ",";
cout << (--d) << ","; //等价于operator--(d);
cout << d << endl;
;
}
class CDemo{
private:
int n;
public:
CDemo() :n(i) { }
CDemo& operator++(); //用于前置++形式
CDemo operator++(int); //用于后置++形式
operator int() { return n; }
friend CDemo& operator--(CDemo&); //用于前置--形式
friend CDemo operator--(CDemo&, int); //用于后置--形式
operator int() { return n; }// 类型强制转换运算符,不能写返回值类型
};
CDemo& CDemo::operator++() { //前置++
n++;
return *this;
}
CDemo CDemo::operator++(int k) { //后置++
CDemo tmp(*this); //记录修改前的对象
n++;
return tmp; //返回修改前的对象
}
CDemo& operator--(CDemo& d) { //前置--
d.n--;
return d;
}
CDemo operator--(CDemo& d, int) { //后置--
CDemo tmp(d);
d.n--;
return tmp;
}
C++程序设计(三)的更多相关文章
- CUDA程序设计(三)
算法设计:基数排序 CUDA程序里应当尽量避免递归,因而在迭代排序算法里,基数排序通常作为首选. 1.1 串行算法实现 十进制位的基数排序需要考虑数位对齐问题,比较麻烦.通常实现的是二进制位的基数排序 ...
- JavaScript高级程序设计(三):基本概念:数据类型
特别注意:ECMAScript是区分大小写的. 一.变量 1.ECMAScript的变量是松散型的.所谓松散型就是可以用来保存任何类型的数据.即每个变量仅仅是一个用于保存值的占位符而已.定义变量时要使 ...
- javascript 高级程序设计 三
Sorry,前两张介绍的主题还是JavaScript,而第一章介绍了JavaScript和ECMAScript区别,所以前两章介绍的主题应该改为ECMAScript,但是 标题就不改了因为现在人们习惯 ...
- 20145308刘昊阳 《Java程序设计》第2周学习总结
20145308刘昊阳 <Java程序设计>第2周学习总结 教材学习内容总结 第三章 基础语法 3.1 类型.变量与运算符 类型 基本类型 整数(short/int/long) short ...
- 2018-2019-2 20175227张雪莹《Java程序设计》 实验二《Java面向对象程序设计》
2018-2019-2 20175227张雪莹<Java程序设计> 实验二<Java面向对象程序设计> 实验报告封面 课程:Java程序设计 班级:1752班 姓名:张雪莹 学 ...
- 054 Python程序设计思维
目录 一.单元开篇 二.计算思维与程序设计 2.1 计算思维 2.1.1 第3种人类思维特征 2.1.2 抽象和自动化 2.1.3 计数求和:计算1-100的计数和 2.1.4 圆周率的计算 2.1. ...
- 【C语言】第5章 循环结构程序设计
第5章 循环结构程序设计 三种基本循环控制结构 使用while语句实现循环 先判断条件表达式,后执行循环体语句 while (循环条件表达式) { 循环体 } 用do-while语句实现循环 先无条件 ...
- web前端开发必懂之一:JS继承和继承基础总结
首先,推荐一篇博客豪情的博客JS提高: http://www.cnblogs.com/jikey/p/3604459.html ,里面的链接全是精华, 一般人我不告诉他; 我们会先从JS的基本的设计模 ...
- 自学java坎坷之路——20155312张竞予
20155312 2006-2007-2 <Java程序设计>第一周学习总结 教材学习内容总结 第一周并没有在课堂上对教材内容进行学习,学习内容概括如下 课程分数构成,其中包括课堂测验(每 ...
- Java学习笔记之面向对象、static关键字
一周Java学习总结 今天就总结理清一下关于面向对象和面向过程的程序设计的一些不同特点,以及讲下static关键字. 面向对象 现在接触的Java是面向对象的,现在的程序开发几乎都是以面向对象为基础的 ...
随机推荐
- SOAPUI测试步骤之断言测试(Assertion TestStep)
什么是没有办法验证结果的测试?soapUI提供了两种方法来测试断言:断言TestSteps现在断言一步步测试(PRO版本).The Assertion TestStep,扩展了断言处理和管理的想法.此 ...
- BZOJ3072 : [Pa2012]Two Cakes
考虑DP,设$f[i][j]$表示考虑了$a[1..i]$和$b[1..j]$的最小代价. 若$a[i]==b[j]$,则$f[i][j]=\min(f[i-1][j],f[i][j-1])+1$. ...
- [linux]ubuntu 下安装RMySQL包
http://downloads.mysql.com/docs/connector-odbc-en.pdf http://blog.csdn.net/ixidof/article/details/59 ...
- topcoder SRM 610 DIV2 DivideByZero
题目的意思是给你一组数,然后不断的进行除法(注意是大数除以小数),然后将得到的结果加入这组数种然后继续进行除法, 直到没有新添加的数为止 此题按照提议模拟即可 注意要保持元素的不同 int Count ...
- AFNetworking 2.0 出现Use of undeclared identifier AFURLSessionManager错误
当向下面使用时会出现错误 #import "AFNetworking.h" #import "AFURLSessionManager.h" AFURLSessi ...
- ios retain 与 copy 的区别
.retain 与copy区别 retain 的仅仅是引用计数加1,但是并没有创建新的对象.它们的指针是指向相同的内存地址. copy 是创建一个新的对象作为原来对象的副本,新创建出来的引用计数并没有 ...
- Android --时间控件的使用
1. mian.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: ...
- Linux_记录ping命令的日志包括时间戳
while true; do ping -c 1 www.baidu.com | awk '{print "["strftime("%F %H:%M:%S")& ...
- Html5_移动前端不得不了解的html5 head 头标签
移动前端不得不了解的html5 head 头标签 本文主要内容来自一丝的常用的 HTML 头部标签和百度FEX的HTML head 头标签. 移动端的工作已经越来越成为前端工作的重要内容,除了平常 ...
- 使用C#将HTML文本转换为普通文本,去掉所有的Html标记(转)
using System; using System.Collections.Generic; using System.Linq; using System.Text; //首先需要导入命名空间 u ...